Skip to content

Commit 2176b85

Browse files
authored
Merge branch 'main' into feat/disk-spill-v2-parallel-r2
2 parents 8d33a32 + 8ebca95 commit 2176b85

26 files changed

Lines changed: 4110 additions & 1 deletion

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"crypto/stark",
66
"crypto/crypto",
77
"crypto/math",
8+
"crypto/math-cuda",
89
"bin/cli",
910
]
1011

Makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: deps deps-linux deps-macos prepare-test-data compile-programs-asm compile-programs-rust compile-bench \
22
compile-programs clean-asm clean-rust clean-bench clean-shared clean test test-asm test-no-compile \
33
test-asm-no-compile test-rust test-rust-no-compile test-executor flamegraph-prover \
4-
test-fast test-prover test-prover-all build check clippy fmt lint
4+
test-fast test-prover test-prover-all test-math-cuda bench-math-cuda build check clippy fmt lint
55

66
UNAME := $(shell uname)
77

@@ -166,6 +166,14 @@ test-prover-all:
166166
test-prover-debug:
167167
cargo test -p lambda-vm-prover --features debug-checks -- --nocapture
168168

169+
# math-cuda parity tests (requires NVIDIA GPU + nvcc)
170+
test-math-cuda:
171+
cargo test -p math-cuda --release
172+
173+
# math-cuda quick microbench (median of 10 runs)
174+
bench-math-cuda:
175+
cargo test -p math-cuda --release --test bench_quick -- --ignored --nocapture
176+
169177
# Build all
170178
build:
171179
cargo build --workspace

crypto/math-cuda/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "math-cuda"
3+
description = "CUDA-accelerated FFT/NTT for Goldilocks (base field) used by the lambda-vm STARK prover"
4+
version = "0.1.0"
5+
edition = "2024"
6+
7+
[dependencies]
8+
cudarc = { version = "0.19", default-features = false, features = [
9+
"driver",
10+
"nvrtc",
11+
"std",
12+
"cuda-version-from-build-system",
13+
"fallback-latest",
14+
"dynamic-loading",
15+
] }
16+
math = { path = "../math" }
17+
rayon = "1.7"
18+
19+
[dev-dependencies]
20+
rand = { version = "0.8.5", features = ["std"] }
21+
rand_chacha = "0.3.1"
22+
rayon = "1.7"
23+
sha3 = "0.10.8"

