Skip to main content

cortiq_core/
quant.rs

1//! Canonical quantization layouts and scalar dequantization.
2//!
3//! Layouts are byte-identical to `.vmfc` ("quants first, then scales"):
4//! - `q8_row`  (2-D `[out, in]`): `[int8: out·in][f16: out]`,
5//!   `w = q[o,i] · scale[o]`, `scale[o] = absmax(row_o) / 127`.
6//! - `q4_block`: groups of 32 over the flattened tensor (zero-padded),
7//!   `[u8: ceil(n/32)·16][f16: ceil(n/32)]`, nibbles low-first,
8//!   `w = (q − 8) · scale`, `scale = absmax(group) / 7`.
9//!
10//! 1-D tensors (norms) are always stored `f16`.
11
12use crate::format::TensorEntry;
13use crate::types::TensorDtype;
14
15pub const GROUP_SIZE: usize = 32;
16
17/// IEEE half → f32.
18#[inline]
19pub fn f16_to_f32(h: u16) -> f32 {
20    let sign = (h >> 15) as u32;
21    let exp = ((h >> 10) & 0x1F) as u32;
22    let mant = (h & 0x3FF) as u32;
23    let bits = if exp == 0 {
24        if mant == 0 {
25            sign << 31
26        } else {
27            // subnormal: normalize. A subnormal half equals mant·2^-24; shifting
28            // its MSB up to bit 10 takes e = 10-b shifts (b = MSB position), so
29            // the true exponent is b-24 and the f32 biased exponent is b+103 =
30            // 113-e. (The old `127-15-e` form was off by one — it halved every
31            // subnormal, which corrupts K-quant super-block scales.)
32            let mut e = 0u32;
33            let mut m = mant;
34            while m & 0x400 == 0 {
35                m <<= 1;
36                e += 1;
37            }
38            m &= 0x3FF;
39            (sign << 31) | ((113 - e) << 23) | (m << 13)
40        }
41    } else if exp == 0x1F {
42        (sign << 31) | (0xFF << 23) | (mant << 13)
43    } else {
44        (sign << 31) | ((exp + 127 - 15) << 23) | (mant << 13)
45    };
46    f32::from_bits(bits)
47}
48
49/// f32 → IEEE half (round-to-nearest-even). Used by the Rust writer.
50#[inline]
51pub fn f32_to_f16(x: f32) -> u16 {
52    let bits = x.to_bits();
53    let sign = ((bits >> 16) & 0x8000) as u16;
54    let mut exp = ((bits >> 23) & 0xFF) as i32;
55    let mant = bits & 0x7F_FFFF;
56
57    if exp == 0xFF {
58        // Inf / NaN
59        return sign | 0x7C00 | if mant != 0 { 0x200 } else { 0 };
60    }
61    exp -= 127 - 15;
62    if exp >= 0x1F {
63        return sign | 0x7C00; // overflow → Inf
64    }
65    if exp <= 0 {
66        if exp < -10 {
67            return sign; // underflow → 0
68        }
69        // subnormal
70        let m = mant | 0x80_0000;
71        let shift = (14 - exp) as u32;
72        let half = m >> shift;
73        let round = (m >> (shift - 1)) & 1;
74        return sign | ((half + round) as u16);
75    }
76    let half = ((exp as u32) << 10) | (mant >> 13);
77    let round = (mant >> 12) & 1;
78    // round-to-nearest-even: bump if round bit set and (sticky or odd)
79    let sticky = (mant & 0xFFF) != 0;
80    let bump = round & (sticky as u32 | (half & 1));
81    sign | ((half + bump) as u16)
82}
83
84/// bfloat16 → f32.
85#[inline]
86pub fn bf16_to_f32(h: u16) -> f32 {
87    f32::from_bits((h as u32) << 16)
88}
89
90/// Dequantize a full `q8_row` tensor: `[int8: out·in][f16: out]`.
91pub fn dequant_q8_row(bytes: &[u8], out_dim: usize, in_dim: usize, dst: &mut [f32]) {
92    debug_assert_eq!(bytes.len(), out_dim * in_dim + out_dim * 2);
93    debug_assert_eq!(dst.len(), out_dim * in_dim);
94    let (q, scales) = bytes.split_at(out_dim * in_dim);
95    for o in 0..out_dim {
96        let s = f16_to_f32(u16::from_le_bytes([scales[o * 2], scales[o * 2 + 1]]));
97        let row = &q[o * in_dim..(o + 1) * in_dim];
98        let out = &mut dst[o * in_dim..(o + 1) * in_dim];
99        for (d, &b) in out.iter_mut().zip(row) {
100            *d = (b as i8) as f32 * s;
101        }
102    }
103}
104
105/// Dequantize a full `q8_2f` tensor (two-scale row/column, dtype 9):
106/// `[int8: out·in][f16 row_scale: out][f16 col: in]`,
107/// `w[o,i] = q[o,i] · row_scale[o] · col[i]`. The column field absorbs
108/// outlier input channels — validated in vmfcore (+37% at equal size
109/// for the two-field family; q8_2f recovers ~75% of the q8→f16 gap).
110pub fn dequant_q8_2f(bytes: &[u8], out_dim: usize, in_dim: usize, dst: &mut [f32]) {
111    debug_assert_eq!(bytes.len(), out_dim * in_dim + out_dim * 2 + in_dim * 2);
112    debug_assert_eq!(dst.len(), out_dim * in_dim);
113    let (q, rest) = bytes.split_at(out_dim * in_dim);
114    let (scales, cols) = rest.split_at(out_dim * 2);
115    let col: Vec<f32> = (0..in_dim)
116        .map(|i| f16_to_f32(u16::from_le_bytes([cols[i * 2], cols[i * 2 + 1]])))
117        .collect();
118    for o in 0..out_dim {
119        let s = f16_to_f32(u16::from_le_bytes([scales[o * 2], scales[o * 2 + 1]]));
120        let row = &q[o * in_dim..(o + 1) * in_dim];
121        let out = &mut dst[o * in_dim..(o + 1) * in_dim];
122        for i in 0..in_dim {
123            out[i] = (row[i] as i8) as f32 * s * col[i];
124        }
125    }
126}
127
128/// Dequantize a full `q4_block` tensor into `dst` (`dst.len()` = real
129/// element count; the trailing pad group elements are discarded).
130pub fn dequant_q4_block(bytes: &[u8], dst: &mut [f32]) {
131    let n_groups = dst.len().div_ceil(GROUP_SIZE);
132    let packed_len = n_groups * GROUP_SIZE / 2;
133    debug_assert_eq!(bytes.len(), packed_len + n_groups * 2);
134    let (packed, scales) = bytes.split_at(packed_len);
135    for g in 0..n_groups {
136        let s = f16_to_f32(u16::from_le_bytes([scales[g * 2], scales[g * 2 + 1]]));
137        let base = g * GROUP_SIZE;
138        let pk = &packed[g * 16..(g + 1) * 16];
139        for (k, &byte) in pk.iter().enumerate() {
140            let i0 = base + k * 2;
141            let i1 = i0 + 1;
142            if i0 < dst.len() {
143                dst[i0] = ((byte & 0x0F) as f32 - 8.0) * s;
144            }
145            if i1 < dst.len() {
146                dst[i1] = (((byte >> 4) & 0x0F) as f32 - 8.0) * s;
147            }
148        }
149    }
150}
151
152/// Dequantize a full `vbit` tensor (P13 FIG.3, grouped variant):
153/// [u8 bits: rows][f16 scales: rows·cols/32][bit-packed rows MSB-first,
154/// each row padded to a whole byte]. w = (u − L)·scale, L = 2^{b−1}−1.
155pub fn dequant_vbit(bytes: &[u8], rows: usize, cols: usize, dst: &mut [f32]) -> Result<(), String> {
156    if cols % GROUP_SIZE != 0 {
157        return Err(format!("vbit: cols {cols} not a multiple of {GROUP_SIZE}"));
158    }
159    let ng = cols / GROUP_SIZE;
160    let bits = &bytes[..rows];
161    if let Some(&b) = bits.iter().find(|&&b| !(3..=8).contains(&b)) {
162        return Err(format!("vbit: bit-width {b} outside safe range 3..=8"));
163    }
164    let sc_off = rows;
165    let data_off = sc_off + rows * ng * 2;
166    let mut off = data_off;
167    for r in 0..rows {
168        let b = bits[r] as usize;
169        let l = ((1usize << (b - 1)) - 1) as f32;
170        let rowlen = (cols * b).div_ceil(8);
171        let data = &bytes[off..off + rowlen];
172        let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
173        for i in 0..cols {
174            while nbits < b {
175                acc = (acc << 8) | data[idx] as u64;
176                idx += 1;
177                nbits += 8;
178            }
179            let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
180            nbits -= b;
181            let so = (r * ng + i / GROUP_SIZE) * 2;
182            let s = f16_to_f32(u16::from_le_bytes([
183                bytes[sc_off + so],
184                bytes[sc_off + so + 1],
185            ]));
186            dst[r * cols + i] = (u - l) * s;
187        }
188        off += rowlen;
189    }
190    Ok(())
191}
192
193/// Bytes per q4_tiled group tile: 2 (f16 scale) + 16 (nibbles).
194pub const Q4_TILE: usize = 18;
195
196/// Bytes per q1 group tile: 2 (f16 scale) + 4 (32 sign bits).
197pub const Q1_TILE: usize = 6;
198
199/// Dequantize a full `q1` tensor: per 32-group `[f16 scale][4B bits]`,
200/// bit k of byte j (LSB-first) is weight j·8+k of the group;
201/// value = scale · (2·bit − 1) ∈ {−s, +s}. 1-bit-TRAINED models only —
202/// see the dtype doc.
203pub fn dequant_q1(bytes: &[u8], dst: &mut [f32]) {
204    let n_groups = dst.len().div_ceil(GROUP_SIZE);
205    debug_assert_eq!(bytes.len(), n_groups * Q1_TILE);
206    for g in 0..n_groups {
207        let tile = &bytes[g * Q1_TILE..(g + 1) * Q1_TILE];
208        let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
209        let base = g * GROUP_SIZE;
210        for (j, &byte) in tile[2..].iter().enumerate() {
211            for k in 0..8 {
212                let i = base + j * 8 + k;
213                if i < dst.len() {
214                    dst[i] = if (byte >> k) & 1 == 1 { s } else { -s };
215                }
216            }
217        }
218    }
219}
220
221/// Dequantize a full `q1s` tensor (1-bit + sparse outlier overlay): the
222/// leading `n_groups·6` bytes are a plain `q1` base, then `[u32 count]`
223/// and `count × [u32 flat-index][f16 value]` restore the salient weights
224/// the two-field mask kept at full precision (they overwrite the ±s base).
225pub fn dequant_q1s(bytes: &[u8], dst: &mut [f32]) {
226    let n_groups = dst.len().div_ceil(GROUP_SIZE);
227    let base_len = n_groups * Q1_TILE;
228    dequant_q1(&bytes[..base_len.min(bytes.len())], dst);
229    let mut off = base_len;
230    if off + 4 > bytes.len() {
231        return;
232    }
233    let count =
234        u32::from_le_bytes([bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]]) as usize;
235    off += 4;
236    for _ in 0..count {
237        if off + 6 > bytes.len() {
238            break;
239        }
240        let idx = u32::from_le_bytes([bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]])
241            as usize;
242        let val = f16_to_f32(u16::from_le_bytes([bytes[off + 4], bytes[off + 5]]));
243        if idx < dst.len() {
244            dst[idx] = val;
245        }
246        off += 6;
247    }
248}
249
250/// q1g group: ONE f16 scale per 128 weights — the native group of
251/// 1-bit-trained checkpoints, which `q1` stores four times (once per
252/// 32-tile).
253pub const Q1G_GROUP: usize = 128;
254/// Sign-bit bytes per q1g group (128 bits).
255pub const Q1G_BITS: usize = Q1G_GROUP / 8;
256
257/// Byte offset of the q1g scale plane inside the payload: the sign plane
258/// comes first, `rows · cols/8` bytes (a multiple of 16, so the scale
259/// plane is as aligned as the tensor itself).
260#[inline]
261pub fn q1g_scale_offset(rows: usize, cols: usize) -> usize {
262    rows * (cols / 8)
263}
264
265/// Scale of q1g group `g` (flat index `row · cols/128 + group`).
266#[inline]
267pub fn q1g_scale(bytes: &[u8], scale_off: usize, g: usize) -> f32 {
268    let o = scale_off + g * 2;
269    f16_to_f32(u16::from_le_bytes([bytes[o], bytes[o + 1]]))
270}
271
272/// Dequantize a full `q1g` tensor: `[sign bits: rows·cols/8][f16 scales:
273/// rows·cols/128]`; bit k of byte j (LSB-first) is weight j·8+k of the
274/// row, value = scale · (2·bit − 1). `cols % 128 == 0`.
275pub fn dequant_q1g(bytes: &[u8], rows: usize, cols: usize, dst: &mut [f32]) {
276    debug_assert_eq!(cols % Q1G_GROUP, 0);
277    let sc_off = q1g_scale_offset(rows, cols);
278    let n_groups = rows * (cols / Q1G_GROUP);
279    for g in 0..n_groups {
280        let s = q1g_scale(bytes, sc_off, g);
281        let base = g * Q1G_GROUP;
282        for (j, &byte) in bytes[g * Q1G_BITS..(g + 1) * Q1G_BITS].iter().enumerate() {
283            for k in 0..8 {
284                dst[base + j * 8 + k] = if (byte >> k) & 1 == 1 { s } else { -s };
285            }
286        }
287    }
288}
289
290/// Outcome of a `q1 → q1g` repack ([`repack_q1_to_q1g`]).
291#[derive(Debug, Clone)]
292pub struct Q1gRepack {
293    /// The q1g payload. Always built: a group whose four tile scales
294    /// differ carries its representative (see `repack_q1_to_q1g`), so the
295    /// caller decides from the counts whether to keep it.
296    pub data: Vec<u8>,
297    /// 128-groups in the tensor.
298    pub groups: usize,
299    /// Groups whose four 32-tile scales are NOT byte-identical — the only
300    /// groups whose decoded weights differ from the source.
301    pub differing: usize,
302    /// Largest `|s_tile − s_group| / s_group` over the differing groups
303    /// (0 when none differ; infinite when the representative is zero or
304    /// a scale is non-finite).
305    pub max_rel: f64,
306    /// Differing groups beyond the caller's relative tolerance.
307    pub over_tol: usize,
308}
309
310/// Repack a `q1` payload into `q1g` (2-D, `cols % 128 == 0`).
311///
312/// The sign plane is the tiles' bit fields concatenated, so it is always
313/// exact. Each 128-group keeps one scale: the tiles' common value when all
314/// four f16 scales are byte-identical (then the group decodes bit for bit
315/// as before), otherwise the f16 nearest the mean of the four — the
316/// L2-optimal single level for those 128 weights, since each tile
317/// contributes 32 weights of magnitude `s_tile`. Groups like that are
318/// counted in `differing`, and `over_tol` counts the ones whose relative
319/// spread exceeds `tol`.
320pub fn repack_q1_to_q1g(bytes: &[u8], rows: usize, cols: usize, tol: f64) -> Q1gRepack {
321    assert_eq!(cols % Q1G_GROUP, 0, "q1g needs cols % 128 == 0");
322    assert_eq!(
323        bytes.len(),
324        rows * (cols / GROUP_SIZE) * Q1_TILE,
325        "q1 payload length"
326    );
327    let groups = rows * (cols / Q1G_GROUP);
328    let sc_off = q1g_scale_offset(rows, cols);
329    let mut data = vec![0u8; sc_off + groups * 2];
330    let (mut differing, mut over_tol, mut max_rel) = (0usize, 0usize, 0f64);
331    for g in 0..groups {
332        let mut sc = [0u16; 4];
333        for (t, s) in sc.iter_mut().enumerate() {
334            let tile = &bytes[(g * 4 + t) * Q1_TILE..(g * 4 + t + 1) * Q1_TILE];
335            *s = u16::from_le_bytes([tile[0], tile[1]]);
336            data[g * Q1G_BITS + t * 4..g * Q1G_BITS + t * 4 + 4].copy_from_slice(&tile[2..6]);
337        }
338        let rep = if sc.iter().all(|&s| s == sc[0]) {
339            sc[0]
340        } else {
341            differing += 1;
342            let vals = sc.map(f16_to_f32);
343            let mean = vals.iter().map(|&v| v as f64).sum::<f64>() / 4.0;
344            let rep = f32_to_f16(mean as f32);
345            let r = f16_to_f32(rep) as f64;
346            let rel = vals
347                .iter()
348                .map(|&v| {
349                    let d = (v as f64 - r).abs();
350                    if d == 0.0 {
351                        0.0
352                    } else if r != 0.0 && r.is_finite() {
353                        d / r.abs()
354                    } else {
355                        f64::INFINITY
356                    }
357                })
358                .fold(
359                    0f64,
360                    |a, b| if b.is_nan() { f64::INFINITY } else { a.max(b) },
361                );
362            max_rel = max_rel.max(rel);
363            if rel.is_nan() || rel > tol {
364                over_tol += 1;
365            }
366            rep
367        };
368        data[sc_off + g * 2..sc_off + g * 2 + 2].copy_from_slice(&rep.to_le_bytes());
369    }
370    Q1gRepack {
371        data,
372        groups,
373        differing,
374        max_rel,
375        over_tol,
376    }
377}
378
379/// The inverse of [`repack_q1_to_q1g`]: a `q1g` payload back to `q1` tiles
380/// (each group's scale written into its four tiles). Exact — the result
381/// decodes to the same weights bit for bit — so a runtime that predates
382/// dtype 20 can still read the model, and a q1/q1g pair built this way
383/// isolates the kernels from the repack's tolerance groups.
384pub fn unpack_q1g_to_q1(bytes: &[u8], rows: usize, cols: usize) -> Vec<u8> {
385    assert_eq!(cols % Q1G_GROUP, 0, "q1g needs cols % 128 == 0");
386    let sc_off = q1g_scale_offset(rows, cols);
387    let tiles = rows * (cols / GROUP_SIZE);
388    assert_eq!(bytes.len(), sc_off + tiles / 4 * 2, "q1g payload length");
389    let mut q = vec![0u8; tiles * Q1_TILE];
390    for t in 0..tiles {
391        let o = t * Q1_TILE;
392        q[o..o + 2].copy_from_slice(&bytes[sc_off + (t / 4) * 2..sc_off + (t / 4) * 2 + 2]);
393        q[o + 2..o + 6].copy_from_slice(&bytes[t * 4..t * 4 + 4]);
394    }
395    q
396}
397
398/// Ternary tile: 2 (f16 scale) + 7 (32 base-3 codes, 5 ternary values per
399/// byte, `3^5 = 243 ≤ 256`) = 9 bytes ⇒ ~2.25 bpw base (vs the old 2-bit
400/// 10-byte tile). The packed codes carry the same `{0,+s,−s}` values, so
401/// the reconstruction is bit-identical — this is pure size, no quality change.
402pub const Q1T_TILE: usize = 9;
403const Q1T_POW3: [u16; 5] = [1, 3, 9, 27, 81];
404
405/// Base-3 code (0/1/2) of ternary weight `k` from a group's packed 7 bytes.
406#[inline]
407pub fn q1t_code(codes: &[u8], k: usize) -> u8 {
408    ((codes[k / 5] as u16 / Q1T_POW3[k % 5]) % 3) as u8
409}
410
411/// Pack one base-3 code into the group's 7-byte code block (accumulative;
412/// start from a zeroed block, call for k = 0..32 in order).
413#[inline]
414pub fn q1t_pack(codes: &mut [u8; 7], k: usize, code: u8) {
415    codes[k / 5] += code * Q1T_POW3[k % 5] as u8;
416}
417
418/// Dequantize a full `q1t` tensor (ternary + sparse outlier overlay): per
419/// 32-group `[f16 scale][7B base-3 codes]` (0 → 0, 1 → +s, 2 → −s), then
420/// `[u32 count]` and `count × [u32 index][f16 value]`.
421/// `rows`×`cols` shape is needed for the per-row overlay (`[u32 row_ptr[rows+1]]`
422/// then `[(u16 col, f16 val)]` grouped by row — 4 B/outlier, no flat index).
423pub fn dequant_q1t(bytes: &[u8], rows: usize, cols: usize, dst: &mut [f32]) {
424    let n_groups = dst.len().div_ceil(GROUP_SIZE);
425    let base_len = n_groups * Q1T_TILE;
426    for g in 0..n_groups {
427        let off = g * Q1T_TILE;
428        if off + Q1T_TILE > bytes.len() {
429            break;
430        }
431        let s = f16_to_f32(u16::from_le_bytes([bytes[off], bytes[off + 1]]));
432        let codes = &bytes[off + 2..off + Q1T_TILE];
433        let base = g * GROUP_SIZE;
434        for k in 0..GROUP_SIZE {
435            let i = base + k;
436            if i < dst.len() {
437                dst[i] = match q1t_code(codes, k) {
438                    1 => s,
439                    2 => -s,
440                    _ => 0.0,
441                };
442            }
443        }
444    }
445    // Overlay: [u32 row_ptr[rows+1]] then entries grouped by row.
446    let entries = base_len + (rows + 1) * 4;
447    if entries > bytes.len() {
448        return;
449    }
450    let rp = |r: usize| -> usize {
451        let o = base_len + r * 4;
452        u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]) as usize
453    };
454    for r in 0..rows {
455        for p in rp(r)..rp(r + 1) {
456            let e = entries + p * 4;
457            if e + 4 > bytes.len() {
458                return;
459            }
460            let col = u16::from_le_bytes([bytes[e], bytes[e + 1]]) as usize;
461            let val = f16_to_f32(u16::from_le_bytes([bytes[e + 2], bytes[e + 3]]));
462            let i = r * cols + col;
463            if i < dst.len() {
464                dst[i] = val;
465            }
466        }
467    }
468}
469
470/// Dequantize a full `q4_tiled` tensor: per 32-group
471/// `[f16 scale][16B nibbles]`, nibbles low-first inside each byte —
472/// the same values/order as `q4_block`, only the placement of the
473/// scale differs.
474pub fn dequant_q4_tiled(bytes: &[u8], dst: &mut [f32]) {
475    let n_groups = dst.len().div_ceil(GROUP_SIZE);
476    debug_assert_eq!(bytes.len(), n_groups * Q4_TILE);
477    for g in 0..n_groups {
478        let tile = &bytes[g * Q4_TILE..(g + 1) * Q4_TILE];
479        let s = f16_to_f32(u16::from_le_bytes([tile[0], tile[1]]));
480        let base = g * GROUP_SIZE;
481        for (k, &byte) in tile[2..].iter().enumerate() {
482            let i0 = base + k * 2;
483            let i1 = i0 + 1;
484            if i0 < dst.len() {
485                dst[i0] = ((byte & 0x0F) as f32 - 8.0) * s;
486            }
487            if i1 < dst.len() {
488                dst[i1] = (((byte >> 4) & 0x0F) as f32 - 8.0) * s;
489            }
490        }
491    }
492}
493
494/// Nibble bytes per `q4tp` group tile — the scale lives in a side plane,
495/// so the nibble stream is 16-byte strided (better aligned than q4t's 18).
496pub const Q4TP_NIB: usize = 16;
497
498/// Highest `q4tp` scale code: 5 bits, so the ladder has 32 rungs.
499pub const Q4TP_LMAX: usize = 31;
500
501/// Bytes of 5-bit codes per row. Rounded up to whole bytes so every row
502/// decodes independently — worth the ≤7 wasted bits for parallel row work.
503pub const fn q4tp_code_stride(gpr: usize) -> usize {
504    (gpr * 5).div_ceil(8)
505}
506
507/// Byte offsets inside a `q4tp` payload: `(params_off, codes_off, code_stride)`.
508/// Layout is `[nibbles][row params: (f16 lo, f16 step)][codes]`.
509pub fn q4tp_sections(rows: usize, cols: usize) -> (usize, usize, usize) {
510    let gpr = cols / GROUP_SIZE;
511    let nib = rows * gpr * Q4TP_NIB;
512    (nib, nib + rows * 4, q4tp_code_stride(gpr))
513}
514
515/// Read tile `g`'s 5-bit scale code out of one row's code slice.
516#[inline]
517pub fn q4tp_code(codes: &[u8], g: usize) -> usize {
518    let bit = g * 5;
519    let (b, sh) = (bit / 8, bit % 8);
520    let lo = codes[b] as u16;
521    // A 5-bit field starting past bit 3 spills into the next byte; the
522    // stride always has that byte when it does, but stay total anyway.
523    let hi = if sh > 3 {
524        codes.get(b + 1).copied().unwrap_or(0) as u16
525    } else {
526        0
527    };
528    (((lo | (hi << 8)) >> sh) & 0x1F) as usize
529}
530
531/// Write tile `g`'s 5-bit code (encoder side; assumes the slice starts zeroed).
532#[inline]
533pub fn q4tp_put_code(codes: &mut [u8], g: usize, code: usize) {
534    let bit = g * 5;
535    let (b, sh) = (bit / 8, bit % 8);
536    let v = (code & 0x1F) as u16;
537    codes[b] |= (v << sh) as u8;
538    if sh > 3 {
539        codes[b + 1] |= (v >> (8 - sh)) as u8;
540    }
541}
542
543/// Expand row `r`'s 32-rung scale ladder: `s[c] = 2^(lo + c·step)`,
544/// evaluated as one `exp2` plus 31 multiplies so that every consumer —
545/// scalar dequant, SIMD kernel, GPU shader — lands on the SAME f32 bits.
546/// The geometric form is the format's definition, not an optimization.
547#[inline]
548pub fn q4tp_ladder(params: &[u8], r: usize) -> [f32; 32] {
549    let lo = f16_to_f32(u16::from_le_bytes([params[r * 4], params[r * 4 + 1]]));
550    let st = f16_to_f32(u16::from_le_bytes([params[r * 4 + 2], params[r * 4 + 3]]));
551    let ratio = st.exp2();
552    let mut t = [0f32; 32];
553    t[0] = lo.exp2();
554    for c in 1..32 {
555        t[c] = t[c - 1] * ratio;
556    }
557    t
558}
559
560/// Dequantize a full `q4tp` tensor. Nibbles are read exactly as `q4_tiled`
561/// reads them; only where the scale comes from differs.
562pub fn dequant_q4tp(bytes: &[u8], rows: usize, cols: usize, dst: &mut [f32]) {
563    let gpr = cols / GROUP_SIZE;
564    let (params_off, codes_off, stride) = q4tp_sections(rows, cols);
565    let params = &bytes[params_off..params_off + rows * 4];
566    for r in 0..rows {
567        let tab = q4tp_ladder(params, r);
568        let codes = &bytes[codes_off + r * stride..codes_off + (r + 1) * stride];
569        for g in 0..gpr {
570            let s = tab[q4tp_code(codes, g)];
571            let tile = &bytes[(r * gpr + g) * Q4TP_NIB..(r * gpr + g + 1) * Q4TP_NIB];
572            let base = r * cols + g * GROUP_SIZE;
573            for (k, &byte) in tile.iter().enumerate() {
574                dst[base + k * 2] = ((byte & 0x0F) as f32 - 8.0) * s;
575                dst[base + k * 2 + 1] = (((byte >> 4) & 0x0F) as f32 - 8.0) * s;
576            }
577        }
578    }
579}
580
581/// Chunk bytes per `q2tp` group — 32 weights at 2 bits. The scale ladder,
582/// row params and 5-bit rung codes are byte-identical to `q4tp`'s planes;
583/// only the weight plane shrinks from 16 to 8 bytes per group.
584pub const Q2TP_CHUNK: usize = 8;
585
586/// Byte offsets inside a `q2tp` payload: `(params_off, codes_off, code_stride)`.
587/// Layout is `[2-bit chunks][row params: (f16 lo, f16 step)][codes]`.
588pub fn q2tp_sections(rows: usize, cols: usize) -> (usize, usize, usize) {
589    let gpr = cols / GROUP_SIZE;
590    let chunk = rows * gpr * Q2TP_CHUNK;
591    (chunk, chunk + rows * 4, q4tp_code_stride(gpr))
592}
593
594/// Highest `q2tp` scale rung. One fewer than q4tp's: rung 0 is spent on
595/// the exact zero below, so the geometric part is rungs 1..=31.
596pub const Q2TP_LMAX: usize = 30;
597
598/// `q2tp` scale ladder. The 4-level grid ±0.5/±1.5 is the RMS-optimal
599/// symmetric quantizer for Gaussian-ish weights, but it cannot spell
600/// ZERO — so a pruned or masked group would come back as noise. Rung 0
601/// therefore means "this group is exactly zero" and rungs 1..=31 are the
602/// q4tp ladder shifted down one: `s(c) = 2^(lo + (c−1)·step)`.
603pub fn q2tp_ladder(params: &[u8], r: usize) -> [f32; 32] {
604    let base = q4tp_ladder(params, r);
605    let mut t = [0f32; 32];
606    t[1..32].copy_from_slice(&base[..31]);
607    t
608}
609
610/// Dequantize a full `q2tp` tensor. Weights are 2-bit fields, LSB-first
611/// within each byte: code c ∈ 0..4 → (c − 1.5)·s (and s = 0 at rung 0).
612pub fn dequant_q2tp(bytes: &[u8], rows: usize, cols: usize, dst: &mut [f32]) {
613    let gpr = cols / GROUP_SIZE;
614    let (params_off, codes_off, stride) = q2tp_sections(rows, cols);
615    let params = &bytes[params_off..params_off + rows * 4];
616    for r in 0..rows {
617        let tab = q2tp_ladder(params, r);
618        let codes = &bytes[codes_off + r * stride..codes_off + (r + 1) * stride];
619        for g in 0..gpr {
620            let s = tab[q4tp_code(codes, g)];
621            let chunk = &bytes[(r * gpr + g) * Q2TP_CHUNK..(r * gpr + g + 1) * Q2TP_CHUNK];
622            let base = r * cols + g * GROUP_SIZE;
623            for (k, &byte) in chunk.iter().enumerate() {
624                for j in 0..4 {
625                    dst[base + k * 4 + j] = (((byte >> (2 * j)) & 3) as f32 - 1.5) * s;
626                }
627            }
628        }
629    }
630}
631
632/// Byte layout of a `vbit_ro` payload (roadmap §4.2):
633/// `[u8 bits: rows][f16 scales: rows·cols/32][u32 row_offsets: rows+1]
634///  [bit-packed rows]` — offsets are relative to the packed area, so
635/// `offsets[r]..offsets[r+1]` is row r without any prefix scan.
636/// Returns (scales_off, offsets_off, packed_off).
637pub fn vbit_ro_sections(rows: usize, cols: usize) -> (usize, usize, usize) {
638    let ng = cols / GROUP_SIZE;
639    let scales_off = rows;
640    let offsets_off = scales_off + rows * ng * 2;
641    let packed_off = offsets_off + (rows + 1) * 4;
642    (scales_off, offsets_off, packed_off)
643}
644
645/// Read one u32 row offset from a `vbit_ro` offsets table.
646#[inline]
647pub fn vbit_ro_offset(bytes: &[u8], offsets_off: usize, r: usize) -> usize {
648    let o = offsets_off + r * 4;
649    u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]) as usize
650}
651
652/// Dequantize a full `vbit_ro` tensor — same math as `dequant_vbit`,
653/// rows addressed through the stored offset table.
654pub fn dequant_vbit_ro(
655    bytes: &[u8],
656    rows: usize,
657    cols: usize,
658    dst: &mut [f32],
659) -> Result<(), String> {
660    if cols % GROUP_SIZE != 0 {
661        return Err(format!(
662            "vbit_ro: cols {cols} not a multiple of {GROUP_SIZE}"
663        ));
664    }
665    let ng = cols / GROUP_SIZE;
666    let (sc_off, off_off, packed_off) = vbit_ro_sections(rows, cols);
667    let bits = &bytes[..rows];
668    for r in 0..rows {
669        let b = bits[r] as usize;
670        if !matches!(b, 3..=6 | 8) {
671            return Err(format!(
672                "vbit_ro row {r}: bit width {b} outside {{3,4,5,6,8}}"
673            ));
674        }
675        let l = ((1usize << (b - 1)) - 1) as f32;
676        let start = packed_off + vbit_ro_offset(bytes, off_off, r);
677        let end = packed_off + vbit_ro_offset(bytes, off_off, r + 1);
678        let data = &bytes[start..end];
679        let (mut acc, mut nbits, mut idx) = (0u64, 0usize, 0usize);
680        for i in 0..cols {
681            while nbits < b {
682                acc = (acc << 8) | data[idx] as u64;
683                idx += 1;
684                nbits += 8;
685            }
686            let u = ((acc >> (nbits - b)) & ((1u64 << b) - 1)) as f32;
687            nbits -= b;
688            let so = (r * ng + i / GROUP_SIZE) * 2;
689            let sc = f16_to_f32(u16::from_le_bytes([
690                bytes[sc_off + so],
691                bytes[sc_off + so + 1],
692            ]));
693            dst[r * cols + i] = (u - l) * sc;
694        }
695    }
696    Ok(())
697}
698
699/// Expected byte length of a tensor given dtype and element count.
700pub fn expected_nbytes(dtype: TensorDtype, shape: &[usize]) -> Option<usize> {
701    let n = shape
702        .iter()
703        .try_fold(1usize, |acc, &dim| acc.checked_mul(dim))?;
704    match dtype {
705        TensorDtype::F32 => n.checked_mul(4),
706        TensorDtype::F16 | TensorDtype::Bf16 => n.checked_mul(2),
707        // Raw tables (spec §9.5.2): one, four or eight bytes per element.
708        TensorDtype::U8 => Some(n),
709        TensorDtype::U32 => n.checked_mul(4),
710        TensorDtype::U64 => n.checked_mul(8),
711        TensorDtype::Q8Row => {
712            let out = *shape.first()?;
713            n.checked_add(out.checked_mul(2)?)
714        }
715        TensorDtype::Q4Block => {
716            let groups = n.div_ceil(GROUP_SIZE);
717            groups.checked_mul(18)
718        }
719        TensorDtype::Q4Tiled => {
720            // Interleaved tiles: [f16 scale][16B nibbles] per 32-group.
721            n.div_ceil(GROUP_SIZE).checked_mul(18)
722        }
723        TensorDtype::Q1 => {
724            // Interleaved tiles: [f16 scale][4B bits] per 32-group.
725            n.div_ceil(GROUP_SIZE).checked_mul(Q1_TILE)
726        }
727        TensorDtype::Q1G => {
728            // [sign plane n/8][f16 scale plane n/128 · 2] — 2-D only, whole
729            // 128-groups per row (the encoder emits nothing else).
730            if shape.len() != 2 || shape[1] % Q1G_GROUP != 0 {
731                return None;
732            }
733            (n / 8).checked_add((n / Q1G_GROUP).checked_mul(2)?)
734        }
735        TensorDtype::Q4TiledP => {
736            // [nibbles][row params][row-aligned 5-bit codes] — needs the
737            // row count, so 2-D only (the encoder emits nothing else).
738            if shape.len() != 2 {
739                return None;
740            }
741            let (rows, cols) = (shape[0], shape[1]);
742            if cols % GROUP_SIZE != 0 {
743                return None;
744            }
745            let gpr = cols / GROUP_SIZE;
746            rows.checked_mul(gpr)?
747                .checked_mul(Q4TP_NIB)?
748                .checked_add(rows.checked_mul(4)?)?
749                .checked_add(rows.checked_mul(q4tp_code_stride(gpr))?)
750        }
751        TensorDtype::Q2TiledP => {
752            if shape.len() != 2 {
753                return None;
754            }
755            let (rows, cols) = (shape[0], shape[1]);
756            if cols % GROUP_SIZE != 0 {
757                return None;
758            }
759            let gpr = cols / GROUP_SIZE;
760            rows.checked_mul(gpr)?
761                .checked_mul(Q2TP_CHUNK)?
762                .checked_add(rows.checked_mul(4)?)?
763                .checked_add(rows.checked_mul(q4tp_code_stride(gpr))?)
764        }
765        TensorDtype::Q8_2f => {
766            let out = *shape.first()?;
767            let inn = n / out.max(1);
768            n.checked_add(out.checked_mul(2)?)?
769                .checked_add(inn.checked_mul(2)?)
770        }
771        _ => None, // variable/reserved dtypes: size not defined by this reader
772    }
773}
774
775fn has_fixed_payload_size(dtype: TensorDtype) -> bool {
776    matches!(
777        dtype,
778        TensorDtype::F32
779            | TensorDtype::F16
780            | TensorDtype::Bf16
781            | TensorDtype::Q8Row
782            | TensorDtype::Q4Block
783            | TensorDtype::Q4Tiled
784            | TensorDtype::Q4TiledP
785            | TensorDtype::Q2TiledP
786            | TensorDtype::Q1
787            | TensorDtype::Q1G
788            | TensorDtype::Q8_2f
789            | TensorDtype::U8
790            | TensorDtype::U32
791            | TensorDtype::U64
792    )
793}
794
795/// Validate a tensor payload against its directory entry (roadmap
796/// §4.9): every length is checked BEFORE any slice is taken, so a
797/// corrupted or truncated file fails loudly at `open()` instead of
798/// panicking in a kernel. For fixed-size dtypes this is the
799/// `expected_nbytes` equality; for vbit — whose payload length depends
800/// on the per-row bit widths stored in the payload itself — the exact
801/// length is computed from the (validated) width header.
802pub fn validate_payload(dtype: TensorDtype, shape: &[usize], bytes: &[u8]) -> Result<(), String> {
803    if dtype == TensorDtype::BinaryLowRank {
804        // Self-describing variable length: the header carries the rank.
805        return crate::blr::validate_blr(shape, bytes).map(|_| ());
806    }
807    if dtype == TensorDtype::Q4TiledP {
808        // Say WHY the shape is wrong; the generic length check below cannot,
809        // since expected_nbytes has to fold both causes into None.
810        if shape.len() != 2 {
811            return Err(format!("q4tp tensor must be 2-D, got {shape:?}"));
812        }
813        if shape[1] == 0 || shape[1] % GROUP_SIZE != 0 {
814            return Err(format!(
815                "q4tp cols {} not a positive multiple of {GROUP_SIZE}",
816                shape[1]
817            ));
818        }
819    }
820    if dtype == TensorDtype::Q1G {
821        if shape.len() != 2 {
822            return Err(format!("q1g tensor must be 2-D, got {shape:?}"));
823        }
824        if shape[1] == 0 || shape[1] % Q1G_GROUP != 0 {
825            return Err(format!(
826                "q1g cols {} not a positive multiple of {Q1G_GROUP}",
827                shape[1]
828            ));
829        }
830    }
831    if dtype == TensorDtype::Q2TiledP {
832        if shape.len() != 2 {
833            return Err(format!("q2tp tensor must be 2-D, got {shape:?}"));
834        }
835        if shape[1] == 0 || shape[1] % GROUP_SIZE != 0 {
836            return Err(format!(
837                "q2tp cols {} not a positive multiple of {GROUP_SIZE}",
838                shape[1]
839            ));
840        }
841    }
842    if dtype == TensorDtype::VbitRo {
843        if shape.len() != 2 {
844            return Err(format!("vbit_ro tensor must be 2-D, got {shape:?}"));
845        }
846        let (rows, cols) = (shape[0], shape[1]);
847        if cols == 0 || cols % GROUP_SIZE != 0 {
848            return Err(format!(
849                "vbit_ro cols {cols} not a positive multiple of {GROUP_SIZE}"
850            ));
851        }
852        let (_, off_off, packed_off) = vbit_ro_sections(rows, cols);
853        if bytes.len() < packed_off {
854            return Err(format!(
855                "vbit_ro payload {} bytes cannot hold headers ({packed_off})",
856                bytes.len()
857            ));
858        }
859        if vbit_ro_offset(bytes, off_off, 0) != 0 {
860            return Err("vbit_ro offsets[0] must be 0".to_string());
861        }
862        for r in 0..rows {
863            let b = bytes[r];
864            if !matches!(b, 3..=6 | 8) {
865                return Err(format!(
866                    "vbit_ro row {r}: bit width {b} outside {{3,4,5,6,8}}"
867                ));
868            }
869            let want = (cols * b as usize).div_ceil(8);
870            let got = vbit_ro_offset(bytes, off_off, r + 1)
871                .checked_sub(vbit_ro_offset(bytes, off_off, r))
872                .ok_or_else(|| format!("vbit_ro offsets not monotonic at row {r}"))?;
873            if want != got {
874                return Err(format!(
875                    "vbit_ro row {r}: offset span {got} != {want} for width {b}"
876                ));
877            }
878        }
879        let total = packed_off + vbit_ro_offset(bytes, off_off, rows);
880        if total != bytes.len() {
881            return Err(format!(
882                "vbit_ro payload length {} != computed {total}",
883                bytes.len()
884            ));
885        }
886        return Ok(());
887    }
888    if dtype == TensorDtype::Vbit {
889        if shape.len() != 2 {
890            return Err(format!("vbit tensor must be 2-D, got {shape:?}"));
891        }
892        let (rows, cols) = (shape[0], shape[1]);
893        if cols == 0 || cols % GROUP_SIZE != 0 {
894            return Err(format!(
895                "vbit cols {cols} not a positive multiple of {GROUP_SIZE}"
896            ));
897        }
898        // bits header: bounds BEFORE slicing.
899        if bytes.len() < rows {
900            return Err(format!(
901                "vbit payload {} bytes cannot hold the {rows}-byte width header",
902                bytes.len()
903            ));
904        }
905        let ng = cols / GROUP_SIZE;
906        let mut total = rows + rows * ng * 2;
907        for (r, &b) in bytes[..rows].iter().enumerate() {
908            if !matches!(b, 3..=6 | 8) {
909                return Err(format!("vbit row {r}: bit width {b} outside {{3,4,5,6,8}}"));
910            }
911            total += (cols * b as usize).div_ceil(8);
912        }
913        if total != bytes.len() {
914            return Err(format!(
915                "vbit payload length {} != computed {} (rows {rows}, cols {cols})",
916                bytes.len(),
917                total
918            ));
919        }
920        return Ok(());
921    }
922    if has_fixed_payload_size(dtype) && expected_nbytes(dtype, shape).is_none() {
923        return Err(format!(
924            "tensor size overflows usize for {dtype:?}{shape:?}"
925        ));
926    }
927    if let Some(expect) = expected_nbytes(dtype, shape) {
928        if expect != bytes.len() {
929            return Err(format!(
930                "payload length {} != expected {expect} for {dtype:?}{shape:?}",
931                bytes.len()
932            ));
933        }
934    }
935    Ok(())
936}
937
938/// Dequantize any supported tensor into f32.
939pub fn dequant_tensor(entry: &TensorEntry, bytes: &[u8], dst: &mut [f32]) -> Result<(), String> {
940    let n: usize = entry.shape.iter().product();
941    if dst.len() != n {
942        return Err(format!(
943            "dst len {} != tensor elems {} for '{}'",
944            dst.len(),
945            n,
946            entry.name
947        ));
948    }
949    match entry.dtype {
950        TensorDtype::F32 => {
951            for (i, d) in dst.iter_mut().enumerate() {
952                *d = f32::from_le_bytes(bytes[i * 4..i * 4 + 4].try_into().unwrap());
953            }
954        }
955        TensorDtype::F16 => {
956            for (i, d) in dst.iter_mut().enumerate() {
957                *d = f16_to_f32(u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]));
958            }
959        }
960        TensorDtype::Bf16 => {
961            for (i, d) in dst.iter_mut().enumerate() {
962                *d = bf16_to_f32(u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]));
963            }
964        }
965        TensorDtype::Q8Row => {
966            if entry.shape.len() != 2 {
967                return Err(format!("q8_row tensor '{}' must be 2-D", entry.name));
968            }
969            dequant_q8_row(bytes, entry.shape[0], entry.shape[1], dst);
970        }
971        TensorDtype::Q4Block => dequant_q4_block(bytes, dst),
972        TensorDtype::Q4Tiled => dequant_q4_tiled(bytes, dst),
973        TensorDtype::Q4TiledP => {
974            if entry.shape.len() != 2 {
975                return Err(format!("q4tp tensor '{}' must be 2-D", entry.name));
976            }
977            dequant_q4tp(bytes, entry.shape[0], entry.shape[1], dst);
978        }
979        TensorDtype::Q2TiledP => {
980            if entry.shape.len() != 2 {
981                return Err(format!("q2tp tensor '{}' must be 2-D", entry.name));
982            }
983            dequant_q2tp(bytes, entry.shape[0], entry.shape[1], dst);
984        }
985        TensorDtype::Q1 => dequant_q1(bytes, dst),
986        TensorDtype::Q1G => {
987            if entry.shape.len() != 2 || entry.shape[1] % Q1G_GROUP != 0 {
988                return Err(format!(
989                    "q1g tensor '{}' must be 2-D with cols % {Q1G_GROUP} == 0",
990                    entry.name
991                ));
992            }
993            let need = expected_nbytes(TensorDtype::Q1G, &entry.shape).unwrap_or(usize::MAX);
994            if bytes.len() < need {
995                return Err(format!(
996                    "q1g tensor '{}': payload {} < {need} bytes",
997                    entry.name,
998                    bytes.len()
999                ));
1000            }
1001            dequant_q1g(bytes, entry.shape[0], entry.shape[1], dst);
1002        }
1003        TensorDtype::Q1S => dequant_q1s(bytes, dst),
1004        TensorDtype::Q1T => {
1005            if entry.shape.len() != 2 {
1006                return Err(format!("q1t tensor '{}' must be 2-D", entry.name));
1007            }
1008            dequant_q1t(bytes, entry.shape[0], entry.shape[1], dst);
1009        }
1010        TensorDtype::BinaryLowRank => {
1011            if entry.shape.len() != 2 {
1012                return Err(format!("blr tensor '{}' must be 2-D", entry.name));
1013            }
1014            crate::blr::dequant_blr(bytes, entry.shape[0], entry.shape[1], dst)
1015                .map_err(|e| format!("'{}': {e}", entry.name))?;
1016        }
1017        TensorDtype::Vbit => {
1018            if entry.shape.len() != 2 {
1019                return Err(format!("vbit tensor '{}' must be 2-D", entry.name));
1020            }
1021            dequant_vbit(bytes, entry.shape[0], entry.shape[1], dst)?;
1022        }
1023        TensorDtype::VbitRo => {
1024            if entry.shape.len() != 2 {
1025                return Err(format!("vbit_ro tensor '{}' must be 2-D", entry.name));
1026            }
1027            dequant_vbit_ro(bytes, entry.shape[0], entry.shape[1], dst)?;
1028        }
1029        TensorDtype::Q8_2f => {
1030            if entry.shape.len() != 2 {
1031                return Err(format!("q8_2f tensor '{}' must be 2-D", entry.name));
1032            }
1033            dequant_q8_2f(bytes, entry.shape[0], entry.shape[1], dst);
1034        }
1035        other => {
1036            return Err(format!(
1037                "dtype {} of '{}' is reserved — not decodable by this runtime",
1038                other.name(),
1039                entry.name
1040            ));
1041        }
1042    }
1043    Ok(())
1044}
1045
1046/// Approximate stored bytes per weight for a dtype (informational).
1047pub fn bytes_per_weight(dtype: TensorDtype) -> f32 {
1048    match dtype {
1049        TensorDtype::F32 => 4.0,
1050        TensorDtype::F16 | TensorDtype::Bf16 => 2.0,
1051        TensorDtype::Q8Row | TensorDtype::Q8_2f => 1.0,
1052        TensorDtype::Q4Block | TensorDtype::Q4Col | TensorDtype::Mix84 | TensorDtype::Q4Tiled => {
1053            0.5625
1054        }
1055        // 16 B nibbles + 5 bits of scale per 32 weights; the 4 B/row of
1056        // ladder params are shape-dependent and not counted here.
1057        TensorDtype::Q4TiledP => 0.519_531_25,
1058        // 8 B chunks + the same 5-bit rung per 32 weights.
1059        TensorDtype::Q2TiledP => 0.269_531_25,
1060        TensorDtype::Vbit | TensorDtype::VbitRo => 0.5,
1061        TensorDtype::Q1 => 0.1875, // 6 bytes per 32 weights
1062        TensorDtype::Q1G => 0.140_625, // 18 bytes per 128 weights
1063        // q1 base + a small sparse f16 overlay (informational; the true
1064        // size is the stored span, which grows with the outlier budget).
1065        TensorDtype::Q1S => 0.3125,
1066        TensorDtype::Q1T => 0.281_25, // 9 bytes per 32 weights (base-3 packed)
1067        TensorDtype::U8 => 1.0,
1068        TensorDtype::U32 => 4.0,
1069        TensorDtype::U64 => 8.0,
1070        // Shape- and rank-dependent (`blr::blr_bits` is exact); ~0.55
1071        // bits/weight, informational only.
1072        TensorDtype::BinaryLowRank => 0.07,
1073    }
1074}
1075
1076#[cfg(test)]
1077mod f16_tests {
1078    use super::{GROUP_SIZE, f16_to_f32, f32_to_f16, validate_payload};
1079
1080    #[test]
1081    fn f16_subnormals_decode_correctly() {
1082        // Smallest positive subnormal: 2^-24.
1083        assert!((f16_to_f32(0x0001) - 5.9604645e-8).abs() < 1e-12);
1084        // Largest subnormal: 1023 * 2^-24.
1085        assert!((f16_to_f32(0x03FF) - 6.097_555e-5).abs() < 1e-9);
1086        // The value that exposed the halving bug (mant=299, subnormal).
1087        assert!((f16_to_f32(0x812b) - -1.7821789e-5).abs() < 1e-9);
1088        // Smallest normal (boundary) still correct: 2^-14.
1089        assert!((f16_to_f32(0x0400) - 6.1035156e-5).abs() < 1e-9);
1090    }
1091
1092    #[test]
1093    fn f16_roundtrip_including_subnormals() {
1094        for &v in &[
1095            0.0f32, 1.0, -2.5, 6.097e-5, 3.0e-5, 5.96e-8, -1.782e-5, 65504.0,
1096        ] {
1097            let back = f16_to_f32(f32_to_f16(v));
1098            let tol = (v.abs() * 1e-3).max(1e-9);
1099            assert!((back - v).abs() <= tol, "roundtrip {v} -> {back}");
1100        }
1101    }
1102    /// §4.9: vbit payload validation — exact length from the width
1103    /// header, bounds before any slice, width whitelist.
1104    #[test]
1105    fn validate_payload_vbit_contract() {
1106        use crate::types::TensorDtype as D;
1107        let (rows, cols) = (3usize, 64usize);
1108        let ng = cols / GROUP_SIZE;
1109        let bits = [4u8, 3, 8];
1110        let mut good = bits.to_vec();
1111        good.extend(std::iter::repeat_n(0u8, rows * ng * 2)); // scales
1112        for &b in &bits {
1113            good.extend(std::iter::repeat_n(0u8, (cols * b as usize).div_ceil(8)));
1114        }
1115        assert!(validate_payload(D::Vbit, &[rows, cols], &good).is_ok());
1116
1117        // Truncated: shorter than the width header itself.
1118        assert!(validate_payload(D::Vbit, &[rows, cols], &good[..2]).is_err());
1119        // One byte short / one byte long.
1120        assert!(validate_payload(D::Vbit, &[rows, cols], &good[..good.len() - 1]).is_err());
1121        let mut long = good.clone();
1122        long.push(0);
1123        assert!(validate_payload(D::Vbit, &[rows, cols], &long).is_err());
1124        // Forbidden width (7).
1125        let mut bad = good.clone();
1126        bad[0] = 7;
1127        assert!(validate_payload(D::Vbit, &[rows, cols], &bad).is_err());
1128        // Non-2D / non-multiple-of-group cols.
1129        assert!(validate_payload(D::Vbit, &[rows * cols], &good).is_err());
1130        assert!(validate_payload(D::Vbit, &[rows, 33], &good).is_err());
1131
1132        // Fixed-size dtype goes through expected_nbytes.
1133        let q8 = vec![0u8; 2 * 8 + 2 * 2];
1134        assert!(validate_payload(D::Q8Row, &[2, 8], &q8).is_ok());
1135        assert!(validate_payload(D::Q8Row, &[2, 8], &q8[..q8.len() - 1]).is_err());
1136    }
1137
1138    /// §4.9: vbit is a first-class supported dtype.
1139    #[test]
1140    fn vbit_is_supported() {
1141        assert!(crate::types::TensorDtype::Vbit.is_supported());
1142    }
1143}
1144
1145#[cfg(test)]
1146mod q1g_tests {
1147    use super::*;
1148
1149    /// Deterministic xorshift — the tests need reproducible "real-like"
1150    /// payloads, not a crate dependency.
1151    fn rng(state: &mut u64) -> u64 {
1152        *state ^= *state << 13;
1153        *state ^= *state >> 7;
1154        *state ^= *state << 17;
1155        *state
1156    }
1157
1158    /// A Bonsai-like q1 payload: one bf16-representable magnitude per
1159    /// 128-group (log-spread over ~3 octaves like the published model's
1160    /// scales), written into all four 32-tiles as f16, random sign bits.
1161    fn bonsai_like_q1(rows: usize, cols: usize, seed: u64) -> Vec<u8> {
1162        let mut st = seed | 1;
1163        let gpr = cols / GROUP_SIZE;
1164        let mut q = vec![0u8; rows * gpr * Q1_TILE];
1165        for g in 0..rows * cols / Q1G_GROUP {
1166            let e = (rng(&mut st) % 3000) as f32 / 1000.0;
1167            let s = 0.004 * 2f32.powf(e);
1168            // bf16 → f16: a bf16 value in this range is exact in f16.
1169            let s = f32::from_bits(s.to_bits() & 0xFFFF_0000);
1170            let h = f32_to_f16(s);
1171            assert_eq!(f16_to_f32(h), s, "bf16 scale must be f16-exact");
1172            for t in 0..4 {
1173                let o = (g * 4 + t) * Q1_TILE;
1174                q[o..o + 2].copy_from_slice(&h.to_le_bytes());
1175                let bits = rng(&mut st) as u32;
1176                q[o + 2..o + 6].copy_from_slice(&bits.to_le_bytes());
1177            }
1178        }
1179        q
1180    }
1181
1182    #[test]
1183    fn q1g_dtype_identity() {
1184        let d = TensorDtype::Q1G;
1185        assert_eq!(d.id(), 20);
1186        assert_eq!(TensorDtype::from_id(20), Some(d));
1187        assert_eq!(d.name(), "q1g");
1188        assert!(d.is_supported());
1189        assert!(!d.is_raw());
1190        assert_eq!(serde_json::to_string(&d).unwrap(), "\"q1g\"");
1191    }
1192
1193    #[test]
1194    fn q1g_size_and_validation() {
1195        let (rows, cols) = (6usize, 384usize);
1196        let n = rows * cols;
1197        // 1.125 bits/weight exactly: n/8 sign bytes + n/128 f16 scales.
1198        assert_eq!(
1199            expected_nbytes(TensorDtype::Q1G, &[rows, cols]),
1200            Some(n * 9 / 64)
1201        );
1202        assert_eq!(
1203            expected_nbytes(TensorDtype::Q1G, &[rows, cols]).unwrap() * 4,
1204            expected_nbytes(TensorDtype::Q1, &[rows, cols]).unwrap() * 3,
1205            "q1g is exactly 3/4 of q1"
1206        );
1207        assert_eq!(expected_nbytes(TensorDtype::Q1G, &[rows, 96]), None);
1208        assert_eq!(expected_nbytes(TensorDtype::Q1G, &[n]), None);
1209        let good = vec![0u8; n * 9 / 64];
1210        assert!(validate_payload(TensorDtype::Q1G, &[rows, cols], &good).is_ok());
1211        assert!(validate_payload(TensorDtype::Q1G, &[rows, cols], &good[1..]).is_err());
1212        let e = validate_payload(TensorDtype::Q1G, &[rows * 3, 128], &good[..1]).unwrap_err();
1213        assert!(e.contains("payload length"), "{e}");
1214        let e = validate_payload(TensorDtype::Q1G, &[rows, 96], &good).unwrap_err();
1215        assert!(e.contains("multiple of 128"), "{e}");
1216        let e = validate_payload(TensorDtype::Q1G, &[n], &good).unwrap_err();
1217        assert!(e.contains("2-D"), "{e}");
1218    }
1219
1220    /// The point of the dtype: a q1 tensor whose four tile scales agree in
1221    /// every 128-group repacks to q1g with NO change at all — same sign
1222    /// bits byte for byte, same decoded f32 bit for bit.
1223    #[test]
1224    fn uniform_q1_repacks_bit_exactly() {
1225        for &(rows, cols) in &[(1usize, 128usize), (7, 256), (33, 2048), (4, 6144)] {
1226            let q1 = bonsai_like_q1(rows, cols, (rows * 131 + cols) as u64);
1227            let rp = repack_q1_to_q1g(&q1, rows, cols, 0.0);
1228            assert_eq!(rp.groups, rows * cols / Q1G_GROUP);
1229            assert_eq!((rp.differing, rp.over_tol, rp.max_rel), (0, 0, 0.0));
1230            assert_eq!(
1231                rp.data.len(),
1232                expected_nbytes(TensorDtype::Q1G, &[rows, cols]).unwrap()
1233            );
1234            assert!(validate_payload(TensorDtype::Q1G, &[rows, cols], &rp.data).is_ok());
1235            // The sign plane IS the tiles' bit fields, concatenated.
1236            for t in 0..rows * cols / GROUP_SIZE {
1237                assert_eq!(
1238                    &rp.data[t * 4..t * 4 + 4],
1239                    &q1[t * Q1_TILE + 2..t * Q1_TILE + 6]
1240                );
1241            }
1242            let (mut a, mut b) = (vec![0f32; rows * cols], vec![0f32; rows * cols]);
1243            dequant_q1(&q1, &mut a);
1244            dequant_q1g(&rp.data, rows, cols, &mut b);
1245            assert!(a.iter().zip(&b).all(|(x, y)| x.to_bits() == y.to_bits()));
1246            // …and through the generic entry point.
1247            let entry = TensorEntry {
1248                name: "w".into(),
1249                dtype: TensorDtype::Q1G,
1250                shape: vec![rows, cols],
1251                off: 0,
1252                nbytes: rp.data.len() as u64,
1253                hash: 0,
1254                shard: 0,
1255            };
1256            let mut c = vec![0f32; rows * cols];
1257            dequant_tensor(&entry, &rp.data, &mut c).unwrap();
1258            assert_eq!(b, c);
1259            // …and back: the q1 bytes themselves, exactly.
1260            assert_eq!(unpack_q1g_to_q1(&rp.data, rows, cols), q1);
1261        }
1262    }
1263
1264    /// A group whose tiles disagree by f16 rounding (the 393 of 13.4 M on
1265    /// Bonsai-1.7B) is counted, gets the f16 nearest the tiles' mean, and is
1266    /// the ONLY place the decoded weights move.
1267    #[test]
1268    fn differing_groups_are_counted_and_confined() {
1269        let (rows, cols) = (8usize, 512usize);
1270        let mut q1 = bonsai_like_q1(rows, cols, 99);
1271        let bumped = [3usize, 17, 30];
1272        for &g in &bumped {
1273            let o = (g * 4 + 2) * Q1_TILE; // third tile of the group
1274            let h = u16::from_le_bytes([q1[o], q1[o + 1]]) + 1; // one f16 ulp
1275            q1[o..o + 2].copy_from_slice(&h.to_le_bytes());
1276        }
1277        let strict = repack_q1_to_q1g(&q1, rows, cols, 0.0);
1278        assert_eq!(strict.differing, bumped.len());
1279        assert_eq!(
1280            strict.over_tol,
1281            bumped.len(),
1282            "tol 0 refuses every differing group"
1283        );
1284        assert!(
1285            strict.max_rel > 0.0 && strict.max_rel < 2f64.powi(-9),
1286            "{}",
1287            strict.max_rel
1288        );
1289        let loose = repack_q1_to_q1g(&q1, rows, cols, 2f64.powi(-8));
1290        assert_eq!((loose.differing, loose.over_tol), (bumped.len(), 0));
1291        assert_eq!(
1292            loose.data, strict.data,
1293            "the tolerance only decides, never encodes"
1294        );
1295        let (mut a, mut b) = (vec![0f32; rows * cols], vec![0f32; rows * cols]);
1296        dequant_q1(&q1, &mut a);
1297        dequant_q1g(&loose.data, rows, cols, &mut b);
1298        // q1g → q1 decodes to the q1g weights (not the source's) bit for bit.
1299        let mut back = vec![0f32; rows * cols];
1300        dequant_q1(&unpack_q1g_to_q1(&loose.data, rows, cols), &mut back);
1301        assert!(back.iter().zip(&b).all(|(x, y)| x.to_bits() == y.to_bits()));
1302        for g in 0..rows * cols / Q1G_GROUP {
1303            let (wa, wb) = (&a[g * 128..(g + 1) * 128], &b[g * 128..(g + 1) * 128]);
1304            if bumped.contains(&g) {
1305                // Same signs; one magnitude for the whole group, within the
1306                // spread of the four tile scales.
1307                let s = wb[0].abs();
1308                assert!(wb.iter().all(|v| v.abs() == s));
1309                assert!(wa.iter().zip(wb).all(|(x, y)| x.signum() == y.signum()));
1310                let (lo, hi) = wa.iter().fold((f32::MAX, 0f32), |(l, h), v| {
1311                    (l.min(v.abs()), h.max(v.abs()))
1312                });
1313                assert!(s >= lo && s <= hi, "{s} outside [{lo}, {hi}]");
1314            } else {
1315                assert!(wa.iter().zip(wb).all(|(x, y)| x.to_bits() == y.to_bits()));
1316            }
1317        }
1318    }
1319
1320    /// A scale that is zero in one tile and not in the others is not one
1321    /// group: the zero tile sits a whole representative away (rel ≥ 1),
1322    /// far outside any f16-rounding tolerance.
1323    #[test]
1324    fn zero_vs_nonzero_tile_is_never_within_tolerance() {
1325        let (rows, cols) = (1usize, 128usize);
1326        let mut q1 = bonsai_like_q1(rows, cols, 5);
1327        q1[0..2].copy_from_slice(&0u16.to_le_bytes());
1328        let rp = repack_q1_to_q1g(&q1, rows, cols, 0.5);
1329        assert_eq!((rp.differing, rp.over_tol), (1, 1));
1330        assert!(rp.max_rel >= 1.0, "{}", rp.max_rel);
1331        // All four zero is a uniform group like any other.
1332        for t in 0..4 {
1333            q1[t * Q1_TILE..t * Q1_TILE + 2].copy_from_slice(&0u16.to_le_bytes());
1334        }
1335        let rp = repack_q1_to_q1g(&q1, rows, cols, 0.0);
1336        assert_eq!((rp.differing, rp.over_tol), (0, 0));
1337    }
1338}