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}