Skip to main content

cubecl_runtime/memory_management/
mod.rs

1mod base;
2mod config;
3mod handle;
4mod layout;
5
6pub use base::*;
7pub use config::*;
8pub use handle::*;
9pub use layout::*;
10
11use alloc::vec::Vec;
12
13/// The type of memory pool to use.
14#[derive(Debug, Clone)]
15pub enum PoolType {
16    /// Use a memory where every allocation is a separate page.
17    ExclusivePages {
18        /// The minimum number of bytes to allocate in this pool.
19        max_alloc_size: u64,
20    },
21    /// Give every allocation its own device allocation, sized to the request
22    /// and reused by exact size.
23    ///
24    /// No carving at all, so it wastes only alignment padding. Worth it where
25    /// padding matters more than allocation count — under a
26    /// [`DryRun`](crate::dry_run::DryRun), where unresolved reservations never
27    /// reach the driver at all, or on a device the workload barely fits.
28    Direct {
29        /// Reserved bytes above which free slices are returned to the driver.
30        /// A watermark rather than a budget; `None` reclaims only on an
31        /// explicit cleanup.
32        reclaim_at: Option<u64>,
33    },
34    /// Use a memory where each allocation is a slice of a bigger allocation.
35    SlicedPages {
36        /// The page size to allocate.
37        page_size: u64,
38        /// The maximum size of a slice to allocate in the pool.
39        max_slice_size: u64,
40        /// Hard cap on the total bytes of pages this pool may hold.
41        ///
42        /// The effective cap is `floor(max_pool_size / page_size)` whole pages.
43        /// If `max_pool_size < page_size`, the page size is shrunk to the
44        /// (alignment-rounded) cap so the budget is honored with a single page.
45        /// When the cap is reached and no free slice fits after coalescing,
46        /// reserving returns [`IoError`](crate::server::IoError)
47        /// `PoolCapacityExceeded` instead of silently growing. `None` (the
48        /// previous behavior) keeps unbounded growth.
49        ///
50        /// Note: runtimes that create one memory management per stream (CUDA,
51        /// HIP) apply the cap per stream.
52        max_pool_size: Option<u64>,
53    },
54}
55
56/// Options to create a memory pool.
57#[derive(Debug, Clone)]
58pub struct MemoryPoolOptions {
59    /// What kind of pool to use.
60    pub pool_type: PoolType,
61    /// Period after which allocations are deemed unused and deallocated.
62    ///
63    /// This period is measured in the number of allocations in the parent allocator. If a page
64    /// in the pool was unused for the entire period, it will be deallocated. This period is
65    /// approximmate, as checks are only done occasionally.
66    pub dealloc_period: Option<u64>,
67}
68
69/// High level configuration of memory management.
70#[derive(Clone, Debug)]
71pub enum MemoryConfiguration {
72    /// The default preset, which uses pools that allocate sub slices.
73    #[cfg(not(exclusive_memory_only))]
74    SubSlices,
75    /// Default preset for using exclusive pages.
76    /// This can be necessary for backends don't support sub-slices.
77    ExclusivePages,
78    /// Custom settings.
79    Custom {
80        /// Options for each pool to construct. When allocating, the first
81        /// possible pool will be picked for an allocation.
82        pool_options: Vec<MemoryPoolOptions>,
83    },
84}
85
86#[allow(clippy::derivable_impls)]
87impl Default for MemoryConfiguration {
88    fn default() -> Self {
89        #[cfg(exclusive_memory_only)]
90        {
91            MemoryConfiguration::ExclusivePages
92        }
93        #[cfg(not(exclusive_memory_only))]
94        {
95            MemoryConfiguration::SubSlices
96        }
97    }
98}
99
100#[derive(Default, Clone, Copy, Debug)]
101/// The mode of allocation used.
102pub enum MemoryAllocationMode {
103    /// Use the automatic memory management strategy for allocation.
104    #[default]
105    Auto,
106    /// Use a persistent memory management strategy, meaning that all allocations are for data that is
107    /// likely never going to be freed.
108    Persistent,
109}
110
111/// Why installing a dynamic pool layout did not take effect.
112///
113/// The layout itself was already valid — that is
114/// [`PoolConfigError`](PoolConfigError), reported when the configuration is
115/// resolved. This is about the pools' *state* at the moment of the swap.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum InstallMemoryPoolsError {
118    /// The dynamic pools still hold live allocations, so the old layout was
119    /// kept. A live slice carries its pool position, and swapping the pool
120    /// list under it would leave that position pointing at a different pool.
121    ///
122    /// Transient: retry once whatever holds them drains. A cleanup that does
123    /// not clear it usually means a cache is holding slices (the metadata
124    /// info cache) or a captured graph is pinning them.
125    PoolsInUse {
126        /// Bytes still live in the dynamic pools.
127        bytes_in_use: u64,
128    },
129    /// This server has no configurable dynamic pools. Permanent — unlike
130    /// [`PoolsInUse`](Self::PoolsInUse), retrying will never succeed.
131    Unsupported,
132}
133
134impl core::fmt::Display for InstallMemoryPoolsError {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        match self {
137            InstallMemoryPoolsError::PoolsInUse { bytes_in_use } => write!(
138                f,
139                "the dynamic pools kept their layout: {bytes_in_use} bytes are still live in them"
140            ),
141            InstallMemoryPoolsError::Unsupported => {
142                write!(f, "this server has no configurable dynamic memory pools")
143            }
144        }
145    }
146}
147
148impl core::error::Error for InstallMemoryPoolsError {}