Skip to content

2026-05-15: Four Candles For The Cathedral

Four small lit candles at the base of a sketched cathedral arch, a single teal halo encircling them

The cathedral was fast. It was not done being fast.

John Carmack has a habit worth stealing: when a system already runs quick, he asks where the last cycles hide, not whether any are left. So after the fusion work took the coder to 50 tok/s and ported clean onto Yoda’s 35B, we put that same question to Qui-Gon, who never met a warm cycle he didn’t want to reclaim. Where do the cycles actually go now?

Not into matmul — matmul was already fused. They were leaking into KV cache reads, kernel-launch overhead, and the dead weight of a fallback path nobody used in production. Four answers came back. Four candles for the cathedral, lit one at a time, in the order they earned their light.

#1 — Drop the fallback weights after FusedXxxProj builds

Section titled “#1 — Drop the fallback weights after FusedXxxProj builds”

The fusion pattern kept the canonical q_proj, k_proj, v_proj (and gate_proj, up_proj) alive next to the concat’d fused weight. That fallback existed for the env-var-dynamic toggle: if someone disabled fusion mid-flight, the original 3-matmul path had to still work.

The dynamic toggle is theoretical hygiene. The memory is real. With the canonicals retained, every fused layer held about 3x the projection weight pool — on the 35B, roughly 6 GB of resident memory that inference never touched.

sanctum-rs 0adb32b (PR #20) makes the surgical move: after try_build succeeds, force-eval the concat’d weights to materialize them into their own buffers, then replace q_proj/k_proj/v_proj with 1x1 placeholder Linears. MLX refcount drops the originals immediately. The fallback path is dead in production; the env-var toggle survives for build-time-only A/B testing.

Bisects still need the canonicals alive for their parity comparison, so SANCTUM_MLX_FUSION_KEEP_FALLBACK=1 is a one-line escape hatch set at the top of main. Production unset; bisects set. All three regression bisects (qwen2 QKV, qwen2 gate+up, qwen3_5_moe QKV+MLP) stay byte-exact across 20 decode steps with populated cache.

The prompt cache pool’s LCP matching means a second request that shares a system-prompt prefix with the first gets that prefix for free. The first request still pays the prefill. What if it didn’t have to?

sanctum-rs 42de932 (PR #21) adds SANCTUM_MLX_PREWARM_FILE. At AppState construction the cathedral reads the file, tokenizes it, runs forward_last_logit to fill a fresh KV cache, and drops the resulting (tokens, cache) pair into slot 0 of the prompt cache pool. Future requests LCP-match the slot and skip the prefix prefill.

Validated live: a request whose system prompt matched the prewarm file logged cache_hit_ratio=0.73 on its first cold request — 45 of 62 prompt tokens already in cache at boot. Output correct; the remaining 17 tokens are the only prefill cost.

The catch is subtle. The prewarm tokens must be a strict token prefix of typical requests. Tokenize-then-truncate-mid-template breaks the LCP match, because the extra cached tokens become “ghost context” the model was never supposed to see. Concretely: include <|im_start|>system\n plus the system content, but not the closing <|im_end|> — the closing tag and what follows are request-specific. That caveat is the first thing in the source a future author who edits the prewarm file will read.

Memory said CompressedKVCache was somewhere in the tree. Was it actually on the hot path, or a good idea that never got wired in?

Audit complete: the --turboquant flag on the Yoda plist selects CacheKind::TurboQuant, which routes the full sampling stack through crate::turboquant::CompressedKVCache. Yoda is already running with its cache reads halved by 4-bit compression. The 14B coder runs an Fp16 cache (full precision); extending TurboQuant there would need a calibration adapter — a separate workstream.

The audit took 30 seconds and the result was “nothing to do here.” That is still a hack worth running, because the cost of not running it is rebuilding what was already shipped.

Cathedral memory had prompt-lookup decoding on the 14B coder, logging pld_max_lookup=5 pld_ngram_size=3. The Yoda plist set neither env var, so PLD was effectively off for the 35B — even though decode_with_pld_fp16 is generic over LoadedModel and would route through qwen3_5_moe just fine.

sanctum-rs 0adb32b (PR #20, same commit as #1) adds the two env vars to the Yoda plist. The log line AppState: prompt-lookup decoding ENABLED pld_max_lookup=5 pld_ngram_size=3 now fires on Yoda startup. The win shows on repetitive prose where n-gram lookups land; freeform creative output is unchanged.

SurfacePhase 1 fusionPLDCarmack #1 dropCarmack #2 prewarm
Coder :1338 (14B)QKV + gate+upyesyesyes
Yoda :1337 (35B MoE)QKV + gate+upyesyesn/a (TurboQuant path has no prompt cache pool)

The 14B coder ships at 35–50 tok/s on freeform completions, with cache_hit_ratio=0.73 on the first cold request when the matching system prompt is configured. The 35B Yoda ships at 43–55 tok/s on short freeform, PLD ENABLED in the log.

Memory-budget-aware routed-experts fusion is the thread left dangling. PR #19 explored fusing the 256 routed experts in the qwen3_5_moe MLP path; it produced byte-exact math via the moe bisect and a 6x perf regression on the live 35B. Root cause: concatenating the per-expert gate+up weights across 40 MoE layers added roughly 10 GB of duplicated weight memory while the originals were kept around for the fallback path.

Carmack #1 — drop the fallback — is exactly what unblocks it. The next session inherits a clean surface: build the routed-experts fused weight, force-eval it, then drop switch_mlp.gate_proj and switch_mlp.up_proj. The FusedSwitchGateUp struct and its try_build are already checked in as documented dead code with the why-not comment, waiting for someone to wire them back into forward.