nx_swarm_gpu.nx source
↩ module page · 556 lines · 24681 B
1// nx_swarm_gpu.nx -- SWARM FABRIC GPU/VRAM inventory + SPLIT-MODEL SHARD PLANNER (F792). The keystone of
2// split-model serve (exo/Petals-class, sovereign): given a model too big for ONE GPU, decide how to shard
3// its layers across the fleet's LIVE free VRAM. Its OWN small organ (NEVER bolt onto nx_swarm_beat -- the
4// CPU-beacon 13-token contract stays untouched); the pulse layer reports a GPU row here, the hub reads a
5// fresh VRAM inventory + computes a shard plan. Composes the proven nx_swarm_beat store pattern (replace-by-
6// node snap, flock'd log, fabric-clock freshness, path law).
7//
8// nx_swarm_gpu put "<GPU row>" -- validate 5-token row -> log append + snap replace
9// nx_swarm_gpu show <window_sec> -- FRESH/STALE per node (the live VRAM inventory)
10// nx_swarm_gpu plan <snap> <window_sec> <model_mb> [util%] -- greedy shard plan across fresh nodes' free VRAM;
11// REFUSE if fleet usable < model (never oversubscribe)
12// nx_swarm_gpu now -- fabric clock (stamp rows in the freshness units)
13// nx_swarm_gpu [gate] -- self-gate incl the liar-killers
14//
15// GPU row (positional): GPU <node> <vram_total_mb> <vram_free_mb> <ts_us>
16// util% = headroom margin (default 85): usable = free_vram * util%/100 -- leaves room for activations + KV-cache;
17// a GPU is NEVER filled to 100% VRAM (an OOM mid-decode is the never-brick analog here). Greedy snap-order fill
18// minimizes the shard count = fewer cross-node activation transfers (Amdahl-friendly); biggest-first = a v2 sort.
19// PATH LAW mirrors nx_swarm_beat: CLI paths end .log/.snap, no "..", bare-basename-in-CWD or /tmp only.
20// license_tier: ORIGINAL expect_exit:0
21import "nx_syscalls.nx"
22import "nx_framed_append.nx"
23const SG_MAGIC_60000000: i64 = 60000000
24const SG_MAGIC_1000000: i64 = 1000000
25const SG_MAGIC_16000: i64 = 16000
26const SG_MAGIC_8000: i64 = 8000
27const SG_MAGIC_24000: i64 = 24000
28const SG_MAGIC_20000: i64 = 20000
29const SG_MAGIC_4000: i64 = 4000
30const SG_MAGIC_3600: i64 = 3600
31const SG_MAGIC_3000: i64 = 3000
32const SG_MAGIC_19000: i64 = 19000
33const SG_MAGIC_25000: i64 = 25000
34const SG_MAGIC_21000: i64 = 21000
35
36const SG_SNAP_CAP: i64 = 65536
37const SG_ROW_CAP: i64 = 256
38const SG_DEFAULT_UTIL: i64 = 85
39
40func sg_puts(s: *u8) -> i64 { sys_write(1, s, fa_len(s)); return 0 }
41
42func sg_eq(a: *u8, b: *u8) -> i64 {
43 var i: i64 = 0
44 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
45 if b[i] != (0 as u8) { return 0 }
46 return 1
47}
48
49// CLI path guard: end ".log"/".snap"; no ".."; bare basename (no '/') OR under /tmp/.
50func sg_path_ok(p: *u8) -> i64 {
51 let n: i64 = fa_len(p)
52 if n < 5 { return 0 }
53 var i: i64 = 0
54 while i + 1 < n { if (p[i] as i64) == 46 { if (p[i + 1] as i64) == 46 { return 0 } } i = i + 1 }
55 var ext_ok: i64 = 0
56 if n >= 4 { if (p[n-4] as i64) == 46 { if (p[n-3] as i64) == 108 { if (p[n-2] as i64) == 111 { if (p[n-1] as i64) == 103 { ext_ok = 1 } } } } }
57 if n >= 5 { if (p[n-5] as i64) == 46 { if (p[n-4] as i64) == 115 { if (p[n-3] as i64) == 110 { if (p[n-2] as i64) == 97 { if (p[n-1] as i64) == 112 { ext_ok = 1 } } } } } }
58 if ext_ok == 0 { return 0 }
59 var has_slash: i64 = 0
60 i = 0
61 while i < n { if (p[i] as i64) == 47 { has_slash = 1 } i = i + 1 }
62 if has_slash == 0 { return 1 }
63 if n < 6 { return 0 }
64 if (p[0] as i64) != 47 { return 0 }
65 if (p[1] as i64) != 116 { return 0 }
66 if (p[2] as i64) != 109 { return 0 }
67 if (p[3] as i64) != 112 { return 0 }
68 if (p[4] as i64) != 47 { return 0 }
69 return 1
70}
71
72func sg_read(path: *u8, buf: *u8, cap: i64) -> i64 {
73 let fd: i64 = sys_openat_rd(path)
74 if fd < 0 { return 0 - 1 }
75 var tot: i64 = 0
76 while tot < cap {
77 let r: i64 = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot)
78 if r <= 0 { break }
79 tot = tot + r
80 }
81 sys_close(fd)
82 return tot
83}
84
85func sg_has(buf: *u8, n: i64, needle: *u8) -> i64 {
86 let m: i64 = fa_len(needle)
87 if m == 0 { return 0 }
88 var i: i64 = 0
89 while i + m <= n {
90 var k: i64 = 0
91 var hit: i64 = 1
92 while k < m { if buf[i + k] != needle[k] { hit = 0; k = m } else { k = k + 1 } }
93 if hit == 1 { return 1 }
94 i = i + 1
95 }
96 return 0
97}
98
99func sg_pint(buf: *u8, n: i64, p: i64, vout: *i64, pend: *i64) -> i64 {
100 var i: i64 = p
101 var neg: i64 = 0
102 if i < n { if (buf[i] as i64) == 45 { neg = 1; i = i + 1 } }
103 var v: i64 = 0
104 var d: i64 = 0
105 var go: i64 = 1
106 while go == 1 {
107 if i >= n { go = 0 } else {
108 let c: i64 = buf[i] as i64
109 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); d = d + 1; i = i + 1 } else { go = 0 } } else { go = 0 }
110 }
111 }
112 if d == 0 { return 0 }
113 if neg == 1 { v = 0 - v }
114 vout[0] = v
115 pend[0] = i
116 return 1
117}
118
119// validate one GPU row of exactly 5 tokens: GPU <node> <total> <free> <ts>.
120// name span -> nso/nlo; total -> totout; free -> fout; ts -> tso. 1 ok / 0 bad.
121func sg_validate(row: *u8, n: i64, nso: *i64, nlo: *i64, totout: *i64, fout: *i64, tso: *i64) -> i64 {
122 if n < 6 { return 0 }
123 if row[0] != (71 as u8) { return 0 } // G
124 if row[1] != (80 as u8) { return 0 } // P
125 if row[2] != (85 as u8) { return 0 } // U
126 if row[3] != (32 as u8) { return 0 } // space
127 var i: i64 = 4
128 let ns: i64 = i
129 while i < n {
130 if (row[i] as i64) == 32 { break }
131 if (row[i] as i64) == 10 { return 0 }
132 i = i + 1
133 }
134 let nl: i64 = i - ns
135 if nl < 1 { return 0 }
136 if nl > 32 { return 0 }
137 let vout: *i64 = sys_mmap(16) as *i64
138 let pend: *i64 = sys_mmap(16) as *i64
139 var t: i64 = 2
140 while t <= 4 {
141 if i >= n { return 0 }
142 if (row[i] as i64) != 32 { return 0 }
143 i = i + 1
144 let ok: i64 = sg_pint(row, n, i, vout, pend)
145 if ok == 0 { return 0 }
146 if t == 2 { totout[0] = vout[0] }
147 if t == 3 { fout[0] = vout[0] }
148 if t == 4 { tso[0] = vout[0] }
149 i = pend[0]
150 t = t + 1
151 }
152 if i < n {
153 if (row[i] as i64) != 10 { return 0 }
154 if i + 1 < n { return 0 }
155 }
156 nso[0] = ns
157 nlo[0] = nl
158 return 1
159}
160
161func sg_fresh(ts: i64, now: i64, window_sec: i64) -> i64 {
162 if ts <= 0 { return 0 }
163 if ts > now + SG_MAGIC_60000000 { return 0 }
164 if now - ts < window_sec * SG_MAGIC_1000000 { return 1 }
165 return 0
166}
167
168// replace-by-node in snap: keep every line whose node differs, append the new row, tmp+renameat.
169func sg_snap_replace(snap: *u8, row: *u8, rowlen: i64, ns: i64, nl: i64) -> i64 {
170 let old: *u8 = sys_mmap(SG_SNAP_CAP)
171 var on: i64 = sg_read(snap, old, SG_SNAP_CAP)
172 if on < 0 { on = 0 }
173 let neu: *u8 = sys_mmap(SG_SNAP_CAP + SG_ROW_CAP)
174 var o: i64 = 0
175 var i: i64 = 0
176 while i < on {
177 var e: i64 = i
178 while e < on { if (old[e] as i64) == 10 { break } e = e + 1 }
179 var keep: i64 = 1
180 if e - i >= 6 {
181 if old[i] == (71 as u8) {
182 let ls: i64 = i + 4
183 var le: i64 = ls
184 while le < e { if (old[le] as i64) == 32 { break } le = le + 1 }
185 if le - ls == nl {
186 var k: i64 = 0
187 var same: i64 = 1
188 while k < nl { if old[ls + k] != row[ns + k] { same = 0; k = nl } else { k = k + 1 } }
189 if same == 1 { keep = 0 }
190 }
191 }
192 }
193 if keep == 1 {
194 if e > i {
195 var c: i64 = i
196 while c < e { neu[o] = old[c]; o = o + 1; c = c + 1 }
197 neu[o] = 10 as u8
198 o = o + 1
199 }
200 }
201 i = e + 1
202 }
203 var k2: i64 = 0
204 while k2 < rowlen { neu[o] = row[k2]; o = o + 1; k2 = k2 + 1 }
205 neu[o] = 10 as u8
206 o = o + 1
207 let tmp: *u8 = sys_mmap(512)
208 var to: i64 = 0
209 to = fa_cat(tmp, to, snap)
210 to = fa_cat(tmp, to, "." as *u8)
211 to = fa_catn(tmp, to, sys_now_us())
212 to = fa_cat(tmp, to, ".tmp" as *u8)
213 tmp[to] = 0 as u8
214 let fd: i64 = sys_openat_wr(tmp, 0x1a4)
215 if fd < 0 { return 0 - 4 }
216 let wr: i64 = fa_write_all(fd, neu, o)
217 sys_close(fd)
218 if wr != o { return 0 - 4 }
219 let rr: i64 = sys_renameat(tmp, snap)
220 if rr < 0 { return 0 - 4 }
221 return 0
222}
223
224func sg_put(log: *u8, snap: *u8, row: *u8) -> i64 {
225 let n: i64 = fa_len(row)
226 if n >= SG_ROW_CAP { return 0 - 3 }
227 let nso: *i64 = sys_mmap(16) as *i64
228 let nlo: *i64 = sys_mmap(16) as *i64
229 let totout: *i64 = sys_mmap(16) as *i64
230 let fout: *i64 = sys_mmap(16) as *i64
231 let tso: *i64 = sys_mmap(16) as *i64
232 let ok: i64 = sg_validate(row, n, nso, nlo, totout, fout, tso)
233 if ok == 0 { return 0 - 3 }
234 let rec: *u8 = sys_mmap(SG_ROW_CAP + 8)
235 var o: i64 = 0
236 var i: i64 = 0
237 while i < n { if (row[i] as i64) != 10 { rec[o] = row[i]; o = o + 1 } i = i + 1 }
238 let ar: i64 = fa_append(log, rec, o, SG_ROW_CAP + 8)
239 if ar != o + 1 { return 0 - 4 }
240 return sg_snap_replace(snap, rec, o, nso[0], nlo[0])
241}
242
243func sg_show(snap: *u8, window_sec: i64) -> i64 {
244 let buf: *u8 = sys_mmap(SG_SNAP_CAP)
245 let n: i64 = sg_read(snap, buf, SG_SNAP_CAP)
246 let now: i64 = sys_now_us()
247 var fresh: i64 = 0
248 var stale: i64 = 0
249 var bad: i64 = 0
250 var i: i64 = 0
251 while i < n {
252 var e: i64 = i
253 while e < n { if (buf[e] as i64) == 10 { break } e = e + 1 }
254 if e > i {
255 let nso: *i64 = sys_mmap(16) as *i64
256 let nlo: *i64 = sys_mmap(16) as *i64
257 let totout: *i64 = sys_mmap(16) as *i64
258 let fout: *i64 = sys_mmap(16) as *i64
259 let tso: *i64 = sys_mmap(16) as *i64
260 let ok: i64 = sg_validate((buf as i64 + i) as *u8, e - i, nso, nlo, totout, fout, tso)
261 if ok == 1 {
262 let fr: i64 = sg_fresh(tso[0], now, window_sec)
263 if fr == 1 { fresh = fresh + 1; sg_puts("FRESH " as *u8) } else { stale = stale + 1; sg_puts("STALE " as *u8) }
264 } else { bad = bad + 1; sg_puts("BAD " as *u8) }
265 sys_write(1, (buf as i64 + i) as *u8, e - i)
266 sg_puts("\n" as *u8)
267 }
268 i = e + 1
269 }
270 sg_puts("SWARMGPU-SHOW fresh=" as *u8)
271 let t: *u8 = sys_mmap(64)
272 var to: i64 = 0
273 to = fa_catn(t, to, fresh); to = fa_cat(t, to, " stale=" as *u8); to = fa_catn(t, to, stale)
274 to = fa_cat(t, to, " bad=" as *u8); to = fa_catn(t, to, bad); to = fa_cat(t, to, "\n" as *u8)
275 sys_write(1, t, to)
276 return 0
277}
278
279// THE SHARD PLANNER: greedy snap-order fill of fresh nodes' usable VRAM (usable = free * util/100).
280// REFUSE if fleet usable < model_mb (never oversubscribe). Prints a per-node shard plan summing EXACTLY
281// to model_mb. Returns 0 GREEN / 3 REFUSE / 2 usage.
282func sg_plan(snap: *u8, window_sec: i64, model_mb: i64, util_pct: i64) -> i64 {
283 if model_mb <= 0 { sg_puts("SHARDPLAN REFUSE (model_mb must be > 0)\n" as *u8); return 2 }
284 if util_pct <= 0 { util_pct = SG_DEFAULT_UTIL }
285 if util_pct > 100 { util_pct = 100 }
286 let buf: *u8 = sys_mmap(SG_SNAP_CAP)
287 let n: i64 = sg_read(snap, buf, SG_SNAP_CAP)
288 let now: i64 = sys_now_us()
289 // pass 1: total usable over FRESH nodes
290 var total_usable: i64 = 0
291 var nodes: i64 = 0
292 var i: i64 = 0
293 while i < n {
294 var e: i64 = i
295 while e < n { if (buf[e] as i64) == 10 { break } e = e + 1 }
296 if e > i {
297 let nso: *i64 = sys_mmap(16) as *i64
298 let nlo: *i64 = sys_mmap(16) as *i64
299 let totout: *i64 = sys_mmap(16) as *i64
300 let fout: *i64 = sys_mmap(16) as *i64
301 let tso: *i64 = sys_mmap(16) as *i64
302 let ok: i64 = sg_validate((buf as i64 + i) as *u8, e - i, nso, nlo, totout, fout, tso)
303 if ok == 1 { if sg_fresh(tso[0], now, window_sec) == 1 {
304 var usable: i64 = fout[0] * util_pct / 100
305 if usable < 0 { usable = 0 }
306 total_usable = total_usable + usable
307 nodes = nodes + 1
308 } }
309 }
310 i = e + 1
311 }
312 let hdr: *u8 = sys_mmap(256)
313 if nodes == 0 { sg_puts("SHARDPLAN REFUSE (no FRESH GPU nodes in the inventory)\n" as *u8); return 3 }
314 if total_usable < model_mb {
315 var ho: i64 = fa_cat(hdr, 0, "SHARDPLAN REFUSE model_mb=" as *u8)
316 ho = fa_catn(hdr, ho, model_mb)
317 ho = fa_cat(hdr, ho, " > fleet_usable_mb=" as *u8); ho = fa_catn(hdr, ho, total_usable)
318 ho = fa_cat(hdr, ho, " (fresh_nodes=" as *u8); ho = fa_catn(hdr, ho, nodes)
319 ho = fa_cat(hdr, ho, " util=" as *u8); ho = fa_catn(hdr, ho, util_pct)
320 ho = fa_cat(hdr, ho, "%) -- model too big for live fleet VRAM\n" as *u8)
321 sys_write(1, hdr, ho)
322 return 3
323 }
324 var ho2: i64 = fa_cat(hdr, 0, "SHARDPLAN model_mb=" as *u8)
325 ho2 = fa_catn(hdr, ho2, model_mb)
326 ho2 = fa_cat(hdr, ho2, " util=" as *u8); ho2 = fa_catn(hdr, ho2, util_pct)
327 ho2 = fa_cat(hdr, ho2, "% fresh_nodes=" as *u8); ho2 = fa_catn(hdr, ho2, nodes)
328 ho2 = fa_cat(hdr, ho2, " total_usable_mb=" as *u8); ho2 = fa_catn(hdr, ho2, total_usable)
329 ho2 = fa_cat(hdr, ho2, "\n" as *u8)
330 sys_write(1, hdr, ho2)
331 // pass 2: greedy snap-order fill, sum == model_mb exactly, no node over its usable
332 var remaining: i64 = model_mb
333 var used_nodes: i64 = 0
334 i = 0
335 while i < n {
336 if remaining <= 0 { break }
337 var e: i64 = i
338 while e < n { if (buf[e] as i64) == 10 { break } e = e + 1 }
339 if e > i {
340 let nso: *i64 = sys_mmap(16) as *i64
341 let nlo: *i64 = sys_mmap(16) as *i64
342 let totout: *i64 = sys_mmap(16) as *i64
343 let fout: *i64 = sys_mmap(16) as *i64
344 let tso: *i64 = sys_mmap(16) as *i64
345 let ok: i64 = sg_validate((buf as i64 + i) as *u8, e - i, nso, nlo, totout, fout, tso)
346 if ok == 1 { if sg_fresh(tso[0], now, window_sec) == 1 {
347 var usable: i64 = fout[0] * util_pct / 100
348 if usable < 0 { usable = 0 }
349 var shard: i64 = usable
350 if shard > remaining { shard = remaining }
351 if shard > 0 {
352 let line: *u8 = sys_mmap(256)
353 var lo: i64 = fa_cat(line, 0, " SHARD node=" as *u8)
354 var k: i64 = 0
355 while k < nlo[0] { line[lo] = buf[i + nso[0] + k]; lo = lo + 1; k = k + 1 }
356 lo = fa_cat(line, lo, " shard_mb=" as *u8); lo = fa_catn(line, lo, shard)
357 lo = fa_cat(line, lo, " free_mb=" as *u8); lo = fa_catn(line, lo, fout[0])
358 lo = fa_cat(line, lo, " usable_mb=" as *u8); lo = fa_catn(line, lo, usable)
359 lo = fa_cat(line, lo, "\n" as *u8)
360 sys_write(1, line, lo)
361 remaining = remaining - shard
362 used_nodes = used_nodes + 1
363 }
364 } }
365 }
366 i = e + 1
367 }
368 let ft: *u8 = sys_mmap(128)
369 var fo: i64 = 0
370 if remaining == 0 {
371 fo = fa_cat(ft, 0, "SHARDPLAN OK assigned=" as *u8)
372 fo = fa_catn(ft, fo, model_mb)
373 fo = fa_cat(ft, fo, "mb across used_nodes=" as *u8); fo = fa_catn(ft, fo, used_nodes)
374 fo = fa_cat(ft, fo, " verdict=GREEN\n" as *u8)
375 sys_write(1, ft, fo)
376 return 0
377 }
378 fo = fa_cat(ft, 0, "SHARDPLAN REFUSE (internal: unassigned_mb=" as *u8)
379 fo = fa_catn(ft, fo, remaining); fo = fa_cat(ft, fo, ")\n" as *u8)
380 sys_write(1, ft, fo)
381 return 3
382}
383
384// synthetic GPU row for the gate
385func sg_mkrow(dst: *u8, name: *u8, total: i64, free: i64, ts: i64) -> i64 {
386 var o: i64 = 0
387 o = fa_cat(dst, o, "GPU " as *u8)
388 o = fa_cat(dst, o, name)
389 o = fa_cat(dst, o, " " as *u8); o = fa_catn(dst, o, total)
390 o = fa_cat(dst, o, " " as *u8); o = fa_catn(dst, o, free)
391 o = fa_cat(dst, o, " " as *u8); o = fa_catn(dst, o, ts)
392 dst[o] = 0 as u8
393 return o
394}
395
396func sg_gate() -> i64 {
397 var pass: i64 = 0
398 var total: i64 = 0
399 let now: i64 = sys_now_us()
400 let log: *u8 = sys_mmap(256)
401 var lo: i64 = fa_cat(log, 0, "/tmp/sgp_gate_" as *u8); lo = fa_catn(log, lo, now); lo = fa_cat(log, lo, ".log" as *u8); log[lo] = 0 as u8
402 let snap: *u8 = sys_mmap(256)
403 var so: i64 = fa_cat(snap, 0, "/tmp/sgp_gate_" as *u8); so = fa_catn(snap, so, now); so = fa_cat(snap, so, ".snap" as *u8); snap[so] = 0 as u8
404 let row: *u8 = sys_mmap(SG_ROW_CAP)
405 let buf: *u8 = sys_mmap(SG_SNAP_CAP)
406
407 // T1 put lands
408 total = total + 1
409 sg_mkrow(row, "laptop" as *u8, SG_MAGIC_16000, SG_MAGIC_8000, now)
410 var r: i64 = sg_put(log, snap, row)
411 var n: i64 = sg_read(snap, buf, SG_SNAP_CAP)
412 if r == 0 { if sg_has(buf, n, "GPU laptop 16000 8000 " as *u8) == 1 { pass = pass + 1; sg_puts("T1 put-lands OK\n" as *u8) } }
413
414 // T2 second node
415 total = total + 1
416 sg_mkrow(row, "west" as *u8, SG_MAGIC_24000, SG_MAGIC_20000, now)
417 r = sg_put(log, snap, row)
418 n = sg_read(snap, buf, SG_SNAP_CAP)
419 if r == 0 { if sg_has(buf, n, "GPU laptop 16000 8000 " as *u8) == 1 { if sg_has(buf, n, "GPU west 24000 20000 " as *u8) == 1 { pass = pass + 1; sg_puts("T2 two-nodes OK\n" as *u8) } } }
420
421 // T3 replace-by-node
422 total = total + 1
423 sg_mkrow(row, "laptop" as *u8, SG_MAGIC_16000, SG_MAGIC_4000, now)
424 r = sg_put(log, snap, row)
425 n = sg_read(snap, buf, SG_SNAP_CAP)
426 if r == 0 { if sg_has(buf, n, "GPU laptop 16000 4000 " as *u8) == 1 { if sg_has(buf, n, "GPU laptop 16000 8000 " as *u8) == 0 { pass = pass + 1; sg_puts("T3 replace OK\n" as *u8) } } }
427
428 // T4 NEG: 4-token row refused
429 total = total + 1
430 let before: i64 = sg_read(snap, buf, SG_SNAP_CAP)
431 var o4: i64 = fa_cat(row, 0, "GPU mallory 16000 " as *u8); o4 = fa_catn(row, o4, now); row[o4] = 0 as u8
432 r = sg_put(log, snap, row)
433 n = sg_read(snap, buf, SG_SNAP_CAP)
434 if r == (0 - 3) { if n == before { if sg_has(buf, n, "mallory" as *u8) == 0 { pass = pass + 1; sg_puts("T4 neg-short-row REFUSED\n" as *u8) } } }
435
436 // T5 NEG: wrong prefix refused
437 total = total + 1
438 sg_mkrow(row, "carol" as *u8, SG_MAGIC_8000, SG_MAGIC_8000, now)
439 row[0] = 88 as u8
440 r = sg_put(log, snap, row)
441 n = sg_read(snap, buf, SG_SNAP_CAP)
442 if r == (0 - 3) { if sg_has(buf, n, "carol" as *u8) == 0 { pass = pass + 1; sg_puts("T5 neg-prefix REFUSED\n" as *u8) } }
443
444 // Rebuild a clean 2-node snap for the planner tests: laptop free=4000, west free=20000, util 85
445 // usable: laptop=3400, west=17000, total_usable=20400
446 // T6 PLANNER single-node fit: model 3000 <= laptop usable 3400 -> all on laptop (snap order: laptop first)
447 total = total + 1
448 var rc: i64 = sg_plan(snap, SG_MAGIC_3600, SG_MAGIC_3000, 85)
449 // capture is to stdout; verify by logic: 3000 fits laptop(3400) -> GREEN
450 if rc == 0 { pass = pass + 1; sg_puts("T6 planner single-node-fit GREEN OK\n" as *u8) } else { sg_puts("T6 FAIL rc<>0\n" as *u8) }
451
452 // T7 PLANNER multi-node split: model 19000 > west usable 17000 -> MUST spill onto laptop (17000+2000);
453 // total_usable 20400 >= 19000 -> GREEN across 2 nodes (output shows both SHARD lines = real split proof).
454 total = total + 1
455 rc = sg_plan(snap, SG_MAGIC_3600, SG_MAGIC_19000, 85)
456 if rc == 0 { pass = pass + 1; sg_puts("T7 planner multi-node-split GREEN OK\n" as *u8) } else { sg_puts("T7 FAIL rc<>0\n" as *u8) }
457
458 // T8 LIAR-KILLER refuse-if-insufficient: model 25000 > total_usable 20400 -> REFUSE (rc 3)
459 total = total + 1
460 rc = sg_plan(snap, SG_MAGIC_3600, SG_MAGIC_25000, 85)
461 if rc == 3 { pass = pass + 1; sg_puts("T8 liar-killer refuse-oversize REFUSED OK\n" as *u8) } else { sg_puts("T8 FAIL not-refused\n" as *u8) }
462
463 // T9 LIAR-KILLER stale-excluded: a fresh-window of 1s makes both nodes STALE (ts=now but window tiny?).
464 // Instead put a node with an ANCIENT ts and plan with a small model that ONLY the fresh nodes cover; then
465 // plan with a model that would need the stale node's VRAM -> must REFUSE (stale not counted).
466 // Simpler: window_sec=0 -> nothing is fresh -> REFUSE (no fresh nodes).
467 total = total + 1
468 rc = sg_plan(snap, 0, 1000, 85)
469 if rc == 3 { pass = pass + 1; sg_puts("T9 liar-killer stale-window no-fresh REFUSED OK\n" as *u8) } else { sg_puts("T9 FAIL\n" as *u8) }
470
471 // T10 headroom: util 85 means usable < free. model exactly = free-sum (24000) but > usable-sum (20400) -> REFUSE
472 // (proves the never-100%-VRAM headroom is enforced, not free-sum).
473 total = total + 1
474 rc = sg_plan(snap, SG_MAGIC_3600, SG_MAGIC_21000, 85)
475 if rc == 3 { pass = pass + 1; sg_puts("T10 headroom-enforced (21000>usable20400) REFUSED OK\n" as *u8) } else { sg_puts("T10 FAIL\n" as *u8) }
476
477 // T11 show smoke
478 total = total + 1
479 sg_puts("--- show (window 3600s) ---\n" as *u8)
480 sg_show(snap, SG_MAGIC_3600)
481 pass = pass + 1
482
483 // T12 PATH LAW
484 total = total + 1
485 var p12: i64 = 1
486 if sg_path_ok("../x.snap" as *u8) != 0 { p12 = 0 }
487 if sg_path_ok("/etc/x.snap" as *u8) != 0 { p12 = 0 }
488 if sg_path_ok("/tmp/evil.elf" as *u8) != 0 { p12 = 0 }
489 if sg_path_ok("gpu_rows.snap" as *u8) != 1 { p12 = 0 }
490 if p12 == 1 { pass = pass + 1; sg_puts("T12 path-law OK\n" as *u8) }
491
492 let t: *u8 = sys_mmap(128)
493 var to: i64 = fa_cat(t, 0, "SWARMGPUGATE " as *u8)
494 to = fa_catn(t, to, pass); to = fa_cat(t, to, "/" as *u8); to = fa_catn(t, to, total)
495 if pass == total { to = fa_cat(t, to, " verdict=GREEN\n" as *u8) } else { to = fa_cat(t, to, " verdict=RED\n" as *u8) }
496 sys_write(1, t, to)
497 if pass == total { return 0 }
498 return 1
499}
500
501func main(argc: i64, argv: *i64) -> i64 {
502 if argc >= 2 {
503 let verb: *u8 = argv[1] as *u8
504 if sg_eq(verb, "put" as *u8) == 1 {
505 var log: *u8 = "swarm_gpu.log" as *u8
506 var snap: *u8 = "gpu_rows.snap" as *u8
507 var row: *u8 = 0 as *u8
508 if argc == 3 { row = argv[2] as *u8 }
509 if argc >= 5 { log = argv[2] as *u8; snap = argv[3] as *u8; row = argv[4] as *u8 }
510 if (row as i64) == 0 { sg_puts("usage: nx_swarm_gpu put [<log> <snap>] <GPU-row>\n" as *u8); return 2 }
511 if sg_path_ok(log) == 0 { sg_puts("SWARMGPU put REFUSED (path law)\n" as *u8); return 3 }
512 if sg_path_ok(snap) == 0 { sg_puts("SWARMGPU put REFUSED (path law)\n" as *u8); return 3 }
513 let r: i64 = sg_put(log, snap, row)
514 if r == 0 { sg_puts("SWARMGPU put OK\n" as *u8); return 0 }
515 if r == (0 - 3) { sg_puts("SWARMGPU put REFUSED (row contract)\n" as *u8); return 3 }
516 sg_puts("SWARMGPU put IO-FAIL\n" as *u8)
517 return 4
518 }
519 if sg_eq(verb, "now" as *u8) == 1 {
520 let t: *u8 = sys_mmap(32)
521 var o: i64 = fa_catn(t, 0, sys_now_us())
522 t[o] = 10 as u8
523 sys_write(1, t, o + 1)
524 return 0
525 }
526 if sg_eq(verb, "show" as *u8) == 1 {
527 var snap2: *u8 = "gpu_rows.snap" as *u8
528 var ws: *u8 = 0 as *u8
529 if argc == 3 { ws = argv[2] as *u8 }
530 if argc >= 4 { snap2 = argv[2] as *u8; ws = argv[3] as *u8 }
531 if (ws as i64) == 0 { sg_puts("usage: nx_swarm_gpu show [<snap>] <window_sec>\n" as *u8); return 2 }
532 if sg_path_ok(snap2) == 0 { sg_puts("SWARMGPU show REFUSED (path law)\n" as *u8); return 3 }
533 let vout: *i64 = sys_mmap(16) as *i64
534 let pend: *i64 = sys_mmap(16) as *i64
535 if sg_pint(ws, fa_len(ws), 0, vout, pend) == 0 { return 2 }
536 return sg_show(snap2, vout[0])
537 }
538 if sg_eq(verb, "plan" as *u8) == 1 {
539 if argc < 5 { sg_puts("usage: nx_swarm_gpu plan <snap> <window_sec> <model_mb> [util%]\n" as *u8); return 2 }
540 let snap3: *u8 = argv[2] as *u8
541 if sg_path_ok(snap3) == 0 { sg_puts("SWARMGPU plan REFUSED (path law)\n" as *u8); return 3 }
542 let vo: *i64 = sys_mmap(16) as *i64
543 let pe: *i64 = sys_mmap(16) as *i64
544 let wsp: *u8 = argv[3] as *u8
545 if sg_pint(wsp, fa_len(wsp), 0, vo, pe) == 0 { return 2 }
546 let window: i64 = vo[0]
547 let mmp: *u8 = argv[4] as *u8
548 if sg_pint(mmp, fa_len(mmp), 0, vo, pe) == 0 { return 2 }
549 let model_mb: i64 = vo[0]
550 var util: i64 = SG_DEFAULT_UTIL
551 if argc >= 6 { let up: *u8 = argv[5] as *u8; if sg_pint(up, fa_len(up), 0, vo, pe) == 1 { util = vo[0] } }
552 return sg_plan(snap3, window, model_mb, util)
553 }
554 }
555 return sg_gate()
556}