perf(gpu): grind the proof-of-work nonce on the GPU - #936
Conversation
|
/bench-gpu |
|
/ai-review |
GPU Benchmark (ABBA) —
|
Codex Code Review
|
| let expected = 1u64.checked_shl(grinding_factor as u32).unwrap_or(u64::MAX); | ||
| let count = expected | ||
| .saturating_mul(8) | ||
| .clamp(1 << 18, 1 << 28); |
There was a problem hiding this comment.
Low (perf) — no minimum-factor gate, so small factors pay a launch to replace ~2 hashes.
ProofOptions::default_test_options() and MIN_PROOF_OPTIONS both use grinding_factor: 1, i.e. limit = 1 << 63, where the CPU finds a valid nonce at nonce 0 or 1 in ~100 ns. On that path this function still does 2 H2D allocs + a 2^18-thread launch + D2H + stream.synchronize() on a shared pool stream — tens of µs, per table per epoch, and the sync blocks whatever else a rayon peer had queued on that stream. Pure loss, and it's the configuration every non-GPU-benchmark test uses.
The repo already gates other dispatches this way (GPU_LOGUP_MIN_ROWS, LAMBDA_VM_GPU_LDE_THRESHOLD). Suggest an early if grinding_factor < GRIND_MIN_FACTOR { return None; } (something like 12–16, where the CPU search is still sub-millisecond) alongside the existing range check at line 23.
Review: GPU proof-of-work grindingI verified the kernel derivation against the host predicate and it checks out: Findings, none blocking correctness of the happy path: Medium
Low
Nits
|
AI ReviewPR #936 · 6 changed files Findings
Status column reflects the verdict from the verifier: deepseek-verifier (openrouter/deepseek/deepseek-v4-pro). AI-001: GPU nonce validity only checked in debug builds
Claim In release builds, an invalid nonce returned by the GPU kernel would be accepted and used in the proof, producing an invalid proof. The GPU result is only validated by a debug_assert!, which is stripped in release. Evidence crypto/stark/src/grinding.rs lines 115-120: after generate_nonce_gpu returns a nonce, the code runs debug_assert!(is_valid_nonce(seed, nonce, grinding_factor), ...). debug_assert! is compiled out in release, so any GPU-side bug (kernel regression, driver/hardware issue) would silently yield an invalid nonce. The check costs only ~2 Keccak hashes versus the ~2^grinding_factor hashes already performed, so there is no performance justification for disabling it in release. Suggested fix Either make the check a real runtime AI-003: Non-atomic read of atomic result for early exit optimization
Claim The early-exit check Evidence Line 172 reads *result directly. The variable is written via atomicMin on line 187. This is a classic data race pattern, though harmless for this optimization. Suggested fix Use atomicLoad or __ldg to read the value, or simply remove the optimization since the grid-stride loop already bounds work. If kept, annotate with // NOLINT or similar to suppress race detectors. AI-005: Repeated device allocation in generate_nonce_gpu loop
Claim A new result_dev buffer is allocated via clone_htod(&[u64::MAX]) on every loop iteration, causing unnecessary allocation overhead when multiple grid launches are needed (e.g., for high grinding factors). Evidence Line 49: let mut result_dev = stream.clone_htod(&[u64::MAX]).ok()?; inside the loop at line 48. The buffer is only used to receive the atomicMin result and could be allocated once before the loop and reused. Suggested fix Move the result_dev allocation before the loop and reuse it by resetting to u64::MAX each iteration (e.g., via stream.memset or a tiny kernel). Reviewer Lanes
Verification Lanes
Native Codex and Claude reviews run separately and post their own comments. They are not included in this structured provenance report. Discarded candidates (2) — rejected by the verifier
Raw lane outputs, candidates, final issues, and model metrics are uploaded as workflow artifacts. |
7ee4d16 to
0b3ab88
Compare
|
/bench-gpu |
Grinding (generate_nonce) runs a ~2^grinding_factor parallel Keccak search per table per epoch and is the prover's dominant CPU cost — 64.7% of on-CPU time in a 100tx flamegraph, on the 16 cores while the GPU sits ~66% idle. Add a keccak nonce-search kernel (each thread strides a nonce block, atomicMin keeps the smallest valid nonce), a math-cuda wrapper that searches in expanding blocks from 0, and a stark dispatch that computes the inner hash on the host, validates the device result unconditionally, and falls back to the CPU search on any device miss or invalid nonce. Result-valid: the verifier only checks is_valid_nonce, so any valid nonce works. A device launch is skipped below a minimum grinding factor (tiny factors are faster on the CPU), and LAMBDA_VM_NO_GPU_GRIND forces the CPU path. GPU_GRIND_CALLS counts the dispatches so a silent fallback is caught by the integration test. 100tx e20 (ABBA, same binary): 18.89s -> 13.10s = -30.6%.
0b3ab88 to
5a895bc
Compare
|
/bench-gpu |
1 similar comment
|
/bench-gpu |
Grinding (generate_nonce) runs a ~2^grinding_factor parallel Keccak search per table per epoch and is the prover's dominant CPU cost — 64.7% of on-CPU time in a 100tx flamegraph, on the 16 cores while the GPU sits ~66% idle.
Add a keccak nonce-search kernel (each thread strides a nonce block, atomicMin keeps the smallest valid nonce), a math-cuda wrapper that searches in expanding blocks from 0, and a stark dispatch that computes the inner hash on the host and falls back to the CPU search on any device miss. Result-valid: the verifier only checks is_valid_nonce, so any valid nonce works. LAMBDA_VM_NO_GPU_GRIND forces the CPU path; below a minimum grinding factor the GPU launch is skipped (tiny factors are faster on the CPU), and GPU_GRIND_CALLS counts the dispatches.
100tx e20 (ABBA, same binary): 18.89s -> 13.10s = -30.6%.