code wiki / (root) / nx_placement.nx

nx_placement.nx source

↩ module page · 253 lines · 9464 B

1// nx_placement.nx -- multi-tier tensor placement + lazy materialization. 2// 3// L1.5 brick. The smallest demonstrable step toward the conductor 4// cardinal (2026-05-19): tensors aren't FORCED into VRAM-equivalent 5// storage at load time; they can live "where they already are" 6// (mmap'd GGUF buffer) and only materialize per-use. 7// 8// Without this brick: nx_gguf_load_tensor allocates a full Q10 i64 9// buffer for every tensor at load. A real Llama-70B Q4_K file has 10// ~40GB of weights; materializing all of them into i64 Q10 would 11// require ~320GB of RAM. Infeasible on any consumer device. 12// 13// With this brick: tensors live as NxPlacedTensor with placement tag. 14// Lazy (MMAP) tensors hold a (buf, offset, ggml_type) handle and only 15// materialize a Q10 i64 NxTensor on first access -- callers that 16// stream weights by layer can demote earlier layers, keeping working 17// set bounded. 18// 19// This is Phase A of the multi-phase conductor arc: 20// A) [this brick] Placement tag + lazy materialization 21// B) Lazy GGUF loader (nx_gguf_load_tensor that returns lazy) 22// C) Dispatcher (per-op hardware routing) 23// D) Resource arbiter (multi-tier budget tracking) 24// E) Async DMA primitives 25// 26// Bits-up composition: 27// nx_tensor.nx -- NxTensor (the materialized form) 28// nx_gguf.nx -- GGML type tags 29// nx_gguf_load.nx -- dequant routing for materialization 30// 31// genealogy_id: numpy_memmap_pattern + mmap_lazy_load_canonical 32// lineage_id: substrate_placement_v1_lazy_promote_demote 33 34// nx_safety_envelope: 35// intended_use: "Wrap NxTensor with placement metadata so 36// substrate can defer materialization until 37// first compute access. Single-tier lazy 38// (MMAP only) in v1; multi-tier (RAM warm / 39// NETWORK distributed) follows in B/D/E 40// phases." 41// sil_target: SIL2 42// asil_target: QM 43// dal_target: DAL C 44// evidence: [conductor_cardinal_2026-05-19, mmap_lazy 45// is_a_proven_pattern_numpy_memmap_etc, 46// composes_only_shipped_canonical_bricks] 47// hazard_register: [bug-tape-demote-during-active-compute, 48// bug-tape-source-buf-freed-with-lazy-refs] 49// residual_risk: "Caller must keep the source GGUF byte 50// buffer alive as long as any lazy placed 51// tensor references it" 52// verdict: NOT_YET_EVALUATED 53 54import "nx_syscalls.nx" 55import "nx_tier.nx" 56import "nx_loop.nx" 57import "nx_tensor.nx" 58import "nx_gguf.nx" 59import "nx_gguf_load.nx" 60 61// ===== Sealed-enum: NxTensorPlacement ============================= 62 63const NX_PLACE_OWNED: nx_int = 0 // caller already materialized; no source 64const NX_PLACE_MMAP: nx_int = 1 // backed by raw bytes in a buffer (GGUF) 65const NX_PLACE_VRAM: nx_int = 2 // (future) GPU-resident; not yet implemented 66const NX_PLACE_NETWORK: nx_int = 3 // (future) on a peer device 67const NX_PLACE_N: nx_int = 4 68 69func nx_place_is_valid(p: nx_int) -> nx_int { 70 if p < 0 { return 0 } 71 if p >= NX_PLACE_N { return 0 } 72 return 1 73} 74 75// ===== Sealed-enum: PlacementVerdict =============================== 76 77const NX_PT_OK: nx_int = 0 78const NX_PT_ERR_BAD_PLACEMENT: nx_int = 1 79const NX_PT_ERR_NO_SOURCE: nx_int = 2 80const NX_PT_ERR_BAD_TYPE: nx_int = 3 81const NX_PT_ERR_ALREADY_LIVE: nx_int = 4 82const NX_PT_ERR_NOT_LIVE: nx_int = 5 83const NX_PT_N_VERDICTS: nx_int = 6 84 85func nx_pt_verdict_is_valid(v: nx_int) -> nx_int { 86 if v < 0 { return 0 } 87 if v >= NX_PT_N_VERDICTS { return 0 } 88 return 1 89} 90 91// ===== The struct ================================================= 92// 93// 11 fields * 8 = 88 bytes. 94 95struct NxPlacedTensor { 96 t: *NxTensor, // materialized form (0 when lazy) 97 placement: nx_int, 98 src_buf: *u8, // bytes the lazy form reads from 99 src_off: i64, // byte offset into src_buf 100 src_ggml_type: i64, // NX_GGML_TYPE_* 101 src_n_values: i64, // expected element count 102 shape_0: i64, // shape (up to 4-D) so we can alloc 103 shape_1: i64, 104 shape_2: i64, 105 shape_3: i64, 106 n_dims: nx_int 107} 108 109const NX_PT_STRUCT_BYTES: nx_int = 88 // 11 * 8 110 111// ===== Constructors ================================================ 112// 113// Lazy: wrap a GGUF byte range. No NxTensor allocated yet; t = 0. 114// Caller must ensure src_buf outlives the lazy ref. 115 116func nx_placed_tensor_new_lazy(src_buf: *u8, src_off: i64, 117 ggml_type: i64, n_values: i64, 118 shape: *i64, ndim: nx_int) -> *NxPlacedTensor { 119 let p: *NxPlacedTensor = sys_mmap(NX_PT_STRUCT_BYTES) as *NxPlacedTensor 120 p.t = 0 as *NxTensor 121 p.placement = NX_PLACE_MMAP 122 p.src_buf = src_buf 123 p.src_off = src_off 124 p.src_ggml_type = ggml_type 125 p.src_n_values = n_values 126 p.shape_0 = 0 127 p.shape_1 = 0 128 p.shape_2 = 0 129 p.shape_3 = 0 130 if ndim >= 1 { p.shape_0 = shape[0] } 131 if ndim >= 2 { p.shape_1 = shape[1] } 132 if ndim >= 3 { p.shape_2 = shape[2] } 133 if ndim >= 4 { p.shape_3 = shape[3] } 134 p.n_dims = ndim 135 return p 136} 137 138// Owned: wrap an already-materialized NxTensor. No source for 139// re-materialization on demote; this is the equivalent of "the 140// caller built this tensor in memory and we just wrap it for the 141// dispatcher to treat uniformly". 142 143func nx_placed_tensor_new_owned(t: *NxTensor) -> *NxPlacedTensor { 144 let p: *NxPlacedTensor = sys_mmap(NX_PT_STRUCT_BYTES) as *NxPlacedTensor 145 p.t = t 146 p.placement = NX_PLACE_OWNED 147 p.src_buf = 0 as *u8 148 p.src_off = 0 149 p.src_ggml_type = 0 150 p.src_n_values = 0 151 p.shape_0 = 0 152 p.shape_1 = 0 153 p.shape_2 = 0 154 p.shape_3 = 0 155 if t.ndim >= 1 { p.shape_0 = t.shape[0] } 156 if t.ndim >= 2 { p.shape_1 = t.shape[1] } 157 if t.ndim >= 3 { p.shape_2 = t.shape[2] } 158 if t.ndim >= 4 { p.shape_3 = t.shape[3] } 159 p.n_dims = t.ndim 160 return p 161} 162 163// ===== Lifecycle: materialize / demote ============================ 164// 165// Materialize: if not yet materialized, alloc NxTensor + dequant from 166// src_buf/src_off via the same per-type dispatch nx_gguf_load_tensor 167// uses. Returns the NxTensor (also stored in p.t). 168// 169// Demote: free the NxTensor's storage (sys_munmap-equivalent isn't 170// shipped; v1 just zeros the pointer and lets the OS reclaim on 171// process exit -- conductor PHASE D will add real release). Re- 172// materialization on next access is the contract. 173 174func nx_placed_tensor_is_live(p: *NxPlacedTensor) -> nx_int { 175 if p.t == (0 as *NxTensor) { return 0 } 176 return 1 177} 178 179func nx_placed_tensor_storage_bytes(p: *NxPlacedTensor) -> i64 { 180 if p.t == (0 as *NxTensor) { return 0 } 181 return p.t.storage_n 182} 183 184// Build the NxTensor + dequant payload. Returns the (now-live) 185// NxTensor. 186 187func _pt_materialize(p: *NxPlacedTensor) -> *NxTensor { 188 let sh: *i64 = sys_mmap(4 * 8) as *i64 189 sh[0] = p.shape_0 190 sh[1] = p.shape_1 191 sh[2] = p.shape_2 192 sh[3] = p.shape_3 193 let err: *i64 = sys_mmap(8) as *i64 194 err[0] = 0 195 let t: *NxTensor = nx_t_alloc(NX_DT_I64, sh, p.n_dims, err) 196 if err[0] != 0 { return 0 as *NxTensor } 197 198 let storage: *i64 = t.storage as *i64 199 if p.src_ggml_type == NX_GGML_TYPE_F32 { 200 nx_gguf_dequant_f32(p.src_buf, p.src_off, p.src_n_values, storage) 201 } else { 202 if p.src_ggml_type == NX_GGML_TYPE_F16 { 203 nx_gguf_dequant_f16(p.src_buf, p.src_off, p.src_n_values, storage) 204 } else { 205 if p.src_ggml_type == NX_GGML_TYPE_Q8_0 { 206 nx_gguf_dequant_q8_0(p.src_buf, p.src_off, p.src_n_values, storage) 207 } else { 208 if p.src_ggml_type == NX_GGML_TYPE_Q4_K { 209 nx_gguf_dequant_q4_k(p.src_buf, p.src_off, p.src_n_values, storage) 210 } else { 211 return 0 as *NxTensor 212 } 213 } 214 } 215 } 216 return t 217} 218 219func nx_placed_tensor_materialize(p: *NxPlacedTensor) -> nx_int { 220 if p.t != (0 as *NxTensor) { return NX_PT_ERR_ALREADY_LIVE } 221 if p.placement == NX_PLACE_OWNED { return NX_PT_ERR_NO_SOURCE } 222 if p.src_buf == (0 as *u8) { return NX_PT_ERR_NO_SOURCE } 223 224 let t: *NxTensor = _pt_materialize(p) 225 if t == (0 as *NxTensor) { return NX_PT_ERR_BAD_TYPE } 226 p.t = t 227 return NX_PT_OK 228} 229 230func nx_placed_tensor_demote(p: *NxPlacedTensor) -> nx_int { 231 if p.t == (0 as *NxTensor) { return NX_PT_ERR_NOT_LIVE } 232 if p.placement == NX_PLACE_OWNED { return NX_PT_ERR_BAD_PLACEMENT } 233 // v1: zero the pointer. The storage bytes leak until process 234 // exit; conductor phase D adds real munmap. But the LIFECYCLE 235 // contract (materialize -> live -> demote -> lazy -> materialize) 236 // is honored. 237 p.t = 0 as *NxTensor 238 return NX_PT_OK 239} 240 241// ===== Convenience accessor ======================================= 242// 243// On-demand materialize: returns the live NxTensor; materializes 244// first if currently lazy. This is the API a dispatcher calls 245// before invoking a compute op. 246 247func nx_placed_tensor_get(p: *NxPlacedTensor) -> *NxTensor { 248 if p.t == (0 as *NxTensor) { 249 let v: nx_int = nx_placed_tensor_materialize(p) 250 if v != NX_PT_OK { return 0 as *NxTensor } 251 } 252 return p.t 253}