cubecl_server/memory_management/relocation/base.rs
1//! Moving live allocations off outdated pages, so those pages go back to the
2//! driver now rather than when their longest-lived allocation ends.
3//!
4//! A [`Relocation`] is planned by the pools it empties, copied on the device
5//! through a [`CopyQueue`], and committed by those pools again from the
6//! [`Landed`] relocation the copy step hands back: an allocation cannot move
7//! before its bytes are where it moves to.
8
9use super::CopyQueue;
10use crate::memory_management::{ManagedMemoryHandle, release_or_leak};
11use crate::server::ServerError;
12use crate::storage::ComputeStorage;
13use crate::storage::StorageHandle;
14use alloc::vec::Vec;
15
16/// Live allocations on outdated pages, each with a slice reserved on a current
17/// page to receive it.
18///
19/// Nothing has moved yet: dropping it abandons the relocation with nothing
20/// lost, the reserved slices freed with it.
21#[derive(Debug)]
22pub struct Relocation {
23 moves: Vec<Move>,
24}
25
26/// Where a relocation may reserve the slices it moves allocations into.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum TargetRoom {
29 /// Only the room the current pages already have. Memory is what is short,
30 /// and a relocation that allocated a page would spend more than it frees.
31 Held,
32 /// New pages of the current size too, when the room held runs out. Memory
33 /// is not what is short, so emptying every outdated page is worth a page.
34 MayAllocate,
35}
36
37/// A [`Relocation`] whose copies have landed: what the pools commit, and only
38/// [`Relocation::copy`] makes.
39#[derive(Debug)]
40pub struct Landed {
41 moves: Vec<Move>,
42}
43
44/// One device copy a relocation needs, between two resolved storages of the
45/// same size.
46#[derive(Debug, Clone)]
47pub struct StorageCopy {
48 /// Where the bytes are now.
49 pub source: StorageHandle,
50 /// Where they go.
51 pub target: StorageHandle,
52}
53
54/// One live allocation moving to a reserved slice: `allocation`'s handle is
55/// handed over to the target once `copy` has landed.
56#[derive(Debug)]
57pub(crate) struct Move {
58 /// The allocation being moved, as its owners hold it.
59 pub allocation: ManagedMemoryHandle,
60 /// The slice reserved to receive it.
61 pub target: ManagedMemoryHandle,
62 /// The bytes to copy, or `None` when the source was carved under a dry run
63 /// and never resolved: there is nothing behind it to copy.
64 pub copy: Option<StorageCopy>,
65}
66
67impl Relocation {
68 /// A relocation of `moves`, each with its target already reserved.
69 pub(crate) fn new(moves: Vec<Move>) -> Self {
70 Self { moves }
71 }
72
73 /// How many allocations move.
74 pub fn len(&self) -> usize {
75 self.moves.len()
76 }
77
78 /// Whether there is nothing to move.
79 pub fn is_empty(&self) -> bool {
80 self.moves.is_empty()
81 }
82
83 /// Copy every allocation's bytes on `queue`, waiting for the device
84 /// before the first copy and for the copies after the last.
85 ///
86 /// The wait for the copies happens whether every one of them was enqueued
87 /// or not, so a relocation abandoned here never frees a target a copy is
88 /// still writing.
89 ///
90 /// # Errors
91 ///
92 /// The first copy the device refused, or the fault a wait revealed. The
93 /// relocation is abandoned either way.
94 pub fn copy<Storage: ComputeStorage>(
95 self,
96 storage: &mut Storage,
97 queue: &mut dyn CopyQueue<Storage>,
98 ) -> Result<Landed, ServerError> {
99 queue.wait_device()?;
100 let enqueued = self
101 .moves
102 .iter()
103 .filter_map(|relocated| relocated.copy.as_ref())
104 .try_for_each(|copy| queue.copy(storage, copy));
105 if let Err(err) = queue.wait_copies() {
106 release_or_leak(self, &err);
107 enqueued?;
108 return Err(err);
109 }
110 enqueued?;
111 Ok(Landed { moves: self.moves })
112 }
113}
114
115impl Landed {
116 /// The moves, for the pools that planned them to commit.
117 pub(crate) fn into_moves(self) -> Vec<Move> {
118 self.moves
119 }
120}
121
122#[cfg(all(test, feature = "storage-bytes"))]
123mod tests {
124 use super::*;
125 use crate::{driver::DriverError, server::IoError, storage::BytesStorage};
126
127 struct FailedWait;
128
129 impl CopyQueue<BytesStorage> for FailedWait {
130 fn wait_device(&mut self) -> Result<(), ServerError> {
131 Ok(())
132 }
133
134 fn copy(&mut self, _: &mut BytesStorage, _: &StorageCopy) -> Result<(), IoError> {
135 Ok(())
136 }
137
138 fn wait_copies(&mut self) -> Result<(), ServerError> {
139 Err(DriverError::new("wait", 201).into())
140 }
141 }
142
143 #[test]
144 fn failed_wait_keeps_pending_relocation_reserved() {
145 let mut storage = BytesStorage::default();
146 let source = ManagedMemoryHandle::new();
147 let target = ManagedMemoryHandle::new();
148 let relocation = Relocation::new(alloc::vec![Move {
149 allocation: source.clone(),
150 target: target.clone(),
151 copy: Some(StorageCopy {
152 source: storage.alloc(4).unwrap(),
153 target: storage.alloc(4).unwrap(),
154 }),
155 }]);
156 assert!(relocation.copy(&mut storage, &mut FailedWait).is_err());
157 assert!(!source.is_free());
158 assert!(!target.is_free());
159 }
160}