URL: https://linuxfr.org/users/aboulle/journaux/introduction-pratique-aux-grands-modeles-de-langage-llm Title: Introduction pratique aux grands modèles de langage / LLM Authors: aboulle Date: 2024年03月03日T11:22:56+01:00 License: CC By-SA Tags: intelligence_artificielle, grands_modèles_de_langage, programmation et hugging_face Score: 73 Dans cet article je me propose de faire une introduction aux [grands modèles de langage](https://fr.wikipedia.org/wiki/Grand_mod%C3%A8le_de_langage), et en particulier à leur utilisation en local (*i.e.*, hors ligne). Si vous ne souhaitez pas vous inscrire sur des plateformes en ligne, que vous êtes soucieux de l'utilisation qui est faite de vos données, que vous ne souhaitez pas débourser le prix d'un abonnement, mais que toutefois ces technologies suscitent votre intérêt, alors cet article est peut-être pour vous. Je n'aborderai pas les aspects théoriques/algorithmiques, et je serai amené à faire des simplifications que des experts jugeront probablement excessives. Par ailleurs, ce journal sera truffé d'anglicismes. Bien qu'il existe la plupart du temps des équivalents en français, la majorité des ressources disponibles étant en anglais, pour des raisons pédagogiques il m'a semblé préférable de garder les termes anglophones. ## 1\. Notions de base ### 1.1 Fonctionnement d'un LLM (Large Language Model) La tâche d'un LLM est de prédire le prochain mot d'une séquence de mots passée en instruction. De façon schématique un générateur de texte fonctionne de la façon suivante: - le texte entré par l'utilisateur est découpé en *tokens*. Ces tokens correspondent à des mots, des fractions de mots, des signes de ponctuations, de symboles d'instructions... chaque LLM a son propre système de *tokenisation*. - les tokens sont vectorisés, c'est-à-dire convertis en vecteurs qui seront ensuite injectés dans le réseau de neurones. L'ensemble de ces vecteurs forme un *embedding*. Les coordonnées des vecteurs-tokens sont déterminées pendant la phase d'apprentissage du réseau de neurone. - la liste de ces vecteurs (ainsi que la liste de leur position) sont entrés dans le réseau de neurone. Les réseaux de neurones sont basés sur des modèles [transformers](https://fr.wikipedia.org/wiki/Transformeur), inventés par Google [\[1\]](https://arxiv.org/pdf/1706.03762.pdf) et qui constituent la base de tous les outils tels que [chatGPT](https://chat.openai.com/) d'openAI et d'autres, notamment la start-up française Mistral qui a réussi à s'imposer comme un acteur majeur du secteur. - la sortie du réseau de neurones est une série de tokens associée à une distribution de probabilité. Une tâche essentielle consiste à choisir un token parmi ceux proposés. C'est le travail du *sampler*. - dans les modèles de type [GPT](https://fr.wikipedia.org/wiki/Transformeur_g%C3%A9n%C3%A9ratif_pr%C3%A9-entra%C3%AEn%C3%A9) (*Generative pre-trained transformer*) la génération d'une grande suite de mot est conduite de façon auto-régressive: le token prédit est ajouté à la séquence d'entrée pour prédire le suivant, et ainsi de suite jusqu'à ce qu'un critère d'arrêt soit vérifié. ### 1.2 Considérations matérielles Une des particularités des réseaux de neurones est leur taille imposante. Par exemple le modèle Llama 13B de Meta pèse 25 Go; "13B" signifie ici que le modèle contient 13 milliards de paramètres (poids et biais) ; ces paramètres sont codés sur 16 bits, de sorte que la taille finale du modèle, en octets, est 2 fois le nombre de paramètres. Pour chaque token généré, l'intégralité du réseau de neurone doit être transféré de la mémoire vers les unités de calculs. Ceci implique que 1) le modèle doit tenir en mémoire, 2) la bande passante doit être suffisante pour ne pas altérer la vitesse de génération, et 3) les unité de calculs doivent pouvoir exécuter le réseau de neurone rapidement. Exemple concret. Je dispose d'une carte [Nvidia Quadro P5000](https://www.techpowerup.com/gpu-specs/quadro-p5000.c2864), datant de 2016, avec 16Go de vRAM, FP16 = 138.6 GFLOPS en float 16 et une bande passante de BW = 288.5 Go/s. Le rapport opération par octet (FP16/BW) vaut 0.48. Ceci doit être comparé à [la densité arithmétique du réseau de neurone](https://www.baseten.co/blog/llm-transformer-inference-guide/) [\[2\]](https://arxiv.org/pdf/2205.14135.pdf "https://www.baseten.co/blog/llm-transformer-inference-guide/"). Pour Llama 13B, une fois le modèle chargé en mémoire, pour une inférence, il y a 0.2Go de données déplacées et 12.8 milliards d'opérations, soit un rapport opération par octet de ~63 ce qui dépasse largement les capacités de la carte (pour le détail des calculs je vous renvoie au [site précédent](https://www.baseten.co/blog/llm-transformer-inference-guide/)). Dans le cas de ce GPU, nous sommes limités par sa puissance brute. La vitesse théorique de génération de texte, pour une fenêtre de contexte de 5000 tokens (ce terme sera défini plus bas), est de ~11 tokens/s. En réalité celle-ci est plus proche de 17 tokens/s. La raison de cette différence sera abordée plus loin. À l'inverse pour une [RTX 4090](https://www.techpowerup.com/gpu-specs/geforce-rtx-4090.c3889) (24Go vRAM, FP16 = 82.6 TFLOPS, BW = 1008 Go/s), le rapport opération par octet vaut 83. Dans ce cas, c'est la bande passante mémoire qui limite la vitesse de génération de texte. Pour un modèle 25 Go, et une fenêtre de contexte de 5000 tokens, la vitesse de génération de texte est donc de 40 tokens/s. Étant donnés les tarifs très élevés des GPU haut de gamme, évaluer ses besoins en termes de vitesse de génération peut être à prendre en compte avant l’acquisition d’un GPU dédié aux LLM. À noter qu'il est parfaitement possible de faire tourner un LLM sur CPU, avec cependant des vitesses de génération fortement dégradées. ### 1.3 Compression des modèles et nomenclature Un lecteur attentif aura sans doute noté que le modèle Llama 13B (25Go) ne devrait pas tenir dans la mémoire vidéo de la carte Quadro P5000 (16 Go), ni dans celle de la carte RTX 4090 (24 Go). En effet, et même si cela parait complètement contre intuitif au premier abord, il est possible de réduire le nombre d'octets sur lesquels sont codés les paramètres sans significativement altérer les performances du modèle. Le terme utilisé est *quantization*. Le format historique est [GGML](https://ggml.ai/) (GPT-Generated Model Language) qui permet de réduire l'encodage jusqu'à 4 bits. Développé par Georgi Gerganov (également auteur de l'indispensable bibliothèque [llama.cpp](https://github.com/ggerganov/llama.cpp)), ce format est aujourd'hui obsolète et remplacé par [GGUF](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md) (GPT-Generated Unified Format). Il existe un autre format, [GPTQ](https://github.com/IST-DASLab/gptq) (Generalized Post-Training Quantization), optimisé pour GPU [\[3\]](https://arxiv.org/abs/2210.17323). Le site incontournable pour trouver ces modèles compressés est [huggingface.co](https://huggingface.co/), et en particulier le dépôt de [TheBloke](https://huggingface.co/TheBloke). Ceci nous amène à décrire la nomenclature de nommage des modèles. Par exemple "[codellama-13b-instruct.Q5\_K\_M.gguf](https://huggingface.co/TheBloke/CodeLlama-13B-Python-GGUF)" signifie: - modèle de base: codellama - 13b: 13 milliards de paramètres - instruct: modèle optimisé pour les questions/réponses (chat) - Q5_K: 5 bits de quantization - M: taille moyenne - gguf: le format de compression Avec ce format, le modèle initial de 25Go passe à 9.23Go. TheBloke donne des indications qualitatives sur la dégradation des performances induites par la compression. Par exemple pour Q5\_K\_M: "large, very low quality loss - recommended". Avec ces formats compressés, il est donc possible d’exécuter des modèles 13B et jusqu'à 30B sur des GPUs "grands publics", avec une amélioration significative des performances (en termes de tokens/s) comparativement aux modèles non compressés. ### 1.4 Cas d'étude et aparté sur les licences Il suffit de visiter le site huggingface pour se rendre compte de la quantité pharaonique de modèles disponibles. Dans ce journal je me focaliserai sur des modèles optimisés pour de la génération de code, en particulier Code Llama 13B Instruct, [développé par Meta](https://ai.meta.com/blog/code-llama-large-language-model-coding/) et [compressé à 5bits](https://huggingface.co/TheBloke/CodeLlama-13B-Python-GGUF). *Stricto sensu* les modèles de Meta ne sont pas libres car, même si on a accès au code, qu'on peut le modifier et le redistribuer, l'utilisation commerciale est [limitée à 700 millions d'utilisateurs mensuels](https://ai.meta.com/llama/license/). Cette restriction ne posera pas de problème pour la plupart des lecteurs, mais les plus puristes pourront préférer, par exemple, [StarCoder2](https://huggingface.co/bigcode/starcoder2-15b), ou encore les modèles de [Mistral](https://huggingface.co/mistralai) qui fonctionnent très bien pour de la génération de code et sont distribués sous licence Apache... pour l'instant, puisque leurs derniers modèles ont basculé vers des [licences commerciales](https://mistral.ai/technology/#models). ## 2\. LM studio: le point d'entrée Pour débuter [LM studio](https://lmstudio.ai/) est parfait. Il est disponible sur toutes les plateformes. Il permet l’exécution locale, sur CPU ou GPU, le téléchargement de modèles sans quitter le programme, les modèles sont bien décrits, LM studio indique s'ils sont compatibles avec le matériel (notamment en termes de vRAM). ![LMStudio1](https://i.imgur.com/NhLMZDW.png) Pour chaque modèle LM studio charge des paramètres par défaut (pour le prompt, calcul et sampler) qui fonctionnent, mais qu'il est possible de modifier à loisir, cf. encadré rouge ci-dessous. Comme tout fonctionne *out of the box*, c'est une bonne occasion d'examiner le rôle des différents paramètres de calcul, de l'inférence et du sampler. Ces paramètres sont accessibles dans le volet de droite du programme (encadré en rouge). ![LMStudio2](https://i.imgur.com/5vMQRed.png) **Paramètres de prompt** - Preset: format du prompt, à choisir en fonction du modèle - Pre-prompt: permet de guider les réponses du LLM. L'instruction "You are a helpful coding AI assistant" est celle donnée par défaut. J'ai ajouté "Answer in a concise way" afin de réduire la verbosité des réponses. **Paramètres de calcul** ("*GPU acceleration*") - n\_gpu\_layers : les réseaux de neurones sont formés d'un superposition de couches. Ce paramètre permet d'affecter le calcul d'un certain nombre de couches au GPU. Le reste sera pris en charge par le CPU. Pour Code Llama 13B (9.23 Go), qui compte 40 couches, le modèle tient intégralement dans les 16 Go de vRAM. Dans le cas contraire il aurait fallu mettre un nombre entre 1 et 40. "-1" = affecter toutes les couches au GPU. - n_ctx : la longueur du contexte. C'est le nombre de tokens maximum passé au LLM. Cela va définir la longueur de sa mémoire. Code Llama a été entraîné avec une fenêtre de contexte d'environ 16 000 tokens et peut fonctionner pour des contextes allant jusqu'à 100 000 ! [\[4\]](https://scontent-mrs2-2.xx.fbcdn.net/v/t39.2365-6/369856151_1754812304950972_1159666448927483931_n.pdf?_nc_cat=107&ccb=1-7&_nc_sid=3c67a6&_nc_ohc=demLcwkRHegAX_svkhD&_nc_ht=scontent-mrs2-2.xx&oh=00_AfCO3B2XzCvdlNhMOpvlNLZgWGbakM-3dy6bLMWRUFVVKQ&oe=65E5DD8F "https://ai.meta.com/blog/code-llama-large-language-model-coding/"). En pratique, pour des questions/réponses, quelques milliers suffisent. **Paramètres du sampler** ("*Inference parameters*") - temp (température) : détermine la possibilité pour le sampler de sélectionner un token qui n'a pas la probabilité la plus élevée. On parle souvent de "créativité" du LLM ; "variabilité" serait plus approprié... deux réponses successives au même prompt varieront très fortement pour des températures élevées. Traditionnellement la température varie entre 1 et 2. Pour du code on souhaite que la génération soit la plus factuelle possible, donc de très faibles températures sont préférables. La valeur est ici 0.01. - tokens to generate: nombre maximum de token à générer. "-1" = pas de limite. - top\_k, top\_p et min\_p sont similaires et visent à réduire le nombre de tokens parmi lesquels choisir en sortie du réseau de neurone. top\_k réduit la liste des tokens possibles aux k les plus probables. top\_p, sélectionne les tokens les plus probables dont la somme est égale à top\_p. Les tokens de probabilités inférieure à min_p sont exclus. - repeat_penalty: pénalise les tokens répétés. Pour du code, mettre cette valeur au minimum possible = 1. En tout état de cause, dès lors que la température est proche de 0, les paramètres du sampler n'ont plus d'influence car c'est toujours le token le plus probable qui sera sélectionné. De plus amples informations sont données ici [\[5\]](https://peterchng.com/blog/2023/05/02/token-selection-strategies-top-k-top-p-and-temperature/) [\[6\]](https://medium.com/@daniel.puenteviejo/the-science-of-control-how-temperature-top-p-and-top-k-shape-large-language-models-853cb0480dae) [\[7\]](https://www.reddit.com/r/LocalLLaMA/comments/17vonjo/your_settings_are_probably_hurting_your_model_why/) . La [documentation de ollama](https://github.com/ollama/ollama/blob/main/docs/modelfile.md#parameter), fournit une liste très complète des paramètres qu'il est possible de contrôler, et une description de leur rôle. Comme beaucoup d'autres logiciels du même type, LMstudio est basé sur le projet [Llama.cpp](https://github.com/ggerganov/llama.cpp), mais n'est pas libre. Ceci nous amène donc au logiciel suivant. ## 3\. Oobabooga/text-generation-webui: le couteau suisse [Oobabooga/text-generation-webui](https://github.com/oobabooga/text-generation-webui) est un *frontend* à de nombreux modèles, écrit en Gradio. La prise en main est plus compliquée, mais tout est paramétrable et la licence est libre. L'installation est automatisée via un [script shell](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#how-to-install), ou peut se faire [à la main](https://github.com/oobabooga/text-generation-webui?tab=readme-ov-file#manual-installation-using-conda), ce qui peut être intéressant si une installation de python est déjà présente sur le PC. Première chose à faire, dans l'onglet "Model", télécharger le modèle depuis huggingface, en précisant bien quel niveau de compression pour les modèles GGUF (à droite ci-dessous). Les paramètres de calcul sont à définir dans la partie gauche. Il conviendra de sélectionner le "loader" adapté au model: ici llama.cpp pour codellama-13b. Les captures ci-dessous donnent les paramètres optimisés pour Code Llama. La quantité de paramètres est beaucoup plus importante que pour LM sudio ; la lecture de la [documentation](https://github.com/oobabooga/text-generation-webui/wiki) est fortement recommandée. ![Oobabooga 1](https://i.imgur.com/BOdWEXa.png) Les paramètres d'inférence, tels que définis précédemment, se règlent dans l'onglet "Parameters". Le menu "Preset" permet de sélectionner des types de personnalités pré-configurées, mais tout est paramétrable. ![Oobabooga 2](https://i.imgur.com/wVFJCBl.png) Les paramètres du prompt se règlent dans le sous-onglet "Parameters/Instruction template". Les paramètres par défaut fonctionnent bien. Ici l'instruction (entre les balises ) a été modifiée de la même façon que dans le cas de LM studio. ![Oobabooga 3](https://i.imgur.com/sVrvpUZ.png) Enfin, l'interaction avec le LLM se passe dans l'onglet "Chat" (ou "Default", ou "Notebook", seule la mise en forme change), en prenant soin de sélectionner le format de prompt adapté au modèle (ici, "instruct"). La réponse fournie par LLM est correcte et rigoureusement identique à celle fournie par LM studio avec le même modèle. ![Oobabooga 4](https://i.imgur.com/oz5nP0e.png) ## 4\. Ollama: le client CLI L'interaction avec un LLM se faisant essentiellement en mode texte, une interface en ligne de commande est particulièrement pertinente. L'un des programmes le plus populaire est [ollama](https://ollama.com/download "https://ollama.com/"). Les principaux LLM, et pas seulement ceux basés sur Llama, sont [compatibles avec ollama](https://ollama.com/library). Après installation, dans un terminal il suffit d'entrer, par exemple, `ollama run codellama:13b-instruct`. Si le modèle est déjà présent l'interaction avec le LLM commencera. Si non, ollama téléchargera le modèle, puis initiera l'interaction. Le principal avantage de ollama est sa grande simplicité d'utilisation, permettant de rapidement tester plusieurs LLM. Celui-ci souffre cependant de deux inconvénients. Sous Linux et Windows ollama n'est (pour l'instant ?) pas compatible avec les GPU. Sous Macos, ollama prend en charge les récentes puces M. Un deuxième problème est la valeur des paramètres par défauts. En particulier la température est trop élevée, ce qui amène ollama à produire des réponses qui ne sont pas identiques pour différentes inférences avec le même prompt. Ollama va jusqu'à produire des liens vers des images qui n'existent pas. Ceci est illustré sur la figure ci-dessous. ![ollama1](https://i.imgur.com/ADVTb4u.png) Il est néanmoins possible de modifier ces paramètres, mais de façon moins immédiate que pour les précédents programmes. Pour cela il faudra créer un "[Modelfile](https://github.com/ollama/ollama/blob/main/docs/modelfile.md)" sur mesure: ``` FROM codellama:13b-instruct PARAMETER temperature 0.01 PARAMETER num_ctx 5000 PARAMETER repeat_penalty 1 PARAMETER num_predict -1 PARAMETER top_k 40 PARAMETER top_p 0.95 PARAMETER seed -1 SYSTEM """ You are a helpful coding assistant. Answer in a concise way. """ TEMPLATE """[INST] <>{{ .System }}<> {{ .Prompt }} [/INST] """ PARAMETER rope_frequency_base 1e+06 PARAMETER stop "[INST]" PARAMETER stop "[/INST]" PARAMETER stop "<>" PARAMETER stop "<>" ``` Les paramètres SYSTEM et TEMPLATE définissent le format du prompt. Chaque LLM a sa propre syntaxe. Ici il s'agit de celle pour des LLM basés sur llama. Le respect de la syntaxe est crucial, notamment les espaces et les sauts de lignes, faute de quoi le LLM produira des réponses potentiellement absurdes (NB: cette syntaxe a déjà été aperçue dans le cas de Oobabooga). Il faudra ensuite créer un nouveau modèle contenant ces modifications, avant de pouvoir interagir avec : ```bash> ollama create NomDuModèle -f /chemin/vers/le/fichier/Modelfile> ollama run NomDuModèle ``` Avec ces modifications, les réponses de ollama sont parfaitement répétables et sans "[hallucinations](https://fr.wikipedia.org/wiki/Hallucination_%28intelligence_artificielle%29)". Exemple ci-dessous: ![ollama2](https://i.imgur.com/F1yhybX.png) ## 5\. Développer son propre client CLI Étant donné les limitations de ollama, en particulier l'absence de compatibilité GPU, il peut être intéressant de développer sa propre interface. La principale bibliothèque pour cela est [llama.cpp](https://github.com/ggerganov/llama.cpp). Cette bibliothèque est à la base de la plupart des clients existants à l'heure actuelle. Contrairement à ce que son nom pourrait laisser penser, llama.cpp est compatible avec un très grand nombre de modèles de langage. Cette bibliothèque, comme les programmes précédents, permet de fonctionner en mode serveur web qui pourra être interrogé à distance par des clients n'ayant pas les ressources suffisantes pour exécuter un LLM. Ce mode serveur est intégralement compatible avec l'API de Open AI, de sorte qu'une application développée avec l'API d'Open API pourra se brancher sur un serveur hébergé localement. Autre avantage indéniable, il existe un très grand nombre de *bindings* pour différents langages. En python, il s'agit de [llama-cpp-python](https://github.com/abetlen/llama-cpp-python), qui s'installe via pip. La prise en charge du GPU nécessite au préalable l’installation des outils CUDA, puis l'installation de llama-cpp-python: ```bash> conda install cudatoolkit-dev> CMAKE\_ARGS="-DLLAMA\_CUBLAS=on" pip install llama-cpp-python --no-cache-dir ``` Le simple fichier Test.py ci-dessous : ``` from llama_cpp import Llama llm = Llama(model_path="codellama-13b-instruct.Q5_K_M.gguf", n_gpu_layers=-1, n_ctx=5000, n_batch=512, n_threads=None, n_threads_batch=None, verbose = False) output = llm("Q: plot a sine curve. A:", max_tokens=10000, temperature=0.01, repeat_penalty=1, top_p=0.95, top_k=20, min_p=0, echo=False, stop=["Q"]) print(output['choices'][0]['text']) ``` renvoie la réponse suivante: ``` \begin{code} plot(sin(x),x=0..2*pi) \end{code} ``` Ceci diffère fortement des cas précédents. La raison est que le format du prompt sous la forme de "Q: ... A:", ne respecte pas la syntaxe avec laquelle le réseau de neurones a été entraîné. La syntaxe correcte est celle décrite précédemment dans le cas de ollama. Avec un peu de raffinement, notamment sur la mise en forme du prompt, il est possible d'avoir un client tout à fait acceptable. Pour les lecteurs intéressés, je joins le lien vers mon dépôt github: https://github.com/aboulle/mica Voilà ce que ça donne: ![anim](https://i.imgur.com/2eU456P.gif) Évidemment, tracer une sinusoïde n'est pas une tâche très complexe. Cet exemple a servi ici à régler les différents paramètres. Je vous invite à essayer sur des cas plus complexes. Par ailleurs Code Llama et Code Llama instruct, sont [multilangages](https://ai.meta.com/blog/code-llama-large-language-model-coding), Code Llama Python étant, comme son nom l'indique, optimisé pour Python. Enfin, seul le modèle instruct, présenté dans cet article, est optimisé pour les questions/réponses. Les autres sont recommandés pour de la complétion de code et du *infilling*. Remerciement : la rédaction de ce journal m'a été inspirée par l'excellent Guillaume Poggiaspalla, co-présentateur du non moins excellent et indispensable podcast [Tech Café](https://techcafe.fr/). La qualité de la veille technologique et les efforts de vulgarisation, produits deux fois par semaine, sont tout simplement impressionnants. PS: je ne suis pas du tout affilié avec ce podcast.

AltStyle によって変換されたページ (->オリジナル) /