Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/QWEN36_METAL_RESPONSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Measured Terms: A Reply on Qwen3.6 Metal

> A response to the Unsloth-vs-Strata comparison in **urmanac/nocknock** (`docs/kb/how-to-strata-vs-unsloth-qwen36-metal-benchmarks.md`), from the people who maintain the engine it tested. Receipts and the claim-by-claim audit: [`docs/QWEN36_METAL_REVIEW.md`](QWEN36_METAL_REVIEW.md). 2026-10-19.

The comparison article did the community a service: it put two engines on the same 24 GB M2 and published the numbers, including the unflattering ones. We accept the numbers that were measured — the ~263.6 tok/s prefill, the sustained decode, the screenshots' internal consistency all check out. This reply answers the three questions the comparison raises, on measured terms, and corrects the record where the article outran its evidence.

## What makes their prefill faster

Dispatch structure, not mystery. Their engine (llama.cpp's Metal backend, in the pinned prebuilt `b11724-mix-c208e5f` — no pinned commit touches Metal compute) evaluates a prompt in whole-batch GEMM kernels. Strata's chunk path still dispatches attention per row — a deliberate choice we made to keep chunked prefill bit-identical to decode, which is the contract this engine ships by default. Our own measurements say the same thing from our side: at depth, chunked and token-by-token read a 4,098-token prompt in 243 vs 245 seconds (`docs/QWEN36_METAL.md` §6), because the binding term is the KV re-read, not the batching. The gaps decompose: batch structure (their win today), fixed per-dispatch overheads, and KV traffic at depth. Two of the three are exactly what our Phase 2 kernels attack.

One number from their side that we find more interesting than any of ours: pinned fork PR #264 sped up MoE *prompt processing* on CUDA by 16.4–17.8% for Qwen3.6 while leaving decode tied. Kernel structure is where prefill lives — on their flagship backend, on their model, with their own engineers. On Metal their fork pins nothing; the M2 comparison ran against upstream ggml-metal. That is the bar we are measuring ourselves against, and it is a fair one.

## What we have that they don't

- **A deep-decode lever, built and waiting on one measurement.** The model's gated-attention layers read each KV cell 8 times (16 query heads share 2 KV heads — read from the model card's published geometry). Our GQA fold reads each cell once, and `--kv-f16` halves what's left (§9: 0.34 → 0.17 GB at 8K, measured on the M2). The bandwidth arithmetic at 11.3k depth puts the ceiling at ~76–89 tok/s with both levers; their measured 21.2 tok/s there sits at ~26–30% of the *un*-folded ceiling. If our kernels reach the same ~25–35% efficiency class their engine demonstrates at both depths, the projection lands at ~19–31 tok/s — parity with the best deep-decode number in either camp's record. The assumption is labeled, and the A/B that tests it is the first thing we run.
- **A memory contract instead of a guess.** Their Auto context-sizing budgeted 8,192 tokens on a 24 GB machine that had room for 32k — the maintainer proved it by loading 32.8k manually with one warning and no failure. Our engine refuses, at startup, to allocate what the machine does not have, with an override for people who know better. Predictability is the feature; the 8.2k truncation in the article was a guess being conservative, not a ceiling.
- **Reproducibility by default.** The fork's own opt-in exact-concurrency mode (#194) exists because people want byte-identical output regardless of neighbouring requests — at a measured cost where they state one (~9–15% on solo decode in their tables). We ship that contract as the default, with a 38-stage bit-parity harness behind it. For sovereign computing, "the same prompt gives the same answer" is not a nice-to-have.
- **An honest negative result.** Our in-tree MTP measured net-negative on the M2 (5.0–11.1 tok/s vs 17.5–21.4 base; the verify window dominates at ~15–25% acceptance — `docs/QWEN36_METAL.md` §7). We publish that. The card's "~1.5–2× with MTP" is a different mechanism configuration (`-np 1`, draft-n-max sweep) our verdict did not measure, and the calibration experiment to test it is named in the review. The expectation on our side: a calibrated win in that range is plausible once the window cost is tuned — that is a testable prior, not a claim.

## Is the combined implementation possible?

Yes, and half of it is already proven. The 32k experiment established the memory side on their engine; ours has the same shape: ~11.4 GB weights + 0.67 GB KV at 32k (f16, measured) + ~100–330 MB scratch depending on chunk size (§10) ≈ **12.5–13 GB working set — about 10 GB left for Chrome, the IDE, and macOS**. The prefill gap is kernel structure (T9: batched flash attention; T10: batched MoE GEMM, with a proven donor kernel and their own CUDA numbers saying what structure is worth). The deep-context gap is T6's measurement. Both engines want the same destination from opposite sides: they have prefill speed and guessy memory budgets; we have explicit budgets, bit-parity, and the deep-context architecture, with the prefill homework written down and gated on measurement.

## The experiment we'd like to see next

Long-context inference on this class of machine is thermally bounded — the benchmark session peaked at 204°F with 5.9 GB of swap while measuring 14.4 tok/s, then recovered to 21.2 tok/s at *greater* depth once contention eased. Fewer bytes moved per token is also less energy per token. The hypothesis, testable on hardware both projects own: **tokens per joule at 11.3k depth, folded vs unfolded KV path, cool machine, Chrome closed, `powermetrics` or the menu-bar thermometer as the meter.** If the ~16× KV-traffic cut shows up as sustained clocks at depth as well as tok/s, "runs cool all afternoon with Chrome open" becomes a measured sovereign-computing metric — and it is the one metric neither engine has published yet.

## Where Unsloth Desktop is simply the right answer

When you want speed today, don't need reproducibility guarantees, and are happy to let a tool guess your memory budget for you — use it. It is genuinely good at what it does; the numbers in the comparison say so, and we cite them in our own roadmap as the bar. We are building for the case where the guess is the problem: explicit budgets, verified output, measured claims, and hardware the user owns end to end.

This exchange has since converged once, end to end: the comparison article applied the full audit in two merged revisions (eleven instructions, then the five polish nits — commits `c179a7e`, `039269b`), and the roadmap in this repo is rewritten measurement-first. What remains on both sides is measurements, not words: the run blocks in the review's §7, the two captures in its §8, and the scoreboard in its §11 — the table that holds every comparative number with its status, ready to be re-checked when the runs land.
Loading
Loading