nx_quant_block_test.nx source
↩ module page · 149 lines · 5245 B
1// nx_quant_block_test.nx -- algo-led memory-win correctness.
2
3import "nx_syscalls.nx"
4import "nx_tier.nx"
5import "nx_tensor.nx"
6import "nx_quant_block.nx"
7import "nx_numeric_oracle.nx"
8
9func main() -> nx_int {
10 let err: *i64 = (sys_mmap(8)) as *i64
11
12 // ===== Single-block test (32 values) =========================
13 //
14 // Build a known sequence; quantise; dequantise; verify round-trip
15 // within the documented epsilon tolerance.
16 let n: nx_int = 32
17 let buf_in: *i64 = (sys_mmap(n * 8)) as *i64
18 var i: nx_int = 0
19 while i < n {
20 // Span [-100, 100]: -100, -92.5, ..., 100
21 buf_in[i] = 100 - i * 6 // -86, -80, -74, ..., 100 -> 32 values
22 i = i + 1
23 }
24 // Specifically check the values span both signs
25 // First value: 100, mid: 100 - 16*6 = 4, last: 100 - 31*6 = -86
26
27 let qb: *NxQuantBlock = nx_qb_alloc(n)
28 if qb.n_blocks != 1 { return 1 }
29 if qb.n_values != 32 { return 2 }
30
31 let q_rc: nx_int = nx_qb_quantize(buf_in, n, qb)
32 if q_rc != NX_QB_OK { return 3 }
33
34 // Scale = max_abs / 7. max_abs in our sequence = 100. 100/7 = 14.
35 if qb.scales[0] != 14 { return 4 }
36
37 // Dequantise into a separate buffer
38 let buf_out: *i64 = (sys_mmap(n * 8)) as *i64
39 let dq_rc: nx_int = nx_qb_dequantize(qb, buf_out, n)
40 if dq_rc != NX_QB_OK { return 5 }
41
42 // Round-trip check: every value within scale/2 = 7 of original.
43 // For our 32-value sequence with scale=14, max error is 7,
44 // relative to |max| = 100 -> 7%. Use Q10 eps = 102 (10%) which
45 // accommodates that with margin.
46 var max_err: nx_int = 0
47 var k: nx_int = 0
48 while k < n {
49 var d: nx_int = buf_in[k] - buf_out[k]
50 if d < 0 { d = 0 - d }
51 if d > max_err { max_err = d }
52 k = k + 1
53 }
54 // max_err must be <= scale/2 + 1 (rounding) = 8
55 if max_err > 8 { return 10 }
56
57 // Wrap both buffers as tensors and use the oracle's epsilon check
58 let sh: *i64 = (sys_mmap(8)) as *i64
59 sh[0] = n
60 let t_in: *NxTensor = nx_t_alloc(NX_DT_I64, sh, 1, err)
61 let t_out: *NxTensor = nx_t_alloc(NX_DT_I64, sh, 1, err)
62 let pi: *i64 = t_in.storage as *i64
63 let po: *i64 = t_out.storage as *i64
64 var c: nx_int = 0
65 while c < n {
66 pi[c] = buf_in[c]
67 po[c] = buf_out[c]
68 c = c + 1
69 }
70 let witness: *i64 = (sys_mmap(NX_NO_WITNESS_FIELDS * 8)) as *i64
71 // eps_q10 = 102 (10%)
72 let v_eps: nx_int = nx_no_check_epsilon_rel_q10(t_in, t_out, 102, witness)
73 if v_eps != NX_NO_VERDICT_EQUAL { return 11 }
74
75 // ===== Compression ratio sanity ==============================
76 //
77 // Dense = 32 * 8 = 256 bytes
78 // Quant = 1 block * 24 = 24 bytes
79 // ratio_q10 = 256 * 1024 / 24 = 262144 / 24 = 10922 (-> ~10.67x)
80 let ratio: nx_int = nx_qb_compression_ratio_q10(qb)
81 if ratio != 10922 { return 20 }
82
83 // ===== Multi-block test (96 values = 3 blocks) ===============
84 //
85 // Each block has its own scale; values across blocks span very
86 // different magnitudes so the per-block calibration matters.
87 let m: nx_int = 96
88 let buf_m: *i64 = (sys_mmap(m * 8)) as *i64
89 var b: nx_int = 0
90 while b < m {
91 if b < 32 {
92 buf_m[b] = b - 16 // [-16..15] range, max_abs=16
93 }
94 if b >= 32 {
95 if b < 64 {
96 buf_m[b] = 1000 * (b - 48) // values up to +/-16000
97 }
98 if b >= 64 {
99 buf_m[b] = 0 // entire block is zero
100 }
101 }
102 b = b + 1
103 }
104
105 let qb_m: *NxQuantBlock = nx_qb_alloc(m)
106 if qb_m.n_blocks != 3 { return 30 }
107 nx_qb_quantize(buf_m, m, qb_m)
108 // Block 0: max_abs=16 -> scale = 16/7 = 2
109 if qb_m.scales[0] != 2 { return 31 }
110 // Block 1: max_abs=16000 -> scale = 16000/7 = 2285
111 if qb_m.scales[1] != 2285 { return 32 }
112 // Block 2: all zeros -> max_abs=0 -> scale floored to 1
113 if qb_m.scales[2] != 1 { return 33 }
114
115 let buf_m_out: *i64 = (sys_mmap(m * 8)) as *i64
116 nx_qb_dequantize(qb_m, buf_m_out, m)
117
118 // Block 2 (all-zero source) must round-trip exact (every value
119 // quantises to nibble 0 -> dequantises to 0).
120 var z: nx_int = 64
121 while z < 96 {
122 if buf_m_out[z] != 0 { return 34 }
123 z = z + 1
124 }
125
126 // ===== Tensor-style API ======================================
127 let sh1: *i64 = (sys_mmap(8)) as *i64
128 sh1[0] = 32
129 let t1: *NxTensor = nx_t_alloc(NX_DT_I64, sh1, 1, err)
130 let pt1: *i64 = t1.storage as *i64
131 var x: nx_int = 0
132 while x < 32 {
133 pt1[x] = x * 3 - 50 // [-50, 43] range
134 x = x + 1
135 }
136 let qb_t: *NxQuantBlock = nx_qb_quantize_tensor(t1)
137 if (qb_t as nx_int) == 0 { return 40 }
138 let t1_back: *NxTensor = nx_t_alloc(NX_DT_I64, sh1, 1, err)
139 nx_qb_dequantize_tensor(qb_t, t1_back)
140 let v_t: nx_int = nx_no_check_epsilon_rel_q10(t1, t1_back, 200, witness)
141 if v_t != NX_NO_VERDICT_EQUAL { return 41 }
142
143 // ===== Sealed-enum coverage ==================================
144 if nx_qb_verdict_is_valid(NX_QB_OK) != 1 { return 50 }
145 if nx_qb_verdict_is_valid(NX_QB_N_VERDICTS) != 0 { return 51 }
146 if nx_qb_verdict_is_valid(0 - 1) != 0 { return 52 }
147
148 return 0
149}