use crate::capability::{KernelAvailability, sanitize_diagnostic};
use crate::gpu::error::{GpuError, GpuResult};
use cust::error::CudaError;
use cust::function::Function;
use cust::module::Module;
use std::collections::HashMap;
use nvtx::{range_pop, range_push};
static SPIKING_NETWORK_PTX: &str =
include_str!(concat!(env!("OUT_DIR"), "/spiking_network_sm_120.ptx"));
static VECTOR_SIMILARITY_PTX: &str =
include_str!(concat!(env!("OUT_DIR"), "/vector_similarity_sm_120.ptx"));
static SATSOLVER_PTX: &str = include_str!(concat!(env!("OUT_DIR"), "/satsolver_sm_120.ptx"));
static TERNARY_GEMM_PTX: &str = include_str!(concat!(env!("OUT_DIR"), "/ternary_gemm_sm_120.ptx"));
const SPIKING_KERNELS: &[&str] = &[
"poisson_encode",
"lif_step",
"lif_step_weighted",
"spike_rate",
"reset_membrane",
"stdp_update_weights",
"stdp_update_traces",
"neuro_bias_logits",
"membrane_dv_dt_reduce_pass1",
"routing_entropy_reduce_pass1",
"latent_reduce_pass2",
];
const VECTOR_SIMILARITY_KERNELS: &[&str] =
&["cosine_similarity_batched", "cosine_similarity_top_k"];
const SATSOLVER_KERNELS: &[&str] = &[
"satsolver_init",
"satsolver_step",
"satsolver_aux_update",
"satsolver_check_solution",
"satsolver_extract",
"satsolver_best_reduce_pass1",
"satsolver_best_reduce_pass2",
];
const TERNARY_GEMM_KERNELS: &[&str] = &["ternary_gemv", "ternary_gemm"];
pub struct KernelModule {
modules: HashMap<String, Module>,
func_map: HashMap<String, String>,
}
pub(crate) struct KernelLoadFailure {
pub(crate) error: GpuError,
pub(crate) availability: KernelAvailability,
}
impl KernelModule {
pub fn load() -> GpuResult<Self> {
Self::load_with_availability().map_err(|failure| failure.error)
}
pub(crate) fn load_with_availability() -> Result<Self, KernelLoadFailure> {
range_push!("KernelModule::load");
let result = Self::load_inner();
range_pop!();
result
}
fn load_inner() -> Result<Self, KernelLoadFailure> {
let mut modules = HashMap::new();
let mut func_map = HashMap::new();
let mut availability = KernelAvailability::compiled_unverified();
if let Err(error) = Self::load_and_map(
&mut modules,
&mut func_map,
SPIKING_NETWORK_PTX,
"spiking_network",
SPIKING_KERNELS,
) {
availability.spiking_network = Some(false);
return Err(KernelLoadFailure {
error,
availability,
});
}
availability.spiking_network = Some(true);
if let Err(error) = Self::load_and_map(
&mut modules,
&mut func_map,
VECTOR_SIMILARITY_PTX,
"vector_similarity",
VECTOR_SIMILARITY_KERNELS,
) {
availability.vector_similarity = Some(false);
return Err(KernelLoadFailure {
error,
availability,
});
}
availability.vector_similarity = Some(true);
if let Err(error) = Self::load_and_map(
&mut modules,
&mut func_map,
SATSOLVER_PTX,
"satsolver",
SATSOLVER_KERNELS,
) {
availability.satsolver = Some(false);
return Err(KernelLoadFailure {
error,
availability,
});
}
availability.satsolver = Some(true);
if let Err(error) = Self::load_and_map(
&mut modules,
&mut func_map,
TERNARY_GEMM_PTX,
"ternary_gemm",
TERNARY_GEMM_KERNELS,
) {
availability.ternary_gemm = Some(false);
return Err(KernelLoadFailure {
error,
availability,
});
}
Ok(Self { modules, func_map })
}
fn load_and_map(
modules: &mut HashMap<String, Module>,
func_map: &mut HashMap<String, String>,
ptx: &str,
mod_name: &str,
funcs: &[&str],
) -> GpuResult<()> {
let module = Self::load_module_from_ptx(ptx, mod_name)?;
for &func_name in funcs {
module
.get_function(func_name)
.map_err(|error| function_lookup_error(func_name, error))?;
func_map.insert(func_name.to_string(), mod_name.to_string());
}
modules.insert(mod_name.to_string(), module);
Ok(())
}
pub fn load_satsolver() -> GpuResult<Self> {
Self::load()
}
pub fn get_function<'a>(&'a self, name: &str) -> GpuResult<Function<'a>> {
let mod_name = self
.func_map
.get(name)
.ok_or_else(|| GpuError::KernelNotFound(name.to_string()))?;
let module = self
.modules
.get(mod_name)
.ok_or_else(|| GpuError::KernelNotFound(format!("module {mod_name} missing")))?;
module
.get_function(name)
.map_err(|error| function_lookup_error(name, error))
}
fn load_module_from_ptx(ptx: &str, name: &str) -> GpuResult<Module> {
Module::from_ptx(ptx, &[]).map_err(|error| {
let detail = sanitize_diagnostic(&format!("{error:?}"));
eprintln!("[CUDA JIT] Failed to load module '{name}': {detail}");
module_load_error(name, error)
})
}
}
fn function_lookup_error(name: &str, error: CudaError) -> GpuError {
let detail = sanitize_diagnostic(&format!("{name}: {error:?}"));
if error == CudaError::NotFound {
GpuError::KernelNotFound(detail)
} else {
GpuError::CudaError(detail)
}
}
fn module_load_error(name: &str, error: CudaError) -> GpuError {
let detail = sanitize_diagnostic(&format!("{error:?}"));
if matches!(
error,
CudaError::InvalidImage
| CudaError::NoBinaryForGpu
| CudaError::InvalidPtx
| CudaError::InvalidSource
) {
GpuError::ModuleLoadFailed(format!(
"JIT compilation failed for '{name}': {detail} \
(target: sm_120 — check driver ≥ 570 and CUDA toolkit ≥ 12.8)"
))
} else {
GpuError::CudaError(format!("loading module '{name}': {detail}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gpu::GpuContext;
use cust::error::CudaError;
#[test]
fn missing_symbol_is_distinct_from_runtime_lookup_failure() {
assert!(matches!(
function_lookup_error("missing", CudaError::NotFound),
GpuError::KernelNotFound(_)
));
let runtime = function_lookup_error("lif_step", CudaError::InvalidContext);
assert!(matches!(runtime, GpuError::CudaError(_)));
assert_eq!(
runtime.fallback_reason(),
Some(crate::FallbackReason::DriverRuntimeFailure)
);
}
#[test]
fn invalid_ptx_is_distinct_from_runtime_module_failure() {
assert!(matches!(
module_load_error("spiking_network", CudaError::InvalidPtx),
GpuError::ModuleLoadFailed(_)
));
let runtime = module_load_error("spiking_network", CudaError::InvalidContext);
assert!(matches!(runtime, GpuError::CudaError(_)));
}
#[test]
#[ignore] fn test_load_kernels() {
let _ctx = GpuContext::init().expect("Failed to initialize GPU context");
let kernels = KernelModule::load().expect("Failed to load kernels");
assert!(kernels.get_function("cosine_similarity_batched").is_ok());
assert!(kernels.get_function("lif_step").is_ok());
assert!(kernels.get_function("satsolver_step").is_ok());
assert!(kernels.get_function("ternary_gemv").is_ok());
assert!(kernels.get_function("ternary_gemm").is_ok());
}
}