1use crate::{
2 memory_management::optimal_align,
3 server::{
4 Handle, MemoryLayout, MemoryLayoutDescriptor, MemoryLayoutPolicy, MemoryLayoutStrategy,
5 },
6};
7use alloc::vec::Vec;
8use cubecl_common::device::ServiceId;
9use cubecl_environment::stream::StreamId;
10use cubecl_zspace::{Shape, Strides, strides};
11
12pub struct ContiguousMemoryLayoutPolicy {
14 mem_alignment: usize,
15}
16
17pub struct PitchedMemoryLayoutPolicy {
19 mem_alignment: usize,
20}
21
22impl MemoryLayoutPolicy for PitchedMemoryLayoutPolicy {
23 fn apply(
24 &self,
25 service: ServiceId,
26 stream_id: StreamId,
27 descriptors: &[MemoryLayoutDescriptor],
28 ) -> (Handle, Vec<MemoryLayout>) {
29 let mut total_size = 0u64;
30
31 let (sizes, strides): (Vec<_>, Vec<_>) = descriptors
32 .iter()
33 .map(|descriptor| {
34 let last_dim = descriptor.shape.last().copied().unwrap_or(1);
35 let pitch_align = match descriptor.strategy {
36 MemoryLayoutStrategy::Contiguous => 1,
37 MemoryLayoutStrategy::Optimized => {
38 optimal_align(last_dim, descriptor.elem_size, self.mem_alignment)
39 }
40 };
41
42 let rank = descriptor.shape.len();
43 let width = *descriptor.shape.last().unwrap_or(&1);
44 let height: usize = descriptor.shape.iter().rev().skip(1).product();
45 let height = Ord::max(height, 1);
46
47 let width_bytes = width * descriptor.elem_size;
48 let pitch = width_bytes.next_multiple_of(pitch_align);
49 let size = height * pitch;
50
51 let mut strides = strides![1; rank];
52 if rank > 1 {
53 strides[rank - 2] = pitch / descriptor.elem_size;
54 }
55 if rank > 2 {
56 for i in (0..rank - 2).rev() {
57 strides[i] = strides[i + 1] * descriptor.shape[i + 1];
58 }
59 }
60 total_size += size.next_multiple_of(self.mem_alignment) as u64;
61 (size, strides)
62 })
63 .unzip();
64
65 let base_handle = Handle::new(service, stream_id, total_size);
66
67 let layouts = offset_handles(base_handle.clone(), &sizes, self.mem_alignment)
68 .into_iter()
69 .zip(strides)
70 .map(|(handle, strides)| MemoryLayout::new(handle, strides))
71 .collect();
72 (base_handle, layouts)
73 }
74}
75
76impl ContiguousMemoryLayoutPolicy {
77 pub fn new(mem_alignment: usize) -> Self {
79 Self { mem_alignment }
80 }
81}
82
83impl PitchedMemoryLayoutPolicy {
84 pub fn new(mem_alignment: usize) -> Self {
86 Self { mem_alignment }
87 }
88}
89
90impl MemoryLayoutPolicy for ContiguousMemoryLayoutPolicy {
91 fn apply(
92 &self,
93 service: ServiceId,
94 stream_id: StreamId,
95 descriptors: &[MemoryLayoutDescriptor],
96 ) -> (Handle, Vec<MemoryLayout>) {
97 let mut total_size = 0u64;
98 let (sizes, strides): (Vec<_>, Vec<_>) = descriptors
99 .iter()
100 .map(|desc| {
101 let size = desc.shape.iter().product::<usize>() * desc.elem_size;
102 total_size += size.next_multiple_of(self.mem_alignment) as u64;
103 (size, contiguous_strides(&desc.shape))
104 })
105 .unzip();
106
107 let base_handle = Handle::new(service, stream_id, total_size);
108
109 let layouts = offset_handles(base_handle.clone(), &sizes, self.mem_alignment)
110 .into_iter()
111 .zip(strides)
112 .map(|(handle, stride)| MemoryLayout::new(handle, stride))
113 .collect();
114
115 (base_handle, layouts)
116 }
117}
118
119pub(crate) fn contiguous_strides(shape: &Shape) -> Strides {
120 let rank = shape.len();
121 let mut strides = strides![1; rank];
122 for i in (0..rank - 1).rev() {
123 strides[i] = strides[i + 1] * shape[i + 1];
124 }
125 strides
126}
127
128pub fn offset_handles(
131 base_handle: Handle,
132 sizes_bytes: &[usize],
133 buffer_align: usize,
134) -> Vec<Handle> {
135 let total_size = base_handle.size() as usize;
136 let mut offset = 0;
137 let mut out = Vec::new();
138
139 for size in sizes_bytes {
140 let handle = base_handle
141 .clone()
142 .offset_start(offset as u64)
143 .offset_end((total_size - offset - size) as u64);
144 out.push(handle);
145 offset += size.next_multiple_of(buffer_align);
146 }
147
148 out
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub struct Pitch {
164 pub width_bytes: usize,
166 pub height: usize,
168 pub stride_bytes: usize,
170}
171
172impl Pitch {
173 pub fn of(shape: &[usize], strides: &[usize], elem_size: usize) -> Option<Self> {
179 let rank = shape.len();
180 let width = *shape.last().unwrap_or(&1);
181 if rank < 2 || strides[rank - 2] == width {
182 return None;
183 }
184 Some(Self {
185 width_bytes: width * elem_size,
186 height: shape.iter().rev().skip(1).product(),
187 stride_bytes: strides[rank - 2] * elem_size,
188 })
189 }
190}
191
192#[cfg(test)]
193mod pitch_tests {
194 use super::Pitch;
195
196 #[test]
201 fn contiguous_rows_have_no_pitch() {
202 assert_eq!(Pitch::of(&[4, 8], &[8, 1], 4), None);
204 assert_eq!(Pitch::of(&[8], &[1], 4), None);
206 assert_eq!(Pitch::of(&[], &[], 4), None);
207 }
208
209 #[test]
215 fn padded_rows_report_width_height_and_stride() {
216 let pitch = Pitch::of(&[4, 8], &[12, 1], 4).expect("a padded row is a pitch");
218 assert_eq!(pitch.width_bytes, 8 * 4);
219 assert_eq!(pitch.stride_bytes, 12 * 4);
220 assert_eq!(pitch.height, 4);
221 }
222
223 #[test]
226 fn height_counts_every_row_not_only_the_last_dimension() {
227 let pitch = Pitch::of(&[2, 3, 8], &[36, 12, 1], 4).expect("a padded row is a pitch");
228 assert_eq!(pitch.height, 6);
229 assert_eq!(pitch.width_bytes, 8 * 4);
230 assert_eq!(pitch.stride_bytes, 12 * 4);
231 }
232
233 #[test]
236 fn the_geometry_is_bytes_not_elements() {
237 let narrow = Pitch::of(&[4, 8], &[12, 1], 1).expect("a padded row is a pitch");
238 let wide = Pitch::of(&[4, 8], &[12, 1], 8).expect("a padded row is a pitch");
239 assert_eq!(wide.width_bytes, narrow.width_bytes * 8);
240 assert_eq!(wide.stride_bytes, narrow.stride_bytes * 8);
241 assert_eq!(wide.height, narrow.height);
242 }
243}