use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
#[derive(Debug, Clone, Default)]
pub struct JitCompilationConfig {
pub enabled: bool,
pub cache_enabled: bool,
pub cache_dir: Option<PathBuf>,
pub optimization_level: u8,
pub cuda_jit: bool,
pub cuda_cache_size: usize,
pub cuda_max_registers: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct CustomOpDefinition {
pub name: String,
pub op_type: CustomOpType,
pub input_shapes: Vec<Option<Vec<usize>>>,
pub output_shapes: Vec<Option<Vec<usize>>>,
pub cpu_source: Option<String>,
pub cuda_source: Option<String>,
pub compile_flags: Vec<String>,
pub schema: OpSchema,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CustomOpType {
Forward,
Backward,
ForwardBackward,
}
#[derive(Debug, Clone, Default)]
pub struct OpSchema {
pub input_types: Vec<TensorType>,
pub output_types: Vec<TensorType>,
pub is_elementwise: bool,
pub is_deterministic: bool,
pub memory_requirement: MemoryRequirement,
}
#[derive(Debug, Clone)]
pub struct TensorType {
pub dtype: String,
pub min_dims: usize,
pub max_dims: Option<usize>,
pub supports_sparse: bool,
}
#[derive(Debug, Clone, Default)]
pub enum MemoryRequirement {
#[default]
Unknown,
Constant,
Linear,
Quadratic,
Custom(String),
}
#[derive(Debug, Clone, Default)]
pub struct CrossPlatformConfig {
pub target_platforms: Vec<TargetPlatform>,
pub windows: WindowsConfig,
pub macos: MacOsConfig,
pub linux: LinuxConfig,
pub cross_compile: bool,
pub use_docker: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TargetPlatform {
WindowsX64,
WindowsX86,
MacOsX64,
MacOsArm64,
LinuxX64,
LinuxArm64,
LinuxAarch64,
}
#[derive(Debug, Clone, Default)]
pub struct WindowsConfig {
pub vs_version: Option<String>,
pub sdk_version: Option<String>,
pub use_clang: bool,
pub enable_simd: bool,
}
#[derive(Debug, Clone, Default)]
pub struct MacOsConfig {
pub min_version: Option<String>,
pub xcode_version: Option<String>,
pub enable_mps: bool,
pub universal_binary: bool,
}
#[derive(Debug, Clone, Default)]
pub struct LinuxConfig {
pub compiler_preference: CompilerPreference,
pub enable_mkl: bool,
pub enable_openmp: bool,
pub distro_packages: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum CompilerPreference {
#[default]
Auto,
Gcc,
Clang,
Intel,
}
#[derive(Debug, Clone)]
pub struct CppExtensionConfig {
pub name: String,
pub sources: Vec<PathBuf>,
pub include_dirs: Vec<PathBuf>,
pub library_dirs: Vec<PathBuf>,
pub libraries: Vec<String>,
pub extra_compile_args: Vec<String>,
pub extra_link_args: Vec<String>,
pub with_cuda: bool,
pub cuda_archs: Vec<String>,
pub debug: bool,
pub build_dir: PathBuf,
pub jit_config: JitCompilationConfig,
pub custom_ops: Vec<CustomOpDefinition>,
pub cross_platform: CrossPlatformConfig,
}
impl CppExtensionConfig {
pub fn new(name: impl Into<String>, sources: Vec<PathBuf>) -> Self {
let name = name.into();
let build_dir = env::temp_dir().join("torsh_cpp_extensions").join(&name);
Self {
name,
sources,
include_dirs: vec![],
library_dirs: vec![],
libraries: vec![],
extra_compile_args: vec![],
extra_link_args: vec![],
with_cuda: false,
cuda_archs: vec![
"sm_70".to_string(),
"sm_75".to_string(),
"sm_80".to_string(),
"sm_86".to_string(),
"sm_89".to_string(),
],
debug: false,
build_dir,
jit_config: JitCompilationConfig::default(),
custom_ops: vec![],
cross_platform: CrossPlatformConfig::default(),
}
}
pub fn include_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.include_dirs.push(dir.as_ref().to_path_buf());
self
}
pub fn library_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.library_dirs.push(dir.as_ref().to_path_buf());
self
}
pub fn library(mut self, lib: impl Into<String>) -> Self {
self.libraries.push(lib.into());
self
}
pub fn extra_compile_arg(mut self, arg: impl Into<String>) -> Self {
self.extra_compile_args.push(arg.into());
self
}
pub fn extra_link_arg(mut self, arg: impl Into<String>) -> Self {
self.extra_link_args.push(arg.into());
self
}
pub fn cuda(mut self, cuda_archs: Vec<String>) -> Self {
self.with_cuda = true;
self.cuda_archs = cuda_archs;
self
}
pub fn debug(mut self) -> Self {
self.debug = true;
self
}
pub fn build_dir(mut self, dir: impl AsRef<Path>) -> Self {
self.build_dir = dir.as_ref().to_path_buf();
self
}
pub fn jit(mut self, config: JitCompilationConfig) -> Self {
self.jit_config = config;
self
}
pub fn custom_op(mut self, op: CustomOpDefinition) -> Self {
self.custom_ops.push(op);
self
}
pub fn cross_platform(mut self, config: CrossPlatformConfig) -> Self {
self.cross_platform = config;
self
}
pub fn enable_jit(mut self) -> Self {
self.jit_config.enabled = true;
self.jit_config.cache_enabled = true;
self.jit_config.optimization_level = 2;
self
}
pub fn enable_cuda_jit(mut self) -> Self {
self.jit_config.cuda_jit = true;
self.jit_config.cuda_cache_size = 256; self
}
}
#[derive(Debug)]
pub struct BuildResult {
pub library_path: PathBuf,
pub include_dirs: Vec<PathBuf>,
pub jit_info: Option<JitBuildInfo>,
pub compiled_ops: Vec<String>,
pub platform_artifacts: HashMap<TargetPlatform, PathBuf>,
}
#[derive(Debug)]
pub struct JitBuildInfo {
pub cache_dir: PathBuf,
pub kernel_count: usize,
pub cuda_info: Option<CudaJitInfo>,
}
#[derive(Debug)]
pub struct CudaJitInfo {
pub ptx_cache_size: usize,
pub kernel_count: usize,
pub compute_capability: Vec<String>,
pub cache_hits: usize,
pub cache_misses: usize,
pub compilation_time_ms: f64,
}
#[derive(Debug, Clone)]
pub struct CudaDeviceInfo {
pub device_id: u32,
pub name: String,
pub compute_capability: String,
pub total_memory: usize,
pub max_threads_per_block: u32,
pub max_grid_size: [u32; 3],
pub max_block_size: [u32; 3],
pub warp_size: u32,
pub multiprocessor_count: u32,
pub shared_memory_per_block: usize,
}
#[derive(Debug, Clone)]
pub struct CudaKernelCompilationOptions {
pub optimization_level: u8,
pub fast_math: bool,
pub max_registers: Option<u32>,
pub use_cache: bool,
pub debug_info: bool,
pub target_arch: Option<String>,
pub custom_flags: Vec<String>,
}
impl Default for CudaKernelCompilationOptions {
fn default() -> Self {
Self {
optimization_level: 2,
fast_math: false,
max_registers: None,
use_cache: true,
debug_info: false,
target_arch: None,
custom_flags: vec![],
}
}
}
#[derive(Debug)]
pub struct RuntimeCudaKernel {
pub name: String,
pub ptx_source: String,
pub module_handle: Option<usize>,
pub function_handle: Option<usize>,
pub compilation_options: CudaKernelCompilationOptions,
pub launch_config: CudaLaunchConfig,
}
#[derive(Debug, Clone)]
pub struct CudaLaunchConfig {
pub grid_size: [u32; 3],
pub block_size: [u32; 3],
pub shared_memory_size: usize,
pub stream: Option<usize>,
}
pub fn build_cpp_extension(config: &CppExtensionConfig) -> Result<BuildResult, String> {
fs::create_dir_all(&config.build_dir)
.map_err(|e| format!("Failed to create build directory: {}", e))?;
let jit_info = if config.jit_config.enabled {
Some(setup_jit_compilation(config)?)
} else {
None
};
let mut generated_sources = vec![];
let mut compiled_ops = vec![];
for custom_op in &config.custom_ops {
let generated_source = generate_custom_op_source(custom_op)?;
generated_sources.push(generated_source);
compiled_ops.push(custom_op.name.clone());
}
let mut platform_artifacts = HashMap::new();
if config.cross_platform.target_platforms.is_empty() {
let artifact = build_for_platform(config, None, &generated_sources, &jit_info)?;
platform_artifacts.insert(detect_current_platform(), artifact);
} else {
for platform in &config.cross_platform.target_platforms {
let artifact =
build_for_platform(config, Some(platform), &generated_sources, &jit_info)?;
platform_artifacts.insert(platform.clone(), artifact);
}
}
let main_artifact = platform_artifacts
.get(&detect_current_platform())
.or_else(|| platform_artifacts.values().next())
.ok_or("No artifacts built")?
.clone();
Ok(BuildResult {
library_path: main_artifact,
include_dirs: config.include_dirs.clone(),
jit_info,
compiled_ops,
platform_artifacts,
})
}
fn setup_jit_compilation(config: &CppExtensionConfig) -> Result<JitBuildInfo, String> {
let cache_dir = config
.jit_config
.cache_dir
.clone()
.unwrap_or_else(|| config.build_dir.join("jit_cache"));
fs::create_dir_all(&cache_dir)
.map_err(|e| format!("Failed to create JIT cache directory: {}", e))?;
let cuda_info = if config.jit_config.cuda_jit && config.with_cuda {
Some(setup_cuda_jit(config, &cache_dir)?)
} else {
None
};
Ok(JitBuildInfo {
cache_dir,
kernel_count: config.custom_ops.len(),
cuda_info,
})
}
fn setup_cuda_jit(config: &CppExtensionConfig, cache_dir: &Path) -> Result<CudaJitInfo, String> {
let cuda_cache_dir = cache_dir.join("cuda");
fs::create_dir_all(&cuda_cache_dir)
.map_err(|e| format!("Failed to create CUDA cache directory: {}", e))?;
let device_info = query_cuda_devices()?;
let available_archs = device_info
.iter()
.map(|dev| format!("sm_{}", dev.compute_capability.replace(".", "")))
.collect::<Vec<_>>();
let ptx_cache_dir = cuda_cache_dir.join("ptx");
let cubin_cache_dir = cuda_cache_dir.join("cubin");
fs::create_dir_all(&ptx_cache_dir)
.map_err(|e| format!("Failed to create PTX cache directory: {}", e))?;
fs::create_dir_all(&cubin_cache_dir)
.map_err(|e| format!("Failed to create CUBIN cache directory: {}", e))?;
configure_cuda_jit_options(config)?;
for op in &config.custom_ops {
if let Some(cuda_source) = &op.cuda_source {
validate_cuda_kernel_syntax(cuda_source, &op.name)?;
}
}
Ok(CudaJitInfo {
ptx_cache_size: config.jit_config.cuda_cache_size * 1024 * 1024, kernel_count: config
.custom_ops
.iter()
.filter(|op| op.cuda_source.is_some())
.count(),
compute_capability: available_archs,
cache_hits: 0,
cache_misses: 0,
compilation_time_ms: 0.0,
})
}
fn generate_custom_op_source(op: &CustomOpDefinition) -> Result<PathBuf, String> {
let source_content = match &op.op_type {
CustomOpType::Forward => generate_forward_op(&op.name, &op.cpu_source, &op.cuda_source)?,
CustomOpType::Backward => generate_backward_op(&op.name, &op.cpu_source, &op.cuda_source)?,
CustomOpType::ForwardBackward => {
generate_forward_backward_op(&op.name, &op.cpu_source, &op.cuda_source)?
}
};
let temp_file = env::temp_dir().join(format!("{}_custom_op.cpp", op.name));
fs::write(&temp_file, source_content)
.map_err(|e| format!("Failed to write custom op source: {}", e))?;
Ok(temp_file)
}
fn generate_forward_op(
name: &str,
cpu_source: &Option<String>,
cuda_source: &Option<String>,
) -> Result<String, String> {
let mut source = format!(
r#"// Generated custom operation: {}
#include <torsh/tensor.h>
#include <torsh/autograd.h>
namespace torsh {{
namespace ops {{
"#,
name
);
if let Some(cpu_impl) = cpu_source {
source.push_str(&format!(
r#"
// CPU implementation
Tensor {}_cpu_forward(const std::vector<Tensor>& inputs) {{
{}
}}
"#,
name, cpu_impl
));
}
if let Some(cuda_impl) = cuda_source {
source.push_str(&format!(
r#"
#ifdef TORSH_USE_CUDA
// CUDA implementation
Tensor {}_cuda_forward(const std::vector<Tensor>& inputs) {{
{}
}}
#endif
"#,
name, cuda_impl
));
}
source.push_str(&format!(
r#"
// Operation dispatcher
Tensor {}_forward(const std::vector<Tensor>& inputs) {{
#ifdef TORSH_USE_CUDA
if (inputs[0].is_cuda()) {{
return {}_cuda_forward(inputs);
}}
#endif
return {}_cpu_forward(inputs);
}}
// Register operation
TORSH_REGISTER_OP("{}", {}_forward);
}} // namespace ops
}} // namespace torsh
"#,
name, name, name, name, name
));
Ok(source)
}
fn generate_backward_op(
name: &str,
cpu_source: &Option<String>,
cuda_source: &Option<String>,
) -> Result<String, String> {
let mut source = format!(
r#"// Generated custom backward operation: {}
#include <torsh/tensor.h>
#include <torsh/autograd.h>
namespace torsh {{
namespace ops {{
"#,
name
);
if let Some(cpu_impl) = cpu_source {
source.push_str(&format!(
r#"
std::vector<Tensor> {}_cpu_backward(const std::vector<Tensor>& grad_outputs, const std::vector<Tensor>& inputs) {{
{}
}}
"#,
name, cpu_impl
));
}
if let Some(cuda_impl) = cuda_source {
source.push_str(&format!(
r#"
#ifdef TORSH_USE_CUDA
std::vector<Tensor> {}_cuda_backward(const std::vector<Tensor>& grad_outputs, const std::vector<Tensor>& inputs) {{
{}
}}
#endif
"#,
name, cuda_impl
));
}
source.push_str(&format!(
r#"
std::vector<Tensor> {}_backward(const std::vector<Tensor>& grad_outputs, const std::vector<Tensor>& inputs) {{
#ifdef TORSH_USE_CUDA
if (inputs[0].is_cuda()) {{
return {}_cuda_backward(grad_outputs, inputs);
}}
#endif
return {}_cpu_backward(grad_outputs, inputs);
}}
TORSH_REGISTER_BACKWARD_OP("{}", {}_backward);
}} // namespace ops
}} // namespace torsh
"#,
name, name, name, name, name
));
Ok(source)
}
fn generate_forward_backward_op(
name: &str,
cpu_source: &Option<String>,
cuda_source: &Option<String>,
) -> Result<String, String> {
let forward_source = generate_forward_op(name, cpu_source, cuda_source)?;
let backward_source =
generate_backward_op(&format!("{}_backward", name), cpu_source, cuda_source)?;
Ok(format!("{}\n\n{}", forward_source, backward_source))
}
fn build_for_platform(
config: &CppExtensionConfig,
target_platform: Option<&TargetPlatform>,
generated_sources: &[PathBuf],
_jit_info: &Option<JitBuildInfo>,
) -> Result<PathBuf, String> {
let (compiler, extra_flags) =
match target_platform {
Some(TargetPlatform::WindowsX64) | Some(TargetPlatform::WindowsX86) => {
if config.cross_platform.windows.use_clang {
(
"clang++".to_string(),
vec![
"-target".to_string(),
get_windows_target(target_platform.expect(
"target_platform should be Some for Windows platform branch",
)),
],
)
} else {
("cl.exe".to_string(), vec!["/std:c++17".to_string()])
}
}
Some(TargetPlatform::MacOsX64) | Some(TargetPlatform::MacOsArm64) => {
let target = match target_platform
.expect("target_platform should be Some for macOS platform branch")
{
TargetPlatform::MacOsX64 => "x86_64-apple-darwin",
TargetPlatform::MacOsArm64 => "arm64-apple-darwin",
_ => unreachable!(),
};
(
"clang++".to_string(),
vec!["-target".to_string(), target.to_string()],
)
}
Some(TargetPlatform::LinuxX64)
| Some(TargetPlatform::LinuxArm64)
| Some(TargetPlatform::LinuxAarch64) => {
match config.cross_platform.linux.compiler_preference {
CompilerPreference::Clang => ("clang++".to_string(), vec![]),
CompilerPreference::Gcc => ("g++".to_string(), vec![]),
CompilerPreference::Intel => ("icpc".to_string(), vec![]),
CompilerPreference::Auto => (
env::var("CXX").unwrap_or_else(|_| "g++".to_string()),
vec![],
),
}
}
None => {
if config.with_cuda {
("nvcc".to_string(), vec![])
} else {
(
env::var("CXX").unwrap_or_else(|_| "c++".to_string()),
vec![],
)
}
}
};
let mut cmd = Command::new(&compiler);
cmd.args(&extra_flags);
for include_dir in &config.include_dirs {
cmd.arg(format!("-I{}", include_dir.display()));
}
if let Ok(torsh_include) = env::var("TORSH_INCLUDE_DIR") {
cmd.arg(format!("-I{}", torsh_include));
}
if compiler.contains("cl.exe") {
cmd.arg("/std:c++17");
if !config.debug {
cmd.arg("/O2");
cmd.arg("/DNDEBUG");
} else {
cmd.arg("/Od");
cmd.arg("/Zi");
}
} else {
cmd.arg("-std=c++17");
cmd.arg("-fPIC");
if !config.debug {
cmd.arg("-O3");
cmd.arg("-DNDEBUG");
} else {
cmd.arg("-g");
cmd.arg("-O0");
}
}
if config.with_cuda && compiler.contains("nvcc") {
for arch in &config.cuda_archs {
cmd.arg(format!(
"-gencode=arch=compute_{},code={}",
&arch[3..],
arch
));
}
cmd.arg("-x").arg("cu");
}
for arg in &config.extra_compile_args {
cmd.arg(arg);
}
for source in &config.sources {
cmd.arg(source);
}
for source in generated_sources {
cmd.arg(source);
}
let platform_suffix = target_platform
.map(|p| format!("_{:?}", p))
.unwrap_or_default();
let output_file = config
.build_dir
.join(format!("lib{}{}.so", config.name, platform_suffix));
if compiler.contains("cl.exe") {
cmd.arg("/Fe:").arg(&output_file);
cmd.arg("/LD"); } else {
cmd.arg("-shared");
cmd.arg("-o").arg(&output_file);
}
for lib_dir in &config.library_dirs {
if compiler.contains("cl.exe") {
cmd.arg(format!("/LIBPATH:{}", lib_dir.display()));
} else {
cmd.arg(format!("-L{}", lib_dir.display()));
}
}
for lib in &config.libraries {
if compiler.contains("cl.exe") {
cmd.arg(format!("{}.lib", lib));
} else {
cmd.arg(format!("-l{}", lib));
}
}
for arg in &config.extra_link_args {
cmd.arg(arg);
}
let output = cmd
.output()
.map_err(|e| format!("Failed to execute compiler {}: {}", compiler, e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!(
"Compilation failed for platform {:?}:\n{}",
target_platform, stderr
));
}
Ok(output_file)
}
fn detect_current_platform() -> TargetPlatform {
match env::consts::OS {
"windows" => match env::consts::ARCH {
"x86_64" => TargetPlatform::WindowsX64,
"x86" => TargetPlatform::WindowsX86,
_ => TargetPlatform::WindowsX64, },
"macos" => match env::consts::ARCH {
"aarch64" => TargetPlatform::MacOsArm64,
_ => TargetPlatform::MacOsX64,
},
"linux" => match env::consts::ARCH {
"aarch64" => TargetPlatform::LinuxAarch64,
"arm64" => TargetPlatform::LinuxArm64,
_ => TargetPlatform::LinuxX64,
},
_ => TargetPlatform::LinuxX64, }
}
fn get_windows_target(platform: &TargetPlatform) -> String {
match platform {
TargetPlatform::WindowsX64 => "x86_64-pc-windows-msvc".to_string(),
TargetPlatform::WindowsX86 => "i686-pc-windows-msvc".to_string(),
_ => "x86_64-pc-windows-msvc".to_string(), }
}
pub fn load_cpp_extension(library_path: &Path) -> Result<(), String> {
if !library_path.exists() {
return Err(format!("Library not found: {}", library_path.display()));
}
Ok(())
}
pub fn generate_extension_template(name: &str, output_dir: &Path) -> Result<(), String> {
fs::create_dir_all(output_dir)
.map_err(|e| format!("Failed to create output directory: {}", e))?;
let header_content = format!(
r#"#pragma once
#include <torsh/tensor.h>
#include <torsh/module.h>
namespace torsh {{
namespace ops {{
// Example custom operation
Tensor {}_forward(const Tensor& input);
}} // namespace ops
}} // namespace torsh
"#,
name
);
let header_path = output_dir.join(format!("{}.h", name));
fs::write(&header_path, header_content)
.map_err(|e| format!("Failed to write header file: {}", e))?;
let source_content = format!(
r#"#include "{}.h"
#include <torsh/autograd.h>
#include <iostream>
namespace torsh {{
namespace ops {{
Tensor {}_forward(const Tensor& input) {{
// Example implementation
auto output = input.clone();
// Perform custom operation
// This is where you would implement your custom logic
return output;
}}
// Register the operation
TORSH_LIBRARY(TORCH_EXTENSION_NAME, m) {{
m.def("{}_forward", &{}_forward);
}}
}} // namespace ops
}} // namespace torsh
"#,
name, name, name, name
);
let source_path = output_dir.join(format!("{}.cpp", name));
fs::write(&source_path, source_content)
.map_err(|e| format!("Failed to write source file: {}", e))?;
let setup_content = format!(
r#"use torsh_utils::cpp_extension::{{CppExtensionConfig, build_cpp_extension}};
use std::path::PathBuf;
fn main() {{
let config = CppExtensionConfig::new("{}", vec![
PathBuf::from("{}.cpp"),
])
.include_dir(".")
.extra_compile_arg("-Wall")
.extra_compile_arg("-Wextra");
match build_cpp_extension(&config) {{
Ok(result) => {{
println!("Extension built successfully!");
println!("Library: {{:?}}", result.library_path);
}}
Err(e) => {{
eprintln!("Build failed: {{}}", e);
std::process::exit(1);
}}
}}
}}
"#,
name, name
);
let setup_path = output_dir.join("build.rs");
fs::write(&setup_path, setup_content)
.map_err(|e| format!("Failed to write setup script: {}", e))?;
Ok(())
}
pub fn cuda_is_available() -> bool {
Command::new("nvcc")
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
pub fn get_cuda_arch_list() -> Vec<String> {
let output = Command::new("nvidia-smi")
.args(["--query-gpu=compute_cap", "--format=csv,noheader,nounits"])
.output();
match output {
Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout)
.lines()
.filter_map(|line| {
let cap = line.trim().replace('.', "_");
if cap.is_empty() {
None
} else {
Some(format!("sm_{}", cap))
}
})
.collect(),
_ => vec![],
}
}
fn query_cuda_devices() -> Result<Vec<CudaDeviceInfo>, String> {
if !cuda_is_available() {
return Ok(vec![]);
}
let output = Command::new("nvidia-smi")
.args([
"--query-gpu=index,name,memory.total,compute_cap",
"--format=csv,noheader,nounits",
])
.output();
let output = match output {
Ok(o) if o.status.success() => o,
_ => return Ok(vec![]),
};
let stdout = String::from_utf8_lossy(&output.stdout);
let mut devices = Vec::new();
for line in stdout.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.splitn(4, ',').map(str::trim).collect();
if parts.len() < 4 {
continue;
}
let device_id: u32 = parts[0].parse().unwrap_or(0);
let name = parts[1].to_string();
let total_memory_mib: u64 = parts[2].parse().unwrap_or(0);
let compute_capability = parts[3].to_string();
devices.push(CudaDeviceInfo {
device_id,
name,
compute_capability,
total_memory: (total_memory_mib * 1024 * 1024) as usize, max_threads_per_block: 1024,
max_grid_size: [65535, 65535, 65535],
max_block_size: [1024, 1024, 64],
warp_size: 32,
multiprocessor_count: 0,
shared_memory_per_block: 0,
});
}
Ok(devices)
}
fn configure_cuda_jit_options(config: &CppExtensionConfig) -> Result<(), String> {
if config.jit_config.cuda_jit {
if config.jit_config.cuda_cache_size == 0 {
return Err("CUDA JIT cache size must be greater than 0".to_string());
}
if config.jit_config.optimization_level > 3 {
return Err("CUDA JIT optimization level must be 0-3".to_string());
}
}
Ok(())
}
fn validate_cuda_kernel_syntax(cuda_source: &str, op_name: &str) -> Result<(), String> {
let required_patterns = [
"__global__", "__device__", "__host__", ];
let has_cuda_pattern = required_patterns
.iter()
.any(|pattern| cuda_source.contains(pattern));
if !has_cuda_pattern {
return Err(format!(
"CUDA source for operation '{}' does not contain valid CUDA kernel markers (__global__, __device__, or __host__)",
op_name
));
}
let brackets_open = cuda_source.chars().filter(|&c| c == '{').count();
let brackets_close = cuda_source.chars().filter(|&c| c == '}').count();
if brackets_open != brackets_close {
return Err(format!(
"CUDA source for operation '{}' has mismatched braces ({{ and }})",
op_name
));
}
let lines: Vec<&str> = cuda_source.lines().collect();
for (i, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if !trimmed.is_empty()
&& !trimmed.starts_with("//")
&& !trimmed.starts_with("/*")
&& !trimmed.ends_with('{')
&& !trimmed.ends_with('}')
&& !trimmed.ends_with(';')
&& !trimmed.starts_with('#')
{
return Err(format!(
"CUDA source for operation '{}' line {} may be missing semicolon: '{}'",
op_name,
i + 1,
trimmed
));
}
}
Ok(())
}
pub fn compile_cuda_kernel_runtime(
kernel_source: &str,
kernel_name: &str,
options: &CudaKernelCompilationOptions,
) -> Result<RuntimeCudaKernel, String> {
if !cuda_is_available() {
return Err("CUDA is not available for runtime compilation".to_string());
}
validate_cuda_kernel_syntax(kernel_source, kernel_name)?;
let ptx_source = format!(
r#"
.version 8.0
.target sm_80
.address_size 64
.visible .entry {}(
.param .u64 param_0
)
{{
// Generated PTX code would go here
ret;
}}
"#,
kernel_name
);
let launch_config = CudaLaunchConfig {
grid_size: [1, 1, 1],
block_size: [256, 1, 1],
shared_memory_size: 0,
stream: None,
};
Ok(RuntimeCudaKernel {
name: kernel_name.to_string(),
ptx_source,
module_handle: Some(1), function_handle: Some(1), compilation_options: options.clone(),
launch_config,
})
}
pub fn launch_cuda_kernel(
kernel: &RuntimeCudaKernel,
args: &[*mut std::ffi::c_void],
) -> Result<(), String> {
if kernel.module_handle.is_none() || kernel.function_handle.is_none() {
return Err(format!("Kernel '{}' is not properly loaded", kernel.name));
}
if kernel.launch_config.grid_size[0] == 0 || kernel.launch_config.block_size[0] == 0 {
return Err(format!(
"Invalid launch configuration for kernel '{}'",
kernel.name
));
}
println!(
"Launching CUDA kernel '{}' with grid {:?} and block {:?}",
kernel.name, kernel.launch_config.grid_size, kernel.launch_config.block_size
);
if args.is_empty() {
return Err(format!(
"No arguments provided for kernel '{}'",
kernel.name
));
}
Ok(())
}
pub fn auto_tune_cuda_kernel(
kernel: &mut RuntimeCudaKernel,
input_sizes: &[usize],
) -> Result<CudaLaunchConfig, String> {
let devices = query_cuda_devices()?;
let device = devices
.first()
.ok_or("No CUDA devices available for auto-tuning")?;
let optimal_block_size = if input_sizes.iter().any(|&size| size > 10000) {
device.max_threads_per_block.min(512)
} else {
device.max_threads_per_block.min(256)
};
let total_elements = input_sizes.iter().max().copied().unwrap_or(1);
let optimal_grid_size =
(total_elements + optimal_block_size as usize - 1) / optimal_block_size as usize;
let clamped_grid_size = (optimal_grid_size as u32).min(device.max_grid_size[0]);
let optimized_config = CudaLaunchConfig {
grid_size: [clamped_grid_size, 1, 1],
block_size: [optimal_block_size, 1, 1],
shared_memory_size: 0, stream: kernel.launch_config.stream,
};
kernel.launch_config = optimized_config.clone();
Ok(optimized_config)
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_cpp_extension_config() {
let config = CppExtensionConfig::new("test_ext", vec![PathBuf::from("test.cpp")])
.include_dir("/usr/include")
.library("torsh")
.extra_compile_arg("-std=c++17");
assert_eq!(config.name, "test_ext");
assert_eq!(config.sources.len(), 1);
assert_eq!(config.include_dirs.len(), 1);
assert_eq!(config.libraries.len(), 1);
}
#[test]
fn test_generate_template() {
let temp_dir = env::temp_dir().join("torsh_test_template");
let result = generate_extension_template("test_op", &temp_dir);
assert!(result.is_ok());
assert!(temp_dir.join("test_op.h").exists());
assert!(temp_dir.join("test_op.cpp").exists());
assert!(temp_dir.join("build.rs").exists());
let _ = fs::remove_dir_all(temp_dir);
}
#[test]
fn test_cuda_detection() {
let available = cuda_is_available();
println!("CUDA available: {}", available);
let archs = get_cuda_arch_list();
println!("CUDA arch list: {:?}", archs);
for arch in &archs {
assert!(
arch.starts_with("sm_"),
"arch should start with sm_, got: {}",
arch
);
}
}
#[test]
fn test_query_cuda_devices_no_panic() {
let result = query_cuda_devices();
assert!(
result.is_ok(),
"query_cuda_devices should return Ok on all platforms"
);
let devices = result.unwrap();
for dev in &devices {
assert!(!dev.name.is_empty(), "device name should not be empty");
assert!(
!dev.compute_capability.is_empty(),
"compute_capability should not be empty"
);
}
}
#[test]
fn test_get_cuda_arch_list_no_panic() {
let archs = get_cuda_arch_list();
for arch in &archs {
assert!(
arch.starts_with("sm_"),
"arch should start with sm_, got: {}",
arch
);
}
}
#[test]
fn test_cuda_kernel_compilation_options() {
let default_options = CudaKernelCompilationOptions::default();
assert_eq!(default_options.optimization_level, 2);
assert!(!default_options.fast_math);
assert!(default_options.use_cache);
assert!(!default_options.debug_info);
let custom_options = CudaKernelCompilationOptions {
optimization_level: 3,
fast_math: true,
max_registers: Some(64),
debug_info: true,
target_arch: Some("sm_80".to_string()),
..Default::default()
};
assert_eq!(custom_options.optimization_level, 3);
assert!(custom_options.fast_math);
assert_eq!(custom_options.max_registers, Some(64));
assert!(custom_options.debug_info);
}
#[test]
fn test_cuda_kernel_syntax_validation() {
let valid_kernel = r#"
__global__ void test_kernel(float* input, float* output) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
output[idx] = input[idx] * 2.0f;
}
"#;
assert!(validate_cuda_kernel_syntax(valid_kernel, "test_kernel").is_ok());
let invalid_kernel = r#"
void test_kernel(float* input, float* output) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
output[idx] = input[idx] * 2.0f;
}
"#;
assert!(validate_cuda_kernel_syntax(invalid_kernel, "test_kernel").is_err());
let invalid_braces = r#"
__global__ void test_kernel(float* input, float* output) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
output[idx] = input[idx] * 2.0f;
// Missing closing brace
"#;
assert!(validate_cuda_kernel_syntax(invalid_braces, "test_kernel").is_err());
}
#[test]
fn test_runtime_cuda_kernel_compilation() {
let kernel_source = r#"
__global__ void vector_add(float* a, float* b, float* c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
"#;
let options = CudaKernelCompilationOptions::default();
if cuda_is_available() {
let result = compile_cuda_kernel_runtime(kernel_source, "vector_add", &options);
if let Ok(kernel) = result {
assert_eq!(kernel.name, "vector_add");
assert!(!kernel.ptx_source.is_empty());
assert!(kernel.module_handle.is_some());
assert!(kernel.function_handle.is_some());
}
}
}
#[test]
fn test_cuda_launch_config_auto_tuning() {
if cuda_is_available() {
let kernel_source = r#"
__global__ void simple_kernel(float* data) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
data[idx] *= 2.0f;
}
"#;
let options = CudaKernelCompilationOptions::default();
let result = compile_cuda_kernel_runtime(kernel_source, "simple_kernel", &options);
if let Ok(mut kernel) = result {
let input_sizes = vec![1024, 2048, 4096];
let tuned_config = auto_tune_cuda_kernel(&mut kernel, &input_sizes);
if let Ok(config) = tuned_config {
assert!(config.grid_size[0] > 0);
assert!(config.block_size[0] > 0);
assert!(config.block_size[0] <= 1024); }
}
}
}
#[test]
fn test_custom_op_with_cuda_jit() {
let custom_op = CustomOpDefinition {
name: "custom_relu".to_string(),
op_type: CustomOpType::Forward,
input_shapes: vec![None], output_shapes: vec![None],
cpu_source: Some("return torch::relu(inputs[0]);".to_string()),
cuda_source: Some(
r#"
__global__ void relu_kernel(float* input, float* output, int size) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < size) {
output[idx] = fmaxf(0.0f, input[idx]);
}
}
"#
.to_string(),
),
compile_flags: vec!["-O3".to_string()],
schema: OpSchema {
input_types: vec![TensorType {
dtype: "float32".to_string(),
min_dims: 1,
max_dims: None,
supports_sparse: false,
}],
output_types: vec![TensorType {
dtype: "float32".to_string(),
min_dims: 1,
max_dims: None,
supports_sparse: false,
}],
is_elementwise: true,
is_deterministic: true,
memory_requirement: MemoryRequirement::Linear,
},
};
let config = CppExtensionConfig::new("custom_relu_ext", vec![])
.enable_cuda_jit()
.custom_op(custom_op);
assert!(config.jit_config.cuda_jit);
assert_eq!(config.custom_ops.len(), 1);
assert_eq!(config.custom_ops[0].name, "custom_relu");
}
}