Summary
Draft acceptance collapses to 0–6% on any Qwen3.5/3.8-family checkpoint that is both
MLX-quantized and embeds its MTP head in the trunk shards. The cause is not in the MTP
sidecar — it is that mlx-lm's qwen3_5 sanitizer adds +1.0 to every trunk RMSNorm
gain, because it infers "this checkpoint needs the delta→absolute restore" from the mere
presence of mtp.* keys rather than from the values.
The trunk is corrupted before the drafter ever runs, so no sidecar can match it.
This is distinct from the sidecar-extraction bug also filed in #301. That one was real and
is fixed (Refs #301), but fixing it does not restore acceptance, and #301 should not
be closed on the strength of it.
Measured
Loading a forged artifact whose shards embed the head, then comparing one trunk norm on disk
against the same tensor after mlx_lm.utils.load:
trunk norm language_model.model.layers.0.input_layernorm.weight
on disk : 0.9666
runtime : 1.9666
delta : +1.0000
Verification of that artifact:
AR 15.04 tok/s | D1 13.46 (0.895x) acc 6.7%
D2 11.47 (0.763x) acc 0.9/0.0% | D3 11.18 (0.743x) acc 2.9/0.0/0.0%
verdict: mtp_acceptance_collapsed
Contract calibration tested 64 candidates and returned best_agreement: 0.0,
agreement_by_depth: [0.0, 0.0, 0.0], calibration_status: no_agreement_signal.
A control artifact built from a trunk of the same base model, same
{"bits": 8, "group_size": 64, "mode": "affine"} quantization, but with no mtp.* keys
in its shards (sidecar supplied separately) reaches best_agreement: 1.0 and:
AR 17.23 tok/s | D1 39.14 (2.271x) acc 97.8%
D2 47.15 (2.736x) acc 95.2/89.6% | D3 54.12 (3.140x) acc 94.8/90.7/79.3%
verdict: mtp_depth_wins
The two trunks' norms are numerically identical (layers.0.input_layernorm 0.9666 vs
0.9666, post_attention_layernorm 0.7827 vs 0.7827, model.norm 1.9441 vs 1.9441). The
only structural difference is the presence of the 29 embedded mtp.* keys.
Mechanism
mlx_lm/models/qwen3_5.py, Model.sanitize:
def sanitize(self, weights):
has_mtp_weights = any("mtp." in k for k in weights)
has_unsanitized_conv1d = any(
"conv1d.weight" in k and v.shape[-1] != 1 for k, v in weights.items()
)
should_shift_norm_weights = has_mtp_weights or has_unsanitized_conv1d
weights = {k: v for k, v in weights.items() if "mtp." not in k}
...
if should_shift_norm_weights and any(k.endswith(sfx) for sfx in norm_keys):
if v.ndim == 1:
weights[k] = v + 1.0
has_unsanitized_conv1d is a genuine value-based signal (shape[-1] != 1).
has_mtp_weights is a bare presence check. A raw HF export happens to satisfy both, so the
heuristic looks correct there — but an MLX build that already stores gains absolute and
still ships its head trips the first condition and gets shifted a second time.
Note the MTP tensors are discarded on the next line, so the keys that trigger the shift are
not even the ones being shifted.
Pruning model.safetensors.index.json does not help: mlx_lm/utils.py loads via
glob.glob(str(model_path / "model*.safetensors")) and ignores the index entirely.
Why the sidecar is not implicated
MTPLX loads mtp.safetensors exclusively when present, treating embedded weights as a
fallback. Substituting a known-good sidecar (sha256-identical to the one driving the 3.140x
control above) into the failing artifact and re-verifying against the same trunk changed
nothing — D3 acceptance 2.0/0.0/0.0, 11.20 tok/s, 0.735x. The sidecar is inert here.
I also confirmed the extracted sidecar is byte-faithful after the #301 extraction fix:
input_layernorm 1.0361, post_attention_layernorm 1.2063, k_norm 1.7795,
q_norm 1.7906, mtp.norm 2.2520 — source and sidecar identical to within 1e-6.
Reproduce
hf download chimingw/Qwen3.8-27B-Uncensored-OrcaRouter-MLX-8bit (31.2 GB, 29 embedded
language_model.mtp.* tensors).
mtplx forge build --repo <path> --recipe '{"body_bits":8,"body_group_size":64,"body_mode":"affine","mtp_policy":"keep_bf16"}' ...
- Compare any trunk norm on disk against its value after
mlx_lm.utils.load on the forged
artifact — it is exactly +1.0 higher.
Suggested direction
has_mtp_weights should be replaced with a value-based test, the same way
has_unsanitized_conv1d already is. The gains separate cleanly: on this family
delta-encoded q/k norms sit near 0.78 and absolute ones near 1.78, and MTPLX already ships a
two-signal predicate (qk_max >= 1.25 or low_min >= 0.5 ⇒ already absolute) that classifies
both conventions correctly with wide margins.
Since the fix is upstream, MTPLX may want an interim guard: detect at forge time that a
source embeds mtp.* keys and stores gains absolute, and either refuse to brand it or
strip the embedded keys from the physical shards rather than only from the index.
Environment
- mtplx 2.9.0
- mlx_lm 0.31.3, mlx 0.32.1
- macOS 26.5.2, Apple M5 Max, 128 GB
- Source:
chimingw/Qwen3.8-27B-Uncensored-OrcaRouter-MLX-8bit
- Control:
orcarouter/Qwen3.8-27B-Uncensored-MLX (8-bit/) + an externally supplied sidecar
Summary
Draft acceptance collapses to 0–6% on any Qwen3.5/3.8-family checkpoint that is both
MLX-quantized and embeds its MTP head in the trunk shards. The cause is not in the MTP
sidecar — it is that
mlx-lm'sqwen3_5sanitizer adds +1.0 to every trunk RMSNormgain, because it infers "this checkpoint needs the delta→absolute restore" from the mere
presence of
mtp.*keys rather than from the values.The trunk is corrupted before the drafter ever runs, so no sidecar can match it.
This is distinct from the sidecar-extraction bug also filed in #301. That one was real and
is fixed (
Refs #301), but fixing it does not restore acceptance, and #301 should notbe closed on the strength of it.
Measured
Loading a forged artifact whose shards embed the head, then comparing one trunk norm on disk
against the same tensor after
mlx_lm.utils.load:Verification of that artifact:
Contract calibration tested 64 candidates and returned
best_agreement: 0.0,agreement_by_depth: [0.0, 0.0, 0.0],calibration_status: no_agreement_signal.A control artifact built from a trunk of the same base model, same
{"bits": 8, "group_size": 64, "mode": "affine"}quantization, but with nomtp.*keysin its shards (sidecar supplied separately) reaches
best_agreement: 1.0and:The two trunks' norms are numerically identical (
layers.0.input_layernorm0.9666 vs0.9666,
post_attention_layernorm0.7827 vs 0.7827,model.norm1.9441 vs 1.9441). Theonly structural difference is the presence of the 29 embedded
mtp.*keys.Mechanism
mlx_lm/models/qwen3_5.py,Model.sanitize:has_unsanitized_conv1dis a genuine value-based signal (shape[-1] != 1).has_mtp_weightsis a bare presence check. A raw HF export happens to satisfy both, so theheuristic looks correct there — but an MLX build that already stores gains absolute and
still ships its head trips the first condition and gets shifted a second time.
Note the MTP tensors are discarded on the next line, so the keys that trigger the shift are
not even the ones being shifted.
Pruning
model.safetensors.index.jsondoes not help:mlx_lm/utils.pyloads viaglob.glob(str(model_path / "model*.safetensors"))and ignores the index entirely.Why the sidecar is not implicated
MTPLX loads
mtp.safetensorsexclusively when present, treating embedded weights as afallback. Substituting a known-good sidecar (sha256-identical to the one driving the 3.140x
control above) into the failing artifact and re-verifying against the same trunk changed
nothing — D3 acceptance 2.0/0.0/0.0, 11.20 tok/s, 0.735x. The sidecar is inert here.
I also confirmed the extracted sidecar is byte-faithful after the #301 extraction fix:
input_layernorm 1.0361,post_attention_layernorm 1.2063,k_norm 1.7795,q_norm 1.7906,mtp.norm 2.2520— source and sidecar identical to within 1e-6.Reproduce
hf download chimingw/Qwen3.8-27B-Uncensored-OrcaRouter-MLX-8bit(31.2 GB, 29 embeddedlanguage_model.mtp.*tensors).mtplx forge build --repo <path> --recipe '{"body_bits":8,"body_group_size":64,"body_mode":"affine","mtp_policy":"keep_bf16"}' ...mlx_lm.utils.loadon the forgedartifact — it is exactly +1.0 higher.
Suggested direction
has_mtp_weightsshould be replaced with a value-based test, the same wayhas_unsanitized_conv1dalready is. The gains separate cleanly: on this familydelta-encoded q/k norms sit near 0.78 and absolute ones near 1.78, and MTPLX already ships a
two-signal predicate (
qk_max >= 1.25 or low_min >= 0.5⇒ already absolute) that classifiesboth conventions correctly with wide margins.
Since the fix is upstream, MTPLX may want an interim guard: detect at forge time that a
source embeds
mtp.*keys and stores gains absolute, and either refuse to brand it orstrip the embedded keys from the physical shards rather than only from the index.
Environment
chimingw/Qwen3.8-27B-Uncensored-OrcaRouter-MLX-8bitorcarouter/Qwen3.8-27B-Uncensored-MLX(8-bit/) + an externally supplied sidecar