metrale_gpu_runtime/
cuda_host.rs1use std::sync::{Arc, OnceLock};
14
15use cudarc::driver::{CudaContext, CudaStream};
16
17use crate::registry::MetraleRegistry;
18use metrale_core::error::{MetraleError, Result};
19
20pub struct CudaHost {
23 pub ctx: Arc<CudaContext>,
24 pub stream: Arc<CudaStream>,
25 ordinal: usize,
26}
27
28impl CudaHost {
29 pub fn ordinal(&self) -> usize {
30 self.ordinal
31 }
32}
33
34unsafe impl Send for CudaHost {}
37unsafe impl Sync for CudaHost {}
38
39static HOST: OnceLock<std::result::Result<Arc<CudaHost>, String>> = OnceLock::new();
43
44pub fn host(ordinal: usize) -> Result<Arc<CudaHost>> {
47 let result = HOST.get_or_init(|| {
48 let ctx = CudaContext::new(ordinal).map_err(|e| format!("{e}"))?;
49 let stream = ctx.new_stream().map_err(|e| format!("{e}"))?;
50 Ok(Arc::new(CudaHost {
51 ctx,
52 stream,
53 ordinal,
54 }))
55 });
56 match result {
57 Ok(h) if h.ordinal == ordinal => Ok(h.clone()),
58 Ok(h) => Err(MetraleError::ModuleLoad(format!(
59 "CUDA host already bound to GPU {} — cannot rebind to {ordinal}",
60 h.ordinal
61 ))),
62 Err(msg) => Err(MetraleError::ModuleLoad(msg.clone())),
63 }
64}
65
66pub fn release(registry: Arc<MetraleRegistry>) -> Result<()> {
70 let outstanding = Arc::strong_count(®istry) - 1;
71 if outstanding > 0 {
72 return Err(MetraleError::ModuleLoad(format!(
73 "cannot release the kernel modules: {outstanding} handle(s) are still live. \
74 Something is holding the previous model's registry — find it before swapping, \
75 or the next model will run against unloaded modules."
76 )));
77 }
78 let mut owned = Arc::try_unwrap(registry).map_err(|_| {
79 MetraleError::ModuleLoad("registry handle count changed during release".to_string())
80 })?;
81 let failures = owned.unload_raw();
82 if !failures.is_empty() {
83 return Err(MetraleError::ModuleLoad(format!(
84 "{} module(s) failed to unload: {}",
85 failures.len(),
86 failures.join("; ")
87 )));
88 }
89 Ok(())
90}