High-performance external mining service for Quantus Network with support for CPU, GPU, and hybrid CPU+GPU mining.
Like the official miner, but fast: a native CUDA engine for NVIDIA cards, kept bit-exact with the reference hash and measured against every other miner we can get hold of. No dev fee, no licence server, no telemetry, solo or pool. Apache-2.0, like upstream.
Measured hashrate, three interleaved 30-second rounds per card at the enforced power cap, memory clock locked at 810 MHz, CUDA 13.0 driver 580:
| card | power cap | this fork | upstream --cuda-gpu | quantusminer.com qpow-cuda 1.0.7 | quanpool-miner 6.2.4 (5% fee) |
|---|---|---|---|---|---|
| RTX 5090 | 400 W | 1104 MH/s | 944 | 1171 | 1234 (1172 after fee) |
| RTX 4090 | 250 W | 727 MH/s (loop kernel) | |||
| RTX 3060 | 130 W | 141 MH/s |
The 5090 column was measured on the same card in the same session (2026-09-13); the closed pool binaries were run in benchmark mode only. How the pool kernels get their remaining edge, and what has been tried, is written up in issue #27.
Since v4.0.2-lair.2 the binary carries two kernels. The default (unrolled)
is fastest on the RTX 5090 and 3060; RTX 40-series cards measure 1.7% better
on the rolled-loop one, selected with MINER_CUDA_KERNEL=loop in the
environment (it reports as kernel cuda-loop on the metrics). Both are
bit-exact with the CPU reference; try each with quantus-bench on your card.
Binaries are on the releases page:
one Linux x86_64 quantus-miner carrying cubins for sm_86 (RTX 30), sm_89
(RTX 40) and sm_120 (RTX 50) plus PTX for anything newer, built on Fedora 43
(glibc 2.42 or newer), and quantus-bench, the measurement harness. Nothing
is compiled at run time; the driver needs CUDA 13.0 support (580.x).
Reproduce the numbers. quantus-bench pins the power limit it expects,
warms up, reports the median of timed windows with their spread, and checks
GPU hashes against the CPU reference for random jobs:
# hashrate: median of 5 x 30 s windows, one worker thread per card
./quantus-bench --duration-secs 30 --runs 5 --workers 1 --expect-power-limit 400
# parity: 2000 random jobs, every found hash recomputed on the CPU
./quantus-bench --duration-secs 3 --runs 1 --parity-jobs 2000
Compare builds with interleaved rounds (A, B, A, B, ...) rather than one block of runs each: on a power-capped card, clock drift between blocks looks like a 0.5% code change.
Solo mining against your own node is the serve command below with the
node's auth token and certificate fingerprint; nothing else is needed.
# CPU-only build (default)
cargo build -p miner-cli --release
# With GPU support (recommended)
cargo build -p miner-cli --release
The binary will be available at target/release/quantus-miner.
The node requires a shared auth token and TLS cert pin. Both live under the
node's chain config dir (<base-path>/chains/<chain>/):
miner-auth-token — shared secret (not logged by the node; read this file)miner-tls-cert-sha256 — SHA-256 of the miner TLS cert (also printed in node logs)# Preferred: mount/read the node's chain config files
./target/release/quantus-miner serve \
--node-addr 127.0.0.1:9833 \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 4
# Or pass values directly (token from miner-auth-token; fingerprint also in node logs)
./target/release/quantus-miner serve \
--node-addr 127.0.0.1:9833 \
--auth-token <TOKEN> \
--tls-cert-sha256 <FINGERPRINT> \
--gpu-devices 1
# Hybrid CPU+GPU mining
./target/release/quantus-miner serve \
--node-addr 127.0.0.1:9833 \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 4 \
--gpu-devices 1
| Argument | Environment Variable | Description | Default |
|---|---|---|---|
--node-addr <ADDR> | MINER_NODE_ADDR | Node address to connect to | 127.0.0.1:9833 |
--auth-token <TOKEN> | MINER_AUTH_TOKEN | Shared secret from the node's miner-auth-token file (not logged) | required |
--auth-token-file <PATH> | MINER_AUTH_TOKEN_FILE | Read the shared secret from a file (preferred) | — |
--tls-cert-sha256 <HEX> | MINER_TLS_CERT_SHA256 | SHA-256 of the node's miner TLS cert (miner-tls-cert-sha256 / node logs) | required |
--tls-cert-sha256-file <PATH> | MINER_TLS_CERT_SHA256_FILE | Read the TLS cert fingerprint from a file | — |
--cpu-workers <N> | MINER_CPU_WORKERS | Number of CPU worker threads | Auto-detect |
--gpu-devices <N> | MINER_GPU_DEVICES | Number of GPU devices | Auto-detect |
--gpu-batch-size <N> | MINER_GPU_BATCH_SIZE | GPU batch size in nonces | 1000000 |
--cpu-batch-size <N> | MINER_CPU_BATCH_SIZE | CPU batch size in hashes | 10000 |
--gpu-throttle-ms <MS> | MINER_GPU_THROTTLE_MS | Sleep duration (ms) between GPU batches | 0 |
--metrics-port <PORT> | MINER_METRICS_PORT | Prometheus metrics port | 9900 |
GPU support uses WGPU for cross-platform acceleration:
Build with GPU support:
cargo build -p miner-cli --release
Platform requirements:
nvidia-driver, mesa-vulkan-drivers)sudo powermetrics --samplers gpu_power -i 1000nvidia-smi (NVIDIA) or radeontop (AMD)All serve examples need the auth token and TLS pin (files or inline values).
# CPU mining with 8 workers
./target/release/quantus-miner serve \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 8
# Pure GPU mining
./target/release/quantus-miner serve \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--gpu-devices 1
# GPU mining with throttle (reduce GPU utilization)
./target/release/quantus-miner serve \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--gpu-devices 1 --gpu-throttle-ms 50
# Hybrid mining: 4 CPU + 1 GPU workers
./target/release/quantus-miner serve \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 4 --gpu-devices 1
# With verbose logging
RUST_LOG=debug ./target/release/quantus-miner serve \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 2 --gpu-devices 1
# Production setup with metrics
./target/release/quantus-miner serve \
--node-addr 127.0.0.1:9833 \
--auth-token-file /path/to/miner-auth-token \
--tls-cert-sha256-file /path/to/miner-tls-cert-sha256 \
--cpu-workers 6 \
--gpu-devices 1 \
--metrics-port 9900
The miner uses a QUIC-based protocol for communication with the node:
Ready { token } must match the node's miner-auth-tokenquantus-miner/2Ready (miner→node), NewJob (node→miner), JobResult (miner→node)For full protocol specification, see the node's MINING.md.
# Benchmark CPU performance
./target/release/quantus-miner benchmark --cpu-workers 8 --duration 30
# Benchmark GPU performance
./target/release/quantus-miner benchmark --gpu-devices 1 --duration 30
# Benchmark hybrid performance
./target/release/quantus-miner benchmark --cpu-workers 4 --gpu-devices 1 --duration 30
33 activities
1520e19 Merge pull request 'deploy: tie the GPU profile to the miner's lifecycle' (#33) from feat/gpu-profile-with-lifecycle into main97073b0 deploy: tie the GPU profile to the miner's lifecycleWhere 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.
monday, september 14, 2026 — 19:15:20 utc2160ed6 Merge pull request 'deploy: require nvcc at build time' (#32) from ci/deploy-require-cuda into main6e300d5 deploy: require nvcc at build time6e300d5 deploy: require nvcc at build timebb92224 Merge pull request 'engine-cuda: loop kernel diagonal from launch parameters (+1.3% loop kernel), multiply-add screening variants' (#31) from perf/int-round-chain into main1fea51c engine-cuda: loop kernel takes the diagonal from the launch parameters4572a5e engine-cuda: attribute the loop kernel's internal-round excess; drop two no-op knobs338b4a7 engine-cuda: LAIR_MULADD screening variants (0 stays; 1-4 measured worse)1fea51c engine-cuda: loop kernel takes the diagonal from the launch parametersLead (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 branch perf/int-round-chain with the screening variants; PR to follow, benjy gets it through its existing cuda_kernel: loop row.
With mining_loop in (#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.py on beast:/tmp; branch perf/int-round-chain carries 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_MULADD 0..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 volatile loads 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.
4572a5e engine-cuda: attribute the loop kernel's internal-round excess; drop two no-op knobs338b4a7 engine-cuda: LAIR_MULADD screening variants (0 stays; 1-4 measured worse)4a6ed42 Merge pull request 'readme: 4090 at 727 MH/s on the loop kernel, how to select it' (#30) from docs/readme-lair2 into mainbbbaf81 readme: 4090 at 727 MH/s on the loop kernel, how to select itad77d99 Merge pull request 'engine-cuda: loop-structured mining_loop kernel, opt-in via MINER_CUDA_KERNEL=loop' (#29) from perf/loop-kernel into main12bd36a deploy: per-host CUDA kernel choice; benjy runs the loop kernel816b1ef engine-cuda: loop-structured mining_loop kernel behind MINER_CUDA_KERNEL