Skip to main content

cubecl_runtime/server/
handle.rs

1use cubecl_common::device::ServiceId;
2use cubecl_environment::stream::StreamId;
3use cubecl_zspace::{Shape, Strides};
4
5use crate::{
6    memory_management::{
7        ManagedMemoryBinding, ManagedMemoryHandle, ManagedMemoryId, WeakMemoryBinding,
8    },
9    server::{CopyDescriptor, TensorMapBinding},
10};
11
12/// Server handle containing the [memory handle](crate::server::Handle).
13pub struct Handle {
14    /// Memory handle.
15    pub memory: ManagedMemoryHandle,
16    /// The service whose memory this handle addresses. A client checks it
17    /// before handing the handle to its device: memory coordinates mean
18    /// nothing on another device, and reading them there is not an error
19    /// but garbage.
20    pub service: ServiceId,
21    /// Memory offset in bytes.
22    pub offset_start: Option<u64>,
23    /// Memory offset in bytes.
24    pub offset_end: Option<u64>,
25    /// The stream where the data was created.
26    pub stream: StreamId,
27    /// Length of the underlying buffer ignoring offsets
28    pub(crate) size: u64,
29}
30
31impl core::fmt::Debug for Handle {
32    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
33        f.debug_struct("Handle")
34            .field("id", &self.memory)
35            .field("service", &self.service)
36            .field("offset_start", &self.offset_start)
37            .field("offset_end", &self.offset_end)
38            .field("stream", &self.stream)
39            .field("size", &self.size)
40            .finish()
41    }
42}
43
44impl Clone for Handle {
45    fn clone(&self) -> Self {
46        Self {
47            memory: self.memory.clone(),
48            service: self.service,
49            offset_start: self.offset_start,
50            offset_end: self.offset_end,
51            stream: self.stream,
52            size: self.size,
53        }
54    }
55}
56
57impl Handle {
58    /// Creates a new handle of the given size.
59    pub fn from_memory(
60        id: ManagedMemoryHandle,
61        service: ServiceId,
62        stream: StreamId,
63        size: u64,
64    ) -> Self {
65        Self {
66            memory: id,
67            service,
68            offset_start: None,
69            offset_end: None,
70            stream,
71            size,
72        }
73    }
74    /// Creates a new handle of the given size.
75    pub fn new(service: ServiceId, stream: StreamId, size: u64) -> Self {
76        Self {
77            memory: ManagedMemoryHandle::new(),
78            service,
79            offset_start: None,
80            offset_end: None,
81            stream,
82            size,
83        }
84    }
85    /// Checks whether the handle can be mutated in-place without affecting other computation.
86    pub fn can_mut(&self) -> bool {
87        self.memory.can_mut()
88    }
89
90    /// Returns the [`BufferBinding`] corresponding to the current handle.
91    pub fn binding(self) -> BufferBinding {
92        BufferBinding {
93            memory: self.memory.binding(),
94            service: self.service,
95            offset_start: self.offset_start,
96            offset_end: self.offset_end,
97            stream: self.stream,
98            size: self.size,
99        }
100    }
101
102    /// Add to the current offset in bytes.
103    pub fn offset_start(mut self, offset: u64) -> Self {
104        if let Some(val) = &mut self.offset_start {
105            *val += offset;
106        } else {
107            self.offset_start = Some(offset);
108        }
109
110        self
111    }
112    /// Add to the current offset in bytes.
113    pub fn offset_end(mut self, offset: u64) -> Self {
114        if let Some(val) = &mut self.offset_end {
115            *val += offset;
116        } else {
117            self.offset_end = Some(offset);
118        }
119
120        self
121    }
122
123    /// Convert the [handle](Handle) into a [binding](Binding) with shape and stride metadata.
124    pub fn copy_descriptor(
125        self,
126        shape: Shape,
127        strides: Strides,
128        elem_size: usize,
129    ) -> CopyDescriptor {
130        CopyDescriptor {
131            shape,
132            strides,
133            elem_size,
134            handle: self.binding(),
135        }
136    }
137    /// Get the size of the handle, in bytes, accounting for offsets
138    pub fn size_in_used(&self) -> u64 {
139        self.size - self.offset_start.unwrap_or(0) - self.offset_end.unwrap_or(0)
140    }
141    /// Get the total size of the handle, in bytes.
142    pub fn size(&self) -> u64 {
143        self.size
144    }
145}
146
147/// A resource passed to a kernel function
148#[allow(clippy::large_enum_variant)]
149#[derive(Clone, Debug)]
150pub enum KernelResource {
151    /// Buffer resource
152    Buffer(BufferBinding),
153    /// Tensor map resource for CUDA
154    TensorMap(TensorMapBinding),
155}
156
157/// A buffer binding represents a [Handle] that is bound to managed memory.
158///
159/// The memory used is known by the compute server.
160/// A buffer binding is only valid after being initlized with [`super::Server::initialize_bindings`]
161///
162/// # Notes
163///
164/// A buffer binding is detached from a [`Handle`], meaning that is won't affect [`Handle::can_mut`].
165#[derive(Clone, Debug)]
166pub struct BufferBinding {
167    /// The id of the handle the binding is bound to.
168    pub memory: ManagedMemoryBinding,
169    /// The service whose memory this binding addresses; see [`Handle::service`].
170    pub service: ServiceId,
171    /// Memory offset in bytes.
172    pub offset_start: Option<u64>,
173    /// Memory offset in bytes.
174    pub offset_end: Option<u64>,
175    /// The stream where the data was created.
176    pub stream: StreamId,
177    /// Length of the underlying buffer ignoring offsets
178    pub size: u64,
179}
180
181/// A [`BufferBinding`] that does not keep its memory reserved: what a record
182/// of a buffer holds when holding the buffer would change what its pool may
183/// reuse.
184#[derive(Clone, Debug)]
185pub struct WeakBufferBinding {
186    memory: WeakMemoryBinding,
187    service: ServiceId,
188    offset_start: Option<u64>,
189    offset_end: Option<u64>,
190    stream: StreamId,
191    size: u64,
192}
193
194impl WeakBufferBinding {
195    /// The binding back, while its memory is allocated (see
196    /// [`WeakMemoryBinding::upgrade`]).
197    pub fn upgrade(&self) -> Option<BufferBinding> {
198        Some(BufferBinding {
199            memory: self.memory.upgrade()?,
200            service: self.service,
201            offset_start: self.offset_start,
202            offset_end: self.offset_end,
203            stream: self.stream,
204            size: self.size,
205        })
206    }
207}
208
209impl BufferBinding {
210    /// This binding, without keeping its memory reserved.
211    pub fn downgrade(&self) -> WeakBufferBinding {
212        WeakBufferBinding {
213            memory: self.memory.downgrade(),
214            service: self.service,
215            offset_start: self.offset_start,
216            offset_end: self.offset_end,
217            stream: self.stream,
218            size: self.size,
219        }
220    }
221
222    /// Get the size of the handle, in bytes, accounting for offsets
223    pub fn size_in_used(&self) -> u64 {
224        self.size - self.offset_start.unwrap_or(0) - self.offset_end.unwrap_or(0)
225    }
226
227    /// The byte range of the allocation this binding names: what the offsets
228    /// leave of the buffer. This is the region the taint bookkeeping claims
229    /// when work writing through this binding fails, and releases when work
230    /// writing through it lands.
231    pub fn range(&self) -> core::ops::Range<u64> {
232        self.offset_start.unwrap_or(0)..self.size - self.offset_end.unwrap_or(0)
233    }
234
235    /// The identity a claim on this binding is stored under: the allocation
236    /// and the byte range of it, exactly as [`range`](Self::range) computes
237    /// it.
238    ///
239    /// Anything that deduplicates or compares claims — a capture's write set,
240    /// above all — must key by this and nothing coarser. Two tensors carved
241    /// from one batched allocation share a memory id and nothing else; keyed
242    /// by the id alone, one sibling's claim swallows the others'.
243    pub fn claim_key(&self) -> (ManagedMemoryId, u64, u64) {
244        let range = self.range();
245        (self.memory.id(), range.start, range.end)
246    }
247    /// Get the total size of the handle, in bytes.
248    pub fn size(&self) -> u64 {
249        self.size
250    }
251}