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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
//! Cooperative grid-sync contracts for resident and concurrent CUDA dispatch.
//!
//! Every launch must reset and serialize the module-scope barrier counter.
//! Tests assert exact cross-block outputs because an early barrier release
//! returns populated buffers containing wrong values rather than an error.
mod common;
use common::{
bytes_u32, cross_block_grid_sync_expected, cross_block_grid_sync_inputs,
cross_block_grid_sync_program, CROSS_BLOCK_GRID_SYNC_WORKGROUP,
};
use vyre_driver::{DispatchConfig, VyreBackend};
use vyre_driver_cuda::{cuda_factory, CudaBackend};
/// Lane count for every case here. 512 blocks of 256 threads sits well inside
/// the cooperative residency ceiling on a grid-sync capable device while still
/// spreading block start times widely enough that segment 1's read of the LAST
/// lane's slot genuinely depends on the barrier. An 8-block grid does NOT: every
/// block starts within a few microseconds, so the output is correct even with the
/// barrier released early, and the gate passes while the defect is present. That
/// was measured, not assumed.
const LANES: u32 = 512 * CROSS_BLOCK_GRID_SYNC_WORKGROUP;
/// Launch repetitions for the reuse gates. A stale counter is released early on
/// the SECOND launch, so 2 is the minimum that can fail; going further catches a
/// counter that drifts only after several reuses, such as a reset that lands on
/// the wrong stream and races rather than being absent.
const REUSE_LAUNCHES: usize = 8;
/// Acquire the registered CUDA backend used by shared-backend concurrency tests.
fn live_registered_backend() -> Box<dyn VyreBackend> {
let backend = cuda_factory()
.expect("Fix: CUDA backend factory must succeed on the GPU-required test host.");
assert!(
backend.supports_grid_sync(),
"Fix: this host reports no native cooperative grid-sync lowering, so every gate in this \
suite would pass vacuously. Native grid-sync is required on the CUDA test fleet."
);
backend
}
/// Assert one `out` buffer holds exactly the expected value for every lane, and
/// report the first offending lane with both values when it does not.
///
/// The failure message separates the two ways a lane goes wrong, because they
/// point at different code. A lane BELOW its expected value read the last lane's
/// accumulator before the last block finished, so the barrier did not block. A
/// lane above it, or off by something unrelated to the accumulator, is a
/// segment-0 or readback fault rather than a barrier fault.
fn assert_grid_sync_output(out_bytes: &[u8], context: &str) {
let actual = bytes_u32(out_bytes);
let expected = cross_block_grid_sync_expected(LANES);
assert_eq!(
actual.len(),
expected.len(),
"Fix: {context} returned {} lanes, expected {}. The `out` buffer must cover every lane.",
actual.len(),
expected.len()
);
if let Some((lane, (&got, &want))) = actual
.iter()
.zip(expected.iter())
.enumerate()
.find(|(_, (got, want))| got != want)
{
let diagnosis = if got < want {
format!(
"that lane read scratch[n - 1] while the LAST block was still {} iterations from \
finishing, so the whole-grid barrier did not block. On a reused launch this is \
the signature of a stale _vyre_grid_barrier counter that already sat at or past \
this barrier's release target, releasing it on arrival.",
want - got
)
} else {
"that lane is ABOVE its expected value, which the barrier cannot cause: the \
accumulator is monotonic and capped by the last block's iteration count. Look at \
segment 0's accumulate or the output readback, not the barrier."
.to_string()
};
panic!(
"Fix: {context} produced out[{lane}] = {got}, expected {want}. Block {} of {}. \
{diagnosis}",
u32::try_from(lane).unwrap_or(u32::MAX) / CROSS_BLOCK_GRID_SYNC_WORKGROUP,
LANES / CROSS_BLOCK_GRID_SYNC_WORKGROUP
);
}
}
/// The last output buffer of the fixture program is `out`. Taking it positionally
/// keeps the assertion honest if a buffer is ever added ahead of it.
fn out_buffer(outputs: &[Vec<u8>], context: &str) -> Vec<u8> {
outputs
.last()
.unwrap_or_else(|| {
panic!("Fix: {context} returned no output buffers; the fixture declares `out`.")
})
.clone()
}
/// The resident launch route must reset the barrier counter between launches.
///
/// This is the route that was actually broken. `dispatch_resident` did not zero
/// `_vyre_grid_barrier` at all, and it did not force cooperative launch for a
/// grid-sync program either, so a second resident launch of one grid-sync
/// program released its first barrier immediately. A compiled pipeline reaches
/// this code through its persistent-handle entry points, which is why the
/// ahead-of-time refusal was load-bearing until this was fixed.
///
/// `scratch` is re-uploaded before each launch so the second launch starts from
/// the same state as the first. Without that, the second launch would read an
/// already-incremented `scratch[0]` and pass even with no barrier at all.
#[test]
fn resident_grid_sync_dispatch_synchronizes_on_its_second_launch() {
let backend = CudaBackend::acquire()
.expect("Fix: CUDA backend acquisition must succeed on the GPU-required test host.");
let program = cross_block_grid_sync_program(LANES);
let config = DispatchConfig::default();
let inputs = cross_block_grid_sync_inputs(LANES);
let out_bytes = inputs[0].len();
let mut handles = Vec::with_capacity(3);
for input in &inputs {
let handle = backend
.allocate_resident(input.len())
.expect("Fix: resident input allocation must succeed.");
handles.push(handle);
}
let out_handle = backend
.allocate_resident(out_bytes)
.expect("Fix: resident output allocation must succeed.");
handles.push(out_handle);
for launch in 1..=REUSE_LAUNCHES {
// Re-upload every input, including the read_write `scratch`, so each
// launch starts from identical state and only the barrier counter
// carries over.
for (index, input) in inputs.iter().enumerate() {
backend
.upload_resident(handles[index], input)
.unwrap_or_else(|error| {
panic!("Fix: resident input {index} upload for launch {launch} failed: {error}")
});
}
backend
.dispatch_resident(&program, &handles, &config)
.unwrap_or_else(|error| {
panic!("Fix: resident grid-sync dispatch launch {launch} failed: {error}")
});
let out = backend
.download_resident(out_handle)
.unwrap_or_else(|error| {
panic!("Fix: resident output download for launch {launch} failed: {error}")
});
assert_grid_sync_output(
&out,
&format!(
"resident grid-sync launch {launch} of {REUSE_LAUNCHES} (the route that did not \
reset the counter at all)"
),
);
}
for handle in handles {
backend
.free_resident(handle)
.expect("Fix: resident handle cleanup must succeed.");
}
}
/// Concurrent cooperative dispatches of ONE program through ONE backend must all
/// be correct.
///
/// This is a permanent property, not a tripwire. `CudaBackend` is `Clone` and
/// every clone shares one `Arc<CudaModuleCache>`, so all threads dispatching one
/// program share a single loaded module and therefore a single
/// `_vyre_grid_barrier`. Sharing one backend across a thread pool is the obvious
/// and only realistic way to use the API, so if per-launch zeroing of a
/// module-scope counter is unsafe under concurrency, it is reachable from the
/// shipped surface by ordinary code and this test is what notices.
///
/// Note precisely what this does NOT cover: separate `CudaBackend::acquire`
/// calls build separate module caches and therefore separate counters, so a
/// per-thread-backend test cannot provoke aliasing no matter how many threads it
/// runs. That is why this test shares one backend deliberately.
#[test]
fn concurrent_cooperative_dispatches_through_one_shared_backend_stay_correct() {
const THREADS: usize = 8;
const PER_THREAD: usize = 16;
let backend = live_registered_backend();
let backend: &dyn VyreBackend = backend.as_ref();
let program = cross_block_grid_sync_program(LANES);
let config = DispatchConfig::default();
let inputs = cross_block_grid_sync_inputs(LANES);
let failures = std::thread::scope(|scope| {
let workers: Vec<_> = (0..THREADS)
.map(|thread| {
let program = &program;
let config = &config;
let inputs = &inputs;
scope.spawn(move || {
let mut local_failures = Vec::new();
for iteration in 0..PER_THREAD {
match backend.dispatch(program, inputs, config) {
Ok(outputs) => match outputs.last() {
Some(out) => {
let actual = bytes_u32(out);
let expected = cross_block_grid_sync_expected(LANES);
if actual != expected {
let lane = actual
.iter()
.zip(expected.iter())
.position(|(got, want)| got != want)
.unwrap_or(0);
local_failures.push(format!(
"thread {thread} iteration {iteration}: out[{lane}] = \
{} expected {}",
actual[lane], expected[lane]
));
}
}
None => local_failures.push(format!(
"thread {thread} iteration {iteration}: no output buffers"
)),
},
Err(error) => local_failures.push(format!(
"thread {thread} iteration {iteration}: dispatch failed: {error}"
)),
}
}
local_failures
})
})
.collect();
workers
.into_iter()
.flat_map(|worker| {
worker
.join()
.expect("Fix: a concurrent cooperative dispatch worker panicked.")
})
.collect::<Vec<_>>()
});
assert!(
failures.is_empty(),
"Fix: {} of {} concurrent cooperative grid-sync dispatches through ONE shared backend \
produced wrong results. All threads share one loaded module and therefore one \
module-scope _vyre_grid_barrier counter, so a per-launch zeroing of that counter races \
against another thread's in-flight grid: one thread's reset lands while another's blocks \
are still spinning, that grid's wait predicate goes false on arrival, and it runs \
unsynchronized. Scope the counter per launch or serialize cooperative launches that \
share a module. First failures:\n{}",
failures.len(),
THREADS * PER_THREAD,
failures
.iter()
.take(8)
.cloned()
.collect::<Vec<_>>()
.join("\n")
);
}
/// Concurrent cooperative dispatches on INDEPENDENT backends must all be
/// correct, and this test records why that is a weaker statement.
///
/// Each `CudaBackend::acquire` builds a fresh `Arc<CudaModuleCache>`, so each
/// backend loads its own CUmodule and gets its own `_vyre_grid_barrier` at its
/// own device address. Threads that each own a backend therefore cannot alias
/// the counter regardless of thread count. Keeping this case explicit stops a
/// future reader from mistaking a green per-thread-backend test for coverage of
/// the shared-backend property above, which is the mistake that hid this
/// question in the first place.
#[test]
fn concurrent_cooperative_dispatches_on_independent_backends_stay_correct() {
const THREADS: usize = 4;
const PER_THREAD: usize = 8;
let program = cross_block_grid_sync_program(LANES);
let config = DispatchConfig::default();
let inputs = cross_block_grid_sync_inputs(LANES);
let expected = cross_block_grid_sync_expected(LANES);
let failures = std::thread::scope(|scope| {
let workers: Vec<_> = (0..THREADS)
.map(|thread| {
let program = &program;
let config = &config;
let inputs = &inputs;
let expected = &expected;
scope.spawn(move || {
let backend = cuda_factory()
.expect("Fix: per-thread CUDA backend acquisition must succeed.");
let mut local_failures = Vec::new();
for iteration in 0..PER_THREAD {
match backend.dispatch(program, inputs, config) {
Ok(outputs) => match outputs.last().map(|out| bytes_u32(out)) {
Some(actual) if &actual == expected => {}
Some(actual) => local_failures.push(format!(
"thread {thread} iteration {iteration}: first wrong lane {:?}",
actual
.iter()
.zip(expected.iter())
.enumerate()
.find(|(_, (got, want))| got != want)
)),
None => local_failures.push(format!(
"thread {thread} iteration {iteration}: no output buffers"
)),
},
Err(error) => local_failures.push(format!(
"thread {thread} iteration {iteration}: dispatch failed: {error}"
)),
}
}
local_failures
})
})
.collect();
workers
.into_iter()
.flat_map(|worker| {
worker
.join()
.expect("Fix: an independent-backend cooperative worker panicked.")
})
.collect::<Vec<_>>()
});
assert!(
failures.is_empty(),
"Fix: {} of {} concurrent cooperative grid-sync dispatches on INDEPENDENT backends \
produced wrong results. Independent backends hold independent module caches and \
therefore independent barrier counters, so a failure here is NOT counter aliasing: look \
at per-launch device state such as the transient allocation pool or the stream pool. \
First failures:\n{}",
failures.len(),
THREADS * PER_THREAD,
failures
.iter()
.take(8)
.cloned()
.collect::<Vec<_>>()
.join("\n")
);
}