nx_quant_policy.nx
buildroot/runtime/nx_quant_policy.nx
about
nx_quant_policy.nx -- per-layer mixed-precision quantization policy.
Ships VRAM-track Q-003 per docs/VRAM_OPTIMIZATION_REALISTIC_TRACKING.md.
Pure composition over the three quantizers already shipped:
nx_quant_block.nx -- q4_0 (4-bit symmetric, 10.67x compression)
nx_quant_block_q8.nx -- q8_0 (8-bit symmetric, 6.4x compression)
nx_quant_q4k.nx -- q4_K (4-bit k-quants w/ per-group scales)
The model loader consults this policy to decide WHICH quantizer
to apply per-layer. Not every layer is equally sensitive to
quantization loss; cheap-to-compress layers (FFN up/down/gate)
go aggressive (q4_K), expensive-to-compress layers (Q/K/V
projections, embeddings, final output) stay safer (q8_0 or
passthrough at f16).
Sensitivity heuristic literature:
- Frantar 2022 _GPTQ_: per-channel scale, calibration-data-driven
- Lin et al. 2023 _AWQ_: activation-aware -- protect outlier
channels at higher precision
- Xiao et al. 2023 _SmoothQuant_: pre-shift activation outliers
into weights so weights can be quantized further without
activation-side accuracy loss
- ggml q-mix presets 2024: practitioner-tuned per-tensor-name
defaults for Llama/Mistral/Qwen
The substrate ships the POLICY TABLE structure + a default
heuristic. Per-model calibration (the AWQ pass) is a separate
workstream that fills the table with measured sensitivities.
VRAM impact composition:
* 70% of params at q4_K (4-bit + k-quant overhead): ~3.5x save
* 25% of params at q8_0: ~1.4x save
* 5% of params at f16 passthrough: ~0.5x save
* Aggregate: ~3.0x save
Per the bounded-loop cardinal: every loop here uses the
LoopVerdict pattern from nx_loop.nx.
genealogy_id: gptq_frantar_2022 + awq_lin_2023 + smoothquant_xiao_2023 +
ggml_quant_mix_presets_2024
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_tier.nxnx_loop.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 103 | struct NxQuantPolicyEntry |
| 160 | struct NxQuantPolicy |
consts
| 58 | const NX_QP_F16: nx_int = 0 // passthrough (no quantization) |
| 59 | const NX_QP_Q8_0: nx_int = 1 // 8-bit symmetric, ~6.4x compression |
| 60 | const NX_QP_Q4_K: nx_int = 2 // 4-bit k-quant, ~8.5x compression (with super-block overhead) |
| 61 | const NX_QP_Q4_0: nx_int = 3 // 4-bit symmetric legacy, ~10.7x compression |
| 62 | const NX_QP_F32: nx_int = 4 // full precision (rare, debug) |
| 63 | const NX_QP_N_KINDS: nx_int = 5 |
| 77 | const NX_QR_UNKNOWN: nx_int = 0 |
| 78 | const NX_QR_EMBED: nx_int = 1 // token / position embeddings |
| 79 | const NX_QR_ATTN_Q: nx_int = 2 // Q projection |
| 80 | const NX_QR_ATTN_K: nx_int = 3 // K projection |
| 81 | const NX_QR_ATTN_V: nx_int = 4 // V projection |
| 82 | const NX_QR_ATTN_O: nx_int = 5 // output projection (after attention) |
| 83 | const NX_QR_FFN_UP: nx_int = 6 // up-projection |
| 84 | const NX_QR_FFN_DOWN: nx_int = 7 // down-projection |
| 85 | const NX_QR_FFN_GATE: nx_int = 8 // gate projection (Llama-style) |
| 86 | const NX_QR_NORM: nx_int = 9 // LayerNorm / RMSNorm scale |
| 87 | const NX_QR_OUTPUT: nx_int = 10 // final output projection / lm_head |
| 88 | const NX_QR_BIAS: nx_int = 11 // bias vectors (always small, keep f16) |
| 89 | const NX_QR_N_ROLES: nx_int = 12 |
| 110 | const NX_QP_ENTRY_BYTES: nx_int = 32 // 4 fields * 8 |
| 166 | const NX_QP_REG_BYTES: nx_int = 24 |
functions
| 65 | func nx_qp_is_valid(k: nx_int) -> nx_int |
| 91 | func nx_qr_is_valid(r: nx_int) -> nx_int |
| 126 | func nx_qp_select_kind(role: nx_int, depth_from_first: nx_int, depth_from_last: nx_int) -> nx_int |
| 168 | func nx_qp_alloc(cap: nx_int) -> *NxQuantPolicy |
| 176 | func nx_qp_register(p: *NxQuantPolicy, |
| 204 | func nx_qp_bytes_per_param_q14(kind: nx_int) -> nx_int |
| 220 | func nx_qp_total_bytes(p: *NxQuantPolicy, params_per_entry: *i64) -> nx_int |
| 236 | func nx_qp_total_bytes_dense_f16(params_per_entry: *i64, n: nx_int) -> nx_int called by 1: nx_qp_compression_ratio_q10 |
| 250 | func nx_qp_compression_ratio_q10(p: *NxQuantPolicy, params_per_entry: *i64) -> nx_int |
| 270 | func main() -> i64 |