Davidmg0815's picture
Upload README.md with huggingface_hub
dc1f32e verified
|
Raw
History Blame
14.7 kB
---
license: apache-2.0
license_link: https://huggingface.co/Qwen/Qwen3.8-27B/blob/main/LICENSE
base_model:
- Qwen/Qwen3.8-27B
base_model_relation: quantized
pipeline_tag: image-text-to-text
library_name: llama.cpp
language:
- de
- en
tags:
- gguf
- llama.cpp
- mtp
- speculative-decoding
- qwen3
- multimodal
---
# Qwen3.8-27B GGUF — **mit separatem MTP-Draft**
GGUF-Konvertierung von [Qwen/Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B),
gebaut am 14.08.2026 aus den **BF16**-Originalgewichten.
Der Grund für dieses Repo ist die Datei **`qwen3.8-27b-MTP-Q8_0.gguf`**: der
Multi-Token-Prediction-Kopf als eigenständiges Draft-Modell, direkt nutzbar mit
`--spec-type draft-mtp`. Die MTP-Gewichte stecken im Upstream-Checkpoint
(`mtp_num_hidden_layers` in der `config.json`), werden von den üblichen
GGUF-Repos aber nicht als getrennte Datei ausgeliefert — und ohne getrennte
Datei kann `llama-server` sie nicht als Draft laden.
**Es bringt Faktor 1,6 bis 2,7.** Gemessen, nicht geschätzt — siehe unten.
## Dateien
| Datei | Größe | BPW | Zweck |
|---|---|---|---|
| `qwen3.8-27b-Q8_0.gguf` | 27,3 GB | 8,50 | Zielmodell — **empfohlen** |
| `qwen3.8-27b-Q6_K.gguf` | 21,0 GB | 6,57 | kleinere Alternative |
| `qwen3.8-27b-MTP-Q8_0.gguf` | 3,0 GB | — | **MTP-Draft** für spekulatives Dekodieren |
| `qwen3.8-27b-mmproj-F16.gguf` | 0,9 GB | — | Vision/Video-Projektor |
Die MTP-Tensoren sind aus den Zielmodellen **entfernt** (`--no-mtp`) und liegen
ausschließlich in der Draft-Datei. Ziel- und Draft-Datei gehören zusammen.
**Welches Zielmodell?** Q8_0 ist das genauere, Q6_K das schnellere. Gemessen
ohne Draft, gleiche Einstellungen, 20 Aufgaben: **Q6_K 23,7 t/s gegen Q8_0
19,6 t/s** — Q6_K liegt 21 Prozent vorn, weil beim Erzeugen jedes Tokens alle
Gewichte einmal durch den Speicher müssen und 21 GB nun mal schneller gelesen
sind als 27 GB.
> **Korrektur, 15.08.2026.** Eine frühere Fassung dieser Karte behauptete das
> Gegenteil (»Q8_0 ist durchgehend schneller«). Diese Messung verglich Q8_0
> *mit* Draft gegen Q6_K *mit* Draft — dort hängt die Rate an der
> Draft-Trefferquote, nicht an der Dequantisierung, und der Vergleich war
> damit wertlos. Beim sauberen Vergleich ohne Draft dreht sich das Ergebnis um.
## Verwendung
```bash
llama-server \
-m qwen3.8-27b-Q8_0.gguf \
--mmproj qwen3.8-27b-mmproj-F16.gguf \
-md qwen3.8-27b-MTP-Q8_0.gguf \
--spec-type draft-mtp --spec-draft-n-max 3 --spec-draft-ngl 99 \
--ctx-size 97000 --parallel 1 \
--n-gpu-layers 99 --tensor-split 1,1,1 \
--cache-type-k q8_0 --cache-type-v q8_0 \
--flash-attn on --jinja
```
Ohne Spekulation einfach `-md` und die drei `--spec-*`-Schalter weglassen.
Ohne Bildeingabe zusätzlich `--mmproj` weglassen.
## Was MTP tatsächlich bringt
Beide Läufe mit **identischer** Konfiguration (Q8_0, 97K Kontext, eine Session),
einziger Unterschied ist das Draft-Modell. Deutsche Prompts, je 2–3 Durchgänge,
ausgewertet wird der Median. Die Zahlen kommen aus `timings.predicted_per_second`
bzw. `timings.draft_n` / `draft_n_accepted`, die `llama-server` pro Anfrage
selbst meldet.
| Textsorte | ohne Draft | **mit Draft** | **Faktor** | Draft-Trefferquote |
|---|---|---|---|---|
| Wiederholung | 22,9 t/s | **62,0 t/s** | **2,71×** | 85,0 % |
| Aufzählung | 23,0 t/s | **61,6 t/s** | **2,68×** | 93,1 % |
| Übersetzung | 22,9 t/s | **51,8 t/s** | **2,26×** | 70,9 % |
| Struktur / JSON | 23,0 t/s | **47,6 t/s** | **2,07×** | 59,7 % |
| Fachtext | 23,0 t/s | **41,7 t/s** | 1,81× | 48,0 % |
| Code | 23,0 t/s | **41,1 t/s** | 1,79× | 48,9 % |
| Fließtext | 22,9 t/s | **39,5 t/s** | 1,72× | 44,5 % |
| Kreativtext | 23,0 t/s | **35,9 t/s** | **1,56×** | 37,4 % |
Gesamt über alle Textsorten: **57,3 % Trefferquote** (4.491 von 7.842
Draft-Token).
### Der wichtigste Befund
**Ohne Draft ist die Rate völlig flach — 22,9 bis 23,0 t/s, egal was man
generiert.** Mit Draft spreizt sie sich um Faktor 1,7 auseinander, weil die
Trefferquote massiv an der Textsorte hängt: vorhersagbarer Text wird zu über
90 Prozent korrekt geraten, freier Kreativtext nur zu gut einem Drittel.
Wer MTP an einem Aufzählungs-Benchmark misst, kommt auf 2,7× und überschätzt
den Nutzen für normale Chatlast deutlich. Der realistische Wert für
Fließtext-Dialog liegt bei **rund 1,7×**.
### Was der Draft kostet
* **3,0 GB** auf der Platte
* **rund 2,7 GB VRAM** (`[spec] estimated memory usage of draft model is
2678.78 MiB` bei 97K Kontext; 1.953 MiB bei 4K)
* Ladezeit steigt kaum — 7 bis 27 Sekunden je nach Kontextgröße
Deutlich mehr als etwa der Gemma-4-12B-Draft mit 0,5 GB. Wer knapp an VRAM ist,
muss das einplanen.
## HumanEval+ — sieben Varianten, ein Ergebnis
Alle 164 Aufgaben, alle sieben Varianten, identische Bedingungen. Bewertet wird
durch Ausführen: jede gelieferte Funktion läuft gegen den vollständigen
EvalPlus-Testblock, der die HumanEval-Originaltests um etwa das Achtzigfache
erweitert.
![HumanEval+ je Variante](bench/quoten-hell.png)
| Variante | bestanden | Quote | Token/s | 95-%-Intervall |
|---|---|---|---|---|
| Qwen3.6-27B UD-Q6_K_XL | 149/164 | 90,9 % | 25,1 | ±4,4 |
| Qwen3.6-27B Uncensored Q6_K_P | 148/164 | 90,2 % | 26,9 | ±4,5 |
| **Qwen3.8-27B Q6_K** | 148/164 | **90,2 %** | **27,9** | ±4,5 |
| Qwen3.6-27B Q6_K | 146/164 | 89,0 % | 27,6 | ±4,8 |
| **Qwen3.8-27B Q8_0** | 145/164 | **88,4 %** | 19,5 | ±4,9 |
| **Qwen3.8-27B BF16** | 145/164 | **88,4 %** | 13,2 | ±4,9 |
| Qwen3.6-27B Q5_K_M | 145/164 | 88,4 % | 31,3 | ±4,9 |
### Der eigentliche Befund: die Unterschiede sind keine
Die gesamte Spanne beträgt **vier Aufgaben von 164**. Bei ±4,4 bis ±4,9 Punkten
Konfidenzintervall überlappen alle sieben Bereiche vollständig — **statistisch
ist keine Variante von einer anderen unterscheidbar.**
Noch deutlicher wird es beim Blick auf einzelne Aufgaben:
* **135 der 164 Aufgaben lösen alle sieben** Varianten
* **9 Aufgaben löst keine einzige** (HumanEval/32, 76, 91, 101, 127, 132, 145,
154, 163)
* nur **20 Aufgaben** unterscheiden überhaupt — und dort geht es mal so, mal so
aus
Die scheinbare Rangfolge entsteht ausschließlich aus diesen 20 Wackelkandidaten.
Wer daraus ein »Modell A ist besser als B« liest, liest Rauschen.
### Was man trotzdem mitnehmen kann
**Quantisierung kostet hier nichts.** BF16 (51 GB), Q8_0 (27 GB) und Q5_K_M
(19 GB) landen alle auf **exakt 145 von 164**. Von 51 auf 19 Gigabyte
heruntergerechnet — kein messbarer Qualitätsverlust bei Code, aber 13,2 gegen
31,3 Token/s. Die unquantisierte Variante ist für diesen Zweck reine
Verschwendung.
**Qwen3.8 schlägt Qwen3.6 bei Code nicht.** Die neue Generation liegt im selben
Bereich. Wer wegen Code aufrüstet, tut es umsonst; die Verbesserungen liegen
woanders (Vision, MTP-Kopf, Kontextlänge).
![Tempo gegen Qualität](bench/tempo-hell.png)
### Einordnung gegen publizierte Werte
| Modell | HumanEval+ |
|---|---|
| Phi-4 Reasoning (Microsoft) | 92,9 % |
| **Qwen3.8-27B Q6_K — diese Messung** | **90,2 %** |
| Llama 3 405B Instruct | 89 % |
| **Qwen3.8-27B Q8_0 — diese Messung** | **88,4 %** |
| Granite 3.3 8B Base | 86,1 % |
| Kimi K2 Base | 80,3 % |
> **Diese Zahlen sind nicht eins zu eins mit den Leaderboards vergleichbar.**
> Drei Abweichungen vom offiziellen EvalPlus-Verfahren: die Aufgabe wird im
> **Chat-Format** gestellt (»Vervollständige diese Funktion«) statt als reine
> Textvervollständigung; **Temperatur 0,2** statt greedy; und der Code wird aus
> Markdown-Zäunen geschält. Jeder Punkt kann einige Prozent bewegen, meist nach
> oben — Chat-Modelle schneiden im Chat-Format besser ab. Wer vergleichen will,
> nimmt die Zahl als Hausmessung, nicht als Leaderboard-Eintrag.
Das Fehlerbild ist gesund: bei Q8_0 sind von 19 Fehlschlägen **15
AssertionError**, also echte Logikfehler. Dazu je ein TypeError, SyntaxError,
IndexError und eine Zeitüberschreitung — kein Muster, das auf ein kaputtes
Testgerüst hindeutet.
Das komplette Messgerüst liegt unter `bench/` samt beider Selbsttests: einer
prüft das Aufgaben-Harness gegen bekannt richtige *und* bekannt falsche
Lösungen, der andere die EvalPlus-Ausführung gegen die mitgelieferten
Referenzlösungen (25/25 grün). Ohne diese Prüfung misst ein Benchmark unter
Umständen nur die eigenen Denkfehler.
## Durchsatz im Vergleich
![Durchsatz je Variante](bench/durchsatz-hell.png)
Sieben 27B-Varianten unter identischen Bedingungen — 16K Kontext, eine Session,
kein Draft, `--tensor-split 1,1,1`, KV-Cache q8_0, Temperatur 0,2:
| Variante | Größe | Token/s |
|---|---|---|
| Qwen3.6-27B Q5_K_M | 19 GB | 26,4 |
| Qwen3.6-27B Q6_K | 21 GB | 23,9 |
| **Qwen3.8-27B Q6_K** | 21 GB | **23,7** |
| Qwen3.6-27B Uncensored Q6_K_P | 22 GB | 23,1 |
| Qwen3.6-27B UD-Q6_K_XL | 24 GB | 21,1 |
| **Qwen3.8-27B Q8_0** | 27 GB | **19,6** |
| **Qwen3.8-27B BF16** | 51 GB | **11,5** |
Der Zusammenhang ist schlicht: mehr Gigabyte, weniger Token pro Sekunde. Bei der
Texterzeugung liest die GPU für **jedes einzelne Token** sämtliche Gewichte
einmal durch — Speicherbandbreite ist die Grenze, nicht Rechenleistung.
**Die BF16 läuft auf drei RTX 3080 — knapp.** 51,3 GB Gewichte belegen
16,7 / 16,8 / 17,7 GB der drei Karten, macht 51,2 von 60 GB. Mit 16K Kontext
bleibt genug für KV-Cache und Puffer. Sie kostet allerdings 40 Prozent Tempo
gegenüber Q8_0, ohne dass sich in unserem eigenen Aufgabensatz ein
Qualitätsvorsprung gezeigt hätte — beide scheiterten an derselben einen Aufgabe.
## Testaufbau
* 3× NVIDIA RTX 3080 20 GB — **Ampere, SM 8.6**
* AMD EPYC 7F32, 8 Kerne / 16 Threads
* llama.cpp Build **10423** (`a94d563ed`), Container `ghcr.io/ggml-org/llama.cpp:full-cuda`
* `--tensor-split 1,1,1`, KV-Cache `q8_0`, `--flash-attn on`
* Eine Session (`--parallel 1`), 97.024 Token Kontext
* Belegt: 31,2 GB von 60 GB — Modell 27,3 + Draft 2,7 + KV/Puffer
Der Kontext ist praktisch gratis: dieselbe Konfiguration mit nur 4K statt 97K
lieferte im Rahmen der Messstreuung dieselben Werte (58,8 gegen 60,5 t/s bei
Aufzählungen). Man kann den großen Kontext also mitnehmen, ohne Durchsatz zu
bezahlen.
## Wie diese Dateien gebaut wurden
```bash
# Zielmodell ohne MTP-Tensoren
convert_hf_to_gguf.py Qwen3.8-27B/ --outfile tmp-BF16.gguf --outtype bf16 --no-mtp
# nur der MTP-Kopf, als Draft
convert_hf_to_gguf.py Qwen3.8-27B/ --outfile qwen3.8-27b-MTP-Q8_0.gguf --outtype q8_0 --mtp
# Vision/Video-Projektor
convert_hf_to_gguf.py Qwen3.8-27B/ --outfile qwen3.8-27b-mmproj-F16.gguf --outtype f16 --mmproj
# quantisieren
llama-quantize tmp-BF16.gguf qwen3.8-27b-Q8_0.gguf Q8_0 16
llama-quantize tmp-BF16.gguf qwen3.8-27b-Q6_K.gguf Q6_K 16
```
Die Flags `--mtp` und `--no-mtp` sind neu; ältere llama.cpp-Builds kennen sie
nicht. Die Architektur `Qwen3_5ForConditionalGeneration` (Qwen3.8 nutzt sie
unverändert weiter) erbt `supports_mtp_export = True` über
`Qwen3_5TextModel → _LinearAttentionVReorderBase → Qwen3NextModel → _QwenMtpMixin`.
Quelle: `Qwen/Qwen3.8-27B` in **BF16** (51,3 GB, 16,00 BPW), nicht das
FP8-Release — also **keine** doppelte Quantisierung.
## Kontextlänge und KV-Cache — ab 48 GB hört die Abwägung auf
Auf kleineren Karten zwingt der KV-Cache zu einer Entscheidung: weniger Bits
im Cache, dafür mehr Kontext. Gemessen auf **60 GB** (3× RTX 3080) stellt sich
diese Frage nicht mehr, weil vorher das Modell selbst deckelt:
| KV-Cache | angefordert | tatsächlich belegt |
|---|---|---|
| q8_0 | 256.000 | 256.000 — läuft |
| q8_0 | 384.000+ | läuft, aber jenseits von `n_ctx_train` |
| q5_1 | 256.000 | 256.000 — läuft |
| q5_1 | 447.488 | abgelehnt |
`n_ctx_train` liegt bei Qwen3.8-27B bei **262.144 Token**. Darüber meldet
llama.cpp:
```
llama_context: n_ctx_seq (384000) > n_ctx_train (262144)
-- possible training context overflow
```
**Mit q8_0 erreicht man auf 60 GB bereits den vollen trainierten Kontext.** Ein
Wechsel auf q5_1 oder q4_0 bringt hier keine Kontextlänge mehr, sondern nur
freies VRAM — nützlich für mehr parallele Sitzungen, nicht für längere Eingaben.
Auf 24 oder 32 GB sieht das anders aus, dort ist die Abwägung real.
## Flash-Attention mit quantisiertem KV-Cache: bitte selbst nachmessen
[llama.cpp-Issue #24485](https://github.com/ggml-org/llama.cpp/issues/24485)
beschreibt einen **stillen** Rückfall auf CPU-Attention, wenn ein quantisierter
KV-Cache mit Flash-Attention kombiniert wird und der Build ohne
`GGML_CUDA_FA_ALL_QUANTS=ON` erzeugt wurde. Keine Warnung, aber 25- bis
45-fach langsamerer Prefill. Das Issue wurde als *not planned* geschlossen —
das Verhalten bleibt also bestehen.
Unser Build ist **nicht** betroffen. Der Test dauert fünf Minuten: derselbe
Prompt (8.933 Token), einmal mit q8_0-Cache, einmal mit f16.
| KV-Cache | Prefill | Generierung |
|---|---|---|
| q8_0 | **2.111,7 Tok/s** | 22,8 Tok/s |
| f16 | **2.142,7 Tok/s** | 23,0 Tok/s |
1,4 Prozent Abstand — kein Rückfall. **Wichtig: an der Generierungsrate sieht
man den Fehler nicht.** Die ist durch die Speicherbandbreite begrenzt und ändert
sich kaum; nur die Prefill-Rate deckt das Problem auf. Das dürfte der Grund
sein, warum der Fehler in vielen Aufbauten unbemerkt bleibt.
Nebenbefund: `--flash-attn off` mit quantisiertem KV-Cache stürzt auf diesem
Build ab (Speicherzugriffsfehler), statt sauber abzulehnen.
## Zwei Hinweise für Ampere-Besitzer
**FP8 läuft auf Ampere nicht nativ.** SM 8.6 hat keine FP8-Tensorcores, die gibt
es erst ab SM 8.9. Qwens FP8-Release lässt sich dort nur dequantisiert fahren —
Speicher gespart, Tempo nicht. Für GGUF ist das gegenstandslos, weil `Q8_0` und
`Q6_K` reine Integer-Formate mit nativen Ampere-Kernen sind.
**vLLM scheitert hier an drei GPUs.** Das Modell hat 24 Attention-Heads, aber
nur **4 KV-Heads**. vLLM verlangt, dass `tensor_parallel_size` die KV-Head-Zahl
teilt — 4 durch 3 geht nicht auf. Bleiben TP=2 und damit eine ungenutzte Karte.
llama.cpp kennt das Problem nicht, weil `--tensor-split` Layer verteilt statt
Attention-Köpfe. **Für ungerade GPU-Zahlen ist llama.cpp hier der einzige Weg,
alle Karten zu nutzen.**
## Reproduzieren
Das Messkript ist beigelegt (`bench_mtp.py`, deutsche Prompts über acht
Textsorten, Median aus N Durchgängen):
```bash
python3 bench_mtp.py --port 8290 --runs 3 --max-tokens 300 --out ergebnis.md
```
Einzelmessungen schwanken um etwa ±6 Prozent, deshalb Median statt Einzelwert.
## Lizenz
Apache 2.0, wie das Basismodell. Alle Rechte am Modell liegen beim Qwen-Team /
Alibaba Cloud. Dieses Repo enthält ausschließlich formatkonvertierte Gewichte,
kein zusätzliches Training und keine Änderung am Verhalten.