3 points par GN⁺ 3 시간 전 | 1 commentaires | Partager sur WhatsApp
  • GGUF est le format de fichier de modèle de langage utilisé par llama.cpp ; il regroupe dans un fichier unique les métadonnées nécessaires à l’exécution, ce qui simplifie la distribution et le chargement des modèles
  • Les chat templates sont des scripts Jinja2 qui gèrent le format de conversation, les appels d’outils et l’encodage des messages multimédias, mais leur comportement varie selon les implémentations
  • GGUF peut contenir des tokens spéciaux comme le token de fin, ainsi que des paramètres de sampler recommandés ; récemment, il est aussi devenu possible d’y préciser l’ordre de la chaîne de sampling
  • Le format d’appel d’outils varie encore selon les modèles, ce qui impose du code en dur dans les moteurs d’inférence ; la génération de parseurs à partir d’une grammaire reste une piste d’amélioration du standard
  • Il manque encore think_token, le bundling des modèles de projection et des indicateurs de fonctionnalités, ce qui complique toujours la séparation des phases de réflexion, la configuration multimodale et la détection des capacités prises en charge

Ce que contient GGUF

  • GGUF est le format de fichier que llama.cpp utilise pour les modèles de langage
  • L’avantage central de GGUF est de regrouper dans un fichier unique plusieurs éléments nécessaires à l’exécution du modèle
  • GGUF met ces informations annexes dans un seul fichier, ce qui rend les modèles plus faciles à manipuler

Chat templates

  • Les modèles de langage conversationnels sont entraînés sur des séquences de tokens dans un format spécifique, qui ressemble à une structure de dialogue
  • Voici un exemple de format Gemma4
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
  • Voici un exemple de template au format LFM2
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
  • En pratique, les templates sont bien plus complexes : ils peuvent inclure des blocs de raisonnement, des descriptions d’outils, des appels et réponses d’outils, ainsi que l’encodage de messages multimédias comme les images, l’audio ou la vidéo
  • Ce traitement est assuré par les chat templates, qui sont des scripts écrits dans le langage de template Jinja2
    • On peut par exemple voir le chat template inclus dans Gemma 4
    • Dans les métadonnées GGUF, le chat template par défaut est stocké sous la clé tokenizer.chat_template
  • Un modèle peut avoir plusieurs chat templates
    • Il peut exister un template qui prend en charge les appels d’outils et un autre qui ne les prend pas en charge
    • La plupart des modèles fournissent un seul énorme chat template, qui n’active le traitement lié aux appels d’outils que lorsqu’un outil est spécifié
    • Pour certains modèles, il faut aller chercher séparément un chat template dédié aux outils
  • Jinja2 ressemble davantage à un langage de programmation avec des boucles, des conditions, des affectations, des listes et des dictionnaires
    • Une application LLM conversationnelle doit embarquer un interpréteur capable d’exécuter, à chaque ajout de message, un programme comme le script Jinja d’environ 250 lignes fourni par Gemma
  • Le traitement de Jinja varie aussi selon les implémentations
    • Hugging Face transformers utilise la bibliothèque Python jinja2 existante
    • llama-server et llama-cli de llama.cpp utilisent une implémentation Jinja maison
    • llama_chat_apply_template, exposé dans l’API libllama, est l’ancienne approche qui code en dur directement en C++ quelques formats de chat
    • NobodyWho utilise minijinja, une réimplémentation en Rust par l’auteur original de Jinja
    • À ne pas confondre avec minja, la bibliothèque Jinja minimale qu’a un temps utilisée llama.cpp
  • Il existe d’importantes différences de performance entre les implémentations de Jinja
    • Ce n’est pas un sujet très polémique, car dans les applications LLM locales, le traitement des chat templates n’est pas un goulot d’étranglement de performance

