Immersion en profondeur dans l'inférence de LLM à grande échelle — Harshul Jain, Audible & Tanmay Sah, Chercheur indépendant en IA
AAI Engineer
컴퓨터/소프트웨어경제 뉴스AI/미래기술
스크립트
00:00:00Bonjour à tous. Je m'appelle Harshal Jain et voici Tanmay. Nous
00:00:21aimerions vous souhaiter la bienvenue à cet atelier de deux heures sur l'inférence des LLM.
00:00:26Le but de cet atelier est de comprendre ce domaine à partir des premiers
00:00:33principes, d'approfondir le sujet et de comprendre ce qui se passe dans toute
00:00:39l'industrie. Un peu de contexte sur nous. Je suis ingénieur logiciel senior chez
00:00:47Audible. Je conçois des plates-formes de données ML/AI depuis cinq ans et, à
00:00:53côté, j'écris ce manuel open source sur l'inférence des LLM. Et
00:00:59Tanmay est modélisateur quantitatif senior à la Xi'an's Bank Corporation. Il
00:01:06a récemment terminé son doctorat et mène activement des recherches sur les
00:01:12vérificateurs d'agents et les modèles du monde. Faisons un rapide sondage à main levée. Qui découvre tout juste l'inférence des LLM ?
00:01:24D'accord, très bien. Et qui a déjà déployé ces modèles en production ? Qui les a
00:01:33optimisés et géré le trafic de production ? D'accord, très bien. Cet
00:01:42atelier s'adresse aux niveaux débutant et intermédiaire. Tous les
00:01:48diaporamas et exercices sont disponibles dans le dépôt que je partagerai bientôt. Voici un bref
00:01:56programme de l'atelier. Nous commencerons par l'énoncé du problème. Nous essaierons de
00:02:01comprendre certains points de friction liés à l'inférence des LLM. Ensuite, nous verrons ce qui cause ces
00:02:08difficultés pour poser nos bases. Puis, nous plongerons dans deux types
00:02:14d'optimisations que nous réalisons, à savoir les optimisations de modèles et les optimisations de
00:02:19service. Nous découvrirons ensuite les différents moteurs de service disponibles
00:02:25pour déployer nos solutions d'inférence LLM en production. Enfin, nous présenterons des benchmarks
00:02:32ainsi qu'un diagramme de décision pour savoir quel moteur utiliser.
00:02:39Très bien. Pour comprendre les difficultés, il faut d'abord savoir ce qu'est l'inférence d'un LLM. Donc
00:02:47beaucoup d'entre nous le savent probablement déjà. Mais tout ce que vous demandez à votre IA de faire,
00:02:55qu'il s'agisse de générer une vidéo, de l'audio, d'analyser du texte, d'examiner vos rapports médicaux ou vos
00:03:02factures d'impôt, relève de l'inférence LLM. Ce marché pèse aujourd'hui environ 23 milliards de dollars.
00:03:12SemiAnalysis a récemment indiqué que pour modéliser des requêtes de recherche Google avec des LLM, il faudrait un budget faramineux de l'ordre de 36 milliards de dollars.
00:03:25Et le coût par requête doit être inférieur à un demi-cent pour que votre activité de recherche reste rentable.
00:03:33D'un autre côté, Business Insider mentionne que l'IA doit être utilisée à bon escient et que chacun doit commencer à auditer et budgétiser sa consommation de jetons.
00:03:43Pourquoi tout cela se produit-il ? Parce que votre matériel est limité, la puissance de calcul est coûteuse, et l'inférence est onéreuse.
00:03:50Et avec la demande croissante pour une utilisation accrue de l'IA, le coût de cette inférence ne cesse d'augmenter.
00:04:03Cette statistique vient d'OpenAI ; elle est ancienne, mais elle reste vraie.
00:04:11Si l'on regarde le coût d'entraînement de GPT-3, il était d'environ 4,6 millions de dollars. C'était un coût unique.
00:04:19Mais si l'on observe les coûts d'inférence, ils sont récurrents car il s'agit d'un coût opérationnel qui évolue avec chaque utilisateur, chaque jeton et chaque session initialisée sur l'IA.
00:04:36Il n'y a fondamentalement que deux façons de contrer cela : la première consiste à réduire votre consommation de jetons.
00:04:47L'autre option est d'essayer d'optimiser vos solutions d'inférence en tant que fournisseur de services d'inférence pour vos clients et pour vous-même.
00:04:58Et c'est pourquoi nous voyons constamment de plus en plus de nouvelles solutions émerger.
00:05:06L'idée est donc d'établir ces bases pour nous aider à comprendre et à évaluer tout ce qui sortira par la suite.
00:05:18Pour commencer, nous allons faire une petite démo montrant les différents points de friction liés à l'inférence.
00:05:28Voici le dépôt.
00:05:31Vous pouvez le cloner ou l'ouvrir directement sur GitHub.
00:05:36Il s'intitule « LLM inference at scale ».
00:05:39Pour un peu de contexte, il y a quatre mois, alors que je ne connaissais rien à l'inférence des LLM, j'ai commencé à l'étudier.
00:05:46J'ai constaté que de nombreuses ressources étaient éparpillées.
00:05:49Nous avons donc commencé à tout regrouper au même endroit pour en faire profiter les gens.
00:05:55Laissez-moi quitter ce mode diaporama et passer en mode étendu.
00:06:11Dans ce dépôt, si vous consultez le fichier README, vous trouverez un lien vers les diapositives.
00:06:27Ce lien mène vers un dossier contenant un fichier PPTX et un rapport de benchmark.
00:06:34Vous pouvez toujours le télécharger et, pour les besoins de la démo, nous avons préparé quelques notebooks Jupyter.
00:06:43Nous avons collaboré avec Molab, une alternative à Google Colab qui met à disposition un GPU RTX 6000 gratuit.
00:06:54Il s'agit d'un GPU doté de 100 Go de RAM, et nous avons préconfiguré ces notebooks pour faciliter les expérimentations, avec toutes les ressources prêtes à l'emploi.
00:07:09Nous allons commencer par une simple démo A.
00:07:16Laissez-moi regarder.
00:07:31En matière d'inférence, il faut exécuter l'inférence sur un certain modèle, n'est-ce pas ?
00:07:40Pour cet atelier, nous utilisons un modèle Mistral 7B.
00:07:45C'est un petit modèle d'environ 15 Go.
00:07:49Nous allons donc le charger dans le GPU.
00:07:53Nous examinerons également quelques statistiques du GPU.
00:07:58Nous voyons que nous travaillons sur une RTX 6000 Blackwell.
00:08:01Vous pensez peut-être que je n'exécute pas les cellules parce que je ne fais pas confiance au Wi-Fi des conférences.
00:08:08C'est exact, je vais donc simplement passer en revue les résultats obtenus lors des tests précédents.
00:08:18Nous disposons d'un GPU de 102 Go.
00:08:22La première question qui me vient à l'esprit est de savoir quelle est ma consommation de mémoire lors de l'inférence du LLM.
00:08:31Je charge ce modèle et je constate que j'utilise environ 15 Go.
00:08:37Il me reste donc grosso modo 87,5 Go.
00:08:40Et lorsque je lance l'inférence, je remarque que plus le nombre d'entrées transmises est élevé, plus j'ai besoin de mémoire.
00:08:52L'augmentation est progressive, mais bien réelle.
00:08:56Imaginez si vous avez une longueur de contexte d'environ 4 000, 16 000 ou 32 000 jetons.
00:09:03Cette mémoire peut vraiment exploser et provoquer des erreurs de dépassement de mémoire (OOM).
00:09:12C'est donc votre premier problème : la mémoire augmente avec le nombre de jetons.
00:09:18Sous forme de représentation visuelle simple, cela donne ceci.
00:09:24Le deuxième problème que vous rencontrerez est que le temps d'obtention du premier jeton est très, très lent.
00:09:32Nous mesurons cela à l'aide d'une métrique appelée TTFT.
00:09:35C'en est l'abréviation.
00:09:37Lorsque vous essayez de mesurer le TTFT par rapport à la taille de l'entrée, vous remarquez que plus le contexte est long, plus le TTFT ralentit.
00:09:52Il y a donc deux problèmes.
00:09:54Votre mémoire augmente avec la taille des jetons.
00:09:56Votre TTFT augmente avec la taille des jetons.
00:09:59Pardon, pas la taille des jetons, la taille du contexte.
00:10:07Le troisième problème concerne le débit.
00:10:09Le débit correspond au nombre de jetons que vous pouvez traiter par seconde.
00:10:13Et au nombre d'utilisateurs que vous pouvez servir par seconde.
00:10:16Si vous adoptez une implémentation très rudimentaire sur votre système local, elle sera purement séquentielle.
00:10:24Par conséquent, si vous envoyez cinq requêtes, elles seront toutes traitées les unes après les autres plutôt qu'en parallèle.
00:10:31Et donc votre requête prend essentiellement plus de temps à s'exécuter si vous avez plusieurs utilisateurs.
00:10:40Voici donc ces trois problèmes.
00:10:43Il y en a un quatrième.
00:10:44Je ne l'ai pas décrit ici.
00:10:45Nous l'aborderons probablement au fur et à mesure.
00:10:48Retenez bien ces trois problèmes : la mémoire, le TTFT et le débit.
00:10:55Bien.
00:10:56Je retourne aux diapositives.
00:11:02D'accord.
00:11:03Parfait.
00:11:04Ça devrait être ceci.
00:11:17Est-ce visible ?
00:11:18Oui.
00:11:19C'est visible.
00:11:20C'est visible.
00:11:21Oui.
00:11:22C'est visible.
00:11:23C'est visible.
00:11:24C'est visible.
00:11:25Oui.
00:11:26C'est visible.
00:11:27C'est visible.
00:11:28Oui.
00:11:29C'est visible.
00:11:30Oui.
00:11:31C'est visible.
00:11:32Oui.
00:11:33C'est visible.
00:11:34Oui.
00:11:35C'est visible.
00:11:36C'est visible.
00:11:37Oui.
00:11:38C'est visible.
00:11:39Oui.
00:11:40C'est visible.
00:11:41Oui.
00:11:42C'est visible.
00:11:43Oui.
00:11:44C'est visible.
00:11:45Oui.
00:11:46Oui.
00:11:47Dans ce dépôt, si vous regardez le dossier de l'atelier, vous verrez le README, et ce README
00:11:55contient tous les liens, les diaporamas et les démonstrations.
00:12:01Est-ce que ça fonctionne ?
00:12:06D'accord.
00:12:07Parfait.
00:12:09D'accord.
00:12:10Parfait.
00:12:16D'accord.
00:12:17Abordons maintenant les bases.
00:12:19Comprenons quelles sont les raisons de ces difficultés.
00:12:25Et pour cela, nous devons examiner ce pipeline d'inférence.
00:12:32Nous avons donc un texte d'entrée.
00:12:35Ce texte peut contenir un certain nombre de mots.
00:12:38Vous convertissez ces mots en jetons.
00:12:41Pour faire simple, vous pouvez supposer qu'un mot équivaut à un jeton.
00:12:46Ensuite, vous les convertissez en vecteurs d'embedding pour les envoyer aux transformeurs.
00:12:53Il y a 32 couches de transformeurs, mais c'est propre à Mistral 7B.
00:12:58Les différents modèles ont un nombre de couches différent.
00:13:01Et ensuite, vous générez un nouveau jeton.
00:13:04Ce jeton est pratiquement renvoyé vers l'entrée.
00:13:06Puis vous générez un autre jeton, et ainsi de suite.
00:13:09Dans tout ce pipeline, vous remarquerez que 95 % du calcul est effectué par ces couches de transformeurs.
00:13:18Il est donc intéressant de voir ce qui se passe à l'intérieur de cette couche de transformeur.
00:13:24À l'intérieur de cette couche, vous avez d'autres sous-couches.
00:13:28Vous avez une couche de normalisation.
00:13:30Vous avez une couche d'attention.
00:13:32Vous avez une couche feed-forward, etc.
00:13:35Et la couche d'attention est celle qui est devenue très, très célèbre.
00:13:40Pour ceux qui connaissent Attention Is All You Need.
00:13:42Je pense que c'est très connu.
00:13:44L'attention est donc la couche la plus intensive en calcul.
00:13:48Et nous devons comprendre ce qui se passe au sein de cette couche d'attention.
00:13:53Que fait donc l'attention ?
00:13:56Si vous avez un texte d'entrée, elle doit trouver les scores d'attention de chaque jeton par rapport à tous les jetons précédents.
00:14:05Pour ce faire, elle doit projeter chaque jeton dans un espace de clés, de requêtes et de valeurs.
00:14:14En termes plus simples, comprenez ceci.
00:14:20Si vous avez 10 jetons, il vous faut 10 vecteurs de requêtes, de clés et de valeurs différents.
00:14:26S'il y a 100 jetons, il vous faudra 100 vecteurs de clés et de valeurs.
00:14:31S'il y a 1000 jetons, il vous faudra 1000 vecteurs clé-valeur.
00:14:35Par conséquent, le nombre de vecteurs de clés et de valeurs augmente avec la taille de l'entrée.
00:14:45Et si l'on calcule la taille KV par jeton pour Mistral 7B, on arrive à 131 KV.
00:14:54C'est parce que vous avez deux vecteurs, K et V.
00:14:59Vous devez multiplier la taille.
00:15:02Un vecteur fait 128 dimensions.
00:15:04Vous devez le multiplier par 32 couches de transformeurs.
00:15:08Puis vous devez le multiplier par les têtes KV.
00:15:11Pour Mistral 7B, ce sont les têtes KV.
00:15:15Ce n'est pas 32 car il utilise un mécanisme d'attention différent, dont nous allons parler c'est sûr.
00:15:23Mais oui, la taille KV par jeton est donc de 131 KV.
00:15:30Maintenant, imaginez un contexte de 4K, cette taille atteint environ un demi-Go.
00:15:37Si vous prenez un contexte de 16K, cette taille passe à 2,1 Go.
00:15:42Multipliez ensuite par le nombre d'utilisateurs.
00:15:45Supposons que vous puissiez servir plusieurs utilisateurs en même temps.
00:15:49En même temps sur ce GPU, vous pourriez atteindre 42 Go avec un contexte de 4K et 80 utilisateurs.
00:15:58Et si votre GPU ne fait que 24 Go, par exemple, vous êtes déjà à court de mémoire.
00:16:04Vous ne pouvez donc pas servir autant d'utilisateurs avec autant de contexte.
00:16:11Pour visualiser cela, regardons la mémoire d'un GPU.
00:16:14La mémoire du GPU contient les poids du modèle, qui sont assez fixes.
00:16:19Ce sont des poids pré-entraînés.
00:16:21Il y a aussi une surcharge qui est fixe.
00:16:24Celle-ci varie, mais pas tant que ça.
00:16:29Dans l'ensemble, vous pouvez considérer qu'elle est fixe.
00:16:32Ensuite, il reste de la mémoire libre.
00:16:35Cette mémoire restante est utilisée par votre mémoire KV, c'est-à-dire les vecteurs de clés et de valeurs.
00:16:41Supposons donc que vous ayez un seul utilisateur.
00:16:46Vous ne pouvez servir que le nombre de vecteurs clés-valeurs ou de jetons qui tiennent dans ces 80 Go de mémoire restante.
00:17:00Nous pouvons aussi illustrer cela avec une simple démo.
00:17:07D'accord, super.
00:17:08D'accord, super.
00:17:08Laissez-moi voir si je peux vraiment lancer ceci.
00:17:14D'accord, super.
00:17:15Laissez-moi voir si je peux vraiment lancer ceci.
00:17:15Où diable est-ce ?
00:17:15D'accord, super.
00:17:16Laissez-moi voir si je peux vraiment lancer ceci.
00:17:21Où diable est-ce ?
00:17:22D'accord, super.
00:17:23D'accord, super.
00:17:24Oui.
00:17:25Oui.
00:17:25Vous verrez donc... laissez-moi voir si je peux vraiment lancer ceci.
00:17:31Laissez-moi voir si je peux vraiment lancer ceci.
00:17:33Laissez-moi voir si je peux lancer ceci.
00:17:34Laissez-moi voir si je peux lancer ceci.
00:17:35Laissez-moi voir si je peux lancer ceci.
00:17:36Laissez-moi voir si je peux lancer ceci.
00:17:37D'accord, super.
00:17:38Oui.
00:17:39Vous verrez donc que le GPU est connecté.
00:17:43D'accord, super.
00:17:44Oui.
00:17:45Vous verrez donc que le GPU est connecté.
00:17:56Ici, nous essayons simplement de confirmer la mémoire en nous basant sur les calculs et l'intuition
00:18:13que nous avons développée.
00:18:14La mémoire du modèle est donc, disons, si vous avez 7 milliards de paramètres avec
00:18:19une précision de 16 bits.
00:18:20Votre mémoire totale s'élève à 14,6 Go.
00:18:23Vous pouvez essentiellement le vérifier par le calcul.
00:18:27Donc, si vous faites tous ces calculs, cela donne 14,6 Go.
00:18:33Viennent ensuite le KV et la taille KV.
00:18:37Cette taille KV est donc de 131 KV par jeton.
00:18:42Et si vous faites ce calcul et essayez de visualiser cela.
00:18:47Oh, bien sûr.
00:18:52Attendez.
00:18:53D'accord.
00:18:54Et visualisons cela.
00:19:07D'accord, super.
00:19:10Super.
00:19:11Oui.
00:19:12Voici donc le graphique de la mémoire.
00:19:15Si vous regardez, à mesure que votre contexte augmente, votre mémoire augmente continuellement.
00:19:21Autre chose à réaliser : à mesure que le nombre d'utilisateurs augmente, votre mémoire augmente aussi.
00:19:28Ainsi, si vous voulez servir 160 utilisateurs sur un GPU, vous ne pouvez en prendre en charge que
00:19:37avec une longueur de contexte plus faible.
00:19:40Il y a donc toujours un compromis entre la longueur du contexte que vous pouvez servir et le coût
00:19:47que vous pouvez économiser en regroupant plusieurs utilisateurs ou utilisateurs simultanés sur un seul GPU.
00:19:54Vous devez donc toujours faire ce compromis.
00:19:57Et nous allons aborder cela dans quelques diapositives de plus.
00:20:06Pouvez-vous répéter, s'il vous plaît ?
00:20:10Je suis désolé.
00:20:11Je ne vous entends pas.
00:20:12Voyez-vous un pool différent avec une longueur de contexte différente pour servir la mémoire
00:20:25correcte ?
00:20:26Oui.
00:20:27Cool.
00:20:28Cool.
00:20:29D'accord.
00:20:30Bien.
00:20:31Alors, revenons en arrière.
00:20:32C'était la question de la mémoire.
00:20:33Nous devons comprendre pourquoi nous avions un temps de génération du premier jeton plus lent lorsque nous augmentions la longueur du contexte.
00:20:53Pour cela, nous devons comprendre les deux phases de l'inférence.
00:20:54Ces phases sont le pré-remplissage et le décodage.
00:20:58Je pense que vous l'avez tous lu dans de nombreux articles, mais nous voulions l'expliquer.
00:21:01Ainsi, lorsque vous envoyez tous ces jetons d'entrée, ce que vous voulez faire, c'est construire
00:21:06ces vecteurs de clés et de valeurs dont j'ai parlé pour tous les jetons.
00:21:20Ensuite, vous devez calculer les scores d'attention de chaque jeton par rapport aux jetons précédents.
00:21:27Toutes ces opérations que vous effectuez sont très, très lourdes en calcul matriciel.
00:21:31C'est une opération très, très exigeante en matière de calcul.
00:21:34Et nous savons tous que les GPU sont très bien adaptés aux charges de travail lourdes en calcul.
00:21:41Nous qualifions donc le pré-remplissage de phase limitée par le calcul (compute-bound).
00:21:46Et cela prend un certain temps pour se terminer.
00:21:49Le temps nécessaire pour achever cette phase correspond donc au temps d'attente du premier jeton (TTFT).
00:21:55Ainsi, si vous avez plus de jetons d'entrée, vous devez générer plus de vecteurs clés-valeurs.
00:22:02Vous devez effectuer beaucoup plus de calculs d'attention.
00:22:05Et par conséquent, votre TTFT devient de plus en plus lent.
00:22:12En revanche, une fois qu'un jeton est généré, vous devez répéter l'opération pour les suivants, de façon séquentielle.
00:22:20Mais au cours de ce processus, vous devez reconstruire à chaque fois les vecteurs de clés et de valeurs de tous les jetons précédents, comme lors du remplissage.
00:22:30De même que vous construisiez des vecteurs clés-valeurs là-bas, vous le faites aussi ici.
00:22:34Mais dans la phase de décodage, vous ne calculez l'attention que pour le nouveau jeton.
00:22:40Et c'est pourquoi c'est une opération beaucoup moins orientée vers le calcul.
00:22:45On dit aussi qu'elle est limitée par la mémoire.
00:22:48Nous allons voir dans un instant pourquoi elle est qualifiée ainsi.
00:22:54Dans un calendrier classique, les phases de remplissage et de décodage se présentent ainsi.
00:22:59Le temps pris par le remplissage correspond au temps d'obtention du premier jeton.
00:23:03Ensuite, le temps pris par chaque étape de décodage correspond essentiellement à votre latence inter-jetons.
00:23:12C'est donc la quatrième métrique qui doit vous préoccuper.
00:23:18Quel est le temps pris par votre étape de décodage ?
00:23:21D'accord.
00:23:22D'accord.
00:23:23Bien.
00:23:24Maintenant, pourquoi l'étape de décodage, ou pourquoi le décodage prend-il du temps ?
00:23:34Et pourquoi est-il qualifié d'opération limitée par la mémoire ?
00:23:38Essayons de comprendre cela.
00:23:40Pour ce faire, nous devons examiner globalement le fonctionnement de la matrice sur le GPU.
00:23:47Le GPU dispose donc de deux types de mémoire.
00:23:50Vous avez une mémoire à haute bande passante.
00:23:52Vous avez une mémoire partagée.
00:23:55La mémoire à haute bande passante a une plus grande capacité, mais une bande passante plus faible.
00:24:01Par bande passante plus faible, je veux dire que vous y transférez des données à un débit inférieur.
00:24:06Comparée à la mémoire partagée, qui est plus petite en taille, mais possède une très, très haute bande passante.
00:24:13Cela signifie que vous pouvez y transférer des données très rapidement.
00:24:19Ainsi, lorsque vous effectuez un calcul matriciel, vous devez extraire les données par blocs de la mémoire à haute bande passante.
00:24:27Vous devez les placer dans la mémoire partagée.
00:24:30Effectuer le calcul.
00:24:32Et réécrire le résultat dans la mémoire à haute bande passante.
00:24:38Pendant la phase de remplissage, ce calcul matriciel ne doit être effectué qu'une seule fois.
00:24:46Mais pour la phase de décodage, vous devez répéter ce calcul sans cesse puisque vous générez chaque jeton de manière séquentielle.
00:24:59Par conséquent, la vitesse de votre décodage importe peu, car vous transférez les données de la mémoire à haute bande passante vers la mémoire partagée à une certaine vitesse limite.
00:25:14Vous êtes limité par la vitesse de la bande passante de cette mémoire.
00:25:18Cela détermine donc votre plafond de jetons, c'est-à-dire le taux auquel vous pouvez réellement générer des jetons lors de l'étape de décodage.
00:25:32Si l'on regarde cela sur un graphique de performance, la section de gauche est dite limitée par la mémoire.
00:25:42Mathématiquement, elle est régie par l'intensité arithmétique.
00:25:47L'intensité arithmétique est le nombre d'opérations en virgule flottante effectuées par octet de données transféré.
00:25:55Pour l'étape de décodage, comme vous transférez beaucoup de données (les vecteurs clés et valeurs de tous les jetons précédents, ainsi que les poids du modèle),
00:26:08mais que vous effectuez moins de calculs car vous ne calculez l'attention que pour un seul jeton,
00:26:13son intensité arithmétique est très faible.
00:26:18En revanche, pour la phase de remplissage, vous transférez les données une seule fois, mais vous réalisez un calcul très lourd.
00:26:27Son intensité arithmétique est donc très élevée.
00:26:30Vous comprenez désormais mathématiquement pourquoi l'intensité arithmétique du remplissage est si élevée par rapport au décodage.
00:26:44D'accord.
00:26:46Voici une autre petite démonstration.
00:26:51D'accord.
00:26:52Chaque fois que je dois le faire.
00:26:53D'accord.
00:26:54D'accord.
00:26:55Parfait.
00:26:56J'espère que c'est bon.
00:26:58Alors, oui.
00:26:59Encore une fois, nous chargeons le modèle.
00:27:04Voici maintenant le coût du remplissage.
00:27:09Ce que nous faisons concrètement, c'est récupérer le texte d'entrée, puis mesurer le temps nécessaire pour cette étape de remplissage.
00:27:22Nous constatons que lorsque la taille des jetons d'entrée augmente, le temps de remplissage augmente également.
00:27:28C'est la raison pour laquelle le temps d'obtention du premier jeton augmente.
00:27:33Et voici votre temps de décodage.
00:27:36Le temps de décodage reste quant à lui à peu près le même en moyenne.
00:27:41Ainsi, si l'on fait abstraction du démarrage à froid, votre temps de décodage se situe approximativement autour de la ligne moyenne.
00:27:50Il est tout de même influencé par la taille de l'entrée.
00:27:57Ce n'est pas un temps totalement constant.
00:28:00Et cela s'explique par le fait qu'il doit toujours extraire de la mémoire les vecteurs clés et valeurs de tous les jetons précédents.
00:28:07On observe donc toujours une légère augmentation du temps pour l'étape de décodage.
00:28:15Et voici le graphique de performance classique.
00:28:20D'accord.
00:28:22D'accord.
00:28:23Présentation.
00:28:24D'accord.
00:28:25D'accord.
00:28:26D'accord.
00:28:27Intéressons-nous maintenant à la dimension du débit.
00:28:43Vous voulez savoir combien d'utilisateurs vous pouvez réellement prendre en charge.
00:28:48Nous avons vu sur le schéma de la mémoire du GPU qu'une partie de la mémoire est libre pour permettre la croissance des vecteurs clés-valeurs.
00:28:56Très bien.
00:28:57Imaginons que vous n'ayez qu'un seul utilisateur.
00:29:01Quelle est la taille totale de ces vecteurs que vous pouvez prendre en charge ?
00:29:09Elle est définie par votre limite de contexte.
00:29:11Le nombre maximal d'utilisateurs s'obtient en divisant la mémoire disponible sur le GPU par la taille des vecteurs clés-valeurs par utilisateur.
00:29:25Le résultat correspond au nombre d'utilisateurs simultanés.
00:29:32Supposons maintenant que votre GPU et votre modèle soient fixes, de sorte que la taille des vecteurs par jeton est fixe.
00:29:46Il ne reste que deux dimensions : le contexte et les utilisateurs simultanés.
00:29:53Si vous voulez servir plus d'utilisateurs simultanés, vous devez réduire la longueur du contexte.
00:29:57Réduire la longueur du contexte peut nuire à la qualité.
00:30:02Ce sont donc les deux dimensions que nous cherchons à concilier.
00:30:09Mais pouvez-vous réellement servir le nombre maximal d'utilisateurs simultanés ?
00:30:17Dans un monde idéal, probablement pas, car chaque entreprise doit respecter un objectif de niveau de service en matière de latence.
00:30:27Comme je l'ai mentionné pour l'étape de décodage, le temps de décodage augmente si vous avez davantage d'entrées.
00:30:37Il augmente également si vous avez plus d'utilisateurs.
00:30:40Par conséquent, votre latence inter-jetons est également affectée si vous utilisez une taille de lot plus élevée.
00:30:51Et votre temps pour le premier jeton l'est aussi.
00:30:53Il y a donc une troisième dimension à prendre en compte : la latence.
00:30:58Les trois dimensions sont donc la qualité, la latence et le débit.
00:31:04Cela forme un triangle de compromis où il faut faire des choix.
00:31:11Pour une application de chat haut de gamme, vous voudrez clairement prioriser la qualité et la latence.
00:31:21Vous ne voudrez pas que vos utilisateurs attendent trop longtemps.
00:31:30Vous pouvez toujours sacrifier le nombre d'utilisateurs pris en charge sur le GPU et absorber ce coût par souci du client.
00:31:39En revanche, pour une charge de travail asynchrone par agent, vous voudrez privilégier la qualité et le débit.
00:31:53Car il s'agit de tâches de longue durée.
00:31:56Vous voudrez servir autant de tâches simultanées que possible, tout en maintenant une très haute qualité.
00:32:06Souvent, on se dit qu'un GPU très cher ne nous conviendra pas.
00:32:19Mais il s'avère qu'il peut en réalité vous offrir le coût le plus bas par million de jetons.
00:32:27Toutefois, vous devez vraiment faire confiance à vos calculs sur le nombre maximal d'utilisateurs et effectuer des estimations correctes.
00:32:40Nous avons, attendez, très bien.
00:32:55Pour le calculateur de capacité, il y a un lien vers le Colab car je connaissais cet outil.
00:33:01J'ai dû migrer toute la bibliothèque de widgets et je n'avais pas le temps.
00:33:16Par paresse, j'ai simplement pris Colab.
00:33:20Toutes mes excuses à mon labo.
00:33:23Ma mémoire vidéo est connectée.
00:33:34D'accord.
00:33:45C'est probablement le Wi-Fi.
00:33:46Très bien.
00:34:02Nous avons répertorié ici plusieurs GPU avec leur mémoire vidéo, leur bande passante, leurs opérations et leur coût horaire.
00:34:11Ensuite, nous avons construit ce calculateur de capacité simple.
00:34:19Il s'agit d'un visualiseur où, lorsque vous augmentez le nombre de jetons, vous voyez la taille des vecteurs augmenter.
00:34:29Et si vous augmentez le nombre d'utilisateurs, cette taille grandit beaucoup plus vite.
00:34:37Puis, dans ce calculateur de capacité, laissons-le tourner.
00:34:47Nous avons sélectionné un modèle de 7 milliards de paramètres.
00:34:56Nous avons défini la précision à FP16.
00:35:01Pour choisir un GPU, il faut d'abord fixer la dimension qui nous importe le plus.
00:35:15Pour un chat haut de gamme, j'ai mentionné que la latence est primordiale.
00:35:21Pour les charges de travail asynchrones, la taille de lot minimale à traiter sur un seul GPU constitue la deuxième dimension.
00:35:31Il faut donc commencer par fixer ces paramètres.
00:35:34Prenons l'exemple d'une application de chat haut de gamme.
00:35:39Je peux opter pour une latence de 10 millisecondes.
00:35:44La taille de lot minimale m'importe peu.
00:35:46Je peux me contenter d'une valeur de deux par exemple.
00:35:53D'accord.
00:35:54Probablement avec sept utilisateurs simultanés sur un seul GPU.
00:35:59Et ma limite de contexte est très importante car je veux aussi me concentrer sur la qualité.
00:36:07J'observe ainsi certains GPU.
00:36:10Le H100 de 80 Go coûte environ 8 dollars de l'heure.
00:36:15Mais qu'en est-il du mien ?
00:36:19Est-ce le cas ?
00:36:20Oui.
00:36:21Donc c'est environ 10 dollars de l'heure.
00:36:24Donc, si vous faites tous ces calculs de débit qu'on a vus dans les mathématiques précédentes, vous pouvez trouver votre coût par million de tokens.
00:36:34Cela pourrait être très, très, cela pourrait être inférieur.
00:36:37Donc, vous devez faire ces calculs en fixant ces dimensions et vous devez choisir votre GPU pour réduire vos coûts d'inférence en quelque sorte.
00:36:49C'est donc au moins la première étape que vous pouvez franchir pour optimiser l'inférence.
00:36:56D'accord.
00:37:01Passons à la diapositive suivante.
00:37:04Laissez-moi...
00:37:08D'accord, super.
00:37:10Et maintenant, la prochaine étape concerne l'optimisation des modèles.
00:37:15Nous avons donc maintenant posé les bases en comprenant certains points de friction, les raisons de ces problèmes, et pourquoi ils se produisaient.
00:37:23Comment nous pouvions résoudre le problème de la capacité des GPU.
00:37:29Nous devons comprendre ce que nous pouvons faire de plus à ce sujet.
00:37:33Il s'agit donc d'optimisation de modèles et je voudrais inviter Tanmay.
00:37:39Il peut parler plus en détail de ces optimisations, étant donné qu'il a travaillé là-dessus lors de ses recherches.
00:37:47D'accord.
00:37:48D'accord.
00:37:49Je peux contrôler.
00:37:50Je peux contrôler.
00:37:54Oui.
00:37:55Ici.
00:37:56D'accord.
00:37:57Bonjour à tous.
00:37:58Test micro.
00:37:59Est-ce qu'on m'entend enfin ?
00:38:00Oui.
00:38:01D'accord.
00:38:02Bonjour.
00:38:03Je m'appelle Tanmay Shah.
00:38:04Je travaille comme modeleur quantitatif senior et je suis aussi chercheur en IA.
00:38:08Je me concentre sur la vérification des agents et actuellement sur la création de modèles du monde.
00:38:13Donc, pour ce sujet, l'optimisation des modèles.
00:38:16Avant de commencer, j'ai créé un modèle de recherche pour nous aider à comprendre toutes ces choses complexes.
00:38:25Notre modèle est simple.
00:38:28Premièrement, nous identifions le problème.
00:38:30Deuxième étape, nous résolvons le problème en utilisant deux algorithmes.
00:38:34Ce sont de faux algorithmes.
00:38:35Le premier s'appelle l'algorithme de l'autruche.
00:38:39Comme l'autruche, dès qu'on voit un problème, on met la tête dans le sable.
00:38:46C'est ce que nous allons faire.
00:38:47Dès que nous faisons face à un problème, nous l'ignorons.
00:38:51C'est un algorithme important à suivre.
00:38:55Le deuxième s'appelle l'algorithme de la Coupe du Monde.
00:38:59Par exemple, on ne sait pas qui va gagner cette Coupe du Monde de la FIFA.
00:39:03Les organisateurs ont donc réparti les 48 équipes en 12 groupes.
00:39:11Ensuite les 32es de finale, qui se déroulent actuellement.
00:39:16Puis les 8es, les quarts, les demi-finales et la finale.
00:39:21Ils découpent le problème en morceaux plus petits et seuls les résultats utiles avancent.
00:39:30Nous allons utiliser la même analogie pour comprendre l'optimisation des modèles.
00:39:38Alors, allons-y.
00:39:41J'ai un processeur graphique H100.
00:39:46Je dois utiliser ce modèle open source appelé GPT OSS, un modèle de 120 milliards de paramètres.
00:39:54Il a été entraîné en BF float 16 et pèse 240 gigaoctets.
00:40:02Que dois-je faire ?
00:40:04C'est le problème auquel nous faisons face.
00:40:07D'abord, nous avons 240 gigaoctets d'un côté et un H100 de 80 gigaoctets de l'autre.
00:40:16Et je dois le faire tenir sur un seul GPU, pas sur plusieurs.
00:40:21Alors, que pouvons-nous faire ?
00:40:22Je pense que la solution simple est de le compresser.
00:40:27Mais comment le compresser ?
00:40:29C'est un autre défi.
00:40:30Si l'on compresse le BF float 16 en FP8, on arrive à environ 120 gigaoctets.
00:40:38Mais notre GPU H100 ne fait que 80 gigaoctets.
00:40:42Je crois qu'ils l'ont donc compressé encore plus en MXFP4.
00:40:49Et sa taille est d'environ 65 gigaoctets.
00:40:53C'est donc possible, mais il y a une question.
00:40:59Nous allons appliquer notre algorithme de l'autruche.
00:41:03Nous supposons qu'il n'y a aucune perte en compressant un grand modèle dans une plus petite taille.
00:41:10Deuxième point, dans celui-ci, d'accord, oui.
00:41:16Sur cette diapositive, nous avons utilisé Mistral 7B.
00:41:20Soit sept milliards de paramètres.
00:41:22C'est un petit modèle de sept milliards de paramètres.
00:41:25Si on multiplie par deux octets, le poids est d'environ 14.
00:41:3114,5 gigaoctets, ce qui tient facilement dans un H100 ou même un A40.
00:41:38Ensuite, au lieu de le compresser en virgule flottante 16, nous pouvons appliquer des techniques comme int8, int4 ou nf4.
00:41:53En gros, nous devons juste utiliser l'algorithme de l'autruche et croire qu'il n'y a pas de perte de qualité.
00:42:01Mais d'une certaine manière, nous devons aussi le prouver mathématiquement en effectuant des tests sur des benchmarks externes.
00:42:10Cela relève de la quantification post-entraînement.
00:42:16On peut aussi le faire pendant le réglage fin.
00:42:20On peut aussi appliquer ce type de quantification.
00:42:22Cela relève de l'entraînement conscient de la quantification.
00:42:25Passons au problème suivant.
00:42:31Nous avons ces immenses matrices.
00:42:37Imaginez une matrice A de 1000 par 1000 dimensions, et une autre matrice de 1000 par 1000.
00:42:51Si vous multipliez ces deux matrices, le nombre d'opérations sera de 1000 à la puissance Q.
00:43:00C'est un problème en termes de calcul.
00:43:06Nous voulons que notre multiplication de matrices soit rapide et économe en mémoire.
00:43:13Que devons-nous faire ?
00:43:15Nous avons une matrice géante.
00:43:17Prenons celle-ci, de 4096 par 4096.
00:43:23Que faire pour accélérer les choses et économiser de la mémoire avec du 4096 par 4096 ?
00:43:33Donc, la première chose est que nous allons utiliser notre algorithme de la Coupe du Monde.
00:43:37On choisit un nombre aléatoire et on découpe le bloc verticalement.
00:43:42Peu importe ce que vous choisissez.
00:43:45Disons que nous avons 4096 colonnes.
00:43:51Nous les divisons en groupes de 128 colonnes chacun.
00:43:57Soit 128, 128, 128, 128, 128 verticalement.
00:44:03Nous obtiendrons ainsi 32 blocs en divisant 4096.
00:44:09Alors, que va-t-il se passer en faisant cela ?
00:44:13Si nous divisons cela verticalement, nous pouvons utiliser plusieurs GPU pour accélérer le processus.
00:44:21C'est ce qu'on appelle l'attention multi-tête.
00:44:26Que pouvons-nous faire d'autre ?
00:44:29Nous avons une grande matrice, comme je l'ai mentionné avec l'algorithme de l'autruche.
00:44:36Notre principal problème est la taille.
00:44:39Au lieu d'avoir ces 32 blocs verticaux, nous allons en jeter 31.
00:44:49Et nous supposerons qu'un seul bloc suffit pour que toutes les requêtes puissent les traiter.
00:44:57Notre perte sera presque négligeable.
00:45:00Et nous arrivons à cet algorithme.
00:45:04Cet algorithme s'appelle l'attention multi-requête.
00:45:08Comme nous pouvons le voir, nous sommes maintenant à deux extrêmes.
00:45:12L'un est l'attention multi-tête, où nous coupons en 32 blocs et utilisons différents GPU ou un traitement parallèle.
00:45:22Et en même temps, nous jetons 31 blocs.
00:45:26C'est ce que nous appelons l'attention multi-requête.
00:45:31Entre ces deux extrêmes, nous devrions trouver un juste milieu.
00:45:38On pourrait se dire qu'au lieu de jeter les 31, on peut regrouper certains blocs.
00:45:49Et supposer que des blocs similaires traiteront un type de requête similaire.
00:45:58Cette technique relève de l'attention à requêtes groupées, qui est très populaire en ce moment.
00:46:04Même dans Mistral ou d'autres modèles, cette attention à requêtes groupées fonctionne.
00:46:11Nous avons donc compris que nous avons une grande matrice.
00:46:16Nous pouvons la diviser comme nous voulons et, en faisant des calculs mathématiques,
00:46:19prouver que la perte est pour ainsi dire négligeable.
00:46:23Que pouvons-nous faire d'autre ?
00:46:25Après cela, après cette attention à requêtes groupées,
00:46:34voyez-vous, nous avons une grande matrice.
00:46:38L'une est la clé et l'autre est la valeur.
00:46:42Compressons cette matrice en un vecteur latent.
00:46:47Puis concevons un algorithme pour reconstruire notre matrice d'origine à partir du vecteur latent.
00:46:55Ce genre de stratégie relève de ceci.
00:46:59L'attention latente multi-tête.
00:47:02Mais cela pose des problèmes avec RoPE, car RoPE dépend de la position, alors que l'autre l'est moins.
00:47:10Il faut donc inclure un certain index pour les clés afin de pouvoir les mapper.
00:47:16Mais le principal problème reste de savoir pourquoi nous multiplions toutes ces grandes matrices.
00:47:25Parce que c'est ainsi que fonctionne le mécanisme d'attention : chaque token fait attention à tous les autres tokens.
00:47:33Alors, pourquoi ne pas prêter attention seulement aux tokens importants pour nous au lieu de tous les tokens précédents ?
00:47:42C'est un domaine qui est en train d'évoluer.
00:47:46Cela relève de l'attention creuse, l'attention creuse de DeepSeek.
00:47:51Donc, oui.
00:47:53Et, oui, très bien.
00:47:56Suivant.
00:47:59Oui, le suivant est Flash Attention.
00:48:02Dans Flash Attention, le problème principal est que...
00:48:09Actuellement, tout le monde utilise Flash Attention, mais en 2022 ou 2023, c'était différent.
00:48:20Voici comment cela fonctionne.
00:48:23Le fonctionnement est le suivant : ces matrices Q et K, de requête et de clé, se trouvaient dans la mémoire HBM.
00:48:34Elles sont chargées dans notre cœur tensoriel, effectuent des calculs, puis sont réécrites dans la mémoire HBM.
00:48:47Ce processus se répète de nombreuses fois.
00:48:51Dans Flash Attention, au lieu de multiplier l'ensemble des matrices,
00:48:59ils les ont divisées, comme dans notre algorithme World Cup, en petites portions,
00:49:05et placent uniquement ces portions dans la mémoire SBM pour accélérer la multiplication,
00:49:13tout en suivant trois variables pour calculer cette fonction softmax en ligne.
00:49:20C'est purement mathématique : si nous avons une attention multi-tête pour 524 kV,
00:49:34cela dépend du regroupement souhaité. Au lieu d'utiliser 32 têtes kV, nous pouvons n'en utiliser que 8,
00:49:47ce qui permet une compression par 4, grâce à l'attention latente multi-tête.
00:49:54Cette formule dépend du modèle et du nombre de couches qu'il possède.
00:50:00Dans le papier original de DeepSeek, je crois que la dimension est de 128... Je ne me souviens plus de la dimension exacte, mais selon cela,
00:50:11ils ont utilisé ce vecteur latent avec une dimension de 512 et environ 64 pour l'indice RoPE,
00:50:23montrant qu'il est 56 fois plus compressé que l'attention multi-tête.
00:50:32D'accord.
00:50:33Voici le diagramme des compromis. Nous n'avons pas encore parlé de l'attention linéaire ni de Mamba.
00:50:50Le problème principal réside dans ces multiplications matricielles, et tout le monde utilise l'attention actuellement.
00:50:56Si à l'avenir nous ne voulons plus utiliser l'attention, et générer les éléments en même temps via des modèles de diffusion par exemple,
00:51:07tous ces algorithmes devront également changer.
00:51:14Mais ici, je crois qu'ils en ont deux de plus : l'attention linéaire et Mamba.
00:51:19Selon cette diapositive, si nous ne compressons rien, le mécanisme MHA parallélise simplement le processus.
00:51:29Il n'y a donc aucune perte de qualité, ce qui est bien. Vient ensuite l'attention groupée (GQA et DSA), utilisée par presque tous les modèles.
00:51:42Oui.
00:51:43C'est ce que nous retrouvons dans le tableau de score de l'attention : la qualité MHA est bonne, et le débit est correct.
00:51:56Pour l'attention groupée, bien que la qualité soit presque similaire à l'attention multi-tête, le cas d'usage importe énormément.
00:52:10L'attention multi-requête n'est qu'un cas extrème.
00:52:13Sans raison apparente, nous supposons qu'un seul bloc suffit et que toutes les requêtes s'adressent à ces blocs plus petits.
00:52:24La qualité n'est donc pas extraordinaire pour l'attention multi-requête.
00:52:29Concernant l'attention latente multi-tête, les modèles DeepSeek s'en sortent très bien en termes de qualité, tout comme la fenêtre glissante.
00:52:44Ce sont des techniques d'ajustement de fenêtres. L'attention linéaire consiste à tout résumer d'abord avant de consulter, et Mamba est un modèle d'état d'espace.
00:53:09Pour les optimisations de modèles, nous disposons également de deux notebooks ici.
00:53:28Je dois y accéder.
00:53:35Bien, pour la quantification, est-ce que cette démo a déjà été exécutée ? Non.
00:53:51Laissez-moi l'exécuter.
00:53:54Nous chargeons un modèle, à savoir Mistral 7B.
00:54:10Celui-ci utilise la référence FP16.
00:54:20Attendez.
00:54:21Est-ce que ça a tourné ?
00:54:21Attendez.
00:54:22Est-ce que ça a tourné ?
00:54:23D'accord.
00:54:24C'est bon, ça a pris deux millisecondes.
00:54:27Est-ce que cela s'est exécuté ?
00:54:28D'accord.
00:54:29Cette fois, le modèle est récupéré avec la précision FP16.
00:54:35D'accord.
00:54:36C'est bon, ça a pris deux millisecondes.
00:54:40Est-ce que cela s'est exécuté ?
00:54:41D'accord.
00:54:42Cette fois, le modèle est récupéré avec la précision FP16.
00:54:47Le Wi-Fi.
00:54:58Cela va prendre du temps.
00:55:03D'accord.
00:55:08Oui, car il télécharge les poids depuis Hugging Face.
00:55:14Hein ?
00:55:17Oui, Colab s'exécute en ligne.
00:55:22Oui.
00:55:23Parce qu'il doit effectuer un appel réseau vers Hugging Face pour récupérer les données.
00:55:30Le téléchargement prend un peu de temps.
00:55:35D'accord.
00:55:36D'accord.
00:55:37D'accord.
00:55:38D'accord.
00:55:39D'accord.
00:55:40Nous pouvons voir que la taille de la mémoire est d'environ 15 Go avec la précision FP16.
00:56:00Nous essayons d'effectuer une compression par 2, comme l'a mentionné Talmud, en utilisant int8.
00:56:15D'accord.
00:56:16La taille de la mémoire descend alors à environ 0,7 ou 0,5 Go.
00:56:21Cela signifie que vous disposez de plus de mémoire pour permettre à votre cache KV de croître.
00:56:27Vous pouvez ainsi prendre en charge une limite de contexte plus élevée ou davantage d'utilisateurs simultanés.
00:56:36Avec int4, vous réalisez une compression par 4.
00:56:41La consommation sera encore plus faible.
00:56:45Elle devrait être d'environ 3 à 4 Go.
00:56:50Oui, 4,5 Go.
00:56:51Et voilà.
00:56:52Ceci est... attendez.
00:56:53Il s'agit d'un graphique de base présentant des valeurs théoriques.
00:57:09Nous ne réalisons pas de test de débit ici.
00:57:12Cependant, on observe généralement que la mémoire augmente.
00:57:15Vous bénéficiez donc également d'un débit légèrement supérieur.
00:57:21D'après certains benchmarks étudiés, la compression int8
00:57:26entraîne en revanche un débit plus faible.
00:57:32D'accord.
00:57:33Ensuite, il y a une démonstration sur les mécanismes d'attention.
00:57:42Concernant l'attention, voyons voir.
00:57:45Je dois lancer ceci.
00:57:58Bien.
00:57:59L'exécution est terminée.
00:58:02Oh.
00:58:03Attendez.
00:58:04Pourquoi indique-t-il qu'aucun GPU n'est détecté ?
00:58:09Il devrait indiquer que le GPU est détecté.
00:58:18Ah.
00:58:19D'accord.
00:58:29Attendez.
00:58:30Attendez.
00:58:30Attendez.
00:58:50C'est surprenant.
00:58:57Je suppose qu'il n'arrive pas à détecter le GPU pour une raison quelconque.
00:59:05Nous avons pourtant bien un GPU ici.
00:59:11D'accord.
00:59:12Ce n'est pas grave.
00:59:14Oui.
00:59:14L'idée principale ici était de montrer que lorsque l'on cherche à compresser les calculs en modifiant les mécanismes d'attention,
00:59:26passant de l'attention multi-tête à l'attention groupée, puis à l'attention MLA,
00:59:33on commence à observer des optimisations.
00:59:38Je crois qu'hier soir, nous faisions des benchmarks.
00:59:43Je tiens à corriger un point.
00:59:45Ce n'était pas 56 fois.
00:59:48C'était 14 fois.
00:59:50Il y avait une erreur de calcul dans la démo, car le nombre de couches n'avait pas été multiplié.
01:00:02Oui.
01:00:03Toutes nos excuses pour cela.
01:00:04Cette MLA représente donc des économies d'un facteur 14 par rapport à l'attention multi-tête.
01:00:15Maintenant que nous avons cerné les points de douleur, les fondations et les optimisations côté modèles, abordons ce qu'il est possible de faire côté service.
01:00:31Premièrement, lors d'une simple étape de décodage, on récupère les poids du modèle puis on recalcule les vecteurs de clé et de valeur pour tous les jetons précédents,
01:00:50alors même que ces vecteurs ont déjà été calculés pour tous les jetons.
01:00:55Il y a donc un gaspillage considérable de calculs.
01:01:02Si l'on analyse la complexité temporelle, elle s'élève à O de N carré.
01:01:07La solution consiste à faire un compromis classique avec la mémoire.
01:01:12On peut conserver en mémoire ces vecteurs associés aux jetons et s'y référer.
01:01:19Cette mémoire est appelée le cache KV.
01:01:24Le flux ressemble à ceci.
01:01:27À partir de ce cache KV, quatre optimisations majeures étaient réellement possibles.
01:01:35La première concerne l'attention paginée.
01:01:42Quelle est la différence et le problème actuel ?
01:01:45Donc, quand vous envoyez plusieurs requêtes en entrée au GPU, ces requêtes se trouvent dans un lot.
01:01:52À chaque requête est alloué un espace de stockage en mémoire continu.
01:01:58Disons, pour prendre un exemple, disons 2 KV.
01:02:05Cependant, votre requête n'avait besoin que de 1 KV, par exemple.
01:02:11Il y a donc environ 50 % de fragmentation de cette mémoire.
01:02:19Et cette fragmentation entraîne fondamentalement un gaspillage de mémoire.
01:02:24Cela signifie qu'il y avait de l'espace en mémoire où vous auriez pu traiter davantage de requêtes, mais vous ne l'avez pas pu parce que vous cherchiez ce bloc de mémoire contigu.
01:02:36L'inspiration a donc été tirée du fonctionnement des systèmes d'exploitation.
01:02:41Vous maintenez une mémoire logique et vous avez essentiellement une mémoire physique.
01:02:47Ainsi, dans la mémoire logique, on a toujours l'impression que le vecteur KV pour chaque jeton est contigu.
01:02:59Mais il sera mappé à une adresse physique différente.
01:03:06Cela a donc vraiment aidé à économiser beaucoup de mémoire.
01:03:11Et cela n'a été possible que parce qu'ils considèrent la mémoire comme un ensemble de blocs.
01:03:18Et vous allouerez dynamiquement ces blocs selon les besoins des requêtes.
01:03:22Au fur et à mesure que de nouveaux jetons arrivent et ont besoin de ce type de mémoire.
01:03:27Un autre levier intervient lorsque vous envoyez plusieurs requêtes dans le lot,
01:03:39le GPU prend ces requêtes.
01:03:42Mais il n'accepte pas le nouveau lot tant que toutes les requêtes de ce lot ne sont pas terminées.
01:03:49Le diagramme ressemble donc davantage à de la pagination.
01:03:52Mais ici, il s'agit plutôt de savoir quand le GPU est disponible pour prendre le lot suivant.
01:03:59Il y a donc une période où le GPU reste vraiment inactif.
01:04:04Et vous voulez résoudre ce problème.
01:04:08Et pour cela, l'idée était de dire : d'accord, faisons du traitement par lots continu.
01:04:17Le traitement par lots continu a également beaucoup aidé le débit, car vous pouvez désormais expédier plus de requêtes assez rapidement.
01:04:24En veillant à ce que le GPU soit toujours occupé et ne reste pas inactif.
01:04:33Vous économisez donc sur cette puissance de calcul.
01:04:36Le troisième point est la mise en cache des préfixes.
01:04:39Vous vous souvenez que le cache KV vous a aidé à économiser du calcul pour une seule requête à travers les jetons.
01:04:46Mais que se passe-t-il si vous avez les mêmes jetons dans plusieurs requêtes ?
01:04:53Comment faites-vous pour économiser face à cela ?
01:04:55La mise en cache des préfixes, introduite par VLLM, contre exactement cela.
01:05:04Et puis le troisième, enfin, nous avons parlé du quatrième en fait.
01:05:10Nous avons parlé de la quantification du modèle, mais vous pouvez aussi quantifier les poids KV.
01:05:21Cela signifie que vous avez maintenant besoin de moins d'espace pour vos vecteurs clés et valeurs.
01:05:28Cela signifie que vous pouvez stocker plus de vecteurs clés et valeurs en mémoire.
01:05:32Et cela signifie que vous pouvez traiter plus de jetons.
01:05:34Cela signifie que vous pouvez offrir une limite de contexte plus élevée.
01:05:37Et cela signifie que vous pouvez servir un modèle de meilleure qualité.
01:05:41Et tout cela est déjà présent dans VLLM.
01:05:51Vous n'avez vraiment pas besoin réinventer la roue.
01:05:56Et vous pouvez déployer ce VLLM en production et voir cette croissance.
01:06:03Ensuite, nous avons un benchmark que nous avons réalisé.
01:06:08Ce benchmark, voyons si je l'ai sous les yeux.
01:06:14Ici.
01:06:17Les démos.
01:06:22Réaliser ce benchmark prend environ une heure car il faut sans cesse arrêter et redémarrer les serveurs VLLM, charger les modèles, etc.
01:06:35Cela prend donc beaucoup de temps pour les tests, mais je peux vous expliquer ici ce que nous faisons.
01:06:41Nous avons gardé le même modèle, à savoir Mistral 7B.
01:06:46Ensuite, nous avons l'ensemble des questions d'entrée que nous envoyons.
01:06:53Considérez-les comme les prompts.
01:06:56Ensuite, nous avons quelques fonctions auxiliaires ici, comme vérifier si le serveur est actif ou non.
01:07:01Ce serveur est le serveur VLLM.
01:07:04Il y a ensuite des fonctions auxiliaires pour obtenir les métriques de VLLM.
01:07:09Et je parlerai de ce que sont ces métriques.
01:07:14Ensuite, il y a beaucoup de benchmarks et tout le reste.
01:07:17Et il faut mesurer l'utilisation du cache KV, etc.
01:07:22Ce sont donc les fonctions auxiliaires.
01:07:24La référence est très simple.
01:07:26Nous avons une référence Hugging Face.
01:07:29Il s'agit d'un envoi brut de texte au LLM pour obtenir la réponse en retour.
01:07:36Nous voyons quelques résultats ici.
01:07:38Nous avons constaté que Hugging Face a un débit d'environ 51 jetons par seconde.
01:07:44Le temps d'attente avant le premier jeton était d'environ 54.
01:07:46Et la latence inter-jetons était de 19.
01:07:49Tout cela a été exécuté sur H100.
01:07:56Ensuite, nous démarrons un serveur VLLM par défaut.
01:08:00Par défaut, VLLM vous fournit la pagination, le traitement par lots continu et le cache KV.
01:08:08Ces trois éléments sont donc présents par défaut.
01:08:13Et lorsque vous essayez de comparer ces benchmarks, vous voyez que votre débit est multiplié par près de 15.
01:08:21Vous êtes capable de traiter plus de jetons par seconde.
01:08:24Ensuite, le temps d'attente avant le premier jeton augmente également.
01:08:34Puis la latence inter-jetons diminue en quelque sorte.
01:08:38Et votre utilisation du cache KV par rapport aux utilisateurs et au contexte augmente, c'est sûr.
01:08:43Maintenant, lorsque vous y appliquez la mise en cache des préfixes.
01:08:51Avec la mise en cache des préfixes, vous voyez que votre débit augmente encore plus.
01:08:57Votre TTFT diminue.
01:08:59Votre latence inter-jetons est approximativement la même.
01:09:02Et l'utilisation de votre cache KV par rapport aux utilisateurs diminue en quelque sorte.
01:09:09Par rapport au contexte, elle ne diminue pas.
01:09:12Elle est approximativement la même.
01:09:14Je pense que c'est également approximativement la même.
01:09:16Ce n'est pas si important que ça.
01:09:19Lorsque vous y appliquez la quantification KV par-dessus.
01:09:25Le débit est donc presque similaire.
01:09:33Votre temps jusqu'au premier jeton est similaire.
01:09:36Votre latence de jeton est similaire.
01:09:39Mais votre utilisation de la mémoire KV diminue réellement.
01:09:42C'est parce que vous avez quantifié votre espace clé-valeur.
01:09:49Et puis il y a le concept de décodage spéculatif dont Tanmay va parler.
01:09:54Lorsque vous essayez de les évaluer, vous remarquez également qu'il y a un peu moins d'utilisation du cache KV.
01:10:06Bien que les résultats soient approximativement les mêmes.
01:10:08Donc oui, globalement, ce sont les métriques dans l'ensemble.
01:10:21Je devrais probablement faire un zoom arrière.
01:10:25D'accord.
01:10:26Ce n'est pas...
01:10:27Zoom arrière.
01:10:28Ça ne marche pas.
01:10:29Super.
01:10:30Voilà donc pour les benchmarks de VLLM.
01:10:35C'est votre configuration de production par défaut, au fait.
01:10:38Nous partagerons également cet arbre de décision lorsque nous aborderons les autres moteurs.
01:10:48Donc, oui, nous devrions parler de quelques autres optimisations d'inférence que nous pouvons faire par-dessus.
01:10:58Et quelles sont quelques-unes des autres solutions qui sont apparues.
01:11:02J'aimerais donc inviter à nouveau Tanmay.
01:11:07Il va nous parler de certaines de ces optimisations.
01:11:11Oh, désolé.
01:11:12Je suis vraiment désolé.
01:11:13Je n'ai pas activé les diapositives.
01:11:26C'était quoi ?
01:11:27D'accord.
01:11:28Super.
01:11:29Parfait.
01:11:30Laquelle ?
01:11:31Le décodage spéculatif.
01:11:32Oui.
01:11:33Merci, Harshal.
01:11:34Oui.
01:11:35Donc tout cela concerne le décodage spéculatif.
01:11:40Tout cela, ce sont, comme on dit, différentes saveurs du même soda.
01:11:46Cette technique relève donc des accélérateurs de décodage.
01:11:51La première, donc nous parlons uniquement de ce décodage spéculatif, mais il existe d'autres variantes, comme le auto-spéculatif, Eagle, Medusa.
01:12:01J'aime seulement, je crois, celle-ci, l'algorithme Eagle.
01:12:05Commençons donc par le décodage spéculatif.
01:12:06D'accord.
01:12:07D'accord.
01:12:08Commençons par ce qu'est le décodage spéculatif.
01:12:09Le problème principal est que dans l'architecture Transformer, tous ces jetons sont générés séquentiellement, un par un.
01:12:26Pourquoi ne pas utiliser un modèle plus petit pour lui laisser générer, disons, quatre ou cinq jetons ?
01:12:37Et ce modèle enseignant, ou qu'on peut appeler, selon notre algorithme de Coupe du Monde, un arbitre.
01:12:43L'arbitre décidera donc du nombre de jetons qu'il accepte.
01:12:48Et cette boucle continue de tourner.
01:12:51Notre hypothèse est qu'il existe certains domaines où ce genre de choses fonctionne.
01:12:59Comme peut-être dans le code, où il n'y a presque aucune créativité.
01:13:05Chaque code ou syntaxe est presque similaire.
01:13:08Cela peut donc peut-être aider.
01:13:10Mais, d'après mes tests personnels, je n'ai pas trouvé ce décodage spéculatif utile du tout.
01:13:18Cependant, d'autres techniques comme le décodage auto-spéculatif, où le modèle enseignant possède aussi une tête, une tête auxiliaire, et fait un travail similaire à ce que fait ce modèle de base ou petit modèle.
01:13:35Mais ensuite est arrivé EGLE, EGLE 1, 2, 3, je ne sais pas combien de versions il y a, qui propose simplement qu'au lieu de générer des jetons, entraînons un petit modèle et récupérons les caractéristiques de l'une de ses couches principales pour que, au lieu de générer des jetons, il génère cette caractéristique.
01:14:02EGLE est donc meilleur comparé à ce type d'autres technologies.
01:14:10Et puis une autre est MEDUSA, qui consiste simplement à générer tous ces jetons en parallèle.
01:14:17D'accord, alors ici, alors ici sur cette diapositive.
01:14:21Oui.
01:14:22La diapositive suivante.
01:14:24D'accord.
01:14:25D'accord.
01:14:26D'accord, oui.
01:14:27D'accord.
01:14:28Maintenant nous arrivons à, nous allons aborder la mise en cache de préfixe.
01:14:34Donc, je ne sais pas si les gens utilisent cette mise en cache de préfixe statique ou non.
01:14:39Mais le fait est que le principal problème avec la mise en cache de préfixe, c'est que parfois nous tapons et faisons une petite erreur.
01:14:47Et cette mise en cache de préfixe statique standard prend essentiellement un prompt et effectue un hachage.
01:14:53Et la prochaine fois que l'utilisateur pose un type de question similaire, il essaie de faire correspondre le hachage.
01:14:58Donc, si le hachage est égal, au lieu de recalculer tous ces k et v, il les récupère simplement depuis le stockage.
01:15:09Mais vous savez que parfois nous faisons une erreur ou que nous changeons un mot ou une lettre, quelque chose comme ça.
01:15:15Nous avons alors un taux d'échec de cache beaucoup plus élevé.
01:15:21C'est pourquoi celui-ci, l'arbre de Radix.
01:15:25L'arbre de Radix devient donc très populaire, aussi en raison des agents.
01:15:30Je pense donc que tout le monde ou presque utilise des agents et que la majeure partie du calcul se fait pendant le temps d'exécution, lors de l'inférence.
01:15:38Où l'on ne cesse de poser les mêmes types de questions et de prompts.
01:15:42Par exemple, vous êtes un ingénieur logiciel expert multiplié par 200 fois.
01:15:48Ce genre de boucle se répète continuellement au sein de ces processus agents.
01:15:55Il est alors nécessaire de conserver ou de stocker des éléments similaires dans un arbre de Radix.
01:16:03Un arbre de Radix n'est donc qu'une version avancée de cet arbre de préfixes où l'on fusionne un nœud s'il n'a pas de branche.
01:16:17Et pour ce genre de travail où l'on répète sans cesse la même chose.
01:16:24Cet arbre de Radix aide beaucoup et sglang utilise ce type d'algorithme pour la mise en cache des préfixes.
01:16:35D'accord, oui, ensuite il y a autre chose.
01:16:38L'un d'eux est Tensor RT-LLM.
01:16:41C'est très confus.
01:16:42Quand j'ai commencé, j'étais tout simplement perdu.
01:16:46Qu'est-ce que Tensor RT-LLM ?
01:16:49Donc, oui, Tensor RT est juste un SDK standard en quelque sorte.
01:16:55Tensor RT-LLM est simplement un moteur d'inférence.
01:16:59Tout comme vLLM, sglang.
01:17:01Mais le problème est que c'est lié à NVIDIA.
01:17:05Ils ont optimisé chaque couche et chaque problème.
01:17:10Comme je l'ai mentionné dans notre algorithme de la Coupe du Monde, ils cassent tout et ont tout optimisé également au niveau matériel.
01:17:18Alors, oui, d'accord, suivant.
01:17:23Oui, donc pour cet atelier, nous avons également fait des benchmarks pour voir quel est le meilleur.
01:17:33Notre configuration était donc similaire.
01:17:36Nous avons donc fait deux types de tests.
01:17:39Le premier sans test d'agent, où nous...
01:17:44Nous avons donc utilisé le jeu de données ShareGPT et posé ces questions en utilisant vLLM et sglang.
01:17:57D'accord.
01:18:05Oui, d'accord.
01:18:07Et laissez-moi simplement faire un zoom avant.
01:18:12D'accord, super.
01:18:13D'accord, oui, alors pour cet atelier, nous avons utilisé des H100, et notre premier test a consisté à demander...
01:18:23Nous avons pris des questions de ShareGPT pour les soumettre à vLLM et sglang, et nous avons constaté qu'il n'y a en fait aucune différence statistique quant au meilleur.
01:18:34Les deux ont donc des résultats presque similaires...
01:18:37Les deux répondent donc à un nombre de requêtes par seconde, un TTFT et une latence similaires.
01:18:43Cependant, la seule différence que nous avons observée concerne la ramification des agents.
01:18:50Nous avons donc posé une question similaire : vous êtes le meilleur ingénieur logiciel au monde.
01:18:59Résolvez donc le problème de la congestion du trafic dans la ville, par exemple.
01:19:04Ensuite, nous avons soumis cela au LLM.
01:19:08Le LLM génère un résultat.
01:19:10Puis nous avons fait un deuxième tour également.
01:19:13Une fois ce résultat généré par le LLM, au tour numéro deux, nous avons spécifiquement mentionné...
01:19:22de fournir, d'examiner la proposition et de donner des notes de un à dix.
01:19:28Ce sont les deux tours que nous avons faits, et cette boucle se répète sans cesse.
01:19:35Ce que nous avons découvert, c'est que pour ce type de flux de travail où tout est standard, tous ces prompts et l'ingénierie de contexte entrent en jeu.
01:19:47Par conséquent, si nous effectuons correctement cette ramification d'agents, je pense que SGLang est trois à quatre fois meilleur.
01:19:54Mais encore une fois, cela dépend peut-être des différentes configurations.
01:19:58Si vous le faites, vous obtiendrez peut-être des résultats différents.
01:20:02D'accord.
01:20:03Oui.
01:20:04Alors, je pense...
01:20:06L'avons-nous mis sur GitHub ?
01:20:08Oui.
01:20:09D'accord.
01:20:10Oui.
01:20:11Le PDF est également dans le Drive.
01:20:15C'est le même lien que les diapositives.
01:20:18Un bref résumé donc.
01:20:22Sur un débit de charge de travail d'API standard, vous verriez que vLLM et SGLang se valent.
01:20:31Donc, si vous n'avez pas...
01:20:33Si vous avez une charge de travail standard, optez définitivement pour vLLM.
01:20:36C'est le choix par défaut pour la production de toute façon.
01:20:38Mais ce que disait aussi Tanmay, c'est que lorsque vous essayez de créer des charges de travail d'agents, c'est là que SGLang brille vraiment.
01:20:50Et il vous apporte en quelque sorte tous les avantages.
01:20:55Donc, oui.
01:20:58Gardez vLLM comme valeur par défaut.
01:21:00Mais si vous avez des charges de travail d'agents, essayez probablement de vous tourner vers SGLang.
01:21:05Si vous n'êtes pas satisfait de la partie vLLM.
01:21:09D'accord.
01:21:10Laissez-moi...
01:21:15Attendez.
01:21:18D'accord.
01:21:21Et puis il y a aussi...
01:21:29Une comparaison qui a été faite à 120 milliards.
01:21:33Pour le modèle GPT OSS 120 milliards.
01:21:36C'est un benchmark préparé par PlayPy.
01:21:42Il y a donc un lien vers un blog ici.
01:21:46Oh, super.
01:21:48D'accord.
01:21:49Oui.
01:21:50Ils ont fait un benchmark similaire et y ont inclus TensorRT-LLM.
01:21:57Vous pouvez toujours consulter ces benchmarks et essayer de comprendre ce qui correspond le mieux à votre cas d'usage.
01:22:05Comme nous l'avons mentionné pour TensorRT, ils essaient d'optimiser le côté matériel également, pour obtenir des performances matérielles maximales.
01:22:16Et puis, lorsqu'il s'agit de choisir vos moteurs, une fois que vous avez tranché entre vLLM, SGLang, TensorRT, il y a de nouveaux moteurs qui font leur apparition.
01:22:29NVIDIA Dynamo assurément.
01:22:43Ils sont également orientés vers le routage de sessions pour agents.
01:22:49Hugging Face est toujours là.
01:22:51C'est une évidence.
01:22:53Ensuite, il y a le moteur MSTAR qui a été récemment proposé par Stanford.
01:23:00NVIDIA Dynamo pour les modèles multiples.
01:23:05Vous pouvez donc certainement les explorer.
01:23:08Et pour donner un bref résumé, nous commençons par une référence.
01:23:15Nous essayons de trouver quel modèle peut convenir à nos cas d'usage.
01:23:22Vous pouvez choisir un DeepSeek.
01:23:27Vous pouvez choisir, ne choisissez pas Mistral 7B.
01:23:30Je veux dire, il n'est pas bon.
01:23:32Mais, oui.
01:23:35Vous choisissez donc votre modèle et vous voulez avoir une mémoire plus petite tout en essayant de faire tenir ce plus grand modèle dans cette mémoire plus petite.
01:23:44De cette façon, vous économisez sur les coûts de GPU.
01:23:47Vous pouvez donc faire toute cette quantification.
01:23:51Ensuite, vous appliquez toutes ces optimisations de service en utilisant le bon moteur de service sous le capot.
01:23:58Cela peut vraiment vous offrir le débit que vous désirez tant.
01:24:07Et maintenant, ce que vous pouvez faire en rentrant chez vous, puisque nous ne pouvons pas aborder tout le matériel ici, c'est de lire certaines des informations sources, comme les différents mécanismes d'attention ou ces différents moteurs.
01:24:27Essayez simplement de lire les différents benchmarks disponibles en ligne également.
01:24:34Il y a ensuite de nombreux guides approfondis ou les phases suivantes, comme l'apprentissage de certaines stratégies d'éviction de cache KV.
01:24:45Le monde s'oriente donc vers l'émergence d'un domaine distinct d'ingénierie du cache KV.
01:24:50Vous voulez donc comprendre ce qui s'y passe.
01:24:52Éviction KV, compressions de cache, mémoires hybrides.
01:24:57Il y a donc beaucoup de solutions qui voient le jour dans ce domaine.
01:25:01Essayez toujours de vous en tenir à ces bases, aux fondamentaux ou aux premiers principes.
01:25:08Et essayez de voir quelle solution résout quel problème et si vous avez réellement besoin de résoudre ce problème pour votre cas d'usage.
01:25:17Ensuite, il y a l'inférence de LLM distribuée, qui est un sujet totalement différent.
01:25:26Il faudrait probablement aussi un atelier de deux heures rien que pour passer en revue tous les mécanismes internes et faire de la pratique.
01:25:40Oui, et c'est ce que nous essayons de proposer pour la session AI Engineer à New York, à savoir approfondir les sections avancées de l'inférence de LLM.
01:25:51Cet atelier était donc plutôt destiné aux niveaux débutant et intermédiaire.
01:25:55Donc, dans ce formulaire, nous avons également des retours ainsi que l'intérêt.
01:26:02Si vous pensez que nous devons apporter certaines améliorations dans certaines sections, n'hésitez pas à donner votre avis.
01:26:09Et si vous souhaitez voir cet atelier à New York, je veux dire, n'hésitez surtout pas à manifester votre intérêt.
01:26:22Hein ?
01:26:24Oh, comment est-ce possible ?
01:26:27Boum.
01:26:32Laissez-moi juste vérifier.
01:26:37D'accord.
01:26:38Hein ?
01:26:39Oui.
01:26:40L'URL fonctionne, n'est-ce pas ?
01:26:41Oui.
01:26:42Pas le code QR ?
01:26:43D'accord.
01:26:44J'ai probablement oublié de lier les deux ensemble.
01:26:45D'accord.
01:26:46Cool.
01:26:46Oui.
01:26:47Donc, si vous pouvez le donner.
01:26:49D'accord.
01:26:50Cool.
01:26:51Oui.
01:26:52Donc, si vous pouvez le donner.
01:26:53D'accord.
01:26:54Oui.
01:26:55D'accord.
01:26:56Cool.
01:26:57Oui.
01:26:58Donc, si vous pouvez le donner.
01:26:59Laissez-moi juste.
01:27:00D'accord.
01:27:00D'accord.
01:27:01Cool.
01:27:02Oui.
01:27:02Donc, si vous pouvez le donner.
01:27:03Laissez-moi juste.
01:27:04D'accord.
01:27:05Ça ira.
01:27:06Euh, et oui, je pense que nous aimerions conclure cet atelier maintenant.
01:27:13Et je suis sûr que beaucoup d'entre vous ont pas mal de questions.
01:27:14Donc, nous pourrons aborder tout cela hors ligne.
01:27:15Euh, nous pourrons nous rencontrer, euh, et discuter de ces questions.
01:27:16Oui.
01:27:17Bien sûr.
01:27:18Bien sûr.
01:27:19Euh, merci à tous.
01:27:20Merci d'avoir participé.
01:27:21Euh, je pense que c'était vraiment.
01:27:22Euh, merci à tous.
01:27:23Euh, merci à tous.
01:27:24Merci d'avoir participé.
01:27:25Euh, merci à tous.
01:27:26Merci d'avoir participé.
01:27:27Euh, je pense que c'était vraiment.
01:27:28Oh, d'accord.
01:27:29Euh, d'accord.
01:27:30Ça ira.
01:27:31Euh, et oui, je pense que nous aimerions conclure cet atelier alors.
01:27:33Euh, et oui, je pense que nous aimerions conclure cet atelier alors.
01:27:36Et je suis sûr que beaucoup d'entre vous auraient beaucoup de questions.
01:27:39Donc, nous pouvons régler tout cela en quelque sorte hors ligne.
01:27:41Euh, nous pouvons nous rencontrer, euh, et nous pouvons, euh, parler de ces questions.
01:27:42Oui, bien sûr.
01:27:43Euh, merci à tous.
01:27:44Euh, je pense que c'était vraiment constructif et que vous êtes tous venus ici.
01:27:49Euh, un grand merci.
01:27:50Oui, merci.
커뮤니티 글
아직 글이 없습니다. 이 영상에 대한 첫 번째 글을 작성해 보세요!
이 영상에 대해 글쓰기