nx_gguf_meta.nx source
↩ module page · 238 lines · 8371 B
1// nx_gguf_meta.nx -- GGUF metadata key-value walker + typed readers.
2//
3// Extends nx_gguf.nx by parsing the metadata section that nx_gguf_parse
4// currently SKIPS. Unblocks reading model architecture parameters
5// (n_layers / hidden_dim / rope_base / n_heads / vocab_size) + the
6// BPE tokenizer vocab from real Llama / Mistral / Qwen GGUF files,
7// rather than requiring the caller to know the spec a priori.
8//
9// Canonical Llama metadata keys (public spec, ggml docs):
10// "general.architecture" STRING -> "llama"
11// "llama.attention.head_count" UINT32 -> n_heads
12// "llama.attention.head_count_kv" UINT32 -> n_kv_heads
13// "llama.embedding_length" UINT32 -> hidden_dim
14// "llama.block_count" UINT32 -> n_layers
15// "llama.feed_forward_length" UINT32 -> ffn_dim
16// "llama.context_length" UINT32 -> max_seq_len
17// "llama.rope.freq_base" FLOAT32 -> rope_base
18// "llama.attention.layer_norm_rms_epsilon" FLOAT32 -> rms eps
19// "tokenizer.ggml.tokens" ARRAY of STRING -> BPE vocab
20// "tokenizer.ggml.scores" ARRAY of FLOAT32 -> BPE merge scores
21//
22// Bits-up composition:
23// nx_gguf.nx -- header + skip-value canonical helper
24// nx_gguf_load.nx -- _gguf_f32_to_q10 (composed for f32 read)
25// nx_le.nx -- LE binary readers
26//
27// genealogy_id: gguf_v3_metadata_spec_gerganov_2024
28// lineage_id: substrate_gguf_meta_v1_walker
29
30// nx_safety_envelope:
31// intended_use: "Find a metadata entry by key + read its
32// value as a typed scalar; v1 supports
33// U8/I8/U16/I16/U32/I32/U64/I64/F32/BOOL/STRING.
34// Arrays expose len + start-offset for caller
35// to iterate."
36// sil_target: SIL2
37// asil_target: QM
38// dal_target: DAL C
39// evidence: [gguf_v3_spec_public, clean_room,
40// composes_nx_gguf_skip_value]
41// hazard_register: [bug-tape-malformed-key-len,
42// bug-tape-truncated-buf-during-walk,
43// bug-tape-nested-arrays-rejected]
44// residual_risk: "Caller must pre-parse header so
45// hdr.metadata_count is trustworthy"
46// verdict: NOT_YET_EVALUATED
47
48import "nx_syscalls.nx"
49import "nx_tier.nx"
50import "nx_loop.nx"
51import "nx_le.nx"
52import "nx_gguf.nx"
53import "nx_gguf_load.nx"
54const NX_MAGIC_32768: i64 = 32768
55const NX_MAGIC_65536: i64 = 65536
56const NX_MAGIC_2147483648: i64 = 2147483648
57const NX_MAGIC_4294967296: i64 = 4294967296
58
59// ===== Sealed-enum: GgufMetaVerdict ===============================
60
61const NX_GMETA_OK: nx_int = 0
62const NX_GMETA_NOT_FOUND: nx_int = 1
63const NX_GMETA_ERR_OOB: nx_int = 2
64const NX_GMETA_ERR_BAD_TYPE: nx_int = 3
65const NX_GMETA_ERR_TRUNCATED: nx_int = 4
66const NX_GMETA_N_VERDICTS: nx_int = 5
67
68func nx_gmeta_verdict_is_valid(v: nx_int) -> nx_int {
69 if v < 0 { return 0 }
70 if v >= NX_GMETA_N_VERDICTS { return 0 }
71 return 1
72}
73
74// Byte-equal helper (private to this file).
75func _gmeta_bytes_equal(a: *u8, b: *u8, n: nx_int) -> nx_int {
76 var i: nx_int = 0
77 while i < n {
78 if a[i] != b[i] { return 0 }
79 i = i + 1
80 }
81 return 1
82}
83
84// ===== Public: find metadata entry by key =========================
85//
86// buf file bytes
87// len file length
88// hdr parsed header (hdr.metadata_count is the upper bound)
89// key caller-supplied key bytes
90// key_len length of key
91// out_value_off *i64 written on success: byte offset of value-body
92// out_type *i64 written on success: GGUF type tag (NX_GGUF_TYPE_*)
93//
94// Returns NX_GMETA_OK on hit; NX_GMETA_NOT_FOUND on miss; OOB/
95// TRUNCATED on file corruption.
96
97func nx_gguf_meta_find(buf: *u8, len: i64, hdr: *NxGgufHeader,
98 key: *u8, key_len: nx_int,
99 out_value_off: *i64, out_type: *i64) -> nx_int {
100 // Metadata section starts at byte 24 (after magic+ver+tens+meta).
101 var off: i64 = 24
102 var i: nx_int = 0
103 var iter: nx_int = 0
104 var verdict: nx_int = NX_LOOP_RUNNING
105 let BUDGET: nx_int = hdr.metadata_count
106 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
107 if off + 8 > len { return NX_GMETA_ERR_OOB }
108 let cur_key_len: i64 = nx_le_read_u64(buf, off)
109 off = off + 8
110 if off + cur_key_len > len { return NX_GMETA_ERR_OOB }
111 let key_off: i64 = off
112 off = off + cur_key_len
113 if off + 4 > len { return NX_GMETA_ERR_OOB }
114 let cur_type: i64 = nx_le_read_u32(buf, off)
115 off = off + 4
116
117 // Compare key.
118 if cur_key_len == key_len {
119 if _gmeta_bytes_equal((buf as i64 + key_off) as *u8, key, key_len) == 1 {
120 out_value_off[0] = off
121 out_type[0] = cur_type
122 return NX_GMETA_OK
123 }
124 }
125
126 // Skip the value to advance to next entry.
127 off = nx_gguf_skip_value(buf, len, off, cur_type)
128 if off < 0 { return NX_GMETA_ERR_TRUNCATED }
129
130 i = i + 1
131 iter = iter + 1
132 }
133 return NX_GMETA_NOT_FOUND
134}
135
136// ===== Typed readers =============================================
137
138func nx_gguf_meta_read_u8(buf: *u8, off: i64) -> i64 {
139 return nx_le_read_u8(buf, off)
140}
141
142func nx_gguf_meta_read_u16(buf: *u8, off: i64) -> i64 {
143 return nx_le_read_u16(buf, off)
144}
145
146func nx_gguf_meta_read_u32(buf: *u8, off: i64) -> i64 {
147 return nx_le_read_u32(buf, off)
148}
149
150func nx_gguf_meta_read_u64(buf: *u8, off: i64) -> i64 {
151 return nx_le_read_u64(buf, off)
152}
153
154// Signed variants -- explicit sign extension since the LE readers
155// return unsigned.
156
157func nx_gguf_meta_read_i8(buf: *u8, off: i64) -> i64 {
158 let v: i64 = nx_le_read_u8(buf, off)
159 if v >= 128 { return v - 256 }
160 return v
161}
162
163func nx_gguf_meta_read_i16(buf: *u8, off: i64) -> i64 {
164 let v: i64 = nx_le_read_u16(buf, off)
165 if v >= NX_MAGIC_32768 { return v - NX_MAGIC_65536 }
166 return v
167}
168
169func nx_gguf_meta_read_i32(buf: *u8, off: i64) -> i64 {
170 let v: i64 = nx_le_read_u32(buf, off)
171 if v >= NX_MAGIC_2147483648 { return v - NX_MAGIC_4294967296 }
172 return v
173}
174
175// F32 → Q10 i64; composes the clean-room IEEE-754 decoder from
176// nx_gguf_load.
177
178func nx_gguf_meta_read_f32_q10(buf: *u8, off: i64) -> i64 {
179 let raw: i64 = nx_le_read_u32(buf, off)
180 return _gguf_f32_to_q10(raw)
181}
182
183// Bool: 1-byte unsigned 0/1.
184
185func nx_gguf_meta_read_bool(buf: *u8, off: i64) -> i64 {
186 return nx_le_read_u8(buf, off)
187}
188
189// String: 8-byte LE length followed by raw UTF-8 bytes.
190//
191// nx_gguf_meta_read_string_len returns the byte length of the string.
192// nx_gguf_meta_read_string_ptr returns a pointer to the string bytes
193// (no copy; lives inside the file buf).
194
195func nx_gguf_meta_read_string_len(buf: *u8, off: i64) -> i64 {
196 return nx_le_read_u64(buf, off)
197}
198
199func nx_gguf_meta_read_string_ptr(buf: *u8, off: i64) -> *u8 {
200 return (buf as i64 + off + 8) as *u8
201}
202
203// ===== Array helpers =============================================
204//
205// Array value-body layout:
206// u32 inner_type
207// u64 element_count
208// <elements packed; layout depends on inner_type>
209//
210// We expose:
211// nx_gguf_meta_array_inner_type(buf, off) -> i64
212// nx_gguf_meta_array_count(buf, off) -> i64
213// nx_gguf_meta_array_first_elt_off(buf, off) -> i64
214//
215// Caller iterates elements themselves by composing inner_type with
216// the typed readers above. For STRING arrays each element is
217// length-prefixed; caller advances by 8 + slen per step.
218
219func nx_gguf_meta_array_inner_type(buf: *u8, off: i64) -> i64 {
220 return nx_le_read_u32(buf, off)
221}
222
223func nx_gguf_meta_array_count(buf: *u8, off: i64) -> i64 {
224 return nx_le_read_u64(buf, off + 4)
225}
226
227func nx_gguf_meta_array_first_elt_off(buf: *u8, off: i64) -> i64 {
228 return off + 12
229}
230
231// Convenience: advance past one element of `inner_type` from byte
232// offset `off`. Returns the offset AFTER the element. Caller is
233// expected to chain this in a loop with bounds checking.
234
235func nx_gguf_meta_array_advance(buf: *u8, len: i64, off: i64,
236 inner_type: i64) -> i64 {
237 return nx_gguf_skip_value(buf, len, off, inner_type)
238}