VERSION: MLX / Apple Silicon conversions (working MTP + vision), plus a reconstructed bf16

#31
by philipjohnbasile - opened

Thank you for this tune β€” it's genuinely excellent, and the MTP quants were the
reason I went down this road.

Since the repo is GGUF-only, I reconstructed HF-format safetensors from your Q8_0 MTP
export and built MLX versions for Apple Silicon. The MTP head works β€” these run with real
speculative decoding under MTPLX, and they also load as plain mlx_lm
checkpoints:

Measured on an M5 Max 128 GB (numbers and methodology are on each card): 2.2–2.9x decode
speedup from MTP, 5/5 on a 5-problem AIME run and 8/8 on an executable coding eval for all
three quant levels, and wikitext-2 perplexity for the 6- and 8-bit builds that is
statistically indistinguishable from the bf16 reconstruction. The vision tower is included
too (grafted from mmproj-BF16.gguf, verified on real images).

Two honest caveats I've put on the cards:

  1. The bf16 repo is a dequantization of your Q8_0, not the original training checkpoint.
    If you ever publish the true BF16 safetensors, that would be strictly better and I'd
    happily rebuild everything from it.
  2. NEO-Imatrix doesn't carry over β€” it shapes your sub-8-bit GGUF quants, and the Q8_0
    source doesn't use it. So my 4-bit is plain affine, and your 4-bit NEO GGUFs may well beat
    it at the same size.

Everything is Apache-2.0 with credit to you, and every card links back here for your settings
guide and class notes. If you'd rather I take any of them down, rename them, or change how
they're attributed, just say so and I'll do it.

One thing that may be useful to others attempting this: llama.cpp's Qwen3.5/3.6 converter
applies three transforms that have to be inverted, and getting them wrong produces a model
that degenerates while still passing consistency checks β€” the weight + 1 RMSNorm baking,
the Gated-DeltaNet value-head reorder (grouped to tiled, including out_proj columns),
and ssm_a = -exp(A_log). All three are documented on the cards.

I also made the F32 version of the model, available here. It might work better for lower quants like NVFP4

https://huggingface.co/nightmedia/Qwen3.6-27B-Architect-Polaris2-Fable-B-F451-F32

Thank you β€” that's genuinely useful, and it let me verify the conversion against a source I hadn't used.

I compared the F32 repo tensor-by-tensor against my reconstruction (which came out of the Q8_0 MTP GGUF):

  • mtp.fc.weight β€” cosine 0.99997
  • model.language_model.norm.weight β€” cosine 1.00000
  • model.language_model.layers.0.mlp.gate_proj.weight β€” cosine 0.99998

Identical structure too: 1199 tensors, 15 mtp.*, 333 vision. The residual is exactly the Q8_0 step my copy passed through, so this independently confirms the reverse-conversion β€” the weight + 1 RMSNorm unshift, the Gated-DeltaNet value-head reorder (including out_proj columns), ssm_a = -exp(A_log), and the nextn.* β†’ mtp.* mapping all came out right.

I've updated all four model cards to credit the F32 repo and to point anyone building their own quants at it rather than my reconstruction, since it never went through a quantizer. Agreed it should serve lower quants like NVFP4 better.

One question if you know: does the NM in the GGUF's internal name (Qwen3.6 27B Architect Polaris2 Fable B F451 NM) mark a step after this merge, or is it just a naming tag? If there's a stage between the F32 merge and the GGUF, I'd want to say so on the cards.

Here are the formulas side by side: the only thing I changed was the precision.

The drift in metrics is as expected, and F32 provides more resolution to cook fine-tuned quants.

The mxfp4 metrics are identical between BF16 and F32.

models:
  - model: Qwen3.6-27B-Architect-Polaris2-Fable-B
    parameters:
      weight: 1.4
  - model: Qwen3.6-27B-Architect-Polaris-Fable-F451
    parameters:
      weight: 0.6
merge_method: nuslerp
dtype: bfloat16
name: Qwen3.6-27B-Architect-Polaris2-Fable-B-F451
mxfp8     0.711,0.879,0.910,0.790,0.514,0.823,0.763
qx86-hi   0.696,0.876,0.912,0.791,0.518,0.824,0.760
qx64-hi   0.702,0.873,0.909,0.794,0.514,0.822,0.750
mxfp4     0.701,0.873,0.909,0.786,0.488,0.813,0.759
nvfp4     0.701,0.866,0.907
Quant     Perplexity      Peak Memory   Tokens/sec
mxfp8     3.783 Β± 0.023   34.74 GB      203
qx86-hi   3.735 Β± 0.023   33.25 GB      183
qx64-hi   3.747 Β± 0.023   27.03 GB      194
mxfp4     3.854 Β± 0.024   21.30 GB      197

Qwen3.6-27B-Architect-Polaris2-Fable-B
mxfp8     0.706,0.875,0.911,0.788,0.516,0.821,0.769
Qwen3.6-27B-Architect-Polaris-Fable-F451
mxfp8     0.699,0.869,0.911,0.789,0.498,0.818,0.772



