Implements #3 steps 1 through 3 in one go: crate, straight port of the kernel, and the __umul64hi field multiply. Nothing in engine-gpu is touched.
What changed
crates/engine-cuda (lair-owned): CudaEngine behind MinerEngine, same batch loop, cancellation, thread-local device assignment, metrics (kernel label cuda) and stale accounting as the wgpu engine. cudarc 0.19 with dynamic loading, so there is no link-time CUDA dependency and the binary runs on hosts without a driver (it just falls back).
kernels/mining.cu: same host contract and sponge schedule as mining_u64.wgsl (host midstate, low-256-bit nonce increments, lazy second squeeze), bit-exact with pow_core. The field multiply is __umul64hi plus a plain multiply and one reduction instead of four 32-bit partials with carry reconstruction.
build.rs: generates poseidon2_constants.cuh from qp-poseidon-constants (the rule from #1: a hash change at origin is a dependency bump, never a hand-copied table) and compiles one fat binary with cubins for sm_86, sm_89, sm_120 plus compute_120 PTX when nvcc is present. Without nvcc (the rust lint runner, workstations) it writes an empty image and warns; the crate still builds and try_new fails with a clear message.
miner-service: resolve_gpu_configuration tries CUDA first unless --gpu-engine wgpu; --gpu-engine cuda makes its absence an error. miner-cli gains the flag (MINER_GPU_ENGINE) on serve and benchmark. Worker exit clears CUDA resources too.
bench-harness: --engine auto|cuda|wgpu.
deploy.yaml: matrix rows carry the kernel validate expects on miner_device_hashes_total; a silent wgpu fallback on a CUDA host now fails the deploy.
Measured
quadbrat, RTX 3060 at 130 W, miner paused, 2 x 10 s windows:
engine
MH/s
parity
wgpu (u64 kernel)
37.3
5/5
CUDA
59.5
5/5
1.59x from the straight port. Grid sizing sweep (MINER_CUDA_THREADS_PER_SM): 8192 and above are flat at 59.4; 2048 loses 10 percent. The bench workflow on this PR runs --engine auto, so its comment below is the 4090 number against the 144.4 MH/s wgpu baseline.
clippy -D warnings, fmt and workspace tests clean (with the empty-kernel build here; the kernel built and passed parity on quadbrat).
What merging does
The deploy builds on cuda-13.0, where nvcc is present, so the fat binary is embedded and both hosts switch to the CUDA engine on this merge; validate asserts kernel="cuda" on device 0.
Not yet: __constant__/unroll tuning and pipelined submission (steps 5 and 6 of #3), sccache.
Implements #3 steps 1 through 3 in one go: crate, straight port of the kernel, and the `__umul64hi` field multiply. Nothing in `engine-gpu` is touched.
## What changed
- **`crates/engine-cuda`** (lair-owned): `CudaEngine` behind `MinerEngine`, same batch loop, cancellation, thread-local device assignment, metrics (kernel label `cuda`) and stale accounting as the wgpu engine. `cudarc` 0.19 with dynamic loading, so there is no link-time CUDA dependency and the binary runs on hosts without a driver (it just falls back).
- **`kernels/mining.cu`**: same host contract and sponge schedule as `mining_u64.wgsl` (host midstate, low-256-bit nonce increments, lazy second squeeze), bit-exact with `pow_core`. The field multiply is `__umul64hi` plus a plain multiply and one reduction instead of four 32-bit partials with carry reconstruction.
- **`build.rs`**: generates `poseidon2_constants.cuh` from `qp-poseidon-constants` (the rule from #1: a hash change at origin is a dependency bump, never a hand-copied table) and compiles one fat binary with cubins for `sm_86`, `sm_89`, `sm_120` plus `compute_120` PTX when nvcc is present. Without nvcc (the `rust` lint runner, workstations) it writes an empty image and warns; the crate still builds and `try_new` fails with a clear message.
- **`miner-service`**: `resolve_gpu_configuration` tries CUDA first unless `--gpu-engine wgpu`; `--gpu-engine cuda` makes its absence an error. `miner-cli` gains the flag (`MINER_GPU_ENGINE`) on `serve` and `benchmark`. Worker exit clears CUDA resources too.
- **`bench-harness`**: `--engine auto|cuda|wgpu`.
- **`deploy.yaml`**: matrix rows carry the kernel validate expects on `miner_device_hashes_total`; a silent wgpu fallback on a CUDA host now fails the deploy.
## Measured
quadbrat, RTX 3060 at 130 W, miner paused, 2 x 10 s windows:
| engine | MH/s | parity |
| --- | --- | --- |
| wgpu (u64 kernel) | 37.3 | 5/5 |
| CUDA | 59.5 | 5/5 |
**1.59x** from the straight port. Grid sizing sweep (`MINER_CUDA_THREADS_PER_SM`): 8192 and above are flat at 59.4; 2048 loses 10 percent. The bench workflow on this PR runs `--engine auto`, so its comment below is the 4090 number against the 144.4 MH/s wgpu baseline.
clippy `-D warnings`, fmt and workspace tests clean (with the empty-kernel build here; the kernel built and passed parity on quadbrat).
## What merging does
The deploy builds on `cuda-13.0`, where nvcc is present, so the fat binary is embedded and both hosts switch to the CUDA engine on this merge; validate asserts `kernel="cuda"` on device 0.
Not yet: `__constant__`/unroll tuning and pipelined submission (steps 5 and 6 of #3), `sccache`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
quantus/miner#3. New crate, nothing in engine-gpu touched.
- kernels/mining.cu: the same host contract and sponge schedule as
mining_u64.wgsl (host midstate, low-256-bit nonce increments, lazy second
squeeze), bit-exact with pow_core. The field multiply is __umul64hi plus a
plain multiply and one reduction, instead of four 32-bit partials with
carry reconstruction.
- build.rs: generates poseidon2_constants.cuh from qp-poseidon-constants
(a hash change at origin is a dependency bump, never a hand-copied table)
and, when nvcc is present, compiles one fat binary with cubins for
sm_86/89/120 plus compute_120 PTX. Without nvcc it writes an empty image,
the crate still builds, and CudaEngine::try_new fails with a clear message
so the miner falls back to wgpu.
- lib.rs: CudaEngine with the same batch loop, cancellation, thread-local
device assignment, metrics (kernel label "cuda") and stale accounting as
engine-gpu. cudarc 0.19 with dynamic loading; no link-time CUDA dependency.
- miner-service: resolve_gpu_configuration tries CUDA first unless
--gpu-engine wgpu; --gpu-engine cuda makes its absence an error.
miner-cli gains the flag on serve and benchmark (MINER_GPU_ENGINE).
- bench-harness: --engine auto|cuda|wgpu.
- deploy.yaml: matrix rows carry the kernel validate expects; a wgpu
fallback on a CUDA host now fails the deploy instead of passing at a
fraction of the hashrate.
Measured on quadbrat (RTX 3060, 130 W, miner paused, 2 x 10 s windows,
parity 5/5 verified against CPU): wgpu 37.3 MH/s, CUDA 59.5 MH/s (1.59x).
Grid sizing 8192 threads/SM or more is flat; 2048 loses 10%.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
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.
Implements #3 steps 1 through 3 in one go: crate, straight port of the kernel, and the
__umul64hifield multiply. Nothing inengine-gpuis touched.What changed
crates/engine-cuda(lair-owned):CudaEnginebehindMinerEngine, same batch loop, cancellation, thread-local device assignment, metrics (kernel labelcuda) and stale accounting as the wgpu engine.cudarc0.19 with dynamic loading, so there is no link-time CUDA dependency and the binary runs on hosts without a driver (it just falls back).kernels/mining.cu: same host contract and sponge schedule asmining_u64.wgsl(host midstate, low-256-bit nonce increments, lazy second squeeze), bit-exact withpow_core. The field multiply is__umul64hiplus a plain multiply and one reduction instead of four 32-bit partials with carry reconstruction.build.rs: generatesposeidon2_constants.cuhfromqp-poseidon-constants(the rule from #1: a hash change at origin is a dependency bump, never a hand-copied table) and compiles one fat binary with cubins forsm_86,sm_89,sm_120pluscompute_120PTX when nvcc is present. Without nvcc (therustlint runner, workstations) it writes an empty image and warns; the crate still builds andtry_newfails with a clear message.miner-service:resolve_gpu_configurationtries CUDA first unless--gpu-engine wgpu;--gpu-engine cudamakes its absence an error.miner-cligains the flag (MINER_GPU_ENGINE) onserveandbenchmark. Worker exit clears CUDA resources too.bench-harness:--engine auto|cuda|wgpu.deploy.yaml: matrix rows carry the kernel validate expects onminer_device_hashes_total; a silent wgpu fallback on a CUDA host now fails the deploy.Measured
quadbrat, RTX 3060 at 130 W, miner paused, 2 x 10 s windows:
1.59x from the straight port. Grid sizing sweep (
MINER_CUDA_THREADS_PER_SM): 8192 and above are flat at 59.4; 2048 loses 10 percent. The bench workflow on this PR runs--engine auto, so its comment below is the 4090 number against the 144.4 MH/s wgpu baseline.clippy
-D warnings, fmt and workspace tests clean (with the empty-kernel build here; the kernel built and passed parity on quadbrat).What merging does
The deploy builds on
cuda-13.0, where nvcc is present, so the fat binary is embedded and both hosts switch to the CUDA engine on this merge; validate assertskernel="cuda"on device 0.Not yet:
__constant__/unroll tuning and pipelined submission (steps 5 and 6 of #3),sccache.🤖 Generated with Claude Code
https://claude.ai/code/session_01CBgs2nSi4H2mdh8kD8vMX5
quantus-bench
6ecc5ee6c7on benjy.hanzalova.internaltotal median: 297.70 MH/s (batch 1000000, 5 x 30s, engine gpu-cuda)
--- resume miner ---
active