Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -587,15 +587,29 @@ test-cuda-integration:
--test cuda_path_integration -- --ignored --nocapture --test-threads=1

# num_parts==1 (DECODE) device DEEP/FRI coverage (requires NVIDIA GPU + nvcc).
# The steady-state cuda_path_integration fixtures never cross the default LDE
# threshold (1<<14) for a num_parts==1 table, so lower it here so DECODE engages
# the d=1 device path end to end. 64 = the exact LDE size of fib_iterative_1M's
# DECODE ROM: high enough to engage only DECODE (not the tiny tables whose GPU
# NTT degenerates below ~16), low enough that DECODE crosses it. Its own binary +
# a process-wide env because gpu_lde_threshold() caches the value on first read
# (OnceLock), so it must be set before any prove in the process.
# No fixture crosses the default LDE threshold (1<<14) for a num_parts==1 table,
# so lower it here until DECODE engages the d=1 device path end to end.
#
# Threshold and fixture are one choice, because there are exactly two d=1 tables
# (a d=1 table is one with a single bus interaction): DECODE, whose rows come from
# the guest's instruction count, and KECCAK_RC, fixed at NUM_ROWS=32 => LDE 64.
# DECODE's ROM is derived from the ELF, NOT from cycles, so the whole
# fib_iterative_* family is 13 executable words (the variants differ only in the
# `li a0, <count>` immediate) => 16 rows => LDE 32. That sits BELOW KECCAK_RC's 64,
# so with a fib fixture no threshold isolates DECODE: <=32 engages both and
# 33..=64 engages only KECCAK_RC.
#
# all_instructions_64 is 66 executable words => 128 rows => DECODE LDE 256. At 128,
# DECODE engages with 2x margin and KECCAK_RC (64) declines, so a nonzero
# gpu_comp_h_slabs_calls() uniquely attributes to DECODE. 128 is also ABOVE the
# PR's original 64, so it sends strictly fewer tables onto the GPU-committed path
# and narrows -- rather than widens -- the R4 gather_proofs_dev abort site that
# crypto/stark/src/gpu_lde.rs warns about for lowered thresholds.
#
# Its own binary + a process-wide env because gpu_lde_threshold() caches the value
# on first read (OnceLock), so it must be set before any prove in the process.
test-cuda-d1:
LAMBDA_VM_GPU_LDE_THRESHOLD=64 $(GPU_TEST_TIMEOUT) cargo test -p lambda-vm-prover \
LAMBDA_VM_GPU_LDE_THRESHOLD=128 $(GPU_TEST_TIMEOUT) cargo test -p lambda-vm-prover \
--release --features cuda \
--test cuda_d1_path -- --ignored --nocapture --test-threads=1

Expand Down
59 changes: 38 additions & 21 deletions prover/tests/cuda_d1_path.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
//! End-to-end coverage for the num_parts==1 (DECODE) device DEEP/FRI path.
//!
//! The steady-state fixtures in `cuda_path_integration.rs` only exercise the
//! degree-2 composition path: every asm fixture is a `fib_iterative_*` whose
//! DECODE ROM sits below the default GPU LDE threshold, so no num_parts==1 table
//! ever engages the device path there. This binary lowers
//! `LAMBDA_VM_GPU_LDE_THRESHOLD` (via `make test-cuda-d1`) so DECODE crosses it
//! and the whole d=1 wiring — de-interleave -> R2 commit -> R3 OOD -> R4 DEEP ->
//! FRI -> openings — runs end to end, validated by the release query-0
//! composition canary and the final verify.
//! No fixture crosses the default GPU LDE threshold for a num_parts==1 table, so
//! this binary lowers `LAMBDA_VM_GPU_LDE_THRESHOLD` (via `make test-cuda-d1`)
//! until DECODE engages and the whole d=1 wiring — de-interleave -> R2 commit ->
//! R3 OOD -> R4 DEEP -> FRI -> openings — runs end to end, validated by the
//! release query-0 composition canary and the final verify.
//!
//! Fixture and threshold are one choice. There are exactly two d=1 tables (a d=1
//! table is one with a single bus interaction): DECODE, sized from the guest's
//! instruction count, and KECCAK_RC, fixed at `NUM_ROWS = 32` => LDE 64. DECODE's
//! ROM comes from the ELF and not from cycles, so every `fib_iterative_*` variant
//! is 13 executable words => 16 rows => LDE 32 — below KECCAK_RC's 64, which means
//! no threshold isolates DECODE with a fib fixture. `all_instructions_64` is 66
//! executable words => 128 rows => LDE 256, so at threshold 128 DECODE engages and
//! KECCAK_RC declines: a nonzero counter uniquely attributes to DECODE.
//!
//! Its own binary (not another test in `cuda_path_integration.rs`) on purpose:
//! `gpu_lde_threshold()` caches the env in a `OnceLock` on first read, so the
Expand All @@ -22,28 +28,38 @@ use lambda_vm_prover::test_utils::asm_elf_bytes;
use lambda_vm_prover::{prove, verify};
use stark::gpu_lde::{gpu_comp_h_slabs_calls, reset_all_gpu_call_counters};

