Skip to main content

cubecl_server/stream/
base.rs

1//! The pool of backend streams, and the primitives that record on their
2//! memory what a unit of work did or did not write.
3//!
4//! The pool answers one question — which stream sits in a slot — and the
5//! free functions below answer the other: which stream allocated a binding,
6//! so the claim lands on the memory rather than on whoever failed. They are
7//! free because the pool and the failure graph are held apart by every
8//! caller; the surface a driver actually uses is
9//! [`FailureStore`](super::FailureStore).
10
11use crate::memory_management::{ErrorGraph, FailureId, ManagedMemoryId};
12use crate::server::{BufferBinding, ServerError};
13use alloc::vec::Vec;
14use cubecl_environment::stream::StreamId;
15
16/// What a launch's read-set check found: the failure claiming an input, the
17/// input it claims, and the error — everything the skip needs to record and
18/// a capture needs to fail with.
19pub struct ReadFailure {
20    /// The failure claiming the input.
21    pub failure: FailureId,
22    /// The claimed input, which the skip record names as what the launch
23    /// needed.
24    pub needed: ManagedMemoryId,
25    /// The failure's error, cloned for the paths that report it directly.
26    pub error: ServerError,
27}
28
29/// Trait for creating streams, used by the stream pool to generate streams as needed.
30pub trait StreamFactory {
31    /// The type of stream produced by this factory.
32    type Stream;
33    /// Creates a new stream instance.
34    ///
35    /// # Errors
36    ///
37    /// Returns a [`ServerError`] when the backend cannot create a stream, e.g. on a device that
38    /// is poisoned.
39    fn create(&mut self) -> Result<Self::Stream, ServerError>;
40}
41
42/// The memory a stream's kernels see, for the taint bookkeeping.
43///
44/// Whether a buffer can be trusted lives on its allocation, inside one of the
45/// stream's memory managers. A backend supplies only which manager that is —
46/// the one whose allocations back [`BufferBinding`]s, never the auxiliary
47/// staging or uniform managers — and everything the drivers do with the
48/// answer lives on the shared wrappers.
49///
50/// The whole binding is passed rather than its memory handle because a
51/// binding names a byte range of its allocation ([`BufferBinding::range`]),
52/// and the claim is exactly that range: a launch that failed writing one
53/// region of a buffer says nothing about the rest of it.
54pub trait StreamMemory {
55    /// The failure claiming any byte the binding names, if one does.
56    fn failure(&self, binding: &BufferBinding) -> Option<FailureId>;
57
58    /// Point the bytes `binding` names at `failure`.
59    fn taint(&mut self, binding: &BufferBinding, failure: FailureId, failures: &mut ErrorGraph);
60
61    /// The bytes `binding` names have a writer again: release every claim on
62    /// them, and only on them.
63    fn written(&mut self, binding: &BufferBinding, failures: &mut ErrorGraph);
64}
65
66/// Represents a pool of streams, managing a collection of streams created by a factory.
67#[derive(Debug)]
68pub struct StreamPool<F: StreamFactory> {
69    /// Vector storing optional streams, where None indicates an uninitialized stream.
70    streams: Vec<Option<F::Stream>>,
71    /// The factory used to create new streams when needed.
72    factory: F,
73    /// Maximum number of regular streams (excludes special streams).
74    max_streams: usize,
75}
76
77impl<F: StreamFactory> StreamPool<F> {
78    /// Creates a new stream pool with the given backend factory and capacity constraints.
79    pub fn new(backend: F, max_streams: u8, num_special: u8) -> Self {
80        // Initialize a vector with capacity for regular and special streams.
81        let mut streams = Vec::with_capacity(max_streams as usize);
82        // Pre-populate the vector with None to reserve space for all streams.
83        for _ in 0..(max_streams.saturating_add(num_special)) {
84            streams.push(None);
85        }
86
87        Self {
88            streams,
89            factory: backend,
90            max_streams: max_streams as usize,
91        }
92    }
93
94    /// Read-only iterator over initialized streams (unlike [`Self::get_mut`], never creates one).
95    pub fn streams(&self) -> impl Iterator<Item = &F::Stream> {
96        self.streams.iter().flatten()
97    }
98
99    /// [`streams`](Self::streams), mutably.
100    pub fn streams_mut(&mut self) -> impl Iterator<Item = &mut F::Stream> {
101        self.streams.iter_mut().flatten()
102    }
103
104    /// Synthetic [`StreamId`]s, one per initialized regular pool slot.
105    ///
106    /// Each id round-trips through [`Self::get_mut`] to the same slot it
107    /// came from (slot `i` is reachable via `StreamId { value: i }` since
108    /// indexing is `value % max_streams`), so it's safe to feed these
109    /// ids back into per-stream APIs.
110    pub fn stream_ids(&self) -> impl Iterator<Item = StreamId> + '_ {
111        self.streams[..self.max_streams]
112            .iter()
113            .enumerate()
114            .filter_map(|(i, s)| s.as_ref().map(|_| StreamId { value: i as u64 }))
115    }
116
117    /// Retrieves a mutable reference to a stream for a given stream ID.
118    pub fn get_mut(&mut self, stream_id: &StreamId) -> &mut F::Stream {
119        // Calculate the index for the stream ID.
120        let index = self.stream_index(stream_id);
121
122        // Use unsafe method to retrieve the stream, assuming the index is valid.
123        //
124        // # Safety
125        //
126        // * The `stream_index` function ensures the index is within bounds.
127        unsafe { self.get_mut_index(index) }
128    }
129
130    /// Retrieves a mutable reference to a stream at the specified index, initializing it if needed.
131    ///
132    /// # Panics
133    ///
134    /// When a required stream creation fails.
135    ///
136    /// # Safety
137    ///
138    /// * Caller must ensure the index is valid (less than `max_streams + num_special`).
139    /// * Lifetimes still follow the Rust rules.
140    pub unsafe fn get_mut_index(&mut self, index: usize) -> &mut F::Stream {
141        unsafe { self.get_or_create_index(index) }
142            .unwrap_or_else(|err| panic!("a stream could not be created: {err}"))
143    }
144
145    /// The stream for `stream_id`, created if it does not exist yet.
146    ///
147    /// # Errors
148    ///
149    /// Returns a [`ServerError`] when a required stream creation fails.
150    pub fn get_or_create(&mut self, stream_id: &StreamId) -> Result<&mut F::Stream, ServerError> {
151        let index = self.stream_index(stream_id);
152        // SAFETY: `stream_index` keeps the index within the regular streams.
153        unsafe { self.get_or_create_index(index) }
154    }
155
156    /// The special stream at `index`, created if it does not exist yet.
157    ///
158    /// # Errors
159    ///
160    /// Same as [`get_or_create`](Self::get_or_create).
161    ///
162    /// # Safety
163    ///
164    /// * Caller must ensure the index corresponds to a valid special stream.
165    pub unsafe fn get_or_create_special(
166        &mut self,
167        index: u8,
168    ) -> Result<&mut F::Stream, ServerError> {
169        unsafe { self.get_or_create_index(self.max_streams + index as usize) }
170    }
171
172    /// The stream at `index`, created if it does not exist yet.
173    ///
174    /// # Safety
175    ///
176    /// * Caller must ensure the index is valid (less than `max_streams + num_special`).
177    pub(crate) unsafe fn get_or_create_index(
178        &mut self,
179        index: usize,
180    ) -> Result<&mut F::Stream, ServerError> {
181        unsafe {
182            // Access the stream entry without bounds checking for performance.
183            let entry = self.streams.get_unchecked_mut(index);
184            match entry {
185                // If the stream exists, return it.
186                Some(val) => Ok(val),
187                // If the stream is None, create a new one using the factory.
188                None => {
189                    let stream = self.factory.create()?;
190                    // Store the new stream in the vector.
191                    *entry = Some(stream);
192
193                    // Re-access the entry, which is now guaranteed to be Some.
194                    match entry {
195                        Some(val) => Ok(val),
196                        // Unreachable because we just set it to Some.
197                        None => unreachable!(),
198                    }
199                }
200            }
201        }
202    }
203
204    /// Retrieves a mutable reference to a special stream at the given index.
205    ///
206    /// # Safety
207    ///
208    /// * Caller must ensure the index corresponds to a valid special stream.
209    /// * Lifetimes still follow the Rust rules.
210    pub unsafe fn get_special(&mut self, index: u8) -> &mut F::Stream {
211        // Calculate the index for the special stream (offset by max_streams).
212        unsafe { self.get_mut_index(self.max_streams + index as usize) }
213    }
214
215    /// Calculates the index for a given stream ID, mapping it to the pool's capacity.
216    pub fn stream_index(&mut self, id: &StreamId) -> usize {
217        stream_index(id, self.max_streams)
218    }
219
220    /// The stream on `id`'s slot, when that slot was ever initialized.
221    ///
222    /// Never creates one: resolving a buffer's owning slot must not bring a
223    /// backend stream into existence, which on CUDA and HIP would bind it to
224    /// whichever context happens to be current. A buffer's slot was
225    /// initialized by the allocation itself, so `None` here means the binding
226    /// is not this pool's to answer for.
227    pub fn try_get(&self, id: &StreamId) -> Option<&F::Stream> {
228        self.streams[stream_index(id, self.max_streams)].as_ref()
229    }
230
231    /// [`try_get`](Self::try_get), mutably.
232    pub fn try_get_mut(&mut self, id: &StreamId) -> Option<&mut F::Stream> {
233        self.streams[stream_index(id, self.max_streams)].as_mut()
234    }
235
236    /// Mutable access to the factory, e.g. to change the configuration new
237    /// streams are created with. Already-created streams are unaffected.
238    pub fn factory_mut(&mut self) -> &mut F {
239        &mut self.factory
240    }
241}
242
243/// Maps a stream ID to an index within the pool's capacity using modulo arithmetic.
244pub fn stream_index(stream_id: &StreamId, max_streams: usize) -> usize {
245    stream_id.value as usize % max_streams
246}
247
248/// Point the bytes every binding in `written` names at `failure`.
249///
250/// Each binding is resolved to the stream that allocated it, which may not be
251/// the stream that failed — that is the point: the fact lands on the memory,
252/// wherever it lives. A binding whose slot no stream ever initialized is
253/// skipped; it is not this pool's to answer for.
254///
255/// Free rather than a method because the pool and the graph are held apart by
256/// every caller: a driver owns both, a resolved borrow holds both mutably.
257pub fn taint_with<'a, F>(
258    pool: &mut StreamPool<F>,
259    failure: FailureId,
260    written: impl Iterator<Item = &'a BufferBinding>,
261    graph: &mut ErrorGraph,
262) where
263    F: StreamFactory<Stream: StreamMemory>,
264{
265    for handle in written {
266        if let Some(stream) = pool.try_get_mut(&handle.stream) {
267            stream.taint(handle, failure, graph);
268        }
269    }
270}
271
272/// [`taint_with`] under a failure minted for `error`, dropped again when it
273/// claimed nothing: a failure no buffer still holds has nothing to wait for.
274pub fn taint<'a, F>(
275    pool: &mut StreamPool<F>,
276    error: ServerError,
277    written: impl Iterator<Item = &'a BufferBinding>,
278    graph: &mut ErrorGraph,
279) where
280    F: StreamFactory<Stream: StreamMemory>,
281{
282    let failure = graph.insert(error);
283    taint_with(pool, failure, written, graph);
284    graph.prune(failure);
285}
286
287/// Release the failure on every allocation in `written`: work that writes
288/// them has been enqueued, so a read of one is no longer reading bytes
289/// nothing wrote.
290pub fn written<'a, F>(
291    pool: &mut StreamPool<F>,
292    written: impl Iterator<Item = &'a BufferBinding>,
293    graph: &mut ErrorGraph,
294) where
295    F: StreamFactory<Stream: StreamMemory>,
296{
297    for handle in written {
298        if let Some(stream) = pool.try_get_mut(&handle.stream) {
299            stream.written(handle, graph);
300        }
301    }
302}