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};
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/// Four, because everything else a command does — deciding what to stage, when
116/// to reclaim, whether a layout needs a 2D copy, when the drop queue may be
117/// 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
135 /// Hand out `size` bytes of the pinned host allocation `binding` names,
136 /// released back to the pool when the [`Bytes`] drop.
137 ///
138 /// # Safety
139 ///
140 /// `binding` names initialized host memory of at least `size` bytes, and
141 /// `resource` resolves it.
142 unsafe fn pinned_bytes(
143 binding: ManagedMemoryBinding,
144 resource: HostResource<Self>,
145 size: usize,
146 ) -> Bytes;
147
148 /// Enqueue a copy from device memory into `bytes` on `stream`.
149 ///
150 /// # Safety
151 ///
152 /// `resource` is a live device allocation of at least `bytes.len()`
153 /// readable bytes, `bytes` has room for the copy, and the caller
154 /// synchronizes `stream` before reading it back.
155 ///
156 /// # Errors
157 ///
158 /// The driver's refusal to copy.
159 unsafe fn copy_to_host(
160 resource: &DeviceResource<Self>,
161 layout: &CopyLayout<'_>,
162 bytes: &mut Bytes,
163 stream: &Self::Stream,
164 ) -> Result<(), IoError>;
165
166 /// Enqueue a copy of `data` into device memory on `stream`.
167 ///
168 /// # Safety
169 ///
170 /// `resource` is a live device allocation big enough for `data`, and
171 /// `data` stays alive until `stream` is synchronized — which is what the
172 /// caller's drop queue guarantees.
173 ///
174 /// # Errors
175 ///
176 /// The driver's refusal to copy.
177 unsafe fn copy_to_device(
178 resource: &DeviceResource<Self>,
179 layout: &CopyLayout<'_>,
180 data: &[u8],
181 stream: &Self::Stream,
182 ) -> Result<(), IoError>;
183
184 /// Enqueue an already-compiled kernel on `stream`.
185 ///
186 /// Always a compiled kernel: the server compiles before entering its write
187 /// scope, and a skipped launch stops there, before any resource is
188 /// resolved.
189 ///
190 /// # Errors
191 ///
192 /// The driver's refusal to enqueue the launch.
193 fn launch(
194 ctx: &mut Self::Context,
195 stream: &mut Self::Stream,
196 kernel: KernelId,
197 count: (u32, u32, u32),
198 args: &mut Self::LaunchArgs,
199 ) -> Result<(), LaunchError>;
200}
201
202/// The device allocation a driver's buffers resolve to.
203pub type DeviceResource<D> =
204 <<<D as Driver>::Stream as DeviceStream>::DeviceStorage as ComputeStorage>::Resource;
205
206/// The host allocation a driver's staging buffers resolve to.
207pub type HostResource<D> =
208 <<<D as Driver>::Stream as DeviceStream>::HostStorage as ComputeStorage>::Resource;