1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
//! [`Meeting`], the fill-vs-read rendezvous of one staging slot, and its [`Rendezvous`] strategy.
use cubecl::prelude::barrier::Barrier;
use cubecl::prelude::*;
use crate::*;
/// How a slot rendezvouses its fill against its read, fixed at construction.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Rendezvous {
/// Cooperative copy, synchronized by one `sync_cube` per phase.
Cube,
/// Cooperative copy into a stage one plane owns, synchronized by one `sync_plane` per phase.
///
/// Every plane of the cube still has to reach the same count of them: a backend with no
/// plane-wide barrier (WGSL) lowers `sync_plane` to the workgroup's.
Plane,
/// Async copy (TMA, `cp.async`) over a `full`/`empty` mbarrier pair.
Barrier,
}
impl Rendezvous {
/// Join the rendezvous of a slot's sources over a walk `fillers` planes fill, for stages
/// `owner` holds; `Barrier` wins, and a plane's own stages meet on `sync_plane`.
///
/// Panics on a plane's own stages filled by a bulk copy or by planes set aside to fill: both
/// meet on barriers the whole cube arms.
pub(crate) fn for_deliveries(
deliveries: &[Delivery],
fillers: usize,
owner: StageOwner,
) -> Rendezvous {
assert!(
!deliveries.is_empty(),
"Slot: a slot must have at least one delivery"
);
if let StageOwner::Plane { .. } = owner {
assert!(
fillers == 0
&& deliveries
.iter()
.all(|delivery| delivery.rendezvous() == Rendezvous::Cube),
"Slot: a stage one plane owns is filled by that plane's units alone, so neither an \
async copy nor planes set aside to fill can deliver it"
);
return Rendezvous::Plane;
}
let floor = if fillers > 0 {
Rendezvous::Barrier
} else {
Rendezvous::Cube
};
deliveries.iter().fold(floor, |sync, delivery| {
match (sync, delivery.rendezvous()) {
(Rendezvous::Barrier, _) | (_, Rendezvous::Barrier) => Rendezvous::Barrier,
(Rendezvous::Cube, Rendezvous::Cube) => Rendezvous::Cube,
(Rendezvous::Plane, _) | (_, Rendezvous::Plane) => {
unreachable!("a delivery asks for the cube or a barrier, never a plane")
}
}
})
}
/// Whether a barrier slot needs every unit to publish its writes.
pub(crate) fn collective_full(deliveries: &[Delivery]) -> bool {
deliveries.iter().any(Delivery::every_unit_fills)
}
/// Whether a barrier slot's units copy asynchronously, which `full` must track before it flips.
pub(crate) fn commits(deliveries: &[Delivery]) -> bool {
deliveries.contains(&Delivery::AsyncPerUnit)
}
/// Whether a barrier slot's barriers must be fenced into the async proxy once initialized:
/// only a bulk copy completes on them from there. The fence is sm90+, so a slot without one
/// must not emit it.
pub(crate) fn fences(deliveries: &[Delivery]) -> bool {
deliveries.iter().any(Delivery::through_async_proxy)
}
}
/// The rendezvous for one slot and the barriers it owns.
#[expect(
dead_code,
reason = "built through the expand type's generated constructors"
)]
#[derive(CubeType, Clone)]
#[expand(derive(Clone))]
pub(crate) enum Meeting {
/// Synchronous cooperative copy, synchronized by one `sync_cube` per phase.
Cube,
/// Synchronous cooperative copy into a stage one plane owns, synchronized by one `sync_plane`
/// per phase ([`Rendezvous::Plane`]).
Plane,
/// Async producer/consumer over a `full`/`empty` mbarrier pair, one parity each.
Barrier {
/// Producer to consumer: flips once declared arrivals and TMA bytes land.
full: Shared<Barrier>,
/// Consumer to producer: flips once every reader has freed the slot.
empty: Shared<Barrier>,
/// Whether `full` counts every producer's arrival or only the elected issuer's.
all_publish: bool,
/// Whether each producer hands `full` its async copies before it arrives, so the phase
/// waits for their bytes too ([`Delivery::AsyncPerUnit`]).
commits: bool,
/// The unit that issues this slot's bulk copies and declares their bytes.
elected: u32,
/// `full`'s parity, flipped by the producer's release.
writes: u32,
/// `empty`'s parity, flipped by the consumer's release.
reads: u32,
},
}
#[cube]
impl Meeting {
/// Allocate the pipeline for `sync`; for [`Barrier`](Rendezvous::Barrier), arm and fence the
/// mbarrier pair. Must run while every unit of the cube is present.
pub(crate) fn new(
#[comptime] sync: Rendezvous,
#[comptime] collective_full: bool,
#[comptime] commits: bool,
#[comptime] fences: bool,
#[comptime] fillers: usize,
) -> Meeting {
match sync {
Rendezvous::Cube => Meeting::new_Cube(),
Rendezvous::Plane => Meeting::new_Plane(),
Rendezvous::Barrier => {
let full =
Barrier::shared(Meeting::producers(collective_full, fillers), UNIT_POS == 0);
let empty = Barrier::shared(Meeting::consumers(fillers), UNIT_POS == 0);
if comptime!(fences) {
sync_async_proxy_shared();
}
sync_cube();
let elected = Meeting::elected(fillers);
let all_publish = comptime!(collective_full || fillers > 0);
Meeting::new_Barrier(full, empty, all_publish, commits, elected, 0, 0)
}
}
}
/// Units that arrive on `full`.
pub(crate) fn producers(#[comptime] collective_full: bool, #[comptime] fillers: usize) -> u32 {
if comptime!(collective_full) {
CUBE_DIM
} else if comptime!(fillers > 0) {
comptime!(fillers as u32) * CUBE_DIM_X
} else {
1u32.runtime()
}
}
/// Units that arrive on `empty`: the ones that read the slot.
pub(crate) fn consumers(#[comptime] fillers: usize) -> u32 {
if comptime!(fillers == 0) {
CUBE_DIM
} else {
CUBE_DIM - comptime!(fillers as u32) * CUBE_DIM_X
}
}
/// The unit that issues a bulk copy and declares its bytes.
pub(crate) fn elected(#[comptime] fillers: usize) -> u32 {
if comptime!(fillers == 0) {
0u32.runtime()
} else {
Meeting::consumers(fillers)
}
}
/// Fill staged `dst` from `src`, spread over the units `dst` names ([`FillUnits`]).
pub(crate) fn fill<E: Numeric>(&self, dst: &mut Tile<E>, src: &Tile<E>) {
// Bound before the match, which borrows the kind.
let space = comptime!(dst.place.space.clone());
let decodes = src.scaled();
if comptime!(decodes) {
dst.copy_from(src);
} else {
self.fill_as_it_lies(dst, src, space);
}
}
/// [`fill`](Meeting::fill) a source that decodes nothing.
fn fill_as_it_lies<E: Numeric>(
&self,
dst: &mut Tile<E>,
src: &Tile<E>,
#[comptime] space: Space,
) {
match self {
Meeting::Barrier { full, elected, .. } => match (&mut dst.kind, &src.kind) {
(TileKind::Memory(d), TileKind::TmaGmem(s)) => {
// Single issuer: the transaction count is the elected unit's alone.
if UNIT_POS == *elected {
full.expect_tx(d.size_bytes());
s.stage_into(d, full);
}
}
(TileKind::Memory(d), TileKind::Memory(s)) => d.fill_from(s, space),
(TileKind::Memory(d), TileKind::Procedural(s)) => d.fill_procedural(s, space),
_ => panic!("Meeting::fill: unsupported kind pairing"),
},
Meeting::Cube | Meeting::Plane => dst.copy_from(src),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn procedural_and_strided_share_a_cube_pipeline() {
assert_eq!(
Rendezvous::for_deliveries(
&[Delivery::Procedural, Delivery::SyncPerUnit],
0,
StageOwner::Cube
),
Rendezvous::Cube
);
}
#[test]
fn procedural_and_tma_share_a_barrier_pipeline() {
assert_eq!(
Rendezvous::for_deliveries(&[Delivery::Procedural, Delivery::Tma], 0, StageOwner::Cube),
Rendezvous::Barrier
);
assert!(Rendezvous::collective_full(&[
Delivery::Procedural,
Delivery::Tma
]));
}
/// An async copy lands after the fill returns, so only the barrier publishes it, and every
/// unit issued a share it has to hand over.
#[test]
fn an_async_copy_rendezvouses_on_a_barrier_every_unit_commits_to() {
let deliveries = [Delivery::AsyncPerUnit];
assert_eq!(
Rendezvous::for_deliveries(&deliveries, 0, StageOwner::Cube),
Rendezvous::Barrier
);
assert!(Rendezvous::collective_full(&deliveries));
assert!(Rendezvous::commits(&deliveries));
assert!(!Rendezvous::commits(&[
Delivery::Tma,
Delivery::SyncPerUnit
]));
}
/// A bulk copy has one issuer, as TMA does: its bytes are counted on `full`, not handed over
/// by every unit.
#[test]
fn a_bulk_copy_keeps_its_single_producer_arrival() {
let deliveries = [Delivery::AsyncBulk];
assert_eq!(
Rendezvous::for_deliveries(&deliveries, 0, StageOwner::Cube),
Rendezvous::Barrier
);
assert!(!Rendezvous::collective_full(&deliveries));
assert!(!Rendezvous::commits(&deliveries));
}
/// Only a bulk copy completes on the barrier from the async proxy. `cp.async` stays in the
/// generic proxy, and the fence it would otherwise emit does not exist before sm90.
#[test]
fn only_a_bulk_copy_fences_the_barrier_into_the_async_proxy() {
assert!(Rendezvous::fences(&[Delivery::Tma]));
assert!(Rendezvous::fences(&[
Delivery::SyncPerUnit,
Delivery::AsyncBulk
]));
assert!(!Rendezvous::fences(&[Delivery::AsyncPerUnit]));
assert!(!Rendezvous::fences(&[
Delivery::SyncPerUnit,
Delivery::Procedural
]));
}
#[test]
fn pure_tma_keeps_its_single_producer_arrival() {
assert!(!Rendezvous::collective_full(&[Delivery::Tma]));
}
#[test]
fn a_filled_slot_rendezvouses_on_a_barrier_whatever_delivered_it() {
assert_eq!(
Rendezvous::for_deliveries(&[Delivery::SyncPerUnit], 1, StageOwner::Cube),
Rendezvous::Barrier
);
}
/// A plane's own stages meet on its own barrier, whatever copy fills them.
#[test]
fn a_planes_own_stage_rendezvouses_on_the_plane() {
let owner = StageOwner::Plane { planes: 4 };
assert_eq!(
Rendezvous::for_deliveries(&[Delivery::Procedural, Delivery::SyncPerUnit], 0, owner),
Rendezvous::Plane
);
}
/// A bulk copy's barrier is armed by the whole cube, which a plane walking its own stages
/// never meets.
#[test]
#[should_panic(expected = "filled by that plane's units alone")]
fn a_planes_own_stage_takes_no_bulk_copy() {
Rendezvous::for_deliveries(&[Delivery::Tma], 0, StageOwner::Plane { planes: 4 });
}
/// Planes set aside to fill are planes the owning plane's units are not.
#[test]
#[should_panic(expected = "filled by that plane's units alone")]
fn a_planes_own_stage_takes_no_filling_planes() {
Rendezvous::for_deliveries(&[Delivery::SyncPerUnit], 1, StageOwner::Plane { planes: 4 });
}
}