nx_quant_q4k.nx
buildroot/runtime/nx_quant_q4k.nx
about
nx_quant_q4k.nx -- hierarchical k-quants (ggml q4_K shape).
Improvement over nx_quant_block.nx (q4_0 shape):
q4_0: one scale per 32-value block. All 32 values share the
same dynamic range; groups with wildly different magnitudes
waste precision (small-magnitude groups get coarse steps).
q4_K: super-block of 256 values = 8 groups of 32. TWO scales:
a SUPER scale per super-block + a LOCAL scale per group.
The hierarchy lets each group adapt its precision while
still sharing the super-block magnitude estimate -- which
empirically halves worst-case error vs q4_0 at near-identical
storage.
Concrete win for the user's "VRAM optimization realistic tracking":
This is the realisation of the Q-002 row -- q4_K + AWQ
calibration -- which the doc projected at 1-2% quality loss vs
q4_0's 5-7%.
Storage per 256 values:
1 super_d (i64) = 8 bytes
8 group_d_local (i64 each) = 64 bytes
128 packed nibbles (256 values) = 128 bytes
Total = 200 bytes
vs dense 256 * 8 = 2048 bytes -> 10.24x compression
Reconstruction: value = nibble * group_d_local * super_d / Q10
Algorithm-led precision:
1. Find super_max = max(|values|) across the 256-value super-block
2. super_d = super_max / 127 (range of effective combined scale)
3. For each 32-value group:
a. find group_max = max(|values|) in the group
b. group_d_local = (group_max * Q10) / (7 * super_d)
(so that nibble of 7 reconstructs to group_max)
4. For each value: nibble = round(value * Q10 / (group_d_local * super_d))
Hierarchy benefit: a group with group_max << super_max gets a SMALL
dependencies 4 imports · 1 importers
imports: nx_syscalls.nxnx_tier.nxnx_tensor.nxnx_quant_block.nx
imported by: nx_quant_q4k_test.nx
structs
| 87 | struct NxQuantQ4K |
consts
| 59 | const NX_MAGIC_1024: i64 = 1024 |
| 61 | const NX_Q4K_Q10: nx_int = 1024 |
| 62 | const NX_Q4K_SUPER_VALUES: nx_int = 256 // values per super-block |
| 63 | const NX_Q4K_GROUP_VALUES: nx_int = 32 // values per group |
| 64 | const NX_Q4K_GROUPS: nx_int = 8 // groups per super-block |
| 65 | const NX_Q4K_NIBBLE_MAX: nx_int = 7 // [-7..+7] |
| 66 | const NX_Q4K_SUPER_RANGE: nx_int = 127 // super_d denominator |
| 69 | const NX_Q4K_BYTES_PER_SUPER: nx_int = 200 |
| 73 | const NX_Q4K_OK: nx_int = 0 |
| 74 | const NX_Q4K_ERR_BAD_LEN: nx_int = 1 // n not multiple of 256 |
| 75 | const NX_Q4K_ERR_BAD_DTYPE: nx_int = 2 |
| 76 | const NX_Q4K_ERR_SHAPE_MISMATCH: nx_int = 3 |
| 77 | const NX_Q4K_N_VERDICTS: nx_int = 4 |
| 95 | const NX_Q4K_STRUCT_BYTES: nx_int = 40 // 5 fields * 8 |
functions
| 79 | func nx_q4k_verdict_is_valid(v: nx_int) -> nx_int called by 1: main |
| 97 | func nx_q4k_alloc(n_values: nx_int) -> *NxQuantQ4K |
| 118 | func _q4k_pack_nibble(qb: *NxQuantQ4K, value_idx: nx_int, signed_nib: nx_int) -> nx_int called by 1: nx_q4k_quantize |
| 134 | func _q4k_unpack_nibble(qb: *NxQuantQ4K, value_idx: nx_int) -> nx_int called by 1: nx_q4k_dequantize |
| 148 | func _q4k_abs(x: nx_int) -> nx_int called by 1: nx_q4k_quantize |
| 170 | func nx_q4k_quantize(values: *i64, n: nx_int, qb: *NxQuantQ4K) -> nx_int |
| 237 | func nx_q4k_dequantize(qb: *NxQuantQ4K, values_out: *i64, n: nx_int) -> nx_int |
| 254 | func nx_q4k_compression_ratio_q10(qb: *NxQuantQ4K) -> nx_int called by 1: main |
| 263 | func nx_q4k_quantize_tensor(t: *NxTensor) -> *NxQuantQ4K |
| 272 | func nx_q4k_dequantize_tensor(qb: *NxQuantQ4K, t: *NxTensor) -> nx_int |