Skip to content

hip: decode router over the bf16 rows the checkpoint holds - #1614

Draft
gilby wants to merge 1 commit into
ashhart:mainfrom
gilby:strix/hip-router-bf16
Draft

gilby wants to merge 1 commit into
ashhart:mainfrom
gilby:strix/hip-router-bf16

Conversation

@gilby

@gilby gilby commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

What this changes

The HIP decode router reads the bf16 router rows the checkpoint already holds instead of the fp32 copy widened at load, keeping each lane's multiply-add order, so its logits are byte-identical; it is registered as a replacement of tf_router_decode_bf16 / tf_router_decode_f16. Part of #1603 (the router was the largest of the small per-round decode kernels; the hyper-connection and select/combine fusions measured smaller and are not in this pull request).

Receipt

  • Environment: TensorFold 7c068895 plus this change, tensorfold-native 1.1.8 built with zig build native-hip -Dhip-arch=gfx1151, Ubuntu archive HIP 7.1 (clang 21), AMD Ryzen AI MAX+ 395 with Radeon 8060S gfx1151 (128 GB unified memory). Checkpoint TensorFold/Qwen3.8-Flash-Next-MLX-4bit-MTP revision 2b170fa6, MTP 3 drafts, --parallel 1 --context 131072 --prompt-cache-gib 0, base and candidate on the same machine.
  • Exactness: token_sha256 of tools/bench_openai.py, tools/bench_concurrent.py and both tools/bench_cache.py prompts identical to the base build in two candidate runs; bench_concurrent --alone --serial every cell alone 3/3 and 12/12 equal, serial equal, 0 failed.
  • Decode speed, tools/bench_openai.py --tokens 256 --reps 3 --temperatures 0, median tok/s code / chat: base 50.03 / 43.93; this change 51.25 / 45.08 and 51.25 / 45.09 (two server starts), about +2.5%. Router kernel alone, 48 layers of 513 x 2560 rows at 4 rows: 49.8-50.2 us -> 16.1-16.5 us.
  • Prompt speed: the change only takes the decode-window router path (1 to 16 rows); tools/bench_cache.py --require-cold first-request token hashes are identical and cold timings unchanged within noise.
  • Tests run: zig build hip-kernel-contract on gfx1151: SUMMARY gfx1151 51 kernels: 51 pass gates 1-4, 0 fail, 0 known findings; 5 controls: 5 red as planted, 0 missed; 126 limit problems (base: 49 kernels, the same 126 limit problems, which are live-versus-recorded register counts for existing kernels under this machine's compiler); the router lines: shapes bit-equal to the replaced kernel at 8 shapes (1, 4 and 16 rows x 513 x 2560 and others), exact sums at k 2560, live limits 35/36 VGPRs and 0 LDS as recorded. zig build hip-kernel-test 104/104 pass, including a new test comparing the bf16-rows and fp32-rows routers byte for byte for both activation kinds at 1-16 rows with planted -0, inf and NaN inputs (fails against a deliberately reordered sum). zig build test -Dhip=true --summary all 466/466 steps, 0 failed. python3 tools/zig/lean_check.py 425 on base and change. git diff --check clean.
  • Overlap: it adds entries to the same HIP launcher and record lists as hip: load a shared expert wider than the routed experts beside the stack #1612 (shared expert) and the sparse prompt attention pull request; those are list additions that rebase trivially.
  • Not run: hip-kernel-contract-limits stops on this machine's compiler at tiles/dot2_tiles.hip for gfx1100 (branch size exceeds simm16, same on the base, see HIP: dot2_tiles.hip fails to compile for gfx1100 with Ubuntu clang 21.1.8 ("branch size exceeds simm16") #1521); the new records for gfx1030, gfx1100 and gfx1201 come from each chip's compiled code object metadata. Metal and CUDA (HIP-only change); other AMD chips at runtime.

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off.
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.
  • No precision traded for speed. If the bits change, the description says which sums change and why.
  • A new or changed GPU kernel is registered with the kernel contract (docs/kernel-contract.md); each backend's SUMMARY line is in the receipt.
  • Prompt processing is no slower than the last release.
  • The description says which platforms I ran, Metal M1 to M5 and CUDA, and which I could not.
  • New tests fail before the change, pass after it, and skip cleanly without their dependency.
  • Comments and docstrings are one line. No measurements or history in the source.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

The decode-window router read the router weights as fp32 rows widened at
load. tf_router_bf16 (and its fp16-activation form) reads the bf16 rows
the checkpoint already stores, prefetching four groups, with each lane's
multiply-add order unchanged, so its outputs equal the fp32-rows router's
bytes. Registered with the HIP kernel contract as that router's
replacement.

Refs ashhart#1603
@ashhart

ashhart commented Oct 30, 2026

Copy link
Copy Markdown
Owner

Thanks — this is well put together: matching the fp32 router's lane order and gating it bit-for-bit is exactly the right approach. To leave draft, could you attach the device receipts: the contract run for the two new router records and the decode kernel test on each arch the records list, plus SASS/byte-identity for the existing decode-image kernels and a Flash Next token-equality run with the new path on? A one-line note on how much extra memory the bf16 router copy adds per layer would help too. Heads-up: #1622 touches the same lines in hip_records.zig and contract_main.zig, so whichever lands second will need a small rebase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants