1use cubecl::prelude::*;
4
5use cubecl_std::tensor::{ErasedTensor, WriteOnly};
6
7use crate::*;
8
9#[derive(CubeType)]
11pub struct GlobalOperand {
12 pub(crate) backing: Backing,
13 pub geometry: RuntimeGeometry,
15 #[cube(comptime)]
17 pub bound_width: usize,
18 #[cube(comptime)]
20 pub space: Space,
21 #[cube(comptime)]
22 pub spec: TileSpec,
23 #[cube(comptime)]
25 pub write: Write,
26 pub coefficients: Coords<u32>,
29 pub offsets: Coords<i32>,
31 pub(crate) paging: ComptimeOption<PageTable>,
34}
35
36#[cube]
37impl GlobalOperand {
38 pub fn tensor<T: Numeric, E: CubePrimitive<Scalar = T>>(
40 tensor: &Tensor<E>,
41 #[comptime] space: Space,
42 #[comptime] spec: TileSpec,
43 ) -> GlobalOperand {
44 GlobalOperand::of_tensor::<T, E>(
45 tensor,
46 space,
47 spec,
48 Coords::<u32>::new(),
49 Coords::<i32>::new(),
50 ComptimeOption::new_None(),
51 )
52 }
53
54 pub fn paged<T: Numeric, E: CubePrimitive<Scalar = T>>(
58 tensor: &Tensor<E>,
59 table: &Tensor<u32>,
60 #[comptime] space: Space,
61 #[comptime] spec: TileSpec,
62 ) -> GlobalOperand {
63 let paging = comptime!(spec.paging.unwrap_or_else(|| panic!(
64 "GlobalOperand::paged: the spec states no paging for the table to resolve"
65 )));
66 let projection = comptime!(spec.projection.clone());
67 let (sequence_dim, _) = comptime!(
68 paging
69 .dims(&projection)
70 .unwrap_or_else(|refusal| panic!("GlobalOperand::paged: {refusal}"))
71 );
72 let table = PageTable::new(
73 table,
74 tensor.shape(sequence_dim) as u32,
75 paging,
76 projection,
77 tensor.vector_size(),
78 );
79 GlobalOperand::of_tensor::<T, E>(
80 tensor,
81 space,
82 spec,
83 Coords::<u32>::new(),
84 Coords::<i32>::new(),
85 ComptimeOption::new_Some(table),
86 )
87 }
88
89 pub fn gathered<T: Numeric, E: CubePrimitive<Scalar = T>>(
92 tensor: &Tensor<E>,
93 #[comptime] space: Space,
94 #[comptime] spec: TileSpec,
95 coefficients: Coords<u32>,
96 offsets: Coords<i32>,
97 ) -> GlobalOperand {
98 GlobalOperand::of_tensor::<T, E>(
99 tensor,
100 space,
101 spec,
102 coefficients,
103 offsets,
104 ComptimeOption::new_None(),
105 )
106 }
107
108 fn of_tensor<T: Numeric, E: CubePrimitive>(
111 tensor: &Tensor<E>,
112 #[comptime] space: Space,
113 #[comptime] spec: TileSpec,
114 coefficients: Coords<u32>,
115 offsets: Coords<i32>,
116 paging: ComptimeOption<PageTable>,
117 ) -> GlobalOperand {
118 let rank = comptime!(spec.projection.physical_rank());
119 let backing = match comptime!(spec.packing) {
121 Packing::Plain => Backing::new_Buffer(Raw::<Slice>::of::<T>(unsafe {
122 tensor
123 .as_slice()
124 .downcast_unchecked::<T>()
125 .as_boxed_unchecked()
126 })),
127 Packing::Packed { .. } => Backing::new_Buffer(Raw::<Slice>::of::<u32>(unsafe {
128 tensor
129 .as_slice()
130 .downcast_unchecked::<u32>()
131 .as_boxed_unchecked()
132 })),
133 };
134 let pool = RuntimeGeometry::of_tensor::<E>(tensor, rank);
135 #[comptime]
136 let geometry = match &paging {
137 ComptimeOption::Some(table) => table.sequences_over(&pool),
138 ComptimeOption::None => pool,
139 };
140 GlobalOperand {
141 backing,
142 geometry,
143 bound_width: tensor.vector_size(),
144 space,
145 spec,
146 write: comptime!(Write::Replace),
147 coefficients,
148 offsets,
149 paging,
150 }
151 }
152
153 pub fn sink<T: Numeric>(
157 sink: ErasedTensor<T, WriteOnly>,
158 geometry: RuntimeGeometry,
159 #[comptime] vector_size: usize,
160 #[comptime] space: Space,
161 #[comptime] spec: TileSpec,
162 #[comptime] write: Write,
163 ) -> GlobalOperand {
164 comptime!(assert!(
165 spec.packing == Packing::Plain,
166 "GlobalOperand::sink: a sink is written at the width it states, so its spec may not \
167 state a packing ({:?}) on top of it",
168 spec.packing
169 ));
170 GlobalOperand {
171 backing: Backing::new_WriteCall(RawCall::of::<T>(sink)),
172 geometry,
173 bound_width: vector_size,
174 space,
175 spec,
176 write,
177 coefficients: Coords::<u32>::new(),
178 offsets: Coords::<i32>::new(),
179 paging: ComptimeOption::new_None(),
180 }
181 }
182
183 pub fn source<T: Numeric>(
185 source: ErasedTensor<T, ReadOnly>,
186 geometry: RuntimeGeometry,
187 #[comptime] vector_size: usize,
188 #[comptime] space: Space,
189 #[comptime] spec: TileSpec,
190 ) -> GlobalOperand {
191 comptime!(assert!(
192 spec.packing == Packing::Plain,
193 "GlobalOperand::source: a source is read at the width it states, so its spec may not \
194 state a packing ({:?}) on top of it",
195 spec.packing
196 ));
197 GlobalOperand {
198 backing: Backing::new_ReadCall(RawCall::of::<T>(source)),
199 geometry,
200 bound_width: vector_size,
201 space,
202 spec,
203 write: comptime!(Write::Replace),
204 coefficients: Coords::<u32>::new(),
205 offsets: Coords::<i32>::new(),
206 paging: ComptimeOption::new_None(),
207 }
208 }
209
210 pub fn tile<T: Numeric>(self, #[comptime] levels: Vec<Level>) -> Tile {
212 let place = comptime!(Placement::root(
213 self.space.subspace(self.spec.axes()),
214 levels
215 ));
216 Tile::new::<T>(TileKind::new_Memory(Memory::global(self)), place)
217 }
218}
219
220#[cube]
221impl Memory {
222 #[allow(clippy::needless_range_loop)]
225 pub(crate) fn global(operand: GlobalOperand) -> Memory {
226 let backing = operand.backing;
227 let geometry = operand.geometry;
228 let paging = operand.paging;
229 let coefficients = operand.coefficients;
230 let offsets = operand.offsets;
231 let bound_width = comptime!(operand.bound_width);
232 let space = comptime!(operand.space.clone());
233 let spec = comptime!(operand.spec.clone());
234 let write = comptime!(operand.write);
235 let split_share = comptime!(SplitShare::Whole);
236 let space = comptime!(space.subspace(spec.axes()));
237 let projection = comptime!(spec.projection.clone());
238 let coords = comptime!(projection.untiled());
239 let packing = comptime!(spec.packing);
240 let vector_size = comptime!(packing.served(bound_width));
241 let dims_given = geometry.shape.len();
242 let coefficients_given = coefficients.len();
243 let offsets_given = offsets.len();
244 comptime!(assert!(
245 dims_given == projection.physical_rank(),
246 "GlobalOperand: the projection addresses {} physical dims but {dims_given} were given",
247 projection.physical_rank()
248 ));
249 comptime!(assert!(
250 coefficients_given == coords.dynamic_coefficient_count(),
251 "GlobalOperand: the projection has {} Dynamic coefficients and divisors but \
252 {coefficients_given} were given",
253 coords.dynamic_coefficient_count()
254 ));
255 comptime!(assert!(
256 offsets_given == coords.dynamic_offset_count(),
257 "GlobalOperand: the projection has {} Dynamic offsets but {offsets_given} were given",
258 coords.dynamic_offset_count()
259 ));
260 comptime!(check_operand(&space, &spec, vector_size));
261 comptime!(assert!(
262 paging.is_some() == spec.paging.is_some(),
263 "GlobalOperand: a spec stating paging is read through its table \
264 (GlobalOperand::paged), and only such a spec is"
265 ));
266 let rank = comptime!(projection.physical_rank());
268 let load = comptime!(
272 VectorTile::new(
273 &spec.stored_tiles,
274 space.axis_at(space.rank() - 1),
275 vector_size
276 )
277 .unwrap_or_else(|why| panic!("GlobalOperand: {vector_size} values a load: {why}"))
278 );
279 let parts = comptime!(load.parts(&spec.stored_tiles, &projection.dense_labels(), rank));
280 let stated = comptime!(VectorTile::stated_extents(
283 &spec.stored_tiles,
284 &projection.dense_labels(),
285 rank
286 ));
287 let mut physical_shape = Coords::<u32>::new();
288 let mut physical_strides = Coords::<u32>::new();
289 #[unroll]
290 for i in 0..rank {
291 let part = comptime!(parts[i] as u32);
292 if comptime!(stated[i].is_some()) {
293 physical_shape.push(comptime!(stated[i].unwrap() as u32 / part));
294 } else {
295 physical_shape.push(geometry.shape.at(i) / part);
296 }
297 physical_strides.push(geometry.strides.at(i) * part / comptime!(vector_size as u32));
298 }
299 let gmem_projection = comptime!(projection.positional());
300 let bound = logical_extent(comptime!(gmem_projection.clone()), &physical_shape);
302 let (origin, extent, map) = top_window(
303 comptime!(space.clone()),
304 &bound,
305 &offsets,
306 coefficients,
307 comptime!(load.clone()),
308 comptime!(coords.clone()),
309 );
310 Memory {
311 address: comptime!(AddressSpace::Global),
312 store: Store {
313 backing,
314 vector_size: comptime!(vector_size),
315 packing: comptime!(packing),
316 stored_tiles: comptime!(spec.stored_tiles.clone()),
317 },
318 layout: BufferLayout {
319 physical_shape,
320 physical_strides,
321 projection: gmem_projection,
322 rows: RowArrangement::InOrder,
323 paging,
324 },
325 window: Window::new(
326 origin,
327 extent,
328 bound,
329 comptime!(coords.may_underflow()),
330 comptime!(spec.boundaries.clone()),
331 ),
332 source_window: ComptimeOption::new_None(),
333 projection: comptime!(coords),
334 map,
335 offsets,
336 window_start: 0u32,
337 access: comptime!(Access {
338 whole: true,
339 overhang: if spec.is_checked() {
340 Overhang::Masked
341 } else {
342 Overhang::Fits
343 },
344 write,
345 fill: FillUnits::cube(spec.units),
346 storage: WindowStorage::Stored(spec.storage),
347 delivery: spec.delivery,
348 }),
349 unit_share: comptime!(UnitShare::Repeated),
350 split_share,
351 init_from: comptime!(InitFrom::Cell),
352 contraction: comptime!(None),
353 codebook: Codebook::none(),
354 index: ComptimeOption::new_None(),
355 }
356 }
357}
358
359fn check_operand(space: &Space, spec: &TileSpec, vector_size: usize) {
361 let projection = &spec.projection;
362 if let Err(refusal) = projection.validate(vector_size) {
365 panic!("{refusal}");
366 }
367 projection.validate_composition(|axis| space.extent(axis));
368 if let Some(paging) = &spec.paging {
369 let checked = paging.validate(projection, &spec.stored_tiles, spec.delivery);
370 if let Err(refusal) = checked {
371 panic!("GlobalOperand: {refusal}");
372 }
373 }
374 let coord_rank = projection.coordinate_rank();
375 assert!(
376 spec.boundaries.is_empty() || spec.boundaries.len() == coord_rank,
377 "GlobalOperand: boundaries rank ({}) does not match coordinate rank ({coord_rank})",
378 spec.boundaries.len()
379 );
380 assert!(
381 vector_size == 1 || spec.boundaries.last().copied().flatten() != Some(Boundary::Clamp),
382 "GlobalOperand: Boundary::Clamp cannot clamp the vectorized innermost axis (served at \
383 {vector_size})"
384 );
385}
386
387#[cube]
389fn top_window(
390 #[comptime] space: Space,
391 bound: &Coords<u32>,
392 offsets: &Coords<i32>,
393 coefficients: Coords<u32>,
394 #[comptime] load: VectorTile,
395 #[comptime] projection: Projection,
396) -> (Coords<i32>, Coords<u32>, RuntimeMap) {
397 let mut origin = Coords::<i32>::new();
398 let mut extent = Coords::<u32>::new();
399 let mut residues = Coords::<u32>::new();
400 let rank = comptime!(projection.physical_rank());
401
402 #[unroll]
403 for pa in 0..rank {
404 let size = if comptime!(projection.is_direct()) {
405 origin.push(0);
406 residues.push(0u32);
407 let axis = comptime!(space.axis_at(pa));
408 match comptime!(space.extent_raw(axis)) {
409 Extent::Static(e) => (comptime!(e / load.extent_along(axis)) as u32).runtime(),
410 Extent::Dynamic => bound.at(pa),
411 }
412 } else {
413 let (start, phase) =
414 gathered_origin(comptime!(projection.clone()), offsets, &coefficients, pa);
415 origin.push(start);
416 residues.push(phase);
417 bound.at(pa)
418 };
419 extent.push(size);
420 }
421
422 (
423 origin,
424 extent,
425 RuntimeMap {
426 coefficients,
427 residues,
428 },
429 )
430}
431
432#[cube]
434fn gathered_origin(
435 #[comptime] projection: Projection,
436 offsets: &Coords<i32>,
437 coefficients: &Coords<u32>,
438 #[comptime] pa: usize,
439) -> (i32, u32) {
440 let axis_map = comptime!(projection.physical_axis(pa));
441
442 if comptime!(axis_map.origin().is_some()) {
443 (
444 comptime!(axis_map.origin().unwrap() as i32).runtime(),
445 comptime!(axis_map.residue().unwrap() as u32).runtime(),
446 )
447 } else {
448 let offset = match comptime!(axis_map.offset()) {
450 Offset::Static(o) => comptime!(o as i32).runtime(),
451 Offset::Dynamic => offsets.at(comptime!(projection.dynamic_offset_index(pa).unwrap())),
452 };
453 if comptime!(!axis_map.is_rational()) {
454 (offset, 0u32)
455 } else {
456 let divisor = match comptime!(axis_map.divisor()) {
457 Divisor::Static(d) => comptime!(d as i32).runtime(),
458 Divisor::Dynamic { .. } => coefficients
459 .at(comptime!(projection.dynamic_divisor_index(pa).unwrap()))
460 .cast::<i32>(),
461 };
462 let (start, residue) = floor_div_rem(offset, divisor);
463 (start, residue.cast::<u32>())
464 }
465 }
466}