1use crate::format::TensorEntry;
13use crate::types::TensorDtype;
14
15pub const GROUP_SIZE: usize = 32;
16
17#[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 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#[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 return sign | 0x7C00 | if mant != 0 { 0x200 } else { 0 };
60 }
61 exp -= 127 - 15;
62 if exp >= 0x1F {
63 return sign | 0x7C00; }
65 if exp <= 0 {
66 if exp < -10 {
67 return sign; }
69 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 let sticky = (mant & 0xFFF) != 0;
80 let bump = round & (sticky as u32 | (half & 1));
81 sign | ((half + bump) as u16)
82}
83
84#[inline]
86pub fn bf16_to_f32(h: u16) -> f32 {
87 f32::from_bits((h as u32) << 16)
88}
89
90pub 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
105pub 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
128pub 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
152pub 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
193pub const Q4_TILE: usize = 18;
195
196pub const Q1_TILE: usize = 6;
198
199pub 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
221pub 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
250pub const Q1G_GROUP: usize = 128;
254pub const Q1G_BITS: usize = Q1G_GROUP / 8;
256
257#[inline]
261pub fn q1g_scale_offset(rows: usize, cols: usize) -> usize {
262 rows * (cols / 8)
263}
264
265#[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
272pub 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#[derive(Debug, Clone)]
292pub struct Q1gRepack {
293 pub data: Vec<u8>,
297 pub groups: usize,
299 pub differing: usize,
302 pub max_rel: f64,
306 pub over_tol: usize,
308}
309
310pub 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
379pub 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
398pub const Q1T_TILE: usize = 9;
403const Q1T_POW3: [u16; 5] = [1, 3, 9, 27, 81];
404
405#[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#[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
418pub 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 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
470pub 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
494pub const Q4TP_NIB: usize = 16;
497
498pub const Q4TP_LMAX: usize = 31;
500
501pub const fn q4tp_code_stride(gpr: usize) -> usize {
504 (gpr * 5).div_ceil(8)
505}
506
507pub 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#[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 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#[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#[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
560pub 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
581pub const Q2TP_CHUNK: usize = 8;
585
586pub 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
594pub const Q2TP_LMAX: usize = 30;
597
598pub 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
610pub 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
632pub 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#[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
652pub 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
699pub 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 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 n.div_ceil(GROUP_SIZE).checked_mul(18)
722 }
723 TensorDtype::Q1 => {
724 n.div_ceil(GROUP_SIZE).checked_mul(Q1_TILE)
726 }
727 TensorDtype::Q1G => {
728 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 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, }
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
795pub fn validate_payload(dtype: TensorDtype, shape: &[usize], bytes: &[u8]) -> Result<(), String> {
803 if dtype == TensorDtype::BinaryLowRank {
804 return crate::blr::validate_blr(shape, bytes).map(|_| ());
806 }
807 if dtype == TensorDtype::Q4TiledP {
808 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 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
938pub 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
1046pub 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 TensorDtype::Q4TiledP => 0.519_531_25,
1058 TensorDtype::Q2TiledP => 0.269_531_25,
1060 TensorDtype::Vbit | TensorDtype::VbitRo => 0.5,
1061 TensorDtype::Q1 => 0.1875, TensorDtype::Q1G => 0.140_625, TensorDtype::Q1S => 0.3125,
1066 TensorDtype::Q1T => 0.281_25, TensorDtype::U8 => 1.0,
1068 TensorDtype::U32 => 4.0,
1069 TensorDtype::U64 => 8.0,
1070 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 assert!((f16_to_f32(0x0001) - 5.9604645e-8).abs() < 1e-12);
1084 assert!((f16_to_f32(0x03FF) - 6.097_555e-5).abs() < 1e-9);
1086 assert!((f16_to_f32(0x812b) - -1.7821789e-5).abs() < 1e-9);
1088 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 #[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)); 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 assert!(validate_payload(D::Vbit, &[rows, cols], &good[..2]).is_err());
1119 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 let mut bad = good.clone();
1126 bad[0] = 7;
1127 assert!(validate_payload(D::Vbit, &[rows, cols], &bad).is_err());
1128 assert!(validate_payload(D::Vbit, &[rows * cols], &good).is_err());
1130 assert!(validate_payload(D::Vbit, &[rows, 33], &good).is_err());
1131
1132 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 #[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 fn rng(state: &mut u64) -> u64 {
1152 *state ^= *state << 13;
1153 *state ^= *state >> 7;
1154 *state ^= *state << 17;
1155 *state
1156 }
1157
1158 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 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 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 #[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 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 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 assert_eq!(unpack_q1g_to_q1(&rp.data, rows, cols), q1);
1261 }
1262 }
1263
1264 #[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; let h = u16::from_le_bytes([q1[o], q1[o + 1]]) + 1; 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 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 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 #[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 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}