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>
64 lines
2.4 KiB
Rust
64 lines
2.4 KiB
Rust
use engine_cpu::{AtomicBoolCancelCheck, EngineStatus, MinerEngine, Range};
|
|
use engine_gpu::GpuEngine;
|
|
use primitive_types::U512;
|
|
use rand::RngCore;
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
fn main() {
|
|
env_logger::init();
|
|
let jobs: usize = std::env::args()
|
|
.nth(1)
|
|
.map(|s| s.parse().expect("job count"))
|
|
.unwrap_or(25);
|
|
|
|
let engine = GpuEngine::try_new(1_000_000, 0, false).expect("GPU init failed");
|
|
let cancel_flag = AtomicBool::new(false);
|
|
let cancel = AtomicBoolCancelCheck(&cancel_flag);
|
|
let mut rng = rand::rng();
|
|
|
|
let mut found = 0usize;
|
|
for job in 0..jobs {
|
|
let mut header = [0u8; 32];
|
|
rng.fill_bytes(&mut header);
|
|
let difficulty = U512::from(100_000u64);
|
|
let ctx = engine.prepare_context(header, difficulty);
|
|
|
|
let start = if job == 0 {
|
|
// Cross a 2^256 boundary: the high nonce half changes mid-range,
|
|
// exercising the midstate batch clamp.
|
|
(U512::from(3u64) << 256) - U512::from(1_000u64)
|
|
} else {
|
|
let mut start_bytes = [0u8; 64];
|
|
rng.fill_bytes(&mut start_bytes);
|
|
// Keep clear of the very top so range arithmetic cannot wrap
|
|
start_bytes[0] = 0;
|
|
U512::from_big_endian(&start_bytes)
|
|
};
|
|
let range = Range {
|
|
start,
|
|
end: start + U512::from(10_000_000u64),
|
|
};
|
|
|
|
match engine.search_range(&ctx, range.clone(), &cancel) {
|
|
EngineStatus::Found { candidate, .. } => {
|
|
let cpu_hash = pow_core::hash_from_nonce(&ctx, candidate.nonce);
|
|
assert_eq!(
|
|
cpu_hash, candidate.hash,
|
|
"job {job}: GPU hash != CPU hash for nonce {}",
|
|
candidate.nonce
|
|
);
|
|
assert!(cpu_hash < ctx.target, "job {job}: hash not below target");
|
|
assert!(
|
|
candidate.nonce >= range.start && candidate.nonce <= range.end,
|
|
"job {job}: nonce outside range"
|
|
);
|
|
found += 1;
|
|
}
|
|
EngineStatus::Exhausted { .. } => {}
|
|
other => panic!("job {job}: unexpected status {other:?}"),
|
|
}
|
|
}
|
|
assert!(found > 0, "no solutions found across {jobs} jobs");
|
|
println!("PARITY OK: {found}/{jobs} jobs found solutions, all verified against CPU");
|
|
}
|