Closes the first half of the gap against qpow-cuda/1.0.7. Two kernel changes that measure, one dead end recorded so it is not retried.
Measured, parity verified
Three interleaved rounds per figure, spread <=0.2%, parity against the CPU on every host.
host
card / arch
before
after
change
beast
2x 5090, sm_120
1127.1
1358.0 MH/s
+20.5%
benjy
4090, sm_89
457.9
461.8 MH/s
+0.85%
quadbrat
3060, sm_86
83.5
84.6 MH/s
+1.38%
Fleet ~1.67 -> ~1.91 GH/s.
1. Launch uniforms in the parameter bank
midstate, target and the nonce base were device buffers copied into per-thread register arrays and held live across the whole nonce loop -- 56 registers the permutation's working set needed more. ptxas spilled 104 bytes per thread on sm_120 and 44 on sm_86/89, with 26 LDL + 26 STL in the hot loop.
They are launch-uniform, so they now ride in a by-value MiningUniforms parameter: constant memory, broadcast and cached, no register residency, and it arrives with the launch so three memcpy_htod calls per batch go with it. 128 -> 80/80/106 registers, zero spill everywhere. Both sides static_assert the 224-byte layout, so a mismatch is a build error and not a silent mining bug.
2. Per-architecture carry path
An asm block is opaque to nvcc's optimiser, so carry arithmetic written as inline PTX blocks CSE and strength reduction across neighbouring field operations. On Blackwell that costs a quarter of the kernel. On Ada and Ampere the hand-written sequence still wins, by a similar margin the other way:
sm_120 instrs
beast
sm_86/89 instrs
benjy
quadbrat
PTX_CARRY=1
37,432
1127.1
37,027
461.6
85.5
PTX_CARRY=0
27,837
1358.0
41,196
401.5
76.3
So the default is chosen per device pass. sm_86/89 keep the exact configuration they had -- identical macro values, identical 37,027 instructions -- so those cards are unchanged by construction. LAIR_PTX_ACC is now 1 everywhere rather than following LAIR_PTX_CARRY: in the Acc accumulators the add-with-carry pair is the whole operation, and on sm_120 with the carry path off, Acc PTX on measured 1333.3 against 1285.8 with it off.
PR #17 measured the PTX carry path as a win on every card. That was true of the kernel it was written for, which spilled 104 bytes per thread; once the spills went the trade reversed on Blackwell only.
3. Recorded, not applied
Specialised squaring.a^2 = a0^2 + a0*a1*2^33 + a1^2*2^64 is three 32x32 multiplies where the general product needs four, and nvcc does not find it. Implemented, verified bit-exact against __int128 over 40M values plus limb and power-of-two boundaries, measured 5.7% slower (1072.9 vs 1137.7). It saves 6.7% of widening multiplies and costs 11% more instructions; four formulations all compiled to the same 5 IMAD.WIDE in 56 instructions against 6 in 48, so that is the floor.
Per-arch LAIR_INT_UNROLL=1 on sm_120. Better by every static measure (78 registers instead of 106, three resident blocks instead of two) and 0.92% slower on beast. These cards are power-bound: the extra warps buy power draw and the card clocks down to pay for it.
Both are comments in mining.cu so the next person does not rediscover them.
How this was found
Profiling, not reading. ncu put us at 43,700 executed instructions per nonce against the rival binary's 22,229 on the same card -- a 1.97x ratio matching the 2.05x throughput gap -- with 82% of the excess on the ALU pipe, not the multiply pipe. That ruled out the sparse-path theory, the multiply itself, occupancy and launch shape in one measurement, and pointed straight at our own carry machinery.
Standing after this PR: 35,785 instructions per nonce against their 22,229. The multiply pipe is near parity (8,405 vs 7,030, 1.20x); ALU is still 26,586 vs 14,413, so 89% of the remaining gap is ALU. gf_reduce's borrow/mask/carry-fold chain is the largest single term, roughly 7 ALU ops across 1472 multiplies per nonce.
Risk
Kernel arithmetic only, plus the host-side uniform struct. No protocol or engine-selection changes, so the coupling rules in #1 hold. Every knob remains overridable through MINER_NVCC_FLAGS. Parity ran on all three architectures: 25/25 on quadbrat and benjy, 60/60 on beast.
Closes the first half of the gap against `qpow-cuda/1.0.7`. Two kernel changes that measure, one dead end recorded so it is not retried.
## Measured, parity verified
Three interleaved rounds per figure, spread <=0.2%, parity against the CPU on every host.
| host | card / arch | before | after | change |
| --- | --- | --- | --- | --- |
| beast | 2x 5090, sm_120 | 1127.1 | **1358.0 MH/s** | **+20.5%** |
| benjy | 4090, sm_89 | 457.9 | 461.8 MH/s | +0.85% |
| quadbrat | 3060, sm_86 | 83.5 | 84.6 MH/s | +1.38% |
Fleet ~1.67 -> ~1.91 GH/s.
## 1. Launch uniforms in the parameter bank
`midstate`, `target` and the nonce base were device buffers copied into per-thread register arrays and held live across the whole nonce loop -- 56 registers the permutation's working set needed more. ptxas spilled 104 bytes per thread on sm_120 and 44 on sm_86/89, with 26 `LDL` + 26 `STL` in the hot loop.
They are launch-uniform, so they now ride in a by-value `MiningUniforms` parameter: constant memory, broadcast and cached, no register residency, and it arrives with the launch so three `memcpy_htod` calls per batch go with it. 128 -> 80/80/106 registers, zero spill everywhere. Both sides `static_assert` the 224-byte layout, so a mismatch is a build error and not a silent mining bug.
## 2. Per-architecture carry path
An `asm` block is opaque to nvcc's optimiser, so carry arithmetic written as inline PTX blocks CSE and strength reduction across neighbouring field operations. On Blackwell that costs a quarter of the kernel. On Ada and Ampere the hand-written sequence still wins, by a similar margin the other way:
| | sm_120 instrs | beast | sm_86/89 instrs | benjy | quadbrat |
| --- | --- | --- | --- | --- | --- |
| `PTX_CARRY=1` | 37,432 | 1127.1 | 37,027 | 461.6 | 85.5 |
| `PTX_CARRY=0` | 27,837 | **1358.0** | 41,196 | 401.5 | 76.3 |
So the default is chosen per device pass. sm_86/89 keep the exact configuration they had -- identical macro values, identical 37,027 instructions -- so those cards are unchanged by construction. `LAIR_PTX_ACC` is now 1 everywhere rather than following `LAIR_PTX_CARRY`: in the `Acc` accumulators the add-with-carry pair is the whole operation, and on sm_120 with the carry path off, Acc PTX on measured 1333.3 against 1285.8 with it off.
PR #17 measured the PTX carry path as a win on every card. That was true of the kernel it was written for, which spilled 104 bytes per thread; once the spills went the trade reversed on Blackwell only.
## 3. Recorded, not applied
- **Specialised squaring.** `a^2 = a0^2 + a0*a1*2^33 + a1^2*2^64` is three 32x32 multiplies where the general product needs four, and nvcc does not find it. Implemented, verified bit-exact against `__int128` over 40M values plus limb and power-of-two boundaries, measured **5.7% slower** (1072.9 vs 1137.7). It saves 6.7% of widening multiplies and costs 11% more instructions; four formulations all compiled to the same 5 IMAD.WIDE in 56 instructions against 6 in 48, so that is the floor.
- **Per-arch `LAIR_INT_UNROLL=1` on sm_120.** Better by every static measure (78 registers instead of 106, three resident blocks instead of two) and **0.92% slower** on beast. These cards are power-bound: the extra warps buy power draw and the card clocks down to pay for it.
Both are comments in `mining.cu` so the next person does not rediscover them.
## How this was found
Profiling, not reading. `ncu` put us at 43,700 executed instructions per nonce against the rival binary's 22,229 on the same card -- a 1.97x ratio matching the 2.05x throughput gap -- with 82% of the excess on the ALU pipe, not the multiply pipe. That ruled out the sparse-path theory, the multiply itself, occupancy and launch shape in one measurement, and pointed straight at our own carry machinery.
Standing after this PR: 35,785 instructions per nonce against their 22,229. The multiply pipe is near parity (8,405 vs 7,030, 1.20x); ALU is still 26,586 vs 14,413, so 89% of the remaining gap is ALU. `gf_reduce`'s borrow/mask/carry-fold chain is the largest single term, roughly 7 ALU ops across 1472 multiplies per nonce.
## Risk
Kernel arithmetic only, plus the host-side uniform struct. No protocol or engine-selection changes, so the coupling rules in #1 hold. Every knob remains overridable through `MINER_NVCC_FLAGS`. Parity ran on all three architectures: 25/25 on quadbrat and benjy, 60/60 on beast.
Refs #3.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9D
The kernel copied midstate, target and the nonce base into per-thread
register arrays and held all 56 registers live across the whole nonce
loop, which is exactly what the permutation's own working set needed.
ptxas spilled 104 bytes per thread on sm_120 and 44 on sm_86/89; the SASS
carried 26 LDL + 26 STL inside the hot loop.
They are launch-uniform, so they now travel as a by-value MiningUniforms
parameter. Parameters live in constant memory -- broadcast and cached, no
register residency -- and arrive with the launch, so three memcpy_htod
calls per batch are gone with them. Only the low eight nonce limbs are
materialised now; on a hit the high half is written from the parameter
bank. 128 -> 80/80/106 registers on sm_86/89/120, zero spill everywhere.
Both sides assert the 224-byte layout, so a mismatch is a build error
rather than a silent mining bug.
Measured A/B on the fleet, same session, same silicon, parity verified
against the CPU (25/25 on quadbrat and benjy, 40/40 on beast):
quadbrat 3060 83.45 -> 84.60 MH/s +1.38%
benjy 4090 457.87 -> 461.77 MH/s +0.85%
beast 2x5090 1133.97 -> 1138.79 MH/s +0.43%
fleet 1675.29 -> 1685.16 MH/s +0.59%
Not taken: a per-arch LAIR_INT_UNROLL of 1 on sm_120. It is better by
every static measure -- 78 registers instead of 106, three resident
blocks per SM instead of two, no spill either way -- and measured 0.92%
slower on beast over three interleaved rounds. These cards are
power-bound, not latency-bound: the extra warps buy power draw and the
card clocks down to pay for it. Benjy shows the same thing from the other
side, delivering +0.85% at a lower clock (2085 vs 2160 MHz), +4.4% per
MHz. Occupancy is not the lever here; work per hash is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9D
The three-multiply squaring is a real algebraic saving and a measured
loss. Implemented, verified bit-exact against __int128 over 40M random
values plus limb and power-of-two boundaries, then benchmarked on beast:
1072.9 vs 1137.7 MH/s over three interleaved rounds, 5.7% slower.
It trades 6.7% of the kernel's widening multiplies for 11% more
instructions -- 5 IMAD.WIDE in 56 instructions against the general
product's 6 in 48 -- and four formulations (condition-code carry, compare
carry, two mad.wide.u32 variants) all compiled to the same 5/56, so that
is the floor rather than a tuning failure.
Taken with the LAIR_INT_UNROLL result, the picture is consistent: this
kernel is bound by instruction issue and by the cards' power envelope,
not by multiply throughput or occupancy. Re-expressing the same work
cannot win; only removing work can. Left as a comment so the next person
does not spend the afternoon rediscovering it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9D
An `asm` block is opaque to nvcc's optimiser, so carry arithmetic written
as inline PTX blocks common-subexpression elimination and strength
reduction across neighbouring field operations. On Blackwell that costs a
quarter of the kernel. On Ada and Ampere the hand-written sequence still
wins, by a similar margin in the other direction, so the default is now
chosen per device pass.
Whole-kernel instructions (ptxas + cuobjdump, CUDA 13.0) and measured
hashrate, three interleaved rounds each:
sm_120 sm_86/89
PTX_CARRY=1 37,432 1127.1 MH/s 37,027 benjy 461.6 quadbrat 85.5
PTX_CARRY=0 27,837 1358.0 MH/s 41,196 benjy 401.5 quadbrat 76.3
sm_86/89 keep the exact configuration they had, so those cards are
unchanged by construction -- identical macro values, identical 37,027
instructions. Parity 60/60 against the CPU on beast.
LAIR_PTX_ACC is now 1 everywhere rather than following LAIR_PTX_CARRY. In
the Acc accumulators the add-with-carry pair is the whole operation, so
there is nothing around it for the optimiser to fold; on sm_120 with the
carry path off, Acc PTX on measured 1333.3 against 1285.8 with it off.
PR #17 measured the PTX carry path as a win on every card. That was true
of the kernel it was written for, which spilled 104 bytes per thread;
once the spills went the trade reversed on Blackwell only.
Found by profiling rather than reading: ncu puts us at 43,700 executed
instructions per nonce against the rival binary's 22,229 on the same
card, a 1.97x ratio that matches the 2.05x throughput gap, with 82% of
the excess on the ALU pipe rather than the multiply pipe. This lands
35,785. The remaining gap is still ALU: 26,586 against their 14,413,
while the multiply pipe is now close to parity at 8,405 against 7,030.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9D
grenade
merged commit 78dee04ff6 into main2026-09-13 17:20:54 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the first half of the gap against
qpow-cuda/1.0.7. Two kernel changes that measure, one dead end recorded so it is not retried.Measured, parity verified
Three interleaved rounds per figure, spread <=0.2%, parity against the CPU on every host.
Fleet ~1.67 -> ~1.91 GH/s.
1. Launch uniforms in the parameter bank
midstate,targetand the nonce base were device buffers copied into per-thread register arrays and held live across the whole nonce loop -- 56 registers the permutation's working set needed more. ptxas spilled 104 bytes per thread on sm_120 and 44 on sm_86/89, with 26LDL+ 26STLin the hot loop.They are launch-uniform, so they now ride in a by-value
MiningUniformsparameter: constant memory, broadcast and cached, no register residency, and it arrives with the launch so threememcpy_htodcalls per batch go with it. 128 -> 80/80/106 registers, zero spill everywhere. Both sidesstatic_assertthe 224-byte layout, so a mismatch is a build error and not a silent mining bug.2. Per-architecture carry path
An
asmblock is opaque to nvcc's optimiser, so carry arithmetic written as inline PTX blocks CSE and strength reduction across neighbouring field operations. On Blackwell that costs a quarter of the kernel. On Ada and Ampere the hand-written sequence still wins, by a similar margin the other way:PTX_CARRY=1PTX_CARRY=0So the default is chosen per device pass. sm_86/89 keep the exact configuration they had -- identical macro values, identical 37,027 instructions -- so those cards are unchanged by construction.
LAIR_PTX_ACCis now 1 everywhere rather than followingLAIR_PTX_CARRY: in theAccaccumulators the add-with-carry pair is the whole operation, and on sm_120 with the carry path off, Acc PTX on measured 1333.3 against 1285.8 with it off.PR #17 measured the PTX carry path as a win on every card. That was true of the kernel it was written for, which spilled 104 bytes per thread; once the spills went the trade reversed on Blackwell only.
3. Recorded, not applied
a^2 = a0^2 + a0*a1*2^33 + a1^2*2^64is three 32x32 multiplies where the general product needs four, and nvcc does not find it. Implemented, verified bit-exact against__int128over 40M values plus limb and power-of-two boundaries, measured 5.7% slower (1072.9 vs 1137.7). It saves 6.7% of widening multiplies and costs 11% more instructions; four formulations all compiled to the same 5 IMAD.WIDE in 56 instructions against 6 in 48, so that is the floor.LAIR_INT_UNROLL=1on sm_120. Better by every static measure (78 registers instead of 106, three resident blocks instead of two) and 0.92% slower on beast. These cards are power-bound: the extra warps buy power draw and the card clocks down to pay for it.Both are comments in
mining.cuso the next person does not rediscover them.How this was found
Profiling, not reading.
ncuput us at 43,700 executed instructions per nonce against the rival binary's 22,229 on the same card -- a 1.97x ratio matching the 2.05x throughput gap -- with 82% of the excess on the ALU pipe, not the multiply pipe. That ruled out the sparse-path theory, the multiply itself, occupancy and launch shape in one measurement, and pointed straight at our own carry machinery.Standing after this PR: 35,785 instructions per nonce against their 22,229. The multiply pipe is near parity (8,405 vs 7,030, 1.20x); ALU is still 26,586 vs 14,413, so 89% of the remaining gap is ALU.
gf_reduce's borrow/mask/carry-fold chain is the largest single term, roughly 7 ALU ops across 1472 multiplies per nonce.Risk
Kernel arithmetic only, plus the host-side uniform struct. No protocol or engine-selection changes, so the coupling rules in #1 hold. Every knob remains overridable through
MINER_NVCC_FLAGS. Parity ran on all three architectures: 25/25 on quadbrat and benjy, 60/60 on beast.Refs #3.
🤖 Generated with Claude Code
https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9D
An `asm` block is opaque to nvcc's optimiser, so carry arithmetic written as inline PTX blocks common-subexpression elimination and strength reduction across neighbouring field operations. On Blackwell that costs a quarter of the kernel. On Ada and Ampere the hand-written sequence still wins, by a similar margin in the other direction, so the default is now chosen per device pass. Whole-kernel instructions (ptxas + cuobjdump, CUDA 13.0) and measured hashrate, three interleaved rounds each: sm_120 sm_86/89 PTX_CARRY=1 37,432 1127.1 MH/s 37,027 benjy 461.6 quadbrat 85.5 PTX_CARRY=0 27,837 1358.0 MH/s 41,196 benjy 401.5 quadbrat 76.3 sm_86/89 keep the exact configuration they had, so those cards are unchanged by construction -- identical macro values, identical 37,027 instructions. Parity 60/60 against the CPU on beast. LAIR_PTX_ACC is now 1 everywhere rather than following LAIR_PTX_CARRY. In the Acc accumulators the add-with-carry pair is the whole operation, so there is nothing around it for the optimiser to fold; on sm_120 with the carry path off, Acc PTX on measured 1333.3 against 1285.8 with it off. PR #17 measured the PTX carry path as a win on every card. That was true of the kernel it was written for, which spilled 104 bytes per thread; once the spills went the trade reversed on Blackwell only. Found by profiling rather than reading: ncu puts us at 43,700 executed instructions per nonce against the rival binary's 22,229 on the same card, a 1.97x ratio that matches the 2.05x throughput gap, with 82% of the excess on the ALU pipe rather than the multiply pipe. This lands 35,785. The remaining gap is still ALU: 26,586 against their 14,413, while the multiply pipe is now close to parity at 8,405 against 7,030. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ytCkttecH9SNTUEw8VT9Dquantus-bench
575c4d0abdon benjy.hanzalova.internaltotal median: 456.92 MH/s (batch 1000000, 5 x 30s, engine gpu-cuda)
--- resume miner ---
active