code wiki / (root) / nx_lora_pool.nx

nx_lora_pool.nx source

↩ module page · 318 lines · 12371 B

1// nx_lora_pool.nx -- H9 Multi-LoRA adapter pool (bits-up). 2// 3// Per NISHI_ELDER_AI_OFF_DOCKER_2026_05_20.md ยง2.1 H9: serve hundreds 4// of fine-tuned adapters from a single base model. Punica / S-LoRA 5// ship this at scale -- one VRAM-resident backbone, N small 6// LoRA-rank adapters (rank 4..64), per-request adapter selection 7// at batch step time. 8// 9// V1 mechanics: a fixed pool of NxLoraAdapter records keyed by 10// caller-supplied adapter_id. Each adapter carries opaque A/B 11// tensor pointers (the substrate doesn't need to know the tensor 12// shape to schedule routing; the consumer that runs the forward 13// pass knows). A refcount tracks how many active batch slots / 14// pending requests currently reference each adapter, so evict() 15// can refuse while the adapter is in flight. 16// 17// Composition path: 18// - register_adapter once when the adapter is loaded to disk/VRAM 19// - acquire(id) when admit()ing a batch slot that wants the adapter 20// - release(id) when the batch slot transitions DONE 21// - evict(id) only when refcount drops to zero (cold-swap window) 22// 23// The lookup is linear over n_loaded (V1 honest scope: pool size 24// stays small enough -- NX_LORA_POOL_MAX_ADAPTERS = 128 -- that 25// linear scan beats a hash table on the substrate's allocation 26// model; V2 can promote to open-addressed hash if profile demands). 27// 28// Pure substrate logic. No Linux features. Composes with shipped 29// nx_batch_scheduler (each batch slot will eventually carry an 30// adapter_id field threading through admit/step/complete). 31// 32// V1 honest scope: 33// - Adapter ID is i64 caller-supplied (no auto-allocation in V1) 34// - Allowed LoRA ranks are sealed-enum: 4, 8, 16, 32, 64 35// - Tensor data layout is OPAQUE to this primitive (A_data / B_data 36// are *i64 pointers the consumer dereferences with its own 37// shape knowledge; this matches how H1 nx_kv_arena treats 38// per-page payload bytes) 39// - alpha is stored as Q16 fixed-point (scale * 65536) to keep 40// this primitive integer-only at the substrate layer 41// 42// genealogy_id: punica_2023 + slora_iclr_2024 + 43// cardinal_2026-05-20_elder_ai_off_docker + 44// cardinal_2026-05-20_bits_up_nishi_not_linux 45// lineage_id: substrate_lora_pool_v1 46// 47// nx_capability_manifest: 48// variant_class: lora_pool 49// variant_id: lora_pool_v1_linear_lookup 50// requires_isa: [rv32i, rv64imac, x86_64, cortex_m, armv7a, aarch64, wasm32] 51// requires_syscalls: [mmap] 52// requires_ram_min_b: 8192 53// tier_floor: NX_TIER_INF_MOBILE 54// tier_ceiling: NX_TIER_INF_HPC 55// cost_model: 56// flops_per_n: 1.0 // O(n_loaded) lookup per request 57// bytes_per_n: 72.0 // per-adapter record + ptr 58// syscalls_per_n: 0.0 59// adversary_class: THREAT_AI_ADVERSARY 60// 61// nx_safety_envelope: 62// intended_use: "LoRA adapter pool for multi-tenant LLM serving; 63// bits-up; composes with nx_batch_scheduler" 64// sil_target: SIL2 65// evidence: [canary_bracketed, rank_sealed_enum, 66// refusal_on_in_use_evict, refcount_invariant] 67// verdict: NOT_YET_EVALUATED 68 69import "nx_syscalls.nx" 70const NX_MAGIC_65536: i64 = 65536 71 72// ===== Constants ================================================= 73const NX_LORA_POOL_MAX_ADAPTERS: i64 = 128 74 75// Allowed LoRA ranks (sealed). V1 standard set. 76const NX_LORA_RANK_4: i64 = 4 77const NX_LORA_RANK_8: i64 = 8 78const NX_LORA_RANK_16: i64 = 16 79const NX_LORA_RANK_32: i64 = 32 80const NX_LORA_RANK_64: i64 = 64 81 82func nx_lora_rank_is_valid(r: i64) -> i64 { 83 if r == NX_LORA_RANK_4 { return 1 } 84 if r == NX_LORA_RANK_8 { return 1 } 85 if r == NX_LORA_RANK_16 { return 1 } 86 if r == NX_LORA_RANK_32 { return 1 } 87 if r == NX_LORA_RANK_64 { return 1 } 88 return 0 89} 90 91// Verdicts. 92const NX_LORA_OK: i64 = 0 93const NX_LORA_BAD_INPUT: i64 = 1 94const NX_LORA_NOT_FOUND: i64 = 2 95const NX_LORA_DUPLICATE: i64 = 3 96const NX_LORA_FULL: i64 = 4 97const NX_LORA_IN_USE: i64 = 5 98const NX_LORA_BAD_RANK: i64 = 6 99const NX_LORA_TAMPER: i64 = 7 100const NX_LORA_N_VERDICTS: i64 = 8 101 102func nx_lora_verdict_is_valid(v: i64) -> i64 { 103 if v < 0 { return 0 } 104 if v >= NX_LORA_N_VERDICTS { return 0 } 105 return 1 106} 107 108// Canary magic numbers (random 64-bit constants). 109const NX_LORA_ADAPTER_CANARY_PRE: i64 = 0x4C6F526141647072 // "LoRaAdpr" 110const NX_LORA_ADAPTER_CANARY_POST: i64 = 0x4164707241646E64 // "AdprAdnd" 111const NX_LORA_POOL_CANARY_PRE: i64 = 0x4C6F5261506F6F6C // "LoRaPool" 112const NX_LORA_POOL_CANARY_POST: i64 = 0x506F6F6C456E6464 // "PoolEndd" 113 114// ===== Structs ==================================================== 115struct NxLoraAdapter { 116 canary_pre: i64, 117 adapter_id: i64, 118 rank: i64, 119 n_layers: i64, 120 a_data: *i64, // opaque payload; consumer interprets shape 121 b_data: *i64, 122 alpha_q16: i64, // scale * NX_MAGIC_65536 123 refcount: i64, 124 canary_post: i64, 125} 126 127struct NxLoraPool { 128 canary_pre: i64, 129 max_adapters: i64, 130 n_loaded: i64, 131 adapters: *i64, // *i64 array of *NxLoraAdapter pointers (stored as i64) 132 canary_post: i64, 133} 134 135// ===== Validity ================================================= 136func nx_lora_adapter_is_valid(a: *NxLoraAdapter) -> i64 { 137 if (a as i64) == 0 { return 0 } 138 if a.canary_pre != NX_LORA_ADAPTER_CANARY_PRE { return 0 } 139 if a.canary_post != NX_LORA_ADAPTER_CANARY_POST { return 0 } 140 if nx_lora_rank_is_valid(a.rank) != 1 { return 0 } 141 if a.n_layers <= 0 { return 0 } 142 if a.refcount < 0 { return 0 } 143 return 1 144} 145 146func nx_lora_pool_is_valid(p: *NxLoraPool) -> i64 { 147 if (p as i64) == 0 { return 0 } 148 if p.canary_pre != NX_LORA_POOL_CANARY_PRE { return 0 } 149 if p.canary_post != NX_LORA_POOL_CANARY_POST { return 0 } 150 if p.max_adapters <= 0 { return 0 } 151 if p.max_adapters > NX_LORA_POOL_MAX_ADAPTERS { return 0 } 152 if p.n_loaded < 0 { return 0 } 153 if p.n_loaded > p.max_adapters { return 0 } 154 return 1 155} 156 157// ===== Constructor ================================================= 158func nx_lora_pool_new(max_adapters: i64) -> *NxLoraPool { 159 if max_adapters <= 0 { return (0 as i64) as *NxLoraPool } 160 if max_adapters > NX_LORA_POOL_MAX_ADAPTERS { return (0 as i64) as *NxLoraPool } 161 162 let p: *NxLoraPool = (sys_mmap(48)) as *NxLoraPool 163 p.canary_pre = NX_LORA_POOL_CANARY_PRE 164 p.max_adapters = max_adapters 165 p.n_loaded = 0 166 p.adapters = (sys_mmap(max_adapters * 8)) as *i64 167 p.canary_post = NX_LORA_POOL_CANARY_POST 168 169 var i: i64 = 0 170 while i < max_adapters { 171 p.adapters[i] = 0 172 i = i + 1 173 } 174 return p 175} 176 177// ===== Internal helpers ================================================= 178// Returns slot index of adapter with given id, or -1 if not found. 179func nx_lora__find_slot(p: *NxLoraPool, adapter_id: i64) -> i64 { 180 var i: i64 = 0 181 while i < p.n_loaded { 182 let a_ptr: i64 = p.adapters[i] 183 if a_ptr != 0 { 184 let a: *NxLoraAdapter = a_ptr as *NxLoraAdapter 185 if a.adapter_id == adapter_id { return i } 186 } 187 i = i + 1 188 } 189 return 0 - 1 190} 191 192// ===== Register ================================================= 193// Adds a new adapter. Returns slot idx on success, or 0-error code. 194// - rank must be in sealed set (4/8/16/32/64) 195// - n_layers must be > 0 196// - adapter_id must not already be registered 197// - pool must have a free slot 198// On success the new adapter's refcount is initialized to 0. 199func nx_lora_register(p: *NxLoraPool, adapter_id: i64, rank: i64, n_layers: i64, a_data: *i64, b_data: *i64, alpha_q16: i64) -> i64 { 200 if nx_lora_pool_is_valid(p) != 1 { return 0 - NX_LORA_TAMPER } 201 if nx_lora_rank_is_valid(rank) != 1 { return 0 - NX_LORA_BAD_RANK } 202 if n_layers <= 0 { return 0 - NX_LORA_BAD_INPUT } 203 if alpha_q16 < 0 { return 0 - NX_LORA_BAD_INPUT } 204 if (a_data as i64) == 0 { return 0 - NX_LORA_BAD_INPUT } 205 if (b_data as i64) == 0 { return 0 - NX_LORA_BAD_INPUT } 206 207 if nx_lora__find_slot(p, adapter_id) >= 0 { return 0 - NX_LORA_DUPLICATE } 208 if p.n_loaded >= p.max_adapters { return 0 - NX_LORA_FULL } 209 210 let a: *NxLoraAdapter = (sys_mmap(72)) as *NxLoraAdapter 211 a.canary_pre = NX_LORA_ADAPTER_CANARY_PRE 212 a.adapter_id = adapter_id 213 a.rank = rank 214 a.n_layers = n_layers 215 a.a_data = a_data 216 a.b_data = b_data 217 a.alpha_q16 = alpha_q16 218 a.refcount = 0 219 a.canary_post = NX_LORA_ADAPTER_CANARY_POST 220 221 let slot_idx: i64 = p.n_loaded 222 p.adapters[slot_idx] = a as i64 223 p.n_loaded = p.n_loaded + 1 224 return slot_idx 225} 226 227// ===== Lookup ================================================= 228// Returns the adapter pointer for adapter_id, or null if not found. 229func nx_lora_lookup(p: *NxLoraPool, adapter_id: i64) -> *NxLoraAdapter { 230 if nx_lora_pool_is_valid(p) != 1 { return (0 as i64) as *NxLoraAdapter } 231 let slot: i64 = nx_lora__find_slot(p, adapter_id) 232 if slot < 0 { return (0 as i64) as *NxLoraAdapter } 233 return (p.adapters[slot]) as *NxLoraAdapter 234} 235 236// ===== Acquire ================================================= 237// Bumps refcount. Returns the NEW refcount, or 0-error. 238func nx_lora_acquire(p: *NxLoraPool, adapter_id: i64) -> i64 { 239 if nx_lora_pool_is_valid(p) != 1 { return 0 - NX_LORA_TAMPER } 240 let slot: i64 = nx_lora__find_slot(p, adapter_id) 241 if slot < 0 { return 0 - NX_LORA_NOT_FOUND } 242 let a: *NxLoraAdapter = (p.adapters[slot]) as *NxLoraAdapter 243 if nx_lora_adapter_is_valid(a) != 1 { return 0 - NX_LORA_TAMPER } 244 a.refcount = a.refcount + 1 245 return a.refcount 246} 247 248// ===== Release ================================================= 249// Decrements refcount. Returns the NEW refcount, or 0-error. 250// Refcount cannot go negative; underflow is a BAD_INPUT. 251func nx_lora_release(p: *NxLoraPool, adapter_id: i64) -> i64 { 252 if nx_lora_pool_is_valid(p) != 1 { return 0 - NX_LORA_TAMPER } 253 let slot: i64 = nx_lora__find_slot(p, adapter_id) 254 if slot < 0 { return 0 - NX_LORA_NOT_FOUND } 255 let a: *NxLoraAdapter = (p.adapters[slot]) as *NxLoraAdapter 256 if nx_lora_adapter_is_valid(a) != 1 { return 0 - NX_LORA_TAMPER } 257 if a.refcount <= 0 { return 0 - NX_LORA_BAD_INPUT } 258 a.refcount = a.refcount - 1 259 return a.refcount 260} 261 262// ===== Evict ================================================= 263// Removes the adapter from the pool. Refuses if refcount > 0. 264// On success, the slot is compacted by swapping the tail into the 265// vacated slot. This preserves the n_loaded invariant and the 266// linear-scan model. 267func nx_lora_evict(p: *NxLoraPool, adapter_id: i64) -> i64 { 268 if nx_lora_pool_is_valid(p) != 1 { return 0 - NX_LORA_TAMPER } 269 let slot: i64 = nx_lora__find_slot(p, adapter_id) 270 if slot < 0 { return 0 - NX_LORA_NOT_FOUND } 271 let a: *NxLoraAdapter = (p.adapters[slot]) as *NxLoraAdapter 272 if nx_lora_adapter_is_valid(a) != 1 { return 0 - NX_LORA_TAMPER } 273 if a.refcount > 0 { return 0 - NX_LORA_IN_USE } 274 275 // Compact: swap tail into this slot. 276 let tail: i64 = p.n_loaded - 1 277 if slot != tail { 278 p.adapters[slot] = p.adapters[tail] 279 } 280 p.adapters[tail] = 0 281 p.n_loaded = p.n_loaded - 1 282 return NX_LORA_OK 283} 284 285// ===== Accessors ================================================= 286func nx_lora_n_loaded(p: *NxLoraPool) -> i64 { 287 if nx_lora_pool_is_valid(p) != 1 { return 0 - 1 } 288 return p.n_loaded 289} 290 291func nx_lora_refcount(p: *NxLoraPool, adapter_id: i64) -> i64 { 292 if nx_lora_pool_is_valid(p) != 1 { return 0 - 1 } 293 let slot: i64 = nx_lora__find_slot(p, adapter_id) 294 if slot < 0 { return 0 - 1 } 295 let a: *NxLoraAdapter = (p.adapters[slot]) as *NxLoraAdapter 296 if nx_lora_adapter_is_valid(a) != 1 { return 0 - 1 } 297 return a.refcount 298} 299 300func nx_lora_adapter_rank(a: *NxLoraAdapter) -> i64 { 301 if nx_lora_adapter_is_valid(a) != 1 { return 0 - 1 } 302 return a.rank 303} 304 305func nx_lora_adapter_n_layers(a: *NxLoraAdapter) -> i64 { 306 if nx_lora_adapter_is_valid(a) != 1 { return 0 - 1 } 307 return a.n_layers 308} 309 310func nx_lora_adapter_alpha_q16(a: *NxLoraAdapter) -> i64 { 311 if nx_lora_adapter_is_valid(a) != 1 { return 0 - 1 } 312 return a.alpha_q16 313} 314 315func nx_lora_adapter_id(a: *NxLoraAdapter) -> i64 { 316 if nx_lora_adapter_is_valid(a) != 1 { return 0 - 1 } 317 return a.adapter_id 318}