#![cfg(table_format = "q16_16")]
use crate::fixed_point::frac_config::FRAC_BITS;
use rayon::prelude::*;
pub const TQ5_MAX: i8 = 121;
const Q2F_SHIFT: u32 = 32 - FRAC_BITS;
#[derive(Clone)]
pub struct RowScaledTQ5 {
rows: usize,
cols: usize,
data: Vec<i8>,
scales_q32: Vec<u64>,
}
struct SplitActivation {
lo: Vec<i16>,
hi: Vec<i16>,
hi_nz: Vec<(u32, i16)>,
}
impl SplitActivation {
fn new(x: &[i32]) -> Option<Self> {
if x.iter().any(|&v| v.unsigned_abs() >= 1 << 30) {
return None;
}
let lo: Vec<i16> = x.iter().map(|&v| v as i16).collect(); let hi: Vec<i16> = x
.iter()
.zip(&lo)
.map(|(&v, &l)| ((v - l as i32) >> 16) as i16)
.collect();
let hi_nz = hi
.iter()
.enumerate()
.filter(|(_, &h)| h != 0)
.map(|(i, &h)| (i as u32, h))
.collect();
Some(Self { lo, hi, hi_nz })
}
#[inline]
fn sparse(&self) -> bool {
self.hi_nz.len() * 8 <= self.lo.len()
}
#[inline]
fn hi_correction(&self, row: &[i8]) -> i64 {
let mut s = 0i64;
for &(i, h) in &self.hi_nz {
s += row[i as usize] as i64 * h as i64;
}
s << 16
}
}
fn avx2_available() -> bool {
#[cfg(target_arch = "x86_64")]
{
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| std::is_x86_feature_detected!("avx2"))
}
#[cfg(not(target_arch = "x86_64"))]
{
false
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn dot_split_avx2(row: &[i8], xs: &SplitActivation) -> i64 {
use std::arch::x86_64::*;
let n = row.len();
let (lo, hi) = (&xs.lo, &xs.hi);
let mut total: i64 = 0;
let mut i = 0usize;
let vec_end = n / 16 * 16;
while i < vec_end {
let block_end = (i + 64 * 16).min(vec_end);
let mut acc_lo = _mm256_setzero_si256();
let mut acc_hi = _mm256_setzero_si256();
while i < block_end {
let c = _mm256_cvtepi8_epi16(_mm_loadu_si128(row.as_ptr().add(i) as *const __m128i));
let l = _mm256_loadu_si256(lo.as_ptr().add(i) as *const __m256i);
let h = _mm256_loadu_si256(hi.as_ptr().add(i) as *const __m256i);
acc_lo = _mm256_add_epi32(acc_lo, _mm256_madd_epi16(c, l));
acc_hi = _mm256_add_epi32(acc_hi, _mm256_madd_epi16(c, h));
i += 16;
}
let mut lanes = [0i32; 8];
_mm256_storeu_si256(lanes.as_mut_ptr() as *mut __m256i, acc_lo);
let sum_lo: i64 = lanes.iter().map(|&v| v as i64).sum();
_mm256_storeu_si256(lanes.as_mut_ptr() as *mut __m256i, acc_hi);
let sum_hi: i64 = lanes.iter().map(|&v| v as i64).sum();
total += sum_lo + (sum_hi << 16);
}
for k in vec_end..n {
total += row[k] as i64 * (lo[k] as i64 + ((hi[k] as i64) << 16));
}
total
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn dot_lo_avx2(row: &[i8], lo: &[i16]) -> i64 {
use std::arch::x86_64::*;
let n = row.len();
let mut total: i64 = 0;
let mut i = 0usize;
let vec_end = n / 16 * 16;
while i < vec_end {
let block_end = (i + 64 * 16).min(vec_end);
let mut acc = _mm256_setzero_si256();
while i < block_end {
let c = _mm256_cvtepi8_epi16(_mm_loadu_si128(row.as_ptr().add(i) as *const __m128i));
let l = _mm256_loadu_si256(lo.as_ptr().add(i) as *const __m256i);
acc = _mm256_add_epi32(acc, _mm256_madd_epi16(c, l));
i += 16;
}
let mut lanes = [0i32; 8];
_mm256_storeu_si256(lanes.as_mut_ptr() as *mut __m256i, acc);
total += lanes.iter().map(|&v| v as i64).sum::<i64>();
}
for k in vec_end..n {
total += row[k] as i64 * lo[k] as i64;
}
total
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn tile_lo_4x4(rows: [&[i8]; 4], los: [&[i16]; 4], n: usize) -> [[i64; 4]; 4] {
use std::arch::x86_64::*;
let vec_end = n / 16 * 16;
let mut i = 0usize;
let mut total = [[0i64; 4]; 4];
while i < vec_end {
let block_end = (i + 64 * 16).min(vec_end);
let mut acc = [[_mm256_setzero_si256(); 4]; 4];
while i < block_end {
let mut c = [_mm256_setzero_si256(); 4];
for r in 0..4 {
c[r] = _mm256_cvtepi8_epi16(_mm_loadu_si128(
rows[r].as_ptr().add(i) as *const __m128i
));
}
for t in 0..4 {
let l = _mm256_loadu_si256(los[t].as_ptr().add(i) as *const __m256i);
for r in 0..4 {
acc[r][t] = _mm256_add_epi32(acc[r][t], _mm256_madd_epi16(c[r], l));
}
}
i += 16;
}
let mut lanes = [0i32; 8];
for r in 0..4 {
for t in 0..4 {
_mm256_storeu_si256(lanes.as_mut_ptr() as *mut __m256i, acc[r][t]);
total[r][t] += lanes.iter().map(|&v| v as i64).sum::<i64>();
}
}
}
for k in vec_end..n {
for r in 0..4 {
for t in 0..4 {
total[r][t] += rows[r][k] as i64 * los[t][k] as i64;
}
}
}
total
}
impl RowScaledTQ5 {
pub fn from_parts(rows: usize, cols: usize, data: Vec<i8>, scales_q32: Vec<u64>) -> Self {
assert_eq!(
data.len(),
rows * cols,
"RowScaledTQ5: data length mismatch"
);
assert_eq!(
scales_q32.len(),
rows,
"RowScaledTQ5: scales length mismatch"
);
assert!(data.iter().all(|&w| w != i8::MIN && w.abs() <= TQ5_MAX), "RowScaledTQ5: code outside [-121, 121]");
assert!(cols < 1 << 24, "RowScaledTQ5: more than 2^24 columns");
Self {
rows,
cols,
data,
scales_q32,
}
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn data(&self) -> &[i8] {
&self.data
}
pub fn scales_q32(&self) -> &[u64] {
&self.scales_q32
}
pub fn size_bytes(&self) -> usize {
self.data.len() + self.scales_q32.len() * 8
}
#[inline]
fn dot(row: &[i8], x: &[i32]) -> i64 {
let n = row.len() / 16 * 16;
let blocks: i64 = row[..n]
.chunks_exact(16)
.zip(x[..n].chunks_exact(16))
.map(|(r, v)| {
let mut s = 0i64;
for k in 0..16 {
s += (r[k] as i32 as i64) * (v[k] as i64);
}
s
})
.sum();
blocks
+ row[n..]
.iter()
.zip(&x[n..])
.map(|(&c, &v)| c as i64 * v as i64)
.sum::<i64>()
}
#[inline(always)]
fn dot_any(row: &[i8], x: &[i32], split: Option<&SplitActivation>) -> i64 {
#[cfg(target_arch = "x86_64")]
if let Some(xs) = split {
if avx2_available() {
return unsafe {
if xs.sparse() {
dot_lo_avx2(row, &xs.lo) + xs.hi_correction(row)
} else {
dot_split_avx2(row, xs)
}
};
}
}
Self::dot(row, x)
}
#[inline(always)]
fn scale_row(acc: i64, s_q32: u64) -> i32 {
let scaled = (acc as i128 * s_q32 as i128) >> 32;
if scaled > i32::MAX as i128 || scaled < i32::MIN as i128 {
panic!("RowScaledTQ5: scaled output exceeds storage range");
}
scaled as i32
}
pub fn matvec(&self, x: &[i32]) -> Vec<i32> {
assert_eq!(x.len(), self.cols, "RowScaledTQ5::matvec: activation length mismatch");
let split = SplitActivation::new(x);
(0..self.rows)
.map(|r| {
let row = &self.data[r * self.cols..(r + 1) * self.cols];
Self::scale_row(Self::dot_any(row, x, split.as_ref()), self.scales_q32[r])
})
.collect()
}
pub fn matvec_par(&self, x: &[i32]) -> Vec<i32> {
assert_eq!(
x.len(),
self.cols,
"RowScaledTQ5::matvec_par: activation length mismatch"
);
let split = SplitActivation::new(x);
(0..self.rows)
.into_par_iter()
.map(|r| {
let row = &self.data[r * self.cols..(r + 1) * self.cols];
Self::scale_row(Self::dot_any(row, x, split.as_ref()), self.scales_q32[r])
})
.collect()
}
pub fn matvec_batch_par(&self, batch: &[&[i32]]) -> Vec<Vec<i32>> {
let rows = self.rows;
let mut flat = vec![0i32; batch.len() * rows];
self.matvec_batch_par_into(batch, &mut flat);
if rows == 0 {
return vec![Vec::new(); batch.len()];
}
flat.chunks_exact(rows).map(<[i32]>::to_vec).collect()
}
pub fn matvec_batch_par_into(&self, batch: &[&[i32]], out: &mut [i32]) {
for x in batch {
assert_eq!(
x.len(),
self.cols,
"RowScaledTQ5::matvec_batch_par: activation length mismatch"
);
}
let (rows, cols) = (self.rows, self.cols);
let sink = super::ops::BatchOut::new(out, rows, batch.len(), "RowScaledTQ5::matvec_batch_par_into");
let splits: Vec<Option<SplitActivation>> =
batch.iter().map(|x| SplitActivation::new(x)).collect();
let tile_ok = cfg!(target_arch = "x86_64") && avx2_available();
let (fast, slow): (Vec<usize>, Vec<usize>) = (0..batch.len())
.partition(|&k| tile_ok && splits[k].as_ref().is_some_and(|s| s.sparse()));
let row = |r: usize| &self.data[r * cols..(r + 1) * cols];
let groups = rows / 4;
let chunk = ((256 * 1024) / (2 * cols.max(1))).clamp(4, 64) / 4 * 4;
for toks in fast.chunks(chunk) {
(0..groups).into_par_iter().for_each(|g| {
let r0 = g * 4;
let rs = [row(r0), row(r0 + 1), row(r0 + 2), row(r0 + 3)];
for blk in toks.chunks(4) {
if blk.len() < 4 {
for &k in blk {
for r in 0..4 {
let v = Self::scale_row(
Self::dot_any(rs[r], batch[k], splits[k].as_ref()),
self.scales_q32[r0 + r],
);
unsafe { sink.write(k, r0 + r, v) };
}
}
} else {
let sp = [
splits[blk[0]].as_ref().unwrap(),
splits[blk[1]].as_ref().unwrap(),
splits[blk[2]].as_ref().unwrap(),
splits[blk[3]].as_ref().unwrap(),
];
#[cfg(target_arch = "x86_64")]
let acc = unsafe {
tile_lo_4x4(rs, [&sp[0].lo, &sp[1].lo, &sp[2].lo, &sp[3].lo], cols)
};
#[cfg(not(target_arch = "x86_64"))]
let acc = [[0i64; 4]; 4];
for t in 0..4 {
for r in 0..4 {
let v = Self::scale_row(
acc[r][t] + sp[t].hi_correction(rs[r]),
self.scales_q32[r0 + r],
);
unsafe { sink.write(blk[t], r0 + r, v) };
}
}
}
}
});
for r in groups * 4..rows {
for &k in toks {
let v = Self::scale_row(
Self::dot_any(row(r), batch[k], splits[k].as_ref()),
self.scales_q32[r],
);
unsafe { sink.write(k, r, v) };
}
}
}
const TILE: usize = 8;
for tile in slow.chunks(TILE) {
(0..rows).into_par_iter().for_each(|r| {
for &k in tile {
let v = Self::scale_row(
Self::dot_any(row(r), batch[k], splits[k].as_ref()),
self.scales_q32[r],
);
unsafe { sink.write(k, r, v) };
}
});
}
}
#[inline(always)]
fn scale_row_q2f(acc: i64, s_q32: u64) -> i64 {
let wide = (acc as i128 * s_q32 as i128) >> Q2F_SHIFT;
if wide > i64::MAX as i128 || wide < i64::MIN as i128 {
panic!("RowScaledTQ5: q2f scaled output exceeds compute range");
}
wide as i64
}
pub fn matvec_q2f(&self, x: &[i32]) -> Vec<i64> {
assert_eq!(x.len(), self.cols, "RowScaledTQ5::matvec_q2f: activation length mismatch");
let split = SplitActivation::new(x);
(0..self.rows)
.map(|r| {
let row = &self.data[r * self.cols..(r + 1) * self.cols];
Self::scale_row_q2f(Self::dot_any(row, x, split.as_ref()), self.scales_q32[r])
})
.collect()
}
pub fn matvec_q2f_par(&self, x: &[i32]) -> Vec<i64> {
assert_eq!(
x.len(),
self.cols,
"RowScaledTQ5::matvec_q2f_par: activation length mismatch"
);
let split = SplitActivation::new(x);
(0..self.rows)
.into_par_iter()
.map(|r| {
let row = &self.data[r * self.cols..(r + 1) * self.cols];
Self::scale_row_q2f(Self::dot_any(row, x, split.as_ref()), self.scales_q32[r])
})
.collect()
}
pub fn write_to<W: std::io::Write>(&self, w: &mut W) -> std::io::Result<()> {
w.write_all(&(self.rows as u32).to_le_bytes())?;
w.write_all(&(self.cols as u32).to_le_bytes())?;
let bytes: Vec<u8> = self.data.iter().map(|&v| v as u8).collect();
w.write_all(&bytes)?;
for &s in &self.scales_q32 {
w.write_all(&s.to_le_bytes())?;
}
Ok(())
}
pub fn read_from<R: std::io::Read>(r: &mut R) -> std::io::Result<Self> {
let mut buf4 = [0u8; 4];
r.read_exact(&mut buf4)?;
let rows = u32::from_le_bytes(buf4) as usize;
r.read_exact(&mut buf4)?;
let cols = u32::from_le_bytes(buf4) as usize;
let len = rows.checked_mul(cols).ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "RowScaledTQ5: dimensions overflow"))?;
let mut bytes = vec![0u8; len];
r.read_exact(&mut bytes)?;
let data: Vec<i8> = bytes.into_iter().map(|b| b as i8).collect();
if data.iter().any(|&c| c == i8::MIN || c.abs() > TQ5_MAX) {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "RowScaledTQ5: code outside [-121, 121]"));
}
let mut scales = vec![0u64; rows];
let mut buf8 = [0u8; 8];
for s in scales.iter_mut() {
r.read_exact(&mut buf8)?;
*s = u64::from_le_bytes(buf8);
}
Ok(Self::from_parts(rows, cols, data, scales))
}
}