Skip to content

feat(stark): GPU constraint/composition evaluation#798

Draft
MauroToscano wants to merge 7 commits into
mainfrom
feat/gpu-constraint-eval
Draft

feat(stark): GPU constraint/composition evaluation#798
MauroToscano wants to merge 7 commits into
mainfrom
feat/gpu-constraint-eval

Conversation

@MauroToscano

Copy link
Copy Markdown
Contributor

PR 1 of 3 (stack: this → GPU trace residency → CUDA build robustness).

What

Evaluate the round-2 composition polynomial H on the GPU instead of the CPU. The constraint program is lowered once to a flat device IR (constraint_ir::device), and a fused CUDA kernel computes H(row) = z_inv·Σβᵢ·Cᵢ + boundary reading the already-resident LDE — no per-constraint matrix, no trace D2H for constraint eval.

Structure

  • constraint_interp.cu has two kernels: the fused composition kernel (production, wired into round 2) and a matrix interpreter kernel that is test-only — a parity oracle proving the GPU node-walk matches the CPU folder bit-for-bit, per opcode. They share the device-IR lowering.
  • gpu_interp.rs is the single concrete-Goldilocks dispatch seam (TypeId-gated, one unsafe reinterpret in lower_and_pack).

Win

~−2.7% prove time (ethrex continuation, RTX 5090), measured via the LAMBDA_VM_DISABLE_GPU_COMPOSITION A/B toggle.

Validation

Bit-for-bit parity: synthetic all-ops program + all 26 production AIRs vs the CPU folder; e2e prove→verify (cuda_path_integration). Covered by a multi-agent code review (no wrong-proof findings).

…kernel, dispatch)

Lower the captured ConstraintProgram to a flat device blob and interpret it on
the GPU (one thread per LDE row), for constraint-eval data residency — keep the
large LDE trace on-device instead of round-tripping it to evaluate constraints
on the CPU.

- constraint_ir/device.rs: DeviceProgram::lower — flat #[repr(C)] blob
  (DeviceNode{op,a,b,dim} + u64/[u64;3] const tables + roots + num_base) — plus
  eval_device_program, an independent CPU walk over that flat blob = the pre-GPU
  parity oracle (mirrors interp.rs bit-for-bit).
- constraint_ir/gpu_interp.rs (cfg cuda): try_eval_program_gpu — TypeId gate on
  Goldilocks + one device-edge reinterpret + flatten + kernel launch, Option
  return (None = CPU fallback).
- math-cuda/kernels/constraint_interp.cu: the interpreter kernel — one thread
  per row, per-thread value array in global memory, all-ext3 (base = {x,0,0},
  embed = identity), per-constraint eval matrix output.
- math-cuda/src/constraint_interp.rs + build.rs/device.rs/lib.rs wiring: cudarc
  host wrapper (all-u64 uploads, thread-capped value scratch).
- prover device parity test: lower + walk every one of the 26 production
  programs and diff against the compiled prover folder.

CPU-verified: device unit test (all ops, 1000 rows) + 26-program parity green;
math-cuda and stark --features cuda compile (empty-PTX-stub path when nvcc is
absent); fmt + clippy -D warnings clean. The kernel itself awaits nvcc + a GPU
for compile and GPU<->CPU parity (the next phase).
Drive constraint_interp.cu through gpu_interp::try_eval_program_gpu over a random device-resident LDE and assert the per-constraint eval matrix is bit-for-bit identical to the CPU oracle eval_device_program (itself pinned to the compiled folder). Covers a synthetic all-ops program and all 26 production programs. cuda-gated; validated on an RTX 5090 (sm_120, CUDA 13.1).
Add constraint_composition_kernel: same node walk as the interpreter but fuses the composition-poly accumulation on-device (no per-constraint matrix, no D2H): H(row) = z_inv[row]·Σ βᵢ·Cᵢ + Σ_b z_b_inv[row]·β_b·(trace_b − value_b), the uniform-zerofier case of evaluator::evaluate. Plus the eval_composition_on_device wrapper, the try_eval_composition_gpu dispatch (gated; non-uniform → CPU fallback, which the VM never hits since it has no end-exemptions), and a GPU↔CPU H parity test against a CPU oracle that applies the exact evaluator accumulation to eval_device_program's evals.
ConstraintEvaluator::evaluate now tries try_evaluate_composition_gpu before the CPU per-row loop: when the GPU LDE handles are present, the tower is Goldilocks/degree-3, the zerofier is uniform (VM has no end-exemptions), and transition offsets are contiguous, it produces H(row) on-device (transition + boundary fused, next_step = lde_step_size) and returns it for the existing decompose+commit. Otherwise falls through to the unchanged CPU path. Additive + feature-gated; the CPU path is untouched.
Add GPU_COMPOSITION_CALLS (mirrors the other gpu_*_calls counters), incremented on a successful fused-composition dispatch, plus gpu_composition_path_fires_and_verifies: proves fib_iterative_1M with cuda, asserts the composition path fired (guards silent CPU fallback), and verifies the proof (guards a bad-H regression).
Add set_gpu_composition_disabled / LAMBDA_VM_DISABLE_GPU_COMPOSITION runtime toggle (gpu_lde.rs) checked in try_evaluate_composition_gpu, so one cuda binary can A/B the round-2 GPU composition path against the CPU accumulation with zero build differences. bench_abba_gpu.sh runs an interleaved ABBA over the ethrex workload flipping that toggle (A=GPU on, B=CPU), default 20 transfers.
…oundary shape asserts

- gpu_interp: factor the shared TypeId gate + unsafe program reinterpret + lower
  + uniform packing into lower_and_pack() (was duplicated across both dispatch fns).
- constraint_interp: assert all boundary-input shapes (b_is_aux/b_value/b_beta),
  not just b_z_inv, so a caller mismatch panics cleanly instead of an OOB read.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant