code wiki / (root) / nx_quant_block_test.nx

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}