- GGUF ist das von llama.cpp verwendete Dateiformat für Sprachmodelle und legt die für die Ausführung nötigen Metadaten in einer einzigen Datei ab, was Bereitstellung und Laden von Modellen vereinfacht
- Chat-Templates verarbeiten als Jinja2-Skripte Gesprächsformate, Tool-Calling und die Kodierung multimedialer Nachrichten, verhalten sich aber je nach Implementierung unterschiedlich
- GGUF kann Special Tokens wie End-of-Sequence-Tokens und empfohlene Sampler-Einstellungen enthalten; seit Kurzem lässt sich auch die Reihenfolge der Sampler-Kette angeben
- Für Tool-Calling-Formate gibt es noch kein einheitliches Schema; sie unterscheiden sich je nach Modell, sodass inference engines pro Engine weiterhin hardcodierte Logik brauchen, während grammatikbasierte Parser als Kandidat für eine Standardverbesserung bleiben
- Es fehlen noch
think_token, das Bündeln von Projektionsmodellen und Feature-Flags, wodurch die Trennung von Denkabschnitten, multimodale Setups und die Erkennung unterstützter Funktionen weiterhin schwierig bleiben
Was GGUF enthält
- GGUF ist das Dateiformat, das llama.cpp für Sprachmodelle verwendet
- Der zentrale Vorteil von GGUF besteht darin, mehrere für die Modellausführung nötige Komponenten in einer einzigen Datei unterzubringen
- GGUF packt diese zusätzlichen Informationen in eine Datei und macht Modelle dadurch leichter handhabbar
Chat-Templates
- Konversationsfähige Sprachmodelle werden auf Token-Sequenzen in einem bestimmten Format trainiert, das wie eine Gesprächsstruktur aussieht
- Ein Formatbeispiel von Gemma4 sieht so aus
<|turn>user
Hi there!<turn|>
<|turn>model
Hi there, how can I help you today?<turn|>
- Ein Format-Template von LFM2 sieht so aus
<s>
<|im_start|>user Hi there!<|im_end|>
<|im_start|>assistant Hi there, how can I help you today?<|im_end|>
- In der Praxis werden die Templates deutlich komplexer, weil sie Reasoning-Blöcke, Tool-Beschreibungen, Tool-Aufrufe und -Antworten sowie die Kodierung multimedialer Nachrichten wie Bilder, Audio und Video einschließen
- Dafür sind Chat-Templates zuständig, also Skripte in der Template-Sprache Jinja2
- Ein Beispiel ist das in Gemma 4 enthaltene Chat-Template
- In den GGUF-Metadaten wird das Standard-Chat-Template unter dem Schlüssel
tokenizer.chat_template gespeichert
- Ein Modell kann mehrere Chat-Templates haben
- Es kann getrennte Templates für mit und ohne Tool-Calling-Unterstützung geben
- Die meisten Modelle liefern ein einziges großes Chat-Template und behandeln Tool-Calling nur dann speziell, wenn Tools angegeben sind
- Bei manchen Modellen muss ein separates Tool-spezifisches Chat-Template zusätzlich gefunden werden
- Jinja2 ist eher eine Programmiersprache mit Schleifen, Bedingungen, Zuweisungen, Listen und Dictionaries
- Eine konversationsfähige LLM-Anwendung muss bei jeder neuen Nachricht einen Interpreter enthalten, der ein Programm wie das rund 250 Zeilen lange Jinja-Skript von Gemma ausführt
- Auch die Verarbeitung von Jinja unterscheidet sich je nach Implementierung
- Hugging Face transformers verwendet die bestehende Python-Bibliothek jinja2
llama-server und llama-cli aus llama.cpp verwenden eine eigene Jinja-Implementierung
- Die über die
libllama-API exponierte Funktion llama_chat_apply_template ist die ältere Methode, bei der einige wenige Chat-Formate direkt in C++ hardcodiert sind
- NobodyWho verwendet minijinja, eine Rust-Neuimplementierung des ursprünglichen Jinja-Autors
- Das ist etwas anderes als minja, die minimale Jinja-Bibliothek, die llama.cpp früher einmal verwendet hat
- Zwischen Jinja-Implementierungen gibt es erhebliche Performance-Unterschiede
- In lokalen LLM-Anwendungen ist die Verarbeitung von Chat-Templates jedoch kein Performance-Flaschenhals und daher kein großes Streitthema
Special Tokens
- Ein Sprachmodell kann für eine eingegebene Token-Sequenz immer weiter das nächste Token ausgeben, daher braucht man eine Möglichkeit, die Generierung zu stoppen
- Die übliche Lösung ist ein End-of-Sequence-Token, bei dem die Inference Engine die Generierung beendet, sobald das Modell dieses Token ausgibt
- End-of-Sequence-Tokens sind ein Beispiel für Special Tokens
- Special Tokens haben normalerweise eine Bedeutung, die über gewöhnlich tokenisierte Zeichen hinausgeht
- Sie sollten dem Nutzer in der Regel nicht angezeigt werden, besitzen aber oft eine textuelle Darstellung und können daher prinzipiell sichtbar gemacht werden
- Beispiele einiger Special Tokens von Gemma4:
1 / <eos>: Ende der Sequenz; das Modell gibt es aus, um die Generierung zu beenden
2 / <bos>: Anfang der Sequenz; wird vor die Eingabe gesetzt
46 / <|tool_call>: markiert den Beginn eines Tool-Aufrufs
47 / <tool_call|>: markiert das Ende eines Tool-Aufrufs
105 / <|turn>: markiert den Beginn eines Gesprächs-Turns
106 / <turn|>: markiert das Ende eines Gesprächs-Turns
Sampler-Einstellungen und Reihenfolge
- Sprachmodelle geben eine Wahrscheinlichkeitsverteilung für das nächste Token aus; der Prozess, daraus ein Token auszuwählen, heißt Sampling
- Die einfachste Methode ist eine Zufallsauswahl aus der gewichteten Verteilung
- In der Praxis erzielt man oft bessere Ergebnisse, wenn man die Wahrscheinlichkeitsverteilung vor der konkreten Auswahl transformiert
- Wenn Labore neue Modelle veröffentlichen, liefern sie oft bestimmte empfohlene Sampler-Einstellungen mit
- Nutzer kopieren solche Werte auch häufig aus Markdown-Dateien oder ähnlichen Quellen, um bessere Antworten zu erhalten
- NobodyWho stellte ausgewählte Modelle auf einer Hugging-Face-Seite bereit und bündelte empfohlene Sampler-Einstellungen in einem eigenen Format, um manuelles Kopieren zu reduzieren
- Das funktionierte, aber damit ein Modell wirklich nützlich wurde, war weiterhin eine Konvertierung auf Seiten von NobodyWho nötig
- Durch eine kürzlich ergänzte Funktion im GGUF-Format kann die Sampler-Kette nun direkt in der Modelldatei angegeben werden
- Dadurch wurde NobodyWhos eigenes Format überflüssig, was genau das gewünschte Ergebnis war
- In der Web-App llm-sampling lässt sich schnell nachvollziehen, welche Rolle unterschiedliche Sampler-Schritte spielen
- Zieht man einzelne Schritte per Drag-and-Drop um, zeigt sich, dass die Reihenfolge der Sampling-Schritte die endgültige Verteilung stark verändern kann
- Viele Formate für Sampler-Einstellungen, darunter JSON-Dateien in Ollama-Images oder
generation_config.json auf Hugging Face, haben keine Möglichkeit, die Reihenfolge der Sampling-Schritte anzugeben
- Der GGUF-Standard kann die Sampling-Reihenfolge über das Feld
general.sampling.sequence festlegen
- Viele GGUF-Modelle lassen dieses Feld jedoch weiterhin weg und verlassen sich auf eine implizite Reihenfolge, nämlich das Standardverhalten von llama.cpp
Was noch fehlt
- Gute Inference Engines versuchen, für viele verschiedene Sprachmodelle eine einheitliche Schnittstelle bereitzustellen
- Wenn man die Zusatzinformationen in den GGUF-Metadaten parst und nutzt, lassen sich viele modellspezifische Codepfade reduzieren
-
Tool-Calling-Formate
- Fast alle Inference Engines haben hardcodierte Pfade zum Parsen unterschiedlicher Tool-Calling-Formate
- Ein Beispiel für das Tool-Calling-Format von Qwen3:
<tool_call>{"name": "get_weather", "arguments": {"location": "Copenhagen"}}</tool_call>
- Ein Beispiel für das Tool-Calling-Format von Qwen3.5:
<tool_call>
<function=get_weather>
<parameter=city>
Copenhagen
</parameter>
</function>
</tool_call>
- Ein Beispiel für das Tool-Calling-Format von Gemma4:
<|tool_call>call:get_weather{city:<|"|>Copenhagen<|"|>}<tool_call|>
- Wenn ein neues Modell erscheint, müssen mehrere Inference Engines jeweils eigene Parser implementieren
- Wenn eine Grammatik in der Modelldatei enthalten wäre und sich daraus ein Parser ableiten ließe, wäre das eine hervorragende Ergänzung des GGUF-Standards
- NobodyWho fügt zusätzlich einen Schritt hinzu, der passend zum jeweils übergebenen Tool eine Constraint-Grammatik erzeugt
- Damit lässt sich Typsicherheit für Tool-Aufrufe gewährleisten
- Das ist besonders nützlich, weil kleine Modelle mit 1B Parametern oder weniger etwa dort einen Float statt einer Ganzzahl übergeben können, wo eine Ganzzahl erforderlich ist
- Selbst wenn es eine Grammatik gäbe, aus der sich ein allgemeiner Tool-Calling-Parser erzeugen ließe, müsste NobodyWho weiterhin eine Funktion implementieren, die für die jeweils übergebenen konkreten Tools passende Grammatiken erzeugt
- Ein Meta-Grammatik-Format, mit dem sich konkrete, auf bestimmte Tools zugeschnittene Grammatiken erzeugen und daraus Parser ableiten lassen, bleibt eine interessante offene Frage
-
Think-Token
- Das ist unter den fehlenden Punkten der am einfachsten hinzuzufügende
- Das upstream-Hugging-Face-Repository enthält inzwischen ein Feld
think_token
think_token ist sehr nützlich, um Denkabschnitte in erzeugten Ausgaben zu trennen
- Solche Denkabschnitte sollten normalerweise entfernt oder anders als die eigentliche Ausgabe gerendert werden
- Downstream-GGUF-Konvertierungen enthalten dieses Feld meist nicht
- Deshalb können GGUF-basierte Inference Engines den Denkstrom ohne separaten modellspezifischen Code nicht von der eigentlichen Ausgabe trennen
- Wenn
think_token in die standardisierte GGUF-Konvertierungs-Pipeline aufgenommen würde, wäre dieses Problem gelöst
-
Projektionsmodelle
- Für die Interaktion mit multimodalen LLMs, die Bilder und Audio nativ statt als Text verarbeiten können, wird ein zusätzliches Modell zur Verarbeitung nichttextueller Eingaben benötigt
- Dieses zusätzliche Modell wird als Projektionsmodell bezeichnet
- Derzeit ist es üblich, zwei GGUF-Dateien zu übergeben
- eine GGUF für das eigentliche Sprachmodell
- eine weitere, kleinere für die Verarbeitung von Bildern und Audio
- Das durchbricht den Komfort von GGUF als Ein-Datei-Format
- Es wäre eine große Verbesserung, wenn eine einzelne GGUF-Datei die Gewichte und Einstellungen des Projektionsmodells zusammen mit der Hauptdatei bündeln könnte
- Projektionsmodelle sind oft etwa 1 GB groß
- Das ist groß genug, dass man diesen Overhead vermeiden möchte, wenn man ihn nicht benötigt
- Es ist sinnvoll, zwei Varianten bereitzustellen: GGUF mit und ohne enthaltene Projektionsgewichte
- So könnte man wieder zu einem Zustand zurückkehren, in dem nur eine Download-URL und nur eine auf der Festplatte zu cachende Datei verwaltet werden müssen
-
Liste unterstützter Funktionen
- Modelle unterscheiden sich darin, welche Funktionen sie unterstützen, und allein anhand der GGUF-Datei ist das oft nicht leicht zu erkennen
- Manche Modelle unterstützen Bildeingaben, andere nicht
- Derzeit ist die beste verfügbare Behandlung, Bildunterstützung anzunehmen, wenn ein Projektionsmodell übergeben wird
- Manche Modelle unterstützen natives Tool-Calling, andere nicht
- Derzeit prüft man am besten per teilweiser String-Übereinstimmung, ob das Chat-Template einen Abschnitt enthält, der versucht, eine Liste von Tool-JSON-Schemata zu rendern
- Das ist offensichtlich nur ein Behelf
- Manche Modelle geben Denkblöcke aus, andere nicht
- Da Denk-Tags normalerweise nicht in den GGUF-Metadaten stehen, ist unklar, wie sich zuverlässig feststellen lässt, ob man Denkblöcke vom Modell erwarten sollte
- Wenn die GGUF-Community Feature-Flags zu Modelldateien hinzufügen würde, könnten modellunabhängige Inference-Bibliotheken konsistentere Fehlermeldungen und Warnungen liefern
- So wäre zum Beispiel eine passendere Rückmeldung möglich, wenn man Tool-Calling mit einem Modell versucht, das kein natives Tool-Calling unterstützt
Fazit
- GGUF bündelt die Zusatzinformationen, die für die korrekte Ausführung eines Modells nötig sind, in einer einzigen Datei, sodass nicht viele zusätzliche modellspezifische Codepfade nötig sind
- GGUF ist ein offenes und erweiterbares Format mit einer starken Community
- Wenn man den Standard gemeinsam weiter stärkt, lassen sich Modelle in Anwendungen leichter austauschen, ohne eine gute Developer Experience aufzugeben
- GGUF-Metadaten sind bereits in vieler Hinsicht nützlich, aber es bleibt Verbesserungspotenzial bei Tool-Calling-Grammatiken,
think_token, dem Bündeln von Projektionsmodellen und Feature-Flags
1 Kommentare
Hacker-News-Kommentare
Schade, dass das Projektionsmodell in eine separate Datei aufgeteilt wurde; ich hätte es auch lieber in einer einzigen Datei gehabt
Ich weiß nicht genau, warum es so gekommen ist, aber es weicht ziemlich von der Single-File-Philosophie ab, die man bei der Entwicklung von GGUF im Kopf hatte
Ich hoffe, dass jemand die Zusammenführung vorantreibt; diesmal habe ich wohl den Anschluss an den aktuellen Kurs etwas verloren :-)
Diese Entscheidung gefällt mir. Deshalb finde ich es auch nicht abwegig anzunehmen, dass man grundsätzlich offen dafür sein könnte, auch Mmproj-Dateien in GGUF aufzunehmen
Das einzige Problem, das mir einfällt, ist, welches Format man dafür verwenden würde. Es gibt Optionen wie BF16, F16 usw.
GGML und GGUF waren für das Open-Source-Machine-Learning-/AI-Ökosystem enorm wichtig
Projekte wie llama.cpp, whisper.cpp und stable-diffusion.cpp funktionieren auf verschiedensten Plattformen und Hardware-Backends meist sofort erstaunlich gut
Kompilieren, Modell hineinlegen und ausführen. Dann bekommt man sogar ein Web-UI und eine API dazu
> <|turn>user Hi there!<|turn>model Hi there, how can I help you todayMeine Güte, da wurde ein noch schlechter lesbares Format als XML geschaffen
Dieses Format wurde so entworfen, dass es nicht mit den eigentlichen Inhalten verwechselt wird, und diese Inhalte können beliebiger Text aus dem Internet sein
Dafür muss man ein Format verwenden, das sonst nirgends vorkommt
Der aktuell größte fehlende Punkt ist meines Erachtens eine Möglichkeit, die Modellarchitektur zu definieren, ohne sie in den aktuellen Build fest einzucodieren
Sie muss nicht zwingend 1:1 die gleiche Performance wie vollständig unterstützte Modelle erreichen
Ob es vom ersten Tag an einen vernünftigen, vom Anbieter validierten Support gibt, entscheidet darüber, ob ein Modell als großartig oder als furchtbar wahrgenommen wird. Die jüngsten Releases von Gemma und Qwen zeigen das gut
Ich kenne die Lösung nicht, aber vielleicht könnte man eine DSL schreiben, die den Modellgraphen beschreibt und in GGUF ablegen
Eine andere Möglichkeit wäre, die PyTorch-Module offizieller Modell-Releases zu lesen und irgendwie in GGML-Operationen zu übersetzen
Ich wollte das schon in Version eins unterbringen, aber damals war es wichtiger, erst eine Spezifikation mit minimalem Funktionsumfang herauszubringen und implementieren zu lassen
Ich würde das immer noch gern sehen, aber dafür braucht es jemanden, der den aktuellen Zustand des GGML-IR wirklich sehr gut kennt
Danach könnte man ein gemeinsames Interface bereitstellen, das gemeinsame Parameter entgegennimmt, und zusätzliche benutzerdefinierte Parameter als Erweiterungen nach Art von Wayland behandeln
Dann ließen sich nicht nur Transformer-Familien wie LLaMa unterstützen, sondern auch rekurrente neuronale Netze wie RWKV, multimodale Modelle usw.
Wie man das konkret umsetzt, weiß ich nicht, aber es klingt nach einer großartigen Idee. Meine Sorge wäre nur, dass Architekturverbesserungen oder Optimierungen, die keine neuen Gewichte erfordern, auf bestehende Dateien ohne Konvertierung vielleicht nicht anwendbar wären, wenn der Rechengraph fest im Modellfile steckt
> Das wirklich Saubere an GGUF ist, dass es nur eine Datei ist. Im Vergleich zu einem typischen safetensors-Repository auf Hugging Face liegen die nötigen JSON-Dateien überall verstreut [...]Interessanterweise waren AI-Modelle für mich „schon immer“ Ein-Datei-Modelle, weil das bei lokaler Bilderzeugung der Standard war
Auch safetensors-Dateien können intern allerlei enthalten, dafür braucht es also nicht zwingend GGUF
Allerdings sind die Text-Encoder moderner Modelle selbst Sprachmodelle von mehreren Gigabyte, daher packt niemand für jeden Checkpoint eine doppelte Kopie davon hinein
Die meisten Bildmodelle waren oder sind zwar tatsächlich einzelne Dateien, aber safetensors für LLMs war es zumindest damals nicht, und ich wollte das auf struktureller Ebene erzwingen
Außerdem wollte ich nicht, dass ein Runner wie llama.cpp einen JSON-Reader braucht; beim ST-Ansatz wäre das nötig gewesen
Das größere Problem war, wenn ich mich richtig erinnere, dass ST damals die neuen Quantisierungsformate von GGML nicht unterstützen konnte, und ein eigenes Dateiformat bot die Flexibilität, die mit ST schwer zu erreichen gewesen wäre
Um eine Architektur mit Gewichten tatsächlich auszuführen, braucht man nicht nur eine einzelne Gewichtedatei, sondern auch verschiedene Encoder und Decoder und anderes mehr
Das verwendete Tool kann das vor einem verbergen, aber unter der Oberfläche ist es weiterhin da
Was das etwas seltsame
llama_chat_apply_templateangeht, das über dielibllama-API verfügbar ist und einige Chat-Formate direkt in C++ hart codiert, würde ich mir als jemand, der mit einer Desktop-Inferenz-App auf Basis von FLTK[0] experimentiert, wünschen, dass dafür der echte Jinja2-Template-Parser verwendet wird, den llama.cpp nutztOder zumindest irgendeine andere C-Funktion, die so etwas erledigt. Für korrektes Parsing müsste man offenbar verschiedene Daten übergeben können, damit das Template zum Beispiel weiß, ob Tool-Calling aktiv ist
Im Moment nutze ich diese provisorische Funktion, aber wahrscheinlich werde ich am Ende direkt einen Jinja2-Interpreter verwenden oder mir den Code aus llama.cpp herauskopieren
Trotzdem ist der All-in-One-Ansatz von GGUF sehr praktisch. Ich stimme zu, dass es seltsam wirkt, wenn das Projektionsmodell in einer separaten Datei liegt
Als ich zum ersten Mal ein Modell mit Vision-Support heruntergeladen habe, habe ich nur das GGUF genommen, das passend aussah, und erst viel später gemerkt, dass eine zusätzliche Datei nötig ist, nachdem llama.cpp das Modell nicht verarbeiten konnte
Mein erster Gedanke war damals buchstäblich: „War GGUF nicht das Format, in dem alles drin ist?“ :-P
[0] https://i.imgur.com/GiTBE1j.png
Ich habe immer ein Format ähnlich zu Hugging-Face-Repositories verwendet, also safetensors + Metadaten-Dateien
Es ist überhaupt kein großes Ärgernis, aber GGUF wirkt mit seinem kompakteren Format und der guten Unterstützung durchaus attraktiv
Gerade dadurch, dass ich mir angesehen habe, was GGUF noch fehlt, habe ich sogar mehr über GGUF gelernt
Das Tool-Calling-Format wirkt sehr natürlich und könnte ein Meilenstein auf dem Weg von LLMs zu Agenten sein
Ich habe kürzlich TheBlokes 7B Mistral heruntergeladen, um es auszuprobieren, und habe eine 4070
Ich würde empfehlen, mal Gemma 4 e4b auszuprobieren. Es ist ungefähr so groß wie Mistral 7B und sollte auf einer 4070 gut laufen
Der Name „E4B“ ist allerdings etwas missverständlich
Auf einer 12-GB-4070 kann man Qwen 3.5 9B q4km oder Qwen 3.6 35B laufen lassen. Letzteres ist deutlich intelligenter, aber wegen Memory-Offloading auch viel langsamer
Probier beide mal in LM Studio aus, sie sind wirklich erstaunlich leistungsfähig
Ich mag TheBloke, deshalb wünschte ich immer noch, er würde weiterhin Modelle bauen