use crate::error::{device_assert, device_error, DeviceError};
use crate::scheduling_policies::{SchedulingPolicy, StreamPoolRoundRobin};
use cuda_core::{Device, Function, MemPool, Module, Stream};
use std::cell::Cell;
use std::collections::HashMap;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::{Arc, Mutex, OnceLock};
pub const DEFAULT_DEVICE_ID: usize = 0;
pub const DEFAULT_NUM_DEVICES: usize = 1;
pub const DEFAULT_ROUND_ROBIN_STREAM_POOL_SIZE: usize = 4;
pub trait FunctionKey: Hash {
fn display_hash(&self) -> String {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
let hash_value: u64 = hasher.finish();
format!("{:x}", hash_value)
}
}
#[derive(Debug, Clone)]
pub enum ValidParamType {
Scalar(ScalarParamType),
Pointer(PointerParamType),
Tensor(TensorParamType),
}
#[derive(Debug, Clone)]
pub struct ScalarParamType {
pub element_type: String,
}
#[derive(Debug, Clone)]
pub struct PointerParamType {
pub mutable: bool,
pub element_type: String,
}
#[derive(Debug, Clone)]
pub struct TensorParamType {
pub element_type: String,
pub shape: Vec<i32>,
}
#[derive(Debug, Clone)]
pub struct Validator {
pub params: Vec<ValidParamType>,
pub launch_checks: Vec<crate::predicate::LaunchCheck>,
}
static DEVICES: OnceLock<Mutex<HashMap<usize, Arc<Device>>>> = OnceLock::new();
fn devices() -> &'static Mutex<HashMap<usize, Arc<Device>>> {
DEVICES.get_or_init(|| Mutex::new(HashMap::new()))
}
fn get_or_init_device(device_id: usize) -> Result<Arc<Device>, DeviceError> {
let mut devices = devices()
.lock()
.map_err(|_| device_error(device_id, "device map lock poisoned"))?;
if let Some(device) = devices.get(&device_id) {
return Ok(Arc::clone(device));
}
let device = Device::new(device_id)?;
devices.insert(device_id, Arc::clone(&device));
Ok(device)
}
#[derive(Debug)]
pub struct CompiledKernel {
pub module: Arc<Module>,
pub function: Arc<Function>,
pub validator: Arc<Validator>,
}
pub struct AsyncDeviceContext {
#[expect(dead_code, reason = "will be used when multi-device is implemented")]
device_id: usize,
deallocator_stream: Arc<Stream>,
policy: Arc<dyn SchedulingPolicy>,
pool: Option<Arc<MemPool>>,
}
pub struct AsyncDeviceContexts {
default_device: Cell<usize>,
devices: Cell<Option<HashMap<usize, AsyncDeviceContext>>>,
}
thread_local!(static DEVICE_CONTEXTS: AsyncDeviceContexts = const {
AsyncDeviceContexts {
default_device: Cell::new(DEFAULT_DEVICE_ID),
devices: Cell::new(None),
}
});
pub fn get_default_device() -> usize {
DEVICE_CONTEXTS.with(|ctx| ctx.default_device.get())
}
pub fn init_device_contexts(
default_device_id: usize,
num_devices: usize,
) -> Result<(), DeviceError> {
DEVICE_CONTEXTS.with(|ctx| {
device_assert(
default_device_id,
ctx.devices.replace(None).is_none(),
"Context already initialized.",
)
})?;
let devices = HashMap::with_capacity(num_devices);
DEVICE_CONTEXTS.with(|ctx| {
ctx.default_device.set(default_device_id);
ctx.devices.set(Some(devices));
});
Ok(())
}
pub fn init_device_contexts_default() -> Result<(), DeviceError> {
let default_device = get_default_device();
init_device_contexts(default_device, DEFAULT_NUM_DEVICES)
}
pub fn new_device_context(
device_id: usize,
policy: Arc<dyn SchedulingPolicy>,
) -> Result<AsyncDeviceContext, DeviceError> {
let device = get_or_init_device(device_id)?;
let deallocator_stream = device.new_stream()?;
Ok(AsyncDeviceContext {
device_id,
deallocator_stream,
policy,
pool: None,
})
}
pub fn init_device(
hashmap: &mut HashMap<usize, AsyncDeviceContext>,
device_id: usize,
policy: Arc<dyn SchedulingPolicy>,
) -> Result<(), DeviceError> {
let device_context = new_device_context(device_id, policy)?;
let pred = hashmap.insert(device_id, device_context).is_none();
device_assert(device_id, pred, "Device is already initialized.")
}
pub fn init_with_default_policy(
hashmap: &mut HashMap<usize, AsyncDeviceContext>,
device_id: usize,
) -> Result<(), DeviceError> {
let device = get_or_init_device(device_id)?;
let policy = StreamPoolRoundRobin::new(&device, DEFAULT_ROUND_ROBIN_STREAM_POOL_SIZE)?;
let deallocator_stream = device.new_stream()?;
let device_context = AsyncDeviceContext {
device_id,
deallocator_stream,
policy: Arc::new(policy),
pool: None,
};
let pred = hashmap.insert(device_id, device_context).is_none();
device_assert(device_id, pred, "Device is already initialized.")
}
pub fn with_global_device_context<F, R>(device_id: usize, f: F) -> Result<R, DeviceError>
where
F: FnOnce(&AsyncDeviceContext) -> R,
{
DEVICE_CONTEXTS.with(|ctx| {
let mut hashmap = match ctx.devices.take() {
Some(hashmap) => hashmap,
None => {
init_device_contexts_default()?;
ctx.devices
.take()
.ok_or(device_error(device_id, "Failed to initialize context"))?
}
};
if !hashmap.contains_key(&device_id) {
init_with_default_policy(&mut hashmap, device_id)?;
}
let device_context = hashmap
.get(&device_id)
.ok_or(device_error(device_id, "Failed to get context"))?;
let r = f(device_context);
ctx.devices.replace(Some(hashmap));
Ok(r)
})
}
pub fn with_global_device_context_mut<F, R>(device_id: usize, f: F) -> Result<R, DeviceError>
where
F: FnOnce(&mut AsyncDeviceContext) -> R,
{
DEVICE_CONTEXTS.with(|ctx| {
let mut hashmap = match ctx.devices.take() {
Some(hashmap) => hashmap,
None => {
init_device_contexts_default()?;
ctx.devices
.take()
.ok_or(device_error(device_id, "Failed to initialize context"))?
}
};
if !hashmap.contains_key(&device_id) {
init_with_default_policy(&mut hashmap, device_id)?;
}
let device_context = hashmap
.get_mut(&device_id)
.ok_or(device_error(device_id, "Failed to get context"))?;
let r = f(device_context);
ctx.devices.replace(Some(hashmap));
Ok(r)
})
}
pub fn with_device_policy<F, R>(device_id: usize, f: F) -> Result<R, DeviceError>
where
F: FnOnce(&Arc<dyn SchedulingPolicy>) -> R,
{
with_global_device_context(device_id, |device_context| f(&device_context.policy))
}
pub fn global_policy(device_id: usize) -> Result<Arc<dyn SchedulingPolicy>, DeviceError> {
with_global_device_context(device_id, |device_context| device_context.policy.clone())
}
pub unsafe fn with_deallocator_stream<F, R>(device_id: usize, f: F) -> Result<R, DeviceError>
where
F: FnOnce(&Arc<Stream>) -> R,
{
with_global_device_context(device_id, |device_context| {
f(&device_context.deallocator_stream)
})
}
pub fn with_device<F, R>(device_id: usize, f: F) -> Result<R, DeviceError>
where
F: FnOnce(&Arc<Device>) -> R,
{
let device = get_or_init_device(device_id)?;
Ok(f(&device))
}
pub fn set_default_device(default_device_id: usize) {
DEVICE_CONTEXTS.with(|ctx| {
ctx.default_device.set(default_device_id);
})
}
pub fn set_device_pool(device_id: usize, pool: Arc<MemPool>) -> Result<(), DeviceError> {
let pool_device = pool.device().ordinal();
device_assert(
device_id,
pool_device == device_id,
&format!("pool belongs to device {pool_device}, expected device {device_id}"),
)?;
with_global_device_context_mut(device_id, |device_context| {
device_context.pool = Some(pool);
})
}
pub fn clear_device_pool(device_id: usize) -> Result<(), DeviceError> {
with_global_device_context_mut(device_id, |device_context| {
device_context.pool = None;
})
}
pub fn get_device_pool(device_id: usize) -> Result<Option<Arc<MemPool>>, DeviceError> {
with_global_device_context(device_id, |device_context| device_context.pool.clone())
}
pub fn pool_for_stream(stream: &Arc<Stream>) -> Option<Arc<MemPool>> {
get_device_pool(stream.device().ordinal()).ok().flatten()
}
pub fn with_default_device_policy<F, R>(f: F) -> Result<R, DeviceError>
where
F: FnOnce(&Arc<dyn SchedulingPolicy>) -> R,
{
let default_device = get_default_device();
with_global_device_context(default_device, |device_context| f(&device_context.policy))
}
pub fn load_module_from_file(filename: &str, device_id: usize) -> Result<Arc<Module>, DeviceError> {
with_device(device_id, |device| {
let module = device.load_module_from_file(filename)?;
Ok(module)
})?
}
pub fn load_module_from_bytes(image: &[u8], device_id: usize) -> Result<Arc<Module>, DeviceError> {
with_device(device_id, |device| {
let module = device.load_module_from_bytes(image)?;
Ok(module)
})?
}
pub fn load_module_from_ptx(ptx_src: &str, device_id: usize) -> Result<Arc<Module>, DeviceError> {
with_device(device_id, |device| {
let module = device.load_module_from_ptx_src(ptx_src)?;
Ok(module)
})?
}