cubecl_server/memory_management/relocation/
trigger.rs1use super::TargetRoom;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ArenaState {
9 pub page_size: u64,
11 pub has_outdated: bool,
13 pub full: bool,
15 pub shape: ArenaShape,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct ArenaShape {
28 pub current_pages: u64,
30 pub outdated_pools: usize,
32 pub outdated_pages: u64,
34 pub outdated_guarded: usize,
36}
37
38#[derive(Debug, Clone)]
41pub struct RelocationTrigger {
42 capacity: Option<u64>,
44 stalled: Option<ArenaShape>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum RelocationNeed {
54 Nothing,
57 ArenaFull,
59 UnderPressure {
62 capacity: u64,
64 page_size: u64,
66 },
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum RelocationReason {
72 Explicit,
74 MemoryPressure,
76 ArenaFull,
79 Capture,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86enum MemoryPressure {
87 Low,
89 High,
93}
94
95impl RelocationTrigger {
96 pub fn new(capacity: Option<u64>) -> Self {
98 Self {
99 capacity,
100 stalled: None,
101 }
102 }
103
104 pub fn need(&self, arena: &ArenaState) -> RelocationNeed {
106 if !arena.has_outdated || self.stalled == Some(arena.shape) {
107 return RelocationNeed::Nothing;
108 }
109 if arena.full {
110 return RelocationNeed::ArenaFull;
111 }
112 match self.capacity {
113 Some(capacity) => RelocationNeed::UnderPressure {
114 capacity,
115 page_size: arena.page_size,
116 },
117 None => RelocationNeed::Nothing,
118 }
119 }
120
121 pub fn moved(&mut self) {
124 self.stalled = None;
125 }
126
127 pub fn stalled(&mut self, arena: &ArenaState) {
130 self.stalled = Some(arena.shape);
131 }
132}
133
134impl RelocationNeed {
135 pub fn reason(self, allocated: impl FnOnce() -> u64) -> Option<RelocationReason> {
138 match self {
139 RelocationNeed::Nothing => None,
140 RelocationNeed::ArenaFull => Some(RelocationReason::ArenaFull),
141 RelocationNeed::UnderPressure {
142 capacity,
143 page_size,
144 } => match MemoryPressure::new(allocated(), capacity, page_size) {
145 MemoryPressure::High => Some(RelocationReason::MemoryPressure),
146 MemoryPressure::Low => None,
147 },
148 }
149 }
150}
151
152impl RelocationReason {
153 pub fn room(self) -> TargetRoom {
156 match self {
157 RelocationReason::Explicit | RelocationReason::MemoryPressure => TargetRoom::Held,
158 RelocationReason::ArenaFull | RelocationReason::Capture => TargetRoom::MayAllocate,
159 }
160 }
161}
162
163impl MemoryPressure {
164 fn new(allocated: u64, capacity: u64, page_size: u64) -> Self {
167 match allocated + page_size > capacity.saturating_sub(page_size) {
168 true => MemoryPressure::High,
169 false => MemoryPressure::Low,
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 const MIB: u64 = 1024 * 1024;
179
180 fn arena(page_size: u64) -> ArenaState {
181 ArenaState {
182 page_size,
183 has_outdated: true,
184 full: false,
185 shape: ArenaShape {
186 current_pages: 1,
187 outdated_pools: 1,
188 outdated_pages: 1,
189 outdated_guarded: 0,
190 },
191 }
192 }
193
194 #[test]
195 fn pressure_is_high_once_the_next_page_leaves_less_than_another() {
196 let need = RelocationTrigger::new(Some(24 * MIB)).need(&arena(4 * MIB));
197 assert_eq!(need.reason(|| 10 * MIB), None);
198 assert_eq!(
199 need.reason(|| 17 * MIB),
200 Some(RelocationReason::MemoryPressure)
201 );
202 }
203
204 #[test]
205 fn nothing_outdated_needs_nothing_and_sums_nothing() {
206 let state = ArenaState {
207 has_outdated: false,
208 ..arena(4 * MIB)
209 };
210 let need = RelocationTrigger::new(Some(MIB)).need(&state);
211 assert_eq!(need, RelocationNeed::Nothing);
212 assert_eq!(need.reason(|| panic!("the bytes are never summed")), None);
213 }
214
215 #[test]
216 fn a_full_arena_relocates_whatever_the_device_holds() {
217 let state = ArenaState {
218 full: true,
219 ..arena(4 * MIB)
220 };
221 assert_eq!(
222 RelocationTrigger::new(None).need(&state).reason(|| 0),
223 Some(RelocationReason::ArenaFull)
224 );
225 }
226
227 #[test]
228 fn a_device_that_reports_no_capacity_is_not_judged() {
229 assert_eq!(
230 RelocationTrigger::new(None).need(&arena(MIB)),
231 RelocationNeed::Nothing
232 );
233 }
234
235 #[test]
236 fn a_relocation_that_moved_nothing_waits_for_the_pools_to_change() {
237 let mut trigger = RelocationTrigger::new(Some(24 * MIB));
238 let state = arena(4 * MIB);
239 trigger.stalled(&state);
240 assert_eq!(trigger.need(&state), RelocationNeed::Nothing);
241
242 let grown = ArenaState {
243 shape: ArenaShape {
244 current_pages: 2,
245 ..state.shape
246 },
247 ..state
248 };
249 assert_ne!(trigger.need(&grown), RelocationNeed::Nothing);
250 }
251}