/// With the LDE threshold lowered so the DECODE (num_parts==1) table engages,
/// the device de-interleave path (`gpu_comp_h_slabs_calls`) must fire and the
/// proof — whose DECODE DEEP/FRI now ran on device — must still verify. Guards a
/// silent CPU fallback (counter == 0) and a bad-layout regression (fires but the
/// proof fails verification); the in-prove release query-0 canary guards the
/// The fixture whose DECODE ROM crosses the lowered threshold: 66 executable
/// words -> 128 rows -> LDE 256.
const FIXTURE: &str = "all_instructions_64";
/// DECODE's LDE for [`FIXTURE`], and the LDE of the only other d=1 table. The
/// threshold must fall between them so the counter attributes to DECODE alone.
const DECODE_LDE: usize = 256;
const KECCAK_RC_LDE: usize = 64;

/// With the LDE threshold lowered so the DECODE (num_parts==1) table engages, the
/// device de-interleave path (`gpu_comp_h_slabs_calls`) must fire and the proof —
/// whose DECODE DEEP/FRI now ran on device — must still verify. Guards a silent
/// CPU fallback (counter == 0) and a bad-layout regression (fires but the proof
/// fails verification); the in-prove release query-0 canary guards the
/// composition-row gather on top.
#[test]
#[ignore = "requires GPU + a lowered LAMBDA_VM_GPU_LDE_THRESHOLD; run via `make test-cuda-d1`"]
fn gpu_num_parts_1_decode_path_fires_and_verifies() {
// Meaningful only with the threshold lowered (the make target sets it). Fail
// with a pointed message rather than a confusing "path did not fire".
// Pin the window rather than just "below the default": a threshold anywhere
// outside (KECCAK_RC_LDE, DECODE_LDE] silently measures the wrong table (or no
// table), which is exactly the failure this constant pair exists to prevent.
let thr: usize = std::env::var("LAMBDA_VM_GPU_LDE_THRESHOLD")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0);
assert!(
thr > 0 && thr < 1 << 14,
"run via `make test-cuda-d1`: needs LAMBDA_VM_GPU_LDE_THRESHOLD set below the \
default (1<<14) so the DECODE (num_parts==1) table engages the device path; got {thr}"
thr > KECCAK_RC_LDE && thr <= DECODE_LDE,
"run via `make test-cuda-d1`: LAMBDA_VM_GPU_LDE_THRESHOLD must land in \
({KECCAK_RC_LDE}, {DECODE_LDE}] so {FIXTURE}'s DECODE (LDE {DECODE_LDE}) engages the \
device path while KECCAK_RC (LDE {KECCAK_RC_LDE}) declines; got {thr}"
);

let elf = asm_elf_bytes("fib_iterative_1M");
let elf = asm_elf_bytes(FIXTURE);
// Warm-up amortises PTX load + pool warm-up so the measured prove reflects
// steady state (mirrors cuda_path_integration.rs).
let _ = prove(&elf).expect("warm-up prove");
Expand All @@ -53,8 +69,9 @@ fn gpu_num_parts_1_decode_path_fires_and_verifies() {

assert!(
gpu_comp_h_slabs_calls() > 0,
"num_parts==1 device de-interleave path did not fire: no DECODE-shaped table \
crossed the lowered LDE threshold, so the d=1 DEEP/FRI wiring was not exercised"
"num_parts==1 device de-interleave path did not fire: DECODE (the only d=1 table \
above the threshold for {FIXTURE}) did not take it, so the d=1 DEEP/FRI wiring \
was not exercised"
);
assert!(
verify(&proof, &elf).expect("verify"),
Expand Down
Loading