use crate::memory_management::relocation::{
RelocatableStream, RelocatingStreams, RelocationNeed, RelocationReason,
};
use crate::{
config::streaming::StreamingLogLevel,
logging::ServerLogger,
memory_management::{ErrorGraph, FailureId},
server::{BufferBinding, ServerError},
stream::{FailureStore, Failures, StreamFactory, StreamMemory, StreamPool},
};
use alloc::{format, sync::Arc, vec, vec::Vec};
use cubecl_environment::stream::StreamId;
pub trait SchedulerStreamBackend {
type Task: core::fmt::Debug;
type Stream: core::fmt::Debug + StreamMemory;
type Factory: StreamFactory<Stream = Self::Stream>;
fn enqueue(task: Self::Task, stream: &mut Self::Stream, failures: &mut ErrorGraph);
fn flush(stream: &mut Self::Stream, failures: &mut ErrorGraph);
fn factory(&mut self) -> &mut Self::Factory;
fn requires_isolation(_stream: &Self::Stream) -> bool {
false
}
}
#[derive(Debug)]
pub struct SchedulerMultiStream<B: SchedulerStreamBackend> {
pool: StreamPool<SchedulerPoolMarker<B>>,
failures: Failures,
strategy: SchedulerStrategy,
max_tasks: usize,
pub logger: Arc<ServerLogger>,
}
#[derive(Debug)]
pub enum SchedulerStrategy {
Interleave,
Sequential,
}
#[derive(Debug)]
pub struct Stream<B: SchedulerStreamBackend> {
tasks: Vec<B::Task>,
stream: B::Stream,
}
impl<B: SchedulerStreamBackend> Stream<B> {
fn flush(&mut self) -> Vec<B::Task> {
let mut returned = Vec::with_capacity(self.tasks.capacity());
core::mem::swap(&mut returned, &mut self.tasks);
returned
}
}
#[derive(Debug)]
pub struct SchedulerPoolMarker<B: SchedulerStreamBackend> {
backend: B,
}
impl<B: SchedulerStreamBackend> StreamMemory for Stream<B> {
fn failure(&self, binding: &BufferBinding) -> Option<FailureId> {
self.stream.failure(binding)
}
fn taint(&mut self, binding: &BufferBinding, failure: FailureId, failures: &mut ErrorGraph) {
self.stream.taint(binding, failure, failures)
}
fn written(&mut self, binding: &BufferBinding, failures: &mut ErrorGraph) {
self.stream.written(binding, failures)
}
}
impl<B: SchedulerStreamBackend> FailureStore for SchedulerMultiStream<B> {
type Factory = SchedulerPoolMarker<B>;
fn split(&mut self) -> (&mut StreamPool<Self::Factory>, &mut Failures) {
(&mut self.pool, &mut self.failures)
}
fn parts(&self) -> (&StreamPool<Self::Factory>, &Failures) {
(&self.pool, &self.failures)
}
}
impl<B: SchedulerStreamBackend> StreamFactory for SchedulerPoolMarker<B> {
type Stream = Stream<B>;
fn create(&mut self) -> Result<Self::Stream, ServerError> {
Ok(Stream {
tasks: Vec::new(),
stream: self.backend.factory().create()?,
})
}
}
#[derive(Debug)]
pub struct SchedulerMultiStreamOptions {
pub max_streams: u8,
pub max_tasks: usize,
pub strategy: SchedulerStrategy,
}
pub struct SchedulerRelocation<'a, B: SchedulerStreamBackend> {
scheduler: &'a mut SchedulerMultiStream<B>,
stream_id: StreamId,
}
impl<B> RelocatingStreams for SchedulerRelocation<'_, B>
where
B: SchedulerStreamBackend,
B::Stream: RelocatableStream,
{
fn recording(&mut self) -> bool {
self.scheduler.stream(&self.stream_id).recording()
}
fn has_outdated(&mut self) -> bool {
self.scheduler.stream(&self.stream_id).has_outdated()
}
fn relocation_need(&mut self) -> RelocationNeed {
self.scheduler.stream(&self.stream_id).relocation_need()
}
fn bytes_allocated(&mut self) -> u64 {
self.scheduler
.streams()
.map(RelocatableStream::bytes_allocated)
.sum()
}
fn finish(&mut self) {
self.scheduler.flush_all();
}
fn relocate_memory(&mut self, reason: RelocationReason) {
let (stream, failures) = self.scheduler.stream_and_failures(&self.stream_id);
stream.relocate(reason, failures);
}
fn cleanup_memory(&mut self) {
let (stream, failures) = self.scheduler.stream_and_failures(&self.stream_id);
stream.cleanup_memory(failures);
}
fn device_has_outdated(&mut self) -> bool {
self.scheduler
.streams()
.any(RelocatableStream::has_outdated)
}
fn relocate_device_memory(&mut self, reason: RelocationReason) {
let stream_ids: Vec<_> = self.scheduler.stream_ids().collect();
for stream_id in stream_ids {
let (stream, failures) = self.scheduler.stream_and_failures(&stream_id);
if stream.has_outdated() {
stream.relocate(reason, failures);
}
}
}
}
impl<B: SchedulerStreamBackend> SchedulerMultiStream<B> {
pub fn new(
logger: Arc<ServerLogger>,
backend: B,
options: SchedulerMultiStreamOptions,
) -> Self {
Self {
pool: StreamPool::new(SchedulerPoolMarker { backend }, options.max_streams, 0),
failures: Failures::new(logger.clone()),
max_tasks: options.max_tasks,
strategy: options.strategy,
logger,
}
}
pub fn stream(&mut self, stream_id: &StreamId) -> &mut B::Stream {
let stream = self.pool.get_mut(stream_id);
&mut stream.stream
}
pub fn stream_and_failures(
&mut self,
stream_id: &StreamId,
) -> (&mut B::Stream, &mut ErrorGraph) {
let stream = self.pool.get_mut(stream_id);
(&mut stream.stream, self.failures.graph_mut())
}
pub fn backend_mut(&mut self) -> &mut B {
&mut self.pool.factory_mut().backend
}
pub fn streams(&self) -> impl Iterator<Item = &B::Stream> {
self.pool.streams().map(|s| &s.stream)
}
pub fn stream_ids(&self) -> impl Iterator<Item = StreamId> + '_ {
self.pool.stream_ids()
}
pub fn relocating(&mut self, stream_id: StreamId) -> SchedulerRelocation<'_, B> {
SchedulerRelocation {
scheduler: self,
stream_id,
}
}
pub fn flush_all(&mut self) {
let stream_ids: Vec<_> = self.stream_ids().collect();
self.execute_streams(stream_ids);
let graph = self.failures.graph_mut();
for stream in self.pool.streams_mut() {
B::flush(&mut stream.stream, graph);
}
}
pub fn register(&mut self, stream_id: StreamId, task: B::Task, args_streams: &[StreamId]) {
self.align_streams(stream_id, args_streams);
let current = self.pool.get_mut(&stream_id);
current.tasks.push(task);
if current.tasks.len() >= self.max_tasks {
self.execute_streams(vec![stream_id]);
}
}
pub(crate) fn align_streams(&mut self, stream_id: StreamId, args_streams: &[StreamId]) {
let mut to_flush = Vec::new();
let index = self.pool.stream_index(&stream_id);
for arg_stream in args_streams {
let index_stream = self.pool.stream_index(arg_stream);
if index != index_stream {
to_flush.push(*arg_stream);
self.logger.log_streaming(
|level| matches!(level, StreamingLogLevel::Full),
|| format!("Binding on {} is shared on {}", arg_stream, stream_id),
);
}
}
if to_flush.is_empty() {
return;
}
self.logger.log_streaming(
|level| !matches!(level, StreamingLogLevel::Disabled),
|| {
format!(
"Flushing streams {to_flush:?} before registering more tasks on {stream_id}"
)
},
);
self.execute_streams(to_flush);
}
pub fn execute_streams(&mut self, stream_ids: Vec<StreamId>) {
let mut indices = Vec::with_capacity(stream_ids.len());
for id in stream_ids {
let index = self.pool.stream_index(&id);
if !indices.contains(&index) {
indices.push(index);
}
}
let mut schedules = Vec::new();
let mut isolation = false;
for index in indices {
let stream = unsafe { self.pool.get_mut_index(index) }; isolation |= B::requires_isolation(&stream.stream);
let tasks = stream.flush();
let num_tasks = tasks.len();
schedules.push(Schedule {
tasks: tasks.into_iter(),
num_tasks,
stream_index: index,
});
}
if schedules.is_empty() {
return;
}
match self.strategy {
SchedulerStrategy::Interleave if !isolation => {
self.execute_schedules_interleave(schedules)
}
_ => self.execute_schedules_sequence(schedules),
}
}
fn execute_schedules_sequence(&mut self, schedules: Vec<Schedule<B>>) {
for schedule in schedules {
let stream = unsafe { self.pool.get_mut_index(schedule.stream_index) }; for task in schedule.tasks {
B::enqueue(task, &mut stream.stream, self.failures.graph_mut());
}
B::flush(&mut stream.stream, self.failures.graph_mut());
}
}
fn execute_schedules_interleave(&mut self, mut schedules: Vec<Schedule<B>>) {
for schedule in schedules.iter_mut().skip(1) {
let stream = unsafe { self.pool.get_mut_index(schedule.stream_index) };
B::flush(&mut stream.stream, self.failures.graph_mut());
}
let execution_index = schedules.first().expect("At least one stream").stream_index;
let stream = unsafe { self.pool.get_mut_index(execution_index) };
let num_tasks_max = schedules
.iter()
.map(|s| s.num_tasks)
.max()
.expect("At least one schedule");
for _ in 0..num_tasks_max {
for schedule in schedules.iter_mut() {
if let Some(task) = schedule.tasks.next() {
B::enqueue(task, &mut stream.stream, self.failures.graph_mut());
}
}
}
B::flush(&mut stream.stream, self.failures.graph_mut());
}
}
struct Schedule<B: SchedulerStreamBackend> {
tasks: alloc::vec::IntoIter<B::Task>,
num_tasks: usize,
stream_index: usize,
}