use std::sync::Arc;
use onnx_runtime_ep_api::{
Cost, DeviceBuffer, EpConfig, EpError, ExecutionProvider, Fence, Kernel, KernelMatch,
OpRegistry, Result,
};
use onnx_runtime_ir::{DeviceId, DeviceType, Node, Shape, TensorLayout};
use crate::kernels::build_cuda_registry;
use crate::runtime::{cuptr, raw_ptr, CudaRuntime};
pub struct CudaExecutionProvider {
device: DeviceId,
runtime: Arc<CudaRuntime>,
initialized: bool,
registry: OpRegistry,
}
impl std::fmt::Debug for CudaExecutionProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CudaExecutionProvider")
.field("device", &self.device)
.field("initialized", &self.initialized)
.field("registered_ops", &self.registry.len())
.finish()
}
}
impl CudaExecutionProvider {
pub fn new(ordinal: u32) -> Result<Self> {
let runtime = Arc::new(CudaRuntime::new(ordinal)?);
let registry = build_cuda_registry(runtime.clone());
Ok(Self {
device: DeviceId::cuda(ordinal),
runtime,
initialized: false,
registry,
})
}
pub fn new_default() -> Result<Self> {
Self::new(0)
}
pub fn registry(&self) -> &OpRegistry {
&self.registry
}
pub fn runtime(&self) -> &Arc<CudaRuntime> {
&self.runtime
}
}
impl ExecutionProvider for CudaExecutionProvider {
fn name(&self) -> &str {
"cuda_ep"
}
fn device_type(&self) -> DeviceType {
DeviceType::Cuda
}
fn device_id(&self) -> DeviceId {
self.device
}
fn initialize(&mut self, _config: &EpConfig) -> Result<()> {
self.runtime.bind()?;
self.initialized = true;
Ok(())
}
fn shutdown(&mut self) -> Result<()> {
self.initialized = false;
Ok(())
}
fn supports_op(&self, op: &Node, shapes: &[Shape], _layouts: &[TensorLayout]) -> KernelMatch {
if !self.registry.supports(&op.op_type, &op.domain) {
return KernelMatch::Unsupported;
}
let output_layouts = vec![TensorLayout::contiguous(); op.outputs.len()];
let elems: u64 = shapes
.iter()
.map(|s| {
s.iter()
.map(|d| d.as_static().unwrap_or(1) as u64)
.product::<u64>()
})
.sum();
let cost = Cost::new(elems as f64 * 0.01, elems as f64 * 0.01, 0.0)
.with_launch_us(10.0)
.with_bytes_moved(elems.saturating_mul(4));
KernelMatch::Supported {
cost,
required_input_layouts: None,
output_layouts,
}
}
fn get_kernel(&self, op: &Node, shapes: &[Vec<usize>], opset: u64) -> Result<Box<dyn Kernel>> {
let factory = self
.registry
.lookup(&op.op_type, &op.domain, opset)
.ok_or_else(|| EpError::NoEpForOp {
op_type: op.op_type.clone(),
})?;
factory.create(op, shapes)
}
fn allocate(&self, size: usize, alignment: usize) -> Result<DeviceBuffer> {
if alignment == 0 || !alignment.is_power_of_two() {
return Err(EpError::AlignmentError);
}
let dptr = self.runtime.alloc_raw(size)?;
Ok(unsafe { DeviceBuffer::from_raw_parts(raw_ptr(dptr), self.device, size, alignment) })
}
fn deallocate(&self, buffer: DeviceBuffer) -> Result<()> {
assert_eq!(
buffer.device(),
self.device,
"cuda_ep: refusing to deallocate a buffer from device {:?}",
buffer.device()
);
if buffer.is_borrowed() {
return Ok(());
}
let dptr = cuptr(buffer.into_raw());
unsafe { self.runtime.free_raw(dptr) }
}
fn copy(&self, src: &DeviceBuffer, dst: &mut DeviceBuffer, size: usize) -> Result<()> {
assert_eq!(src.device(), self.device, "cuda_ep::copy: foreign src buffer");
assert_eq!(dst.device(), self.device, "cuda_ep::copy: foreign dst buffer");
if size > src.len() || size > dst.len() {
return Err(EpError::KernelFailed(format!(
"cuda_ep::copy: size {size} exceeds src {} or dst {}",
src.len(),
dst.len()
)));
}
if size == 0 {
return Ok(());
}
let src_p = cuptr(src.as_ptr());
let dst_p = cuptr(dst.as_mut_ptr());
unsafe { self.runtime.dtod(src_p, dst_p, size) }
}
fn copy_async(&self, src: &DeviceBuffer, dst: &mut DeviceBuffer, size: usize) -> Result<Fence> {
self.copy(src, dst, size)?;
Ok(Fence::default())
}
fn sync(&self) -> Result<()> {
self.runtime.synchronize()
}
}