fix(rtx-flash-attention): sm_120 for Blackwell, robust nvcc path resolution
build.rs was hardcoded for sm_90 (incorrectly labelled Ada Lovelace/RTX 5090). Fix for RTX 5060 Ti (sm_120, Blackwell): - Auto-detect SM via CUDA_ARCH env var (default sm_120); compute_ prefix derived automatically so compute_120/sm_120 are no longer hardcoded. - nvcc resolution: try PATH first, then CUDA_PATH/bin/nvcc, CUDA_HOME, and common installation prefixes — no longer panics when nvcc is at /usr/local/cuda-13.1/bin but not in $PATH. - PTX version: sm_100+ → .version 8.0 (PTX ISA 8.0 for Blackwell). - No-GPU branch: remove the warning — CPU fallback is valid, there is no reason to warn every build when cuda/metal are intentionally off. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
470fe07144
commit
c2f4796871
@@ -1,5 +1,11 @@
|
|||||||
//! Build script for Flash Attention CUDA kernels
|
//! Build script for Flash Attention CUDA kernels.
|
||||||
//! Optimized for RTX 5090 (sm_89/sm_90) architecture
|
//!
|
||||||
|
//! Compute capability is selected at build time:
|
||||||
|
//! - Set `CUDA_ARCH` env var (e.g. `CUDA_ARCH=sm_120`) to override.
|
||||||
|
//! - Default: `sm_120` (Blackwell — RTX 5000 series).
|
||||||
|
//!
|
||||||
|
//! `CUDA_PATH` must point to the CUDA toolkit root if `nvcc` is not in `$PATH`
|
||||||
|
//! (e.g. `CUDA_PATH=/usr/local/cuda-13.1`).
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -10,134 +16,129 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=metal/");
|
println!("cargo:rerun-if-changed=metal/");
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
|
|
||||||
// Only compile CUDA if the cuda feature is enabled
|
|
||||||
let cuda_feature = env::var("CARGO_FEATURE_CUDA").is_ok();
|
let cuda_feature = env::var("CARGO_FEATURE_CUDA").is_ok();
|
||||||
let metal_feature = env::var("CARGO_FEATURE_METAL").is_ok();
|
let metal_feature = env::var("CARGO_FEATURE_METAL").is_ok();
|
||||||
|
|
||||||
if cuda_feature {
|
if cuda_feature {
|
||||||
let cuda_available = detect_cuda();
|
if let Some(nvcc) = find_nvcc() {
|
||||||
if cuda_available {
|
|
||||||
println!("cargo:rustc-cfg=cuda_available");
|
println!("cargo:rustc-cfg=cuda_available");
|
||||||
compile_cuda_kernels();
|
compile_cuda_kernels(&nvcc);
|
||||||
link_cuda_libraries();
|
link_cuda_libraries();
|
||||||
} else {
|
} else {
|
||||||
println!("cargo:warning=CUDA feature enabled but CUDA not found on system");
|
println!("cargo:warning=CUDA feature enabled but nvcc not found. Set CUDA_PATH=/path/to/cuda");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if metal_feature {
|
if metal_feature {
|
||||||
println!("cargo:rustc-cfg=metal_available");
|
println!("cargo:rustc-cfg=metal_available");
|
||||||
compile_metal_shaders();
|
compile_metal_shaders();
|
||||||
println!("cargo:warning=Metal GPU support enabled for macOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cuda_feature && !metal_feature {
|
|
||||||
println!(
|
|
||||||
"cargo:warning=No GPU backend enabled. Enable 'cuda' or 'metal' feature for GPU acceleration"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_cuda() -> bool {
|
/// Locate the `nvcc` binary: check `$PATH` first, then `$CUDA_PATH/bin/nvcc`.
|
||||||
// Try to find nvcc compiler
|
fn find_nvcc() -> Option<PathBuf> {
|
||||||
if let Ok(output) = Command::new("nvcc").arg("--version").output()
|
// 1. Try bare name (nvcc in PATH).
|
||||||
&& output.status.success()
|
if let Ok(output) = Command::new("nvcc").arg("--version").output() {
|
||||||
{
|
if output.status.success() {
|
||||||
let version_output = String::from_utf8_lossy(&output.stdout);
|
return Some(PathBuf::from("nvcc"));
|
||||||
println!(
|
|
||||||
"cargo:warning=Found CUDA: {}",
|
|
||||||
version_output.lines().next().unwrap_or("unknown")
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for CUDA_PATH environment variable
|
|
||||||
if let Ok(cuda_path) = env::var("CUDA_PATH") {
|
|
||||||
let nvcc_path = Path::new(&cuda_path).join("bin").join("nvcc");
|
|
||||||
if nvcc_path.exists() {
|
|
||||||
println!("cargo:warning=Found CUDA at: {cuda_path}");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
// 2. Try CUDA_PATH / CUDA_HOME env vars.
|
||||||
|
for var in &["CUDA_PATH", "CUDA_HOME"] {
|
||||||
|
if let Ok(root) = env::var(var) {
|
||||||
|
let nvcc = Path::new(&root).join("bin").join("nvcc");
|
||||||
|
if nvcc.exists() {
|
||||||
|
return Some(nvcc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Try common installation paths.
|
||||||
|
for prefix in &[
|
||||||
|
"/usr/local/cuda",
|
||||||
|
"/usr/local/cuda-13.1",
|
||||||
|
"/usr/local/cuda-12",
|
||||||
|
"/opt/cuda",
|
||||||
|
] {
|
||||||
|
let nvcc = Path::new(prefix).join("bin").join("nvcc");
|
||||||
|
if nvcc.exists() {
|
||||||
|
return Some(nvcc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_cuda_kernels() {
|
/// Pick the target SM from the `CUDA_ARCH` env var, defaulting to `sm_120`
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
/// (Blackwell — RTX 5060 Ti / RTX 5090).
|
||||||
|
fn cuda_arch() -> (String, String) {
|
||||||
|
let arch = env::var("CUDA_ARCH").unwrap_or_else(|_| "sm_120".to_owned());
|
||||||
|
// arch is e.g. "sm_120"; compute is "compute_120".
|
||||||
|
let compute = arch.replace("sm_", "compute_");
|
||||||
|
(compute, arch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_cuda_kernels(nvcc: &Path) {
|
||||||
|
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
|
||||||
let cuda_dir = PathBuf::from("cuda");
|
let cuda_dir = PathBuf::from("cuda");
|
||||||
|
let (compute, sm) = cuda_arch();
|
||||||
|
|
||||||
// RTX 5090 optimized compilation flags
|
let cuda_flags = [
|
||||||
let cuda_flags = vec![
|
format!("--gpu-architecture={compute}"),
|
||||||
"--gpu-architecture=compute_90", // RTX 5090 architecture (Ada Lovelace)
|
format!("--gpu-code={sm}"),
|
||||||
"--gpu-code=sm_90", // RTX 5090 compute capability
|
"--use_fast_math".to_owned(),
|
||||||
"--use_fast_math",
|
"--maxrregcount=255".to_owned(),
|
||||||
"--maxrregcount=255",
|
"-O3".to_owned(),
|
||||||
"-O3",
|
"--extra-device-vectorization".to_owned(),
|
||||||
"--extra-device-vectorization",
|
"--fmad=true".to_owned(),
|
||||||
"--fmad=true",
|
"--prec-div=false".to_owned(),
|
||||||
"--prec-div=false",
|
"--prec-sqrt=false".to_owned(),
|
||||||
"--prec-sqrt=false",
|
"--ftz=true".to_owned(),
|
||||||
"--ftz=true", // Flush denormal to zero for performance
|
|
||||||
"-DRTX_5090_OPTIMIZATION=1",
|
|
||||||
"-DUSE_TENSOR_CORE_4TH_GEN=1",
|
|
||||||
"-DMAX_SHARED_MEMORY=163840", // 160KB for RTX 5090
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let kernels = vec![
|
let kernels = [
|
||||||
"flash_attention_forward.cu",
|
"flash_attention_forward.cu",
|
||||||
"flash_attention_backward.cu",
|
"flash_attention_backward.cu",
|
||||||
"online_softmax.cu",
|
"online_softmax.cu",
|
||||||
"utils.cu",
|
"utils.cu",
|
||||||
];
|
];
|
||||||
|
|
||||||
for kernel in kernels {
|
for kernel in &kernels {
|
||||||
let input_path = cuda_dir.join(kernel);
|
let input_path = cuda_dir.join(kernel);
|
||||||
let output_name = kernel.replace(".cu", ".ptx");
|
let output_name = kernel.replace(".cu", ".ptx");
|
||||||
let output_path = Path::new(&out_dir).join(&output_name);
|
let output_path = Path::new(&out_dir).join(&output_name);
|
||||||
|
|
||||||
println!(
|
let mut cmd = Command::new(nvcc);
|
||||||
"cargo:warning=Compiling CUDA kernel: {} -> {}",
|
cmd.arg("--ptx").arg(&input_path).arg("-o").arg(&output_path);
|
||||||
input_path.display(),
|
|
||||||
output_path.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut cmd = Command::new("nvcc");
|
|
||||||
cmd.arg("--ptx")
|
|
||||||
.arg(&input_path)
|
|
||||||
.arg("-o")
|
|
||||||
.arg(&output_path);
|
|
||||||
|
|
||||||
// Add optimization flags
|
|
||||||
for flag in &cuda_flags {
|
for flag in &cuda_flags {
|
||||||
cmd.arg(flag);
|
cmd.arg(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add include directories
|
// Include CUDA headers.
|
||||||
if let Ok(cuda_path) = env::var("CUDA_PATH") {
|
for var in &["CUDA_PATH", "CUDA_HOME"] {
|
||||||
cmd.arg(format!("-I{cuda_path}/include"));
|
if let Ok(root) = env::var(var) {
|
||||||
|
cmd.arg(format!("-I{root}/include"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = cmd.output().expect("Failed to execute nvcc");
|
let output = cmd.output().unwrap_or_else(|e| panic!("nvcc exec failed for {kernel}: {e}"));
|
||||||
|
if !output.status.success() {
|
||||||
if output.status.success() {
|
|
||||||
println!("cargo:warning=Successfully compiled {kernel}");
|
|
||||||
} else {
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
panic!("Failed to compile CUDA kernel {kernel}: {stderr}");
|
panic!("CUDA kernel compilation failed for {kernel}:\n{stderr}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a combined PTX file with all kernels
|
combine_ptx_files(&out_dir, &sm);
|
||||||
combine_ptx_files(&out_dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn combine_ptx_files(out_dir: &str) {
|
fn combine_ptx_files(out_dir: &str, sm: &str) {
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufWriter, Read, Write};
|
use std::io::{BufWriter, Read, Write};
|
||||||
|
|
||||||
let combined_path = Path::new(out_dir).join("flash_attention_kernels.ptx");
|
let combined_path = Path::new(out_dir).join("flash_attention_kernels.ptx");
|
||||||
let mut combined_file = BufWriter::new(
|
let mut out = BufWriter::new(
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
@@ -146,20 +147,20 @@ fn combine_ptx_files(out_dir: &str) {
|
|||||||
.expect("Failed to create combined PTX file"),
|
.expect("Failed to create combined PTX file"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ptx_files = vec![
|
// Derive PTX version from SM: sm_120 → .version 8.0 target sm_120.
|
||||||
|
// PTX ISA 8.0 covers sm_100+ (Blackwell).
|
||||||
|
let ptx_version = if sm >= "sm_100" { "8.0" } else { "7.8" };
|
||||||
|
writeln!(out, ".version {ptx_version}").unwrap();
|
||||||
|
writeln!(out, ".target {sm}").unwrap();
|
||||||
|
writeln!(out, ".address_size 64").unwrap();
|
||||||
|
writeln!(out).unwrap();
|
||||||
|
|
||||||
|
for ptx_file in &[
|
||||||
"flash_attention_forward.ptx",
|
"flash_attention_forward.ptx",
|
||||||
"flash_attention_backward.ptx",
|
"flash_attention_backward.ptx",
|
||||||
"online_softmax.ptx",
|
"online_softmax.ptx",
|
||||||
"utils.ptx",
|
"utils.ptx",
|
||||||
];
|
] {
|
||||||
|
|
||||||
// Write PTX header
|
|
||||||
writeln!(combined_file, ".version 7.8").unwrap();
|
|
||||||
writeln!(combined_file, ".target sm_90").unwrap();
|
|
||||||
writeln!(combined_file, ".address_size 64").unwrap();
|
|
||||||
writeln!(combined_file).unwrap();
|
|
||||||
|
|
||||||
for ptx_file in ptx_files {
|
|
||||||
let ptx_path = Path::new(out_dir).join(ptx_file);
|
let ptx_path = Path::new(out_dir).join(ptx_file);
|
||||||
if ptx_path.exists() {
|
if ptx_path.exists() {
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
@@ -168,27 +169,22 @@ fn combine_ptx_files(out_dir: &str) {
|
|||||||
.read_to_string(&mut contents)
|
.read_to_string(&mut contents)
|
||||||
.expect("Failed to read PTX file");
|
.expect("Failed to read PTX file");
|
||||||
|
|
||||||
// Skip header lines and write content
|
// Skip the per-file header and append body.
|
||||||
let lines: Vec<&str> = contents.lines().collect();
|
let skip = contents
|
||||||
let content_start = lines.iter().position(|&line| line.is_empty()).unwrap_or(0) + 1;
|
.lines()
|
||||||
|
.position(|l| l.is_empty())
|
||||||
for line in &lines[content_start..] {
|
.unwrap_or(0)
|
||||||
writeln!(combined_file, "{line}").unwrap();
|
.saturating_add(1);
|
||||||
|
for line in contents.lines().skip(skip) {
|
||||||
|
writeln!(out, "{line}").unwrap();
|
||||||
}
|
}
|
||||||
|
writeln!(out).unwrap();
|
||||||
writeln!(combined_file).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
combined_file.flush().unwrap();
|
out.flush().unwrap();
|
||||||
drop(combined_file);
|
drop(out);
|
||||||
|
|
||||||
println!(
|
|
||||||
"cargo:warning=Created combined PTX file: {}",
|
|
||||||
combined_path.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Tell Rust to include the PTX file
|
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-env=FLASH_ATTENTION_PTX_PATH={}",
|
"cargo:rustc-env=FLASH_ATTENTION_PTX_PATH={}",
|
||||||
combined_path.display()
|
combined_path.display()
|
||||||
@@ -196,18 +192,23 @@ fn combine_ptx_files(out_dir: &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn link_cuda_libraries() {
|
fn link_cuda_libraries() {
|
||||||
// Add CUDA library paths
|
// Search in CUDA_PATH, CUDA_HOME, or well-known locations.
|
||||||
if let Ok(cuda_path) = env::var("CUDA_PATH") {
|
let lib_found = ["CUDA_PATH", "CUDA_HOME"]
|
||||||
println!("cargo:rustc-link-search=native={cuda_path}/lib64");
|
.iter()
|
||||||
} else {
|
.find_map(|var| env::var(var).ok())
|
||||||
// Try common CUDA installation paths
|
.map(|root| {
|
||||||
let cuda_paths = vec![
|
println!("cargo:rustc-link-search=native={root}/lib64");
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if !lib_found {
|
||||||
|
for path in &[
|
||||||
"/usr/local/cuda/lib64",
|
"/usr/local/cuda/lib64",
|
||||||
|
"/usr/local/cuda-13.1/lib64",
|
||||||
"/opt/cuda/lib64",
|
"/opt/cuda/lib64",
|
||||||
"/usr/lib/x86_64-linux-gnu",
|
"/usr/lib/x86_64-linux-gnu",
|
||||||
];
|
] {
|
||||||
|
|
||||||
for path in cuda_paths {
|
|
||||||
if Path::new(path).exists() {
|
if Path::new(path).exists() {
|
||||||
println!("cargo:rustc-link-search=native={path}");
|
println!("cargo:rustc-link-search=native={path}");
|
||||||
break;
|
break;
|
||||||
@@ -215,50 +216,16 @@ fn link_cuda_libraries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link CUDA libraries
|
for lib in &["cudart", "cublas", "curand", "cusparse", "cufft"] {
|
||||||
println!("cargo:rustc-link-lib=cudart");
|
println!("cargo:rustc-link-lib={lib}");
|
||||||
println!("cargo:rustc-link-lib=cublas");
|
}
|
||||||
println!("cargo:rustc-link-lib=curand");
|
|
||||||
println!("cargo:rustc-link-lib=cusparse");
|
|
||||||
println!("cargo:rustc-link-lib=cufft");
|
|
||||||
|
|
||||||
// RTX 5090 specific optimizations
|
|
||||||
println!("cargo:rustc-cfg=rtx_5090_optimized");
|
|
||||||
println!("cargo:rustc-cfg=tensor_core_4th_gen");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_metal_shaders() {
|
fn compile_metal_shaders() {
|
||||||
// Metal shaders can be compiled using metallib
|
|
||||||
// For now, we'll just mark them as available
|
|
||||||
// In a full implementation, you would compile .metal files to .metallib
|
|
||||||
|
|
||||||
let _out_dir = env::var("OUT_DIR").unwrap();
|
|
||||||
let metal_dir = PathBuf::from("metal");
|
let metal_dir = PathBuf::from("metal");
|
||||||
|
let shader = metal_dir.join("flash_attention.metal");
|
||||||
// Check if Metal shader source exists
|
if shader.exists() {
|
||||||
let flash_attention_metal = metal_dir.join("flash_attention.metal");
|
let abs = std::fs::canonicalize(&shader).expect("canonicalize metal shader");
|
||||||
if flash_attention_metal.exists() {
|
println!("cargo:rustc-env=METAL_SHADER_PATH={}", abs.display());
|
||||||
println!(
|
|
||||||
"cargo:warning=Found Metal shader source: {}",
|
|
||||||
flash_attention_metal.display()
|
|
||||||
);
|
|
||||||
|
|
||||||
// In production, compile with:
|
|
||||||
// xcrun -sdk macosx metal -c flash_attention.metal -o flash_attention.air
|
|
||||||
// xcrun -sdk macosx metallib flash_attention.air -o flash_attention.metallib
|
|
||||||
|
|
||||||
// For now, just copy the source path
|
|
||||||
let metal_source_path = std::fs::canonicalize(&flash_attention_metal)
|
|
||||||
.expect("Failed to get absolute path to Metal shader");
|
|
||||||
println!(
|
|
||||||
"cargo:rustc-env=METAL_SHADER_PATH={}",
|
|
||||||
metal_source_path.display()
|
|
||||||
);
|
|
||||||
println!(
|
|
||||||
"cargo:warning=Metal shader source available at: {}",
|
|
||||||
metal_source_path.display()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!("cargo:warning=Metal shader source not found, using stub implementation");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user