The scheduling leads left after #23, each measured with three interleaved rounds and parity on every host (quantus/miner#3).
Batches of whole grids (host). The CLI's 1M-nonce batch filled a 5090 eleven waves deep with a half-empty tail wave and paid the launch gap every 1M hashes. Batches are now rounded up to a multiple of the grid (SMs x threads per SM), two grids per batch by default so each thread loops over two nonces: beast +1.6% for one grid, +0.4% more for two; benjy +0.5% for the second grid; quadbrat neutral. A batch is ~10 ms on every card, so a job switch discards ~0.1% of a block interval. MINER_CUDA_BATCH_ALIGN=0 restores the CLI batch, MINER_CUDA_BATCH_WAVES sets the minimum grids per batch.
LAIR_EARLY_REJECT (kernel). The second permutation stops before its last external linear layer; output 0 alone (the hash's top 64 bits) is computed and compared with the target's top words, and only the ~2^-47 of nonces that survive pay for the other eleven outputs and the canonicalisation. +0.6%.
LAIR_NONCE_DIR (host + kernel). The host computes the state after the first external layer and round constant for the batch's first nonce (first_layer_after_absorb, in canonical arithmetic, with a unit test that the matrix column the kernel assumes is [1,1,3,2,2,2,6,4,1,1,3,2]). The kernel derives each nonce's state as that base plus a scalar (the byte-swapped low limb difference) times that column via gf_mul_add, twelve fused multiply-adds instead of the absorb and full layer. Nonces past a carry out of the low limb take the old path. +0.25% on beast, +0.6% on the 4090 and 3060. The uniform block grows from 224 to 320 bytes.
Tried and rejected: 512-thread blocks (-0.4%; the LAIR_TPB / MINER_CUDA_THREADS_PER_BLOCK knob stays for experiments), a smaller grid via MINER_CUDA_THREADS_PER_SM=16384 (-0.6%), a larger one at 65536 (within noise of two waves), and the whole x^7 S-box as one PTX block, which compiles to byte-identical SASS (noted in a comment on gf_sbox).
Parity: 2000/2000 on beast with the final build, 500/500 and 300/300 on the intermediate builds on beast, benjy and quadbrat. Static: sm_120 21,120 instructions, 110 registers, no spill; sm_86/89 22,968 instructions, 80 registers with an 8-byte spill that did not show on hardware.
Where this leaves the gap to the pool binary: instruction parity, ~6% of cycles per nonce on the 5090 that the profiler attributes to scheduling and the power envelope. No further scalar lever is known; the voltage/frequency curve is the remaining host-side one.
The scheduling leads left after #23, each measured with three interleaved rounds and parity on every host (quantus/miner#3).
**Batches of whole grids** (host). The CLI's 1M-nonce batch filled a 5090 eleven waves deep with a half-empty tail wave and paid the launch gap every 1M hashes. Batches are now rounded up to a multiple of the grid (SMs x threads per SM), two grids per batch by default so each thread loops over two nonces: beast +1.6% for one grid, +0.4% more for two; benjy +0.5% for the second grid; quadbrat neutral. A batch is ~10 ms on every card, so a job switch discards ~0.1% of a block interval. `MINER_CUDA_BATCH_ALIGN=0` restores the CLI batch, `MINER_CUDA_BATCH_WAVES` sets the minimum grids per batch.
**`LAIR_EARLY_REJECT`** (kernel). The second permutation stops before its last external linear layer; output 0 alone (the hash's top 64 bits) is computed and compared with the target's top words, and only the ~2^-47 of nonces that survive pay for the other eleven outputs and the canonicalisation. +0.6%.
**`LAIR_NONCE_DIR`** (host + kernel). The host computes the state after the first external layer and round constant for the batch's first nonce (`first_layer_after_absorb`, in canonical arithmetic, with a unit test that the matrix column the kernel assumes is `[1,1,3,2,2,2,6,4,1,1,3,2]`). The kernel derives each nonce's state as that base plus a scalar (the byte-swapped low limb difference) times that column via `gf_mul_add`, twelve fused multiply-adds instead of the absorb and full layer. Nonces past a carry out of the low limb take the old path. +0.25% on beast, +0.6% on the 4090 and 3060. The uniform block grows from 224 to 320 bytes.
**Tried and rejected**: 512-thread blocks (-0.4%; the `LAIR_TPB` / `MINER_CUDA_THREADS_PER_BLOCK` knob stays for experiments), a smaller grid via `MINER_CUDA_THREADS_PER_SM=16384` (-0.6%), a larger one at 65536 (within noise of two waves), and the whole x^7 S-box as one PTX block, which compiles to byte-identical SASS (noted in a comment on `gf_sbox`).
**Measured**, final tree vs the deployed #23 build:
| host | deployed | this PR | |
|---|---|---|---|
| beast 2x5090 | 2143 | 2205 MH/s | +2.9% (1103/card vs the pool binary's 1171) |
| benjy 4090 | 711 | ~718 MH/s | +1.0% (nd build, two waves) |
| quadbrat 3060 | 140.3 | 141.2 MH/s | +0.6% |
Parity: 2000/2000 on beast with the final build, 500/500 and 300/300 on the intermediate builds on beast, benjy and quadbrat. Static: sm_120 21,120 instructions, 110 registers, no spill; sm_86/89 22,968 instructions, 80 registers with an 8-byte spill that did not show on hardware.
Where this leaves the gap to the pool binary: instruction parity, ~6% of cycles per nonce on the 5090 that the profiler attributes to scheduling and the power envelope. No further scalar lever is known; the voltage/frequency curve is the remaining host-side one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Ue5ZZm1Hiv5zPnucykKKuF
Three scheduling changes measured against the #23 kernel, three interleaved
rounds each, parity clean on every host:
- Batches are rounded up to whole grids, two per batch by default
(MINER_CUDA_BATCH_ALIGN, MINER_CUDA_BATCH_WAVES). The CLI's 1M-nonce
batch left a half-empty tail wave and paid the launch gap every 1M
hashes. beast +1.6% for one grid, +0.4% more for two; benjy +0.5% for
the second grid; quadbrat neutral.
- LAIR_EARLY_REJECT: the second permutation stops before its last linear
layer, computes output 0 alone (the hash's top 64 bits) and rejects on
it; only the rare survivors pay for the other eleven outputs. +0.6%.
- LAIR_NONCE_DIR: the host precomputes the state after the first linear
layer for the batch's first nonce (first_layer_after_absorb, unit test
against the matrix column), and the kernel derives each nonce's state
as that plus a scalar times column 7 of the external matrix; nonces
past a carry out of the low limb take the old path. +0.25% on beast,
+0.6% on the 4090 and 3060.
Also tried and rejected: 512-thread blocks (-0.4%), a smaller grid via
MINER_CUDA_THREADS_PER_SM=16384 (-0.6%), a larger one (65536, within
noise of two waves), and the whole S-box as one PTX block, which
compiles to byte-identical SASS. The threads-per-block knob
(MINER_CUDA_THREADS_PER_BLOCK with LAIR_TPB) stays for experiments.
beast 2x5090: 2146 -> ~2206 MH/s (+2.8%); benjy 4090: 711 -> ~718;
quadbrat 3060: 140.2 -> 141.2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ue5ZZm1Hiv5zPnucykKKuF
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.
The scheduling leads left after #23, each measured with three interleaved rounds and parity on every host (quantus/miner#3).
Batches of whole grids (host). The CLI's 1M-nonce batch filled a 5090 eleven waves deep with a half-empty tail wave and paid the launch gap every 1M hashes. Batches are now rounded up to a multiple of the grid (SMs x threads per SM), two grids per batch by default so each thread loops over two nonces: beast +1.6% for one grid, +0.4% more for two; benjy +0.5% for the second grid; quadbrat neutral. A batch is ~10 ms on every card, so a job switch discards ~0.1% of a block interval.
MINER_CUDA_BATCH_ALIGN=0restores the CLI batch,MINER_CUDA_BATCH_WAVESsets the minimum grids per batch.LAIR_EARLY_REJECT(kernel). The second permutation stops before its last external linear layer; output 0 alone (the hash's top 64 bits) is computed and compared with the target's top words, and only the ~2^-47 of nonces that survive pay for the other eleven outputs and the canonicalisation. +0.6%.LAIR_NONCE_DIR(host + kernel). The host computes the state after the first external layer and round constant for the batch's first nonce (first_layer_after_absorb, in canonical arithmetic, with a unit test that the matrix column the kernel assumes is[1,1,3,2,2,2,6,4,1,1,3,2]). The kernel derives each nonce's state as that base plus a scalar (the byte-swapped low limb difference) times that column viagf_mul_add, twelve fused multiply-adds instead of the absorb and full layer. Nonces past a carry out of the low limb take the old path. +0.25% on beast, +0.6% on the 4090 and 3060. The uniform block grows from 224 to 320 bytes.Tried and rejected: 512-thread blocks (-0.4%; the
LAIR_TPB/MINER_CUDA_THREADS_PER_BLOCKknob stays for experiments), a smaller grid viaMINER_CUDA_THREADS_PER_SM=16384(-0.6%), a larger one at 65536 (within noise of two waves), and the whole x^7 S-box as one PTX block, which compiles to byte-identical SASS (noted in a comment ongf_sbox).Measured, final tree vs the deployed #23 build:
Parity: 2000/2000 on beast with the final build, 500/500 and 300/300 on the intermediate builds on beast, benjy and quadbrat. Static: sm_120 21,120 instructions, 110 registers, no spill; sm_86/89 22,968 instructions, 80 registers with an 8-byte spill that did not show on hardware.
Where this leaves the gap to the pool binary: instruction parity, ~6% of cycles per nonce on the 5090 that the profiler attributes to scheduling and the power envelope. No further scalar lever is known; the voltage/frequency curve is the remaining host-side one.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Ue5ZZm1Hiv5zPnucykKKuF
quantus-bench
2e6fce162eon benjy.hanzalova.internaltotal median: 709.92 MH/s (batch 1000000, 5 x 30s, engine gpu-cuda)
--- resume miner ---
active