code wiki / (root) / nx_gguf.nx

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}