crypto/math-cuda/build.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use std::env;
2+
use std::fs;
3+
use std::path::PathBuf;
4+
use std::process::Command;
5+
6+
fn cuda_home() -> PathBuf {
7+
env::var_os("CUDA_HOME")
8+
.or_else(|| env::var_os("CUDA_PATH"))
9+
.map(PathBuf::from)
10+
.unwrap_or_else(|| PathBuf::from("/usr/local/cuda"))
11+
}
12+
13+
fn nvcc_path() -> PathBuf {
14+
cuda_home().join("bin").join("nvcc")
15+
}
16+
17+
/// Query `nvidia-smi` for the local GPU's compute capability (e.g. "12.0"
18+
/// for Blackwell). Returns a `compute_XX` target on success, falling back
19+
/// to `compute_89` (Ada) when no GPU is visible or the query fails.
20+
fn detect_arch() -> String {
21+
const FALLBACK: &str = "compute_89";
22+
let output = match Command::new("nvidia-smi")
23+
.args(["--query-gpu=compute_cap", "--format=csv,noheader"])
24+
.output()
25+
{
26+
Ok(o) if o.status.success() => o,
27+
_ => return FALLBACK.to_string(),
28+
};
29+
let line = match std::str::from_utf8(&output.stdout) {
30+
Ok(s) => s,
31+
Err(_) => return FALLBACK.to_string(),
32+
};
33+
// First line, first comma-separated value (covers multi-GPU hosts).
34+
let cap = match line.lines().next() {
35+
Some(l) => l.split(',').next().unwrap_or("").trim(),
36+
None => return FALLBACK.to_string(),
37+
};
38+
let (major, minor) = match cap.split_once('.') {
39+
Some((m, n)) => (m.trim(), n.trim()),
40+
None => return FALLBACK.to_string(),
41+
};
42+
if major.chars().all(|c| c.is_ascii_digit()) && minor.chars().all(|c| c.is_ascii_digit()) {
43+
format!("compute_{major}{minor}")
44+
} else {
45+
FALLBACK.to_string()
46+
}
47+
}
48+
49+
fn compile_ptx(src: &str, out_name: &str, have_nvcc: bool) {
50+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
51+
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
52+
let src_path = manifest_dir.join("kernels").join(src);
53+
let out_path = out_dir.join(out_name);
54+
55+
println!("cargo:rerun-if-changed=kernels/{src}");
56+
println!("cargo:rerun-if-env-changed=CUDA_HOME");
57+
println!("cargo:rerun-if-env-changed=CUDA_PATH");
58+
println!("cargo:rerun-if-env-changed=CUDARC_NVCC_ARCH");
59+
60+
// When nvcc is missing from PATH, emit an empty PTX stub so the crate
61+
// still compiles. include_str! in src/device.rs needs the file to exist
62+
// at build time. Any runtime kernel call panics in cudarc when loading
63+
// the empty module. We can't run GPU code without nvcc on the build
64+
// host anyway.
65+
if !have_nvcc {
66+
fs::write(&out_path, "").expect("failed to write empty PTX stub");
67+
return;
68+
}
69+
70+
// Emit PTX for a virtual architecture; the CUDA driver JIT-compiles it for the
71+
// actual GPU at load time. Override with CUDARC_NVCC_ARCH to pin a specific
72+
// compute capability. If unset, try `nvidia-smi` to match the host GPU
73+
// (avoids JIT failures like nvcc-13.0 PTX rejected on Blackwell drivers);
74+
// fall back to compute_89 (Ada) when detection fails.
75+
let arch = env::var("CUDARC_NVCC_ARCH").unwrap_or_else(|_| detect_arch());
76+
77+
let status = Command::new(nvcc_path())
78+
.args(["--ptx", "-O3", "-std=c++17", "-arch", &arch, "-o"])
79+
.arg(&out_path)
80+
.arg(&src_path)
81+
.status()
82+
.expect("failed to invoke nvcc — is CUDA installed and CUDA_HOME set?");
83+
84+
if !status.success() {
85+
panic!("nvcc failed compiling {}", src_path.display());
86+
}
87+
}
88+
89+
fn main() {
90+
// Headers aren't compiled, so emit rerun-if-changed to rebuild on
91+
// header edits.
92+
println!("cargo:rerun-if-changed=kernels/goldilocks.cuh");
93+
println!("cargo:rerun-if-changed=kernels/ext3.cuh");
94+
95+
// Probe for nvcc once. Workspace consumers (clippy, fmt, CPU-only test
96+
// runners) build math-cuda incidentally without using its kernels. Stub
97+
// out PTX when nvcc is unavailable so those builds succeed.
98+
let have_nvcc = Command::new(nvcc_path())
99+
.arg("--version")
100+
.output()
101+
.map(|o| o.status.success())
102+
.unwrap_or(false);
103+
if !have_nvcc {
104+
println!(
105+
"cargo:warning=math-cuda: nvcc not found at {} — emitting empty PTX stubs. \
106+
Runtime GPU calls will panic. Install CUDA and rebuild for a working backend.",
107+
nvcc_path().display()
108+
);
109+
}
110+
111+
compile_ptx("arith.cu", "arith.ptx", have_nvcc);
112+
compile_ptx("ntt.cu", "ntt.ptx", have_nvcc);
113+
}