models:
  - model: Qwen3.6-27B-Architect-Polaris2-Fable-B
    parameters:
      weight: 1.4
  - model: Qwen3.6-27B-Architect-Polaris-Fable-F451
    parameters:
      weight: 0.6
merge_method: nuslerp
dtype: float32
name: Qwen3.6-27B-Architect-Polaris2-Fable-B-F451-F32
mxfp8     0.709,0.880,0.909
qx64-hi   0.706,0.873,0.908
nvfp4     0.704,0.868,0.908
mxfp4     0.701,0.873,0.909

Transparency follow-up after completing the post-publication behavioral audit:

Using the exact calibrated Heretic KeywordRate protocol, I measured:

  • official Qwen3.6-27B base: 99/100
  • DavidAU Q6_K: 81/100
  • my local MTPLX 6-bit conversion: 79/100

That is useful evidence that the conversion preserved the source checkpoint's behavior, but it does not support describing either artifact as broadly β€œuncensored.” I have corrected all four conversion cards and removed the uncensored/heretic/refusal-ablation tags from them.

The separate official-Qwen-based v2 artifact is here: https://huggingface.co/philipjohnbasile/Qwen3.6-27B-Refusal-Ablation-v2-MTPLX-6bit

Its card reports 47/100 on the same public Heretic protocol (versus 81/100 DavidAU and 99/100 official base) and 26/100 on a separate locked holdout. It is labeled β€œreduced-refusal,” not β€œuncensored,” and the card documents the intervention, paired evaluation, quality regressions, quantization delta, and consumed-evidence limits.

Keyword matching is still only a narrow behavioral proxy, not proof of semantic compliance or unrestricted behavior. I wanted the public thread to reflect the completed audit rather than leave the earlier preliminary impression unqualified.

Thank you philipjohnbasile for these, very much appreciated! ❀️

Thanks @TooCas β€” glad it's useful.

One more correction since that post, relevant if you're actually running these: the throughput numbers on my MLX cards were wrong and I've fixed the 6-bit one.

