nx_gguf_load.nx source
↩ module page · 747 lines · 28336 B
1// nx_gguf_load.nx -- GGUF tensor lookup + dequantize composer.
2//
3// L4 brick that bridges nx_gguf_parse (header + tensor_info walker,
4// SHIPPED) into NxTensor (L1 container, SHIPPED) by:
5//
6// 1. Finding a tensor by name in a parsed header
7// 2. Computing the data byte-size of any GGML-format tensor
8// 3. Dequantizing F32 / F16 / Q8_0 tensor data into Q10 i64 NxTensor
9//
10// This is the brick that lets nx_llm_run.nx leave its v1 "API-
11// composition proof" scaffold behind and become a real runner sourcing
12// weights from a real Llama / Mistral / Qwen / Z-Image GGUF file.
13//
14// Bits-up composition (every primitive cited already SHIPPED canonical):
15// nx_le.nx -- IEEE-LE binary readers
16// nx_gguf.nx -- header + tensor_info parser
17// nx_tensor.nx -- NxTensor + nx_t_alloc
18// nx_loop.nx -- bounded-loop discipline
19//
20// Format spec sources (public-domain documentation; no copied code):
21// GGUF v3 format -- gguf.md in ggml repo (Gerganov 2024)
22// ggml block_q8_0 layout -- ggml docs
23// IEEE 754 binary32 / binary16 -- IEEE standard
24//
25// genealogy_id: gguf_format_gerganov_2024 + ieee754_binary32_binary16
26// lineage_id: substrate_gguf_loader_v1_f32_f16_q8
27
28// nx_safety_envelope:
29// intended_use: "Load a GGUF tensor by name into an
30// NxTensor; dequantize F32 / F16 / Q8_0
31// formats to Q10 fixed-point i64 storage"
32// sil_target: SIL2
33// asil_target: QM
34// dal_target: DAL C
35// evidence: [gguf_v3_spec_public, q8_0_layout_public,
36// ieee754_standard, clean_room_implementation]
37// hazard_register: [bug-tape-oob-on-malformed-gguf,
38// bug-tape-overflow-on-extreme-f32-exponents,
39// bug-tape-nan-inf-leakage-into-q10]
40// residual_risk: "Adversarial GGUF input must be bounded
41// by caller; this brick assumes parse-clean
42// header from nx_gguf_parse"
43// verdict: NOT_YET_EVALUATED
44
45import "nx_syscalls.nx"
46import "nx_tier.nx"
47import "nx_loop.nx"
48import "nx_le.nx"
49import "nx_tensor.nx"
50import "nx_gguf.nx"
51const NX_MAGIC_8388607: i64 = 8388607
52const NX_MAGIC_8192: i64 = 8192
53const NX_MAGIC_1024: i64 = 1024
54const NX_MAGIC_2047: i64 = 2047
55const NX_MAGIC_16384: i64 = 16384
56const NX_MAGIC_32767: i64 = 32767
57const NX_MAGIC_32752: i64 = 32752
58
59// ===== Sealed-enum: GgufLoadVerdict ===============================
60
61const NX_GL_OK: nx_int = 0
62const NX_GL_ERR_NOT_FOUND: nx_int = 1
63const NX_GL_ERR_BAD_TYPE: nx_int = 2
64const NX_GL_ERR_BAD_SHAPE: nx_int = 3
65const NX_GL_ERR_OOB: nx_int = 4
66const NX_GL_ERR_OOM: nx_int = 5
67const NX_GL_ERR_OVERFLOW: nx_int = 6
68const NX_GL_N_VERDICTS: nx_int = 7
69
70func nx_gl_verdict_is_valid(v: nx_int) -> nx_int {
71 if v < 0 { return 0 }
72 if v >= NX_GL_N_VERDICTS { return 0 }
73 return 1
74}
75
76// ===== Block-size constants (ggml format spec) ====================
77//
78// Each ggml_type defines values_per_block + bytes_per_block. These
79// are PUBLIC FORMAT INVARIANTS, not implementation choices.
80//
81// F32 -- block of 1, bytes = 4
82// F16 -- block of 1, bytes = 2
83// Q8_0 -- block of 32, bytes = 34 (2-byte f16 scale + 32 int8)
84// Q4_0 -- block of 32, bytes = 18 (2-byte f16 scale + 16 nibble bytes)
85//
86// Q4_K / Q5_K / Q6_K / Q8_K super-blocks are queued for the next
87// brick; their 144/176/210/256-byte super-block layouts deserve their
88// own focused arc.
89
90const NX_GL_Q8_0_VPB: nx_int = 32
91const NX_GL_Q8_0_BPB: nx_int = 34
92const NX_GL_Q4_0_VPB: nx_int = 32
93const NX_GL_Q4_0_BPB: nx_int = 18
94const NX_GL_Q4_K_VPB: nx_int = 256
95const NX_GL_Q4_K_BPB: nx_int = 144
96
97// Q10 scale (substrate-wide convention).
98const NX_GL_Q10: nx_int = 1024
99
100// Q14 scale -- 16x finer fractional resolution than Q10.
101//
102// Added 2026-05-19 in response to live-fire finding on real
103// Qwen2.5-0.5B-Instruct Q4_K_M: typical trained-model super-scales
104// `d_real ~ 0.001-0.01` round to 0 or 1 in Q10 (precision floor at
105// 1/1024 = ~9.8e-4). Q14's floor is 1/16384 = ~6.1e-5 which
106// represents real weight magnitudes correctly without underflow.
107//
108// See feedback-q10-precision-floor-collapses-real-model-weights.md.
109const NX_GL_Q14: nx_int = 16384
110
111// Saturating clamp for Q10/Q14 conversion of ±Inf and overflow.
112//
113// 2^60 fits in i64 and leaves room for multiplies in caller paths.
114const NX_GL_Q10_INF_SAT: i64 = 0x1000000000000000
115
116// ===== Public: tensor name equals ================================
117//
118// Compare the bytes of a NxGgufTensorInfo.name (pointer INTO the
119// GGUF file buffer; not null-terminated) against a caller-supplied
120// name buffer of explicit length.
121
122func nx_gguf_name_equals(ti: *NxGgufTensorInfo,
123 name: *u8, name_len: nx_int) -> nx_int {
124 if ti.name_len != name_len { return 0 }
125 var i: nx_int = 0
126 var iter: nx_int = 0
127 var verdict: nx_int = NX_LOOP_RUNNING
128 let BUDGET: nx_int = name_len
129 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
130 if ti.name[i] != name[i] { return 0 }
131 i = i + 1
132 iter = iter + 1
133 }
134 return 1
135}
136
137// ===== Public: find tensor by name ===============================
138//
139// Linear scan of the parsed tensor_info table. Returns the index in
140// [0, hdr.n_tensors) or -1 if not found. Llama-7B has ~290 tensors;
141// linear search is fine for v1. A hash-index lookup is queued if
142// scan time becomes a measurable bottleneck.
143
144func nx_gguf_find_tensor(hdr: *NxGgufHeader,
145 name: *u8, name_len: nx_int) -> nx_int {
146 var i: nx_int = 0
147 var iter: nx_int = 0
148 var verdict: nx_int = NX_LOOP_RUNNING
149 let BUDGET: nx_int = hdr.n_tensors
150 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
151 let ti: *NxGgufTensorInfo = nx_gguf_tensor_at(hdr, i)
152 if nx_gguf_name_equals(ti, name, name_len) == 1 { return i }
153 i = i + 1
154 iter = iter + 1
155 }
156 return 0 - 1
157}
158
159// ===== Public: format-spec helpers ===============================
160
161func nx_gguf_ggml_values_per_block(ggml_type: i64) -> nx_int {
162 if ggml_type == NX_GGML_TYPE_F32 { return 1 }
163 if ggml_type == NX_GGML_TYPE_F16 { return 1 }
164 if ggml_type == NX_GGML_TYPE_Q8_0 { return NX_GL_Q8_0_VPB }
165 if ggml_type == NX_GGML_TYPE_Q4_0 { return NX_GL_Q4_0_VPB }
166 if ggml_type == NX_GGML_TYPE_Q4_K { return NX_GL_Q4_K_VPB }
167 return 0
168}
169
170func nx_gguf_ggml_bytes_per_block(ggml_type: i64) -> nx_int {
171 if ggml_type == NX_GGML_TYPE_F32 { return 4 }
172 if ggml_type == NX_GGML_TYPE_F16 { return 2 }
173 if ggml_type == NX_GGML_TYPE_Q8_0 { return NX_GL_Q8_0_BPB }
174 if ggml_type == NX_GGML_TYPE_Q4_0 { return NX_GL_Q4_0_BPB }
175 if ggml_type == NX_GGML_TYPE_Q4_K { return NX_GL_Q4_K_BPB }
176 return 0
177}
178
179// Total element count for a tensor: product of dims (NX_GGUF_MAX_DIMS
180// cap is 4; unused dims are stored as 1 by the parser? -- actually
181// the parser stores 0 for unused. Treat 0 as "no further dims".
182
183func nx_gguf_tensor_n_values(ti: *NxGgufTensorInfo) -> i64 {
184 var n: i64 = 1
185 if ti.n_dims >= 1 { n = n * ti.dim_0 }
186 if ti.n_dims >= 2 { n = n * ti.dim_1 }
187 if ti.n_dims >= 3 { n = n * ti.dim_2 }
188 if ti.n_dims >= 4 { n = n * ti.dim_3 }
189 return n
190}
191
192// Byte size of a tensor's storage in the GGUF data section.
193
194func nx_gguf_tensor_data_bytes(ti: *NxGgufTensorInfo) -> i64 {
195 let nv: i64 = nx_gguf_tensor_n_values(ti)
196 let vpb: nx_int = nx_gguf_ggml_values_per_block(ti.ggml_type)
197 let bpb: nx_int = nx_gguf_ggml_bytes_per_block(ti.ggml_type)
198 if vpb <= 0 { return 0 - 1 }
199 // n_blocks = ceil(nv / vpb). ggml mandates dim alignment to vpb
200 // (this is a documented spec invariant) so the divide is exact in
201 // a clean file; we still ceil to be defensive.
202 let n_blocks: i64 = (nv + vpb - 1) / vpb
203 return n_blocks * bpb
204}
205
206// ===== IEEE 754 binary32 -> Q10 (clean-room) ======================
207//
208// Decode IEEE 754 binary32 bit pattern (read from u32 LE) into a
209// Q10 i64. All bit twiddling, no FP hardware required.
210//
211// bit 31: sign
212// bits 30..23: exponent (biased by 127)
213// bits 22..0: mantissa (implicit leading 1 for normal)
214//
215// Strategy:
216// value_q10 = (-1)^sign * (Q10 + m_frac_in_Q10) << (e - 127) when e >= 127
217// = (-1)^sign * (Q10 + m_frac_in_Q10) >> (127 - e) when e < 127
218// m_frac_in_Q10 = m >> 13 (23-bit mantissa shifted to fit Q10)
219//
220// Special cases:
221// e == 0: return 0 (zero or subnormal -- subnormals
222// are < 2^-126 ~ 1e-38 == 0 in Q10)
223// e == 255: return ±SAT (Inf), or 0 (NaN)
224// normal too big: saturate
225// normal too small: round to 0
226//
227// Accuracy: ~1 LSB in Q10 (i.e., ~1/1024 absolute error on values of
228// order 1). For model weights this is well below quantization noise.
229
230func _gguf_f32_to_q10(raw: i64) -> i64 {
231 let sign: i64 = (raw >> 31) & 1
232 let exponent: i64 = (raw >> 23) & 255
233 let mantissa: i64 = raw & NX_MAGIC_8388607 // (1 << 23) - 1
234
235 // Zero / subnormal -- round to 0.
236 if exponent == 0 { return 0 }
237
238 // Inf / NaN.
239 if exponent == 255 {
240 if mantissa == 0 {
241 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
242 return NX_GL_Q10_INF_SAT
243 }
244 // NaN -- safe choice is 0.
245 return 0
246 }
247
248 // Normal.
249 let shift_amount: i64 = exponent - 127
250 let m_q10: i64 = mantissa / NX_MAGIC_8192 // mantissa >> 13
251 let base: i64 = NX_GL_Q10 + m_q10 // NX_MAGIC_1024..NX_MAGIC_2047
252
253 // Range guards.
254 if shift_amount > 50 {
255 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
256 return NX_GL_Q10_INF_SAT
257 }
258 if shift_amount < 0 - 20 { return 0 }
259
260 var result: i64 = 0
261 if shift_amount >= 0 {
262 result = base << shift_amount
263 } else {
264 let neg_sa: i64 = 0 - shift_amount
265 result = base >> neg_sa
266 }
267 if sign == 1 { return 0 - result }
268 return result
269}
270
271// ===== IEEE 754 binary32 -> Q14 ==================================
272//
273// 4 more fractional bits than _gguf_f32_to_q10, for the activation path
274// where Q10 quantization gets amplified by SwiGLU's nonlinearity in the
275// FFN (measured worst 3.3%; Q14 tightens it). Same strategy; the 23-bit
276// f32 mantissa keeps its top 14 bits (>> 9) instead of top 10 (>> 13).
277func _gguf_f32_to_q14(raw: i64) -> i64 {
278 let sign: i64 = (raw >> 31) & 1
279 let exponent: i64 = (raw >> 23) & 255
280 let mantissa: i64 = raw & NX_MAGIC_8388607
281 if exponent == 0 { return 0 }
282 if exponent == 255 {
283 if mantissa == 0 {
284 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
285 return NX_GL_Q10_INF_SAT
286 }
287 return 0
288 }
289 let shift_amount: i64 = exponent - 127
290 let m_q14: i64 = mantissa / 512 // mantissa >> 9 (top 14 bits)
291 let base: i64 = NX_GL_Q14 + m_q14 // NX_MAGIC_16384..NX_MAGIC_32767
292 if shift_amount > 46 {
293 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
294 return NX_GL_Q10_INF_SAT
295 }
296 if shift_amount < 0 - 24 { return 0 }
297 var result: i64 = 0
298 if shift_amount >= 0 {
299 result = base << shift_amount
300 } else {
301 let neg_sa: i64 = 0 - shift_amount
302 result = base >> neg_sa
303 }
304 if sign == 1 { return 0 - result }
305 return result
306}
307
308// ===== IEEE 754 binary16 -> Q10 ===================================
309//
310// bit 15: sign
311// bits 14..10: exponent (biased by 15)
312// bits 9..0: mantissa (implicit leading 1 for normal)
313//
314// Same strategy as binary32; the mantissa is already 10 bits so it
315// is itself the Q10 fractional component.
316
317func _gguf_f16_to_q10(raw: i64) -> i64 {
318 let sign: i64 = (raw >> 15) & 1
319 let exponent: i64 = (raw >> 10) & 31
320 let mantissa: i64 = raw & 1023 // (1 << 10) - 1
321
322 if exponent == 0 { return 0 }
323 if exponent == 31 {
324 if mantissa == 0 {
325 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
326 return NX_GL_Q10_INF_SAT
327 }
328 return 0
329 }
330
331 let shift_amount: i64 = exponent - 15
332 let base: i64 = NX_GL_Q10 + mantissa // NX_MAGIC_1024..NX_MAGIC_2047
333
334 if shift_amount > 50 {
335 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
336 return NX_GL_Q10_INF_SAT
337 }
338 if shift_amount < 0 - 20 { return 0 }
339
340 var result: i64 = 0
341 if shift_amount >= 0 {
342 result = base << shift_amount
343 } else {
344 let neg_sa: i64 = 0 - shift_amount
345 result = base >> neg_sa
346 }
347 if sign == 1 { return 0 - result }
348 return result
349}
350
351// ===== IEEE 754 binary16 -> Q14 (precision-lifted) ================
352//
353// Identical structure to _gguf_f16_to_q10 but emits Q14 (multiply by
354// 16384 instead of 1024). Adds 4 bits of fractional precision so
355// small super-scales like d_real ~ 0.001 land at d_q14 = 16 instead
356// of collapsing to 0 or 1 in Q10.
357//
358// The 10-bit f16 mantissa shifts left by 4 to fill the Q14 fractional
359// field directly (was: mantissa fed in as-is for Q10 since 10 bits
360// already matched 10 frac bits).
361//
362// Max representable: ~2^60 same as Q10 path (saturation guard).
363// Min representable: 1/16384 ~= 6.1e-5 (16x better than Q10's
364// 1/1024 ~= 9.8e-4).
365
366func _gguf_f16_to_q14(raw: i64) -> i64 {
367 let sign: i64 = (raw >> 15) & 1
368 let exponent: i64 = (raw >> 10) & 31
369 let mantissa: i64 = raw & 1023
370
371 if exponent == 0 { return 0 }
372 if exponent == 31 {
373 if mantissa == 0 {
374 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
375 return NX_GL_Q10_INF_SAT
376 }
377 return 0
378 }
379
380 let shift_amount: i64 = exponent - 15
381 let base: i64 = NX_GL_Q14 + mantissa * 16 // NX_MAGIC_16384..NX_MAGIC_32752
382
383 if shift_amount > 46 {
384 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
385 return NX_GL_Q10_INF_SAT
386 }
387 if shift_amount < 0 - 24 { return 0 }
388
389 var result: i64 = 0
390 if shift_amount >= 0 {
391 result = base << shift_amount
392 } else {
393 let neg_sa: i64 = 0 - shift_amount
394 result = base >> neg_sa
395 }
396 if sign == 1 { return 0 - result }
397 return result
398}
399
400// ===== IEEE 754 binary16 -> Q24 (EXACT for every finite f16) ======
401//
402// Real GGUF Q4_K super-scales are ~1e-4, and each dequant value
403// d*sc*q - dmin*m is a small difference of near-equal terms. Any lossy
404// super-scale decode is therefore wrong on real weights: Q10 collapses
405// tiny d to 0 (whole tensor -> 0), and Q14 truncates asymmetrically and
406// FLIPS THE SIGN via catastrophic cancellation. Q24 represents every
407// finite f16 EXACTLY -- for a normal f16 the value * 2^24 is a pure left
408// shift with no bits discarded, and a subnormal's value * 2^24 is just
409// its mantissa -- which is exactly what the cancellation demands. This
410// is the canonical super-scale decode for Q4_K dequant; it retires the
411// Q10/Q14 super-scale decoders (see nx_q4k_to_f32 / dequant_iter / matmul).
412const NX_GL_Q24: i64 = 16777216 // 2^24
413
414func _gguf_f16_to_q24(raw: i64) -> i64 {
415 let sign: i64 = (raw >> 15) & 1
416 let exponent: i64 = (raw >> 10) & 31
417 let mantissa: i64 = raw & 1023
418
419 if exponent == 31 {
420 if mantissa == 0 {
421 if sign == 1 { return 0 - NX_GL_Q10_INF_SAT }
422 return NX_GL_Q10_INF_SAT
423 }
424 return 0 // NaN
425 }
426
427 var result: i64 = 0
428 if exponent == 0 {
429 result = mantissa // subnormal: value * 2^24 == mantissa (exact)
430 } else {
431 result = (NX_GL_Q10 + mantissa) << (exponent - 1) // normal: (NX_MAGIC_1024+mant) << (exp-1), exact
432 }
433 if sign == 1 { return 0 - result }
434 return result
435}
436
437// ===== Dequantize: F32 (per-element) =============================
438
439func nx_gguf_dequant_f32(buf: *u8, base_off: i64, n_values: i64,
440 out_q10: *i64) -> nx_int {
441 var i: i64 = 0
442 var iter: nx_int = 0
443 var verdict: nx_int = NX_LOOP_RUNNING
444 let BUDGET: nx_int = n_values
445 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
446 let raw: i64 = nx_le_read_u32(buf, base_off + i * 4)
447 out_q10[i] = _gguf_f32_to_q10(raw)
448 i = i + 1
449 iter = iter + 1
450 }
451 return NX_GL_OK
452}
453
454// ===== Dequantize: F16 (per-element) =============================
455
456func nx_gguf_dequant_f16(buf: *u8, base_off: i64, n_values: i64,
457 out_q10: *i64) -> nx_int {
458 var i: i64 = 0
459 var iter: nx_int = 0
460 var verdict: nx_int = NX_LOOP_RUNNING
461 let BUDGET: nx_int = n_values
462 while verdict == NX_LOOP_RUNNING && iter < BUDGET {
463 let raw: i64 = nx_le_read_u16(buf, base_off + i * 2)
464 out_q10[i] = _gguf_f16_to_q10(raw)
465 i = i + 1
466 iter = iter + 1
467 }
468 return NX_GL_OK
469}
470
471// ===== Dequantize: Q8_0 (block-32, f16 scale + 32 int8) ===========
472//
473// Per block of 32 values:
474// bytes [0..2) = f16 scale d
475// bytes [2..34) = 32 signed int8 values qs[0..32)
476//
477// Dequantized value[i] = d * qs[i]
478//
479// In Q10:
480// scale_q10 = f16_to_q10(d)
481// out_q10[i] = scale_q10 * qs[i] -- already correctly scaled
482
483func nx_gguf_dequant_q8_0(buf: *u8, base_off: i64, n_values: i64,
484 out_q10: *i64) -> nx_int {
485 // n_blocks = ceil(n_values / 32). ggml mandates exact divisibility
486 // for tensors using block formats; we still ceil defensively.
487 let n_blocks: i64 = (n_values + NX_GL_Q8_0_VPB - 1) / NX_GL_Q8_0_VPB
488
489 var b: i64 = 0
490 var b_iter: nx_int = 0
491 var b_verdict: nx_int = NX_LOOP_RUNNING
492 let B_BUDGET: nx_int = n_blocks
493 while b_verdict == NX_LOOP_RUNNING && b_iter < B_BUDGET {
494 let block_off: i64 = base_off + b * NX_GL_Q8_0_BPB
495
496 // f16 scale at byte offset 0.
497 let scale_raw: i64 = nx_le_read_u16(buf, block_off)
498 let scale_q10: i64 = _gguf_f16_to_q10(scale_raw)
499
500 // 32 signed int8 values at byte offset 2.
501 var j: nx_int = 0
502 var j_iter: nx_int = 0
503 var j_verdict: nx_int = NX_LOOP_RUNNING
504 let J_BUDGET: nx_int = NX_GL_Q8_0_VPB
505 while j_verdict == NX_LOOP_RUNNING && j_iter < J_BUDGET {
506 let global_i: i64 = b * NX_GL_Q8_0_VPB + j
507 if global_i >= n_values {
508 // Past the logical end -- skip leftover block tail.
509 j = j + 1
510 j_iter = j_iter + 1
511 } else {
512 let raw_u8: i64 = nx_le_read_u8(buf, block_off + 2 + j)
513 var signed_v: i64 = raw_u8
514 if signed_v > 127 { signed_v = signed_v - 256 }
515 out_q10[global_i] = scale_q10 * signed_v
516 j = j + 1
517 j_iter = j_iter + 1
518 }
519 }
520
521 b = b + 1
522 b_iter = b_iter + 1
523 }
524 return NX_GL_OK
525}
526
527// ===== Dequantize: Q4_K (super-block 256, f16 d + f16 dmin +
528// ===== 12 packed scale/min bytes + 128 4-bit values) ===
529//
530// Format (per ggml q4_K public spec, clean-room):
531// bytes [0..2) f16 d (super-scale)
532// bytes [2..4) f16 dmin (super-min)
533// bytes [4..16) 12 packed bytes encoding 8 6-bit scales + 8 6-bit mins
534// bytes [16..144) 128 bytes of 4-bit nibbles (256 values; low nibble
535// first for each byte)
536//
537// 12-byte scale/min packing (q[0..11] := scales[]):
538// For sub-block j in 0..3 (the "low" half):
539// sc[j] = q[j] & 0x3F (low 6 bits of byte j)
540// m[j] = q[j+4] & 0x3F (low 6 bits of byte j+4)
541// For sub-block j in 4..7 (the "high" half), k = j-4:
542// sc[j] = ((q[k] >> 6) << 4) | (q[8+k] & 0x0F)
543// m[j] = ((q[4+k] >> 6) << 4) | (q[8+k] >> 4)
544//
545// Dequant formula (per ggml's dequantize_row_q4_K, public algorithm --
546// clean-room, verified BIT-EXACT vs ggml-quants.c on the real
547// blk.0.attn_q super-block 0 of a Qwen3-4B Q4_K_M model):
548// d_real = f16_to_real(d)
549// dmin_real = f16_to_real(dmin)
550// Process 32-byte GROUPS g=0..3; group g feeds sub-block 2g (low
551// nibbles) and sub-block 2g+1 (high nibbles), 32 outputs each IN ORDER:
552// d1 = d_real * sc[2g] ; m1 = dmin_real * m[2g]
553// d2 = d_real * sc[2g+1] ; m2 = dmin_real * m[2g+1]
554// for l in 0..32: out[(2g)*32 + l] = d1 * (qs[g*32+l] & 0x0F) - m1
555// for l in 0..32: out[(2g+1)*32 + l] = d2 * (qs[g*32+l] >> 4) - m2
556//
557// OUTPUT UNIT: Q24 (was Q10). Real trained-model super-scales are ~1e-4
558// and each value d*sc*q - dmin*m is a small difference of near-equal
559// terms; Q10/Q14 super-scale decode underflows/flips-sign on real
560// weights (see _gguf_f16_to_q24 note + nx_q4k_q24_gate). Q24 represents
561// every finite f16 super-scale EXACTLY so the cancellation is exact.
562// d_q24 = f16_to_q24(d); dmin_q24 = f16_to_q24(dmin)
563// value_q24 = d_q24 * sc[is] * q4 - dmin_q24 * m[is]
564// (No divide needed -- sc, m, q4 are small integers.)
565
566func nx_gguf_dequant_q4_k(buf: *u8, base_off: i64, n_values: i64,
567 out_q24: *i64) -> nx_int {
568 let n_super: i64 = (n_values + NX_GL_Q4_K_VPB - 1) / NX_GL_Q4_K_VPB
569
570 var sb: i64 = 0
571 var sb_iter: nx_int = 0
572 var sb_verdict: nx_int = NX_LOOP_RUNNING
573 let SB_BUDGET: nx_int = n_super
574 while sb_verdict == NX_LOOP_RUNNING && sb_iter < SB_BUDGET {
575 let super_off: i64 = base_off + sb * NX_GL_Q4_K_BPB
576 let d_raw: i64 = nx_le_read_u16(buf, super_off)
577 let dmin_raw: i64 = nx_le_read_u16(buf, super_off + 2)
578 let d_q24: i64 = _gguf_f16_to_q24(d_raw)
579 let dmin_q24: i64 = _gguf_f16_to_q24(dmin_raw)
580 let scales_off: i64 = super_off + 4
581 let qs_off: i64 = super_off + 16
582
583 var g: nx_int = 0
584 var g_iter: nx_int = 0
585 var g_verdict: nx_int = NX_LOOP_RUNNING
586 let G_BUDGET: nx_int = 4
587 while g_verdict == NX_LOOP_RUNNING && g_iter < G_BUDGET {
588 let is0: nx_int = g + g // 2g
589 let is1: nx_int = is0 + 1 // 2g+1
590 var sc0: i64 = 0
591 var m0: i64 = 0
592 var sc1: i64 = 0
593 var m1s: i64 = 0
594 if is0 < 4 {
595 sc0 = nx_le_read_u8(buf, scales_off + is0) & 0x3F
596 m0 = nx_le_read_u8(buf, scales_off + is0 + 4) & 0x3F
597 } else {
598 let k0: nx_int = is0 - 4
599 let b_k: i64 = nx_le_read_u8(buf, scales_off + k0)
600 let b_k4: i64 = nx_le_read_u8(buf, scales_off + 4 + k0)
601 let b_8k: i64 = nx_le_read_u8(buf, scales_off + 8 + k0)
602 sc0 = ((b_k >> 6) << 4) | (b_8k & 0x0F)
603 m0 = ((b_k4 >> 6) << 4) | (b_8k >> 4)
604 }
605 if is1 < 4 {
606 sc1 = nx_le_read_u8(buf, scales_off + is1) & 0x3F
607 m1s = nx_le_read_u8(buf, scales_off + is1 + 4) & 0x3F
608 } else {
609 let k1: nx_int = is1 - 4
610 let c_k: i64 = nx_le_read_u8(buf, scales_off + k1)
611 let c_k4: i64 = nx_le_read_u8(buf, scales_off + 4 + k1)
612 let c_8k: i64 = nx_le_read_u8(buf, scales_off + 8 + k1)
613 sc1 = ((c_k >> 6) << 4) | (c_8k & 0x0F)
614 m1s = ((c_k4 >> 6) << 4) | (c_8k >> 4)
615 }
616
617 let d1_q24: i64 = d_q24 * sc0
618 let m0_q24: i64 = dmin_q24 * m0
619 let d2_q24: i64 = d_q24 * sc1
620 let m1_q24: i64 = dmin_q24 * m1s
621 let grp_off: i64 = qs_off + g * 32
622
623 var l: nx_int = 0
624 var l_iter: nx_int = 0
625 var l_verdict: nx_int = NX_LOOP_RUNNING
626 let L_BUDGET: nx_int = 32
627 while l_verdict == NX_LOOP_RUNNING && l_iter < L_BUDGET {
628 let byte_v: i64 = nx_le_read_u8(buf, grp_off + l)
629 let q_lo: i64 = byte_v & 0x0F
630 let q_hi: i64 = byte_v >> 4
631 let out_lo: i64 = sb * NX_GL_Q4_K_VPB + is0 * 32 + l
632 let out_hi: i64 = sb * NX_GL_Q4_K_VPB + is1 * 32 + l
633 if out_lo < n_values {
634 out_q24[out_lo] = d1_q24 * q_lo - m0_q24
635 }
636 if out_hi < n_values {
637 out_q24[out_hi] = d2_q24 * q_hi - m1_q24
638 }
639 l = l + 1
640 l_iter = l_iter + 1
641 }
642
643 g = g + 1
644 g_iter = g_iter + 1
645 }
646
647 sb = sb + 1
648 sb_iter = sb_iter + 1
649 }
650 return NX_GL_OK
651}
652
653// ===== Dequantize: Q4_K -> Q24 (was "_q14"; superseded by Q24) =====
654//
655// HISTORY: this used to emit Q14 (via _gguf_f16_to_q14) as a partial
656// fix for Q10 super-scale underflow on trained models. Q14 still
657// truncates the super-scale asymmetrically and FLIPS THE SIGN via
658// catastrophic cancellation on real weights. It now emits Q24 (via the
659// EXACT _gguf_f16_to_q24) -- identical output to nx_gguf_dequant_q4_k.
660// The name is retained so existing callers (nx_q4k_real_gemm) still
661// link; those callers have been updated to interpret the output as Q24.
662// Also carries the ggml 32-byte-group layout fix (group g -> sub-blocks
663// 2g low / 2g+1 high). Prefer nx_gguf_dequant_q4_k for new code.
664//
665// See feedback-q10-precision-floor-collapses-real-model-weights.md
666// for the live-fire discovery on Qwen2.5-0.5B Q4_K_M (2026-05-19).
667
668func nx_gguf_dequant_q4_k_q14(buf: *u8, base_off: i64, n_values: i64,
669 out_q24: *i64) -> nx_int {
670 // Delegate to the canonical Q24 dequant (single source of truth).
671 return nx_gguf_dequant_q4_k(buf, base_off, n_values, out_q24)
672}
673
674// ===== Public: load tensor by name ================================
675//
676// Composes find + alloc + dequant into one call. Caller pre-knows
677// nothing about block layout or dtype; this routes by ggml_type.
678//
679// Output tensor is always NX_DT_I64 fixed-point in v1. UNIT BY TYPE:
680// F32/F16/Q8_0 paths emit Q10; the Q4_K path emits Q24 (the canonical
681// dequant was moved to Q24 to survive real trained-model super-scale
682// cancellation -- see nx_gguf_dequant_q4_k). A caller mixing Q4_K
683// tensors with Q10 tensors must rescale (Q24 -> Q10 = >>14 round-half).
684// When the compiler ships native F32 / F16 / BF16 dtypes, this function
685// gets an optional dtype-preserving fast path; the i64 fixed-point path
686// stays as the canonical "works everywhere" path.
687
688func nx_gguf_load_tensor(buf: *u8, hdr: *NxGgufHeader,
689 name: *u8, name_len: nx_int,
690 out_err: *i64) -> *NxTensor {
691 let idx: nx_int = nx_gguf_find_tensor(hdr, name, name_len)
692 if idx < 0 {
693 out_err[0] = NX_GL_ERR_NOT_FOUND
694 return 0 as *NxTensor
695 }
696 let ti: *NxGgufTensorInfo = nx_gguf_tensor_at(hdr, idx)
697
698 let nv: i64 = nx_gguf_tensor_n_values(ti)
699 if nv <= 0 {
700 out_err[0] = NX_GL_ERR_BAD_SHAPE
701 return 0 as *NxTensor
702 }
703
704 // Allocate NxTensor with the GGUF shape, dtype = I64 Q10.
705 let shape: *i64 = sys_mmap(4 * 8) as *i64
706 shape[0] = ti.dim_0
707 shape[1] = ti.dim_1
708 shape[2] = ti.dim_2
709 shape[3] = ti.dim_3
710 let err: *i64 = sys_mmap(8) as *i64
711 err[0] = 0
712 let t: *NxTensor = nx_t_alloc(NX_DT_I64, shape, ti.n_dims, err)
713 if err[0] != 0 {
714 out_err[0] = NX_GL_ERR_OOM
715 return 0 as *NxTensor
716 }
717
718 let storage: *i64 = t.storage as *i64
719 let data_off: i64 = hdr.data_off + ti.offset
720
721 var verdict: nx_int = NX_GL_OK
722 if ti.ggml_type == NX_GGML_TYPE_F32 {
723 verdict = nx_gguf_dequant_f32(buf, data_off, nv, storage)
724 } else {
725 if ti.ggml_type == NX_GGML_TYPE_F16 {
726 verdict = nx_gguf_dequant_f16(buf, data_off, nv, storage)
727 } else {
728 if ti.ggml_type == NX_GGML_TYPE_Q8_0 {
729 verdict = nx_gguf_dequant_q8_0(buf, data_off, nv, storage)
730 } else {
731 if ti.ggml_type == NX_GGML_TYPE_Q4_K {
732 verdict = nx_gguf_dequant_q4_k(buf, data_off, nv, storage)
733 } else {
734 out_err[0] = NX_GL_ERR_BAD_TYPE
735 return 0 as *NxTensor
736 }
737 }
738 }
739 }
740 if verdict != NX_GL_OK {
741 out_err[0] = verdict
742 return 0 as *NxTensor
743 }
744
745 out_err[0] = NX_GL_OK
746 return t
747}