nx_mesh_autoscale.nx source
↩ module page · 224 lines · 12304 B
1// nx_mesh_autoscale.nx -- WORKER MESH F3: sovereign AUTOSCALE-TO-ZERO controller (the Modal/KServe analog).
2//
3// A GPU worker should hold VRAM only while there is work. This is the LIFECYCLE POLICY: given whether the worker is
4// warm (model in VRAM), whether there is demand (a pending/recent job), and how long it has been idle vs a TTL, it
5// decides one action -- SPAWN (warm it), SERVE (dispatch now), HOLD (do nothing), or REAP (free VRAM). The policy is
6// identical whether it runs natively on a NishiOS+VRAM host or drives a worker-host agent; the actuation (start/stop
7// the worker process) lives at the host boundary (CreateProcessW on Windows today, native on NishiOS).
8//
9// Nishi is STRUCTURALLY ahead on scale-to-zero: idle = 0 MB / 0 procs (measured) and cold start ~1s, so demand-spawn +
10// idle-reap costs almost nothing -- exactly where the serving frameworks are "Best".
11//
12// CLI: (no args) -> self-test GATE (decision table + savings + neg-controls)
13// tick <demand> <idle_sec> <ttl_sec> -> live: probe the worker for warmth, then print the DECISION (a host
14// agent calls this each cycle and actuates SPAWN/REAP)
15// tick <demand> <idle> <ttl> <a> <b> <c> <d> -> probe an explicit worker ipv4
16// Actions: 0 HOLD 1 SPAWN 2 SERVE 3 REAP. NO fake greens: the gate proves every transition + that REAP never fires
17// while warm-and-busy and SPAWN never fires while already warm. license_tier: ORIGINAL
18import "nx_syscalls.nx"
19import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
20import "nx_runtime.nx"
21import "nx_http_client.nx"
22const AM_MAGIC_7861: i64 = 7861
23const AM_MAGIC_2048: i64 = 2048
24const AM_MAGIC_2000: i64 = 2000
25const AM_MAGIC_16384: i64 = 16384
26const AM_MAGIC_16383: i64 = 16383
27
28const AM_HOLD: i64 = 0
29const AM_SPAWN: i64 = 1
30const AM_SERVE: i64 = 2
31const AM_REAP: i64 = 3
32
33func am_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
34func am_wn(fd: i64, v: i64) -> i64 {
35 let bb: *u8 = sys_mmap(28)
36 var m: i64 = v
37 if m < 0 { sys_write(fd, "-" as *u8, 1); m = 0 - m }
38 let tt: *u8 = sys_mmap(28)
39 var k: i64 = 0
40 if m == 0 { tt[0] = 48 as u8; k = 1 }
41 while m > 0 { tt[k] = (48 + (m - (m/10)*10)) as u8; m = m / 10; k = k + 1 }
42 var i: i64 = 0
43 while i < k { bb[i] = tt[k-1-i]; i = i + 1 }
44 sys_write(fd, bb, k)
45 return 0
46}
47func am_p(s: *u8) -> i64 { return am_w(1, s) }
48func am_pn(v: i64) -> i64 { return am_wn(1, v) }
49func am_atoi(s: *u8) -> i64 { var v: i64 = 0; var i: i64 = 0; while s[i] != (0 as u8) { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } return v }
50func am_action_name(a: i64) -> *u8 {
51 if a == AM_HOLD { return "HOLD" as *u8 }
52 if a == AM_SPAWN { return "SPAWN" as *u8 }
53 if a == AM_SERVE { return "SERVE" as *u8 }
54 if a == AM_REAP { return "REAP" as *u8 }
55 return "?" as *u8
56}
57
58// ---- THE POLICY (pure) ----
59// warm: model in VRAM (1/0). demand: pending/recent job (1/0). idle_sec: seconds since last activity. ttl_sec: reap threshold.
60func am_decide(warm: i64, demand: i64, idle_sec: i64, ttl_sec: i64) -> i64 {
61 if demand == 1 {
62 if warm == 0 { return AM_SPAWN } // work waiting + cold -> warm it
63 return AM_SERVE // work waiting + warm -> dispatch now
64 }
65 // no demand
66 if warm == 1 {
67 if idle_sec >= ttl_sec { return AM_REAP } // warm but idle past TTL -> free VRAM (scale to zero)
68 return AM_HOLD // warm + recently used -> keep warm briefly
69 }
70 return AM_HOLD // already cold + no demand -> at zero, nothing to do
71}
72
73// VRAM-seconds-saved vs always-on, in permil: (window - warm) / window.
74func am_savings_permil(warm_sec: i64, window_sec: i64) -> i64 {
75 if window_sec <= 0 { return 0 }
76 var off: i64 = window_sec - warm_sec
77 if off < 0 { off = 0 }
78 return (off * 1000) / window_sec
79}
80
81// ---- live warmth probe: BOUNDED connect (non-blocking + poll) so a COLD worker is detected in ~1s, never hangs.
82// A blocking connect to a firewalled/dropped port hangs for the OS default (tens of seconds) -- fatal for an
83// autoscaler that must SPAWN promptly. Non-blocking connect + poll(POLLOUT, 1000ms) bounds it. ----
84func am_resp_2xx(buf: *u8, n: i64) -> i64 {
85 var i: i64 = 0
86 while i + 3 < n {
87 if buf[i] == (50 as u8) { if buf[i+1] == (48 as u8) { if buf[i+2] == (48 as u8) { return 1 } } }
88 if buf[i] == (10 as u8) { i = n }
89 i = i + 1
90 }
91 return 0
92}
93// build a struct pollfd {int fd; short events; short revents} at pfd (8 bytes)
94func am_pollfd(pfd: *u8, fd: i64, events: i64) -> i64 {
95 pfd[0] = (fd & 0xff) as u8; pfd[1] = ((fd >> 8) & 0xff) as u8; pfd[2] = ((fd >> 16) & 0xff) as u8; pfd[3] = ((fd >> 24) & 0xff) as u8
96 pfd[4] = (events & 0xff) as u8; pfd[5] = ((events >> 8) & 0xff) as u8
97 pfd[6] = 0 as u8; pfd[7] = 0 as u8
98 return 0
99}
100func am_probe_warm(a: i64, b: i64, c: i64, d: i64) -> i64 {
101 let sa: *u8 = sys_mmap(16)
102 nx_http_client_sockaddr_ipv4(sa, a, b, c, d, AM_MAGIC_7861)
103 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM | AM_MAGIC_2048, 0) // SOCK_NONBLOCK=0x800 -> connect returns immediately
104 if fd < 0 { return 0 }
105 nx_connect_bounded(fd, sa, 16, NX_CONN_DEFAULT_MS) // EINPROGRESS expected; do not block on it
106 let pfd: *u8 = sys_mmap(8)
107 am_pollfd(pfd, fd, 4) // POLLOUT
108 let pn: i64 = sys_poll(pfd, 1, 1000) // 1s connect budget
109 if pn <= 0 { sys_close(fd); return 0 } // cold worker detected FAST (no hang)
110 let rev: i64 = (pfd[6] as i64) | ((pfd[7] as i64) << 8)
111 if (rev & 8) != 0 { sys_close(fd); return 0 } // POLLERR -> connect refused
112 if (rev & 16) != 0 { sys_close(fd); return 0 } // POLLHUP -> peer gone
113 // DERIVED FROM THE BUILDER'S OWN BOUND (2026-08-27). This read sys_mmap(512): a guess that
114 // fits today's ~357-byte request and overruns the day the identity or caps headers grow, as
115 // its 256-byte siblings already did (crash guard: ARENA-OVERRUN prev_alloc_size=256 on the
116 // browser's page-demo row). nx_http_client owns the bound; callers compose it.
117 let req: *u8 = sys_mmap(nx_http_client_request_cap(10, 6, 0, 0))
118 let rl: i64 = nx_http_client_build_request("/v1/models" as *u8, 10, "worker" as *u8, 6, req)
119 sys_write(fd, req, rl)
120 am_pollfd(pfd, fd, 1) // POLLIN
121 let pn2: i64 = sys_poll(pfd, 1, AM_MAGIC_2000)
122 if pn2 <= 0 { sys_close(fd); return 0 }
123 let out: *u8 = sys_mmap(AM_MAGIC_16384)
124 let got: i64 = sys_read(fd, out, AM_MAGIC_16383)
125 sys_close(fd)
126 if got <= 0 { return 0 }
127 return am_resp_2xx(out, got)
128}
129
130func am_gate() -> i64 {
131 am_p("=== nx_mesh_autoscale: sovereign autoscale-to-zero controller (Modal/KServe analog) ===\n" as *u8)
132 // decision table
133 var t1: i64 = 0
134 if am_decide(0, 1, 0, 60) == AM_SPAWN { t1 = 1 } // cold + demand -> SPAWN
135 var t2: i64 = 0
136 if am_decide(1, 1, 0, 60) == AM_SERVE { t2 = 1 } // warm + demand -> SERVE
137 var t3: i64 = 0
138 if am_decide(1, 0, 90, 60) == AM_REAP { t3 = 1 } // warm + idle>ttl -> REAP
139 var t4: i64 = 0
140 if am_decide(1, 0, 30, 60) == AM_HOLD { t4 = 1 } // warm + idle<ttl -> HOLD (no early reap)
141 var t5: i64 = 0
142 if am_decide(0, 0, 999, 60) == AM_HOLD { t5 = 1 } // cold + no demand -> HOLD (already at zero)
143 var t6: i64 = 0
144 if am_decide(0, 1, 999, 60) == AM_SPAWN { t6 = 1 } // demand beats idle: cold+demand -> SPAWN not HOLD
145 // savings metric
146 var t7: i64 = 0
147 if am_savings_permil(30, 100) == 700 { t7 = 1 } // warm 30 of 100s -> 700 permil VRAM saved
148 // neg-controls (liar-kill): REAP must NEVER fire while busy or not-idle; SPAWN never while warm
149 var neg1: i64 = 0
150 if am_decide(1, 1, 999, 60) != AM_REAP { neg1 = 1 } // warm+demand never reaps (serves)
151 var neg2: i64 = 0
152 if am_decide(1, 0, 30, 60) != AM_REAP { neg2 = 1 } // warm+not-idle never reaps
153 var neg3: i64 = 0
154 if am_decide(1, 1, 0, 60) != AM_SPAWN { neg3 = 1 } // already warm never spawns
155
156 am_p(" T1 cold+demand->SPAWN: " as *u8); am_pn(t1)
157 am_p(" | T2 warm+demand->SERVE: " as *u8); am_pn(t2)
158 am_p(" | T3 warm+idle>ttl->REAP: " as *u8); am_pn(t3)
159 am_p(" | T4 warm+idle<ttl->HOLD: " as *u8); am_pn(t4)
160 am_p(" | T5 cold+nodemand->HOLD: " as *u8); am_pn(t5)
161 am_p(" | T6 demand-beats-idle: " as *u8); am_pn(t6)
162 am_p(" | T7 savings 30/100=700permil: " as *u8); am_pn(t7)
163 am_p(" | neg1 no-reap-while-busy: " as *u8); am_pn(neg1)
164 am_p(" | neg2 no-reap-while-fresh: " as *u8); am_pn(neg2)
165 am_p(" | neg3 no-spawn-while-warm: " as *u8); am_pn(neg3); am_p("\n" as *u8)
166
167 let sfd: i64 = sys_openat_wr("knowledge/status/mesh_autoscale.tsv" as *u8, 0x1a4)
168 if sfd >= 0 {
169 am_w(sfd, "# nx_mesh_autoscale -- scale-to-zero policy: SPAWN on demand, REAP on idle-TTL, HOLD otherwise\n" as *u8)
170 am_w(sfd, "decision_table_green\t" as *u8); am_wn(sfd, t1); am_w(sfd, "\n" as *u8)
171 am_w(sfd, "savings_permil_30of100\t" as *u8); am_wn(sfd, am_savings_permil(30, 100)); am_w(sfd, "\n" as *u8)
172 sys_close(sfd)
173 }
174
175 var pass: i64 = 0
176 if t1==1 { if t2==1 { if t3==1 { if t4==1 { if t5==1 { if t6==1 { if t7==1 { if neg1==1 { if neg2==1 { if neg3==1 { pass = 1 } } } } } } } } } }
177 if pass == 1 { am_p("MESHAUTOSCALEGATE verdict=GREEN (SPAWN/SERVE/HOLD/REAP correct; savings measured; liar-killed)\n" as *u8); return 0 }
178 am_p("MESHAUTOSCALEGATE verdict=RED\n" as *u8)
179 return 1
180}
181
182func am_streq(a: *u8, b: *u8) -> i64 {
183 var i: i64 = 0
184 while b[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
185 if a[i] != (0 as u8) { return 0 }
186 return 1
187}
188
189func main(argc: i64, argv: *i64) -> i64 {
190 if argc >= 2 {
191 let cmd: *u8 = argv[1] as *u8
192 // decide <warm> <demand> <idle> <ttl> -- pure decision, no probe (deterministic)
193 if am_streq(cmd, "decide" as *u8) == 1 {
194 if argc < 6 { am_w(2, "usage: nx_mesh_autoscale decide <warm> <demand> <idle_sec> <ttl_sec>\n" as *u8); return 2 }
195 let w: i64 = am_atoi(argv[2] as *u8)
196 let dm: i64 = am_atoi(argv[3] as *u8)
197 let idl: i64 = am_atoi(argv[4] as *u8)
198 let tl: i64 = am_atoi(argv[5] as *u8)
199 let act: i64 = am_decide(w, dm, idl, tl)
200 am_p("decide warm=" as *u8); am_pn(w); am_p(" demand=" as *u8); am_pn(dm); am_p(" idle=" as *u8); am_pn(idl); am_p("s ttl=" as *u8); am_pn(tl); am_p("s -> ACTION=" as *u8); am_p(am_action_name(act)); am_p("\n" as *u8)
201 return act
202 }
203 var same: i64 = 1
204 var qi: i64 = 0
205 let t: *u8 = "tick" as *u8
206 while t[qi] != (0 as u8) { if cmd[qi] != t[qi] { same = 0 } qi = qi + 1 }
207 if cmd[qi] != (0 as u8) { same = 0 }
208 if same == 1 {
209 if argc < 5 { am_w(2, "usage: nx_mesh_autoscale tick <demand> <idle_sec> <ttl_sec> [a b c d]\n" as *u8); return 2 }
210 let demand: i64 = am_atoi(argv[2] as *u8)
211 let idle: i64 = am_atoi(argv[3] as *u8)
212 let ttl: i64 = am_atoi(argv[4] as *u8)
213 var a: i64 = 192; var b: i64 = 168; var c: i64 = 8; var d: i64 = 193
214 if argc >= 9 { a = am_atoi(argv[5] as *u8); b = am_atoi(argv[6] as *u8); c = am_atoi(argv[7] as *u8); d = am_atoi(argv[8] as *u8) }
215 let warm: i64 = am_probe_warm(a, b, c, d)
216 let act: i64 = am_decide(warm, demand, idle, ttl)
217 am_p("tick warm=" as *u8); am_pn(warm); am_p(" demand=" as *u8); am_pn(demand); am_p(" idle=" as *u8); am_pn(idle); am_p("s ttl=" as *u8); am_pn(ttl); am_p("s -> ACTION=" as *u8); am_p(am_action_name(act)); am_p("\n" as *u8)
218 return act
219 }
220 am_w(2, "usage: nx_mesh_autoscale tick <demand> <idle_sec> <ttl_sec> (no args = gate)\n" as *u8)
221 return 2
222 }
223 return am_gate()
224}