quanpool-miner 6.2.4 measured on beast: 1234 MH/s per 5090, what it does differently, what was tried #27
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fetched https://download.quanpool.com/quanpool-miner-linux-x86_64 (sha256 1e398a83…f5aaf, "quanpool-miner 6.2.4 (2026-09-11)", a fork of the open-source miner: same metrics names and
benchmarksubcommand, plus adevfeecrate with a licence fetch from license.quanpool.com and telemetry to quanpool.com; 5% dev fee). Run in benchmark mode only, inside a network-less namespace, on beast GPU 0 at the 400 W cap with our miner paused.Result
quanpool: 1020 blocks x 1024 threads x 8 nonces per launch, 2 streams in flight, autotuned geometry cached in
~/.config/quanpool-miner/tuning.json, "early abort off (measured: costs more than it saves)". 279 SM-cycles per nonce against our 354; issue-active 57.8% vs our 50.6%. After their 5% fee the user-visible rate is ~1172, about 6% above ours.What the SASS shows (ncu --print-source sass; the cubin is not stored raw in the ELF)
3,014 static instructions, three loops (external-initial 741, internal 397 per two rounds, external-terminal 662), 12 LDG, 14 LDS, 56 LDCU; round constants and the diagonal come from constant memory with computed offsets. Same 32-bit-limb Goldilocks arithmetic as ours and quantusminer's, with two differences:
mad.lo.cc/madc.hi.ccreduction on 2M random products including the dropped-borrow cases. Same count per multiply, one fewer IMAD.WIDE.The second point is a ptxas scheduling result over one long carry chain, not something the PTX carry model (a single CC flag) lets hand-written asm express; our asm blocks fence it.
Tried against it (three interleaved rounds each, beast)
MINER_CUDA_BATCH_WAVES=8): -1.2%; 4: neutral.unsigned __int128(to let ptxas schedule carries itself): 40,368 static instructions on sm_120 against 21,120, nvcc's 128-bit lowering is far worse than the asm. Not committed.Where that leaves it
Both remaining differences (predicate-carried adds across operations, 64-register loop-structured kernel) need the kernel restructured as rolled loops with the arithmetic as one continuous chain per round, so ptxas can schedule carries globally and the register footprint drops. That is a rewrite of
permute, with the #25 ptxas anomaly as a standing warning to parity-test every step. Expected gain if it fully lands: ~10% per nonce plus whatever the occupancy buys, i.e. roughly their 1234.🤖 Generated with Claude Code
https://claude.ai/code/session_01Ue5ZZm1Hiv5zPnucykKKuF
The
LAIR_MINBLOCKSlaunch-bounds knob used for the register experiments is on branchperf/minblocks-knob(c1cac91), pushed but not merged: it changes no default, and merging would only trigger a rebuild-and-redeploy of the same kernel. Merge it with the next real kernel change.Upstream's own engine measured for comparison. origin/main 0ba8ddf (v4.2.0, "CUDA: software-pipelined internal rounds, free-addend products, no early exit"), built on beast,
quantus-miner benchmark --cuda-gpu --cpu-workers 0 --gpu-devices 1 --duration 30on GPU 0 at the 400 W cap, miner paused, NVRTC from CUDA 13.0:--cuda-gpuSo upstream's CUDA engine is 12.5% slower than ours on this card, and quanpool's lead is their own work on top of it, not something we can merge from origin. Upstream CUDA commit titles are not performance evidence; measure before evaluating any of them for the fork.
Round 2 (2026-09-14): the loop kernel's internal round, itemised
With
mining_loopin (#29), every candidate is now screened by the static size of its internal-round loop body on sm_120 before card time (screen.sh/kbody.pyon beast:/tmp; branchperf/int-round-chaincarries the variants). Reference: ours 239 instructions at 128 registers, quanpool 199.Opcode difference per round (ours minus theirs): LDC +12, IADD-family +24, IMAD +11, MOV +6, SEL -8. Broken down:
Screened and rejected (all static, none beat the shipped form): five multiply-add formulations,
LAIR_MULADD0..4: 239 / 260 / 283 / 275 / 265 (1 = whole 64-bit addend with carry-out, the shape of their SASS; 2 = nvcc's product with the addend in C; 3 = addend added to the 128-bit product as a limb chain; 4 = 32-bit carry assembly of the middle products). Constant hoisting: identical. 32-bit-halves diagonal: identical. Internal rounds paired per iteration: worse on hardware (1976 vs 2105).Registers. 128 with 88 B spill is the floor for this arithmetic in the loop kernel; 64 spills 632 B whatever the block shape. The 4090 nonetheless prefers the loop kernel (+1.7%, deployed on benjy via #29); the 5090 and 3060 do not.
What is left that could move it, in order of plausibility: (1) get the diagonal into uniform registers, which needs a delivery ptxas will not sink back into the loop, e.g. kernel parameters (parameter bank, uniform by construction) or
asm volatileloads before the loop; (2) an ALU-only EPS fold in the S-box reductions to rebalance pipes (count-neutral, needs a bench); (3) the round-constant carry riding into the product's high half instead of a second reduction. Each is small; together they are roughly the 40. The register half of the target (64, no spill, 1024 threads) is not reachable by any shape tried.Lead (1) from the round-2 list, measured: the diagonal delivered through the launch parameters (
MiningUniforms::diag,LAIR_LOOP_PARAM_DIAG). It does not remove the per-round loads (ptxas issues 13 LDC either way, +2 instructions) but the loop kernel's spill drops from 88 to 32 bytes, and that shows on the card: beast loop 2135 -> 2162 MH/s (+1.3%), benjy 725 -> 730 (+0.7%), parity clean. Loop kernel on the 5090 is now 2.0% behind the unrolled one (2162 vs 2206). On branchperf/int-round-chainwith the screening variants; PR to follow, benjy gets it through its existingcuda_kernel: looprow.Close-out (2026-09-14)
Where we stand against the quanpool kernel on the same 5090 at 400 W: ours 1107 MH/s per card (unrolled, deployed), theirs 1234; 1172 after their 5% fee. Instruction count per nonce 22.9k vs 20.7k.
What this issue produced:
mining_loop,MINER_CUDA_KERNEL=loop, #29, #31), parity-clean, deployed where it wins: benjy's 4090 +1.7%. On the 5090 it is 2% behind our unrolled kernel.Why it stops here. The remaining ~40 instructions per internal round are five small items, the largest of which (12 constant reloads per round) is a ptxas scheduling decision that survives every delivery expressible from source, and the register half of the target (64 without spill) is unreachable with this arithmetic in any shape tried. The two ideas left, an ALU-only EPS fold in the S-box reductions and the round-constant carry riding into the product's high half, are worth about a percent each and would not reach instruction parity together. The win condition set for this issue (their instruction count at 1024 threads per SM without spill) is not reachable along this line, and further effort here buys less than the ~1% per step it costs.
Standing guard, unchanged: every kernel change passes parity on all three architectures before merge (#25 remains open for the ptxas anomaly).
Foreign binaries used for the measurements are still on beast under /tmp/kregs (quanpool) and /tmp/kregs/qpow-cuda; delete them if the residual risk of having them there is unwanted.