code wiki / (root) / nx_mesh_autoscale.nx

nx_mesh_autoscale.nx source

↩ module page · 220 lines · 11895 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 let req: *u8 = sys_mmap(512) 114 let rl: i64 = nx_http_client_build_request("/v1/models" as *u8, 10, "worker" as *u8, 6, req) 115 sys_write(fd, req, rl) 116 am_pollfd(pfd, fd, 1) // POLLIN 117 let pn2: i64 = sys_poll(pfd, 1, AM_MAGIC_2000) 118 if pn2 <= 0 { sys_close(fd); return 0 } 119 let out: *u8 = sys_mmap(AM_MAGIC_16384) 120 let got: i64 = sys_read(fd, out, AM_MAGIC_16383) 121 sys_close(fd) 122 if got <= 0 { return 0 } 123 return am_resp_2xx(out, got) 124} 125 126func am_gate() -> i64 { 127 am_p("=== nx_mesh_autoscale: sovereign autoscale-to-zero controller (Modal/KServe analog) ===\n" as *u8) 128 // decision table 129 var t1: i64 = 0 130 if am_decide(0, 1, 0, 60) == AM_SPAWN { t1 = 1 } // cold + demand -> SPAWN 131 var t2: i64 = 0 132 if am_decide(1, 1, 0, 60) == AM_SERVE { t2 = 1 } // warm + demand -> SERVE 133 var t3: i64 = 0 134 if am_decide(1, 0, 90, 60) == AM_REAP { t3 = 1 } // warm + idle>ttl -> REAP 135 var t4: i64 = 0 136 if am_decide(1, 0, 30, 60) == AM_HOLD { t4 = 1 } // warm + idle<ttl -> HOLD (no early reap) 137 var t5: i64 = 0 138 if am_decide(0, 0, 999, 60) == AM_HOLD { t5 = 1 } // cold + no demand -> HOLD (already at zero) 139 var t6: i64 = 0 140 if am_decide(0, 1, 999, 60) == AM_SPAWN { t6 = 1 } // demand beats idle: cold+demand -> SPAWN not HOLD 141 // savings metric 142 var t7: i64 = 0 143 if am_savings_permil(30, 100) == 700 { t7 = 1 } // warm 30 of 100s -> 700 permil VRAM saved 144 // neg-controls (liar-kill): REAP must NEVER fire while busy or not-idle; SPAWN never while warm 145 var neg1: i64 = 0 146 if am_decide(1, 1, 999, 60) != AM_REAP { neg1 = 1 } // warm+demand never reaps (serves) 147 var neg2: i64 = 0 148 if am_decide(1, 0, 30, 60) != AM_REAP { neg2 = 1 } // warm+not-idle never reaps 149 var neg3: i64 = 0 150 if am_decide(1, 1, 0, 60) != AM_SPAWN { neg3 = 1 } // already warm never spawns 151 152 am_p(" T1 cold+demand->SPAWN: " as *u8); am_pn(t1) 153 am_p(" | T2 warm+demand->SERVE: " as *u8); am_pn(t2) 154 am_p(" | T3 warm+idle>ttl->REAP: " as *u8); am_pn(t3) 155 am_p(" | T4 warm+idle<ttl->HOLD: " as *u8); am_pn(t4) 156 am_p(" | T5 cold+nodemand->HOLD: " as *u8); am_pn(t5) 157 am_p(" | T6 demand-beats-idle: " as *u8); am_pn(t6) 158 am_p(" | T7 savings 30/100=700permil: " as *u8); am_pn(t7) 159 am_p(" | neg1 no-reap-while-busy: " as *u8); am_pn(neg1) 160 am_p(" | neg2 no-reap-while-fresh: " as *u8); am_pn(neg2) 161 am_p(" | neg3 no-spawn-while-warm: " as *u8); am_pn(neg3); am_p("\n" as *u8) 162 163 let sfd: i64 = sys_openat_wr("knowledge/status/mesh_autoscale.tsv" as *u8, 0x1a4) 164 if sfd >= 0 { 165 am_w(sfd, "# nx_mesh_autoscale -- scale-to-zero policy: SPAWN on demand, REAP on idle-TTL, HOLD otherwise\n" as *u8) 166 am_w(sfd, "decision_table_green\t" as *u8); am_wn(sfd, t1); am_w(sfd, "\n" as *u8) 167 am_w(sfd, "savings_permil_30of100\t" as *u8); am_wn(sfd, am_savings_permil(30, 100)); am_w(sfd, "\n" as *u8) 168 sys_close(sfd) 169 } 170 171 var pass: i64 = 0 172 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 } } } } } } } } } } 173 if pass == 1 { am_p("MESHAUTOSCALEGATE verdict=GREEN (SPAWN/SERVE/HOLD/REAP correct; savings measured; liar-killed)\n" as *u8); return 0 } 174 am_p("MESHAUTOSCALEGATE verdict=RED\n" as *u8) 175 return 1 176} 177 178func am_streq(a: *u8, b: *u8) -> i64 { 179 var i: i64 = 0 180 while b[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 181 if a[i] != (0 as u8) { return 0 } 182 return 1 183} 184 185func main(argc: i64, argv: *i64) -> i64 { 186 if argc >= 2 { 187 let cmd: *u8 = argv[1] as *u8 188 // decide <warm> <demand> <idle> <ttl> -- pure decision, no probe (deterministic) 189 if am_streq(cmd, "decide" as *u8) == 1 { 190 if argc < 6 { am_w(2, "usage: nx_mesh_autoscale decide <warm> <demand> <idle_sec> <ttl_sec>\n" as *u8); return 2 } 191 let w: i64 = am_atoi(argv[2] as *u8) 192 let dm: i64 = am_atoi(argv[3] as *u8) 193 let idl: i64 = am_atoi(argv[4] as *u8) 194 let tl: i64 = am_atoi(argv[5] as *u8) 195 let act: i64 = am_decide(w, dm, idl, tl) 196 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) 197 return act 198 } 199 var same: i64 = 1 200 var qi: i64 = 0 201 let t: *u8 = "tick" as *u8 202 while t[qi] != (0 as u8) { if cmd[qi] != t[qi] { same = 0 } qi = qi + 1 } 203 if cmd[qi] != (0 as u8) { same = 0 } 204 if same == 1 { 205 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 } 206 let demand: i64 = am_atoi(argv[2] as *u8) 207 let idle: i64 = am_atoi(argv[3] as *u8) 208 let ttl: i64 = am_atoi(argv[4] as *u8) 209 var a: i64 = 192; var b: i64 = 168; var c: i64 = 8; var d: i64 = 193 210 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) } 211 let warm: i64 = am_probe_warm(a, b, c, d) 212 let act: i64 = am_decide(warm, demand, idle, ttl) 213 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) 214 return act 215 } 216 am_w(2, "usage: nx_mesh_autoscale tick <demand> <idle_sec> <ttl_sec> (no args = gate)\n" as *u8) 217 return 2 218 } 219 return am_gate() 220}