code wiki / _hdl_build / nx_quality_balance.nx

nx_quality_balance.nx source

↩ module page · 88 lines · 4501 B

1// nx_quality_balance.nx -- BALANCE quality against speed (the operator's law: always balance 2// quality and quantity, with QUALITY SLIGHTLY MORE VALUABLE than raw speed). Cutting bytes 3// (lower quantization) buys speed in the memory-bound regime but costs QUALITY (perplexity rises). 4// "Just go Q2 for 1.8x" is wrong -- Q2_K is +8.82% perplexity. This module picks the operating 5// point that maximizes a quality-weighted score under a hard QUALITY FLOOR (rule: never strip 6// quality for speed). Data-driven: the quality + size numbers are measured config, not magic. 7// 8// RACI: the COUNCIL governs the POLICY here (the quality floor + the quality:speed weighting -- 9// the value judgment). The ENGINEER supplies the measured numbers (nx_engineer_profile). The 10// BUILDER consumes qb_select to choose the operating point (nx_builder_quant). One organ per job. 11// 12// Real llama.cpp data (Llama-3.1-8B class), perplexity delta to fp16 as PERMIL (pct*10), and bits 13// per weight *10: fp16(0,160) Q8_0(1,85) Q6_K(2,66) Q5_K_M(4,55) Q4_K_M(12,45) Q3_K_M(47,34) 14// Q2_K(88,26). Refs: arxiv 2601.14277 unified quant eval; llama.cpp perplexity discussion #406. 15// license_tier: ORIGINAL 16 17import "nx_syscalls.nx" 18 19// ---- COUNCIL-GOVERNED POLICY (the value judgment: quality slightly > speed, and a hard floor) ---- 20const QB_W_QUALITY: i64 = 11 // quality weight (slightly higher than speed) 21const QB_W_SPEED: i64 = 9 // speed weight 22const QB_QUALITY_FLOOR: i64 = 20 // max acceptable perplexity loss, PERMIL (2.0%); below = reject 23const QB_BITS_FP16: i64 = 160 // bits*10 of the fp16 reference (the size baseline) 24const QB_BITS_MIN: i64 = 26 // bits*10 of the smallest option (Q2_K) -- speed normalisation span 25 26// quality score 0..1000 (1000 = lossless fp16). Higher = better quality. 27func qb_quality_score(loss_permil: i64) -> i64 { 28 if loss_permil < 0 { return 0 } 29 if loss_permil > 1000 { return 0 } 30 return 1000 - loss_permil 31} 32 33// speed score 0..1000 in the MEMORY-BOUND regime (throughput ~ 1/bytes ~ 1/bits): linear in bits 34// SAVED vs fp16. Smallest option -> 1000, fp16 -> 0. Defensive: clamp bits to the valid span. 35func qb_speed_score(bits10: i64) -> i64 { 36 var b: i64 = bits10 37 if b < QB_BITS_MIN { b = QB_BITS_MIN } 38 if b > QB_BITS_FP16 { b = QB_BITS_FP16 } 39 let span: i64 = QB_BITS_FP16 - QB_BITS_MIN 40 if span <= 0 { return 0 } 41 return (1000 * (QB_BITS_FP16 - b)) / span 42} 43 44// combined score: quality weighted slightly more than speed. 0..1000. 45func qb_combined(loss_permil: i64, bits10: i64) -> i64 { 46 let q: i64 = qb_quality_score(loss_permil) 47 let s: i64 = qb_speed_score(bits10) 48 let wsum: i64 = QB_W_QUALITY + QB_W_SPEED 49 if wsum <= 0 { return 0 } 50 return (QB_W_QUALITY * q + QB_W_SPEED * s) / wsum 51} 52 53// COUNCIL gate: is this operating point ADMISSIBLE on quality? (the "never strip quality" floor). 54func council_quality_admit(loss_permil: i64, floor: i64) -> i64 { 55 if loss_permil <= floor { return 1 } 56 return 0 57} 58 59// SELECT the best operating point among n options (loss[], bits10[]), honoring the Council floor: 60// maximize the quality-weighted combined score over options that PASS the floor. If NONE pass 61// (every option too lossy), degrade GRACEFULLY to the highest-quality option (never ship below 62// the floor by going lower) -- returns its index. Defensive: n<=0 -> -1. 63func qb_select(n: i64, loss: *i64, bits10: *i64, floor: i64) -> i64 { 64 if n <= 0 { return 0 - 1 } 65 var best: i64 = 0 - 1; var bestscore: i64 = 0 - 1 66 var i: i64 = 0 67 while i < n { 68 if council_quality_admit(loss[i], floor) == 1 { 69 let sc: i64 = qb_combined(loss[i], bits10[i]) 70 if sc > bestscore { bestscore = sc; best = i } 71 } 72 i = i + 1 73 } 74 if best >= 0 { return best } 75 // graceful degradation: nothing met the floor -> pick the lowest-loss (highest-quality) option. 76 var hq: i64 = 0; var lo: i64 = loss[0]; var j: i64 = 1 77 while j < n { if loss[j] < lo { lo = loss[j]; hq = j } j = j + 1 } 78 return hq 79} 80 81// the pure-SPEED selector (ignores quality) -- kept ONLY to contrast: it would pick the smallest 82// option (e.g. Q2_K) and wreck quality. Proof that the balanced selector is doing real work. 83func qb_select_speed_only(n: i64, bits10: *i64) -> i64 { 84 if n <= 0 { return 0 - 1 } 85 var best: i64 = 0; var lo: i64 = bits10[0]; var i: i64 = 1 86 while i < n { if bits10[i] < lo { lo = bits10[i]; best = i } i = i + 1 } 87 return best 88}