NVIDIA WGSL kernel: port the Apple arithmetic wins without the code-size changes #7

Closed
opened 2026-09-03 09:09:39 +00:00 by grenade · 1 comment
Owner

Part of #1. Fallback-engine work: matters while engine-cuda does not exist, and afterwards for any non-NVIDIA discrete card. Smaller expected gain than the other workstreams, 5 to 15%, and the only one where origin has already done most of the thinking.

History

  • Origin PR 87 (ea5e5e7) got 1.8x on Apple M5 with a bundle of changes to the u64 kernel:
    • deferred-carry additions: accumulate unreduced with a carry counter, fold once per output
    • gf64_mul_add folding the row sum into the 128-bit product before one reduction
    • mul_wide / gf64_sqr assembling the 128-bit product from 32-bit-valued partials with no carry compares
    • one loop driving all 30 rounds, one S-box site, single inlined permute64 phase loop (the code-size changes; the commit says code size dominates on Apple's compiler)
  • Origin PR 92 (09323d3) found that bundle 37% slower on NVIDIA and split the kernels: Metal gets the PR 87 kernel, everything else gets the pre-87 unrolled kernel (mining_u64.wgsl).

Nobody has reported measuring the arithmetic changes alone on the unrolled kernel. NVIDIA's compiler likes unrolled code and dislikes runtime lane counts, so the plausible reading is that the loop restructuring caused the regression and the arithmetic was a win hidden under it.

Plan

Each step is its own harness run on a 5090, parity checked, kept only if positive:

  1. Carry-free mul_wide and gf64_sqr from mining_u64_apple.wgsl into mining_u64.wgsl, everything else unchanged.
  2. gf64_mul_add for the internal layer (int_layer64), which is 22 rounds x 12 multiplies plus a sum, the densest multiply site.
  3. Deferred-carry accumulation in ext_layer64 and the internal row sum.
  4. Round-constant handling: check what naga emits for RC_INITIAL[r][i] with loop-variable indices via RUST_LOG=naga=debug SPIR-V dump and nvdisasm on the driver's cache; if it is private-memory arrays, unroll the round loops fully so constants become immediates.
  5. Workgroup size 128 vs 256 on Blackwell specifically (origin measured on Apple only).

Where the code goes

This is the one workstream that edits an origin kernel file. Rule from #1: do it as a new kernel file, crates/engine-gpu/src/kernels/mining_u64_nvidia.wgsl, selected in kernels/mod.rs by vendor id 0x10DE, leaving mining_u64.wgsl as origin's for every other vendor. The selection arm is a few lines under // lair:. If origin later improves mining_u64.wgsl, we re-run the comparison and drop ours if theirs wins.

Measure

Harness, one 5090, fixed batch 16M, workers 1, pipelining off, so kernel changes are the only variable. Report per step.

Origin coupling

One new kernel file and one selection arm. The kernel duplicates ~500 lines of origin code, which is the acceptable cost of not maintaining a patch on their file.

Part of #1. Fallback-engine work: matters while `engine-cuda` does not exist, and afterwards for any non-NVIDIA discrete card. Smaller expected gain than the other workstreams, 5 to 15%, and the only one where origin has already done most of the thinking. ## History - Origin PR 87 (ea5e5e7) got 1.8x on Apple M5 with a bundle of changes to the u64 kernel: - deferred-carry additions: accumulate unreduced with a carry counter, fold once per output - `gf64_mul_add` folding the row sum into the 128-bit product before one reduction - `mul_wide` / `gf64_sqr` assembling the 128-bit product from 32-bit-valued partials with no carry compares - one loop driving all 30 rounds, one S-box site, single inlined `permute64` phase loop (the code-size changes; the commit says code size dominates on Apple's compiler) - Origin PR 92 (09323d3) found that bundle 37% slower on NVIDIA and split the kernels: Metal gets the PR 87 kernel, everything else gets the pre-87 unrolled kernel (`mining_u64.wgsl`). Nobody has reported measuring the arithmetic changes alone on the unrolled kernel. NVIDIA's compiler likes unrolled code and dislikes runtime lane counts, so the plausible reading is that the loop restructuring caused the regression and the arithmetic was a win hidden under it. ## Plan Each step is its own harness run on a 5090, parity checked, kept only if positive: 1. Carry-free `mul_wide` and `gf64_sqr` from `mining_u64_apple.wgsl` into `mining_u64.wgsl`, everything else unchanged. 2. `gf64_mul_add` for the internal layer (`int_layer64`), which is 22 rounds x 12 multiplies plus a sum, the densest multiply site. 3. Deferred-carry accumulation in `ext_layer64` and the internal row sum. 4. Round-constant handling: check what naga emits for `RC_INITIAL[r][i]` with loop-variable indices via `RUST_LOG=naga=debug` SPIR-V dump and `nvdisasm` on the driver's cache; if it is private-memory arrays, unroll the round loops fully so constants become immediates. 5. Workgroup size 128 vs 256 on Blackwell specifically (origin measured on Apple only). ## Where the code goes This is the one workstream that edits an origin kernel file. Rule from #1: do it as a new kernel file, `crates/engine-gpu/src/kernels/mining_u64_nvidia.wgsl`, selected in `kernels/mod.rs` by vendor id 0x10DE, leaving `mining_u64.wgsl` as origin's for every other vendor. The selection arm is a few lines under `// lair:`. If origin later improves `mining_u64.wgsl`, we re-run the comparison and drop ours if theirs wins. ## Measure Harness, one 5090, fixed batch 16M, workers 1, pipelining off, so kernel changes are the only variable. Report per step. ## Origin coupling One new kernel file and one selection arm. The kernel duplicates ~500 lines of origin code, which is the acceptable cost of not maintaining a patch on their file.
grenade added the needs-benchmarkorigin-couplingperf labels 2026-09-03 09:10:19 +00:00
Author
Owner

Closing as superseded. Every arithmetic idea listed here (carry-free wide products, the row sum folded into the multiply-add, deferred-carry accumulation, constants as immediates) has been implemented and measured in the native CUDA engine instead, where it could be expressed exactly: #16, #17, #23 and #26 take the 4090 from 144 MH/s on the wgpu kernel to 716, and the 5090 from 283 to 1104 per card. On NVIDIA hosts the wgpu kernel is now only the fallback when no CUDA driver is present, and a 5 to 15% improvement to a path that runs at a quarter of the rate is not worth a 500-line duplicate of an origin file to maintain through merges.

If a non-NVIDIA card ever joins the fleet, the relevant question is what origin's kernel does on that vendor, not this port.

Closing as superseded. Every arithmetic idea listed here (carry-free wide products, the row sum folded into the multiply-add, deferred-carry accumulation, constants as immediates) has been implemented and measured in the native CUDA engine instead, where it could be expressed exactly: #16, #17, #23 and #26 take the 4090 from 144 MH/s on the wgpu kernel to 716, and the 5090 from 283 to 1104 per card. On NVIDIA hosts the wgpu kernel is now only the fallback when no CUDA driver is present, and a 5 to 15% improvement to a path that runs at a quarter of the rate is not worth a 500-line duplicate of an origin file to maintain through merges. If a non-NVIDIA card ever joins the fleet, the relevant question is what origin's kernel does on that vendor, not this port.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: blackbeard/miner#7