Tokens spéciaux

  • Un modèle de langage peut continuer à produire le token suivant pour une séquence de tokens en entrée ; il faut donc un moyen d’arrêter la génération
  • La solution classique consiste à définir un token de fin : lorsque le modèle l’émet, le moteur d’inférence arrête la génération
  • Le token de fin est un exemple de token spécial
    • Un token spécial porte généralement un sens qui dépasse les simples caractères tokenisés
    • Il ne devrait en général pas être affiché à l’utilisateur, même s’il possède souvent une représentation textuelle et peut donc être affiché
  • Voici quelques exemples de tokens spéciaux de Gemma4
    • 1 / <eos> : fin de séquence, émis par le modèle pour arrêter la génération
    • 2 / <bos> : début de séquence, ajouté en préfixe de l’entrée
    • 46 / <|tool_call> : marque le début d’un appel d’outil
    • 47 / <tool_call|> : marque la fin d’un appel d’outil
    • 105 / <|turn> : marque le début d’un tour de dialogue
    • 106 / <turn|> : marque la fin d’un tour de dialogue

Paramètres de sampler et ordre

  • Les modèles de langage produisent une distribution de probabilité sur le token suivant ; le processus qui consiste à choisir un token dans cette distribution est appelé sampling
  • La méthode la plus simple consiste à tirer au hasard dans la distribution pondérée
  • En pratique, on obtient souvent de meilleurs résultats en appliquant des transformations à la distribution avant de choisir un token précis
  • Lorsqu’un labo publie un nouveau modèle, il fournit souvent en même temps des paramètres de sampler recommandés
  • Il est fréquent que les utilisateurs copient-collent ces valeurs depuis un fichier Markdown ou autre pour obtenir de meilleures réponses
  • Pour réduire ces copies manuelles, NobodyWho a publié une sélection de modèles sur sa page Hugging Face en y associant des paramètres de sampler recommandés dans un format propriétaire
    • Cela fonctionnait, mais il fallait une conversion côté NobodyWho pour rendre les modèles réellement utiles
  • Une fonction récemment ajoutée au format GGUF permet désormais de spécifier directement la chaîne de samplers dans le fichier du modèle
    • Le format propriétaire de NobodyWho est ainsi devenu inutile, ce qui était précisément l’objectif
  • L’application web llm-sampling permet de visualiser rapidement le rôle des différentes étapes de sampling
  • En déplaçant individuellement les étapes par glisser-déposer, on voit que l’ordre des étapes de sampling peut fortement modifier la distribution finale
  • Beaucoup de formats de paramètres de sampler, comme les fichiers JSON des images Ollama ou le generation_config.json de Hugging Face, n’ont aucun moyen d’exprimer l’ordre des étapes de sampling
  • Le standard GGUF permet d’indiquer l’ordre du sampling via le champ general.sampling.sequence
  • Malgré cela, de nombreux modèles GGUF omettent encore ce champ et s’appuient sur l’ordre implicite du comportement par défaut de llama.cpp

