Alors que les entreprises utilisent les données pour optimiser et personnaliser les expériences des clients, il devient de plus en plus important de mettre en œuvre des services capables d'exécuter des modèles d'apprentissage automatique sur des quantités massives de données afin de générer rapidement des prédictions à grande échelle. Chez DoorDash, notre plateforme utilise les données pour alimenter des modèles qui conservent les résultats de recherche, attribuent des dashers, reconnaissent les fraudes, et plus encore. Cela ne serait possible qu'avec un service de prédiction robuste capable d'appliquer nos modèles aux données et de servir nos différents microservices qui s'appuient sur des informations basées sur les données.
Nous avons récemment mis en place le service de prédiction de nouvelle génération de DoorDash et l'avons baptisé service de prédiction Sibylle, d'après les oracles grecs qui avaient la réputation de faire des prédictions dans une frénésie extatique. prononçaient des prédictions dans une frénésie extatique.. Dans cet article de blog, vous découvrirez l'idéation, l'implémentation et le déploiement de Sibyl, ainsi que les étapes que nous avons suivies pour construire un service de prédiction capable de gérer un nombre massif d'appels par seconde sans transpirer. Si vous êtes intéressé par la façon dont les modèles sont intégrés au service, vous pouvez consulter cet article ici. Bien que Sibyl lui-même soit unique à DoorDash, les enseignements et les considérations que nous avons utilisés pour le construire peuvent être appliqués à presque n'importe quel service à haut débit et à faible latence. Si vous souhaitez en savoir plus sur l'ensemble de la plateforme ML, consultez cet article de blog : DoorDash's ML Platform - The Beginning (La plateforme ML de DoorDash - Le début)
Idéation et exigences : Le rôle du service de prédiction dans l'infrastructure d'apprentissage machine (ML)
Avant de commencer à coder, nous avons pris le temps de réfléchir au rôle exact de Sibyl dans l'écosystème de l'infrastructure ML de DoorDash, ainsi qu'à toutes les fonctionnalités nécessaires à ce nouveau service de prédiction.
En ce qui concerne son rôle, nous voulions que le service de prédiction Sibyl traite toutes les données réelles-temps et qu'il se concentre sur la prédictionen laissant les autres composants, tels que le calcul des caractéristiques, les pipelines d'apprentissage des modèles et le stockage des caractéristiques et des modèles, dans des services/stores distincts. La première chose que nous avons prise en compte était l'évolutivité et la latence, étant donné que nous nous attendions à des centaines de milliers de prédictions par seconde et que, pour convaincre d'autres services d'appeler notre service pour les prédictions, nous devions les rendre si rapides qu'il serait préférable d'appeler Sibyl plutôt que de laisser chaque service individuel faire ses prédictions lui-même.
Comment le service de prédiction Sibyl s'intègre dans l'écosystème de l'infrastructure ML :
Comme le montre la figure 1, toutes les prédictions proviennent d'autres services sous forme de requêtes gRPC, et Sibyl récupère les modèles et les caractéristiques dans des magasins indépendants (avec la possibilité d'envoyer les prédictions à Snowflake pour une évaluation hors ligne). Une extension future possible comprend un évaluateur de modèle séparé, qui peut être utilisé pour le calcul de la prédiction pure nécessaire pour les modèles complexes. Pour la V1, cependant, il sera inclus dans Sibyl.
En ce qui concerne les fonctionnalités requises, voici quelques points saillants :
- Prédictions de lots: Nous devrions permettre à chaque demande de prédiction de contenir un nombre variable d'ensembles de caractéristiques à prédire (remarque : un "ensemble de caractéristiques" est un ensemble de valeurs de caractéristiques sur lesquelles nous voulons prédire. Pour simplifier, on peut dire qu'un ensemble de caractéristiques est simplement une entrée sur laquelle nous voulons prédire). Les prédictions par lots sont essentielles, car elles permettent aux services clients d'envoyer et de récupérer de 1 à N prédictions en une seule fois, ce qui réduit considérablement le nombre d'appels nécessaires.
- Prédictions de l'ombre: En plus de faire des prédictions et de les renvoyer aux services clients, la possibilité de faire des prédictions fantômes de manière asynchrone en arrière-plan était essentielle. Souvent, avant de finaliser et de s'en tenir à un modèle particulier, les équipes peuvent avoir plusieurs modèles candidats et vouloir tester plusieurs modèles à la fois sur les mêmes données. En permettant aux équipes d'utiliser un modèle pour les prédictions officielles et de faire des prédictions asynchrones sur les mêmes données avec différents modèles, on leur donne la flexibilité et la puissance nécessaires pour analyser l'efficacité des différents modèles candidats.
- Recherche de caractéristiques et de modèles: Comme indiqué ci-dessus, Sibyl doit être en mesure de récupérer les caractéristiques et les modèles dans leurs magasins respectifs. Pour les caractéristiques, elles seraient récupérées lors de chaque prédiction, et pour les modèles, nous pourrions gagner du temps et de la puissance de calcul en récupérant d'abord tous les modèles au démarrage du service, puis en les mettant en cache en mémoire, ce qui éviterait de les charger à chaque demande.
Mise en œuvre : Aperçu général du service et points forts de la décision
Pour avoir une idée générale du fonctionnement du service, ainsi qu'un bref aperçu des éléments mobiles du service, voici à quoi ressemble le cycle de vie d'une demande typique :
Référence à la figure 2 :
- La demande arrive.
- Pour chaque modèle identifié dans la requête, nous récupérons le modèle ainsi que la configuration du modèle (qui contient des informations sur ce modèle, telles que toutes les fonctionnalités requises, les valeurs de repli par défaut pour les fonctionnalités et le type de modèle) à partir d'un cache en mémoire.
- Ensuite, nous parcourons les valeurs fournies dans la requête et nous recherchons s'il manque des valeurs de caractéristiques qui n'ont pas été fournies. Nous effectuons cette opération pour tous les modèles et tous les ensembles de caractéristiques en une seule fois, et nous stockons les valeurs dans une carte pour faciliter la consultation.
- Pour toutes les caractéristiques manquantes, nous essayons de récupérer les valeurs des caractéristiques dans le magasin de caractéristiques, qui est un cache Redis de valeurs de caractéristiques. Si elles sont toujours introuvables, nous définissons les valeurs des caractéristiques comme valeur par défaut dans la configuration du modèle.
- Maintenant que nous disposons de toutes les caractéristiques et de toutes les valeurs de caractéristiques requises pour les prédictions, nous effectuons de manière asynchrone une prédiction pour chaque ensemble de caractéristiques. Pour chaque modèle fantôme, nous lançons également une coroutine asynchrone, mais nous n'attendons pas que les résultats soient terminés pour continuer.
- Une fois toutes les prédictions effectuées, nous construisons un objet protobuf de réponse et le remplissons avec les prédictions, avant de renvoyer le protobuf au client.
Je voudrais maintenant mettre en lumière quelques décisions/détails que nous avons pris et qui méritent d'être soulignés :
Optimisation de la vitesse de prédiction à l'aide d'appels d'API natives
Les deux frameworks ML utilisés par Sibyl, LightGBM et Pytorch (si vous êtes curieux de savoir pourquoi nous avons choisi ces deux frameworks, vous pouvez consulter la page La plateforme ML de DoorDash - Le début ), ont des cadres d'API mis en œuvre dans une variété de langages de programmation différents. Cependant, afin d'optimiser la vitesse, nous avons décidé de stocker les modèles dans leur format natif, et de faire des appels de prédiction à ces frameworks en C++. Travailler en C++ nous a permis de minimiser le temps de latence pour effectuer les prédictions proprement dites. Nous avons utilisé l interface native Java (JNI) afin que le service, implémenté en Kotlin, puisse effectuer les appels de prédiction LightGBM et Pytorch, implémentés en C++.
Coroutines, coroutines et encore des coroutines
En raison des exigences élevées en matière d'évolutivité et de latence, l'une de nos principales priorités était de nous assurer que toutes les prédictions étaient effectuées en parallèle et que, lorsqu'ils attendaient que les fonctionnalités soient extraites du magasin de fonctionnalités, les threads effectuaient réellement des calculs (au lieu de se contenter d'attendre). Heureusement, le développement en Kotlin nous a donné le contrôle nécessaire sur les threads grâce à son implémentation intégrée des coroutines. Les coroutines de Kotlin ne sont pas liées à un thread spécifique, et la fonction de suspendent elles-mêmes pendant l'attente, ce qui signifie qu'elles ne retiennent pas le thread, permettant au thread d'effectuer un travail sur autre chose pendant l'attente. Bien qu'il soit possible d'implémenter un comportement similaire en Java en utilisant des callbacks, syntaxiquement, la création et la gestion des coroutines Kotlin sont beaucoup plus propres que les threads Java, ce qui facilite le développement multithread.
Déploiement : D'abord des tests de chargement, puis l'introduction dans la production
Réalisation d'un test
Nous avons décidé de tester les capacités de prédiction de Sibyl sur l'un des services les plus demandés de DoorDash. Le service de recherche de DoorDash a de nombreuses responsabilités, l'une d'entre elles étant de calculer quels restaurants vous montrer lorsque vous visitez le site doordash.com. Vous ne vous en rendez peut-être pas compte, mais chaque restaurant que vous voyez sur DoorDash doit être noté et classé au préalable, le score étant utilisé pour personnaliser votre expérience sur le site, en proposant différents restaurants à différentes positions sur le site pour différentes personnes (Figure 3).
Actuellement, la logique de classement du service de recherche est réalisée en interne et au sein du service lui-même. Nous avons donc décidé que lorsque le service de recherche s'apprêtait à classer un restaurant, il créait un thread asynchrone qui appelait également Sibyl. Cela nous a permis non seulement de vérifier que le service de prédiction fonctionne comme prévu, mais aussi de tester la charge du service avec précision. En outre, les appels asynchrones ont permis de s'assurer que les appels à Sibyl ne ralentissaient pas les points d'extrémité du service de recherche.
Sibyl a fini par traiter plus de 100 000 prédictions par seconde, wow ! Ce test a démontré que Sibyl était désormais prêt à gérer le débit requis pour nos services de production, et que les services de DoorDash pouvaient commencer à migrer leurs modèles pour appeler le service pour toutes les prédictions.
Modification de la taille des lots de requêtes et autres réglages pour optimiser le service
L'une des configurations avec lesquelles nous avons joué est la taille du lot pour chaque demande de prédiction. Étant donné que des centaines de magasins peuvent apparaître dans votre flux de magasins, le service de recherche classe en fait des centaines de magasins à la fois. Nous étions curieux de voir à quel point chaque demande serait plus rapide si, au lieu d'envoyer tous les magasins en même temps à Sibyl, nous divisions la demande en morceaux assez importants, de sorte qu'au lieu de prédire sur ~1000 magasins en même temps, Sibyl prédisait sur 50 magasins en 20 demandes distinctes.
Nous avons constaté que la taille optimale des blocs pour chaque demande était d'environ 100 à 200 magasins. Il est intéressant de noter que des blocs plus petits, tels que des blocs de 10 et 20 magasins, ont en fait aggravé la latence. Il y avait donc un juste milieu, illustrant le fait que si le nombre de magasins par requête était important, le service fonctionnait mieux lorsque les morceaux étaient de taille raisonnable. L'hypothèse est que si la taille des morceaux est trop petite, le nombre de requêtes augmente considérablement, ce qui entraîne une mise en file d'attente des requêtes et des temps de latence plus élevés. D'autre part, si les prédictions contiennent les 1000 magasins, la quantité de données à envoyer et à recevoir augmente considérablement et le délai de propagation entre le client et le service devient notre goulot d'étranglement. Cette constatation était en fait encourageante pour nous, car elle démontrait que nous avions efficacement mis en œuvre Sibyl pour exécuter les prédictions en parallèle et qu'à grande échelle, le service était capable d'effectuer des prédictions par lots substantielles sans problème.
Outre la fragmentation, la compression des requêtes a également été étudiée. Comme nous l'avons mentionné plus haut, l'un des problèmes posés par ces demandes par lots est que la quantité importante de données envoyées se traduit par des délais de propagation élevés. Avec des centaines de magasins et leurs valeurs de caractéristiques incluses dans chaque demande, il était logique d'essayer de compresser les demandes afin de réduire le nombre de paquets dans la couche réseau qui devraient être envoyés à Sibyl.
Utiliser enfin les prédictions du service en production
Lors des tests de charge, Sibyl était appelé à chaque fois qu'un magasin était classé, mais le résultat renvoyé par le service n'était jamais utilisé. L'étape suivante consistait à utiliser réellement ces valeurs calculées et à intégrer officiellement les prédictions du service dans le flux de production pour différents modèles. Bien que le traitement des demandes de notre service de recherche soit utile pour les tests de charge, en raison des exigences très strictes en matière de latence, il ne s'agirait pas des premiers modèles à être transférés. En outre, il faudrait passer plus de temps à migrer toutes les valeurs des caractéristiques du service de recherche vers le magasin de caractéristiques de Sibyl. Nous avons décidé de commencer par quelques modèles ML de détection de fraude et de dasher pay, les raisons étant que le QPS estimé serait bien inférieur et que les exigences en matière de latence n'étaient pas aussi strictes. La détection des fraudes et la rémunération des tireurs n'ont pas besoin d'être calculées aussi rapidement que le chargement de la page d'accueil. Depuis le mois de mars de cette année, les modèles de fraude et de dasher pay utilisent officiellement Sibyl pour les prédictions. Après le déploiement, une grande victoire a été observée : la latence a été divisée par trois (par rapport à notre ancien service de prédiction), ce qui témoigne de l'efficacité de Sibyl.
Conclusion du roulement : la fin du commencement
Après le succès du déploiement des modèles de fraude et de paiement dasher dans l'utilisation de Sibyl pour les prédictions, au cours des deux derniers mois, l'équipe de la plateforme ML n'a cessé d'ajouter de plus en plus de modèles au service, et la migration des modèles vers Sibyl est sur le point de s'achever. Tous les modèles, à l'exception de cinq, ont été migrés et utilisent désormais Sibyl pour leurs prédictions. Pour en savoir plus sur la migration, consultez ce nouveau billet de blog ici.
L'équipe continue d'ajouter la prise en charge de différents types de caractéristiques et de modèles. Par exemple, la prise en charge des fonctions intégrées, utilisées principalement dans les réseaux neuronaux, a été ajoutée. Les modèles composites, qui consistent en une chaîne de sous-modèles et d'expressions, appelés nœuds de calcul, ont également été ajoutés. Bien que le rôle de Sibyl en tant que prédicteur pour DoorDash vienne juste de commencer, il a déjà été passionnant et actif !
Remerciements :
Merci à Param Reddy, Jimmy Zhou, Arbaz Khan et Eric Gu pour leur implication dans le développement du service de prédiction Sibyl.