qwen4exp: support draft-head-only GGUFs (unsloth layout) + fix draft-load regression - #28097
Draft
TheArchitectit wants to merge 4 commits into
Draft
qwen4exp: support draft-head-only GGUFs (unsloth layout) + fix draft-load regression#28097TheArchitectit wants to merge 4 commits into
TheArchitectit wants to merge 4 commits into
Conversation
Adds the MTP head's own hyper-connection mixer tensor names and lists the NextN tensors under the qwen4exp architecture.
Adds --spec-type draft-mtp support for Qwen3.8-Flash-Next. The MTP head folds the next token's embedding into the trunk's wide hyper-connection residual, runs one trunk-style block (dense attention + MoE) over it, and collapses the result with its own mixer before reusing the trunk's LM head. - read nextn_predict_layers so n_layer() excludes the MTP block - load the trailing block through the existing trunk path: is_recr() and is_ple() are already false past the trunk, so it needs no special casing - eh_proj fuses the checkpoint's fc_embedding and fc_hidden side by side, so one matmul computes fc_embedding@e + fc_hidden@h - the head carries its own hyper-connection mixer, mirroring the trunk's hc_head_*, which stands in for the output norm qwen4exp does not have - export the wide pre-collapse residual as t_h_nextn from both graphs, so the driver can feed it back for the next draft step - route MTP contexts to a plain KV cache filtered to the trailing layer The draft block attends densely for now: the trunk's QSA only prunes context past a 2048-token budget, so dense is a numerical superset and drafts are verified either way. Indexer tensors are still loaded.
The MTP block is one trunk-shaped block (dense attention + MoE wrapped in hyper-connections) plus a head-level combiner, so once _QwenMtpMixin renames mtp.layers.0.* to the trailing block index its tensors ride the existing qwen4exp mappings unchanged. Two head-level pieces need handling: - fc_embedding and fc_hidden fuse into the eh_proj the shared NextN code expects, since W_e@e + W_h@h == [W_e|W_h] @ concat(e, h) - mtp.hyper_connection_mixer.* is the head's own copy of the trunk's hc_head_* output mixer, unindexed in the checkpoint and per-block in the GGUF compress_ratios is read with length block_count, so it gains a trailing 0 for the MTP block, which attends densely. --no-nextn drops the head; --mtp exports it on its own.
Draft-head files carry no trunk tensors and no hc_head mixer; they use nextn.shared_head_norm plus the block's own ffn projections instead. Detect such files (n_layer_nextn > 0 and no blk.0 hc_attn_norm), make the trunk hc_head tensors optional for them, and fall back in the MTP graph. Also fix the draft load using the target path instead of the draft path. Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
|
Hi @TheArchitectit, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
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.
qwen4exp: support draft-head-only GGUFs (unsloth layout) + fix draft-load regression
Companion patch to #27836 (qwen4exp architecture + NextN/MTP speculative decoding).
Two fixes that were necessary to run
--spec-type draft-mtpwith the unsloth-shipped Qwen3.8-Flash-Next GGUFs on CPU:Fix 1: draft-head-only GGUFs
PR #27836's qwen4exp loader requires
hc_head_norm/hc_head_down/hc_head_upand the PLE block unconditionally. The unsloth GGUFs ship draft-head-only files that lack these tensors — the target carriesoutput_hc_*in its shards; the draft pack has onlynextn.shared_head_norm+ the block's own ffn projections.This patch adds
mtp_onlydetection (n_layer_nextn > 0 && blk.0.hc_attn_norm absent):hc_head_*tensors markedTENSOR_NOT_REQUIREDwhenmtp_only!mtp_onlymtp_only(layers 0..n_layer-1 absent)shared_head_norm+hc_ffn_down/hc_ffn_upwhen the head's own mixer is absent (mirrors the fallback path in the reference implementation)nextn.hc_head_*markedTENSOR_NOT_REQUIRED; newnextn.shared_head_normtensor loaded when presentFix 2: draft-load regression
common/speculative.cpp:2585loadedparams.model.path(the target model path) instead ofmodel_path(the draft model path) — the variable holding the user-specified-mdpath. This caused the 147 GB target model to be loaded again as the draft, consuming all RAM and failing. One-line fix.Testing
Tested on a 48-core Haswell Xeon (E5-2660 v3) with Qwen3.8-Flash-Next (UD-Q4_K_XL target, 4-shard GGUF, ~147 GB mlock) and the native MTP head quantized to Q8_0 as the draft model.
Acceptance on the upstream per-stream combiner is dramatically higher than the mean-pooled alternative (0.51–0.75 vs 0.18–0.20, same head/flags), confirming PR #27836's note that the combiner must run per hc stream.
Drafter quant ladder (same upstream binary, dn=4)
All rungs requantized from the native BF16 MTP head pack (bit-identical lineage), arm-tested with journal-verified loads:
Acceptance binds only below ~5 bits; every rung beats stock (3.33 t/s). Q8_0 is the practical pick (identical speed to F16 at half the RAM; F16 ties at 4.29 t/s with highest acceptance 2.51 but costs 7.3 GB).
Draft-n-max sweep (Q8_0)
dn=4 optimal; higher dn costs more verify than acceptance recovers.
Files changed
src/models/qwen4exp.cpp— mtp_only detection, optional tensors, graph fallback (+25 lines)common/speculative.cpp— draft-load path fix (1 line)