feat(models): drive Gemma 4 12B's MTP head from Unsloth's sibling drafter - #231
Merged
Conversation
…fter The 12B shipped with `MtpSource::None` because its main GGUF carries no `blk.N.nextn.*` tensors, with a note that Unsloth publishes a drafter separately and it was not wired up until measured. This wires it up so it can be measured. `MTP/mtp-gemma-4-12b-it-Q8_0.gguf`, 465,109,248 bytes, sha256 from the LFS oid the same way the projector hashes are sourced. Q8_0 over the 862 MB F16/BF16: a drafter is quality-tolerant — a rejected draft token costs throughput, not correctness — so the small one is the right trade on a tier this VRAM-tight. Stored locally as `Gemma4-12B-MTP-Q8_0.gguf`, normalised like the projectors since the models dir is flat and the upstream `MTP/` prefix is lost on download. The fit absorbs it: `fixed_vram_bytes` now charges MTP_OVERHEAD_BYTES plus the drafter's own weights when the preference is on, and a 16 GB card still reaches 262144 either way — the ~2.2 GB spare above drops to ~1.2 GB. Guarded by a test that also asserts the weights are genuinely counted, so the equality can't pass vacuously. Two fixes the registry data alone would have left broken: `find_any_model` skipped `.partial` and mmproj files and nothing else. `read_dir` order is arbitrary, so first-run autodetect could have handed back a 465 MB draft head as "the model". It now also skips filenames matching a registry `Sibling`, matched against the registry rather than by substring so a user's own `…mtp….gguf` isn't hidden. The network drift guard checked weights and projector URLs but not the drafter, which fails a download exactly the same way. Extended to cover it; it passes against upstream. Draft tuning, scoped to the sibling path: `--spec-draft-ngl all` because the default `auto` decides after the target and its KV cache have taken theirs, and a partially offloaded drafter still produces tokens — the host round-trips are invisible and read as "MTP bought nothing". And `--spec-draft-n-max 4` per Unsloth's card over llama.cpp's default of 3. Neither applies to the bundled-head 27B: there is no separate drafter to offload there, and no measurement behind moving it off the defaults. A model downloaded before this lands won't have the drafter file. `find_mtp_draft_for_model` returns None and the server starts without the flag rather than failing, which is silent and reads as "MTP made no difference" — so that case now warns in the log. Verified end to end on a 16 GB card: the download fetched weights, projector and drafter, and llama-server came up with `--model-draft … --spec-draft-ngl all --spec-draft-n-max 4 --spec-type draft-mtp`. Claude-Session: https://claude.ai/code/session_011UTGzKCSRDYAzxPtFZdNf7 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The 16 GB tier's Gemma 4 12B shipped with
MtpSource::Noneand a note that Unsloth publishes a drafter separately, whichMtpSource::Siblingexists for and which would fit inside the tier's headroom — "not wired up until it's been measured." This wires it up so it can be measured.The drafter
MTP/mtp-gemma-4-12b-it-Q8_0.gguf, 465,109,248 bytes, sha256 taken from the LFS oid the same way the projector hashes are. Q8_0 over the 862 MB F16/BF16: a drafter is quality-tolerant — a rejected draft token costs throughput, not correctness — so the small one is the right trade on a tier this VRAM-tight. Stored locally asGemma4-12B-MTP-Q8_0.gguf, normalised like the projectors because the models dir is flat and the upstreamMTP/prefix is lost on download.The fit absorbs it.
fixed_vram_byteschargesMTP_OVERHEAD_BYTESplus the drafter's own weights when the preference is on, and a 16 GB card still reaches 262144 either way — the ~2.2 GB spare above drops to ~1.2 GB. The new test asserts both that the ceiling holds and that the weights are genuinely counted, so the equality can't pass vacuously.Two fixes the registry data alone would have left broken
find_any_modelwould have picked up the drafter as a model. It skipped.partialand mmproj files and nothing else, andread_dirorder is arbitrary — first-run autodetect could have handed back a 465 MB draft head as "the model" and started the server on it. It now also skips filenames matching a registrySibling, matched against the registry rather than by substring so a user's own…mtp….ggufisn't hidden.The network drift guard didn't cover the drafter. A drafter that 404s or changes size fails a download exactly the way the weights would. Extended; passes against upstream.
Draft tuning
--spec-draft-ngl alland--spec-draft-n-max 4, both scoped to the sibling path.--spec-draft-ngldefaults toauto, which decides how much of the drafter to keep in VRAM after the target and its KV cache have claimed theirs. A partially offloaded drafter still produces tokens, so the host round-trips are invisible and just read as "MTP bought nothing" — worth pinning rather than leaving to chance.--spec-draft-n-max 4is what Unsloth's drafter card recommends over llama.cpp's default of 3.Neither applies to the bundled-head Qwen 3.8 27B: there's no separate drafter to offload there, and no measurement behind moving that model off llama.cpp's defaults. Both still sit ahead of
extra_args, so they stay overridable.Upgrade path
A 12B downloaded before this lands won't have the drafter file.
find_mtp_draft_for_modelreturnsNoneand the server starts without the flag rather than failing — correct, but silent, and silence here reads as "MTP made no difference". That case now warns in the log naming the missing file. Re-running the download fetches only what's missing, sincedownload_modeldoesn't short-circuit at the top.Verification
-D warnings,cargo fmt --check,npm run check,npm run lint,format:check— all clean. No IPC type drift (mtpstill serialises as the sameboolean).#[ignore]d network drift guard run explicitly, confirming the drafter URL and size against upstream.LLAMA_CPP_VERSIONis b9565 (2026-06-08), 16 commits past the Gemma4 MTP merge (llama : add Gemma4 MTP ggml-org/llama.cpp#23398), so the drafter'sgemma4-assistantarch loads.--model-draft … --spec-draft-ngl all --spec-draft-n-max 4 --spec-type draft-mtp.Still open
The point of this PR is to make the benchmark possible, not to claim a speedup — no throughput numbers here yet. Unsloth's own figure (52 → 162 tok/s at 0.70 acceptance) is a B200 against a Q4_K_M target, so it says little about Vulkan against Q6.
MTP_OVERHEAD_BYTESalso remains the uncalibrated 512 MB placeholder its comment admits to; a with/without pair at fixed context would replace the guess with a number.🤖 Generated with Claude Code
https://claude.ai/code/session_011UTGzKCSRDYAzxPtFZdNf7