VisualLLM-cpp
Was passiert eigentlich zwischen „Prompt eingetippt“ und „Token erscheint“? Diese App zerlegt die Inferenz von llama.cpp in ihre Module und lässt dich jeden einzelnen ggml-Aufruf schrittweise durchlaufen – mit echten Zahlen aus einem winzigen, aber vollständig gerechneten Transformer.
Die Inferenz Schritt für Schritt
Wähle einen Beispiel-Prompt (oder tippe deinen eigenen) und lass die Pipeline laufen – als Ganzes in vier Geschwindigkeiten oder einzeln von ggml-Aufruf zu ggml-Aufruf. Jeder Schritt zeigt die Formel, den zugehörigen C-Code aus llama.cpp und die tatsächlich berechneten Zwischenwerte.
🎛️ Sampler-Parameter (wie bei llama-cli)
⚠️ Die Gewichte dieses Demo-Modells sind deterministisch zufällig, aber untrainiert. Die Architektur, alle Zwischenergebnisse und die komplette Sampler-Kette sind echt gerechnet – nur die Logits werden optional mit einem n-Gramm-Prior (Trigramm mit Bigramm-Backoff) aus einem Mini-Korpus gemischt, damit lesbarer Text entsteht. Stelle Demo-Prior auf 0, um das rohe, untrainierte Modell zu sehen.
🔍 Bereit
Der komplette Inferenz-Lauf wurde vorberechnet: 184 Einzelschritte vom Öffnen der GGUF-Datei bis zum letzten gezogenen Token.
- ▶ spielt alles in der gewählten Geschwindigkeit ab
- ⏭ geht einen einzelnen ggml-Aufruf weiter
- Ein Klick auf eine Pipeline-Stufe oben springt direkt dorthin
- Jede Zelle der Matrizen zeigt beim Überfahren ihren exakten Wert
Wie llama.cpp aufgebaut ist
Fünf Werkbänke zum Nachschlagen und Ausprobieren: die Schichtenarchitektur des Projekts, der byteweise Aufbau der LLM-Datei, das GGUF-Format mit allen Tensoren, der Tokenizer zum Selbsttesten und der ggml-Berechnungsgraph.
🗺️ Schichtenmodell von llama.cpp
┌──────────────────────────────────────────────────────────────┐
│ Anwendung / Werkzeuge │
│ llama-cli llama-server llama-quantize llama-bench │
└───────────────────────────┬──────────────────────────────────┘
│ libllama (C-API: llama.h)
┌───────────────────────────▼──────────────────────────────────┐
│ llama-model-loader ──► llama-model ──► llama-context │
│ │ │ │ │
│ │ │ ├─ llama-batch │
│ │ │ ├─ llama-kv-… │
│ │ │ └─ llama-samp… │
│ ▼ ▼ │
│ llama-mmap llama-vocab llama-graph │
└───────────────────────────┬──────────────────────────────────┘
│ ggml-Graph (Knoten + Tensoren)
┌───────────────────────────▼──────────────────────────────────┐
│ ggml + ggml-backend-sched │
├──────────┬──────────┬──────────┬──────────┬──────────────────┤
│ ggml-cpu │ ggml-cuda│ggml-metal│ggml-vulkan│ ggml-sycl … │
│ AVX2/NEON│ NVIDIA │ Apple │ portabel │ Intel │
└──────────┴──────────┴──────────┴──────────┴──────────────────┘
Die Trennung ist scharf: ggml weiß nichts über Sprachmodelle – es kennt nur Tensoren und Operationen. libllama weiß nichts über Hardware – es baut nur Graphen. Genau deshalb lässt sich ein neues Backend hinzufügen, ohne eine Zeile Modellcode anzufassen.
📦 GGUF & Model-Loader
▾Liest das Dateiformat, prüft Magic und Version, legt die Tensor-Deskriptoren an und blendet die Gewichte per mmap ein.
ggml/src/gguf.cppsrc/llama-model-loader.cppsrc/llama-mmap.cpp- GGUF ist selbstbeschreibend: Hyperparameter, Vokabular und Chat-Template stecken als Key-Value-Paare in der Datei.
- mmap() bedeutet: kein Kopieren, lazy Paging, sofortiger zweiter Start aus dem Page-Cache.
- Der Loader prüft, ob jeder erwartete Tensor in der richtigen Form vorliegt – fehlt einer, bricht das Laden ab.
🔤 Vokabular & Tokenizer
▸Wandelt Text in Token-IDs und zurück. Unterstützt SPM (LLaMA), BPE (GPT-2/Falcon), WPM (BERT), UGM und RWKV.
🧱 ggml – Tensor-Bibliothek
▸Die Rechenmaschine: definiert Tensoren, Operationen und den Berechnungsgraphen. Ohne Abhängigkeiten, in reinem C.
⚙️ Backend-Scheduler
▸Verteilt die Graph-Knoten auf CPU, Metal, CUDA, Vulkan, SYCL … und fügt automatisch Kopieroperationen zwischen Geräten ein.
🕸️ Graph-Builder
▸Übersetzt die Architektur (llama, qwen2, phi3, gemma …) in eine konkrete Folge von ggml-Operationen.
🗃️ KV-Cache
▸Speichert Key- und Value-Vektoren aller bisherigen Positionen, damit Decoding nicht die ganze Sequenz neu rechnen muss.
🎲 Sampling-Kette
▸Formt aus den Logits ein konkretes Token: Penalties, Temperatur, top-k, top-p, min-p, typical, mirostat, Grammatik.
🖥️ Server & Werkzeuge
▸Fertige Programme: OpenAI-kompatibler HTTP-Server, CLI-Chat, Quantisierer, Qualitätsmessung.
Quantisierung und Geschwindigkeit
Warum passt ein 8-Milliarden-Modell in 4.5 GB, und was kostet das an Qualität? Und wie viele Token pro Sekunde sind auf welcher Hardware überhaupt möglich?
🔬 Quantisierung live: ein Block aus 32 Gewichten
Genau so verarbeitet ggml die Gewichte: nicht einzeln, sondern in Blöcken mit gemeinsamem Skalierungsfaktor. Der Fehler jedes einzelnen Werts ist der Preis für 4× weniger Speicher.
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original (f32) | ||||||||||||||||||||||||||||||||
| Code (int) | 6 | 8 | 4 | 6 | 7 | 12 | 8 | 4 | 7 | 0 | 10 | 11 | 13 | 10 | 12 | 11 | 10 | 5 | 15 | 6 | 6 | 7 | 10 | 9 | 8 | 5 | 8 | 9 | 8 | 9 | 14 | 5 |
| rekonstruiert | ||||||||||||||||||||||||||||||||
| Fehler (10× verstärkt) |
F32
32.00 bit/GewichtReferenz ohne Verlust. 32 bit pro Gewicht – vier mal so groß wie Q8_0 und für lokale Inferenz meist unnötig.
Q8_0
8.50 bit/Gewicht32 Werte teilen sich einen f16-Skalierungsfaktor, jedes Gewicht wird zu int8. 8.5 bit/Gewicht, praktisch verlustfrei – der Goldstandard für Vergleichsmessungen.
Q4_0
4.50 bit/Gewicht32 Werte, ein f16-Delta, 4 bit pro Gewicht (zwei Nibbles je Byte). 4.5 bit/Gewicht. Einfach und schnell, aber messbar ungenauer als die K-Quants.
Q4_K
4.50 bit/GewichtSuperblock aus 256 Werten, unterteilt in 8 Subblöcke à 32. Jeder Subblock hat eigene Skalierung UND eigenen Minimalwert (6 bit, selbst quantisiert). Deutlich kleinerer Fehler bei nahezu gleicher Größe – Basis von Q4_K_M.
Q6_K
6.56 bit/Gewicht6 bit pro Gewicht mit 16 Subblöcken. Wird in Q4_K_M gezielt für die empfindlichen Tensoren (ffn_down, attn_v, output) eingesetzt – dort kostet Präzision am wenigsten Speicher und bringt am meisten.
⚖️ Die Mischungen von llama-quantize
Ein „Q4_K_M“ ist kein einzelnes Format, sondern ein Rezept: die meisten Tensoren Q4_K, die empfindlichen (attn_v, ffn_down, output) Q6_K. Das M steht für „medium“.
# so entsteht eine quantisierte Datei $ llama-quantize model-f16.gguf model-Q4_K_M.gguf Q4_K_M # und so misst man, was es gekostet hat $ llama-perplexity -m model-Q4_K_M.gguf -f wiki.test.raw Final estimate: PPL = 5.9642 +/- 0.03348 # F16-Referenz: 5.9066
Was hier echt ist – und was Modell steht
Ehrlichkeit gehört zu einer Lernhilfe dazu.
✅ Echt gerechnet
- Die komplette LLaMA-Architektur: RMSNorm, Q/K/V, RoPE, kausale Attention, SwiGLU, Residuals
- Alle angezeigten Vektoren, Matrizen und Attention-Gewichte stammen aus echten Matrixprodukten
- Der SPM-Tokenizer inklusive jedes einzelnen Merge-Schritts
- Der KV-Cache und sein Wachstum pro Token
- Die vollständige Sampler-Kette: Penalty → Temperatur → top-k → top-p → Ziehen
- Die Quantisierungs-Rekonstruktion und ihr messbarer Fehler
⚠️ Vereinfacht oder simuliert
- Die Gewichte sind untrainiert (deterministisch pseudozufällig). Deshalb wird für lesbare Ausgaben ein n-Gramm-Prior aus einem Mini-Korpus zugemischt – abschaltbar per Regler.
- Winzige Dimensionen (n_embd 32 statt 4096), damit jede Zahl sichtbar bleibt
- Kein echtes GGUF wird geladen – Struktur und Metadaten sind nachgebildet
- Q4_K ist als Einzelblock gezeigt, nicht als vollständiger 256er-Superblock mit 6-bit-Skalen
- FLOPs und Byte-Angaben sind Modellrechnungen, keine Messungen
🚀 Weitermachen mit dem echten llama.cpp
# 1) bauen $ git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp $ cmake -B build && cmake --build build --config Release -j # 2) ein Modell holen (GGUF direkt von Hugging Face) $ build/bin/llama-cli -hf bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M -p "Erkläre RoPE" # 3) als OpenAI-kompatibler Server $ build/bin/llama-server -m model.gguf -c 4096 -ngl 99 --port 8080 # 4) hineinschauen – jede Stufe dieser App hat ein echtes Gegenstück $ build/bin/llama-tokenize -m model.gguf -p "Hallo Welt" # Stufe 🔤 $ build/bin/llama-bench -m model.gguf -p 512 -n 128 # Stufe 📊 $ GGML_SCHED_DEBUG=2 build/bin/llama-cli -m model.gguf -n 1 # Graph + Backends