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
349
350
//! graph-warmup-stress (lane/graph-warmups, 2026-08-05): the pool-growth adversarial gate
//! the MEMRA_GRAPH_WARMUPS default flip owes.
//!
//! MECHANISM UNDER TEST: `capture_graph` runs the step eagerly `warmups` times before
//! capturing. Warmup 1's allocations may GROW/map the async pool (lazy engine pools —
//! fa_part_pool, argmax partials, scratch — plus per-step transients); warmup 2 re-walks
//! the same alloc/free sequence over the settled pool so the captured run bakes
//! steady-state addresses. Dropping to warmups=1 captures on the run whose pool walk may
//! differ from every later replay's — the #68 stale-baked-address class (a captured graph
//! address returns to the pool, a live alloc lands there, the next replay writes over it:
//! stream corruption, usually WITHOUT a CUDA fault). The arbiter is therefore per-token
//! BIT-IDENTITY vs the eager decode_step stream, plus fault propagation.
//!
//! ADVERSARIAL ARMS (each cycle, both directions):
//! large->small: a big session (big cache + big graph) boots, generates across several
//! kernel-class recaptures, is dropped — its buffers return to the async pool as freed
//! blocks. A small session then boots: its capture warms up OVER the freed blocks.
//! small->large: a small session boots/retires first, then the large one — the large
//! session's warmup 1 must GROW the pool (small steady state can't hold it): the exact
//! "warmup 1 grows/maps" scenario warmup 2 exists to absorb.
//! overlap (once, after the cycles): two live sessions interleave steps — the large
//! boot grows the SHARED engine fa_part_pool while the small session's graph holds
//! baked pointers to the pre-grow buffers (retire-on-grow is the guard under test);
//! the small session is then dropped mid-flight, and the survivor takes a FORCED
//! recapture over the freed blocks + keeps generating (the F5-adjacent
//! capture-over-existing-cache path — worker park/resume promotes through the same
//! graph_capture_segment).
//! Every arm also takes one forced mid-stream recapture (capture from a live cache over
//! a churned pool) in addition to the natural class-boundary recaptures.
//!
//! Default-pool RESERVED/USED bytes are printed at phase boundaries so the receipt shows
//! the pool actually grew and actually held freed blocks (adversarial preconditions met,
//! not assumed).
//!
//! --canary: mid-stream, clobber the session's graph-referenced token_d buffer (the
//! observable surface of the stale-address class: a graph-consumed buffer whose contents a
//! stray write changed) and require the bit-identity check to CATCH it. A real cross-
//! allocation alias cannot be forced deterministically from user code (the allocator
//! exposes no placement control), so the canary corrupts the graph's INPUT memory directly
//! — it proves the comparator + plumbing detect graph-memory corruption end-to-end, not
//! merely that a label flips (the chunkinv-canary trap).
//!
//! usage: graph-warmup-stress <model.gguf> [--cycles 10] [--large-steps 160]
//! [--small-steps 90] [--canary]
//! exit 0 = every cycle bit-identical + no fault (canary mode: corruption detected).
use memra_engine::Engine;
use memra_engine::cache::Cache;
use memra_engine::forward::argmax;
use memra_engine::hybrid::HybridModel;
use memra_gguf::GgufFile;
fn arg_val(rest: &[String], key: &str) -> Option<String> {
rest.iter()
.position(|a| a == key)
.and_then(|i| rest.get(i + 1))
.cloned()
}
/// Default async-pool occupancy (reserved = mapped from the OS, used = live allocations).
/// reserved > used == freed blocks parked in the pool (the adversarial precondition).
fn pool_stats() -> (u64, u64) {
use cudarc::driver::sys;
unsafe {
let mut pool: sys::CUmemoryPool = std::ptr::null_mut();
if sys::cuDeviceGetDefaultMemPool(&mut pool, 0) != sys::CUresult::CUDA_SUCCESS {
return (0, 0);
}
let (mut reserved, mut used) = (0u64, 0u64);
let _ = sys::cuMemPoolGetAttribute(
pool,
sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
&mut reserved as *mut u64 as *mut core::ffi::c_void,
);
let _ = sys::cuMemPoolGetAttribute(
pool,
sys::CUmemPool_attribute_enum::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
&mut used as *mut u64 as *mut core::ffi::c_void,
);
(reserved, used)
}
}
fn mb(b: u64) -> f64 {
b as f64 / (1024.0 * 1024.0)
}
/// Ground truth: token-wise eager decode_step stream (same emission convention as the
/// graph gates: token 0 = argmax of the last prime step). Greedy = deterministic.
fn eager_ref(
e: &Engine,
m: &HybridModel,
prompt: &[u32],
n: usize,
) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
let mut cache = Cache::new(e, &m.cfg, prompt.len() + n + 8)?;
let mut ll = Vec::new();
for &t in prompt {
ll = m.decode_step(e, t, &mut cache)?;
}
let mut tok = argmax(&ll) as u32;
let mut out = Vec::with_capacity(n);
out.push(tok);
for _ in 1..n {
ll = m.decode_step(e, tok, &mut cache)?;
tok = argmax(&ll) as u32;
out.push(tok);
}
Ok(out)
}
/// Boot a GraphSession, generate n tokens (forced recapture at `recap_at`, canary clobber
/// at `canary_at`), return the stream. Any Err = propagated CUDA fault = gate FAIL.
fn session_run(
e: &Engine,
m: &HybridModel,
prompt: &[u32],
budget: usize,
n: usize,
recap_at: Option<usize>,
canary_at: Option<usize>,
) -> Result<Vec<u32>, Box<dyn std::error::Error>> {
let (mut sess, first) = m.graph_session_new(e, prompt, budget)?;
let mut out = Vec::with_capacity(n);
out.push(first);
for i in 1..n {
if recap_at == Some(i) {
m.graph_session_recapture_pub(e, &mut sess)?;
}
if canary_at == Some(i) {
// stray write into a graph-referenced buffer (see header). 1234 is in-vocab
// for every gated family and never the greedy continuation here.
e.set_u32_one(&mut sess.gs.token_d, 1234)?;
}
out.push(sess.step(e, m)?);
}
Ok(out)
}
/// First mismatch index, if any.
fn diff(a: &[u32], b: &[u32]) -> Option<usize> {
if a.len() != b.len() {
return Some(a.len().min(b.len()));
}
a.iter().zip(b.iter()).position(|(x, y)| x != y)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let path = args
.next()
.expect("usage: graph-warmup-stress <model.gguf> [--cycles N] [--canary]");
let rest: Vec<String> = args.collect();
let cycles: usize = arg_val(&rest, "--cycles")
.and_then(|v| v.parse().ok())
.unwrap_or(10);
let large_n: usize = arg_val(&rest, "--large-steps")
.and_then(|v| v.parse().ok())
.unwrap_or(160);
let small_n: usize = arg_val(&rest, "--small-steps")
.and_then(|v| v.parse().ok())
.unwrap_or(90);
let canary = rest.iter().any(|a| a == "--canary");
// Size classes: the small session's budget keeps its cache tiny; the large budget
// inflates cache/scratch so its boot MUST grow the pool past small steady state.
let large_budget = 4096usize;
let small_budget = small_n + 6;
let e = Engine::new(0)?;
let g = GgufFile::open(&path)?;
let m = HybridModel::load_without_mtp(&e, &g)?;
let prompt: Vec<u32> = (0..48u32).map(|j| 55 + j * 31).collect();
let warm = std::env::var("MEMRA_GRAPH_WARMUPS").unwrap_or_else(|_| "(default)".into());
println!(
"model {path} arch {} ({} layers) cycles={cycles} large={large_n}tok/@{large_budget} \
small={small_n}tok/@{small_budget} MEMRA_GRAPH_WARMUPS={warm} canary={canary}",
g.arch().unwrap_or("?"),
m.layers.len()
);
let (r0, u0) = pool_stats();
println!(
"[pool] post-load: reserved {:.0} MB used {:.0} MB",
mb(r0),
mb(u0)
);
// ground truth once per size class (greedy determinism; the serving gates pin it).
let ref_small = eager_ref(&e, &m, &prompt, small_n)?;
let ref_large = eager_ref(&e, &m, &prompt, large_n)?;
let (r1, u1) = pool_stats();
println!(
"[pool] post-eager-ref: reserved {:.0} MB used {:.0} MB",
mb(r1),
mb(u1)
);
let mut fails = 0usize;
let mut canary_caught = false;
for c in 1..=cycles {
// direction 1: LARGE boots (pool grows), retires (freed blocks), SMALL captures over them.
let out_l = session_run(
&e,
&m,
&prompt,
large_budget,
large_n,
Some(large_n / 2),
None,
)?;
let (ra, ua) = pool_stats();
let out_s = session_run(
&e,
&m,
&prompt,
small_budget,
small_n,
Some(small_n / 2),
if canary && c == 1 {
Some(small_n / 3)
} else {
None
},
)?;
let (rb, ub) = pool_stats();
// direction 2: SMALL first, then LARGE — the large warmup 1 must grow the pool.
let out_s2 = session_run(
&e,
&m,
&prompt,
small_budget,
small_n,
Some(small_n / 2),
None,
)?;
let out_l2 = session_run(
&e,
&m,
&prompt,
large_budget,
large_n,
Some(large_n / 2),
None,
)?;
let mut cycle_ok = true;
for (label, out, r) in [
("L->", &out_l, &ref_large),
("->S", &out_s, &ref_small),
("S->", &out_s2, &ref_small),
("->L", &out_l2, &ref_large),
] {
let canary_arm = canary && c == 1 && label == "->S";
match diff(out, r) {
None if canary_arm => {
println!(
"cycle {c} {label}: CANARY NOT CAUGHT (corrupted stream still matched — comparator blind)"
);
cycle_ok = false;
}
None => {}
Some(i) if canary_arm => {
println!(
"cycle {c} {label}: canary caught at token {i} (expected — comparator has teeth)"
);
canary_caught = true;
}
Some(i) => {
println!(
"cycle {c} {label}: MISMATCH at token {i} (graph {} vs eager {})",
out.get(i).copied().unwrap_or(0),
r.get(i).copied().unwrap_or(0)
);
cycle_ok = false;
}
}
}
println!(
"cycle {c}: {} [pool after L-drop: {:.0}/{:.0} MB, after S: {:.0}/{:.0} MB rsv/used]",
if cycle_ok { "OK" } else { "FAIL" },
mb(ra),
mb(ua),
mb(rb),
mb(ub)
);
if !cycle_ok {
fails += 1;
}
}
// OVERLAP arm (once): two live graphs share the engine pools; the large boot grows
// fa_part_pool under the small session's baked pointers (retire-on-grow under test),
// the small session dies mid-flight, the survivor recaptures over its freed blocks.
{
let (mut sa, fa) = m.graph_session_new(&e, &prompt, small_budget)?;
let mut out_a = vec![fa];
for _ in 1..40 {
out_a.push(sa.step(&e, &m)?);
}
let (mut sb, fb) = m.graph_session_new(&e, &prompt, large_budget)?;
let mut out_b = vec![fb];
for _ in 1..40 {
out_b.push(sb.step(&e, &m)?);
}
for _ in 40..small_n {
out_a.push(sa.step(&e, &m)?);
} // A replays after B grew the pools
drop(sa); // A's cache -> freed blocks under B
m.graph_session_recapture_pub(&e, &mut sb)?; // fresh capture over them
for _ in 40..large_n {
out_b.push(sb.step(&e, &m)?);
}
let da = diff(&out_a, &ref_small);
let db = diff(&out_b, &ref_large);
if da.is_none() && db.is_none() {
println!(
"overlap arm: OK (A survived B's pool growth; B survived A's free + recapture)"
);
} else {
println!("overlap arm: MISMATCH (A diff {da:?}, B diff {db:?})");
fails += 1;
}
}
let (rz, uz) = pool_stats();
println!(
"[pool] end: reserved {:.0} MB used {:.0} MB",
mb(rz),
mb(uz)
);
if canary {
if canary_caught && fails == 0 {
println!(
"CANARY GATE PASS: injected graph-memory corruption was detected; all clean arms held"
);
Ok(())
} else {
Err(format!("CANARY GATE FAIL: caught={canary_caught} other_fails={fails}").into())
}
} else if fails == 0 {
println!(
"ALL GREEN: graph-warmup-stress ({cycles} cycles x 4 arms + overlap, bit-identical, no fault)"
);
Ok(())
} else {
Err(format!("graph-warmup-stress FAILED: {fails} failing cycle(s)").into())
}
}