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