Skip to main content

cubecl_runtime/memory_management/memory_pool/
handle.rs

1use crate::memory_management::MemoryHandle;
2use alloc::{sync::Arc, vec::Vec};
3use core::cell::Cell;
4
5/// Managed Memory handle
6#[derive(Debug)]
7pub struct ManagedMemoryHandle {
8    descriptor: Arc<ManagedMemoryDescriptor>,
9    // Holds only the reference counts of the handle.
10    handle_count: Arc<()>,
11}
12
13/// Binding of a memory handle
14#[derive(Debug)]
15pub struct ManagedMemoryBinding {
16    descriptor: Arc<ManagedMemoryDescriptor>,
17}
18
19/// A list of bindings that are shared across multiple streams.
20#[derive(Debug, Default)]
21pub struct SharedMemoryBindings {
22    /// The bindings.
23    pub bindings: Vec<ManagedMemoryBinding>,
24}
25
26impl Clone for ManagedMemoryHandle {
27    fn clone(&self) -> Self {
28        Self {
29            descriptor: self.descriptor.clone(),
30            handle_count: self.handle_count.clone(),
31        }
32    }
33}
34
35/// Managed memory descriptor.
36///
37/// The location is wrapped in `Cell` for interior mutability: multiple
38/// handles share the same descriptor via `Arc`, yet the memory management
39/// system needs to update the location after creation (e.g. during
40/// `reserve` / `bind`). All mutation happens on a single device thread,
41/// so `Cell` is safe — we just need `unsafe impl Sync` because `Cell`
42/// is `!Sync`.
43///
44/// An alternative would be `spin::Mutex<MemoryLocation>` which avoids the
45/// `unsafe impl Sync` at the cost of a lock on every access.
46pub(crate) struct ManagedMemoryDescriptor {
47    pub(crate) id: ManagedMemoryId,
48    location: Cell<MemoryLocation>,
49}
50
51// SAFETY: The channel requires ManagedMemoryHandle to be Send + Sync.
52// Cell is _not_ Sync, but, we know that we only access this from the device thread,
53// so we lie to the compiler and claim it is Sync. Other code must NOT rely on
54// ManagedMemoryDescriptor being Send + Sync.
55unsafe impl Send for ManagedMemoryDescriptor {}
56unsafe impl Sync for ManagedMemoryDescriptor {}
57
58impl core::fmt::Debug for ManagedMemoryDescriptor {
59    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
60        f.debug_struct("ManagedMemoryDescriptor")
61            .field("id", &self.id)
62            .field("location", &self.location())
63            .finish()
64    }
65}
66
67#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
68/// Managed memory unique identifier.
69pub struct ManagedMemoryId {
70    pub(crate) value: usize,
71}
72
73impl PartialEq for ManagedMemoryDescriptor {
74    fn eq(&self, other: &Self) -> bool {
75        self.id == other.id
76    }
77}
78
79impl Eq for ManagedMemoryDescriptor {}
80
81#[derive(Clone, Copy, Debug)]
82/// Defines where the [`ManagedMemoryId`] is located.
83pub(crate) struct MemoryLocation {
84    /// The memory pool index in the global memory management.
85    pub pool: u8,
86    /// The memory page index in a memory pool.
87    pub page: u16,
88    /// The memory slice index in a memory page.
89    pub slice: u32,
90    /// Whether the memory location is known/initialized.
91    pub init: u8,
92}
93
94impl ManagedMemoryDescriptor {
95    /// Update the memory location for the given [`ManagedMemoryId`].
96    pub(crate) fn update_location(&self, location: MemoryLocation) {
97        self.location.set(location);
98    }
99
100    /// Update only the slice position for the given [`ManagedMemoryId`].
101    pub(crate) fn update_slice(&self, slice: u32) {
102        self.location.update(|mut loc| {
103            loc.slice = slice;
104            loc
105        });
106    }
107
108    /// Update only the memory page position for the given [`ManagedMemoryId`].
109    pub fn update_page(&self, page: u16) {
110        self.location.update(|mut loc| {
111            loc.page = page;
112            loc
113        });
114    }
115
116    /// Retrieves the current location.
117    pub(crate) fn location(&self) -> MemoryLocation {
118        self.location.get()
119    }
120
121    pub(crate) fn slice(&self) -> usize {
122        self.location.get().slice as usize
123    }
124
125    pub(crate) fn page(&self) -> usize {
126        self.location.get().page as usize
127    }
128}
129
130impl MemoryLocation {
131    /// Creates a new memory location.
132    pub(crate) fn new(pool: u8, page: u16, slice: u32) -> Self {
133        Self {
134            pool,
135            page,
136            slice,
137            init: 1,
138        }
139    }
140
141    /// Creates a new uninitialized memory location.
142    pub(crate) fn uninit() -> Self {
143        Self {
144            pool: 0,
145            page: 0,
146            slice: 0,
147            init: 0,
148        }
149    }
150}
151
152impl ManagedMemoryHandle {
153    /// Creates a new managed memory handle.
154    pub fn new() -> Self {
155        let value = Self::gen_id();
156
157        Self {
158            descriptor: Arc::new(ManagedMemoryDescriptor {
159                id: ManagedMemoryId { value },
160                location: Cell::new(MemoryLocation::uninit()),
161            }),
162            handle_count: Arc::new(()),
163        }
164    }
165
166    /// Retrieves the descriptor for the current handle.
167    pub(crate) fn descriptor(&self) -> &ManagedMemoryDescriptor {
168        &self.descriptor
169    }
170
171    /// Return whether the current handle can be modified in-place.
172    pub fn can_mut(&self) -> bool {
173        Arc::strong_count(&self.handle_count) <= 2
174    }
175
176    /// Return whether the current handle is free.
177    pub fn is_free(&self) -> bool {
178        Arc::strong_count(&self.descriptor) <= 1
179    }
180
181    /// Returns the binding for the current handle.
182    pub fn binding(self) -> ManagedMemoryBinding {
183        ManagedMemoryBinding {
184            descriptor: self.descriptor.clone(),
185        }
186    }
187
188    fn gen_id() -> usize {
189        static COUNTER: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
190        let value = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
191        if value == usize::MAX {
192            core::panic!("Memory ID overflowed");
193        }
194        value
195    }
196}
197
198impl ManagedMemoryBinding {
199    /// Retrieves the descriptor for the current binding.
200    pub(crate) fn descriptor(&self) -> &ManagedMemoryDescriptor {
201        &self.descriptor
202    }
203}
204
205impl Default for ManagedMemoryHandle {
206    fn default() -> Self {
207        Self::new()
208    }
209}
210
211impl Clone for ManagedMemoryBinding {
212    fn clone(&self) -> Self {
213        Self {
214            descriptor: self.descriptor.clone(),
215        }
216    }
217}
218
219impl MemoryHandle<ManagedMemoryBinding> for ManagedMemoryHandle {
220    fn can_mut(&self) -> bool {
221        self.can_mut()
222    }
223
224    fn binding(self) -> ManagedMemoryBinding {
225        self.binding()
226    }
227}
228
229impl SharedMemoryBindings {
230    /// Clears the shared bindings list.
231    pub fn clear(&mut self) {
232        self.bindings.clear();
233    }
234
235    /// Returns true if the shared bindings list is empty.
236    pub fn is_empty(&self) -> bool {
237        self.bindings.is_empty()
238    }
239
240    /// Push a memory binding to the list of shared bindings.
241    pub fn push(&mut self, binding: ManagedMemoryBinding) {
242        self.bindings.push(binding)
243    }
244}
245
246impl cubecl_common::pool::Reclaim for SharedMemoryBindings {
247    fn reclaim(&mut self) {
248        self.clear();
249    }
250}
251
252/// Calculates a best-effort heuristic for the alignment of row-aligned tensors.
253/// Prefers contiguous alignments for unit dimensions, 16-byte minimum alignment for non-unit,
254/// scaling with input size up to `buffer_align`.
255pub fn optimal_align(shape: usize, elem_size: usize, buffer_align: usize) -> usize {
256    if shape == 1 {
257        elem_size
258    } else {
259        (shape * elem_size)
260            .next_power_of_two()
261            .clamp(16, buffer_align)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_memory_id_mutability() {
271        let handle1 = ManagedMemoryHandle::new();
272        handle1.descriptor().update_slice(4);
273        assert_eq!(handle1.descriptor().slice(), 4);
274
275        let handle2 = ManagedMemoryHandle::new();
276        handle2
277            .clone()
278            .descriptor()
279            .update_location(handle1.descriptor().location());
280        assert_eq!(handle2.descriptor().slice(), 4);
281    }
282
283    #[test]
284    fn test_location_visible_through_shared_arc() {
285        let handle = ManagedMemoryHandle::new();
286        let handle2 = handle.clone();
287
288        let location = MemoryLocation::new(1, 2, 3);
289        handle.descriptor().update_location(location);
290
291        assert_eq!(handle2.descriptor().location().pool, 1);
292        assert_eq!(handle2.descriptor().location().page, 2);
293        assert_eq!(handle2.descriptor().location().slice, 3);
294        assert_eq!(handle2.descriptor().location().init, 1);
295
296        handle.descriptor().update_slice(42);
297        assert_eq!(handle2.descriptor().slice(), 42);
298    }
299}