use crate::device_future::DeviceFuture;
use crate::device_operation::{DeviceOperation, ExecutionContext};
use crate::error::{device_error, DeviceError};
use cuda_core::{CudaContext, CudaStream};
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
pub enum GlobalSchedulingPolicy {
RoundRobin(StreamPoolRoundRobin),
}
impl GlobalSchedulingPolicy {
pub fn as_scheduling_policy(&self) -> Result<&impl SchedulingPolicy, DeviceError> {
match self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => Ok(roundrobin),
}
}
}
impl WithDeviceId for GlobalSchedulingPolicy {
fn get_device_id(&self) -> usize {
match self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.get_device_id(),
}
}
}
impl SchedulingPolicy for GlobalSchedulingPolicy {
fn init(&mut self, ctx: &Arc<CudaContext>) -> Result<(), DeviceError> {
match self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.init(ctx),
}
}
fn schedule<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> Result<DeviceFuture<T, O>, DeviceError> {
match self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.schedule(op),
}
}
fn sync<T: Send, O: DeviceOperation<Output = T>>(&self, op: O) -> Result<T, DeviceError> {
match self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.sync(op),
}
}
}
impl SchedulingPolicy for Arc<GlobalSchedulingPolicy> {
fn init(&mut self, _ctx: &Arc<CudaContext>) -> Result<(), DeviceError> {
Err(DeviceError::Scheduling(
"Cannot initialize scheduling policy inside an Arc.".to_string(),
))
}
fn schedule<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> Result<DeviceFuture<T, O>, DeviceError> {
match &**self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.schedule(op),
}
}
fn sync<T: Send, O: DeviceOperation<Output = T>>(&self, op: O) -> Result<T, DeviceError> {
match &**self {
GlobalSchedulingPolicy::RoundRobin(roundrobin) => roundrobin.sync(op),
}
}
}
pub trait WithDeviceId {
fn get_device_id(&self) -> usize;
}
pub trait SchedulingPolicy: Sync {
fn init(&mut self, ctx: &Arc<CudaContext>) -> Result<(), DeviceError>;
fn schedule<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> Result<DeviceFuture<T, O>, DeviceError>;
fn sync<T: Send, O: DeviceOperation<Output = T>>(&self, op: O) -> Result<T, DeviceError>;
}
#[derive(Debug)]
pub struct StreamPoolRoundRobin {
device_id: usize,
next_stream_idx: AtomicUsize,
pub(crate) num_streams: usize,
pub(crate) stream_pool: Option<Vec<Arc<CudaStream>>>,
}
impl StreamPoolRoundRobin {
pub unsafe fn new(device_id: usize, num_streams: usize) -> Self {
Self {
device_id,
num_streams,
stream_pool: None,
next_stream_idx: AtomicUsize::new(0),
}
}
}
impl SchedulingPolicy for StreamPoolRoundRobin {
fn init(&mut self, ctx: &Arc<CudaContext>) -> Result<(), DeviceError> {
let mut stream_pool = vec![];
for _ in 0..self.num_streams {
let stream = ctx.new_stream()?;
stream_pool.push(stream);
}
self.stream_pool = Some(stream_pool);
Ok(())
}
fn sync<T: Send, O: DeviceOperation<Output = T>>(&self, op: O) -> Result<T, DeviceError> {
let non_wrapping_idx = self
.next_stream_idx
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let stream_idx = non_wrapping_idx % self.num_streams;
let stream_pool = self
.stream_pool
.as_ref()
.ok_or(device_error(self.device_id, "Stream pool not initialized."))?;
let stream = stream_pool[stream_idx].clone();
op.sync_on(&stream)
}
fn schedule<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> Result<DeviceFuture<T, O>, DeviceError> {
let non_wrapping_idx = self
.next_stream_idx
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let stream_idx = non_wrapping_idx % self.num_streams;
let stream_pool = self
.stream_pool
.as_ref()
.ok_or(device_error(self.device_id, "Stream pool not initialized."))?;
let stream = stream_pool[stream_idx].clone();
let mut future = DeviceFuture::new();
future.device_operation = Some(op);
future.execution_context = Some(ExecutionContext::new(stream));
Ok(future)
}
}
impl WithDeviceId for StreamPoolRoundRobin {
fn get_device_id(&self) -> usize {
self.device_id
}
}
#[derive(Debug)]
pub struct SingleStream {
#[expect(dead_code, reason = "unsure what this is for")]
device_id: usize,
pub stream: Option<Arc<CudaStream>>,
}
impl SingleStream {
pub unsafe fn new(device_id: usize) -> Self {
Self {
device_id,
stream: None,
}
}
pub fn schedule_single<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> DeviceFuture<T, O> {
let mut future = DeviceFuture::new();
future.device_operation = Some(op);
let stream = self.stream.as_ref().unwrap().clone();
future.execution_context = Some(ExecutionContext::new(stream));
future
}
}
impl SchedulingPolicy for SingleStream {
fn init(&mut self, ctx: &Arc<CudaContext>) -> Result<(), DeviceError> {
self.stream = Some(
ctx.new_stream()
.expect("Failed to create dedicated stream."),
);
Ok(())
}
fn schedule<T: Send, O: DeviceOperation<Output = T>>(
&self,
op: O,
) -> Result<DeviceFuture<T, O>, DeviceError> {
Ok(self.schedule_single(op))
}
fn sync<T: Send, O: DeviceOperation<Output = T>>(&self, op: O) -> Result<T, DeviceError> {
let stream = self.stream.as_ref().unwrap().clone();
op.sync_on(&stream)
}
}