releases/v0.9.0/design-notes.md
Implementation narrative removed from the public report; source material for the Floor Attention writeup. Numbers here may be superseded by report.md.
The optimization pass exposed a latent bug shipped with the aarch64 repacking kernels: the q4k/q5k tiled (m >= 4) matmul kernels read the interleaved q8k bsums with a row-major index while the quantizer stores them quarter-major, corrupting the dmin bias term. Any prefill whose token count is a multiple of 4 (i.e., every real chunked prefill) produced garbage activations; short chat prompts happened to take the non-multiple-of-4 generic path and benchmarks used random tokens, so nothing caught it.
Fixed in candle commit 25b498cd with a repack-vs-reference regression test across m in {1, 4, 8, 23, 512} and all repacked quant types. Verified end to end with long-context recall prompts on qwen3-4b and gemma4-e4b.
All measured on qwen3-4b q4k unless noted; each change validated by unit tests plus long-context generation checks.
execute_chunked, atomic cursor) replacing static per-thread slices in all repacked matmul kernels. Worker spin fell from 32% to 22% of cycles; LFM2.5 decode went from 159 to 456 t/s (2.9x) - tiny models were almost pure imbalance loss.single_q.rs) rewritten: kv-axis splitting with online-softmax partial merge, GQA grouping (stream K/V once per kv head for the 4 to 8 q rows that share it), adaptive unit granularity. Deep decode flipped from far behind to ahead (d8192: 8.8 -> 16.7 t/s vs llama.cpp 15.6).QTensor::gemv_fused_shared_lhs + mistralrs wiring), cutting ~72 barrier crossings per token.fused_glu CPU moved from rayon to the barrier pool. This was the single largest win: rayon threads were fighting the barrier workers spinning between matmuls on the same pinned cores. Gemma decode +46%, qwen decode +14%.full.rs): q-blocked K/V streaming (8 query rows share each K/V pass), barrier pool instead of rayon, and binary-searched live kv ranges per row.The first pass won the measured grid; this pass targets the asymptotes (how throughput degrades with context) and the MoE gap.
Raw data: raw/results_x86.jsonl (normalized, includes both fa configs per point) and
raw/x86_sweep.log (bench stdout).
The aarch64 kernels above do not exist on x86, so a c7i.8xlarge (Xeon Platinum 8488C, 16 cores, AVX512/VNNI/AMX) was rented to port them. llama.cpp at the same pinned commit (2d97363), native build with its AMX path active. Baseline before the port: 0.38-0.79x across the board.
End-of-day board (qwen3-4b, mistral.rs / llama.cpp, ratio):
| q4k | 128 | 512 | 2048 | 8192 | 16384 |
|---|---|---|---|---|---|
| prefill | 0.42x | 0.69x | 0.79x | 0.86x | - |
| decode | 1.06x | 1.07x | 1.12x | 1.53x | 1.81x |
| q8_0 | 128 | 512 | 2048 | 8192 |
|---|---|---|---|---|
| prefill | 0.79x | 0.66x | 0.72x | 0.81x |
| decode | 0.84x | 0.84x | 0.92x | 1.33x |
llama.cpp's flash-attention CPU kernel inverts at depth on x86 (fa=1 beats fa=0 through ~2k, then loses to it: 6.3 vs 8.8 t/s at 16384), so the decode ratios above take llama.cpp's best configuration at every point, mixing fa=1 shallow and fa=0 deep.
Decode wins q4k at every depth and both quants at agent depths, with the same widening-with-depth shape as the aarch64 curves (15.9 vs 8.8 t/s at 16384 depth); recall verified. Prefill trails llama.cpp's mature AMX path (a chip feature most of the x86 fleet lacks; a non-AMX comparison point is a follow-up).
What was built (candle + mistralrs, all runtime-feature-detected):
The consumer tier landed: AVX-VNNI (vpdpbusd ymm) and pure AVX2 (maddubs) kernel variants plus 256-bit attention micro-ops and F16C f16 KV, so non-AVX512 x86 (Ryzen, pre-Ice-Lake Xeon, laptops) runs real kernels instead of scalar fallbacks - forced-AVX2 on Sapphire Rapids reaches ~75% of the AVX512 path and passes recall. A direct aarch64 port of the register-tiled P.V was measured and REJECTED (-9 to -13% at depth: NEON's register file forces per-row V re-streaming); the ~2x aarch64 attention-bandwidth headroom (50 vs 130 GB/s) needs an ARM-native design.
Follow-ups: AMX epilogue amortization, a non-AMX comparison point, an ARM-native attention bandwidth design, and the q8_0 shallow-decode cell (llama.cpp's q8_0 gemv sustains a few percent more effective bandwidth per core; block-pairing landed, the rest needs deeper streaming work).
After the x86 register-tiled P.V trick landed, the aarch64 port took two attempts: a direct copy regressed 9-13% at depth, and profiling showed the culprit was a per-position p == 0 skip branch (never taken in decode, mispredicted every position), not the V re-streaming the smaller NEON register file forces. The branch-free version (accumulator pinned in 64-f32 register chunks, masked positions zeroed before the sweep) lands d8192 22.8 -> 23.3 and d16384 15.6 -> 16.6 t/s, bringing the ARM deep-decode ratio to 1.79x - within a hair of the x86 1.81x. In-engine decode attention now runs at ~68 GB/s effective on GB10 against a ~130 GB/s machine ceiling; the remaining gap (scoring-phase horizontal reductions, second V pass) is scoped for the next release.