Ce qu’il manque encore

  • Un bon moteur d’inférence cherche à proposer une interface unifiée pour des modèles de langage variés
  • Exploiter les informations annexes des métadonnées GGUF permet de réduire fortement le nombre de chemins de code spécifiques à chaque modèle
  • Format d’appel d’outils

    • Presque tous les moteurs d’inférence ont des chemins codés en dur pour parser différents formats d’appel d’outils
    • Voici un exemple de format d’appel d’outils de Qwen3
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
  • Voici un exemple de format d’appel d’outils de Qwen3.5
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
  • Voici un exemple de format d’appel d’outils de Gemma4
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
  • Lorsqu’un nouveau modèle sort, plusieurs moteurs d’inférence doivent chacun implémenter leur propre parseur
  • Si le fichier du modèle embarquait une grammaire et qu’on pouvait en dériver un parseur, ce serait un excellent ajout au standard GGUF
  • NobodyWho ajoute déjà une étape qui génère une grammaire contrainte adaptée aux outils effectivement transmis
    • Cela permet de garantir la sûreté de type des appels d’outils
    • C’est particulièrement utile parce que les petits modèles de 1B ou moins peuvent par exemple fournir un float là où un entier est attendu
  • Même s’il existait une grammaire capable de produire un parseur générique d’appels d’outils, NobodyWho devrait toujours implémenter une fonction qui génère une grammaire spécifique aux outils concrets transmis
  • Un format de méta-grammaire permettant de produire une grammaire concrète adaptée à un outil donné, puis d’en dériver un parseur, reste une piste intéressante
  • Token Think

    • C’est l’élément manquant le plus simple à ajouter
    • Le dépôt Hugging Face upstream a commencé à inclure un champ think_token
    • think_token est très utile pour séparer la phase de réflexion dans les sorties générées
      • Cette phase doit généralement être supprimée ou affichée différemment du contenu principal
    • Les versions GGUF converties downstream n’incluent généralement pas ce champ
    • Résultat : les moteurs d’inférence basés sur GGUF ne peuvent pas séparer le flux de réflexion du flux principal sans ajouter du code spécifique à certaines familles de modèles
    • Ajouter think_token au pipeline standard de conversion vers GGUF résoudrait ce problème
  • Modèles de projection

    • Les interactions avec des LLM multimodaux qui peuvent voir nativement des images et de l’audio, plutôt que du texte, nécessitent un modèle supplémentaire pour traiter les entrées non textuelles
    • Ce modèle supplémentaire est appelé modèle de projection
    • La convention actuelle consiste à fournir deux fichiers GGUF
      • l’un pour le modèle de langage principal
      • l’autre pour un modèle plus petit chargé du traitement des images et de l’audio
    • Cela casse l’avantage du fichier unique de GGUF
    • Il y aurait un gros gain si un seul fichier GGUF pouvait regrouper dans le fichier principal les poids et la configuration du modèle de projection
    • Les modèles de projection pèsent souvent autour de 1 Go
      • C’est suffisamment volumineux pour vouloir éviter cette surcharge lorsqu’on ne s’en sert pas
    • Proposer deux variantes, l’une avec les poids de projection intégrés et l’autre sans, est une approche raisonnable
    • Cela permettrait de revenir à une seule URL à télécharger et à un seul fichier à gérer dans le cache disque
  • Liste des fonctionnalités prises en charge

    • Les fonctionnalités varient selon les modèles, et il est difficile de détecter facilement ce qui est réellement pris en charge à partir du seul fichier GGUF
    • Certains modèles prennent en charge les entrées image, d’autres non
      • À l’heure actuelle, la meilleure approximation consiste à supposer qu’il y a prise en charge des images si un modèle de projection est fourni
    • Certains modèles prennent en charge nativement les appels d’outils, d’autres non
      • À l’heure actuelle, la meilleure approximation consiste à chercher par correspondance partielle de chaîne si le chat template essaie de rendre une liste de schémas JSON d’outils
      • C’est manifestement du bricolage
    • Certains modèles produisent des blocs de réflexion, d’autres non
      • Comme les balises de réflexion ne figurent généralement pas dans les métadonnées GGUF, il n’existe pas de bonne méthode pour savoir si l’on doit s’attendre à des blocs de réflexion
    • Si la communauté GGUF ajoutait des indicateurs de fonctionnalités dans les fichiers de modèle, les bibliothèques d’inférence indépendantes des modèles pourraient fournir des messages d’erreur et des avertissements plus cohérents
      • Par exemple, elles pourraient mieux guider l’utilisateur lorsqu’il tente un appel d’outil avec un modèle qui ne le prend pas en charge nativement

Conclusion

  • GGUF regroupe dans un fichier unique les informations annexes nécessaires pour exécuter correctement un modèle, ce qui évite d’ajouter trop de chemins de code spécifiques à chaque modèle
  • GGUF est un format ouvert, extensible et soutenu par une communauté solide
  • En renforçant ensemble le standard, on peut conserver une bonne expérience développeur tout en facilitant le remplacement des modèles dans les applications
  • Les métadonnées GGUF sont déjà utiles à bien des égards, mais il reste des pistes d’amélioration comme la grammaire d’appel d’outils, think_token, le bundling des modèles de projection et les indicateurs de fonctionnalités

