nx_nofloat_q4k.nx
buildroot/runtime/nx_nofloat_q4k.nx
about
nx_nofloat_q4k.nx -- LM4 RESIDENT-QUANT DECODE (2026-09-02): the 7B fits because the weights STAY Q4_K.
MEASURED (nx_nofloat_llm.nx nf_alloc_layer_wb_i8): the "i8" layer cache stores 2 B/weight (i16 lanes for
__i16x16_madd), so Qwen2.5-Coder-7B = 6.5e9 layer params = 13 GB of anonymous memory before the i32 head
(2.2 GB), the copied model buffer (4.7 GB via sys_read_file) and the MAXT scratch -- it cannot fit a 16 GB
WSL VM and no cache hygiene changes that. This lib keeps every Q4_K projection IN THE FILE MAP (0.5625
B/weight, file-backed, evictable) and dots it with the estate's fused SIMD kernel (nx_q4k_dot_simd_lib,
bit-exact vs nx_q4k_dot_row_col), so a Q4_K layer costs 0 anonymous bytes. A tensor of any other quant
type (Q4_K_M puts Q6_K on some attn_v/ffn_down rows) falls back PER TENSOR to the incumbent i16 cache
(nf_quant_w_i8 + mm_pool_i8) -- counted and announced, never silent.
NUMERIC CONTRACT (why the shift arithmetic is what it is): the serve keeps activations in Q16; its i8
path computes dst = (sx*sw*sum(xq*wq)) >> shift with W in Q16 (shift 24 for a Q24 rmsnorm input, 16 for a
Q16 input). nx_q4k_dot_simd returns sum over the row of W_Q24 * col_i16 -- the weight carries 8 more
fractional bits -- so dst = ((dot >> NQ_PRE_SHIFT) * sx) >> (shift + NQ_W_EXTRA_BITS - NQ_PRE_SHIFT).
The activation is packed to i16 under a dynamic per-call scale sx = ceil(max|x| / 32767) (15 significant
bits) where the i8 path packs to 7 bits: this route is STRICTLY MORE EXACT on the activation side and
lossless on the weight side (the Q4_K bytes ARE the model). The pre-shift is overflow headroom: |dot|
reaches ~1e16 on an 18944-wide ffn_down row and sx reaches ~2^19, so the product is taken after >>16.
Duplicates deliberately NOT created: attention/softmax/residual come from nf_attn_kv_core (shared with the
i8 decoder), the fallback matvec IS mm_pool_i8, the per-row quantiser IS nf_quant_w_i8.
license_tier: ORIGINAL No hw writes (Rule 26).
dependencies 11 imports · 3 importers
diagram shows first 10 each side; +1 more imports, +0 more importers in the complete lists below.
imports: nx_syscalls.nxnx_tier.nxnx_le.nxnx_tensor.nxnx_gguf.nxnx_gguf_load.nxnx_thread_pool.nxnx_nofloat_llm.nxnx_q4k_dot_simd_lib.nxnx_q4k_dot_simd2_lib.nxnx_clock.nx
imported by: nx_nofloat_q4k_gate.nxnx_nofloat_serve_core.nxnx_q4k_simd2_gate.nx
structs
| none |
consts
| 33 | const NQ_KIND_Q4K: i64 = 0 |
| 34 | const NQ_KIND_I16: i64 = 1 |
| 35 | const NQ_KIND_Q8_0: i64 = 2 // search R0s (2026-09-17): Q8_0 blocks dotted in place from the file map (block-native) |
| 36 | const NQ_Q8_BLOCK_VALS: i64 = 32 // Q8_0 block width |
| 37 | const NQ_Q8_BLOCK_BYTES: i64 = 34 // one f16 scale (2 bytes) + 32 int8 codes |
| 38 | const NQ_Q8_SCALE_BYTES: i64 = 2 |
| 39 | const NQ_SLOT_WORDS: i64 = 8 // kind, base_off, in_dim, out_dim, row_stride, w16, sw, n_blocks |
| 40 | const NQ_S_KIND: i64 = 0 |
| 41 | const NQ_S_OFF: i64 = 1 |
| 42 | const NQ_S_IN: i64 = 2 |
| 43 | const NQ_S_OUT: i64 = 3 |
| 44 | const NQ_S_RSTRIDE: i64 = 4 |
| 45 | const NQ_S_W16: i64 = 5 |
| 46 | const NQ_S_SW: i64 = 6 |
| 47 | const NQ_S_NBLK: i64 = 7 |
| 48 | const NQ_NPROJ: i64 = 7 // q k v o gate up down |
| 49 | const NQ_P_Q: i64 = 0 |
| 50 | const NQ_P_K: i64 = 1 |
| 51 | const NQ_P_V: i64 = 2 |
| 52 | const NQ_P_O: i64 = 3 |
| 53 | const NQ_P_GATE: i64 = 4 |
| 54 | const NQ_P_UP: i64 = 5 |
| 55 | const NQ_P_DOWN: i64 = 6 |
| 56 | const NQ_L_GA: i64 = 56 // NQ_NPROJ * NQ_SLOT_WORDS: attn_norm gamma (Q16 i64, ne) |
| 57 | const NQ_L_GF: i64 = 57 // ffn_norm gamma |
| 58 | const NQ_L_BQ: i64 = 58 // q/k/v biases (Q16 i64) |
| 59 | const NQ_L_BK: i64 = 59 |
| 60 | const NQ_L_BV: i64 = 60 |
| 61 | const NQ_LAYER_WORDS: i64 = 64 |
| 62 | const NQ_SUPER_VALS: i64 = 256 // Q4_K super-block width |
| 63 | const NQ_SUPER_BYTES: i64 = 144 // Q4_K super-block bytes |
| 64 | const NQ_SUBBLOCKS: i64 = 8 // sub-blocks per super-block (sc_pre rows) |
| 65 | const NQ_ACT_I16_MAX: i64 = 32767 |
| 66 | const NQ_W_EXTRA_BITS: i64 = 8 // W_Q24 vs the serve's Q16 weight convention |
| 67 | const NQ_PRE_SHIFT: i64 = 16 // overflow headroom before multiplying by sx |
| 68 | const NQ_SHIFT_Q24: i64 = 24 // input in Q24 (post-rmsnorm) -> Q16 out |
| 69 | const NQ_SHIFT_Q16: i64 = 16 // input in Q16 (attention concat, silu*up) -> Q16 out |
| 70 | const NQ_BANDS_MAX: i64 = 32 |
| 71 | const NQ_BAND_BYTES: i64 = 320 // ctx 128 + qpk 64 + qhi 64 + acc 32 + spare 32 |
| 72 | const NQ_CTX_BYTES: i64 = 128 |
| 73 | const NQ_QPK_BYTES: i64 = 64 |
| 74 | const NQ_ACC_OFF: i64 = 256 |
| 75 | const NQ_ST_WORDS: i64 = 8 // stats: [0]=inplace tensors [1]=fallback tensors [2]=fallback anon bytes [3]=layers [4]=inplace file bytes |
| 87 | const NQ_PROF_WORDS: i64 = 6 |
| 88 | const NQ_PROF_Q4K_NS: i64 = 0 |
| 89 | const NQ_PROF_Q4K_CALLS: i64 = 1 |
| 90 | const NQ_PROF_I16_NS: i64 = 2 |
| 91 | const NQ_PROF_I16_CALLS: i64 = 3 |
| 92 | const NQ_PROF_Q8_NS: i64 = 4 |
| 93 | const NQ_PROF_Q8_CALLS: i64 = 5 |
functions
| 94 | func nq_prof() -> *i64 { if (g_nq_prof as i64) == 0 { g_nq_prof = sys_mmap(NQ_PROF_WORDS*8) as *i64 } return g_nq_prof } |
| 95 | func nq_prof_get(k: i64) -> i64 { let p: *i64 = nq_prof(); return p[k] } |
| 99 | func nq_kind_for(ggml_type: i64, in_dim: i64) -> i64 |
| 104 | func nq_post_shift(shift: i64) -> i64 { return shift + NQ_W_EXTRA_BITS - NQ_PRE_SHIFT } |
| 105 | func nq_scale_out(dot: i64, sx: i64, post: i64) -> i64 { return ((dot >> NQ_PRE_SHIFT) * sx) >> post } |
| 108 | func nq_act_scale(xmx: i64) -> i64 { var sx: i64 = (xmx + NQ_ACT_I16_MAX - 1) / NQ_ACT_I16_MAX; if sx < 1 { sx = 1 } return sx } |
| 109 | func nq_slot(lay: *i64, p: i64) -> *i64 { return ((lay as i64) + p*NQ_SLOT_WORDS*8) as *i64 } |
| 111 | func nq_bind_buf(buf: *u8) -> i64 { g_nq_buf = buf; return 0 } |
| 113 | func nq_arena(maxin: i64) -> i64 |
| 124 | func nq_slot_from_q16(Wq16: *i64, slot: *i64, in_dim: i64, out_dim: i64) -> i64 |
| 135 | func nq_slot_fill(buf: *u8, hdr: *NxGgufHeader, nm: *u8, L: i64, sfx: *u8, slot: *i64, in_dim: i64, out_dim: i64, tmp: *i64, st: *i64) -> i64 |
| 167 | func nq_load_vec(buf: *u8, hdr: *NxGgufHeader, nm: *u8, L: i64, sfx: *u8, n: i64) -> i64 |
| 174 | func nq_build_layers(buf: *u8, hdr: *NxGgufHeader, wq: *i64, N: i64, ne: i64, qd: i64, kvd: i64, fd: i64, nm: *u8, st: *i64) -> i64 |
| 205 | func _nq_mm_task(ctx_i: i64) -> i64 |
| 232 | func nq8_dot_row_scalar(buf: *u8, off: i64, nblk: i64, col16: *u8, w16: *u8) -> i64 |
| 257 | func nq8_dot_row(buf: *u8, off: i64, nblk: i64, col16: *u8, w16: *u8) -> i64 |
| 271 | func _nq8_mm_task(ctx_i: i64) -> i64 |
| 288 | func mq8_gemm(x: *i64, slot: *i64, dst: *i64, shift: i64) -> i64 called by 1: nq_mm calls 7: nf_poolnq_act_scalenf_pack2nx_pool_n_completednq_post_shiftnx_pool_submit+1 |
| 320 | func nq_mm(x: *i64, slot: *i64, dst: *i64, shift: i64) -> i64 |
| 341 | func mq_fused_dequant_gemm(x: *i64, slot: *i64, dst: *i64, shift: i64) -> i64 called by 1: nq_mm calls 8: nf_poolnq_act_scalenf_pack2nx_q4k_sc_precomputenx_pool_n_completednq_post_shift+2 |
| 374 | func decode_step_kv_q4k(x1: *i64, out1: *i64, wq: *i64, sb: *i64, freqs: *i64, kvc: *i64, pos: i64, cfgA: *i64, cfgF: *i64, N: i64) -> i64 |