makepad/libs/ggml/build.rs
2026-04-20 13:48:29 +02:00

309 lines
10 KiB
Rust

use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
println!("cargo:rerun-if-env-changed=MAKEPAD_GGML_METAL_PRECOMPILE");
println!("cargo:rerun-if-env-changed=MAKEPAD_GGML_CUDA_ARCH");
println!("cargo:rerun-if-env-changed=CUDA_HOME");
println!("cargo:rerun-if-env-changed=CUDA_PATH");
println!("cargo:rustc-check-cfg=cfg(makepad_ggml_cuda_kernels)");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "macos" {
build_metallib();
}
if target_os == "linux" || target_os == "windows" {
build_cuda_backends(&target_os);
}
}
fn build_metallib() {
let precompile_default = env::var_os("CARGO_FEATURE_METAL_PRECOMPILE").is_some();
let precompile_enabled = env::var("MAKEPAD_GGML_METAL_PRECOMPILE")
.ok()
.map(|v| {
let v = v.trim().to_ascii_lowercase();
!(v.is_empty() || v == "0" || v == "false" || v == "no" || v == "off")
})
.unwrap_or(precompile_default);
let out_dir = env::var("OUT_DIR").unwrap();
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let metal_dir = format!("{}/src/backend/metal/ggml", manifest_dir);
let metal_src = format!("{}/ggml-metal.metal", metal_dir);
let common_h = format!("{}/ggml-common.h", metal_dir);
let impl_h = format!("{}/ggml-metal-impl.h", metal_dir);
println!("cargo:rerun-if-changed={}", metal_src);
println!("cargo:rerun-if-changed={}", common_h);
println!("cargo:rerun-if-changed={}", impl_h);
let _ = fs::create_dir_all(&out_dir);
let air_path = format!("{}/ggml-metal.air", out_dir);
let metallib_path = format!("{}/ggml-default.metallib", out_dir);
if !precompile_enabled {
let _ = fs::write(&metallib_path, []);
println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path);
return;
}
let metal_status = Command::new("xcrun")
.args([
"--sdk",
"macosx",
"metal",
"-O3",
"-fno-fast-math",
"-c",
&metal_src,
"-I",
&metal_dir,
"-o",
&air_path,
])
.status();
let ok = metal_status.as_ref().is_ok_and(|s| s.success());
if !ok {
println!(
"cargo:warning=failed to compile ggml-metal.metal to AIR; runtime source compile will be used"
);
let _ = fs::write(&metallib_path, []);
println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path);
return;
}
let metallib_status = Command::new("xcrun")
.args([
"--sdk",
"macosx",
"metallib",
&air_path,
"-o",
&metallib_path,
])
.status();
let ok = metallib_status.as_ref().is_ok_and(|s| s.success());
if !ok {
println!(
"cargo:warning=failed to build ggml default metallib; runtime source compile will be used"
);
let _ = fs::write(&metallib_path, []);
}
println!("cargo:rustc-env=MAKEPAD_GGML_METALLIB={}", metallib_path);
}
fn build_cuda_backends(target_os: &str) {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let src_paths = [
manifest_dir.join("src/backend/cuda/affine.cu"),
manifest_dir.join("src/backend/cuda/gated_delta_net.cu"),
manifest_dir.join("src/backend/cuda/nvfp4.cu"),
manifest_dir.join("src/backend/cuda/nvfp4_mmq.cu"),
manifest_dir.join("src/backend/cuda/ops.cu"),
manifest_dir.join("src/backend/cuda/qwen_ops.cu"),
manifest_dir.join("src/backend/cuda/ssm_conv.cu"),
];
for src_path in &src_paths {
println!("cargo:rerun-if-changed={}", src_path.display());
}
let Some(cuda_root) = cuda_root(target_os) else {
println!("cargo:warning=CUDA toolkit root not found; CUDA backends disabled");
return;
};
let nvcc = if target_os == "windows" {
cuda_root.join("bin").join("nvcc.exe")
} else {
cuda_root.join("bin").join("nvcc")
};
if !nvcc.exists() {
println!(
"cargo:warning=CUDA nvcc not found at {}; CUDA backends disabled",
nvcc.display()
);
return;
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let lib_path = if target_os == "windows" {
out_dir.join("ggml_cuda_affine.lib")
} else {
out_dir.join("libggml_cuda_affine.a")
};
let obj_ext = if target_os == "windows" { "obj" } else { "o" };
let arch = env::var("MAKEPAD_GGML_CUDA_ARCH").unwrap_or_else(|_| "120a".to_string());
let include_dir = cuda_root.join("include");
let msvc_bin_dir = if target_os == "windows" {
find_msvc_tool("cl.exe").and_then(|path| path.parent().map(Path::to_path_buf))
} else {
None
};
let lib_exe = if target_os == "windows" {
match find_msvc_tool("lib.exe") {
Some(path) => Some(path),
None => {
println!("cargo:warning=MSVC lib.exe not found; CUDA backends disabled");
return;
}
}
} else {
None
};
let mut obj_paths = Vec::new();
for src_path in &src_paths {
let stem = src_path.file_stem().unwrap().to_string_lossy();
let obj_path = out_dir.join(format!("ggml_cuda_{stem}.{obj_ext}"));
let arch_flag = format!("arch=compute_{arch},code=sm_{arch}");
let mut command = Command::new(&nvcc);
command.args(["-std=c++17", "-O3"]);
if target_os == "windows" {
if let Some(msvc_bin_dir) = &msvc_bin_dir {
command.arg("-ccbin").arg(msvc_bin_dir);
}
command.args(["-Xcompiler", "/EHsc"]);
command.args(["-Xcompiler", "/MD"]);
} else {
command.args(["-Xcompiler", "-fPIC"]);
}
let status = command
.args([
"-c",
"-I",
include_dir.to_string_lossy().as_ref(),
"-gencode",
arch_flag.as_str(),
"-o",
obj_path.to_string_lossy().as_ref(),
src_path.to_string_lossy().as_ref(),
])
.status();
let ok = status.as_ref().is_ok_and(|s| s.success());
if !ok {
println!(
"cargo:warning=failed to compile CUDA backend source {}; CUDA path disabled",
src_path.display()
);
return;
}
obj_paths.push(obj_path);
}
let archive_ok = if target_os == "windows" {
let mut lib = Command::new(lib_exe.unwrap());
lib.arg("/NOLOGO")
.arg(format!("/OUT:{}", lib_path.to_string_lossy()));
for obj_path in &obj_paths {
lib.arg(obj_path);
}
lib.status().as_ref().is_ok_and(|s| s.success())
} else {
let mut ar = Command::new("ar");
ar.arg("crus").arg(lib_path.to_string_lossy().as_ref());
for obj_path in &obj_paths {
ar.arg(obj_path.to_string_lossy().as_ref());
}
ar.status().as_ref().is_ok_and(|s| s.success())
};
if !archive_ok {
println!("cargo:warning=failed to archive CUDA backends; CUDA path disabled");
return;
}
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=ggml_cuda_affine");
if target_os == "linux" {
println!("cargo:rustc-link-lib=dylib=stdc++");
}
println!("cargo:rustc-cfg=makepad_ggml_cuda_kernels");
}
fn cuda_root(target_os: &str) -> Option<PathBuf> {
env::var_os("CUDA_HOME")
.or_else(|| env::var_os("CUDA_PATH"))
.map(PathBuf::from)
.filter(|path| path.exists())
.or_else(|| {
if target_os == "windows" {
latest_windows_cuda_root()
} else {
let default = Path::new("/usr/local/cuda");
default.exists().then(|| default.to_path_buf())
}
})
}
fn latest_windows_cuda_root() -> Option<PathBuf> {
let cuda_root = env::var_os("ProgramFiles")
.map(PathBuf::from)
.map(|program_files| {
program_files
.join("NVIDIA GPU Computing Toolkit")
.join("CUDA")
})?;
let mut entries = fs::read_dir(cuda_root)
.ok()?
.filter_map(|entry| entry.ok())
.filter(|entry| entry.file_type().ok().is_some_and(|ty| ty.is_dir()))
.collect::<Vec<_>>();
entries.sort_by_key(|entry| entry.file_name());
entries.pop().map(|entry| entry.path())
}
fn find_msvc_tool(tool_name: &str) -> Option<PathBuf> {
if let Some(paths) = env::var_os("PATH") {
if let Some(path) = env::split_paths(&paths)
.map(|path| path.join(tool_name))
.find(|candidate| candidate.exists())
{
return Some(path);
}
}
let find_pattern = format!(r"VC\Tools\MSVC\**\bin\Hostx64\x64\{tool_name}");
for installer_root in [
r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe",
r"C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe",
] {
let vswhere = Path::new(installer_root);
if !vswhere.exists() {
continue;
}
let output = match Command::new(vswhere)
.args([
"-latest",
"-products",
"*",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-find",
find_pattern.as_str(),
])
.output()
{
Ok(output) => output,
Err(_) => continue,
};
if !output.status.success() {
continue;
}
if let Some(path) = String::from_utf8_lossy(&output.stdout)
.lines()
.map(str::trim)
.find(|line| !line.is_empty())
.map(PathBuf::from)
.filter(|path| path.exists())
{
return Some(path);
}
}
None
}