1 commentaires

 
GN⁺ 3 시간 전
Commentaires sur Hacker News
  • C’est dommage que le modèle de projection ait été séparé dans un fichier distinct, et moi aussi j’aurais préféré qu’il soit inclus dans un fichier unique
    Je ne sais pas exactement pourquoi cela a été fait, mais cela s’écarte assez nettement de la philosophie du fichier unique qui avait guidé la conception de GGUF
    J’espère que quelqu’un prendra l’initiative de réunifier les deux, car cette fois j’ai l’impression d’être un peu trop à contre-courant :-)

    • Vu que la prise en charge de MTP est en cours de développement, il semble qu’au cours de cette discussion l’idée de séparer les modèles MTP du GGUF principal, comme Mmproj, ait circulé, mais ait été rejetée
      J’aime bien cette décision. Dans ce cas, il ne me paraît pas déraisonnable de penser qu’il y a aussi une ouverture à inclure les fichiers Mmproj dans le GGUF
      Le seul problème qui me vient à l’esprit est le choix du format à intégrer : BF16, F16, etc.
  • GGML et GGUF ont été extrêmement importants pour l’écosystème open source du machine learning/de l’IA
    Des projets comme llama.cpp, whisper.cpp ou stable-diffusion.cpp fonctionnent en général très bien immédiatement sur différentes plateformes et avec divers backends matériels

    • llama.cpp vient certes de chez Meta, et je déteste vraiment Meta, mais il faut reconnaître que c’est le plus simple à utiliser par rapport aux autres
      Il suffit de compiler, d’ajouter le modèle et de lancer. On obtient alors aussi une interface web et une API
  • > <|turn>user Hi there!<|turn>model Hi there, how can I help you today
    Mon Dieu, ils ont réussi à créer un format encore moins lisible que du XML

    • Ce n’est pas un format conçu pour être lu par des humains. En pratique, on n’a presque jamais besoin de l’inspecter
      Il a été conçu pour ne pas être confondu avec le contenu réel, lequel peut être n’importe quel texte venant d’Internet
      Pour cela, il faut utiliser un format qui n’est employé nulle part ailleurs
    • Oui. Du point de vue de l’efficacité mémoire, cela ne semble pas être un format optimal
  • À mes yeux, ce qui manque le plus aujourd’hui, c’est une manière de définir l’architecture du modèle sans la coder en dur dans le build courant
    Il n’est pas nécessaire d’avoir une équivalence de performance 1:1 avec les modèles entièrement pris en charge
    Le fait qu’il existe, dès le jour de la sortie, une prise en charge correcte validée par le fournisseur fait toute la différence entre un modèle perçu comme excellent ou comme catastrophique. Les sorties récentes de Gemma et Qwen en sont un bon exemple
    Je ne sais pas quelle est la bonne solution, mais on pourrait imaginer écrire un DSL décrivant le graphe du modèle et l’inclure dans le GGUF
    Une autre possibilité serait de lire le module PyTorch de la version officielle du modèle et de le convertir d’une manière ou d’une autre en opérations GGML

    • Un espace a volontairement été réservé dans la spécification GGUF pour inclure un graphe de calcul, et j’espérais que quelqu’un reprendrait l’idée
      J’aurais voulu l’intégrer dès la première version, mais à l’époque la priorité était plutôt de publier une spécification minimale fonctionnelle et de la faire implémenter
      J’aimerais toujours voir cela arriver, mais il faudrait quelqu’un qui connaisse extrêmement bien l’état actuel de l’IR de GGML pour porter le projet
    • On pourrait probablement embarquer le graphe de calcul dans le fichier de poids, un peu comme ONNX
      Ensuite, on exposerait une interface commune prenant des paramètres communs, et les paramètres personnalisés supplémentaires pourraient être gérés sous forme d’extensions, à la manière de Wayland
      Cela permettrait de prendre en charge non seulement des familles de transformeurs comme LLaMa, mais aussi des réseaux récurrents comme RWKV, ainsi que des modèles multimodaux
      Je ne sais pas à quoi ressemblerait l’implémentation concrète, mais l’idée me paraît excellente. Cela dit, si le graphe de calcul est figé dans le fichier du modèle, j’ai peur que des améliorations d’architecture ou des optimisations ne nécessitant pas de changer les poids ne puissent pas être appliquées aux fichiers existants sans conversion
  • > Ce qui est vraiment élégant avec GGUF, c’est qu’il s’agit d’un seul fichier. Comparé à un dépôt safetensors typique sur Hugging Face, avec les fichiers JSON nécessaires éparpillés un peu partout [...]
    Fait intéressant, pour moi un modèle d’IA a « toujours » été un fichier unique. C’était la norme côté génération d’images en local
    Un fichier safetensors peut aussi contenir toutes sortes de choses en interne, donc GGUF n’est pas strictement indispensable pour cela
    Cela dit, les encodeurs de texte des modèles modernes sont eux-mêmes des modèles de langage de plusieurs gigaoctets, donc personne ne va inclure une copie dupliquée dans chaque checkpoint

    • La distribution en fichier unique était un objectif de conception que je m’étais fixé délibérément
      La plupart des modèles d’image étaient, ou sont encore, distribués sous forme de fichier unique, mais les safetensors des LLM, du moins à l’époque, ne l’étaient pas, et je voulais l’imposer au niveau de la structure même
      Je ne voulais pas non plus exiger d’un exécuteur, par exemple llama.cpp, qu’il ait un lecteur JSON, alors que l’approche ST l’aurait nécessité
      Le problème plus important, si je me souviens bien, était qu’à l’époque ST ne pouvait pas prendre en charge les nouveaux formats de quantification de GGML, et le fait d’avoir notre propre format de fichier nous donnait une flexibilité difficile à obtenir avec ST
    • Dire que « dans la génération d’images locale, les modèles d’IA ont toujours été des fichiers uniques » n’a pas vraiment de sens même dans ce domaine
      Pour exécuter réellement l’architecture à partir des poids, on n’utilise pas seulement un fichier de poids unique : il faut aussi plusieurs encodeurs et décodeurs, entre autres
      L’outil utilisé peut te le masquer, mais en dessous, ils sont toujours là
  • À propos de cet étrange llama_chat_apply_template, exposé dans l’API libllama, qui code en dur directement en C++ quelques formats de chat : en tant que personne qui bricole une application d’inférence desktop avec FLTK[0], j’aimerais que cela utilise le véritable parseur de templates Jinja2 employé par llama.cpp
    Ou au moins qu’il existe une autre fonction C qui fasse ce travail. Pour parser correctement, il semble nécessaire de pouvoir transmettre différentes données, par exemple pour que le template sache si l’appel d’outils est activé
    Pour l’instant j’utilise cette fonction un peu bricolée, mais je finirai probablement par utiliser directement un interpréteur Jinja2, ou par copier-coller le code depuis llama.cpp
    Malgré cela, l’approche tout-en-un de GGUF est extrêmement pratique. Je suis d’accord pour dire qu’il est étrange que le modèle de projection soit dans un fichier séparé
    Quand j’ai téléchargé pour la première fois un modèle avec prise en charge de la vision, j’ai simplement pris le GGUF qui me semblait convenir, puis llama.cpp m’a dit qu’il ne pouvait pas traiter le modèle, et ce n’est que bien plus tard que j’ai compris qu’un fichier supplémentaire était nécessaire
    Ma réaction sur le moment a littéralement été : « GGUF n’était pas censé être le format qui contient tout ? » :-P
    [0] https://i.imgur.com/GiTBE1j.png

  • J’ai toujours utilisé un format de type safetensors + fichiers de métadonnées, proche des dépôts Hugging Face
    Ce n’est absolument pas une gêne majeure, mais GGUF semble intéressant avec son format plus compact et son bon niveau de prise en charge

  • En voyant ce qui manque encore à GGUF, j’ai au contraire mieux compris GGUF
    Le format d’appel d’outils paraît vraiment naturel, et j’ai l’impression qu’il pourrait marquer une étape importante dans le passage des LLM aux agents

  • J’ai récemment téléchargé le Mistral 7B de TheBloke pour le tester, et j’ai une 4070

    • J’aime bien Mistral, mais ce modèle n’est pas le meilleur
      Tu devrais essayer Gemma 4 e4b. C’est à peu près la même taille que Mistral 7B et cela devrait bien tourner sur une 4070
      Le nom « E4B » peut être un peu trompeur
    • Mistral 7B est déjà assez ancien
      Sur une 4070 de 12 Go, tu peux faire tourner Qwen 3.5 9B q4km ou Qwen 3.6 35B. Le second est bien plus intelligent, mais aussi beaucoup plus lent à cause de l’offloading mémoire
      Essaie les deux dans LM Studio, leurs capacités sont vraiment impressionnantes
    • J’ai confirmé que cela tourne aussi très vite et très bien sur une 2070
      J’aime bien TheBloke, donc j’aimerais qu’il continue encore à produire des modèles