Files
miner/crates/engine-gpu/examples/hashrate.rs
Nikolaus Heger d55b970ab6 Optimize GPU Poseidon2 mining 4.1x on Apple Silicon (u64 shader, midstate, lazy squeeze)
Three stacked optimizations, each verified bit-exact against the CPU
reference (qp-poseidon-core) by the dual-shader component suite and a
new GPU/CPU parity example:

- mining_u64.wgsl: native 64-bit Goldilocks arithmetic with plonky2-style
  lazy reduction, used automatically when the adapter supports
  SHADER_INT64 (all Apple Silicon, NVIDIA, modern AMD). mining.wgsl
  remains the 32-bit fallback. (+50%)
- Midstate precompute: the first two of five sponge permutations absorb
  the header and high nonce half, both constant per batch; they are now
  computed once on the CPU (pow_core::mining_midstate) and resumed on
  the GPU. Batches are clamped so nonces never carry past 2^256. (+77%)
- Lazy second squeeze: the first squeeze yields the most significant 256
  bits of the hash, which decide hash-vs-target unless exactly equal to
  the target's high half, so the common reject path skips the final
  permutation. (+56%)

Apple M4 throughput: 2.81 -> 11.4 MH/s at the default 1M batch
(criterion large_range_1m/gpu: 355.8ms -> 86.3ms, -75.7%).

Also:
- Fix criterion GPU bench crash: thread-local worker resources are now
  tagged with an engine id so multiple GpuEngines per process never mix
  devices.
- Fix stale end-to-end test harness (6-binding layout) and run the full
  component suite against both shader variants.
- New examples: hashrate, gpu_cpu_parity (25 jobs incl. 2^256-boundary
  crossing), gpu_features.
- Add Apple M5 family GPU tiers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:40:12 +08:00

53 lines
1.6 KiB
Rust

use engine_cpu::{AtomicBoolCancelCheck, MinerEngine, Range};
use engine_gpu::GpuEngine;
use primitive_types::U512;
use std::sync::atomic::AtomicBool;
fn main() {
env_logger::init();
let args: Vec<String> = std::env::args().collect();
let total: u64 = args
.get(1)
.map(|s| s.parse().expect("total nonces"))
.unwrap_or(4_000_000);
let batch: u32 = args
.get(2)
.map(|s| s.parse().expect("batch size"))
.unwrap_or(1_000_000);
let engine = GpuEngine::try_new(batch, 0, false).expect("GPU init failed");
let cancel_flag = AtomicBool::new(false);
let cancel = AtomicBoolCancelCheck(&cancel_flag);
let header = [42u8; 32];
let ctx = engine.prepare_context(header, U512::from(u64::MAX));
let range = Range {
start: U512::from(1u64) << 200,
end: (U512::from(1u64) << 200) + U512::from(total - 1),
};
// Warmup
let warm = Range {
start: range.start,
end: range.start + U512::from(200_000u64),
};
engine.search_range(&ctx, warm, &cancel);
let start = std::time::Instant::now();
let status = engine.search_range(&ctx, range, &cancel);
let elapsed = start.elapsed().as_secs_f64();
let hashes = match status {
engine_cpu::EngineStatus::Exhausted { hash_count } => hash_count,
engine_cpu::EngineStatus::Found { hash_count, .. } => hash_count,
other => panic!("unexpected status: {other:?}"),
};
println!(
"{} hashes in {:.3}s = {:.3} MH/s",
hashes,
elapsed,
hashes as f64 / elapsed / 1e6
);
}