Repository navigation
MTP: int4 head unusable (int8 fixes acceptance); speculation still a net loss on CPU streaming-MoE — measured. #8
Description
🇮🇹 Italiano
Ambiente
macOS, Apple Silicon (M-series), 128 GB unified memory, SSD interno (~18 GB/s random read misurato con iobench).
GLM-5.2-FP8 convertito con i default (--ebits 4 --io-bits 8); testa MTP aggiunta con convert_fp8_to_int4.py --mtp (quindi anch'essa int4).
Decode greedy (TOPK=0 TOPP=0).
Osservazione
La testa MTP viene caricata correttamente (MTP ATTIVA (draft=3)), ma l'accettazione dei
draft è quasi nulla, anche su testo banalmente prevedibile, quindi la guardia adattiva
disabilita la speculazione dopo 24 proposte e non si ottiene alcun guadagno (tok/fw ≈ 1.0).
Prosa ("Explain how a hash map works."):
MTP acceptance 0% (0/24) -> draft disattivati
128 token in 139.86s (0.92 tok/s) | speculazione 1.01 token/forward
Testo massimamente prevedibile ("Count from one to fifty: one, two, three, ..."):
MTP acceptance 4% (1/24) -> draft disattivati
113 token in 117.64s (0.96 tok/s) | speculazione 1.01 token/forward
4% su un conteggio da 1 a 50 è sorprendentemente basso: con decode greedy una buona testa
draft dovrebbe azzeccare il token successivo molto più spesso.
Ipotesi
La testa MTP a int4 è troppo lossy per predire con accuratezza: i draft sbagliano quasi
sempre, quindi la speculazione non si attiva mai.
Esperimento: testa MTP a int8
Rigenerata SOLO la testa a precisione più alta (--mtp --ebits 8), il resto del modello
invariato (la testa è un solo layer, costo di memoria trascurabile):
MTP acceptance 59% (72/123) <-- era 4% a int4 / was 4% at int4
113 token in 130.14s (0.87 tok/s) | speculazione 2.76 token/forward
L'accettazione è balzata da 4% a 59% e la speculazione da 1.01 a 2.76 token/forward:
la testa int4 era davvero troppo lossy per fare draft.
Secondo datapoint, prosa ("Explain how a hash map works."):
MTP acceptance 39% (69/177) | speculazione 2.17 token/forward
128 token in 202.56s (0.63 tok/s) | hit 15% | expert/token 1183 (baseline ~660)
Nota onesta e importante: su questa macchina il tok/s con MTP peggiora su cache
fredda (0.63–0.87 vs ~1.0 senza MTP). Motivo: ogni forward speculativo verifica ~2–3 token,
e quei token di draft instradano ai loro expert → il forward tocca molti più expert
(expert/token ~660 → ~1024–1183). Su un motore disk-bound con cache piccola/fredda questo
amplificazione del caricamento expert costa più di quanto si risparmi facendo meno
forward. L'accettazione è sana (39–59%): il collo di bottiglia è lo streaming degli expert,
non la qualità del draft. Da notare: il motore disabilita il draft se l'accettazione è bassa,
ma non se una buona accettazione rallenta comunque per via dell'amplificazione su disco.
Si potrebbe pensare che il guadagno serva solo con cache calda e grande (working set
speculativo residente) — ma l'A/B a caldo qui sotto mostra che anche così MTP perde; su
disco lento (~1 GB/s) l'effetto negativo sarebbe ancora più marcato.
A/B pulito (stesso prompt "count to fifty", stessa cache fredda cap=8, solo MTP cambia):
MTP=0 : 0.85 tok/s | hit 36% | expert/token 738 | 1.00 tok/fw
MTP=1 : 0.61 tok/s | hit 24% | expert/token 1024 | 2.76 tok/fw | accettazione 59%
MTP è ~28% più lento a parità di tutto: la speculazione gonfia il working set degli
expert (+39% expert/token) e abbassa la hit-rate (36%→24%), quindi i download da disco
superano il risparmio di forward. Con 59% di accettazione, e comunque perde.
A/B a CALDO (chat, cap=48, cache scaldata con turni ripetuti via :reset, hit ~71–78%):
MTP=0 : 0.61 tok/s (hit 78%)
MTP=1 : 0.47 tok/s (hit 71%)
Anche a caldo MTP è ~23% più lento. Qui il disco non c'entra più (expert in cache): il
decode è matmul/banda-bound, e la speculazione verifica ~3 token per forward, quindi
AUMENTA il matmul per forward invece di ridurlo — risparmia solo il numero di forward, che
non è il collo di bottiglia. Conclusione: su questo motore MoE in streaming la testa MTP
funziona (accettazione sana a int8) ma è un calo netto, sia a freddo sia a caldo.
Suggerimento
Due punti distinti:
Precisione della testa. A int4 la speculazione non si aggancia mai (testa peso morto);
a int8 sì (accettazione ~40–60%). Se MTP deve esistere, la testa andrebbe int8 di
default (o almeno documentare --ebits 8 per --mtp). La testa è un solo layer: costo
RAM/disco minimo.
Convenienza su questo motore. Anche con testa int8 e accettazione sana, la speculazione
è un calo netto a freddo e a caldo su CPU + streaming MoE (matmul-bound): aumenta il
matmul per forward invece di ridurlo. Quindi forse conviene MTP OFF di default su questo
tipo di hardware, e/o una guardia che spenga il draft se rallenta nonostante buona
accettazione (oggi la guardia scatta solo per accettazione bassa). Su HW diverso
(forward-dispatch bound) potrebbe invece aiutare — vale la pena misurarlo.
🇬🇧 English
Environment
macOS, Apple Silicon (M-series), 128 GB unified memory, internal SSD (~18 GB/s random read, measured with iobench).
GLM-5.2-FP8 converted with defaults (--ebits 4 --io-bits 8); MTP head added with convert_fp8_to_int4.py --mtp (so also int4).
Greedy decode (TOPK=0 TOPP=0).
Observation
The MTP head loads fine (MTP ATTIVA (draft=3)), but draft acceptance is near zero, even
on trivially predictable text, so the adaptive guard disables speculation after 24 proposals
and there is no gain (tok/fw ≈ 1.0).
Prose ("Explain how a hash map works."):
MTP acceptance 0% (0/24) -> draft disabled
128 tokens in 139.86s (0.92 tok/s) | speculation 1.01 tokens/forward
Maximally predictable text ("Count from one to fifty: one, two, three, ..."):
MTP acceptance 4% (1/24) -> draft disabled
113 tokens in 117.64s (0.96 tok/s) | speculation 1.01 tokens/forward
4% on counting to fifty is surprisingly low — with greedy decode a good draft head should
predict the next token far more often.
Hypothesis
The int4 MTP head is too lossy to predict accurately: drafts almost always miss, so
speculation never engages.
Experiment: int8 MTP head
Regenerated ONLY the head at higher precision (--mtp --ebits 8), rest of the model
unchanged (the head is a single layer, so the memory cost is negligible):
MTP acceptance 59% (72/123) <-- was 4% at int4
113 tokens in 130.14s (0.87 tok/s) | speculation 2.76 tokens/forward
Acceptance jumped from 4% to 59% and speculation from 1.01 to 2.76 tokens/forward:
the int4 head really was too lossy to draft.
Second datapoint, prose ("Explain how a hash map works."):
MTP acceptance 39% (69/177) | speculation 2.17 tokens/forward
128 tokens in 202.56s (0.63 tok/s) | hit 15% | experts/token 1183 (baseline ~660)
Honest and important note: on this machine tok/s with MTP actually gets worse on a
cold cache (0.63–0.87 vs ~1.0 without MTP). Reason: each speculative forward verifies ~2–3
tokens, and those draft tokens route to their own experts → the forward touches many more
experts (experts/token ~660 → ~1024–1183). On a disk-bound engine with a small/cold cache
this expert-load amplification costs more than the fewer-forwards saving. Acceptance is
healthy (39–59%): the bottleneck is expert streaming, not draft quality. Note the engine
disables draft when acceptance is low, but not when good acceptance still slows things
down via that amplification on a disk-bound run. One might expect the gain to appear only with a warm, large cache (speculative working set
resident) — but the warm A/B below shows even that loses; on a slow disk (~1 GB/s) the
negative effect would be even more pronounced.
Clean A/B (same "count to fifty" prompt, same cold cap=8 cache, only MTP toggled):
MTP=0 : 0.85 tok/s | hit 36% | experts/token 738 | 1.00 tok/fw
MTP=1 : 0.61 tok/s | hit 24% | experts/token 1024 | 2.76 tok/fw | acceptance 59%
MTP is ~28% slower with everything else equal: speculation inflates the expert working
set (+39% experts/token) and drops hit-rate (36%→24%), so disk reads outweigh the saved
forwards. 59% acceptance and it still loses.
WARM A/B (chat, cap=48, cache warmed over repeated turns via :reset, hit ~71–78%):
MTP=0 : 0.61 tok/s (hit 78%)
MTP=1 : 0.47 tok/s (hit 71%)
Even warm, MTP is ~23% slower. Disk is out of the picture now (experts are cached): decode
is matmul/bandwidth-bound, and speculation verifies ~3 tokens per forward, so it increases
matmul per forward rather than reducing it — it only saves the number of forwards, which was
never the bottleneck. Conclusion: on this CPU streaming-MoE engine the MTP head works
(healthy acceptance at int8) but is a net loss, both cold and warm.
Suggestion
Two separate points:
Head precision. At int4 speculation never engages (head is dead weight); at int8 it
does (~40–60% acceptance). If MTP is to exist at all, the head should be int8 by default
(or at least document --ebits 8 for --mtp). The head is a single layer, so the RAM/disk
cost is minimal.
Whether it's worth it on this engine. Even with an int8 head and healthy acceptance,
speculation is a net loss cold and warm on CPU + streaming MoE (matmul-bound): it
increases matmul per forward instead of reducing it. So it may be worth MTP OFF by
default on this class of hardware, and/or a guard that disables draft when it slows
things down despite good acceptance (today the guard only triggers on low acceptance). On
different HW (forward-dispatch bound) it might help instead — worth measuring.
Activity
JustVugg commentedon Jul 8, 2026
Really perfect!!
I had also noticed this with MTP, that's why I was looking into it!
JustVugg commentedon Jul 8, 2026
Thanks for the help!and benchmark!
JustVugg commentedon Jul 9, 2026
I merged it! I think it should work now. I did my tests better! Let me know if there are any errors or anything!
MTP head is int8 by default (int4 head: 0-4% acceptance, unusable — m…
JustVugg commentedon Jul 9, 2026
Resolved in c035f41 — and thank you for what is the single most valuable measurement this project has received:
--mtp(and--indexer) now default the head to int8;coli convert's MTP pass forcesmax(8, ebits)even if the main model is int4. Nobody can produce an unusable int4 head by accident anymore.660→1100 experts/token, so speculation can be a net time loss until the cache/pin warms — adaptive guard andDRAFT=0documented).The warm-cache regime is where colibrì's learning cache is headed anyway (your REPIN RFC helps exactly there), so MTP stays on by default — but the trade-off is now documented instead of hidden.
RDouglasSharp commentedon Jul 9, 2026
JustVugg commentedon Jul 9, 2026
I'm definitely interested, maybe we can add Pi and more! We need to continually improve both this and the speed of the model! I don't have the strongest hardware, in fact, not only that! But it's a great idea. Let's try to understand everything well!
LOOKA: routing predictability counters; PILOT: router-lookahead disk …
Grammar-forced speculative drafts: GBNF grammar as a third draft sour…
Add one-command download + benchmark (make quickstart), wire into README
docs: GLM-5.2 6x RTX 5090 full-residency experiment — 6.84 tok/s, hon…
README: lead with the int8-MTP mirror, make the int4-head 0%-acceptan…
Merge main into dev: README int8-MTP mirror lead (#8,#102)
add Windows helper scripts: staged readiness test + parallel model do…
tools: repair_mtp_int8.py — fix int4-converted MTP heads in place; wa…
MTP head is int8 by default (int4 head: 0-4% acceptance, unusable — m…
LOOKA: routing predictability counters; PILOT: router-lookahead disk …
Grammar-forced speculative drafts: GBNF grammar as a third draft sour…
README: lead with the int8-MTP mirror, make the int4-head 0%-acceptan…
docs: GLM-5.2 6x RTX 5090 full-residency experiment — 6.84 tok/s, hon…
Merge main into dev: README int8-MTP mirror lead (JustVugg#8,JustVugg…
tools: repair_mtp_int8.py — fix int4-converted MTP heads in place; wa…
Merge pull request JustVugg#8 from Avicennasis/merge-upstream-2026-09-16