Per pearl_build_rs_rerun_if_env_changed, every std::env::var() must be paired with cargo:rerun-if-env-changed. Task #321 fixed crates/ml/build.rs (commite3d082968) but missed 6 sibling build.rs files. Each reads CUDA_COMPUTE_CAP without registering the rerun directive, so cargo sees no env-change between e.g. SM 89 (L40S) → SM 90 (H100) workflow re-submissions and cache-hits the wrong-arch cubin from the previous run. At runtime, the H100 fails to load the SM 89 cubin with CUDA_ERROR_NO_BINARY_FOR_GPU on rmsnorm — exactly what killed train-multi-seed-vlv8c (commit0371d6a76, post-Class-B chain). Files (all add `println!("cargo:rerun-if-env-changed=CUDA_COMPUTE_CAP")` just before the env::var() read): - crates/ml-dqn/build.rs (rmsnorm — root cause of vlv8c failure) - crates/ml-ensemble/build.rs - crates/ml-explainability/build.rs - crates/ml-ppo/build.rs - crates/ml-supervised/build.rs - crates/ml-core/build.rs Atomic single commit per feedback_no_partial_refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
3.1 KiB
Rust
104 lines
3.1 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
fn main() {
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
|
|
// Only compile CUDA kernels when the cuda feature is enabled.
|
|
if std::env::var("CARGO_FEATURE_CUDA").is_err() {
|
|
return;
|
|
}
|
|
|
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
|
let kernel_dir = Path::new("src");
|
|
|
|
// Detect GPU architecture from env or default to sm_80 (matches crates/ml/build.rs).
|
|
println!("cargo:rerun-if-env-changed=CUDA_COMPUTE_CAP");
|
|
let cuda_compute_cap = std::env::var("CUDA_COMPUTE_CAP").unwrap_or_else(|_| "80".to_string());
|
|
let arch = format!("sm_{cuda_compute_cap}");
|
|
|
|
// Check if nvcc is available -- gracefully skip if not (non-CUDA builds).
|
|
let nvcc_path = find_nvcc();
|
|
let nvcc = match nvcc_path {
|
|
Some(p) => p,
|
|
None => {
|
|
eprintln!(" warning: nvcc not found, skipping CUDA kernel precompilation");
|
|
eprintln!(" Install CUDA toolkit or set CUDA_HOME for GPU builds");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let kernel_name = "ig_kernels.cu";
|
|
if !compile_kernel(&nvcc, kernel_dir, kernel_name, &arch, &out_dir) {
|
|
panic!("nvcc failed to compile {kernel_name}");
|
|
}
|
|
eprintln!(" Precompiled 1/1 CUDA kernels ({arch}) — f32");
|
|
}
|
|
|
|
/// Compile a single .cu kernel file to a .cubin via nvcc.
|
|
fn compile_kernel(
|
|
nvcc: &Path,
|
|
kernel_dir: &Path,
|
|
kernel_name: &str,
|
|
arch: &str,
|
|
out_dir: &Path,
|
|
) -> bool {
|
|
let kernel_path = kernel_dir.join(kernel_name);
|
|
let cubin_name = kernel_name.replace(".cu", ".cubin");
|
|
let cubin_path = out_dir.join(&cubin_name);
|
|
|
|
println!("cargo:rerun-if-changed={}", kernel_path.display());
|
|
|
|
// Compile with nvcc.
|
|
let status = Command::new(nvcc)
|
|
.args([
|
|
"-cubin",
|
|
&format!("-arch={arch}"),
|
|
"-O3",
|
|
"--ftz=true",
|
|
"--fmad=true",
|
|
"--prec-div=true",
|
|
"--prec-sqrt=true",
|
|
"-o", cubin_path.to_str().unwrap(),
|
|
kernel_path.to_str().unwrap(),
|
|
])
|
|
.status();
|
|
|
|
match status {
|
|
Ok(s) if s.success() => {
|
|
eprintln!(" Compiled {kernel_name} -> {cubin_name} ({arch})");
|
|
true
|
|
}
|
|
Ok(s) => {
|
|
eprintln!(" FAILED: {kernel_name} (exit={})", s.code().unwrap_or(-1));
|
|
false
|
|
}
|
|
Err(e) => {
|
|
eprintln!(" FAILED: {kernel_name} (nvcc error: {e})");
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Find nvcc: prefer $CUDA_HOME/bin/nvcc, then check common paths / $PATH.
|
|
fn find_nvcc() -> Option<PathBuf> {
|
|
if let Ok(home) = std::env::var("CUDA_HOME") {
|
|
let nvcc = PathBuf::from(home).join("bin/nvcc");
|
|
if nvcc.exists() {
|
|
return Some(nvcc);
|
|
}
|
|
}
|
|
|
|
for path in &["/usr/local/cuda/bin/nvcc", "/usr/bin/nvcc"] {
|
|
let p = PathBuf::from(path);
|
|
if p.exists() {
|
|
return Some(p);
|
|
}
|
|
}
|
|
|
|
match Command::new("nvcc").arg("--version").output() {
|
|
Ok(output) if output.status.success() => Some(PathBuf::from("nvcc")),
|
|
_ => None,
|
|
}
|
|
}
|