code wiki / (root) / nx_mcu_fit.nx

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}