I had published AR 23.2 / MTP-D3 55.9 tok/s. On re-measurement I get AR 9.70 / D3 25.70 on the same M5 Max. Three independent measurement paths agree (mtplx tune with fans pinned, the OpenAI endpoint's own logged tok_s, and a sibling build on identical hardware). I ruled out thermal state, custom Metal kernels, runtime profile, and my own measurement harness as causes β€” none of them explain it.

What did reproduce: the speedup multiplier (2.65x measured vs 2.4x published) and draft acceptance (93.5 / 77.4 / 75.9% vs 93 / 81 / 71%). So the MTP claims hold up β€” depth 3 really is ~2.65x over autoregressive β€” but the absolute token rates were roughly 2.4x optimistic. Set expectations from the corrected figures.

I also added long-context numbers that weren't there before, since short-run tok/s tells you very little about real use:

ctx PP tok/s (AR→D2) TG tok/s (AR→D2) TTFT (AR→D2)
4k 280 β†’ 285 7.7 β†’ 6.9 15s β†’ 14s
32k 370 β†’ 427 6.6 β†’ 13.7 89s β†’ 77s
64k 312 β†’ 514 6.7 β†’ 12.3 210s β†’ 127s
131k 198 β†’ 281 5.9 β†’ 6.9 663s β†’ 466s

Depth 2 is the practical sweet spot at long context, not depth 3. And time-to-first-token at 131k is 8–11 minutes β€” if you work with long prompts, prefill is your entire experience and generation speed barely matters.

The 4-bit and 8-bit cards still carry unverified throughput figures; I'm measuring those now and will correct them the same way.

Follow-up: the 4-bit and 8-bit cards are now corrected too, so all three MLX builds carry measured throughput.

build previously published measured (mtplx tune, fans pinned, M5 Max)
4-bit AR 30.6 β†’ D3 68.5 AR 13.17 β†’ D3 29.34
6-bit AR 23.2 β†’ D3 55.9 AR 9.70 β†’ D3 25.70
8-bit AR 17.8 β†’ D3 50.7 AR 9.39 β†’ D3 25.42

All three were inflated by roughly 2x, and in all three the speedup multiplier reproduced: 2.2 claimed vs 2.23 measured (4-bit), 2.4 vs 2.65 (6-bit), 2.8 vs 2.71 (8-bit). Absolute rates uniformly wrong while ratios stay right is a measurement-condition signature, not three independent slips β€” but I could not identify what that condition was. Thermal state, custom Metal kernels, runtime profile, context-window sizing and my own harness were all tested and none explain it. So the cards state the numbers didn't reproduce rather than claiming a cause.

Per-position draft acceptance, measured:

build D1 D2 D3
4-bit 94.0% 89.5 / 79.6% 92.3 / 80.3 / 71.8%
6-bit 93.5% 90.1 / 77.4% 91.1 / 82.2 / 75.9%
8-bit 95.1% 90.1 / 79.6% 96.2 / 89.5 / 82.7%

One correction runs in the favourable direction, which is worth saying plainly: the 8-bit card understated its own acceptance. It claimed 85/65/47; it actually measures 95.1/89.5/82.7. That build's speculative decoding is better than I had advertised, and at depth 3 it holds acceptance far better than the other two.

Practical summary if you're choosing between them: 4-bit is fastest in absolute terms (29.3 tok/s at D3), 8-bit has the best draft acceptance and therefore degrades least at depth, and 6-bit sits between. All three are ~2x slower than the original cards suggested β€” set expectations from the corrected figures, not from what those cards said before today.

DavidAU changed discussion title from MLX / Apple Silicon conversions (working MTP + vision), plus a reconstructed bf16 to VERSION: MLX / Apple Silicon conversions (working MTP + vision), plus a reconstructed bf16

Three corrections to numbers and methodology I posted here, found by adversarially reviewing my own harnesses rather than waiting to be caught.

1. Keyword marker count: I said 37, it is 33

I described the Heretic KeywordRate protocol as using "the 37 substring markers from scorers/keyword_rate.py". The list has 33 markers. I diffed my implementation against upstream p-e-w/heretic β€” it is byte-identical, 33 markers, nothing missing or added β€” so the refusal rates are unaffected. Only my description of the protocol was wrong.

2. Prefill throughput was overstated ~3.3%

The long-context table I posted derived PP as context / TTFT, using the labelled context length. My prompt builder generates target x 0.85 words, and the tokenizer turns those into ~3.3% fewer tokens than the label claimed. Corrected:

labelled actual tokens PP as published PP corrected
4k 3,957 279.7 270.3
32k 31,674 370.2 357.8
64k 63,349 311.9 301.4
131k 126,702 197.6 191.0

TTFT and TG are unaffected β€” those are wall-clock. But the context labels were also ~3.3% optimistic: the "131k" run was actually 126.7k tokens. An independent review put the 4k cell at 3.513% too high specifically.

3. My "calibrated harness" claim was weaker than I presented it

I justified the refusal numbers by noting my harness reproduces the published 99/100 for base Qwen3.6-27B. That is true, and it is much weaker evidence than I implied: 99/100 is saturated. Nearly every prompt matches, so a saturated calibration point cannot detect a dropped marker, a stripping bug, or a scoring error β€” anything would still score ~99. Had I actually lost four markers, that check would have returned 99/100 and I would have called it validated.

A real calibration needs a reference point in the middle of the range, where an error moves the number. I don't have one, so treat the refusal figures as internally consistent and comparable to each other rather than as externally validated.

What does not change

The relative findings stand: DavidAU's released Q6_K at 81/100 against a card claiming 4/100, ours at 79/100, base Qwen at 99/100 β€” all on identical protocol and prompts. ARC and the MTP acceptance rates are unaffected by any of the above.

One further limitation worth stating: my refusal harness never persists response text, only the match boolean and the triggering keyword. That was deliberate β€” I didn't want to store the outputs β€” and it means those scores cannot be independently re-audited without re-running inference. That is a real weakness in the evidence, not a strength.

so basically, use this one if you have a mac?

Retraction: the throughput "correction" I posted here was wrong, and I have withdrawn it.

On 27–28 July I replaced the throughput figures on the 4-bit, 6-bit and 8-bit cards with numbers roughly 2.3x lower, and said the originals "could not be reproduced under any configuration." I was confident, specific, and wrong.

What actually happened: every one of those measurements was taken while this machine was under heavy load β€” an iOS Simulator and an emulator were live, loadavg above 5. That depressed throughput by ~2.3x. I explicitly considered and dismissed thermal/ambient load as a cause without testing it, then described my result as agreeing across "three independent measurement paths." They were not independent. They were one confounded machine measured three ways.

Re-running the identical mtplx tune command on a quiet machine:

I published as "corrected" quiet-machine re-measure independent probe original card
AR 9.70 22.30 21.78 23.2
D1 18.22 38.60 39.33 39.4
D2 21.75 47.07 49.00 48.8
D3 25.70 56.14 56.99 55.9

Same flags, fans pinned in both runs. The only difference was machine load. The original figures were right.

The 6-bit card now carries the quiet-machine re-measurement. The 4-bit and 8-bit cards have their original figures restored, and are explicitly flagged as not re-measured β€” those weights are no longer local, so they are pre-correction values pending a fresh run, not freshly verified numbers.

What still stands, because it was arithmetic rather than measurement: the ~3.3% prefill (PP) overstatement, since I divided by labelled context length rather than actual token count. And the marker-count fix, 37 β†’ 33.

I am sorry for the churn. Publishing a wrong correction is worse than the original error, because it arrives wearing the authority of having been checked. The lesson I should have applied and did not: a measurement taken under uncontrolled load is not evidence against a measurement taken without it, and "three methods agree" means nothing when all three share the same confound.

Sign up or log in to comment