nx_q8_0_from_f32.nx source
↩ module page · 89 lines · 3478 B
1// nx_q8_0_from_f32.nx -- quantize F32 -> Q8_0 blocks (the inverse of
2// nx_q8_0_to_f32), + a sovereign F32->F16 encoder (the ecosystem lacked one).
3//
4// Used to re-quantize the Q6_K (and any F32/other) block weights to Q8_0 ONCE
5// AT LOAD so the forward runs the TRIVIAL Q8_0 SIMD dequant-dot (__f32_i8dot32,
6// 10.2x) on ALL block weights. Near-lossless (Q8_0 8-bit >= Q6_K 6-bit); the
7// per-block absmax scale is the standard ggml Q8_0 quantizer.
8//
9// Q8_0 block (34B): d(f16) + 32 int8. d = absmax/127; int8 = round(x/d).
10// lineage_id: q8_0_from_f32_v1
11import "nx_syscalls.nx"
12import "nx_le.nx"
13import "nx_f32_div.nx"
14
15// IEEE binary32 bits -> binary16 bits, round-to-nearest-even. Handles zero,
16// overflow->inf, NaN, and flushes sub-normal-half magnitudes to signed zero
17// (fine for a positive per-block scale).
18func nx_f32_to_f16(bits: i64) -> i64 {
19 let sign: i64 = (bits >> 16) & 0x8000
20 let mag: i64 = bits & 0x7FFFFFFF
21 if mag == 0 { return sign }
22 if mag >= 0x47800000 {
23 if mag > 0x7F800000 { return sign | 0x7E00 } // NaN
24 return sign | 0x7C00 // Inf / overflow
25 }
26 if mag < 0x38800000 { return sign } // < 2^-14 -> flush to 0
27 let exp: i64 = (mag >> 23) & 0xFF
28 let mant: i64 = mag & 0x7FFFFF
29 let e: i64 = exp - 127 + 15
30 let trunc: i64 = mant >> 13
31 let round_bit: i64 = (mant >> 12) & 1
32 let sticky: i64 = mant & 0xFFF
33 var hm: i64 = trunc
34 if round_bit == 1 {
35 if sticky != 0 { hm = hm + 1 } else { if (trunc & 1) == 1 { hm = hm + 1 } }
36 }
37 var he: i64 = e
38 if hm >= 1024 { hm = 0; he = he + 1; if he >= 31 { return sign | 0x7C00 } }
39 return sign | (he << 10) | hm
40}
41
42// round f32 bits to nearest int (ties away from zero): +/-0.5 then truncate.
43// (no __f32_sub intrinsic -> add the sign-flipped half: -0.5f = 0xBF000000.)
44func _q8f_round_i(x: i64) -> i64 {
45 let PHALF: i64 = 0x3F000000 // +0.5f
46 let NHALF: i64 = 0xBF000000 // -0.5f
47 if (x & 0x80000000) != 0 { return __f32_to_i64(__f32_add(x, NHALF)) }
48 return __f32_to_i64(__f32_add(x, PHALF))
49}
50
51func nx_q8_0_from_f32(f32_in: *i64, n_values: i64, out_q8: *u8) -> i64 {
52 let nblk: i64 = (n_values + 31) / 32
53 let f127: i64 = __f32_from_i64(127)
54 var b: i64 = 0
55 while b < nblk {
56 let base: i64 = b * 32
57 let out_off: i64 = b * 34
58 var amax: i64 = 0
59 var i: i64 = 0
60 while i < 32 {
61 let ab: i64 = f32_in[base + i] & 0x7FFFFFFF // |x| as ordered int
62 if ab > amax { amax = ab }
63 i = i + 1
64 }
65 if amax == 0 {
66 out_q8[out_off] = 0 as u8
67 out_q8[out_off + 1] = 0 as u8
68 i = 0
69 while i < 32 { out_q8[out_off + 2 + i] = 0 as u8; i = i + 1 }
70 } else {
71 let d: i64 = nx_f32_div(amax, f127) // absmax / 127
72 let d16: i64 = nx_f32_to_f16(d)
73 out_q8[out_off + 0] = d16 as u8
74 out_q8[out_off + 1] = (d16 >> 8) as u8
75 let inv: i64 = nx_f32_div(f127, amax) // 127 / absmax
76 i = 0
77 while i < 32 {
78 let q: i64 = _q8f_round_i(__f32_mul(f32_in[base + i], inv))
79 var qc: i64 = q
80 if qc > 127 { qc = 127 }
81 if qc < 0 - 127 { qc = 0 - 127 }
82 out_q8[out_off + 2 + i] = qc as u8
83 i = i + 1
84 }
85 }
86 b = b + 1
87 }
88 return 0
89}