nx_gguf.nx source
↩ module page · 426 lines · 15800 B
1// nx_gguf.nx -- GGUF v3 model-file structural parser.
2//
3// Reads the file format used by llama.cpp / GGML for quantized
4// transformer models. GGUF v3 is the de facto standard for Llama /
5// Mistral / Qwen / Phi / Gemma weights in 2024-2025.
6//
7// Closes the WEIGHT-LOADING gap in the substrate's inference path.
8// With this brick, the substrate can:
9//
10// 1. mmap or sys_read_file the .gguf file into a byte buffer
11// 2. nx_gguf_parse(buf, len, out_header) -- get tensor count + dims
12// 3. Iterate nx_gguf_tensor_at(header, i) -- per-tensor metadata
13// 4. Compose nx_qb_dequantize_tensor / nx_qb8_dequantize_tensor /
14// nx_q4k_dequantize_tensor on the named tensors at the offsets
15// this parser exposes
16//
17// ===== Format (GGUF v3) ==========================================
18//
19// Reference: https://github.com/ggerganov/ggml/blob/master/docs/gguf.md
20//
21// magic u32 LE = "GGUF" (0x46554747)
22// version u32 LE = 3
23// tensor_count u64 LE
24// metadata_count u64 LE
25//
26// metadata_kv[] (count = metadata_count):
27// key_len u64 LE
28// key utf8 bytes [key_len]
29// type u32 LE (GGUF_TYPE_*)
30// value type-dependent encoding
31//
32// tensor_info[] (count = tensor_count):
33// name_len u64 LE
34// name utf8 bytes [name_len]
35// n_dims u32 LE
36// dims u64 LE * n_dims
37// type u32 LE (ggml_type: F32, F16, Q4_0, Q4_K, Q8_0, ...)
38// offset u64 LE (byte offset into the tensor data section)
39//
40// -- alignment padding to GGUF_ALIGNMENT (32) bytes --
41//
42// tensor data section: raw bytes per tensor, packed in offset order
43//
44// v1 scope: header + tensor-info table. Metadata KV parse is
45// "skip-but-consume": we walk past metadata to reach tensor_info but
46// don't expose values to callers. Metadata-value parsing is the
47// queued v2 upgrade (needs ~12 type-cases each with own decoder).
48//
49// Bits-up composition:
50// nx_le.nx (canonical LE binary readers)
51// NxTensor (caller dequantizes into this -- L1)
52// nx_loop.LoopVerdict (bounded loops)
53//
54// genealogy_id: gguf_format_gerganov_2024 + ggml_quant_kernels_2023
55// lineage_id: substrate_gguf_v1_structural_parser
56
57// nx_safety_envelope:
58// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
59// sil_target: SIL1
60// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
61// verdict: NOT_YET_EVALUATED
62
63import "nx_syscalls.nx"
64import "nx_tier.nx"
65import "nx_loop.nx"
66import "nx_le.nx"
67
68// ===== Sealed-enum: GgufVerdict ===================================
69
70const NX_GGUF_OK: nx_int = 0
71const NX_GGUF_ERR_BAD_MAGIC: nx_int = 1
72const NX_GGUF_ERR_UNSUPPORTED_VER: nx_int = 2
73const NX_GGUF_ERR_OOB: nx_int = 3
74const NX_GGUF_ERR_BAD_TYPE: nx_int = 4
75const NX_GGUF_ERR_TOO_MANY: nx_int = 5
76const NX_GGUF_N_VERDICTS: nx_int = 6
77
78func nx_gguf_verdict_is_valid(v: nx_int) -> nx_int {
79 if v < 0 { return 0 }
80 if v >= NX_GGUF_N_VERDICTS { return 0 }
81 return 1
82}
83
84// ===== Constants ==================================================
85
86const NX_GGUF_MAGIC: i64 = 0x46554747 // "GGUF" LE
87const NX_GGUF_VERSION: i64 = 3
88const NX_GGUF_ALIGNMENT: i64 = 32
89
90// GGUF metadata type tags (subset; full set is 13).
91const NX_GGUF_TYPE_UINT8: i64 = 0
92const NX_GGUF_TYPE_INT8: i64 = 1
93const NX_GGUF_TYPE_UINT16: i64 = 2
94const NX_GGUF_TYPE_INT16: i64 = 3
95const NX_GGUF_TYPE_UINT32: i64 = 4
96const NX_GGUF_TYPE_INT32: i64 = 5
97const NX_GGUF_TYPE_FLOAT32: i64 = 6
98const NX_GGUF_TYPE_BOOL: i64 = 7
99const NX_GGUF_TYPE_STRING: i64 = 8
100const NX_GGUF_TYPE_ARRAY: i64 = 9
101const NX_GGUF_TYPE_UINT64: i64 = 10
102const NX_GGUF_TYPE_INT64: i64 = 11
103const NX_GGUF_TYPE_FLOAT64: i64 = 12
104
105// GGML tensor element types (subset of the canonical set; ones our
106// substrate primitives already dequantize).
107const NX_GGML_TYPE_F32: i64 = 0
108const NX_GGML_TYPE_F16: i64 = 1
109const NX_GGML_TYPE_Q4_0: i64 = 2
110const NX_GGML_TYPE_Q4_1: i64 = 3
111const NX_GGML_TYPE_Q5_0: i64 = 6
112const NX_GGML_TYPE_Q5_1: i64 = 7
113const NX_GGML_TYPE_Q8_0: i64 = 8
114const NX_GGML_TYPE_Q8_1: i64 = 9
115const NX_GGML_TYPE_Q2_K: i64 = 10
116const NX_GGML_TYPE_Q3_K: i64 = 11
117const NX_GGML_TYPE_Q4_K: i64 = 12
118const NX_GGML_TYPE_Q5_K: i64 = 13
119const NX_GGML_TYPE_Q6_K: i64 = 14
120const NX_GGML_TYPE_Q8_K: i64 = 15
121const NX_GGML_TYPE_BF16: i64 = 30
122
123// Maximum number of tensors we'll parse from a single file. Real
124// Llama-7B has ~290 tensors; Llama-70B has ~720; we cap at 1024 for
125// safety + we sys_mmap a fixed-size array. Bump if needed.
126
127const NX_GGUF_MAX_TENSORS: nx_int = 1024
128const NX_GGUF_MAX_DIMS: nx_int = 4
129const NX_GGUF_MAX_NAME: nx_int = 64
130
131// ===== Tensor-info entry =========================================
132//
133// One per tensor in the file. Caller looks up by linear search
134// over name + n_dims.
135
136struct NxGgufTensorInfo {
137 name: *u8, // pointer INTO the file buffer (zero-terminated NOT guaranteed)
138 name_len: nx_int,
139 n_dims: nx_int,
140 dim_0: i64, // flattened dims; v1 caps n_dims at 4
141 dim_1: i64,
142 dim_2: i64,
143 dim_3: i64,
144 ggml_type: i64,
145 offset: i64 // byte offset INTO the data section
146}
147
148const NX_GGUF_TI_BYTES: nx_int = 72 // 9 fields * 8 bytes
149
150// ===== File header =================================================
151
152struct NxGgufHeader {
153 version: i64,
154 tensor_count: i64,
155 metadata_count: i64,
156 data_off: i64, // file offset where tensor data begins
157 n_tensors: nx_int, // parsed count (<= tensor_count, capped at MAX)
158 tensors: *NxGgufTensorInfo
159}
160
161const NX_GGUF_HDR_BYTES: nx_int = 48 // 6 fields * 8
162
163// ===== Helpers ====================================================
164//
165// Walk past a single GGUF metadata KV pair. Returns the new offset
166// (after the value) or -1 on parse error. Skips arrays of arrays
167// (rare; if encountered we bail).
168
169func _gguf_skip_value(buf: *u8, len: i64, off: i64, ty: i64) -> i64 {
170 if ty == NX_GGUF_TYPE_UINT8 { return off + 1 }
171 if ty == NX_GGUF_TYPE_INT8 { return off + 1 }
172 if ty == NX_GGUF_TYPE_UINT16 { return off + 2 }
173 if ty == NX_GGUF_TYPE_INT16 { return off + 2 }
174 if ty == NX_GGUF_TYPE_UINT32 { return off + 4 }
175 if ty == NX_GGUF_TYPE_INT32 { return off + 4 }
176 if ty == NX_GGUF_TYPE_FLOAT32 { return off + 4 }
177 if ty == NX_GGUF_TYPE_BOOL { return off + 1 }
178 if ty == NX_GGUF_TYPE_UINT64 { return off + 8 }
179 if ty == NX_GGUF_TYPE_INT64 { return off + 8 }
180 if ty == NX_GGUF_TYPE_FLOAT64 { return off + 8 }
181 if ty == NX_GGUF_TYPE_STRING {
182 if off + 8 > len { return 0 - 1 }
183 let slen: i64 = nx_le_read_u64(buf, off)
184 return off + 8 + slen
185 }
186 if ty == NX_GGUF_TYPE_ARRAY {
187 if off + 12 > len { return 0 - 1 }
188 let inner: i64 = nx_le_read_u32(buf, off)
189 let cnt: i64 = nx_le_read_u64(buf, off + 4)
190 if inner == NX_GGUF_TYPE_ARRAY { return 0 - 1 } // refuse nested arrays
191 var p: i64 = off + 12
192 var iter: nx_int = 0
193 var verdict: nx_int = NX_LOOP_RUNNING
194 let BUDGET: nx_int = cnt
195 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
196 p = _gguf_skip_value(buf, len, p, inner)
197 if p < 0 { verdict = NX_LOOP_ABORTED }
198 iter = iter + 1
199 }
200 if verdict == NX_LOOP_ABORTED { return 0 - 1 }
201 return p
202 }
203 return 0 - 1 // unknown type
204}
205
206// ===== Top-level parse ============================================
207//
208// buf: file bytes (caller via sys_read_file)
209// len: total length
210// out_header: caller-allocated NxGgufHeader (the parser fills it)
211//
212// Returns NX_GGUF_OK on success.
213
214func nx_gguf_parse(buf: *u8, len: i64, out_header: *NxGgufHeader) -> nx_int {
215 if len < 24 { return NX_GGUF_ERR_OOB }
216
217 // Magic + version.
218 let magic: i64 = nx_le_read_u32(buf, 0)
219 if magic != NX_GGUF_MAGIC { return NX_GGUF_ERR_BAD_MAGIC }
220 let version: i64 = nx_le_read_u32(buf, 4)
221 if version != NX_GGUF_VERSION { return NX_GGUF_ERR_UNSUPPORTED_VER }
222
223 out_header.version = version
224 out_header.tensor_count = nx_le_read_u64(buf, 8)
225 out_header.metadata_count = nx_le_read_u64(buf, 16)
226
227 if out_header.tensor_count > NX_GGUF_MAX_TENSORS {
228 return NX_GGUF_ERR_TOO_MANY
229 }
230
231 // Walk past metadata KV pairs.
232 var off: i64 = 24
233 var mi: nx_int = 0
234 var iter: nx_int = 0
235 var verdict: nx_int = NX_LOOP_RUNNING
236 let M_BUDGET: nx_int = out_header.metadata_count
237 while verdict == NX_LOOP_RUNNING && iter < M_BUDGET {
238 if off + 8 > len { verdict = NX_LOOP_ABORTED }
239 if verdict == NX_LOOP_RUNNING {
240 let key_len: i64 = nx_le_read_u64(buf, off)
241 off = off + 8 + key_len
242 if off + 4 > len { verdict = NX_LOOP_ABORTED }
243 if verdict == NX_LOOP_RUNNING {
244 let ty: i64 = nx_le_read_u32(buf, off)
245 off = off + 4
246 off = _gguf_skip_value(buf, len, off, ty)
247 if off < 0 { verdict = NX_LOOP_ABORTED }
248 }
249 }
250 mi = mi + 1
251 iter = iter + 1
252 }
253 if verdict == NX_LOOP_ABORTED { return NX_GGUF_ERR_OOB }
254
255 // Allocate tensor-info array.
256 out_header.tensors = sys_mmap(NX_GGUF_MAX_TENSORS * NX_GGUF_TI_BYTES) as *NxGgufTensorInfo
257
258 // Parse tensor-info entries.
259 var ti: nx_int = 0
260 var t_iter: nx_int = 0
261 var t_verdict: nx_int = NX_LOOP_RUNNING
262 let T_BUDGET: nx_int = out_header.tensor_count
263 while t_verdict == NX_LOOP_RUNNING && t_iter < T_BUDGET {
264 if off + 8 > len { t_verdict = NX_LOOP_ABORTED }
265 if t_verdict == NX_LOOP_RUNNING {
266 let name_len: i64 = nx_le_read_u64(buf, off)
267 off = off + 8
268 if off + name_len > len { t_verdict = NX_LOOP_ABORTED }
269 if t_verdict == NX_LOOP_RUNNING {
270 let entry: *NxGgufTensorInfo =
271 (out_header.tensors as i64 + ti * NX_GGUF_TI_BYTES) as *NxGgufTensorInfo
272 entry.name = (buf as i64 + off) as *u8
273 entry.name_len = name_len
274 off = off + name_len
275
276 if off + 4 > len { t_verdict = NX_LOOP_ABORTED }
277 if t_verdict == NX_LOOP_RUNNING {
278 let n_dims: i64 = nx_le_read_u32(buf, off)
279 off = off + 4
280 entry.n_dims = n_dims
281 if n_dims > NX_GGUF_MAX_DIMS { t_verdict = NX_LOOP_ABORTED }
282 if t_verdict == NX_LOOP_RUNNING {
283 // Read up to 4 dims; zero-pad the rest.
284 entry.dim_0 = 0; entry.dim_1 = 0
285 entry.dim_2 = 0; entry.dim_3 = 0
286 if n_dims >= 1 { entry.dim_0 = nx_le_read_u64(buf, off); off = off + 8 }
287 if n_dims >= 2 { entry.dim_1 = nx_le_read_u64(buf, off); off = off + 8 }
288 if n_dims >= 3 { entry.dim_2 = nx_le_read_u64(buf, off); off = off + 8 }
289 if n_dims >= 4 { entry.dim_3 = nx_le_read_u64(buf, off); off = off + 8 }
290
291 if off + 12 > len { t_verdict = NX_LOOP_ABORTED }
292 if t_verdict == NX_LOOP_RUNNING {
293 entry.ggml_type = nx_le_read_u32(buf, off); off = off + 4
294 entry.offset = nx_le_read_u64(buf, off); off = off + 8
295 ti = ti + 1
296 }
297 }
298 }
299 }
300 }
301 t_iter = t_iter + 1
302 }
303 if t_verdict == NX_LOOP_ABORTED { return NX_GGUF_ERR_OOB }
304
305 out_header.n_tensors = ti
306
307 // Align to NX_GGUF_ALIGNMENT for the data section.
308 let pad: i64 = NX_GGUF_ALIGNMENT - (off - (off / NX_GGUF_ALIGNMENT) * NX_GGUF_ALIGNMENT)
309 if pad == NX_GGUF_ALIGNMENT {
310 out_header.data_off = off
311 }
312 if pad != NX_GGUF_ALIGNMENT {
313 out_header.data_off = off + pad
314 }
315 return NX_GGUF_OK
316}
317
318// ===== Caller-side accessor ======================================
319//
320// Returns pointer to the i-th tensor's info entry.
321
322func nx_gguf_tensor_at(hdr: *NxGgufHeader, i: nx_int) -> *NxGgufTensorInfo {
323 if i < 0 { return 0 as *NxGgufTensorInfo }
324 if i >= hdr.n_tensors { return 0 as *NxGgufTensorInfo }
325 return (hdr.tensors as i64 + i * NX_GGUF_TI_BYTES) as *NxGgufTensorInfo
326}
327
328// ===== Public skip-value helper ==================================
329//
330// Thin wrapper around _gguf_skip_value so downstream metadata-walker
331// bricks (nx_gguf_meta.nx) can compose the canonical type-skip logic
332// without reaching into the underscore-private internal helper.
333
334func nx_gguf_skip_value(buf: *u8, len: i64, off: i64, ty: i64) -> i64 {
335 return _gguf_skip_value(buf, len, off, ty)
336}
337
338// ===== Self-test ==================================================
339//
340// Hand-craft a minimal in-memory GGUF buffer and round-trip parse:
341//
342// magic "GGUF" (4 bytes LE)
343// version 3 (4 bytes LE)
344// tens=1 1 (8 bytes LE)
345// meta=0 0 (8 bytes LE)
346//
347// tensor[0]:
348// name_len 4 (8 bytes LE)
349// name "test" (4 bytes)
350// n_dims 2 (4 bytes LE)
351// dim_0 16 (8 bytes LE)
352// dim_1 8 (8 bytes LE)
353// ggml_type 8 (Q8_0) (4 bytes LE)
354// offset 0 (8 bytes LE)
355//
356// Total header bytes: 24 + 8 + 4 + 4 + 16 + 12 = 68
357// data_off should align to 96 (next 32-byte boundary).
358
359func main() -> i64 {
360 let buf: *u8 = sys_mmap(256)
361
362 // Magic "GGUF" LE = 0x46554747
363 buf[0] = 0x47; buf[1] = 0x47; buf[2] = 0x55; buf[3] = 0x46
364 // Version 3
365 buf[4] = 3; buf[5] = 0; buf[6] = 0; buf[7] = 0
366 // tensor_count = 1
367 nx_le_write_u64(buf, 8, 1)
368 // metadata_count = 0
369 nx_le_write_u64(buf, 16, 0)
370
371 // tensor[0].name_len = 4
372 nx_le_write_u64(buf, 24, 4)
373 // "test"
374 buf[32] = 0x74; buf[33] = 0x65; buf[34] = 0x73; buf[35] = 0x74
375 // n_dims = 2
376 nx_le_write_u32(buf, 36, 2)
377 // dim_0 = 16
378 nx_le_write_u64(buf, 40, 16)
379 // dim_1 = 8
380 nx_le_write_u64(buf, 48, 8)
381 // ggml_type = 8 (Q8_0)
382 nx_le_write_u32(buf, 56, NX_GGML_TYPE_Q8_0)
383 // offset = 0
384 nx_le_write_u64(buf, 60, 0)
385 // End of tensor info at off = 68.
386
387 let hdr_p: *NxGgufHeader = sys_mmap(NX_GGUF_HDR_BYTES) as *NxGgufHeader
388 let v: nx_int = nx_gguf_parse(buf, 256, hdr_p)
389 if v != NX_GGUF_OK { return 10 + v }
390
391 if hdr_p.version != 3 { return 20 }
392 if hdr_p.tensor_count != 1 { return 21 }
393 if hdr_p.metadata_count != 0 { return 22 }
394 if hdr_p.n_tensors != 1 { return 23 }
395
396 let ti0: *NxGgufTensorInfo = nx_gguf_tensor_at(hdr_p, 0)
397 if ti0.name_len != 4 { return 30 }
398 if ti0.n_dims != 2 { return 31 }
399 if ti0.dim_0 != 16 { return 32 }
400 if ti0.dim_1 != 8 { return 33 }
401 if ti0.ggml_type != NX_GGML_TYPE_Q8_0 { return 34 }
402 if ti0.offset != 0 { return 35 }
403
404 // data_off: 68 padded up to 96 = next 32-aligned.
405 if hdr_p.data_off != 96 { return 40 }
406
407 // --- Bad magic rejection ---
408 buf[0] = 0x00
409 let v2: nx_int = nx_gguf_parse(buf, 256, hdr_p)
410 if v2 != NX_GGUF_ERR_BAD_MAGIC { return 50 }
411
412 // Restore magic + corrupt version.
413 buf[0] = 0x47
414 buf[4] = 99
415 let v3: nx_int = nx_gguf_parse(buf, 256, hdr_p)
416 if v3 != NX_GGUF_ERR_UNSUPPORTED_VER { return 60 }
417
418 // --- Verdict gate ---
419 var vi: nx_int = 0
420 while vi < NX_GGUF_N_VERDICTS {
421 if nx_gguf_verdict_is_valid(vi) != 1 { return 70 + vi }
422 vi = vi + 1
423 }
424
425 return 0
426}