Modelle auf dem eigenen Rechner: Ollama und Quantisierung
Open-Source-Modelle auf dem eigenen Rechner ausführen. Erst abschätzen, wie viel Speicher ein Modell belegt, dann Quantisierung von Hand schreiben und sehen, wie viel kleiner und wie viel schlechter die Gewichte bei 8, 4 und 2 Bit werden, und zum Schluss mit Ollama zum Laufen bringen.
- Etwa 40 Minuten
- Niveau: Fortgeschritten
- Getestet: 2026-09-15 torch 2.14, Apple-M4-CPU; Nutzung von Ollama laut offizieller Dokumentation
Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.
In Teil 1 haben wir immer die API von DeepSeek aufgerufen. Das ist bequem, aber manchmal möchte man das Modell auf dem eigenen Rechner laufen lassen: Daten dürfen die Firma nicht verlassen, es gibt kein Netz, die Aufrufmenge ist so groß, dass die API zu teuer wird, oder man will es einfach ausprobieren.
Diese Lektion zeigt, wie man Open-Source-Modelle auf dem eigenen Rechner ausführt. Die entscheidende Frage: Wie großes Modell passt auf meinen Rechner? Die Antwort hängt von zwei Dingen ab: wie viele Parameter das Modell hat und mit wie vielen Bytes jeder Parameter gespeichert wird.
python memory_estimate.py
python quantize.py
Den Speicherbedarf abschätzen
Die Gewichte eines Modells sind ein großer Haufen Zahlen. Als 32-Bit-Gleitkommazahl (FP32) braucht jede Zahl 4 Bytes, mit 16 Bit (BF16 oder FP16) 2 Bytes. Die Gewichte belegen also etwa:
参数量 × 每个参数的字节数
Am Beispiel von Qwen2.5-0.5B-Instruct aus Lektion 3 (494 Millionen Parameter):
== 1. Qwen2.5-0.5B-Instruct 的权重,在不同精度下占多少
FP32 1.84 GB
BF16/FP16 0.92 GB
INT8 0.46 GB
4 比特 0.23 GB
Die heruntergeladene Datei model.safetensors hat 988 MB, genau die Größe in BF16 (988 MB sind etwa 0,92 GB; der Unterschied kommt von der Umrechnung 1000 gegen 1024).
Zur Laufzeit kommt zu den Gewichten der KV-Cache aus Modul 09, Lektion 6 hinzu. Seine Größe lässt sich aus der Konfiguration des Modells berechnen: Pro Token und pro Schicht wird ein K und ein V gespeichert:
def kv_cache_gb(n_layers, n_kv_heads, head_dim, n_tokens, bytes_per_value=2):
# 每个词元、每一层,要存一个 K 和一个 V,各是 n_kv_heads × head_dim 个数
return 2 * n_layers * n_kv_heads * head_dim * n_tokens * bytes_per_value / 1024**3
== 2. 它的 KV 缓存(每层 2 组 K/V,每组 64 维,BF16)
每个词元 12 KB
1000 个词元:0.01 GB
32000 个词元:0.37 GB
如果不用分组查询注意力(14 个头各存一份 K/V),32000 个词元要 2.56 GB,是现在的 7 倍
Die letzte Zeile zeigt die Wirkung von Grouped-Query Attention: Qwen2.5-0.5B hat 14 Attention-Köpfe, aber nur 2 Gruppen K und V; der Cache ist siebenmal kleiner. Bei langen Kontexten ist dieser Unterschied entscheidend.
Für eine praktische Schätzung gilt eine einfache Regel: Parameterzahl × Bytes pro Parameter, plus zwanzig Prozent Reserve für Cache und sonstigen Aufwand:
== 3. 粗略估算:参数量 × 每个参数的字节数,再留两成余量给缓存和其他开销
0.5 B 参数:FP32 2.2 GB BF16/FP16 1.1 GB INT8 0.6 GB 4 比特 0.3 GB
7 B 参数:FP32 31.3 GB BF16/FP16 15.6 GB INT8 7.8 GB 4 比特 3.9 GB
14 B 参数:FP32 62.6 GB BF16/FP16 31.3 GB INT8 15.6 GB 4 比特 7.8 GB
32 B 参数:FP32 143.1 GB BF16/FP16 71.5 GB INT8 35.8 GB 4 比特 17.9 GB
70 B 参数:FP32 312.9 GB BF16/FP16 156.5 GB INT8 78.2 GB 4 比特 39.1 GB
Diese Tabelle kann man direkt verwenden. Ein Laptop mit 16 GB Speicher schafft in BF16 gerade so ein 7B-Modell; mit 4 Bit läuft auch ein 14B-Modell. Bei langen Kontexten kommt der KV-Cache noch hinzu.
Die Tabelle zeigt auch, warum alle von „Quantisierung“ sprechen: Dasselbe Modell braucht in 4 Bit nur ein Viertel von BF16.
Quantisierung von Hand
Quantisierung heißt, jeden Parameter mit weniger Bits zu speichern. Am einfachsten ist die symmetrische Quantisierung: In einer Gruppe von Zahlen die mit dem größten Betrag finden, den Bereich [-Maximum, Maximum] in gleich große Stufen teilen, jede Zahl auf die nächstgelegene Stufe setzen und nur die Nummer der Stufe (eine kleine ganze Zahl) sowie den Skalierungsfaktor der Gruppe speichern.
def quantize(w, bits, group=None):
"""对称量化:每组数用一个缩放系数,把 [-最大绝对值, 最大绝对值] 映射到整数 [-qmax, qmax]。
返回"量化后再还原"的权重,以及实际要存的整数和缩放系数。"""
qmax = 2 ** (bits - 1) - 1 # 8 比特是 127,4 比特是 7
shape = w.shape
w = w.reshape(-1, group) if group else w.reshape(shape[0], -1) # 按组,或者按行
scale = w.abs().amax(dim=1, keepdim=True) / qmax
q = torch.round(w / scale).clamp(-qmax, qmax) # 这就是要存下来的整数
return (q * scale).reshape(shape), q, scale
Ein Beispiel: 8 Zahlen auf 4 Bit quantisiert (ganze Zahlen von -7 bis 7):
== 1. 一个例子:把 8 个小数量化成 4 比特整数
原来: [0.077, -0.0147, -0.1089, 0.0284, -0.0542, -0.0699, 0.0202, 0.0419]
整数: [5, -1, -7, 2, -3, -4, 1, 3](缩放系数 0.01556)
还原后: [0.0778, -0.0156, -0.1089, 0.0311, -0.0467, -0.0623, 0.0156, 0.0467]
Die größte, -0,1089, entspricht -7; der Skalierungsfaktor ist also 0,1089 / 7 = 0,01556. Die übrigen Zahlen werden durch ihn geteilt und gerundet. Beim Zurückrechnen wird wieder multipliziert; bei den meisten Zahlen liegt der Fehler bei etwa 0,003, aus -0,0542 wird -0,0467, ein etwas größerer Fehler.
Jede Zahl schrumpft von 32 auf 4 Bit, dazu kommt nur ein Skalierungsfaktor.
Unser kleines GPT quantisieren
Alle Matrizen des in Modul 09 trainierten kleinen GPT werden quantisiert, um zu sehen, wie viel kleiner und wie viel schlechter das Modell wird (LayerNorm und Biases sind klein und bleiben unverändert):
== 2. 整个模型量化之后
大小 验证损失
32 位小数(原模型) 6.16 MB 4.476 白露起春光,知君得舞衣。雪时疑是静,山意势悠扬。
8 比特,每行一个系数 1.58 MB 4.476 白露起春光,知君得舞衣。雪时疑是静,山意势悠扬。
4 比特,每行一个系数 0.81 MB 4.542 云泉四百古,一树两三五。雪路之山在,山闾势悠扬。
4 比特,每 32 个数一个系数 0.89 MB 4.512 白露起春光,知君得舞衣。雪时疑报晓,山下势悠扬。
2 比特,每 32 个数一个系数 0.51 MB 6.933 百日起春光津倚郭对舞儿年,一之州在子。闾里里里里
Jede Zeile schreibt mit demselben Zufallsstartwert ein Gedicht, damit man vergleichen kann.
8 Bit: Die Größe schrumpft auf ein Viertel, der Validierungsverlust ist 4,476, genau wie beim ursprünglichen Modell, und selbst das Gedicht ist Zeichen für Zeichen gleich. 8-Bit-Quantisierung ist fast „gratis“.
4 Bit, ein Faktor pro Zeile: Die Größe halbiert sich noch einmal, der Verlust steigt von 4,476 auf 4,542, und das Gedicht ändert sich. Eine Zeile hat einige Hundert Zahlen; ist nur eine davon besonders groß, bläht sie den Skalierungsfaktor auf, alle anderen Zahlen drängen sich auf wenige Stufen, und der Fehler wird groß.
4 Bit, ein Faktor pro 32 Zahlen: Man speichert etwas mehr Skalierungsfaktoren (Größe von 0,81 MB auf 0,89 MB), der Verlust fällt zurück auf 4,512, und das Gedicht ist im Wesentlichen wieder das alte, nur an zwei Stellen geändert. Je kleiner die Gruppe, desto besser passt ihr Skalierungsfaktor zu ihren Zahlen, desto kleiner der Fehler. Die praktisch genutzte 4-Bit-Quantisierung ist fast immer gruppenweise.
2 Bit: Nur die drei Werte -1, 0, 1 stehen zur Verfügung, das Modell ist völlig kaputt, Verlust 6,93, das Format des Geschriebenen ist durcheinander, und am Ende wiederholt es „里里里里“.
Das Fazit deckt sich mit der Praxiserfahrung: 8 Bit ist fast verlustfrei; 4 Bit verliert etwas, bringt dafür ein Viertel der Größe und ist die üblichste Wahl für den eigenen Rechner; darunter wird es schnell schlechter.
Echte Quantisierungsverfahren sind komplizierter: Sie behandeln besonders große „Ausreißer“, entscheiden anhand von Daten, wie mit dem geringsten Fehler quantisiert wird, und es gibt eigens für 4 Bit entworfene Datenformate (das in Lektion 2 erwähnte QLoRA nutzt ein Format namens NF4). Das Grundprinzip sind aber die paar Zeilen Code dieses Abschnitts.
Modelle mit Ollama ausführen
Große Modelle selbst per Code zu quantisieren und auszuführen ist mühsam. Ollama ist ein Werkzeug, das all das bündelt: Ein Befehl lädt ein bereits quantisiertes Modell herunter und führt es aus.
Installation (Stand September 2026, maßgeblich ist die offizielle Anleitung):
# macOS 和 Linux
curl -fsSL https://ollama.com/install.sh | sh
# Windows(PowerShell)
irm https://ollama.com/install.ps1 | iex
Für macOS und Windows gibt es auch Installationspakete auf ollama.com.
Qwen2.5-0.5B aus Lektion 3 ausführen:
ollama run qwen2.5:0.5b
Beim ersten Start wird das Modell automatisch heruntergeladen, dann beginnt das Gespräch. Stand September 2026 ist qwen2.5:0.5b in der Modellbibliothek von Ollama standardmäßig die Q4_K_M-quantisierte Version mit 398 MB, weniger als die Hälfte der 988 MB in BF16. Q4_K_M ist ein von llama.cpp definiertes gruppenweises 4-Bit-Format, dieselbe Idee wie das handgeschriebene „4 Bit, gruppenweise“ oben, nur feiner.
Für dasselbe Modell gibt es meist mehrere Quantisierungsversionen, etwa qwen2.5:0.5b-instruct-q8_0 (8 Bit) oder qwen2.5:0.5b-instruct-fp16 (16 Bit, nicht quantisiert). Man wählt anhand der obigen Speicherschätzung die passende Version.
Das lokale Modell per API aufrufen
Läuft Ollama, bietet es auf Port 11434 des eigenen Rechners eine OpenAI-kompatible Schnittstelle. Das heißt: Aller Code aus Teil 1 lässt sich mit drei geänderten Umgebungsvariablen auf das lokale Modell umstellen:
export LLM_BASE_URL=http://localhost:11434/v1
export LLM_API_KEY=ollama # 本地服务不检查密钥,但 openai 库要求有一个值
export LLM_MODEL=qwen2.5:0.5b
Genau für diesen Moment hat Modul 00, Lektion 2 Adresse, Schlüssel und Name des Modells in Umgebungsvariablen gelegt.
Die Erwartungen sollten aber realistisch sein: Das 0,5B-Modell haben wir in Lektion 3 erlebt; es sagt Falsches über die Geografie Pekings. Kleine Modelle, die lokal flüssig laufen, liegen bei komplexen Aufgaben weit hinter großen Modellen wie DeepSeek. Hier hilft wieder der Evaluationsdatensatz aus Modul 06: Nach dem Wechsel zum lokalen Modell einmal durchlaufen lassen und sehen, welchen Aufgaben es noch gewachsen ist.
Das eigene feingetunte Modell nutzen
Ollama kann auch eigene Modelle ausführen. Es kann Modelle im Hugging-Face-Format (safetensors) und im GGUF-Format importieren, und zwar über eine Konfigurationsdatei namens Modelfile, die mit FROM auf die Modelldatei zeigt, und ollama create.
Das in Lektion 3 feingetunte RepoBot kann man zuerst mit LoRA zurück ins ursprüngliche Modell führen (merge_and_unload), speichern und dann nach der offiziellen Dokumentation importieren. Die offizielle Dokumentation weist ausdrücklich darauf hin, dass Ollama beim Import von GGUF-Modellen nicht quantisiert; man muss vorher mit den Werkzeugen von llama.cpp quantisieren. Maßgeblich für die genauen Schritte ist die Import-Dokumentation von Ollama; dieser Teil ändert sich recht schnell.
Übungen
- Sieh nach, wie viel Arbeitsspeicher (oder Grafikspeicher) dein Rechner hat. Wie groß darf ein Modell nach der Schätztabelle dieser Lektion höchstens sein? Wie viel jeweils in 4 und 8 Bit?
- Probier in
quantize.py3 Bit mit Gruppen zu 32 sowie 4 Bit mit Gruppen zu 8 und zu 128 aus und stell Größe und Validierungsverlust in einer Tabelle zusammen. - Installier Ollama, führ ein kleines Modell aus, stell RepoBot v1 aus Modul 03 darauf um (nur Umgebungsvariablen ändern) und sieh nach, ob es normal funktioniert.
Selbsttest
1. Wie schätzt man ab, wie viel Speicher ein Modell mit 7B Parametern in BF16 belegt?
Parameterzahl mal Bytes pro Parameter: 7 Milliarden × 2 Bytes = 14 Milliarden Bytes, etwa 13 GB. Dazu KV-Cache und sonstiger Aufwand mit zwanzig Prozent Reserve, zusammen etwa 16 GB.
2. Warum wirkt bei 4-Bit-Quantisierung „ein Skalierungsfaktor pro 32 Zahlen“ besser als „ein Skalierungsfaktor pro Zeile“?
Der Skalierungsfaktor wird von der Zahl mit dem größten Betrag in der Gruppe bestimmt. Eine Zeile hat einige Hundert Zahlen; ist nur eine besonders groß, bläht sie den Faktor der ganzen Zeile auf, die anderen Zahlen drängen sich auf wenige ganze Zahlen, und der Fehler ist groß. In kleinen Gruppen passt jeder Faktor besser zu seinen Zahlen, der Fehler ist viel kleiner, und der Preis sind nur etwas mehr gespeicherte Skalierungsfaktoren.
3. Warum muss der Code aus Teil 1 kaum geändert werden, um das lokale Modell von Ollama zu nutzen?
Ollama bietet eine OpenAI-kompatible Schnittstelle, und der Code aus Teil 1 nutzt die Bibliothek openai und legt Adresse, Schlüssel und Modellname in Umgebungsvariablen ab. Ändert man diese drei Variablen auf Adresse und Modellnamen von Ollama, ruft der Code direkt das lokale Modell auf.
Fragen und Diskussion
Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.
Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.
Diskussion wird geladen…