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}