Skip to main content

cubecl_server/command/
backend.rs

1//! What a backend supplies so the shared [`Command`](super::Command) can run.
2//!
3//! Two traits, because they answer different questions. [`Driver`] is the four
4//! device calls the runtime cannot make itself. [`DeviceStream`] is where a
5//! backend's stream keeps the state those calls move in step with — a command
6//! reaches all of it, and none of it is the driver's to reach.
7
8use crate::allocator::Pitch;
9use crate::id::KernelId;
10use crate::memory_management::drop_queue::{Fence, PendingDropQueue};
11use crate::memory_management::{ManagedMemoryBinding, MemoryManagement};
12use crate::metadata_cache::MetadataInfoCache;
13use crate::server::{Handle, IoError, LaunchError, ServerError};
14use crate::storage::ComputeStorage;
15use crate::stream::{EventStreamBackend, StreamCapture};
16use cubecl_common::bytes::Bytes;
17use cubecl_environment::backtrace::BackTrace;
18use cubecl_zspace::{Shape, Strides, striding::has_pitched_row_major_strides};
19
20/// The state a backend's stream keeps beside its driver handle.
21///
22/// All of it moves in step with the work queued on that stream, which is why
23/// it is the stream's and not the device's: the deferred frees a fenced flush
24/// releases, the capture window that forbids allocating, the per-launch info
25/// buffers a capture may not evict, and the memory the stream's kernels see.
26pub trait DeviceStream {
27    /// The fence this backend records on a stream. Fencing is how the drop
28    /// queue knows the device is done with what it is holding.
29    type Fence: Fence + Send + 'static;
30    /// The storage backing the memory this stream's kernels address.
31    type DeviceStorage: ComputeStorage;
32    /// The storage backing the host buffers that stage transfers to it.
33    type HostStorage: ComputeStorage;
34
35    /// The memory this stream's kernels see. Allocations are per stream, so a
36    /// buffer resolves to the stream that created it and nowhere else.
37    fn device_memory(&mut self) -> &mut MemoryManagement<Self::DeviceStorage>;
38
39    /// The pinned host memory this stream stages transfers through.
40    fn host_memory(&mut self) -> &mut MemoryManagement<Self::HostStorage>;
41
42    /// Frees deferred until the device is known to be done with them.
43    fn drop_queue(&mut self) -> &mut PendingDropQueue<Self::Fence>;
44
45    /// Where this stream sits in the graph-capture lifecycle.
46    fn capturing(&mut self) -> &mut StreamCapture;
47
48    /// The per-launch metadata buffers this stream reuses.
49    fn info_cache(&mut self) -> &mut MetadataInfoCache<Handle>;
50
51    /// A cheap, copyable identifier for this stream.
52    ///
53    /// It exists so a fence can be recorded while the stream's own fields are
54    /// borrowed: draining the drop queue needs a fresh fence per rotation, and
55    /// the queue is reached through `&mut self`.
56    type Signal: Copy;
57
58    /// This stream's signal.
59    fn signal(&self) -> Self::Signal;
60
61    /// Record a fence on the stream `signal` names, signalled once everything
62    /// already enqueued on it has run.
63    fn fence(signal: Self::Signal) -> Self::Fence;
64}
65
66/// The layout of the device side of a copy.
67///
68/// The pitch is computed once, by [`of`](Self::of), because whether a buffer's
69/// rows are padded is the same question whichever driver performs the copy —
70/// and getting it wrong scrambles the rows rather than failing.
71///
72/// Which is why a driver cannot build one. The fields are readable, since a
73/// driver needs all four, but only `of` puts them together: a hand-built
74/// layout could carry a pitch its strides do not agree with, and nothing
75/// downstream would notice.
76#[non_exhaustive]
77pub struct CopyLayout<'a> {
78    /// The extent of each dimension.
79    pub shape: &'a Shape,
80    /// The stride of each dimension, in elements.
81    pub strides: &'a Strides,
82    /// The size of one element, in bytes.
83    pub elem_size: usize,
84    /// The 2D geometry when the rows are padded; `None` when the whole buffer
85    /// is one contiguous span and a linear copy is both correct and faster.
86    pub pitch: Option<Pitch>,
87}
88
89impl<'a> CopyLayout<'a> {
90    /// The layout of a copy over this shape, refusing anything the drivers
91    /// cannot express.
92    ///
93    /// # Errors
94    ///
95    /// [`IoError::UnsupportedStrides`] for a layout that is not pitched
96    /// row-major. A driver copies either one contiguous span or a stack of
97    /// evenly-spaced rows; nothing else has a call to make.
98    pub fn of(shape: &'a Shape, strides: &'a Strides, elem_size: usize) -> Result<Self, IoError> {
99        if !has_pitched_row_major_strides(shape, strides) {
100            return Err(IoError::UnsupportedStrides {
101                backtrace: BackTrace::capture(),
102            });
103        }
104        Ok(Self {
105            shape,
106            strides,
107            elem_size,
108            pitch: Pitch::of(shape, strides, elem_size),
109        })
110    }
111}
112
113/// The device calls a [`Command`](super::Command) cannot make itself.
114///
115/// Only these, because everything else a command does — deciding what to
116/// stage, when to reclaim, whether a layout needs a 2D copy, when the drop
117/// queue may be flushed — is the same whichever driver is underneath.
118pub trait Driver: Sized {
119    /// The multi-stream backend whose streams this driver drives.
120    type Backend: EventStreamBackend<Stream = Self::Stream>;
121    /// The backend's stream.
122    type Stream: DeviceStream;
123    /// Whatever the backend needs to launch a compiled kernel — its loaded
124    /// modules, its profiling clocks.
125    type Context;
126    /// What this backend hands a kernel at launch.
127    ///
128    /// Not simply the buffers: CUDA passes tensor-map descriptors alongside
129    /// them and HIP has none, so what a launch is given is the backend's to
130    /// say. The command only carries it from the server to [`launch`].
131    ///
132    /// [`launch`]: Self::launch
133    type LaunchArgs: ?Sized;
134    /// A kernel loaded on the device, as the server's
135    /// [`KernelLoader`](crate::compiler::KernelLoader) hands it out: launching
136    /// it takes no lookup, so nothing that empties the loader in between —
137    /// an environment switch — can leave a launch without its kernel.
138    type Loaded;
139
140    /// Hand out `size` bytes of the pinned host allocation `binding` names,
141    /// released back to the pool when the [`Bytes`] drop.
142    ///
143    /// # Safety
144    ///
145    /// `binding` names initialized host memory of at least `size` bytes, and
146    /// `resource` resolves it.
147    unsafe fn pinned_bytes(
148        binding: ManagedMemoryBinding,
149        resource: HostResource<Self>,
150        size: usize,
151    ) -> Bytes;
152
153    /// Enqueue a copy from device memory into `bytes` on `stream`.
154    ///
155    /// # Safety
156    ///
157    /// `resource` is a live device allocation of at least `bytes.len()`
158    /// readable bytes, `bytes` has room for the copy, and the caller
159    /// synchronizes `stream` before reading it back.
160    ///
161    /// # Errors
162    ///
163    /// The driver's refusal to copy.
164    unsafe fn copy_to_host(
165        resource: &DeviceResource<Self>,
166        layout: &CopyLayout<'_>,
167        bytes: &mut Bytes,
168        stream: &Self::Stream,
169    ) -> Result<(), IoError>;
170
171    /// Enqueue a copy of `data` into device memory on `stream`.
172    ///
173    /// # Safety
174    ///
175    /// `resource` is a live device allocation big enough for `data`, and
176    /// `data` stays alive until `stream` is synchronized — which is what the
177    /// caller's drop queue guarantees.
178    ///
179    /// # Errors
180    ///
181    /// The driver's refusal to copy.
182    unsafe fn copy_to_device(
183        resource: &DeviceResource<Self>,
184        layout: &CopyLayout<'_>,
185        data: &[u8],
186        stream: &Self::Stream,
187    ) -> Result<(), IoError>;
188
189    /// Enqueue a copy of `source`'s bytes into `target` on `stream`, both
190    /// device memory of the same size — how a live allocation is relocated
191    /// off an outdated page.
192    ///
193    /// # Safety
194    ///
195    /// Both resources are live device allocations of the same size that do
196    /// not overlap, and nothing reads `target` or writes `source` until the
197    /// caller synchronizes `stream`.
198    ///
199    /// # Errors
200    ///
201    /// The driver's refusal to copy.
202    unsafe fn copy_on_device(
203        source: &DeviceResource<Self>,
204        target: &DeviceResource<Self>,
205        queue: <Self::Stream as DeviceStream>::Signal,
206    ) -> Result<(), IoError>;
207
208    /// Wait for device work enqueued outside the command's streams, such as
209    /// CUDA's collectives and transfers on streams of their own, before a
210    /// relocation moves the allocations that work may still read or write.
211    ///
212    /// Nothing by default: a backend whose every device operation runs on its
213    /// command streams has nothing else to wait on.
214    ///
215    /// # Errors
216    ///
217    /// The driver's refusal to synchronize.
218    fn wait_outside_streams(_ctx: &mut Self::Context) -> Result<(), ServerError> {
219        Ok(())
220    }
221
222    /// Enqueue `kernel`, already compiled and loaded under `id`, on `stream`.
223    ///
224    /// Always a compiled kernel: the server compiles before entering its write
225    /// scope, and a skipped launch stops there, before any resource is
226    /// resolved.
227    ///
228    /// # Errors
229    ///
230    /// The driver's refusal to enqueue the launch.
231    fn launch(
232        ctx: &mut Self::Context,
233        stream: &mut Self::Stream,
234        id: &KernelId,
235        kernel: &Self::Loaded,
236        count: (u32, u32, u32),
237        args: &mut Self::LaunchArgs,
238    ) -> Result<(), LaunchError>;
239}
240
241/// The device allocation a driver's buffers resolve to.
242pub type DeviceResource<D> =
243    <<<D as Driver>::Stream as DeviceStream>::DeviceStorage as ComputeStorage>::Resource;
244
245/// The host allocation a driver's staging buffers resolve to.
246pub type HostResource<D> =
247    <<<D as Driver>::Stream as DeviceStream>::HostStorage as ComputeStorage>::Resource;