use std::collections::HashMap;
use std::ffi::c_void;
use std::sync::{Arc, Mutex};
use cudarc::driver::sys::CUdeviceptr;
use cudarc::driver::{CudaContext, CudaFunction, CudaModule, CudaStream};
use onnx_runtime_ep_api::Result;
use crate::blas::CublasLt;
use crate::cudnn::CudnnBackend;
use crate::error::{driver_err, nvrtc_err};
pub struct CudaRuntime {
context: Arc<CudaContext>,
stream: Arc<CudaStream>,
blas: CublasLt,
cudnn: CudnnBackend,
ordinal: u32,
modules: Mutex<HashMap<&'static str, Arc<CudaModule>>>,
}
impl std::fmt::Debug for CudaRuntime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CudaRuntime")
.field("ordinal", &self.ordinal)
.finish()
}
}
impl CudaRuntime {
pub fn new(ordinal: u32) -> Result<Self> {
let context =
CudaContext::new(ordinal as usize).map_err(|e| driver_err("CudaContext::new", e))?;
let stream = context.default_stream();
let blas = CublasLt::new()?;
let cudnn = CudnnBackend::new(stream.clone());
Ok(Self {
context,
stream,
blas,
cudnn,
ordinal,
modules: Mutex::new(HashMap::new()),
})
}
pub fn ordinal(&self) -> u32 {
self.ordinal
}
pub fn blas(&self) -> &CublasLt {
&self.blas
}
pub fn cudnn(&self) -> &CudnnBackend {
&self.cudnn
}
pub fn stream_ptr(&self) -> cudarc::driver::sys::CUstream {
self.stream.cu_stream()
}
pub fn stream(&self) -> &Arc<CudaStream> {
&self.stream
}
pub fn nvrtc_function(
&self,
module_key: &'static str,
src: &str,
entry: &str,
) -> Result<CudaFunction> {
self.bind()?;
let module = {
let mut cache = self.modules.lock().expect("cuda_ep module cache poisoned");
if let Some(m) = cache.get(module_key) {
m.clone()
} else {
let opts = cudarc::nvrtc::CompileOptions {
arch: Some("compute_90"),
..Default::default()
};
let ptx = cudarc::nvrtc::compile_ptx_with_opts(src, opts)
.map_err(|e| nvrtc_err(&format!("compiling NVRTC module '{module_key}'"), e))?;
let m = self
.context
.load_module(ptx)
.map_err(|e| driver_err(&format!("loading NVRTC module '{module_key}'"), e))?;
cache.insert(module_key, m.clone());
m
}
};
module
.load_function(entry)
.map_err(|e| driver_err(&format!("loading NVRTC function '{entry}'"), e))
}
pub fn bind(&self) -> Result<()> {
self.context
.bind_to_thread()
.map_err(|e| driver_err("bind_to_thread", e))
}
pub fn synchronize(&self) -> Result<()> {
self.stream
.synchronize()
.map_err(|e| driver_err("stream synchronize", e))
}
pub fn alloc_raw(&self, bytes: usize) -> Result<CUdeviceptr> {
self.bind()?;
unsafe { cudarc::driver::result::malloc_sync(bytes.max(1)) }
.map_err(|e| driver_err("cuMemAlloc", e))
}
pub unsafe fn free_raw(&self, ptr: CUdeviceptr) -> Result<()> {
self.bind()?;
unsafe { cudarc::driver::result::free_sync(ptr) }.map_err(|e| driver_err("cuMemFree", e))
}
pub unsafe fn htod(&self, src: &[u8], dst: CUdeviceptr) -> Result<()> {
self.bind()?;
unsafe { cudarc::driver::result::memcpy_htod_sync(dst, src) }
.map_err(|e| driver_err("cuMemcpyHtoD", e))
}
pub unsafe fn dtoh(&self, dst: &mut [u8], src: CUdeviceptr) -> Result<()> {
self.bind()?;
unsafe { cudarc::driver::result::memcpy_dtoh_sync(dst, src) }
.map_err(|e| driver_err("cuMemcpyDtoH", e))?;
self.synchronize()
}
pub unsafe fn dtod(&self, src: CUdeviceptr, dst: CUdeviceptr, bytes: usize) -> Result<()> {
self.bind()?;
unsafe { cudarc::driver::result::memcpy_dtod_sync(dst, src, bytes) }
.map_err(|e| driver_err("cuMemcpyDtoD", e))
}
}
#[inline]
pub fn cuptr(raw: *const c_void) -> CUdeviceptr {
raw as usize as CUdeviceptr
}
#[inline]
pub fn raw_ptr(dptr: CUdeviceptr) -> *mut c_void {
dptr as usize as *mut c_void
}