feat(math-cuda): RPX256 leaf and tree kernels, launch module and device dispatch (lane K, phase 2) - #962
Merged
Merged
Conversation
…ce dispatch Lane K phase 2 of the per-table GPU redo. kernels/rpx.cu gains the device surface, kernel for kernel the twin of blake3.cu:338-620: seven leaf kernels (column-major base per row and per row pair, ext3 per row, comp-poly ext3 row pairs, FRI ext3 pairs, row-major row pairs plain and column-ranged), the Merkle level and tail compressors, and a permutation probe. A node is four canonical felts as big-endian bytes (digest_to_commitment), so device trees are the host's bytes; a leaf absorbs exactly the felt sequence the host leaf hashes (leaves_bit_reversed_grouped), which is the sequence felts_from_bytes rebuilds, so hash_bytes == hash_data holds on device by construction; a parent decodes its children as commitment_to_digest and runs compress. src/rpx.rs is the launch module, launcher for launcher the twin of blake3.rs at block dim 128 (keccak's register-file setting, to be measured with -Xptxas -v). build.rs compiles rpx.cu, lib.rs exports the module, device.rs loads the cubin and its ten functions, and the nine loud DeviceHash::Rpx256 arms in lde.rs, fri.rs and stark's gpu_lde.rs become launches; Rpo256 and Poseidon keep their loud arms. The host-KAT gains a seventh layer that replays every leaf kernel, both compressors and the probe thread by thread through the shim against the CPU leaf spec and the host parent, so the read patterns and the node encoding are pinned with no GPU. prover/tests/rpx_device_parity.rs (cuda-gated) is the device gate: the bare permutation against Rpx256, the fused base and ext3 roots against RpxStarkHash, the comp-poly tree root, the FRI-layer tree node for node against AlgebraicPairBackend, column-ranged leaves against hash_data, and a tamper control.
…-inlined body, rolled loops The first cubin build of rpx.cu ran 41 minutes and emitted 56 MB of PTX (1.33 M lines, 10 entries, zero functions): every device function was force-inlined and every loop fully unrolled, so one permutation was ~49k straight-line lines — the 72-step inverse S-box chain unrolled over twelve lanes three times — and each leaf kernel carried one copy per permute call site, seven in the comp-poly kernel. permute is now a real device function (RPX_NOINLINE, __noinline__ under nvcc; the host shim has no such attribute and the host KAT's inlining is irrelevant), the rounds take a runtime index inside a rolled round loop, the lane loops, the squaring chains, the extension triples and the constant adds are rolled with #pragma unroll 1, and the MDS output-lane loop is rolled over a doubled circulant row so the entry needs no modulo. The MDS inner loop stays unrolled (24 MACs). Nothing arithmetic moves: the host KAT passes unchanged, layer 6 counts the same 2736/144/300 ops, and the canonicalisation witness still pins the loop. Code-size proxy (x86-64 -O2 through the shim, instructions): the whole file drops from 5393 to 1581, with the ten kernels unchanged at 29–137 each (they only call now) and permute's inlined body a few hundred; PTX is expected in the low thousands of lines for the whole file.
…builders #961 gave both BLAKE3 comp-poly tree builders the test-faults-gated sticky hook the keccak twins carry, because the fault suite's comp-tree cliff test arms one counter and must reach it under whichever hash the build pins. The two RPX builders lacked it, so the suite would have gone silent again the day the pinned hash is RPX. Same four lines, first statement of build_comp_poly_tree_from_slabs_dev and build_comp_poly_tree_from_evals_ext3_keep. rpx.cu is untouched.
…y accepts it make lint's cuda pass compiles the cuda-gated rpx_device_parity test and denies unusual_byte_groupings: probe_states(0x5250_58, 256) had digit groups of four and two. 0x0052_5058 is the same seed in groups of four. No other hex literal in the lane's Rust files mixes group sizes.
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lane K, phase 2 of the per-table GPU redo: the RPX256 (XHash12) device surface — leaf and tree kernels, the launch module, the cubin wiring, and the nine
DeviceHash::Rpx256dispatch arms turned from loud aborts into launches. Follows #952 (permutation + host-KAT) and #959 (canonicalisation pin); RPO256 and Poseidon keep their loud arms. Commits: b78d0d5 (the port), 1b22e66 (the code-shape fix the first cubin build forced — see below), 856091b (lane M'stest-faultssticky comp-tree hook mirrored into the two RPX builders, per review), and f8d4147 (the literal-grouping lint fix:probe_states(0x0052_5058, 256); laptopmake lintall six passes exit 0 at that head).What changes
crypto/math-cuda/kernels/rpx.cu— tenextern "C"kernels, kernel for kernel the twin ofblake3.cu:338-620:rpx_leaves_base_batched,rpx_leaves_base_row_pair_batched,rpx_leaves_ext3_batched,rpx_comp_poly_leaves_ext3,rpx_fri_leaves_ext3,rpx_leaves_base_row_major_row_pair,…_range,rpx_merkle_level,rpx_merkle_tail,rpx_permute_probe. A node is four canonical felts as big-endian bytes (digest_to_commitment), so device trees are the host's bytes; a parent decodes its children ascommitment_to_digestand runscompress.crypto/math-cuda/src/rpx.rs— the launch module, launcher for launcher the twin ofblake3.rs(RPX_BLOCK_DIM = 128).build.rscompilesrpx.cu;lib.rsexportsrpxand re-documentsDeviceHash::Rpx256;device.rsloadsrpx.cubinand its ten functions.lde.rs(inner tree levels; row-major row-pair leaves, plain and column-ranged; column-major base leaves per row and per pair; comp-poly ext3 leaves),fri.rs(FRI ext3 leaves; FRI inner levels),stark/src/gpu_lde.rs(comp-poly tree from ext3 evals; from resident slabs — insidematch device_hash_of::<B>(), after lane M's admission checks).tests/host_kat/rpx_host_kat.cpp— layer 7: every leaf kernel, both compressors and the probe replayed thread by thread through the shim against the CPU leaf spec and the host parent (read patterns + node encoding, no GPU).prover/tests/rpx_device_parity.rs(cuda-gated) — the device gate; lives in the prover crate becauseRpxStarkHashand the algebraic backends do.The
hash_bytes == hash_dataleaf contractA device leaf absorbs exactly the felt sequence the host leaf hashes — bit-reversed rows, each column by column, an ext3 element as its three components (
leaves_bit_reversed_grouped) — which is the sequencefelts_from_bytesrebuilds from the leaf bytes, so the contract holds on device by construction. Raw[0, 2^64)storage is absorbed as is: the permutation is representation-independent and the host canonicalises before serialising; same field value, same digest. The felt count is known before the first absorb (the overwrite duplex's padding flag, A1).Code shape — why
permuteis__noinline__and every loop is rolledThe first cubin build (f546de7, box B) had every device function force-inlined and every loop fully unrolled. One permutation compiled to ~49k straight-line PTX lines (the 72-step inverse S-box chain over twelve lanes, three rounds) and every kernel carried one copy per
permutecall site — seven in the comp-poly kernel. The build was aborted after 41 minutes. 1b22e66 makespermutea real__noinline__device function (empty under the host shim), gives the rounds a runtime index in a rolled round loop, rolls the lane, squaring, triple and constant loops (#pragma unroll 1), and rolls the MDS output-lane loop over a doubled circulant row (no modulo); the 24-MAC inner loop stays unrolled. Nothing arithmetic moved: the host-KAT passes unchanged with the same operation counts and the witness pin intact.rpx.ptx.funcbodiespermute, 1,426 lines)rpx_comp_poly_leaves_ext3rpx_leaves_ext3_batchedrpx_leaves_base_row_pair_batchedrpx_leaves_base_row_major_row_pair/…_rangerpx_leaves_base_batchedrpx_fri_leaves_ext3rpx_permute_probe/rpx_merkle_level/rpx_merkle_tailld.local/st.localnvcc -ptx0.99 s; fullcargo build -p math-cuda --release27.84 srpx.cubinptxas -vat sm_120, every entry 0 bytes spill stores / 0 bytes spill loads: probe 40 regs · merkle_level 40 · merkle_tail 53 (1 barrier) · fri 40 · base_batched 56 · comp_poly 62 · row_pair_batched 64 · ext3_batched 64 · row_major_row_pair 64 ·…_range64. The price of the called shape is a 192–208 B per-thread stack frame: the twelve-lane state crosses thepermutecall by pointer and is indexed dynamically inside it (that is theld.local/st.localtraffic — by design, not spills), plus loop overhead of order 10% of the permutation's instructions. The unroll factors ofsquare_nand the lane loops are the tuning knobs if profiling says so.Gate
Box A @ d3f235d (
rpx.cu,src/rpx.rs, the host-KAT files and the parity test are byte-identical at 1b22e66):Laptop @ 1b22e66, stub cubins:
cargo fmt --check,cargo check -p math-cuda --tests,cargo check -p stark --features cuda,cargo check -p lambda-vm-prover --features cuda --test rpx_device_parity— exit 0.make test-rpx-host-kat:Box A @ d3f235d, the remaining two steps (relayed verbatim):
make test-rpx-host-kat→ KAT_EXIT=0,ALL HOST KAT CHECKS PASS(19/19 Miden · 11/11 permutation · 7/7 leaf · 2/2 parent · canonicalisation pin).make lint→ LINT_EXIT=2:error: digits of hex, binary or octal literal not in groups of equal sizeatprover/tests/rpx_device_parity.rs:106(probe_states(0x5250_58, 256), the cuda-arm clippy denyunusual_byte_groupings, invisible tocargo check) — fixed in the follow-up commit below.Merge gate @ 1b22e66 on box A (relayed verbatim; the four RPX paths byte-identical to d3f235d):
Final gate (
make test-cuda-fallback+ parity +make lint) at f8d4147, appended here when relayed.Open items
square_n/ the lane loops get a small unroll factor back.