perf(bonsai): packed rows read the decode shadow's FFN on the int8 tensor cores, one weight read for every row (1.33x cb-decode @c8 on Ternary-Bonsai-2) - #1164
Conversation
…nsor cores, one weight read for every row (1.33x cb-decode @c8 on Ternary-Bonsai-2) A packed step of up to 8 rows read the decode shadow's ternary FFN through dp4a GEMMs whose work grows with every row: the whole leg (rotations, gate, up, SwiGLU, down) took 61 us per layer at 2 rows, 75 at 4 and 109 at 8. From two rows it now runs on the int8 tensor cores: hn rotated and quantized to int8 once per row (one scale per 128, the weight block's granularity, so a block's dot product is exact), gate and up in one mma.m16n8k32 (u8 x s8) launch that decodes each weight block once for every row, SwiGLU folded into the FFN-width rotation, then down with k split 4 ways (5120 rows are only 40 CTAs). 50-52 us per layer at 2-8 rows. Every row is summed in one fixed order (four-block steps, then steps, then splits, with _rn intrinsics, the split a function of the shape alone), so a row's result does not depend on how many rows share the step. One row keeps the dp4a path, bit-identical to single-row decode. Also: xn's rotation buffer is allocated only when a ternary projection reads it; with the default shadow (FFN only), decode rotated and quantized xn every layer for nothing. SPARKINFER_BONSAI_CB_SHADOW_TC=0 keeps the dp4a GEMMs.
cca0275 to
fa6ec88
Compare
sparkinfer bonsai auto-eval —
|
| metric | value |
|---|---|
| label | eval-bonsai:XL |
| model | Ternary-Bonsai-2-27B PTQ1_0 GGUF, default (folded) loader |
| scored at | decode + prefill @ 128/512/4k/16k/32k · concurrent decode @ c2/c4/c8/c16/c32 — 15 axes, each also a regression floor; the label is the best |
| tier from | bonsai-cb-decode@c8 (+33.4%) |
| accuracy vs main (teacher-forced) | ✅ top-1 0.9770 (bar ≥0.93) · KL 0.00652 (bar ≤0.03) · PPL ×0.9995 of main (bar ≤1.02) over 1177 tokens |
| prefill path vs main | ✅ batched prefill within main's spread at prefix 128/1024 |
| bonsai_regression.py | ✅ tensors · score · generate · serve |
| qwen3.6 guard | ✅ no regression (decode + prefill @ 32k) |
| modelopt qwen3.8 guard | ✅ no regression (decode + prefill @ 32k) |
| unsloth qwen3.8 guard | ✅ no regression (decode + prefill @ 32k) |
| muse glimmer guard | ✅ no regression (decode + prefill @ 32k) |
| PPL PR / main | 3.6775 / 3.6794 |
| Polaris receipt | collected, not signed (no key configured) |
| commit | fa6ec8891, measured merged onto main dc723a4 (this round's baseline) |
| ctx | phase | main | PR | delta |
|---|---|---|---|---|
| 128 | decode | 132.73 | 134.56 | +1.4% |
| 128 | prefill | 5164.05 | 5167.66 | +0.1% |
| 512 | decode | 131.94 | 133.69 | +1.3% |
| 512 | prefill | 7047.05 | 7051.93 | +0.1% |
| 4k | decode | 129.27 | 130.88 | +1.2% |
| 4k | prefill | 9170.47 | 9169.62 | -0.0% |
| 16k | decode | 123.61 | 124.95 | +1.1% |
| 16k | prefill | 8677.13 | 8671.74 | -0.1% |
| 32k | decode | 115.07 | 116.30 | +1.1% |
| 32k | prefill | 8020.88 | 8012.82 | -0.1% |
Concurrent decode — aggregate tok/s with N requests in flight, median of 3 runs
| concurrency | main | PR | delta |
|---|---|---|---|
| c2 | 208.40 | 226.10 | +8.5% |
| c4 | 363.60 | 427.60 | +17.6% |
| c8 | 563.20 | 751.20 | +33.4% |
| c16 | 1148.50 | 1148.00 | -0.0% |
| c32 | 1533.80 | 1527.00 | -0.4% |
Prefill path — batched prefill against the token loop, 64 teacher-forced positions, mean of 3 runs per side
| prefix | main top-1 | PR top-1 | top-1 bar | main KL | PR KL | KL bar |
|---|---|---|---|---|---|---|
| 128 | 0.990 | 0.990 | ≥0.890 | 0.0043 | 0.0042 | ≤0.0543 |
| 1024 | 0.964 | 0.969 | ≥0.864 | 0.0055 | 0.0049 | ≤0.0555 |
ok
Measured on the pinned RTX 5090 against a same-box origin/main from the same round. Any axis regressing below 98% of main is a REJECT; otherwise the label is the best measured delta. none only means no Ternary-Bonsai-2-27B speedup was measured, which is expected for a change aimed at another model. The round's best passing speedup is auto-merged as bonsai-merge-first (only at the exact commit scored); a separate comment says so. It does not close PRs.
|
Auto-merged as the round's |
Summary
A packed step of 2 to 8 rows read the decode shadow's ternary FFN through dp4a GEMMs whose work grows with every row. The whole leg (rotations, gate, up, SwiGLU, down) took 61 us per layer at 2 rows, 75 at 4 and 109 at 8. Past 8 rows the same legs already run on the int8 tensor-core rows kernel; this sends 2 to 8 rows there too:
hnis rotated into the weights' basis and quantized to int8 once per row, one scale per 128 values, the weight block's granularity.Per layer, the whole FFN leg on the RTX 5090 (harness, every rotation and SwiGLU included on both sides):
SPARKINFER_BONSAI_CB_SHADOW_TC=0keeps the dp4a GEMMs.Proof of speedup
sm_120)Target model(s)
Decode tok/s (end-to-end, from
bench/scripts/bench.sh— fill if this PR targets decode):Prefill pp tok/s (fill if this PR targets prefill; use
--ctx 4096,32768,65536, or131072and copy theprefill ppline — report your best context. Keep the row labels as-is):Best prefill context is 512. The numbers come from the bot's invocations on one RTX 5090, main
dc723a4against this branch, with runs interleaved main, PR, PR, main. A sample was re-run if another process touched the GPU during it.qwen3_gguf_bench <gguf> 128 sweep(real prompt ids, REPS=3):qwen3_gguf_cb_bench <gguf> c 256 256 512, aggregate tok/s, 0 request errors:Accuracy.
qwen3_gguf_scoreover the bot'seval_corpus.txt, this PR against main (mean of 4 cross pairs): top-1 0.984, KL 0.0051, PPL 3.681 vs 3.672 (x1.003). Main against itself on the same box reads top-1 0.985 / KL 0.0047.qwen3_gguf_prefill_check(batched prefill against the token loop, 64 continuation positions, mean of 2 runs, top-1 / KL):eval/bonsai_regression.pyserve check (a row that outlives its batch against the same row asked alone): matches a baseline on both the folded and the native server (366/366 characters).