cubecl_tile/stage/pipeline/
base.rs1use cubecl::ir::Scope;
6use cubecl::prelude::*;
7use cubecl::unexpanded;
8
9use super::payload::base::{Payload, PayloadExpand, StageSpec};
10use super::payload::pair::OperandPair;
11use super::plan::StagePlan;
12use super::schedule::Prefetch;
13use crate::*;
14
15#[derive(CubeType, CubeTypeMut, IntoRuntime)]
17#[cube(runtime_variants)]
18pub enum Role {
19 Fill,
21 Compute,
23}
24
25#[derive(CubeType)]
27pub struct Stages<T: CubeType> {
28 pub(crate) slots: Sequence<Slot<T>>,
29 pub(crate) sources: T,
30 #[allow(dead_code)]
31 #[cube(comptime)]
32 pub(crate) depth: usize,
33 #[cube(comptime)]
35 pub(crate) fillers: usize,
36}
37
38#[cube]
39impl<T: CubeType> Stages<T> {
40 pub(crate) fn wrap(
42 slots: Sequence<Slot<T>>,
43 sources: T,
44 #[comptime] depth: usize,
45 #[comptime] fillers: usize,
46 ) -> Stages<T> {
47 comptime!(assert!(
48 depth > 0,
49 "Stages: a pipeline needs at least one slot"
50 ));
51 Stages::<T> {
52 slots,
53 sources,
54 depth,
55 fillers,
56 }
57 }
58
59 pub fn role(&self) -> Role {
61 if comptime!(self.fillers == 0) {
62 Role::new_Compute()
63 } else if UNIT_POS >= Meeting::consumers(comptime!(self.fillers)) {
64 Role::new_Fill()
65 } else {
66 Role::new_Compute()
67 }
68 }
69
70 pub fn slot_mut(&mut self, #[comptime] index: usize) -> &mut Slot<T> {
72 self.slots.index_mut(index)
73 }
74}
75
76#[cube]
77impl<P: Payload<P> + Clone + CubeType<ExpandType: Clone>> Stages<P> {
78 #[allow(dead_code)]
83 pub(crate) fn new(
84 walk: &Walk,
85 sources: &P,
86 #[comptime] storage: StageStorage,
87 #[comptime] width: Option<usize>,
88 #[comptime] depth: usize,
89 #[comptime] decoded: Option<ElemType>,
90 ) -> Stages<P> {
91 let operands = sources.operands();
92 let owner = walk.stage_owner();
93 let plan = comptime!(StagePlan::new(&operands, &walk.space, &walk.level, owner));
94 let spec = comptime!(StageSpec {
95 level: walk.level.clone(),
96 depth: walk.depth(),
97 storage,
98 width,
99 decoded,
100 owner: plan.owner(),
101 });
102
103 let first = sources.staged(
105 sources,
106 comptime!(spec.clone()),
107 comptime!(plan.refills(FIRST_SLOT)),
108 );
109 let mut slots = Sequence::<Slot<P>>::new();
110 #[unroll]
111 for slot in 0..depth {
112 let data = if comptime!(slot == FIRST_SLOT) {
113 first.clone()
114 } else {
115 sources.staged(
116 &first,
117 comptime!(spec.clone()),
118 comptime!(plan.refills(slot)),
119 )
120 };
121 slots.push(Slot::wrap(
122 data,
123 Meeting::new(
124 comptime!(plan.sync()),
125 comptime!(plan.collective_full()),
126 comptime!(plan.commits()),
127 comptime!(plan.fences()),
128 comptime!(plan.fillers()),
129 ),
130 comptime!(plan.refills(slot)),
131 ));
132 }
133 Stages::wrap(slots, sources.clone(), depth, comptime!(plan.fillers()))
134 }
135
136 pub fn free(&self) {
141 #[unroll]
142 for slot in 0..comptime!(self.depth) {
143 self.slots.index(slot).data.free();
144 }
145 }
146
147 pub fn fill(&mut self, #[comptime] slot: usize, region: &Region) {
149 let sources = self.sources.clone();
150 self.slot_mut(slot)
151 .refill(&sources, region, comptime!(Refill::EveryRegion));
152 }
153}
154
155#[cube]
156impl Stages<OperandPair> {
157 pub fn smem(
164 walk: &Walk,
165 lhs: &Tile,
166 rhs: &Tile,
167 #[comptime] storage: StageStorage,
168 #[comptime] depth: usize,
169 #[comptime] decoded: Option<ElemType>,
170 ) -> Stages<OperandPair> {
171 let sources = OperandPair {
172 lhs: lhs.clone(),
173 rhs: rhs.clone(),
174 };
175 Stages::new(walk, &sources, storage, comptime!(None), depth, decoded)
176 }
177}
178
179#[cube]
180impl Stages<Tile> {
181 pub fn smem_single(
183 walk: &Walk,
184 input: &Tile,
185 #[comptime] storage: StageStorage,
186 #[comptime] depth: usize,
187 #[comptime] decoded: Option<ElemType>,
188 ) -> Stages<Tile> {
189 Stages::new(walk, input, storage, comptime!(None), depth, decoded)
190 }
191
192 pub fn smem_single_at(
194 walk: &Walk,
195 input: &Tile,
196 #[comptime] storage: StageStorage,
197 #[comptime] width: Option<usize>,
198 #[comptime] depth: usize,
199 #[comptime] decoded: Option<ElemType>,
200 ) -> Stages<Tile> {
201 Stages::new(walk, input, storage, width, depth, decoded)
202 }
203}
204
205define_size!(pub(crate) FL);
207define_size!(pub(crate) FR);
208
209pub(crate) type FetchBuffers<Lhs, Rhs> = (Array<Vector<Lhs, FL>>, Array<Vector<Rhs, FR>>);
211
212#[cube]
214fn register_fetched_widths(#[comptime] lhs: usize, #[comptime] rhs: usize) {
215 intrinsic!(|scope| {
216 scope.register_size::<FL>(lhs);
217 scope.register_size::<FR>(rhs);
218 });
219}
220
221#[cube]
222impl Stages<OperandPair> {
223 #[allow(dead_code)] pub(crate) fn fetch_buffers<Lhs: Numeric, Rhs: Numeric>(&self) -> FetchBuffers<Lhs, Rhs> {
226 let staged = self.slots.index(FIRST_SLOT);
227 let lhs = staged.data.lhs.fetched_scalars();
228 let rhs = staged.data.rhs.fetched_scalars();
229 comptime!(assert!(
230 Prefetch::fits(lhs + rhs),
231 "Stages: a fill fetched into registers holds {lhs} + {rhs} scalars a unit, past the \
232 {MOST_FETCHED_SCALARS} a unit keeps beside its contraction"
233 ));
234 let lhs_width = staged.data.lhs.vector_size();
235 let rhs_width = staged.data.rhs.vector_size();
236 register_fetched_widths(lhs_width, rhs_width);
237 (
238 Array::<Vector<Lhs, FL>>::new(comptime!(lhs / lhs_width)),
239 Array::<Vector<Rhs, FR>>::new(comptime!(rhs / rhs_width)),
240 )
241 }
242
243 #[allow(dead_code)] pub(crate) fn fetch<Lhs: Numeric, Rhs: Numeric>(
247 &self,
248 #[comptime] slot: usize,
249 sources: &OperandPair,
250 region: &Region,
251 lhs: &mut Array<Vector<Lhs, FL>>,
252 rhs: &mut Array<Vector<Rhs, FR>>,
253 ) {
254 let staged = self.slots.index(slot);
255 staged.data.lhs.fetch_from(&sources.lhs.at(region), lhs);
256 staged.data.rhs.fetch_from(&sources.rhs.at(region), rhs);
257 }
258
259 #[allow(dead_code)] pub(crate) fn store<Lhs: Numeric, Rhs: Numeric>(
262 &mut self,
263 #[comptime] slot: usize,
264 lhs: &Array<Vector<Lhs, FL>>,
265 rhs: &Array<Vector<Rhs, FR>>,
266 ) {
267 let slot = self.slot_mut(slot);
268 slot.data.lhs.store_fetched(lhs);
269 slot.data.rhs.store_fetched(rhs);
270 }
271
272 #[allow(dead_code)] pub(crate) fn assert_copied_by_every_unit(&self) {
275 let lhs = self.sources.lhs.delivery();
276 let rhs = self.sources.rhs.delivery();
277 comptime!(assert!(
278 self.fillers == 0 && lhs == Delivery::SyncPerUnit && rhs == Delivery::SyncPerUnit,
279 "Stages: a register-staged schedule fills its slots with every unit's own copy of \
280 memory; these stages are filled by {} plane(s) of their own, or their sources are \
281 delivered {lhs:?} and {rhs:?}",
282 self.fillers
283 ));
284 }
285}
286
287impl Stages<OperandPair> {
289 pub fn consume(&mut self, _slot: usize, _compute: impl FnOnce(&Tile, &Tile)) {
291 unexpanded!()
292 }
293}
294
295impl StagesExpand<OperandPair> {
296 pub fn __expand_consume_method<F>(&mut self, scope: &Scope, slot: usize, compute: F)
297 where
298 F: FnOnce(&Scope, &TileExpand, &TileExpand),
299 {
300 self.__expand_slot_mut_method(scope, slot)
301 .__expand_consume_method(scope, compute);
302 }
303}
304
305impl Stages<Tile> {
306 pub fn consume(&mut self, _slot: usize, _compute: impl FnOnce(&Tile)) {
308 unexpanded!()
309 }
310}
311
312impl StagesExpand<Tile> {
313 pub fn __expand_consume_method<F>(&mut self, scope: &Scope, slot: usize, compute: F)
314 where
315 F: FnOnce(&Scope, &TileExpand),
316 {
317 self.__expand_slot_mut_method(scope, slot)
318 .__expand_consume_method(scope, compute);
319 }
320}