Hardware guide
The consensus function is defined on integers in {±1..±16}, with every accumulation below 2^24. Any device whose GEMM has an exact int32 or a true FP32 accumulator gets the canonical bits. The miner chooses the storage format per device. A wrong path cannot split consensus, because nodes verify on the CPU. It can only waste the miner's work, and zpu-cli detects that (every winner plus random spot checks are re-verified on the CPU).
Which path per GPU
| GPU | arch | recommended path | alternative | FP8 encoding | notes |
|---|---|---|---|---|---|
| H100 / H200 | sm_90 | --gemm blas-int8 (cuBLAS int8 -> int32, exact) |
lt-fp8 |
OCP E4M3 | Hopper FP8 MMA accumulation precision is the known hazard; run selftest + stress before trusting lt-fp8 |
| B200 | sm_100 | blas-int8 |
lt-fp8 |
OCP E4M3 | measure both |
| B300 (Blackwell Ultra) | sm_103 | lt-fp8 |
blas-int8 (slow) |
OCP E4M3 | dense INT8 is about 0.15 to 0.19 POPS vs 4.5 on B200 (traded for NVFP4), so FP8 is the only fast path and its exactness under stress decides whether B300 can mine efficiently |
| RTX 4090 / 5090, RTX PRO 6000 | sm_89 / sm_120 | blas-int8 |
lt-fp8 |
OCP E4M3 | consumer FP8 MMA with FP16 accumulate is inexact (26% of rows diverge in our model); use FP32-output paths only |
| MI300X | gfx942 | blas-int8 (hipBLAS) |
lt-fp8 (hipBLASLt) |
E4M3FNUZ (bias 8, no -0) | the miner selects FNUZ from gcnArchName |
| MI355X | gfx950 | blas-int8 |
lt-fp8 |
OCP E4M3 | |
| Apple M-series | Metal | zpu_miner.py --backend mlx |
--backend torch --device mps |
fp32 GEMM | measured bit-exact |
| any CPU | zpu-cli --backend cpu or zpu-miner --backend cpu |
NumPy fp32 BLAS | reference |
Measured (2026-11-09)
Rerun 2 (bench/results/gpu_rerun_2026-11-09_r2/, ephemeral cloud sandboxes, ledger bench/results/gpu_ledger.json). Every GEMM path passed the 278-check selftest and the 2^20 accumulator stress; every benchmark re-checked sample digests against the CPU reference.
| GPU | path | attempts/s | TMAC/s | launch overhead | board power | exact |
|---|---|---|---|---|---|---|
| H100 80GB HBM3 | blas-int8 |
8,078,510 | 417 | 0.11 s | ~690 W | PASS |
| H100 | lt-fp8 (E4M3) |
7,379,190 | 381 | 0.43 s | ~690 W | PASS |
| MI355X | blas-int8 |
13,124,061 | 678 | 0.13 s | ~1,230 W | PASS |
| MI355X | lt-fp8 (E4M3) |
13,102,900 | 677 | 1.25 s | ~1,200 W | PASS |
Real mining on the production v0.1 profile (bench/gpu/box_mine.sh): GPU miner -> zpu-cli -> CPU-only node, 120 s, then a second CPU-only node syncs and re-validates the chain from scratch.
| GPU | effective mining rate | blocks accepted | rejected | independent re-sync |
|---|---|---|---|---|
| H100 | 7.56 M attempts/s | 42 | 0 | same tip (height 42) |
| MI355X | 12.39 M attempts/s | 57 | 0 | same tip (height 57) |
Rerun 1 found that zpu-cli handed GPU miners 2,000-attempt jobs, capping real mining at 29k attempts/s; adaptive job sizing (--job-secs, default 0.5 s) fixed it. The earlier H100/MI355X measurements in bench/results/gpu_summary.json agree within 1%.
Not measured on hardware (no device reachable): B200/B300 (sm_100), RTX 50 / RTX PRO 6000 (sm_120), MI300X (gfx942, E4M3FNUZ). Device code for all of them compiles in make check (gpu-device-compile).
ROCm from pip (_rocm_sdk_devel, e.g. the rocm/vllm images) keeps rocBLAS/hipBLASLt code objects in _rocm_sdk_libraries/lib/{rocblas,hipblaslt}/library. Point ROCBLAS_TENSILE_LIBPATH / HIPBLASLT_TENSILE_LIBPATH there (bench/gpu/box_run.sh does it automatically), or hipBLAS/hipBLASLt fail with "Could not initialize Tensile host" / "hipModuleLoad failed".
Build
make -C miner/cuda cpu # any OS
make -C miner/cuda cuda CUDA_ARCH="90 100 120" # needs CUDA 12.8+ (sm_120)
make -C miner/cuda hip HIP_ARCH="gfx942 gfx950" # needs ROCm 6.4+ with hipBLAS and hipBLASLt
Bring-up checklist on a new GPU (in order)
./build/zpu-miner-<vendor> selftest tests/vectors/fp8c_v0.1.json --backend gpu --gemm blas-int8: all 278 checks must PASS.- Repeat with
--gemm lt-fp8and--gemm naive. Record FAIL as a result, not as an error to hide. python miner/python/zpu_miner.py stress --backend torch --device cuda --gemm int8|fp8|fp32must show 0 mismatches. It forces |sum| = 2^20 rows that a random test would miss.bench/run_benchmarks.shrecords the throughput rows intobench/results/miners.jsonl.- Mine on the testnet:
zpu-cli mine --backend exec --exec "./build/zpu-miner-cuda --backend gpu --gemm blas-int8".
Sources for the B300 INT8 figure (third-party spec compilations; confirm against NVIDIA's datasheet before relying on exact values): Tom's Hardware, DataCrunch, flopper.io, Glenn Lockwood.