nx_mcu_fit.nx source
↩ module page · 64 lines · 3913 B
1// nx_mcu_fit.nx -- does a model config fit a specific MCU's memory hierarchy? Pure decision logic.
2//
3// THE ONE RULE THAT MAKES THIS HONEST: an UNMEASURED resource is REFUSED, never assumed. mcu_targets.conf
4// carries -1 for every field we have not put on a bench, and a -1 must never be silently treated as 0
5// (infinite room) or as a datasheet guess. If we cannot decide, we say so and name the field to go measure.
6// That is what makes this organ useful for hardware bring-up rather than merely decorative: pointing it at
7// an unmeasured board returns a WORK ORDER, not a verdict.
8//
9// The second rule, inherited from the memfloor incident: REFUSE, NEVER SILENTLY SHRINK. An allocator that
10// quietly reduces a grid to make it fit froze every seat's builds for minutes. So there is no "nearest
11// fitting config" return path here -- the caller is told which wall it hit and decides.
12//
13// Resource model, matching how a PLE-style model actually lands on an ESP32-S3:
14// dense core -> internal SRAM (fast, tiny, the real constraint that sizes the model)
15// weights+table-> SPI flash (large, slow, XIP / memory-mapped)
16// KV + scratch -> PSRAM (medium, bandwidth-bound)
17// Checks run in that order so the BINDING constraint reported is the tightest real one, deterministically.
18// license_tier: ORIGINAL No hw writes (Rule 26).
19import "nx_syscalls.nx"
20
21const MF_FIT: i64 = 0
22const MF_REFUSE_SRAM: i64 = 1
23const MF_REFUSE_FLASH: i64 = 2
24const MF_REFUSE_PSRAM: i64 = 3
25const MF_REFUSE_UNMEASURED: i64 = 4
26const MF_UNMEASURED: i64 = 0 - 1
27const MF_KB: i64 = 1024
28const MF_MB: i64 = 1048576
29
30func mf_reason(v: i64) -> *u8 {
31 if v == MF_FIT { return "FIT" as *u8 }
32 if v == MF_REFUSE_SRAM { return "REFUSE binding=SRAM (dense core does not fit internal SRAM)" as *u8 }
33 if v == MF_REFUSE_FLASH { return "REFUSE binding=FLASH (weights+table do not fit the flash partition)" as *u8 }
34 if v == MF_REFUSE_PSRAM { return "REFUSE binding=PSRAM (KV cache + scratch do not fit PSRAM)" as *u8 }
35 return "REFUSE binding=UNMEASURED (bench this target before trusting any verdict)" as *u8
36}
37
38// bytes a dense core of `params` weights occupies at `bits` per weight
39func mf_core_bytes(params: i64, bits: i64) -> i64 {
40 if params <= 0 { return 0 }
41 if bits <= 0 { return 0 }
42 return (params * bits) / 8
43}
44
45// THE DECISION. Capacities are as read from mcu_targets.conf (-1 == UNMEASURED). Demands are in bytes.
46// Returns MF_FIT or the specific MF_REFUSE_* naming the wall that was hit.
47func mf_admit(sram_kb: i64, psram_mb: i64, flash_mb: i64,
48 core_bytes: i64, flash_bytes: i64, psram_bytes: i64) -> i64 {
49 // Undecidable beats every capacity answer: we refuse to compute on a number nobody measured.
50 // Only a resource we are actually ASKED for can make the call undecidable -- a board with no PSRAM
51 // measurement is still a perfectly decidable target for a model that needs no PSRAM.
52 if core_bytes > 0 { if sram_kb == MF_UNMEASURED { return MF_REFUSE_UNMEASURED } }
53 if flash_bytes > 0 { if flash_mb == MF_UNMEASURED { return MF_REFUSE_UNMEASURED } }
54 if psram_bytes > 0 { if psram_mb == MF_UNMEASURED { return MF_REFUSE_UNMEASURED } }
55
56 // Each capacity check is guarded on a NON-ZERO demand. Without the guard an UNMEASURED (-1) capacity
57 // becomes a NEGATIVE byte budget, so even a demand of 0 exceeds it and the organ refuses a config it
58 // should trivially admit. Caught by T6 (the scoping neg-control) on first run, not in production.
59 // By this point any resource with demand > 0 is known measured, because the block above returned.
60 if core_bytes > 0 { if core_bytes > sram_kb * MF_KB { return MF_REFUSE_SRAM } }
61 if flash_bytes > 0 { if flash_bytes > flash_mb * MF_MB { return MF_REFUSE_FLASH } }
62 if psram_bytes > 0 { if psram_bytes > psram_mb * MF_MB { return MF_REFUSE_PSRAM } }
63 return MF_FIT
64}