crypto/math-cuda/kernels/arith.cu

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Element-wise Goldilocks kernels used by the parity tests. These mirror
2+
// the CPU reference in `crypto/math/src/field/goldilocks.rs` so raw u64 outputs
3+
// are bit-identical to the CPU path.
4+
5+
#include "goldilocks.cuh"
6+
#include "ext3.cuh"
7+
8+
using goldilocks::add;
9+
using goldilocks::sub;
10+
using goldilocks::mul;
11+
using goldilocks::neg;
12+
13+
extern "C" __global__ void vector_add_u64(const uint64_t *a,
14+
const uint64_t *b,
15+
uint64_t *c,
16+
uint64_t n) {
17+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
18+
if (tid < n) c[tid] = a[tid] + b[tid]; // plain wrapping u64 add — toolchain sanity only.
19+
}
20+
21+
extern "C" __global__ void gl_add_kernel(const uint64_t *a,
22+
const uint64_t *b,
23+
uint64_t *c,
24+
uint64_t n) {
25+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
26+
if (tid < n) c[tid] = add(a[tid], b[tid]);
27+
}
28+
29+
extern "C" __global__ void gl_sub_kernel(const uint64_t *a,
30+
const uint64_t *b,
31+
uint64_t *c,
32+
uint64_t n) {
33+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
34+
if (tid < n) c[tid] = sub(a[tid], b[tid]);
35+
}
36+
37+
extern "C" __global__ void gl_mul_kernel(const uint64_t *a,
38+
const uint64_t *b,
39+
uint64_t *c,
40+
uint64_t n) {
41+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
42+
if (tid < n) c[tid] = mul(a[tid], b[tid]);
43+
}
44+
45+
extern "C" __global__ void gl_neg_kernel(const uint64_t *a,
46+
uint64_t *c,
47+
uint64_t n) {
48+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
49+
if (tid < n) c[tid] = neg(a[tid]);
50+
}
51+
52+
// ---------------------------------------------------------------------------
53+
// Ext3 (Goldilocks cubic extension) test kernels.
54+
// Input/output arrays are interleaved [a_0, b_0, c_0, a_1, b_1, c_1, ...].
55+
// ---------------------------------------------------------------------------
56+
57+
extern "C" __global__ void ext3_mul_kernel(const uint64_t *a_int,
58+
const uint64_t *b_int,
59+
uint64_t *c_int,
60+
uint64_t n) {
61+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
62+
if (tid >= n) return;
63+
ext3::Fe3 a = ext3::make(a_int[tid*3 + 0], a_int[tid*3 + 1], a_int[tid*3 + 2]);
64+
ext3::Fe3 b = ext3::make(b_int[tid*3 + 0], b_int[tid*3 + 1], b_int[tid*3 + 2]);
65+
ext3::Fe3 r = ext3::mul(a, b);
66+
c_int[tid*3 + 0] = r.a;
67+
c_int[tid*3 + 1] = r.b;
68+
c_int[tid*3 + 2] = r.c;
69+
}
70+
71+
extern "C" __global__ void ext3_add_kernel(const uint64_t *a_int,
72+
const uint64_t *b_int,
73+
uint64_t *c_int,
74+
uint64_t n) {
75+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
76+
if (tid >= n) return;
77+
ext3::Fe3 a = ext3::make(a_int[tid*3 + 0], a_int[tid*3 + 1], a_int[tid*3 + 2]);
78+
ext3::Fe3 b = ext3::make(b_int[tid*3 + 0], b_int[tid*3 + 1], b_int[tid*3 + 2]);
79+
ext3::Fe3 r = ext3::add(a, b);
80+
c_int[tid*3 + 0] = r.a;
81+
c_int[tid*3 + 1] = r.b;
82+
c_int[tid*3 + 2] = r.c;
83+
}
84+
85+
extern "C" __global__ void ext3_sub_kernel(const uint64_t *a_int,
86+
const uint64_t *b_int,
87+
uint64_t *c_int,
88+
uint64_t n) {
89+
uint64_t tid = blockIdx.x * blockDim.x + threadIdx.x;
90+
if (tid >= n) return;
91+
ext3::Fe3 a = ext3::make(a_int[tid*3 + 0], a_int[tid*3 + 1], a_int[tid*3 + 2]);
92+
ext3::Fe3 b = ext3::make(b_int[tid*3 + 0], b_int[tid*3 + 1], b_int[tid*3 + 2]);
93+
ext3::Fe3 r = ext3::sub(a, b);
94+
c_int[tid*3 + 0] = r.a;
95+
c_int[tid*3 + 1] = r.b;
96+
c_int[tid*3 + 2] = r.c;
97+
}

0 commit comments

Comments
 (0)