use std::collections::HashMap;
const GGUF_MAGIC: u32 = 0x4655_4747;
const GGUF_DEFAULT_ALIGNMENT: usize = 32;
const QK_K: usize = 256;
const QK8_0: usize = 32;
#[inline]
pub(crate) const fn f16_to_f32(h: u16) -> f32 {
let sign = ((h >> 15) & 1) as u32;
let exponent = ((h >> 10) & 0x1f) as u32;
let mantissa = (h & 0x3ff) as u32;
if exponent == 0 {
if mantissa == 0 {
f32::from_bits(sign << 31)
} else {
let mut e = 0u32;
let mut m = mantissa;
while (m & 0x400) == 0 {
m <<= 1;
e += 1;
}
let exp = 127 - 15 - e + 1;
let man = (m & 0x3ff) << 13;
f32::from_bits((sign << 31) | (exp << 23) | man)
}
} else if exponent == 31 {
f32::from_bits((sign << 31) | (0xff << 23) | (mantissa << 13))
} else {
let exp = exponent + 127 - 15;
let man = mantissa << 13;
f32::from_bits((sign << 31) | (exp << 23) | man)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
pub enum GgmlType {
F32,
F16,
Q4_0,
Q4_1,
Q5_0,
Q5_1,
Q8_0,
Q2_K,
Q3_K,
Q4_K,
Q5_K,
Q6_K,
IQ4_XS,
Q1_0,
Q2_0,
Mxfp4,
IQ1_S,
Other(u32),
}
impl GgmlType {
const fn from_u32(v: u32) -> Self {
match v {
0 => Self::F32,
1 => Self::F16,
2 => Self::Q4_0,
3 => Self::Q4_1,
6 => Self::Q5_0,
7 => Self::Q5_1,
8 => Self::Q8_0,
10 => Self::Q2_K,
11 => Self::Q3_K,
12 => Self::Q4_K,
13 => Self::Q5_K,
14 => Self::Q6_K,
19 => Self::IQ1_S,
23 => Self::IQ4_XS,
39 => Self::Mxfp4,
41 => Self::Q1_0,
42 => Self::Q2_0,
other => Self::Other(other),
}
}
#[must_use]
pub const fn block_bytes(&self) -> usize {
match self {
Self::F32 => 4,
Self::F16 => 2,
Self::Q4_0 => 18,
Self::Q4_1 => 20,
Self::Q5_0 => 22,
Self::Q5_1 => 24,
Self::Q8_0 => 34,
Self::Q2_K => 84,
Self::Q3_K => 110,
Self::Q4_K => 144,
Self::Q5_K => 176,
Self::Q6_K => 210,
Self::IQ4_XS => 136,
Self::Q1_0 => 18,
Self::Q2_0 => 34,
Self::Mxfp4 => 17,
Self::IQ1_S => 50,
Self::Other(_) => 0,
}
}
#[must_use]
pub const fn elements_per_block(&self) -> usize {
match self {
Self::F32 | Self::F16 => 1,
Self::Q4_0 | Self::Q4_1 | Self::Q5_0 | Self::Q5_1 | Self::Q8_0 => QK8_0,
Self::Q2_K
| Self::Q3_K
| Self::Q4_K
| Self::Q5_K
| Self::Q6_K
| Self::IQ4_XS
| Self::IQ1_S => QK_K,
Self::Q1_0 | Self::Q2_0 => QK_G128,
Self::Mxfp4 => QK_MXFP4,
Self::Other(_) => 1,
}
}
}
pub const QK_G128: usize = 128;
pub const QK_MXFP4: usize = 32;
#[derive(Debug, Clone)]
pub enum MetaValue {
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
F32(f32),
U64(u64),
I64(i64),
F64(f64),
Bool(bool),
Str(String),
Array(Vec<Self>),
}
impl MetaValue {
pub const fn as_u32(&self) -> Option<u32> {
match self {
Self::U32(v) => Some(*v),
Self::I32(v) => Some(*v as u32),
Self::U64(v) => Some(*v as u32),
_ => None,
}
}
pub const fn as_f32(&self) -> Option<f32> {
match self {
Self::F32(v) => Some(*v),
Self::F64(v) => Some(*v as f32),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Str(s) => Some(s),
_ => None,
}
}
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
Self::U8(v) => Some(*v != 0),
Self::U32(v) => Some(*v != 0),
Self::I32(v) => Some(*v != 0),
_ => None,
}
}
pub fn as_str_array(&self) -> Option<Vec<&str>> {
match self {
Self::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for v in arr {
out.push(v.as_str()?);
}
Some(out)
}
_ => None,
}
}
pub fn as_u32_array(&self) -> Option<Vec<u32>> {
match self {
Self::Array(arr) => {
let mut out = Vec::with_capacity(arr.len());
for v in arr {
out.push(v.as_u32()?);
}
Some(out)
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct TensorInfo {
pub name: String,
pub n_dims: u32,
pub dims: Vec<u64>,
pub qtype: GgmlType,
pub offset: u64,
}
impl TensorInfo {
#[must_use]
pub fn n_elements(&self) -> usize {
self.dims.iter().product::<u64>() as usize
}
#[must_use]
pub fn data_size(&self) -> usize {
let n = self.n_elements();
let epb = self.qtype.elements_per_block();
let n_blocks = n.div_ceil(epb);
n_blocks * self.qtype.block_bytes()
}
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
const fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn read_bytes(&mut self, n: usize) -> Option<&'a [u8]> {
if self.pos + n > self.data.len() {
return None;
}
let slice = &self.data[self.pos..self.pos + n];
self.pos += n;
Some(slice)
}
fn read_u8(&mut self) -> Option<u8> {
let b = self.read_bytes(1)?;
Some(b[0])
}
fn read_i8(&mut self) -> Option<i8> {
Some(self.read_u8()? as i8)
}
fn read_u16(&mut self) -> Option<u16> {
let b = self.read_bytes(2)?;
Some(u16::from_le_bytes([b[0], b[1]]))
}
fn read_i16(&mut self) -> Option<i16> {
let b = self.read_bytes(2)?;
Some(i16::from_le_bytes([b[0], b[1]]))
}
fn read_u32(&mut self) -> Option<u32> {
let b = self.read_bytes(4)?;
Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn read_i32(&mut self) -> Option<i32> {
let b = self.read_bytes(4)?;
Some(i32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn read_u64(&mut self) -> Option<u64> {
let b = self.read_bytes(8)?;
Some(u64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn read_i64(&mut self) -> Option<i64> {
let b = self.read_bytes(8)?;
Some(i64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn read_f32(&mut self) -> Option<f32> {
let b = self.read_bytes(4)?;
Some(f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn read_f64(&mut self) -> Option<f64> {
let b = self.read_bytes(8)?;
Some(f64::from_le_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn read_string(&mut self) -> Option<String> {
let len = self.read_u64()? as usize;
let bytes = self.read_bytes(len)?;
Some(String::from_utf8_lossy(bytes).into_owned())
}
fn read_bool(&mut self) -> Option<bool> {
Some(self.read_u8()? != 0)
}
fn read_meta_value(&mut self, vtype: u32) -> Option<MetaValue> {
match vtype {
0 => Some(MetaValue::U8(self.read_u8()?)),
1 => Some(MetaValue::I8(self.read_i8()?)),
2 => Some(MetaValue::U16(self.read_u16()?)),
3 => Some(MetaValue::I16(self.read_i16()?)),
4 => Some(MetaValue::U32(self.read_u32()?)),
5 => Some(MetaValue::I32(self.read_i32()?)),
6 => Some(MetaValue::F32(self.read_f32()?)),
7 => Some(MetaValue::Bool(self.read_bool()?)),
8 => Some(MetaValue::Str(self.read_string()?)),
9 => {
let arr_type = self.read_u32()?;
let arr_len = self.read_u64()? as usize;
let mut items = Vec::with_capacity(arr_len.min(1_000_000));
for _ in 0..arr_len {
items.push(self.read_meta_value(arr_type)?);
}
Some(MetaValue::Array(items))
}
10 => Some(MetaValue::U64(self.read_u64()?)),
11 => Some(MetaValue::I64(self.read_i64()?)),
12 => Some(MetaValue::F64(self.read_f64()?)),
_ => None,
}
}
const fn align(&mut self, alignment: usize) {
let rem = self.pos % alignment;
if rem != 0 {
self.pos += alignment - rem;
}
}
}
pub struct GgufFile<'a> {
pub metadata: HashMap<String, MetaValue>,
pub tensors: HashMap<String, TensorInfo>,
tensor_data_start: usize,
data: &'a [u8],
_alignment: usize,
}
impl<'a> GgufFile<'a> {
pub fn parse(data: &'a [u8]) -> Option<Self> {
let mut r = Reader::new(data);
let magic = r.read_u32()?;
if magic != GGUF_MAGIC {
return None;
}
let version = r.read_u32()?;
if !(2..=3).contains(&version) {
return None;
}
let tensor_count = r.read_u64()? as usize;
let metadata_count = r.read_u64()? as usize;
let mut metadata = HashMap::with_capacity(metadata_count);
for _ in 0..metadata_count {
let key = r.read_string()?;
let vtype = r.read_u32()?;
let value = r.read_meta_value(vtype)?;
metadata.insert(key, value);
}
let mut tensors = HashMap::with_capacity(tensor_count);
for _ in 0..tensor_count {
let name = r.read_string()?;
let n_dims = r.read_u32()?;
let mut dims = Vec::with_capacity(n_dims as usize);
for _ in 0..n_dims {
dims.push(r.read_u64()?);
}
let qtype = GgmlType::from_u32(r.read_u32()?);
let offset = r.read_u64()?;
tensors.insert(
name.clone(),
TensorInfo {
name,
n_dims,
dims,
qtype,
offset,
},
);
}
let alignment = metadata
.get("general.alignment")
.and_then(MetaValue::as_u32)
.unwrap_or(GGUF_DEFAULT_ALIGNMENT as u32) as usize;
r.align(alignment);
let tensor_data_start = r.pos;
Some(Self {
metadata,
tensors,
tensor_data_start,
data,
_alignment: alignment,
})
}
#[must_use]
pub fn meta(&self, key: &str) -> Option<&MetaValue> {
self.metadata.get(key)
}
#[must_use]
pub fn meta_str(&self, key: &str) -> Option<&str> {
self.meta(key)?.as_str()
}
#[must_use]
pub fn meta_u32(&self, key: &str) -> Option<u32> {
self.meta(key)?.as_u32()
}
#[must_use]
pub fn meta_f32(&self, key: &str) -> Option<f32> {
self.meta(key)?.as_f32()
}
#[must_use]
pub fn meta_bool(&self, key: &str) -> Option<bool> {
self.meta(key)?.as_bool()
}
#[must_use]
pub fn tensor_absolute_offset(&self, name: &str) -> Option<u64> {
let info = self.tensors.get(name)?;
Some(self.tensor_data_start as u64 + info.offset)
}
#[must_use]
pub fn tensor_byte_size(&self, name: &str) -> Option<usize> {
Some(self.tensors.get(name)?.data_size())
}
#[must_use]
pub fn tensor_data(&self, name: &str) -> Option<&'a [u8]> {
let info = self.tensors.get(name)?;
let start = self.tensor_data_start + info.offset as usize;
let size = info.data_size();
if start + size > self.data.len() {
return None;
}
Some(&self.data[start..start + size])
}
#[must_use]
pub fn tensor_info(&self, name: &str) -> Option<&TensorInfo> {
self.tensors.get(name)
}
pub fn tensor_to_f32(&self, name: &str) -> Option<Vec<f32>> {
let info = self.tensors.get(name)?;
let data = self.tensor_data(name)?;
let n_elements = info.n_elements();
let mut out = vec![0.0f32; n_elements];
match info.qtype {
GgmlType::F32 => {
for i in 0..n_elements {
let off = i * 4;
out[i] = f32::from_le_bytes([
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
]);
}
}
GgmlType::F16 => {
for i in 0..n_elements {
let off = i * 2;
out[i] = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
}
}
GgmlType::Q4_0 => dequantize_q4_0(data, &mut out),
GgmlType::Q4_1 => dequantize_q4_1(data, &mut out),
GgmlType::Q5_0 => dequantize_q5_0(data, &mut out),
GgmlType::Q5_1 => dequantize_q5_1(data, &mut out),
GgmlType::IQ4_XS => dequantize_iq4_xs(data, &mut out),
GgmlType::Q8_0 => dequantize_q8_0(data, &mut out),
GgmlType::Q2_K => dequantize_q2_k(data, &mut out),
GgmlType::Q3_K => dequantize_q3_k(data, &mut out),
GgmlType::Q4_K => dequantize_q4_k(data, &mut out),
GgmlType::Q5_K => dequantize_q5_k(data, &mut out),
GgmlType::Q6_K => dequantize_q6_k(data, &mut out),
GgmlType::Q1_0 => dequantize_q1_0(data, &mut out),
GgmlType::Q2_0 => dequantize_q2_0(data, &mut out),
GgmlType::Mxfp4 => dequantize_row_mxfp4(data, &mut out),
GgmlType::IQ1_S => crate::iq1_s::dequantize_iq1_s(data, &mut out),
GgmlType::Other(_) => return None,
}
Some(out)
}
}
pub struct GgufMultiFile<'a> {
shards: Vec<GgufFile<'a>>,
tensor_shard_idx: HashMap<String, usize>,
}
impl<'a> GgufMultiFile<'a> {
pub fn parse_shards(shard_bytes: Vec<&'a [u8]>) -> Option<Self> {
if shard_bytes.is_empty() {
return None;
}
let mut shards = Vec::with_capacity(shard_bytes.len());
for bytes in shard_bytes {
shards.push(GgufFile::parse(bytes)?);
}
let mut tensor_shard_idx: HashMap<String, usize> = HashMap::new();
for (idx, shard) in shards.iter().enumerate() {
for name in shard.tensors.keys() {
tensor_shard_idx.entry(name.clone()).or_insert(idx);
}
}
Some(Self {
shards,
tensor_shard_idx,
})
}
#[must_use]
pub fn shard_count(&self) -> usize {
self.shards.len()
}
#[must_use]
pub fn tensor_count(&self) -> usize {
self.tensor_shard_idx.len()
}
#[must_use]
pub fn meta(&self, key: &str) -> Option<&MetaValue> {
self.shards.first()?.meta(key)
}
#[must_use]
pub fn meta_str(&self, key: &str) -> Option<&str> {
self.meta(key)?.as_str()
}
#[must_use]
pub fn meta_u32(&self, key: &str) -> Option<u32> {
self.meta(key)?.as_u32()
}
#[must_use]
pub fn meta_f32(&self, key: &str) -> Option<f32> {
self.meta(key)?.as_f32()
}
#[must_use]
pub fn meta_bool(&self, key: &str) -> Option<bool> {
self.meta(key)?.as_bool()
}
#[must_use]
pub fn tensor_data(&self, name: &str) -> Option<&'a [u8]> {
let idx = *self.tensor_shard_idx.get(name)?;
self.shards.get(idx)?.tensor_data(name)
}
#[must_use]
pub fn tensor_info(&self, name: &str) -> Option<&TensorInfo> {
let idx = *self.tensor_shard_idx.get(name)?;
self.shards.get(idx)?.tensor_info(name)
}
pub fn tensor_to_f32(&self, name: &str) -> Option<Vec<f32>> {
let idx = *self.tensor_shard_idx.get(name)?;
self.shards.get(idx)?.tensor_to_f32(name)
}
}
pub trait GgufSource<'a> {
fn meta(&self, key: &str) -> Option<&MetaValue>;
fn meta_str(&self, key: &str) -> Option<&str> {
self.meta(key)?.as_str()
}
fn meta_u32(&self, key: &str) -> Option<u32> {
self.meta(key)?.as_u32()
}
fn meta_f32(&self, key: &str) -> Option<f32> {
self.meta(key)?.as_f32()
}
fn meta_bool(&self, key: &str) -> Option<bool> {
self.meta(key)?.as_bool()
}
fn tensor_data(&self, name: &str) -> Option<&'a [u8]>;
fn tensor_info(&self, name: &str) -> Option<&TensorInfo>;
fn tensor_to_f32(&self, name: &str) -> Option<Vec<f32>>;
}
impl<'a> GgufSource<'a> for GgufFile<'a> {
fn meta(&self, key: &str) -> Option<&MetaValue> {
self.metadata.get(key)
}
fn tensor_data(&self, name: &str) -> Option<&'a [u8]> {
self.tensor_data(name)
}
fn tensor_info(&self, name: &str) -> Option<&TensorInfo> {
self.tensor_info(name)
}
fn tensor_to_f32(&self, name: &str) -> Option<Vec<f32>> {
self.tensor_to_f32(name)
}
}
impl<'a> GgufSource<'a> for GgufMultiFile<'a> {
fn meta(&self, key: &str) -> Option<&MetaValue> {
self.meta(key)
}
fn tensor_data(&self, name: &str) -> Option<&'a [u8]> {
self.tensor_data(name)
}
fn tensor_info(&self, name: &str) -> Option<&TensorInfo> {
self.tensor_info(name)
}
fn tensor_to_f32(&self, name: &str) -> Option<Vec<f32>> {
self.tensor_to_f32(name)
}
}
#[cfg(test)]
mod split_tests {
use super::*;
#[test]
fn multi_file_rejects_empty_shard_list() {
assert!(GgufMultiFile::parse_shards(vec![]).is_none());
}
}
#[inline]
fn get_scale_min_k4(j: usize, scales: &[u8]) -> (u8, u8) {
if j < 4 {
(scales[j] & 63, scales[j + 4] & 63)
} else {
let sc = (scales[j + 4] & 0xF) | ((scales[j - 4] >> 6) << 4);
let m = (scales[j + 4] >> 4) | ((scales[j] >> 6) << 4);
(sc, m)
}
}
fn dequantize_q2_k(data: &[u8], out: &mut [f32]) {
let block_bytes = 84;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let scales = &block[0..16];
let qs = &block[16..80];
let d = f16_to_f32(u16::from_le_bytes([block[80], block[81]]));
let dmin = f16_to_f32(u16::from_le_bytes([block[82], block[83]]));
for group in 0..16 {
let sc = (scales[group] & 0xF) as f32;
let m = (scales[group] >> 4) as f32;
for j in 0..16 {
let flat = group * 16 + j;
let byte_idx = flat / 4;
let bit_shift = (flat % 4) * 2;
let q = ((qs[byte_idx] >> bit_shift) & 3) as f32;
out[out_idx + flat] = d * sc * q - dmin * m;
}
}
out_idx += QK_K;
}
}
fn q3k_decode_scales(sc: &[u8]) -> [i32; 16] {
let mut scales = [0i32; 16];
for j in 0..4 {
scales[j] = (sc[j] & 0xF) as i32;
scales[j + 4] = (sc[j] >> 4) as i32;
scales[j + 8] = (sc[j + 4] & 0xF) as i32;
scales[j + 12] = (sc[j + 4] >> 4) as i32;
}
for j in 0..4 {
let hi = sc[8 + j];
scales[j] |= ((hi & 3) as i32) << 4;
scales[j + 4] |= (((hi >> 2) & 3) as i32) << 4;
scales[j + 8] |= (((hi >> 4) & 3) as i32) << 4;
scales[j + 12] |= (((hi >> 6) & 3) as i32) << 4;
}
for s in &mut scales {
*s -= 32;
}
scales
}
fn dequantize_q3_k(data: &[u8], out: &mut [f32]) {
let block_bytes = 110;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let hmask = &block[0..32];
let qs = &block[32..96];
let d = f16_to_f32(u16::from_le_bytes([block[108], block[109]]));
let scales = q3k_decode_scales(&block[96..108]);
let mut m: u8 = 1;
let mut q_off = 0usize;
for j in (0..QK_K).step_by(128) {
for shift in 0..4u8 {
for l in 0..32usize {
let lo2 = (qs[q_off + l] >> (shift * 2)) & 3;
let hi = if hmask[l] & m != 0 { 0i8 } else { -4i8 };
let flat = j + shift as usize * 32 + l;
let is = flat / 16;
out[out_idx + flat] = d * scales[is] as f32 * (lo2 as i8 + hi) as f32;
}
m <<= 1;
}
q_off += 32;
}
out_idx += QK_K;
}
}
fn dequantize_q4_k(data: &[u8], out: &mut [f32]) {
let block_bytes = 144;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([block[2], block[3]]));
let scales = &block[4..16];
let qs = &block[16..144];
let mut is = 0usize;
let mut q_offset = 0usize;
for _ in 0..4 {
let (sc1, m1) = get_scale_min_k4(is, scales);
let (sc2, m2) = get_scale_min_k4(is + 1, scales);
let d1 = d * f32::from(sc1);
let m1f = dmin * f32::from(m1);
let d2 = d * f32::from(sc2);
let m2f = dmin * f32::from(m2);
for l in 0..32 {
out[out_idx] = d1 * f32::from(qs[q_offset + l] & 0xF) - m1f;
out_idx += 1;
}
for l in 0..32 {
out[out_idx] = d2 * f32::from(qs[q_offset + l] >> 4) - m2f;
out_idx += 1;
}
q_offset += 32;
is += 2;
}
}
}
fn dequantize_q5_k(data: &[u8], out: &mut [f32]) {
let block_bytes = 176;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([block[2], block[3]]));
let scales_raw = &block[4..16];
let qh = &block[16..48];
let qs = &block[48..176];
let mut is = 0usize;
let mut q_offset = 0usize;
for im in 0..4u8 {
let (sc1, m1) = get_scale_min_k4(is, scales_raw);
let (sc2, m2) = get_scale_min_k4(is + 1, scales_raw);
let d1 = d * f32::from(sc1);
let m1f = dmin * f32::from(m1);
let d2 = d * f32::from(sc2);
let m2f = dmin * f32::from(m2);
for l in 0..32 {
let hbit = (qh[l] >> (im * 2)) & 1;
out[out_idx] = d1 * f32::from((qs[q_offset + l] & 0xF) | (hbit << 4)) - m1f;
out_idx += 1;
}
for l in 0..32 {
let hbit = (qh[l] >> (im * 2 + 1)) & 1;
out[out_idx] = d2 * f32::from((qs[q_offset + l] >> 4) | (hbit << 4)) - m2f;
out_idx += 1;
}
q_offset += 32;
is += 2;
}
}
}
fn dequantize_q8_0(data: &[u8], out: &mut [f32]) {
let block_bytes = 34;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
for l in 0..QK8_0 {
out[out_idx] = d * f32::from(data[off + 2 + l] as i8);
out_idx += 1;
}
}
}
pub(crate) fn dequantize_q5_1(data: &[u8], out: &mut [f32]) {
let block_bytes = 24;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
let m = f16_to_f32(u16::from_le_bytes([data[off + 2], data[off + 3]]));
let qh = u32::from_le_bytes([data[off + 4], data[off + 5], data[off + 6], data[off + 7]]);
for j in 0..16 {
let qs = data[off + 8 + j];
let xh_0 = ((qh >> j) << 4) & 0x10;
let xh_1 = (qh >> (j + 12)) & 0x10;
let x0 = ((qs & 0x0F) as u32) | xh_0;
let x1 = ((qs >> 4) as u32) | xh_1;
out[out_idx + j] = (x0 as f32) * d + m;
out[out_idx + j + 16] = (x1 as f32) * d + m;
}
out_idx += QK8_0;
}
}
pub(crate) fn dequantize_q4_0(data: &[u8], out: &mut [f32]) {
let block_bytes = 18;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
for j in 0..16 {
let qs = data[off + 2 + j];
let x0 = ((qs & 0x0F) as i32) - 8;
let x1 = ((qs >> 4) as i32) - 8;
out[out_idx + j] = (x0 as f32) * d;
out[out_idx + j + 16] = (x1 as f32) * d;
}
out_idx += QK8_0;
}
}
pub(crate) fn dequantize_q4_1(data: &[u8], out: &mut [f32]) {
let block_bytes = 20;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
let m = f16_to_f32(u16::from_le_bytes([data[off + 2], data[off + 3]]));
for j in 0..16 {
let qs = data[off + 4 + j];
let x0 = (qs & 0x0F) as f32;
let x1 = (qs >> 4) as f32;
out[out_idx + j] = x0 * d + m;
out[out_idx + j + 16] = x1 * d + m;
}
out_idx += QK8_0;
}
}
pub(crate) fn dequantize_q5_0(data: &[u8], out: &mut [f32]) {
let block_bytes = 22;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
let qh = u32::from_le_bytes([data[off + 2], data[off + 3], data[off + 4], data[off + 5]]);
for j in 0..16 {
let qs = data[off + 6 + j];
let xh_0 = ((qh >> j) << 4) & 0x10;
let xh_1 = (qh >> (j + 12)) & 0x10;
let x0 = (((qs & 0x0F) as u32) | xh_0) as i32 - 16;
let x1 = (((qs >> 4) as u32) | xh_1) as i32 - 16;
out[out_idx + j] = (x0 as f32) * d;
out[out_idx + j + 16] = (x1 as f32) * d;
}
out_idx += QK8_0;
}
}
const KVALUES_IQ4NL: [i8; 16] = [
-127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113,
];
pub(crate) fn dequantize_iq4_xs(data: &[u8], out: &mut [f32]) {
let block_bytes = 136;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let off = i * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
let scales_h = u16::from_le_bytes([data[off + 2], data[off + 3]]);
let scales_l = [data[off + 4], data[off + 5], data[off + 6], data[off + 7]];
let qs = &data[off + 8..off + 8 + 128];
for ib in 0..8 {
let ls_lo = (scales_l[ib / 2] >> (4 * (ib as u8 & 1))) & 0x0F;
let ls_hi = ((scales_h >> (2 * ib)) & 0x3) as u8;
let ls = ((ls_hi << 4) | ls_lo) as i32;
let dl = d * ((ls - 32) as f32);
let qs_base = ib * 16;
let out_base = out_idx + ib * 32;
for j in 0..16 {
let byte = qs[qs_base + j];
let n0 = (byte & 0x0F) as usize;
let n1 = (byte >> 4) as usize;
out[out_base + j] = dl * f32::from(KVALUES_IQ4NL[n0]);
out[out_base + j + 16] = dl * f32::from(KVALUES_IQ4NL[n1]);
}
}
out_idx += QK_K;
}
}
pub(crate) fn dequantize_q6_k_public(data: &[u8], out: &mut [f32]) {
dequantize_q6_k(data, out);
}
fn dequantize_q6_k(data: &[u8], out: &mut [f32]) {
let block_bytes = 210;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let ql = &block[0..128];
let qh = &block[128..192];
let scales = &block[192..208];
let d = f16_to_f32(u16::from_le_bytes([block[208], block[209]]));
let mut ql_off = 0usize;
let mut qh_off = 0usize;
let mut sc_off = 0usize;
for _n in (0..QK_K).step_by(128) {
for l in 0..32 {
let is = l / 16; let q1 = ((ql[ql_off + l] & 0xF) | ((qh[qh_off + l] & 3) << 4)) as i8 - 32;
let q2 =
((ql[ql_off + l + 32] & 0xF) | (((qh[qh_off + l] >> 2) & 3) << 4)) as i8 - 32;
let q3 = ((ql[ql_off + l] >> 4) | (((qh[qh_off + l] >> 4) & 3) << 4)) as i8 - 32;
let q4 =
((ql[ql_off + l + 32] >> 4) | (((qh[qh_off + l] >> 6) & 3) << 4)) as i8 - 32;
out[out_idx + l] = d * f32::from(scales[sc_off + is] as i8) * f32::from(q1);
out[out_idx + l + 32] =
d * f32::from(scales[sc_off + is + 2] as i8) * f32::from(q2);
out[out_idx + l + 64] =
d * f32::from(scales[sc_off + is + 4] as i8) * f32::from(q3);
out[out_idx + l + 96] =
d * f32::from(scales[sc_off + is + 6] as i8) * f32::from(q4);
}
out_idx += 128;
ql_off += 64;
qh_off += 32;
sc_off += 8;
}
}
}
fn dequantize_q1_0(data: &[u8], out: &mut [f32]) {
let block_bytes = 18;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
for b in 0..16 {
let byte = block[2 + b];
for k in 0..8 {
let bit = (byte >> k) & 1;
let val = if bit == 1 { d } else { -d };
out[out_idx] = val;
out_idx += 1;
}
}
}
}
pub(crate) fn dequantize_q2_0(data: &[u8], out: &mut [f32]) {
let block_bytes = 34;
let n_blocks = data.len() / block_bytes;
let mut out_idx = 0;
for i in 0..n_blocks {
let block = &data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
for b in 0..32 {
let byte = block[2 + b];
for k in 0..4 {
let slot = ((byte >> (k * 2)) & 0x3) as i8;
let val = (slot - 1) as f32 * d;
out[out_idx] = val;
out_idx += 1;
}
}
}
}
pub struct BlockQ8K {
pub d: f32, pub qs: [i8; QK_K], pub bsums: [i16; 16], }
#[inline]
fn nearest_int(fval: f32) -> i32 {
let val = fval + 12_582_912.0f32; let i = val.to_bits() as i32;
(i & 0x007f_ffff) - 0x0040_0000
}
pub fn quantize_row_q8_k(input: &[f32]) -> Vec<BlockQ8K> {
let nb = input.len() / QK_K;
let mut blocks = Vec::with_capacity(nb);
for i in 0..nb {
let x = &input[i * QK_K..(i + 1) * QK_K];
let mut amax = 0.0f32;
let mut max_val = 0.0f32;
for &v in x {
let av = v.abs();
if av > amax {
amax = av;
max_val = v;
}
}
let mut block = BlockQ8K {
d: 0.0,
qs: [0i8; QK_K],
bsums: [0i16; 16],
};
if amax == 0.0 {
blocks.push(block);
continue;
}
let iscale = 128.0f32 / max_val;
for j in 0..QK_K {
let v = nearest_int(iscale * x[j]);
block.qs[j] = v.max(-128).min(127) as i8;
}
for j in 0..16 {
let mut sum = 0i32;
for ii in 0..16 {
sum += block.qs[j * 16 + ii] as i32;
}
block.bsums[j] = sum as i16;
}
block.d = 1.0 / iscale;
blocks.push(block);
}
blocks
}
#[cfg(target_arch = "aarch64")]
#[allow(dead_code)]
mod neon_dot {
use super::*;
use std::arch::aarch64::*;
#[inline]
#[target_feature(enable = "neon")]
unsafe fn hsum_s32(v: int32x4_t) -> i32 {
let low = vget_low_s32(v);
let high = vget_high_s32(v);
let sum2 = vadd_s32(low, high);
let pair = vpadd_s32(sum2, sum2);
vget_lane_s32::<0>(pair)
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn dot_32(a: *const i8, b: *const i8) -> i32 {
let a0 = vld1q_s8(a);
let a1 = vld1q_s8(a.add(16));
let b0 = vld1q_s8(b);
let b1 = vld1q_s8(b.add(16));
let p0 = vmull_s8(vget_low_s8(a0), vget_low_s8(b0));
let p1 = vmull_s8(vget_high_s8(a0), vget_high_s8(b0));
let p2 = vmull_s8(vget_low_s8(a1), vget_low_s8(b1));
let p3 = vmull_s8(vget_high_s8(a1), vget_high_s8(b1));
let s01 = vaddq_s32(vpaddlq_s16(p0), vpaddlq_s16(p1));
let s23 = vaddq_s32(vpaddlq_s16(p2), vpaddlq_s16(p3));
hsum_s32(vaddq_s32(s01, s23))
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn dot_16(a: *const i8, b: *const i8) -> i32 {
let a0 = vld1q_s8(a);
let b0 = vld1q_s8(b);
let p0 = vmull_s8(vget_low_s8(a0), vget_low_s8(b0));
let p1 = vmull_s8(vget_high_s8(a0), vget_high_s8(b0));
hsum_s32(vaddq_s32(vpaddlq_s16(p0), vpaddlq_s16(p1)))
}
#[target_feature(enable = "neon")]
pub unsafe fn q4k_q8k_dot(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let d = f16_to_f32(u16::from_le_bytes([q4k_block[0], q4k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q4k_block[2], q4k_block[3]]));
let q4 = &q4k_block[16..144];
let (scales, mins) = unpack_scales_mins_q4k(&q4k_block[4..16]);
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mask_lo = vdupq_n_u8(0x0F);
let mut total = 0i32;
let q8 = &q8k.qs;
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut q4_off = 0usize;
for _ in 0..4 {
let q4_0 = vld1q_u8(q4.as_ptr().add(q4_off));
let q4_1 = vld1q_u8(q4.as_ptr().add(q4_off + 16));
let lo_0 = vreinterpretq_s8_u8(vandq_u8(q4_0, mask_lo));
let lo_1 = vreinterpretq_s8_u8(vandq_u8(q4_1, mask_lo));
let hi_0 = vreinterpretq_s8_u8(vshrq_n_u8::<4>(q4_0));
let hi_1 = vreinterpretq_s8_u8(vshrq_n_u8::<4>(q4_1));
vst1q_s8(aux8.as_mut_ptr().add(a_off), lo_0);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 16), lo_1);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 32), hi_0);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 48), hi_1);
a_off += 64;
q4_off += 32;
}
for is in 0..8 {
let off = is * 32;
let dot = dot_32(aux8.as_ptr().add(off), q8.as_ptr().add(off));
total += scales[is] as i32 * dot;
}
d * q8k.d * total as f32 - dmin * q8k.d * sumi as f32
}
#[target_feature(enable = "neon")]
pub unsafe fn q6k_q8k_dot(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let ql = &q6k_block[0..128];
let qh = &q6k_block[128..192];
let scales = &q6k_block[192..208];
let d = f16_to_f32(u16::from_le_bytes([q6k_block[208], q6k_block[209]]));
let mut aux8 = [0i8; QK_K];
let mask_nib = vdupq_n_u8(0x0F);
let mask_hi = vdupq_n_u8(0x30);
let bias = vdupq_n_s8(-32);
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
let ql_a = vld1q_u8(ql.as_ptr().add(ql_off));
let ql_b = vld1q_u8(ql.as_ptr().add(ql_off + 16));
let ql_c = vld1q_u8(ql.as_ptr().add(ql_off + 32));
let ql_d = vld1q_u8(ql.as_ptr().add(ql_off + 48));
let qh_a = vld1q_u8(qh.as_ptr().add(qh_off));
let qh_b = vld1q_u8(qh.as_ptr().add(qh_off + 16));
let hi_a_q0 = vandq_u8(vshlq_n_u8::<4>(qh_a), mask_hi);
let hi_b_q0 = vandq_u8(vshlq_n_u8::<4>(qh_b), mask_hi);
let out_0a = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vandq_u8(ql_a, mask_nib), hi_a_q0)),
bias,
);
let out_0b = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vandq_u8(ql_b, mask_nib), hi_b_q0)),
bias,
);
vst1q_s8(aux8.as_mut_ptr().add(a_off), out_0a);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 16), out_0b);
let hi_a_q1 = vandq_u8(vshlq_n_u8::<2>(qh_a), mask_hi);
let hi_b_q1 = vandq_u8(vshlq_n_u8::<2>(qh_b), mask_hi);
let out_1a = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vandq_u8(ql_c, mask_nib), hi_a_q1)),
bias,
);
let out_1b = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vandq_u8(ql_d, mask_nib), hi_b_q1)),
bias,
);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 32), out_1a);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 48), out_1b);
let hi_a_q2 = vandq_u8(qh_a, mask_hi);
let hi_b_q2 = vandq_u8(qh_b, mask_hi);
let out_2a = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8::<4>(ql_a), hi_a_q2)),
bias,
);
let out_2b = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8::<4>(ql_b), hi_b_q2)),
bias,
);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 64), out_2a);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 80), out_2b);
let hi_a_q3 = vandq_u8(vshrq_n_u8::<2>(qh_a), mask_hi);
let hi_b_q3 = vandq_u8(vshrq_n_u8::<2>(qh_b), mask_hi);
let out_3a = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8::<4>(ql_c), hi_a_q3)),
bias,
);
let out_3b = vaddq_s8(
vreinterpretq_s8_u8(vorrq_u8(vshrq_n_u8::<4>(ql_d), hi_b_q3)),
bias,
);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 96), out_3a);
vst1q_s8(aux8.as_mut_ptr().add(a_off + 112), out_3b);
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let q8 = &q8k.qs;
let mut total = 0i32;
for is in 0..16 {
let scale = scales[is] as i8 as i32;
let off = is * 16;
let dot = dot_16(aux8.as_ptr().add(off), q8.as_ptr().add(off));
total += scale * dot;
}
d * q8k.d * total as f32
}
#[target_feature(enable = "neon")]
pub unsafe fn q8_0_matvec_row(input: &[f32], row_data: &[u8], cols: usize) -> f32 {
let blocks_per_row = cols / QK8_0;
let block_bytes = 34usize;
let mut acc: float32x4_t = vdupq_n_f32(0.0);
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let d_bcast = vdupq_n_f32(d);
let qs_ptr = row_data.as_ptr().add(off + 2).cast::<i8>();
let col_base = bi * QK8_0;
let in_ptr = input.as_ptr().add(col_base);
let a0 = vld1q_s8(qs_ptr);
let a0_lo = vmovl_s8(vget_low_s8(a0));
let a0_hi = vmovl_s8(vget_high_s8(a0));
let f0 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(a0_lo))), d_bcast);
let f1 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(a0_lo))), d_bcast);
let f2 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(a0_hi))), d_bcast);
let f3 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(a0_hi))), d_bcast);
acc = vfmaq_f32(acc, f0, vld1q_f32(in_ptr));
acc = vfmaq_f32(acc, f1, vld1q_f32(in_ptr.add(4)));
acc = vfmaq_f32(acc, f2, vld1q_f32(in_ptr.add(8)));
acc = vfmaq_f32(acc, f3, vld1q_f32(in_ptr.add(12)));
let a1 = vld1q_s8(qs_ptr.add(16));
let a1_lo = vmovl_s8(vget_low_s8(a1));
let a1_hi = vmovl_s8(vget_high_s8(a1));
let g0 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(a1_lo))), d_bcast);
let g1 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(a1_lo))), d_bcast);
let g2 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_low_s16(a1_hi))), d_bcast);
let g3 = vmulq_f32(vcvtq_f32_s32(vmovl_s16(vget_high_s16(a1_hi))), d_bcast);
acc = vfmaq_f32(acc, g0, vld1q_f32(in_ptr.add(16)));
acc = vfmaq_f32(acc, g1, vld1q_f32(in_ptr.add(20)));
acc = vfmaq_f32(acc, g2, vld1q_f32(in_ptr.add(24)));
acc = vfmaq_f32(acc, g3, vld1q_f32(in_ptr.add(28)));
}
vaddvq_f32(acc)
}
#[target_feature(enable = "neon")]
pub unsafe fn ternary_dot_row_unscaled(
pos_mask: &[u8],
neg_mask: &[u8],
input: &[f32],
num_cols: usize,
) -> f32 {
let bit_lanes_lo: uint32x4_t = vld1q_u32([0x01u32, 0x02, 0x04, 0x08].as_ptr());
let bit_lanes_hi: uint32x4_t = vld1q_u32([0x10u32, 0x20, 0x40, 0x80].as_ptr());
let mut pos_acc: float32x4_t = vdupq_n_f32(0.0);
let mut neg_acc: float32x4_t = vdupq_n_f32(0.0);
let full_bytes = num_cols / 8;
for b in 0..full_bytes {
let pm = u32::from(pos_mask[b]);
let nm = u32::from(neg_mask[b]);
if (pm | nm) == 0 {
continue;
}
let in_ptr = input.as_ptr().add(b * 8);
let in_lo = vld1q_f32(in_ptr);
let in_hi = vld1q_f32(in_ptr.add(4));
if pm != 0 {
let bcast = vdupq_n_u32(pm);
let is_set_lo = vceqq_u32(vandq_u32(bcast, bit_lanes_lo), bit_lanes_lo);
let is_set_hi = vceqq_u32(vandq_u32(bcast, bit_lanes_hi), bit_lanes_hi);
let contrib_lo = vandq_u32(is_set_lo, vreinterpretq_u32_f32(in_lo));
let contrib_hi = vandq_u32(is_set_hi, vreinterpretq_u32_f32(in_hi));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_lo));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_hi));
}
if nm != 0 {
let bcast = vdupq_n_u32(nm);
let is_set_lo = vceqq_u32(vandq_u32(bcast, bit_lanes_lo), bit_lanes_lo);
let is_set_hi = vceqq_u32(vandq_u32(bcast, bit_lanes_hi), bit_lanes_hi);
let contrib_lo = vandq_u32(is_set_lo, vreinterpretq_u32_f32(in_lo));
let contrib_hi = vandq_u32(is_set_hi, vreinterpretq_u32_f32(in_hi));
neg_acc = vaddq_f32(neg_acc, vreinterpretq_f32_u32(contrib_lo));
neg_acc = vaddq_f32(neg_acc, vreinterpretq_f32_u32(contrib_hi));
}
}
let mut pos_sum = vaddvq_f32(pos_acc);
let mut neg_sum = vaddvq_f32(neg_acc);
let tail_start = full_bytes * 8;
if tail_start < num_cols {
let last_b = full_bytes;
let pm = pos_mask[last_b];
let nm = neg_mask[last_b];
let remaining = num_cols - tail_start;
for bit in 0..remaining {
let idx = tail_start + bit;
let p = f32::from((pm >> bit) & 1);
let n = f32::from((nm >> bit) & 1);
pos_sum += p * input[idx];
neg_sum += n * input[idx];
}
}
pos_sum - neg_sum
}
#[inline]
fn unpack_scales_mins_q4k(scale_bytes: &[u8]) -> ([u8; 8], [u8; 8]) {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let mut utmp = [0u32; 4];
utmp[0] = u32::from_le_bytes([
scale_bytes[0],
scale_bytes[1],
scale_bytes[2],
scale_bytes[3],
]);
utmp[1] = u32::from_le_bytes([
scale_bytes[4],
scale_bytes[5],
scale_bytes[6],
scale_bytes[7],
]);
utmp[2] = u32::from_le_bytes([
scale_bytes[8],
scale_bytes[9],
scale_bytes[10],
scale_bytes[11],
]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
(
[s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]],
[m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]],
)
}
#[target_feature(enable = "neon")]
pub unsafe fn q1_0_dot_row(row_data: &[u8], input: &[f32]) -> f32 {
let bit_lanes_lo: uint32x4_t = vld1q_u32([0x01u32, 0x02, 0x04, 0x08].as_ptr());
let bit_lanes_hi: uint32x4_t = vld1q_u32([0x10u32, 0x20, 0x40, 0x80].as_ptr());
let block_bytes = 18usize;
let n_blocks = row_data.len() / block_bytes;
let mut total = 0.0f32;
for i in 0..n_blocks {
let block = &row_data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let mut pos_acc: float32x4_t = vdupq_n_f32(0.0);
let mut neg_acc: float32x4_t = vdupq_n_f32(0.0);
let block_input = &input[i * 128..(i + 1) * 128];
for b in 0..16 {
let byte = block[2 + b];
let pm = u32::from(byte);
let nm = u32::from(!byte);
let in_ptr = block_input.as_ptr().add(b * 8);
let in_lo = vld1q_f32(in_ptr);
let in_hi = vld1q_f32(in_ptr.add(4));
let bcast_p = vdupq_n_u32(pm);
let mask_p_lo = vceqq_u32(vandq_u32(bcast_p, bit_lanes_lo), bit_lanes_lo);
let mask_p_hi = vceqq_u32(vandq_u32(bcast_p, bit_lanes_hi), bit_lanes_hi);
let contrib_p_lo = vandq_u32(mask_p_lo, vreinterpretq_u32_f32(in_lo));
let contrib_p_hi = vandq_u32(mask_p_hi, vreinterpretq_u32_f32(in_hi));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_p_lo));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_p_hi));
let bcast_n = vdupq_n_u32(nm);
let mask_n_lo = vceqq_u32(vandq_u32(bcast_n, bit_lanes_lo), bit_lanes_lo);
let mask_n_hi = vceqq_u32(vandq_u32(bcast_n, bit_lanes_hi), bit_lanes_hi);
let contrib_n_lo = vandq_u32(mask_n_lo, vreinterpretq_u32_f32(in_lo));
let contrib_n_hi = vandq_u32(mask_n_hi, vreinterpretq_u32_f32(in_hi));
neg_acc = vaddq_f32(neg_acc, vreinterpretq_f32_u32(contrib_n_lo));
neg_acc = vaddq_f32(neg_acc, vreinterpretq_f32_u32(contrib_n_hi));
}
let pos = vaddvq_f32(pos_acc);
let neg = vaddvq_f32(neg_acc);
total += d * (pos - neg);
}
total
}
#[target_feature(enable = "neon")]
pub unsafe fn q1_0_dot_row_pos_only(
row_data: &[u8],
input: &[f32],
input_block_sums: &[f32],
) -> f32 {
let bit_lanes_lo: uint32x4_t = vld1q_u32([0x01u32, 0x02, 0x04, 0x08].as_ptr());
let bit_lanes_hi: uint32x4_t = vld1q_u32([0x10u32, 0x20, 0x40, 0x80].as_ptr());
let block_bytes = 18usize;
let n_blocks = row_data.len() / block_bytes;
let mut total = 0.0f32;
for i in 0..n_blocks {
let block = &row_data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let mut pos_acc: float32x4_t = vdupq_n_f32(0.0);
let block_input = &input[i * 128..(i + 1) * 128];
for b in 0..16 {
let byte = block[2 + b];
let pm = u32::from(byte);
let in_ptr = block_input.as_ptr().add(b * 8);
let in_lo = vld1q_f32(in_ptr);
let in_hi = vld1q_f32(in_ptr.add(4));
let bcast_p = vdupq_n_u32(pm);
let mask_p_lo = vceqq_u32(vandq_u32(bcast_p, bit_lanes_lo), bit_lanes_lo);
let mask_p_hi = vceqq_u32(vandq_u32(bcast_p, bit_lanes_hi), bit_lanes_hi);
let contrib_p_lo = vandq_u32(mask_p_lo, vreinterpretq_u32_f32(in_lo));
let contrib_p_hi = vandq_u32(mask_p_hi, vreinterpretq_u32_f32(in_hi));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_p_lo));
pos_acc = vaddq_f32(pos_acc, vreinterpretq_f32_u32(contrib_p_hi));
}
let pos = vaddvq_f32(pos_acc);
total += d * (2.0 * pos - input_block_sums[i]);
}
total
}
#[target_feature(enable = "neon")]
pub unsafe fn q2_0_dot_row(row_data: &[u8], input: &[f32]) -> f32 {
let shifts: int32x4_t = vld1q_s32([0i32, -2, -4, -6].as_ptr());
let two_bit_mask: uint32x4_t = vdupq_n_u32(0x3);
let ones_s32: int32x4_t = vdupq_n_s32(1);
let block_bytes = 34usize;
let n_blocks = row_data.len() / block_bytes;
let mut total = 0.0f32;
for i in 0..n_blocks {
let block = &row_data[i * block_bytes..(i + 1) * block_bytes];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let mut acc: float32x4_t = vdupq_n_f32(0.0);
let block_input = &input[i * 128..(i + 1) * 128];
for b in 0..32 {
let byte = u32::from(block[2 + b]);
let bcast: uint32x4_t = vdupq_n_u32(byte);
let shifted = vshlq_u32(bcast, shifts);
let slots = vandq_u32(shifted, two_bit_mask);
let vals_s32 = vsubq_s32(vreinterpretq_s32_u32(slots), ones_s32);
let vals_f32 = vcvtq_f32_s32(vals_s32);
let in_ptr = block_input.as_ptr().add(b * 4);
let in_vec = vld1q_f32(in_ptr);
acc = vfmaq_f32(acc, vals_f32, in_vec);
}
total += d * vaddvq_f32(acc);
}
total
}
#[target_feature(enable = "neon")]
pub unsafe fn mxfp4_dot_row(row_data: &[u8], input: &[f32]) -> f32 {
const BLOCK_BYTES: usize = 17;
let n_blocks = row_data.len() / BLOCK_BYTES;
let mut total = 0.0_f32;
for i in 0..n_blocks {
let block = &row_data[i * BLOCK_BYTES..(i + 1) * BLOCK_BYTES];
let scale = decode_e8m0_scale(block[0]);
let mut dq = [0.0_f32; QK_MXFP4];
for j in 0..16 {
let byte = block[1 + j];
dq[2 * j] = E2M1_DECODE_TABLE[usize::from(byte & 0x0F)] * scale;
dq[2 * j + 1] = E2M1_DECODE_TABLE[usize::from(byte >> 4)] * scale;
}
let block_input = &input[i * QK_MXFP4..(i + 1) * QK_MXFP4];
let mut acc: float32x4_t = vdupq_n_f32(0.0);
for k in 0..(QK_MXFP4 / 4) {
let dq_vec = vld1q_f32(dq.as_ptr().add(k * 4));
let in_vec = vld1q_f32(block_input.as_ptr().add(k * 4));
acc = vfmaq_f32(acc, dq_vec, in_vec);
}
total += vaddvq_f32(acc);
}
total
}
}
#[cfg(target_arch = "x86_64")]
#[allow(dead_code)]
mod avx2_dot {
use super::*;
use std::arch::x86_64::*;
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn hsum_i32_avx2(v: __m256i) -> i32 {
let lo = _mm256_castsi256_si128(v);
let hi = _mm256_extracti128_si256(v, 1);
let sum128 = _mm_add_epi32(lo, hi);
let sh = _mm_shuffle_epi32(sum128, 0b1110);
let sum64 = _mm_add_epi32(sum128, sh);
let sh2 = _mm_shufflelo_epi16(sum64, 0b1110);
let sum32 = _mm_add_epi32(sum64, sh2);
_mm_cvtsi128_si32(sum32)
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn dot_32_i8_signed(acc: __m256i, w: *const i8, x: *const i8) -> __m256i {
let wv = _mm256_loadu_si256(w.cast());
let xv = _mm256_loadu_si256(x.cast());
let w_abs = _mm256_sign_epi8(wv, wv);
let x_signed = _mm256_sign_epi8(xv, wv);
let prod16 = _mm256_maddubs_epi16(w_abs, x_signed);
let ones = _mm256_set1_epi16(1);
let prod32 = _mm256_madd_epi16(prod16, ones);
_mm256_add_epi32(acc, prod32)
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn dot_32_u4_i8_from_nibbles(acc: __m256i, nib32: __m256i, x: *const i8) -> __m256i {
let xv = _mm256_loadu_si256(x.cast());
let prod16 = _mm256_maddubs_epi16(nib32, xv);
let ones = _mm256_set1_epi16(1);
let prod32 = _mm256_madd_epi16(prod16, ones);
_mm256_add_epi32(acc, prod32)
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn unpack_16_q4_nibbles(q4: *const u8) -> __m256i {
let bytes = _mm_loadu_si128(q4.cast());
let mask = _mm_set1_epi8(0x0F);
let lo = _mm_and_si128(bytes, mask);
let hi = _mm_and_si128(_mm_srli_epi16(bytes, 4), mask);
_mm256_inserti128_si256(_mm256_castsi128_si256(lo), hi, 1)
}
#[inline]
#[target_feature(enable = "avx2")]
pub(super) unsafe fn q4k_q8k_dot(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let d = f16_to_f32(u16::from_le_bytes([q4k_block[0], q4k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q4k_block[2], q4k_block[3]]));
let q4 = &q4k_block[16..144];
let mut utmp = [0u32; 4];
let sb = &q4k_block[4..16];
utmp[0] = u32::from_le_bytes([sb[0], sb[1], sb[2], sb[3]]);
utmp[1] = u32::from_le_bytes([sb[4], sb[5], sb[6], sb[7]]);
utmp[2] = u32::from_le_bytes([sb[8], sb[9], sb[10], sb[11]]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
let scales = [s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]];
let mins = [m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]];
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mut total: i32 = 0;
for g in 0..4 {
let q4_ptr = q4.as_ptr().add(g * 32);
let bytes = _mm256_loadu_si256(q4_ptr.cast());
let mask = _mm256_set1_epi8(0x0F);
let lo_nibbles = _mm256_and_si256(bytes, mask);
let hi_nibbles = _mm256_and_si256(_mm256_srli_epi16(bytes, 4), mask);
let q8_ptr_a = q8k.qs.as_ptr().add(g * 64);
let acc_a = _mm256_setzero_si256();
let acc_a = dot_32_u4_i8_from_nibbles(acc_a, lo_nibbles, q8_ptr_a);
let dot_a = hsum_i32_avx2(acc_a);
total += scales[2 * g] as i32 * dot_a;
let q8_ptr_b = q8k.qs.as_ptr().add(g * 64 + 32);
let acc_b = _mm256_setzero_si256();
let acc_b = dot_32_u4_i8_from_nibbles(acc_b, hi_nibbles, q8_ptr_b);
let dot_b = hsum_i32_avx2(acc_b);
total += scales[2 * g + 1] as i32 * dot_b;
}
d * q8k.d * total as f32 - dmin * q8k.d * sumi as f32
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn dot_16_i8_widened(acc: __m256i, w: *const i8, x: *const i8) -> __m256i {
let w16 = _mm256_cvtepi8_epi16(_mm_loadu_si128(w.cast()));
let x16 = _mm256_cvtepi8_epi16(_mm_loadu_si128(x.cast()));
let prod32 = _mm256_madd_epi16(w16, x16);
_mm256_add_epi32(acc, prod32)
}
#[inline]
#[target_feature(enable = "avx2")]
pub(super) unsafe fn q6k_q8k_dot(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let ql = &q6k_block[0..128];
let qh = &q6k_block[128..192];
let scales = &q6k_block[192..208];
let d = f16_to_f32(u16::from_le_bytes([q6k_block[208], q6k_block[209]]));
let mut aux8 = [0i8; QK_K];
let mask_nib = _mm256_set1_epi8(0x0F);
let mask_hi = _mm256_set1_epi8(0x30);
let bias = _mm256_set1_epi8(-32);
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
let ql_lo = _mm256_loadu_si256(ql.as_ptr().add(ql_off).cast());
let ql_hi = _mm256_loadu_si256(ql.as_ptr().add(ql_off + 32).cast());
let qh_v = _mm256_loadu_si256(qh.as_ptr().add(qh_off).cast());
let hi0 = _mm256_and_si256(_mm256_slli_epi16(qh_v, 4), mask_hi);
let q0 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(ql_lo, mask_nib), hi0),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off).cast(), q0);
let hi1 = _mm256_and_si256(_mm256_slli_epi16(qh_v, 2), mask_hi);
let q1 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(ql_hi, mask_nib), hi1),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 32).cast(), q1);
let hi2 = _mm256_and_si256(qh_v, mask_hi);
let q2 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(_mm256_srli_epi16(ql_lo, 4), mask_nib), hi2),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 64).cast(), q2);
let hi3 = _mm256_and_si256(_mm256_srli_epi16(qh_v, 2), mask_hi);
let q3 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(_mm256_srli_epi16(ql_hi, 4), mask_nib), hi3),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 96).cast(), q3);
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let mut total: i32 = 0;
for is in 0..16 {
let a_ptr = aux8.as_ptr().add(is * 16);
let q8_ptr = q8k.qs.as_ptr().add(is * 16);
let acc0 = _mm256_setzero_si256();
let acc = dot_16_i8_widened(acc0, a_ptr, q8_ptr);
let sub_dot = hsum_i32_avx2(acc);
total += scales[is] as i8 as i32 * sub_dot;
}
d * q8k.d * total as f32
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn dot_32_u8_i8_widened(acc: __m256i, u: __m256i, x: *const i8) -> __m256i {
let xv = _mm256_loadu_si256(x.cast());
let u_lo128 = _mm256_castsi256_si128(u);
let u_hi128 = _mm256_extracti128_si256(u, 1);
let x_lo128 = _mm256_castsi256_si128(xv);
let x_hi128 = _mm256_extracti128_si256(xv, 1);
let u_lo = _mm256_cvtepu8_epi16(u_lo128);
let u_hi = _mm256_cvtepu8_epi16(u_hi128);
let x_lo = _mm256_cvtepi8_epi16(x_lo128);
let x_hi = _mm256_cvtepi8_epi16(x_hi128);
let sum_lo = _mm256_madd_epi16(u_lo, x_lo);
let sum_hi = _mm256_madd_epi16(u_hi, x_hi);
_mm256_add_epi32(acc, _mm256_add_epi32(sum_lo, sum_hi))
}
#[inline]
#[target_feature(enable = "avx2")]
pub(super) unsafe fn q5k_q8k_dot(q5k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let d = f16_to_f32(u16::from_le_bytes([q5k_block[0], q5k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q5k_block[2], q5k_block[3]]));
let scales_raw = &q5k_block[4..16];
let qh = &q5k_block[16..48];
let qs = &q5k_block[48..176];
let qh_v = _mm256_loadu_si256(qh.as_ptr().cast());
let mask_lo4 = _mm256_set1_epi8(0x0F);
let mask_bit0 = _mm256_set1_epi8(1);
let mut sumf = 0.0f32;
for im in 0..4u8 {
let (sc1, m1) = get_scale_min_k4((im as usize) * 2, scales_raw);
let (sc2, m2) = get_scale_min_k4((im as usize) * 2 + 1, scales_raw);
let qs_group = _mm256_loadu_si256(qs.as_ptr().add((im as usize) * 32).cast());
let lo_nibbles = _mm256_and_si256(qs_group, mask_lo4);
let hi_nibbles = _mm256_and_si256(_mm256_srli_epi16(qs_group, 4), mask_lo4);
let shift_even = _mm_cvtsi32_si128(i32::from(im) * 2);
let shift_odd = _mm_cvtsi32_si128(i32::from(im) * 2 + 1);
let hbits_even = _mm256_and_si256(_mm256_srl_epi16(qh_v, shift_even), mask_bit0);
let hbits_even_hi = _mm256_slli_epi16(hbits_even, 4);
let q_even = _mm256_or_si256(lo_nibbles, hbits_even_hi);
let hbits_odd = _mm256_and_si256(_mm256_srl_epi16(qh_v, shift_odd), mask_bit0);
let hbits_odd_hi = _mm256_slli_epi16(hbits_odd, 4);
let q_odd = _mm256_or_si256(hi_nibbles, hbits_odd_hi);
let q8_ptr_a = q8k.qs.as_ptr().add((im as usize) * 64);
let acc_a = _mm256_setzero_si256();
let acc_a = dot_32_u8_i8_widened(acc_a, q_even, q8_ptr_a);
let dot_a = hsum_i32_avx2(acc_a);
let q8_ptr_b = q8k.qs.as_ptr().add((im as usize) * 64 + 32);
let acc_b = _mm256_setzero_si256();
let acc_b = dot_32_u8_i8_widened(acc_b, q_odd, q8_ptr_b);
let dot_b = hsum_i32_avx2(acc_b);
let sum1 =
q8k.bsums[(im as usize) * 4] as i32 + q8k.bsums[(im as usize) * 4 + 1] as i32;
let sum2 =
q8k.bsums[(im as usize) * 4 + 2] as i32 + q8k.bsums[(im as usize) * 4 + 3] as i32;
sumf += d * sc1 as f32 * dot_a as f32 - dmin * m1 as f32 * sum1 as f32;
sumf += d * sc2 as f32 * dot_b as f32 - dmin * m2 as f32 * sum2 as f32;
}
sumf * q8k.d
}
#[inline]
#[target_feature(enable = "avx2,fma")]
pub(super) unsafe fn q8_0_matvec_row(input: &[f32], row_data: &[u8], cols: usize) -> f32 {
let blocks_per_row = cols / super::QK8_0;
let block_bytes = 34usize;
let mut acc = _mm256_setzero_ps();
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = super::f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let d_bcast = _mm256_set1_ps(d);
let qs_ptr = row_data.as_ptr().add(off + 2).cast::<i8>();
let col_base = bi * super::QK8_0;
for k in 0..4 {
#[allow(clippy::cast_ptr_alignment)]
let q8 = _mm_loadl_epi64(qs_ptr.add(k * 8).cast::<__m128i>());
let q32 = _mm256_cvtepi8_epi32(q8);
let qf = _mm256_cvtepi32_ps(q32);
let scaled = _mm256_mul_ps(qf, d_bcast);
let x = _mm256_loadu_ps(input.as_ptr().add(col_base + k * 8));
acc = _mm256_fmadd_ps(scaled, x, acc);
}
}
let lo = _mm256_castps256_ps128(acc);
let hi = _mm256_extractf128_ps(acc, 1);
let s128 = _mm_add_ps(lo, hi);
let sh = _mm_movehl_ps(s128, s128);
let s64 = _mm_add_ps(s128, sh);
let sh2 = _mm_shuffle_ps(s64, s64, 0x55);
let s32 = _mm_add_ss(s64, sh2);
_mm_cvtss_f32(s32)
}
#[inline]
#[target_feature(enable = "avx2")]
pub(super) unsafe fn ternary_dot_row_unscaled(
pos_mask: &[u8],
neg_mask: &[u8],
input: &[f32],
num_cols: usize,
) -> f32 {
let bit_lanes = _mm256_setr_epi32(0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80);
let mut pos_acc = _mm256_setzero_ps();
let mut neg_acc = _mm256_setzero_ps();
let full_bytes = num_cols / 8;
for b in 0..full_bytes {
let pm = u32::from(pos_mask[b]);
let nm = u32::from(neg_mask[b]);
if (pm | nm) == 0 {
continue;
}
let input_v = _mm256_loadu_ps(input.as_ptr().add(b * 8));
if pm != 0 {
let bcast = _mm256_set1_epi32(pm as i32);
let masked = _mm256_and_si256(bcast, bit_lanes);
let is_set = _mm256_cmpeq_epi32(masked, bit_lanes);
let contrib = _mm256_and_ps(_mm256_castsi256_ps(is_set), input_v);
pos_acc = _mm256_add_ps(pos_acc, contrib);
}
if nm != 0 {
let bcast = _mm256_set1_epi32(nm as i32);
let masked = _mm256_and_si256(bcast, bit_lanes);
let is_set = _mm256_cmpeq_epi32(masked, bit_lanes);
let contrib = _mm256_and_ps(_mm256_castsi256_ps(is_set), input_v);
neg_acc = _mm256_add_ps(neg_acc, contrib);
}
}
let hsum = |v: __m256| -> f32 {
let lo = _mm256_castps256_ps128(v);
let hi = _mm256_extractf128_ps(v, 1);
let s = _mm_add_ps(lo, hi);
let sh = _mm_movehl_ps(s, s);
let s2 = _mm_add_ps(s, sh);
let sh2 = _mm_shuffle_ps(s2, s2, 0x55);
let s3 = _mm_add_ss(s2, sh2);
_mm_cvtss_f32(s3)
};
let mut pos_sum = hsum(pos_acc);
let mut neg_sum = hsum(neg_acc);
let tail_start = full_bytes * 8;
if tail_start < num_cols {
let last_b = full_bytes;
let pm = pos_mask[last_b];
let nm = neg_mask[last_b];
let remaining = num_cols - tail_start;
for bit in 0..remaining {
let idx = tail_start + bit;
let p = f32::from((pm >> bit) & 1);
let n = f32::from((nm >> bit) & 1);
pos_sum += p * input[idx];
neg_sum += n * input[idx];
}
}
pos_sum - neg_sum
}
#[target_feature(enable = "avx2,fma")]
pub unsafe fn mxfp4_dot_row(row_data: &[u8], input: &[f32]) -> f32 {
const BLOCK_BYTES: usize = 17;
let n_blocks = row_data.len() / BLOCK_BYTES;
let mut total = 0.0_f32;
for i in 0..n_blocks {
let block = &row_data[i * BLOCK_BYTES..(i + 1) * BLOCK_BYTES];
let scale = decode_e8m0_scale(block[0]);
let mut dq = [0.0_f32; QK_MXFP4];
for j in 0..16 {
let byte = block[1 + j];
dq[2 * j] = E2M1_DECODE_TABLE[usize::from(byte & 0x0F)] * scale;
dq[2 * j + 1] = E2M1_DECODE_TABLE[usize::from(byte >> 4)] * scale;
}
let block_input = &input[i * QK_MXFP4..(i + 1) * QK_MXFP4];
let mut acc: __m256 = _mm256_setzero_ps();
for k in 0..(QK_MXFP4 / 8) {
let dq_vec = _mm256_loadu_ps(dq.as_ptr().add(k * 8));
let in_vec = _mm256_loadu_ps(block_input.as_ptr().add(k * 8));
acc = _mm256_fmadd_ps(dq_vec, in_vec, acc);
}
let lo = _mm256_castps256_ps128(acc);
let hi = _mm256_extractf128_ps(acc, 1);
let s = _mm_add_ps(lo, hi);
let sh = _mm_movehl_ps(s, s);
let s2 = _mm_add_ps(s, sh);
let sh2 = _mm_shuffle_ps(s2, s2, 0x55);
let s3 = _mm_add_ss(s2, sh2);
total += _mm_cvtss_f32(s3);
}
total
}
}
#[cfg(target_arch = "x86_64")]
#[allow(dead_code)]
mod avx512_dot {
use super::*;
use std::arch::x86_64::*;
#[inline]
#[target_feature(enable = "avx512bw")]
unsafe fn dot_64_u4_i8_from_nibbles(acc: __m512i, nib64: __m512i, x: *const i8) -> __m512i {
let xv = _mm512_loadu_si512(x.cast());
let prod16 = _mm512_maddubs_epi16(nib64, xv);
let ones = _mm512_set1_epi16(1);
let prod32 = _mm512_madd_epi16(prod16, ones);
_mm512_add_epi32(acc, prod32)
}
#[inline]
#[target_feature(enable = "avx512bw")]
unsafe fn unpack_32_q4_nibbles(q4: *const u8) -> __m512i {
let bytes = _mm256_loadu_si256(q4.cast());
let mask = _mm256_set1_epi8(0x0F);
let lo = _mm256_and_si256(bytes, mask);
let hi = _mm256_and_si256(_mm256_srli_epi16(bytes, 4), mask);
_mm512_inserti64x4(_mm512_castsi256_si512(lo), hi, 1)
}
#[inline]
#[target_feature(enable = "avx512bw")]
pub(super) unsafe fn q4k_q8k_dot(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let d = f16_to_f32(u16::from_le_bytes([q4k_block[0], q4k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q4k_block[2], q4k_block[3]]));
let q4 = &q4k_block[16..144];
let mut utmp = [0u32; 4];
let sb = &q4k_block[4..16];
utmp[0] = u32::from_le_bytes([sb[0], sb[1], sb[2], sb[3]]);
utmp[1] = u32::from_le_bytes([sb[4], sb[5], sb[6], sb[7]]);
utmp[2] = u32::from_le_bytes([sb[8], sb[9], sb[10], sb[11]]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
let scales = [s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]];
let mins = [m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]];
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mut total: i32 = 0;
for g in 0..4 {
let mut sub_dot_a = 0i32;
let mut sub_dot_b = 0i32;
for l in 0..32 {
let lo = (q4[g * 32 + l] & 0x0F) as i32;
let hi = ((q4[g * 32 + l] >> 4) & 0x0F) as i32;
sub_dot_a += lo * q8k.qs[g * 64 + l] as i32;
sub_dot_b += hi * q8k.qs[g * 64 + 32 + l] as i32;
}
total += scales[g * 2] as i32 * sub_dot_a;
total += scales[g * 2 + 1] as i32 * sub_dot_b;
}
d * q8k.d * total as f32 - dmin * q8k.d * sumi as f32
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn dot_16_i8_widened_avx2(acc: __m256i, w: *const i8, x: *const i8) -> __m256i {
let w16 = _mm256_cvtepi8_epi16(_mm_loadu_si128(w.cast()));
let x16 = _mm256_cvtepi8_epi16(_mm_loadu_si128(x.cast()));
let prod32 = _mm256_madd_epi16(w16, x16);
_mm256_add_epi32(acc, prod32)
}
#[inline]
#[target_feature(enable = "avx2")]
unsafe fn hsum_i32_avx2(v: __m256i) -> i32 {
let lo = _mm256_castsi256_si128(v);
let hi = _mm256_extracti128_si256(v, 1);
let sum128 = _mm_add_epi32(lo, hi);
let sh = _mm_shuffle_epi32(sum128, 0b1110);
let sum64 = _mm_add_epi32(sum128, sh);
let sh2 = _mm_shufflelo_epi16(sum64, 0b1110);
let sum32 = _mm_add_epi32(sum64, sh2);
_mm_cvtsi128_si32(sum32)
}
#[inline]
#[target_feature(enable = "avx512bw")]
pub(super) unsafe fn q6k_q8k_dot(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let ql = &q6k_block[0..128];
let qh = &q6k_block[128..192];
let scales = &q6k_block[192..208];
let d = f16_to_f32(u16::from_le_bytes([q6k_block[208], q6k_block[209]]));
let mut aux8 = [0i8; QK_K];
let mask_nib = _mm256_set1_epi8(0x0F);
let mask_hi = _mm256_set1_epi8(0x30);
let bias = _mm256_set1_epi8(-32);
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
let ql_lo = _mm256_loadu_si256(ql.as_ptr().add(ql_off).cast());
let ql_hi = _mm256_loadu_si256(ql.as_ptr().add(ql_off + 32).cast());
let qh_v = _mm256_loadu_si256(qh.as_ptr().add(qh_off).cast());
let hi0 = _mm256_and_si256(_mm256_slli_epi16(qh_v, 4), mask_hi);
let q0 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(ql_lo, mask_nib), hi0),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off).cast(), q0);
let hi1 = _mm256_and_si256(_mm256_slli_epi16(qh_v, 2), mask_hi);
let q1 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(ql_hi, mask_nib), hi1),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 32).cast(), q1);
let hi2 = _mm256_and_si256(qh_v, mask_hi);
let q2 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(_mm256_srli_epi16(ql_lo, 4), mask_nib), hi2),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 64).cast(), q2);
let hi3 = _mm256_and_si256(_mm256_srli_epi16(qh_v, 2), mask_hi);
let q3 = _mm256_add_epi8(
_mm256_or_si256(_mm256_and_si256(_mm256_srli_epi16(ql_hi, 4), mask_nib), hi3),
bias,
);
_mm256_storeu_si256(aux8.as_mut_ptr().add(a_off + 96).cast(), q3);
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let mut total: i32 = 0;
for is in 0..16 {
let a_ptr = aux8.as_ptr().add(is * 16);
let q8_ptr = q8k.qs.as_ptr().add(is * 16);
let acc0 = _mm256_setzero_si256();
let acc = dot_16_i8_widened_avx2(acc0, a_ptr, q8_ptr);
let sub_dot = hsum_i32_avx2(acc);
total += scales[is] as i8 as i32 * sub_dot;
}
d * q8k.d * total as f32
}
#[inline]
#[target_feature(enable = "avx512bw")]
pub(super) unsafe fn q5k_q8k_dot(q5k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let d = f16_to_f32(u16::from_le_bytes([q5k_block[0], q5k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q5k_block[2], q5k_block[3]]));
let scales_raw = &q5k_block[4..16];
let qh = &q5k_block[16..48];
let qs = &q5k_block[48..176];
let qh256 = _mm256_loadu_si256(qh.as_ptr().cast());
let qh_v = _mm512_broadcast_i32x8(qh256);
let mask_bit0 = _mm512_set1_epi8(1);
let mut sumf = 0.0f32;
for im in 0..4u8 {
let (sc1, m1) = get_scale_min_k4((im as usize) * 2, scales_raw);
let (sc2, m2) = get_scale_min_k4((im as usize) * 2 + 1, scales_raw);
let qs256 = _mm256_loadu_si256(qs.as_ptr().add((im as usize) * 32).cast());
let qs_lo = _mm256_and_si256(qs256, _mm256_set1_epi8(0x0F));
let qs_hi = _mm256_and_si256(_mm256_srli_epi16(qs256, 4), _mm256_set1_epi8(0x0F));
let nibbles64 = _mm512_inserti64x4(_mm512_castsi256_si512(qs_lo), qs_hi, 1);
let shift_even = _mm_cvtsi32_si128(i32::from(im) * 2);
let shift_odd = _mm_cvtsi32_si128(i32::from(im) * 2 + 1);
let hbits = _mm512_and_si512(_mm512_srl_epi16(qh_v, shift_even), mask_bit0);
let hbits_odd = _mm512_and_si512(_mm512_srl_epi16(qh_v, shift_odd), mask_bit0);
let hbits_pack = _mm512_inserti64x4(
_mm512_castsi256_si512(_mm512_castsi512_si256(hbits)),
_mm512_castsi512_si256(hbits_odd),
1,
);
let hbits_hi = _mm512_slli_epi16(hbits_pack, 4);
let q_all = _mm512_or_si512(nibbles64, hbits_hi);
let q_lo = _mm512_castsi512_si256(q_all);
let q_hi = _mm512_extracti64x4_epi64(q_all, 1);
let q8_ptr_a = q8k.qs.as_ptr().add((im as usize) * 64);
let q8_ptr_b = q8k.qs.as_ptr().add((im as usize) * 64 + 32);
let dot_a = {
let acc0 = _mm256_setzero_si256();
let acc = super::avx2_dot_bridge_u8_i8(acc0, q_lo, q8_ptr_a);
super::avx2_hsum_i32_bridge(acc)
};
let dot_b = {
let acc0 = _mm256_setzero_si256();
let acc = super::avx2_dot_bridge_u8_i8(acc0, q_hi, q8_ptr_b);
super::avx2_hsum_i32_bridge(acc)
};
let sum1 =
q8k.bsums[(im as usize) * 4] as i32 + q8k.bsums[(im as usize) * 4 + 1] as i32;
let sum2 =
q8k.bsums[(im as usize) * 4 + 2] as i32 + q8k.bsums[(im as usize) * 4 + 3] as i32;
sumf += d * sc1 as f32 * dot_a as f32 - dmin * m1 as f32 * sum1 as f32;
sumf += d * sc2 as f32 * dot_b as f32 - dmin * m2 as f32 * sum2 as f32;
}
sumf * q8k.d
}
#[inline]
#[target_feature(enable = "avx512f")]
pub(super) unsafe fn q8_0_matvec_row(input: &[f32], row_data: &[u8], cols: usize) -> f32 {
let blocks_per_row = cols / super::QK8_0;
let block_bytes = 34usize;
let mut acc = _mm512_setzero_ps();
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = super::f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let d_bcast = _mm512_set1_ps(d);
let qs_ptr = row_data.as_ptr().add(off + 2).cast::<i8>();
let col_base = bi * super::QK8_0;
for k in 0..2 {
#[allow(clippy::cast_ptr_alignment)]
let q8 = _mm_loadu_si128(qs_ptr.add(k * 16).cast::<__m128i>());
let q32 = _mm512_cvtepi8_epi32(q8);
let qf = _mm512_cvtepi32_ps(q32);
let scaled = _mm512_mul_ps(qf, d_bcast);
let x = _mm512_loadu_ps(input.as_ptr().add(col_base + k * 16));
acc = _mm512_fmadd_ps(scaled, x, acc);
}
}
_mm512_reduce_add_ps(acc)
}
#[inline]
#[target_feature(enable = "avx512f,avx512bw")]
pub(super) unsafe fn ternary_dot_row_unscaled(
pos_mask: &[u8],
neg_mask: &[u8],
input: &[f32],
num_cols: usize,
) -> f32 {
let mut pos_acc = _mm512_setzero_ps();
let mut neg_acc = _mm512_setzero_ps();
let full_pairs = num_cols / 16;
for pair in 0..full_pairs {
let pm = u16::from(pos_mask[pair * 2]) | (u16::from(pos_mask[pair * 2 + 1]) << 8);
let nm = u16::from(neg_mask[pair * 2]) | (u16::from(neg_mask[pair * 2 + 1]) << 8);
if (pm | nm) == 0 {
continue;
}
let input_v = _mm512_loadu_ps(input.as_ptr().add(pair * 16));
if pm != 0 {
let k: __mmask16 = pm;
pos_acc = _mm512_mask_add_ps(pos_acc, k, pos_acc, input_v);
}
if nm != 0 {
let k: __mmask16 = nm;
neg_acc = _mm512_mask_add_ps(neg_acc, k, neg_acc, input_v);
}
}
let mut pos_sum = _mm512_reduce_add_ps(pos_acc);
let mut neg_sum = _mm512_reduce_add_ps(neg_acc);
let tail_start = full_pairs * 16;
for idx in tail_start..num_cols {
let byte_idx = idx / 8;
let bit = idx % 8;
let p = f32::from((pos_mask[byte_idx] >> bit) & 1);
let n = f32::from((neg_mask[byte_idx] >> bit) & 1);
pos_sum += p * input[idx];
neg_sum += n * input[idx];
}
pos_sum - neg_sum
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_dot_bridge_u8_i8(
acc: std::arch::x86_64::__m256i,
u: std::arch::x86_64::__m256i,
x: *const i8,
) -> std::arch::x86_64::__m256i {
use std::arch::x86_64::*;
let xv = _mm256_loadu_si256(x.cast());
let u_lo = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(u));
let u_hi = _mm256_cvtepu8_epi16(_mm256_extracti128_si256(u, 1));
let x_lo = _mm256_cvtepi8_epi16(_mm256_castsi256_si128(xv));
let x_hi = _mm256_cvtepi8_epi16(_mm256_extracti128_si256(xv, 1));
let sum_lo = _mm256_madd_epi16(u_lo, x_lo);
let sum_hi = _mm256_madd_epi16(u_hi, x_hi);
_mm256_add_epi32(acc, _mm256_add_epi32(sum_lo, sum_hi))
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn avx2_hsum_i32_bridge(v: std::arch::x86_64::__m256i) -> i32 {
use std::arch::x86_64::*;
let lo = _mm256_castsi256_si128(v);
let hi = _mm256_extracti128_si256(v, 1);
let sum128 = _mm_add_epi32(lo, hi);
let sh = _mm_shuffle_epi32(sum128, 0b1110);
let sum64 = _mm_add_epi32(sum128, sh);
let sh2 = _mm_shufflelo_epi16(sum64, 0b1110);
let sum32 = _mm_add_epi32(sum64, sh2);
_mm_cvtsi128_si32(sum32)
}
#[cfg(target_arch = "x86_64")]
fn x86_avx2_supported() -> bool {
use std::sync::OnceLock;
static CACHE: OnceLock<bool> = OnceLock::new();
*CACHE.get_or_init(|| is_x86_feature_detected!("avx2"))
}
#[cfg(target_arch = "x86_64")]
fn x86_avx512bw_supported() -> bool {
use std::sync::OnceLock;
static CACHE: OnceLock<bool> = OnceLock::new();
*CACHE.get_or_init(|| is_x86_feature_detected!("avx512bw"))
}
#[inline]
#[allow(dead_code)]
fn q4k_q8k_dot_scalar(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let d = f16_to_f32(u16::from_le_bytes([q4k_block[0], q4k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q4k_block[2], q4k_block[3]]));
let q4 = &q4k_block[16..144];
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut q4_off = 0usize;
for _ in 0..4 {
for l in 0..32 {
aux8[a_off + l] = (q4[q4_off + l] & 0xF) as i8;
}
a_off += 32;
for l in 0..32 {
aux8[a_off + l] = (q4[q4_off + l] >> 4) as i8;
}
a_off += 32;
q4_off += 32;
}
let mut utmp = [0u32; 4];
let sb = &q4k_block[4..16];
utmp[0] = u32::from_le_bytes([sb[0], sb[1], sb[2], sb[3]]);
utmp[1] = u32::from_le_bytes([sb[4], sb[5], sb[6], sb[7]]);
utmp[2] = u32::from_le_bytes([sb[8], sb[9], sb[10], sb[11]]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
let scales = [s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]];
let mins = [m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]];
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mut aux32 = [0i32; 8];
let mut a_idx = 0usize;
let mut q8_idx = 0usize;
for is in 0..8 {
let scale = scales[is] as i32;
for _ in 0..4 {
for l in 0..8 {
aux32[l] += scale * (q8k.qs[q8_idx + l] as i32 * aux8[a_idx + l] as i32);
}
q8_idx += 8;
a_idx += 8;
}
}
let d_all = d * q8k.d;
let dmin_all = dmin * q8k.d;
let mut sumf = 0.0f32;
for l in 0..8 {
sumf += d_all * aux32[l] as f32;
}
sumf -= dmin_all * sumi as f32;
sumf
}
#[inline]
#[allow(dead_code)]
fn q6k_q8k_dot_scalar(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let ql = &q6k_block[0..128];
let qh = &q6k_block[128..192];
let scales = &q6k_block[192..208];
let d = f16_to_f32(u16::from_le_bytes([q6k_block[208], q6k_block[209]]));
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
for l in 0..32 {
aux8[a_off + l] = ((ql[ql_off + l] & 0xF) | ((qh[qh_off + l] & 3) << 4)) as i8 - 32;
aux8[a_off + l + 32] =
((ql[ql_off + l + 32] & 0xF) | (((qh[qh_off + l] >> 2) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 64] =
((ql[ql_off + l] >> 4) | (((qh[qh_off + l] >> 4) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 96] =
((ql[ql_off + l + 32] >> 4) | (((qh[qh_off + l] >> 6) & 3) << 4)) as i8 - 32;
}
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let mut aux32 = [0i32; 8];
let mut a_idx = 0usize;
let mut q8_idx = 0usize;
for is in 0..16 {
let scale = scales[is] as i8 as i32;
for _ in 0..2 {
for l in 0..8 {
aux32[l] += scale * (q8k.qs[q8_idx + l] as i32 * aux8[a_idx + l] as i32);
}
q8_idx += 8;
a_idx += 8;
}
}
let d_all = d * q8k.d;
let mut sumf = 0.0f32;
for l in 0..8 {
sumf += d_all * aux32[l] as f32;
}
sumf
}
#[inline]
fn q4k_q8k_dot(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if x86_avx512bw_supported() {
return unsafe { avx512_dot::q4k_q8k_dot(q4k_block, q8k) };
}
if x86_avx2_supported() {
return unsafe { avx2_dot::q4k_q8k_dot(q4k_block, q8k) };
}
}
#[cfg(target_arch = "aarch64")]
{
return unsafe { neon_dot::q4k_q8k_dot(q4k_block, q8k) };
}
#[allow(unreachable_code)]
q4k_q8k_dot_fallback_scalar(q4k_block, q8k)
}
#[inline]
fn q4k_q8k_dot_fallback_scalar(q4k_block: &[u8], q8k: &BlockQ8K) -> f32 {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let d = f16_to_f32(u16::from_le_bytes([q4k_block[0], q4k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q4k_block[2], q4k_block[3]]));
let q4 = &q4k_block[16..144];
let mut aux8 = [0u8; QK_K];
for g in 0..4 {
for l in 0..32 {
aux8[g * 64 + l] = q4[g * 32 + l] & 0xF;
aux8[g * 64 + 32 + l] = q4[g * 32 + l] >> 4;
}
}
let mut utmp = [0u32; 4];
let sb = &q4k_block[4..16];
utmp[0] = u32::from_le_bytes([sb[0], sb[1], sb[2], sb[3]]);
utmp[1] = u32::from_le_bytes([sb[4], sb[5], sb[6], sb[7]]);
utmp[2] = u32::from_le_bytes([sb[8], sb[9], sb[10], sb[11]]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
let scales = [s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]];
let mins = [m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]];
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mut total = 0i32;
for is in 0..8 {
let off = is * 32;
let mut dot = 0i32;
for l in 0..32 {
dot += aux8[off + l] as i32 * q8k.qs[off + l] as i32;
}
total += scales[is] as i32 * dot;
}
d * q8k.d * total as f32 - dmin * q8k.d * sumi as f32
}
#[inline]
fn q6k_q8k_dot(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if x86_avx512bw_supported() {
return unsafe { avx512_dot::q6k_q8k_dot(q6k_block, q8k) };
}
if x86_avx2_supported() {
return unsafe { avx2_dot::q6k_q8k_dot(q6k_block, q8k) };
}
}
#[cfg(target_arch = "aarch64")]
{
return unsafe { neon_dot::q6k_q8k_dot(q6k_block, q8k) };
}
#[allow(unreachable_code)]
q6k_q8k_dot_fallback_scalar(q6k_block, q8k)
}
#[inline]
fn q6k_q8k_dot_fallback_scalar(q6k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let ql = &q6k_block[0..128];
let qh = &q6k_block[128..192];
let scales = &q6k_block[192..208];
let d = f16_to_f32(u16::from_le_bytes([q6k_block[208], q6k_block[209]]));
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
for l in 0..32 {
aux8[a_off + l] = ((ql[ql_off + l] & 0xF) | ((qh[qh_off + l] & 3) << 4)) as i8 - 32;
aux8[a_off + l + 32] =
((ql[ql_off + l + 32] & 0xF) | (((qh[qh_off + l] >> 2) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 64] =
((ql[ql_off + l] >> 4) | (((qh[qh_off + l] >> 4) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 96] =
((ql[ql_off + l + 32] >> 4) | (((qh[qh_off + l] >> 6) & 3) << 4)) as i8 - 32;
}
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let mut total = 0i32;
for is in 0..16 {
let scale = scales[is] as i8 as i32;
let off = is * 16;
let mut dot = 0i32;
for l in 0..16 {
dot += aux8[off + l] as i32 * q8k.qs[off + l] as i32;
}
total += scale * dot;
}
d * q8k.d * total as f32
}
#[inline]
fn q2k_q8k_dot(q2k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let scales = &q2k_block[0..16];
let qs = &q2k_block[16..80];
let d = f16_to_f32(u16::from_le_bytes([q2k_block[80], q2k_block[81]]));
let dmin = f16_to_f32(u16::from_le_bytes([q2k_block[82], q2k_block[83]]));
let mut sumf = 0.0f32;
for group in 0..16 {
let sc = (scales[group] & 0xF) as i32;
let m = (scales[group] >> 4) as i32;
let mut dot = 0i32;
let mut sum_q8 = 0i32;
for j in 0..16 {
let flat = group * 16 + j;
let byte_idx = flat / 4;
let bit_shift = (flat % 4) * 2;
let q = ((qs[byte_idx] >> bit_shift) & 3) as i32;
dot += q * q8k.qs[flat] as i32;
sum_q8 += q8k.qs[flat] as i32;
}
sumf += d * sc as f32 * dot as f32 - dmin * m as f32 * sum_q8 as f32;
}
sumf * q8k.d
}
#[inline]
fn q5k_q8k_dot(q5k_block: &[u8], q8k: &BlockQ8K) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if x86_avx512bw_supported() {
return unsafe { avx512_dot::q5k_q8k_dot(q5k_block, q8k) };
}
if x86_avx2_supported() {
return unsafe { avx2_dot::q5k_q8k_dot(q5k_block, q8k) };
}
}
q5k_q8k_dot_fallback_scalar(q5k_block, q8k)
}
#[inline]
fn q5k_q8k_dot_fallback_scalar(q5k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let d = f16_to_f32(u16::from_le_bytes([q5k_block[0], q5k_block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([q5k_block[2], q5k_block[3]]));
let scales_raw = &q5k_block[4..16];
let qh = &q5k_block[16..48];
let qs = &q5k_block[48..176];
let mut sumf = 0.0f32;
let mut is = 0usize;
let mut q_offset = 0usize;
let mut q8_offset = 0usize;
for im in 0..4u8 {
let (sc1, m1) = get_scale_min_k4(is, scales_raw);
let (sc2, m2) = get_scale_min_k4(is + 1, scales_raw);
let mut dot1 = 0i32;
let mut sum1 = 0i32;
for l in 0..32 {
let hbit = ((qh[l] >> (im * 2)) & 1) as i32;
let q = ((qs[q_offset + l] & 0xF) as i32) | (hbit << 4);
dot1 += q * q8k.qs[q8_offset + l] as i32;
sum1 += q8k.qs[q8_offset + l] as i32;
}
sumf += d * sc1 as f32 * dot1 as f32 - dmin * m1 as f32 * sum1 as f32;
let mut dot2 = 0i32;
let mut sum2 = 0i32;
for l in 0..32 {
let hbit = ((qh[l] >> (im * 2 + 1)) & 1) as i32;
let q = ((qs[q_offset + l] >> 4) as i32) | (hbit << 4);
dot2 += q * q8k.qs[q8_offset + 32 + l] as i32;
sum2 += q8k.qs[q8_offset + 32 + l] as i32;
}
sumf += d * sc2 as f32 * dot2 as f32 - dmin * m2 as f32 * sum2 as f32;
q_offset += 32;
q8_offset += 64;
is += 2;
}
sumf * q8k.d
}
#[inline]
fn q3k_q8k_dot(q3k_block: &[u8], q8k: &BlockQ8K) -> f32 {
let hmask = &q3k_block[0..32];
let qs = &q3k_block[32..96];
let d = f16_to_f32(u16::from_le_bytes([q3k_block[108], q3k_block[109]]));
let scales = q3k_decode_scales(&q3k_block[96..108]);
let mut aux8 = [0i8; QK_K];
let mut m: u8 = 1;
let mut q_off = 0usize;
for j in (0..QK_K).step_by(128) {
for shift in 0..4u8 {
for l in 0..32usize {
let lo2 = (qs[q_off + l] >> (shift * 2)) & 3;
let hi = if hmask[l] & m != 0 { 0i8 } else { -4i8 };
aux8[j + shift as usize * 32 + l] = lo2 as i8 + hi;
}
m <<= 1;
}
q_off += 32;
}
let mut total = 0i32;
for is in 0..16 {
let off = is * 16;
let mut dot = 0i32;
for l in 0..16 {
dot += aux8[off + l] as i32 * q8k.qs[off + l] as i32;
}
total += scales[is] * dot;
}
d * q8k.d * total as f32
}
pub fn q2k_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let q8_blocks = quantize_row_q8_k(input);
q2k_matvec_preq(data, rows, cols, &q8_blocks, output);
}
#[allow(unused_variables)]
pub fn q2k_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 84;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"q2k_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q2k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q2k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
pub fn q5k_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let q8_blocks = quantize_row_q8_k(input);
q5k_matvec_preq(data, rows, cols, &q8_blocks, output);
}
#[allow(unused_variables)]
pub fn q5k_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 176;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"q5k_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q5k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q5k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
pub fn q3k_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let q8_blocks = quantize_row_q8_k(input);
q3k_matvec_preq(data, rows, cols, &q8_blocks, output);
}
#[allow(unused_variables)]
pub fn q3k_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 110;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"q3k_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q3k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q3k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
pub fn q4k_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let q8_blocks = quantize_row_q8_k(input);
q4k_matvec_preq(data, rows, cols, &q8_blocks, output);
}
#[allow(unused_variables)]
pub fn q4k_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 144;
let row_bytes = blocks_per_row * block_bytes;
if std::env::var_os("ALICE_Q4K_LLAMACPP_ACCUM").is_some() {
q4k_matvec_preq_llamacpp_accum(data, rows, cols, q8_blocks, output);
return;
}
if std::env::var_os("ALICE_BYPASS_Q8K_ACTIVATION").is_some() {
let mut input_f32 = vec![0.0f32; cols];
for (b, block) in q8_blocks.iter().enumerate() {
let base = b * QK_K;
for i in 0..QK_K {
if base + i >= cols {
break;
}
input_f32[base + i] = block.d * block.qs[i] as f32;
}
}
let mut row_f32 = vec![0.0f32; cols];
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
dequantize_q4_k(row_data, &mut row_f32);
let mut acc: f64 = 0.0;
for i in 0..cols {
acc += (row_f32[i] as f64) * (input_f32[i] as f64);
}
output[row] = acc as f32;
}
return;
}
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"q4k_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q4k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q4k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
fn q4k_matvec_preq_llamacpp_accum(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
const KMASK1: u32 = 0x3f3f_3f3f;
const KMASK2: u32 = 0x0f0f_0f0f;
const KMASK3: u32 = 0x0303_0303;
let blocks_per_row = cols / QK_K;
let block_bytes = 144;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sums = [0.0f32; 8];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
let block = &row_data[bi * block_bytes..(bi + 1) * block_bytes];
let q8k = &q8_blocks[bi];
let d = f16_to_f32(u16::from_le_bytes([block[0], block[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([block[2], block[3]]));
let q4 = &block[16..144];
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut q4_off = 0usize;
for _ in 0..4 {
for l in 0..32 {
aux8[a_off + l] = (q4[q4_off + l] & 0xF) as i8;
}
a_off += 32;
for l in 0..32 {
aux8[a_off + l] = (q4[q4_off + l] >> 4) as i8;
}
a_off += 32;
q4_off += 32;
}
let mut utmp = [0u32; 4];
let sb = &block[4..16];
utmp[0] = u32::from_le_bytes([sb[0], sb[1], sb[2], sb[3]]);
utmp[1] = u32::from_le_bytes([sb[4], sb[5], sb[6], sb[7]]);
utmp[2] = u32::from_le_bytes([sb[8], sb[9], sb[10], sb[11]]);
utmp[3] = ((utmp[2] >> 4) & KMASK2) | (((utmp[1] >> 6) & KMASK3) << 4);
let uaux = utmp[1] & KMASK1;
utmp[1] = (utmp[2] & KMASK2) | (((utmp[0] >> 6) & KMASK3) << 4);
utmp[2] = uaux;
utmp[0] &= KMASK1;
let s0 = utmp[0].to_le_bytes();
let s1 = utmp[1].to_le_bytes();
let m0 = utmp[2].to_le_bytes();
let m1 = utmp[3].to_le_bytes();
let scales = [s0[0], s0[1], s0[2], s0[3], s1[0], s1[1], s1[2], s1[3]];
let mins = [m0[0], m0[1], m0[2], m0[3], m1[0], m1[1], m1[2], m1[3]];
let mut sumi = 0i32;
for j in 0..16 {
sumi += q8k.bsums[j] as i32 * mins[j / 2] as i32;
}
let mut aux32 = [0i32; 8];
let mut a_idx = 0usize;
let mut q8_idx = 0usize;
for is in 0..8 {
let scale = scales[is] as i32;
for _ in 0..4 {
for l in 0..8 {
aux32[l] += scale * (q8k.qs[q8_idx + l] as i32 * aux8[a_idx + l] as i32);
}
q8_idx += 8;
a_idx += 8;
}
}
let d_all = d * q8k.d;
for l in 0..8 {
sums[l] += d_all * aux32[l] as f32;
}
let dmin_all = dmin * q8k.d;
sumf -= dmin_all * sumi as f32;
}
for l in 0..8 {
sumf += sums[l];
}
output[row] = sumf;
}
}
pub fn q8_0_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK8_0;
let block_bytes = 34;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(target_arch = "x86_64")]
{
if x86_avx512bw_supported() {
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
output[row] = unsafe { avx512_dot::q8_0_matvec_row(input, row_data, cols) };
}
return;
}
if x86_avx2_supported() {
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
output[row] = unsafe { avx2_dot::q8_0_matvec_row(input, row_data, cols) };
}
return;
}
}
#[cfg(target_arch = "aarch64")]
{
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
output[row] = unsafe { neon_dot::q8_0_matvec_row(input, row_data, cols) };
}
return;
}
#[allow(unreachable_code)]
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let col_base = bi * QK8_0;
for l in 0..QK8_0 {
let w = d * f32::from(row_data[off + 2 + l] as i8);
acc += w * input[col_base + l];
}
}
output[row] = acc;
}
}
pub fn q4_0_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK8_0;
let block_bytes = 18;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let col_base = bi * QK8_0;
for j in 0..16 {
let qs = row_data[off + 2 + j];
let x0 = ((qs & 0x0F) as i32) - 8;
let x1 = ((qs >> 4) as i32) - 8;
acc += d * (x0 as f32) * input[col_base + j];
acc += d * (x1 as f32) * input[col_base + j + 16];
}
}
output[row] = acc;
}
}
pub fn q4_1_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK8_0;
let block_bytes = 20;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let m = f16_to_f32(u16::from_le_bytes([row_data[off + 2], row_data[off + 3]]));
let col_base = bi * QK8_0;
for j in 0..16 {
let qs = row_data[off + 4 + j];
let x0 = (qs & 0x0F) as f32;
let x1 = (qs >> 4) as f32;
acc += (d * x0 + m) * input[col_base + j];
acc += (d * x1 + m) * input[col_base + j + 16];
}
}
output[row] = acc;
}
}
pub fn q5_0_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK8_0;
let block_bytes = 22;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let qh = u32::from_le_bytes([
row_data[off + 2],
row_data[off + 3],
row_data[off + 4],
row_data[off + 5],
]);
let col_base = bi * QK8_0;
for j in 0..16 {
let qs = row_data[off + 6 + j];
let xh_0 = ((qh >> j) << 4) & 0x10;
let xh_1 = (qh >> (j + 12)) & 0x10;
let x0 = (((qs & 0x0F) as u32) | xh_0) as i32 - 16;
let x1 = (((qs >> 4) as u32) | xh_1) as i32 - 16;
acc += d * (x0 as f32) * input[col_base + j];
acc += d * (x1 as f32) * input[col_base + j + 16];
}
}
output[row] = acc;
}
}
pub fn q5_1_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK8_0;
let block_bytes = 24;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let m = f16_to_f32(u16::from_le_bytes([row_data[off + 2], row_data[off + 3]]));
let qh = u32::from_le_bytes([
row_data[off + 4],
row_data[off + 5],
row_data[off + 6],
row_data[off + 7],
]);
let col_base = bi * QK8_0;
for j in 0..16 {
let qs = row_data[off + 8 + j];
let xh_0 = ((qh >> j) << 4) & 0x10;
let xh_1 = (qh >> (j + 12)) & 0x10;
let x0 = ((qs & 0x0F) as u32) | xh_0;
let x1 = ((qs >> 4) as u32) | xh_1;
acc += (d * (x0 as f32) + m) * input[col_base + j];
acc += (d * (x1 as f32) + m) * input[col_base + j + 16];
}
}
output[row] = acc;
}
}
pub fn iq4_xs_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK_K;
let block_bytes = 136;
let row_bytes = blocks_per_row * block_bytes;
for row in 0..rows {
let mut acc = 0.0f32;
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
for bi in 0..blocks_per_row {
let off = bi * block_bytes;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let scales_h = u16::from_le_bytes([row_data[off + 2], row_data[off + 3]]);
let scales_l = [
row_data[off + 4],
row_data[off + 5],
row_data[off + 6],
row_data[off + 7],
];
let qs = &row_data[off + 8..off + 8 + 128];
let col_base = bi * QK_K;
for ib in 0..8 {
let ls_lo = (scales_l[ib / 2] >> (4 * (ib as u8 & 1))) & 0x0F;
let ls_hi = ((scales_h >> (2 * ib)) & 0x3) as u8;
let ls = ((ls_hi << 4) | ls_lo) as i32;
let dl = d * ((ls - 32) as f32);
let qs_base = ib * 16;
let sub_col_base = col_base + ib * 32;
for j in 0..16 {
let byte = qs[qs_base + j];
let n0 = (byte & 0x0F) as usize;
let n1 = (byte >> 4) as usize;
acc += dl * f32::from(KVALUES_IQ4NL[n0]) * input[sub_col_base + j];
acc += dl * f32::from(KVALUES_IQ4NL[n1]) * input[sub_col_base + j + 16];
}
}
}
output[row] = acc;
}
}
pub fn f16_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
for row in 0..rows {
let mut acc = 0.0f32;
let row_start = row * cols * 2;
for col in 0..cols {
let off = row_start + col * 2;
let w = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
acc += w * input[col];
}
output[row] = acc;
}
}
pub fn f32_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
for row in 0..rows {
let mut acc = 0.0f32;
let row_start = row * cols * 4;
for col in 0..cols {
let off = row_start + col * 4;
let w = f32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]]);
acc += w * input[col];
}
output[row] = acc;
}
}
pub fn q6k_matvec(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let q8_blocks = quantize_row_q8_k(input);
q6k_matvec_preq(data, rows, cols, &q8_blocks, output);
}
#[allow(unused_variables)]
pub fn q6k_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 210;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"q6k_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q6k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += q6k_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
fn iq4_xs_q8k_dot(iq4_xs_block: &[u8], q8k: &BlockQ8K) -> f32 {
let d_w = f16_to_f32(u16::from_le_bytes([iq4_xs_block[0], iq4_xs_block[1]]));
let scales_h = u16::from_le_bytes([iq4_xs_block[2], iq4_xs_block[3]]);
let scales_l = [
iq4_xs_block[4],
iq4_xs_block[5],
iq4_xs_block[6],
iq4_xs_block[7],
];
let qs = &iq4_xs_block[8..8 + 128];
let mut sum_i32 = 0i64;
for ib in 0..8 {
let ls_lo = (scales_l[ib / 2] >> (4 * (ib as u8 & 1))) & 0x0F;
let ls_hi = ((scales_h >> (2 * ib)) & 0x3) as u8;
let ls = (((ls_hi << 4) | ls_lo) as i32) - 32;
let qs_base = ib * 16;
let q8_base = ib * 32;
let mut sub_dot = 0i32;
for j in 0..16 {
let byte = qs[qs_base + j];
let n0 = (byte & 0x0F) as usize;
let n1 = (byte >> 4) as usize;
sub_dot += i32::from(KVALUES_IQ4NL[n0]) * i32::from(q8k.qs[q8_base + j]);
sub_dot += i32::from(KVALUES_IQ4NL[n1]) * i32::from(q8k.qs[q8_base + j + 16]);
}
sum_i32 += i64::from(ls) * i64::from(sub_dot);
}
d_w * q8k.d * (sum_i32 as f32)
}
pub fn iq4_xs_matvec_preq(
data: &[u8],
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
let blocks_per_row = cols / QK_K;
let block_bytes = 136;
let row_bytes = blocks_per_row * block_bytes;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
debug_assert!(
output.len() >= rows,
"iq4_xs_matvec_preq: output.len()={} < rows={}",
output.len(),
rows
);
output[..rows]
.par_iter_mut()
.enumerate()
.for_each(|(row, out)| {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += iq4_xs_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
*out = sumf;
});
}
#[cfg(not(feature = "parallel"))]
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut sumf = 0.0f32;
for bi in 0..blocks_per_row {
sumf += iq4_xs_q8k_dot(
&row_data[bi * block_bytes..(bi + 1) * block_bytes],
&q8_blocks[bi],
);
}
output[row] = sumf;
}
}
pub fn quantized_matvec(
input: &[f32],
data: &[u8],
qtype: GgmlType,
rows: usize,
cols: usize,
output: &mut [f32],
) {
match qtype {
GgmlType::Q2_K => q2k_matvec(input, data, rows, cols, output),
GgmlType::Q3_K => q3k_matvec(input, data, rows, cols, output),
GgmlType::Q4_K => q4k_matvec(input, data, rows, cols, output),
GgmlType::Q5_K => q5k_matvec(input, data, rows, cols, output),
GgmlType::Q6_K => q6k_matvec(input, data, rows, cols, output),
GgmlType::Q4_0 => q4_0_matvec(input, data, rows, cols, output),
GgmlType::Q4_1 => q4_1_matvec(input, data, rows, cols, output),
GgmlType::Q5_0 => q5_0_matvec(input, data, rows, cols, output),
GgmlType::Q5_1 => q5_1_matvec(input, data, rows, cols, output),
GgmlType::IQ4_XS => iq4_xs_matvec(input, data, rows, cols, output),
GgmlType::Q8_0 => q8_0_matvec(input, data, rows, cols, output),
GgmlType::F16 => f16_matvec(input, data, rows, cols, output),
GgmlType::F32 => f32_matvec(input, data, rows, cols, output),
GgmlType::Q1_0 => q1_0_matvec_fallback(input, data, rows, cols, output),
GgmlType::Q2_0 => q2_0_matvec_fallback(input, data, rows, cols, output),
GgmlType::Mxfp4 => mxfp4_matvec_dispatched(input, data, rows, cols, output),
GgmlType::IQ1_S => iq1_s_matvec_fallback(input, data, rows, cols, output),
GgmlType::Other(_) => panic!("unsupported quantization type: {qtype:?}"),
}
}
#[cfg(test)]
fn mxfp4_matvec_fallback(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let blocks_per_row = cols / QK_MXFP4;
let row_bytes = blocks_per_row * 17;
let mut row_f32 = vec![0.0f32; cols];
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
dequantize_row_mxfp4(row_data, &mut row_f32);
let mut acc = 0.0f32;
for i in 0..cols {
acc += row_f32[i] * input[i];
}
output[row] = acc;
}
}
fn mxfp4_matvec_fused_scalar(
input: &[f32],
data: &[u8],
rows: usize,
cols: usize,
output: &mut [f32],
) {
assert!(
cols.is_multiple_of(QK_MXFP4),
"cols {cols} must be a multiple of QK_MXFP4 = {QK_MXFP4}"
);
assert!(
input.len() >= cols,
"input length {} < cols {cols}",
input.len()
);
assert!(
output.len() >= rows,
"output length {} < rows {rows}",
output.len()
);
let blocks_per_row = cols / QK_MXFP4;
let row_bytes = blocks_per_row * 17;
assert!(
data.len() >= rows * row_bytes,
"data length {} < rows*row_bytes {}",
data.len(),
rows * row_bytes
);
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
let mut acc = 0.0_f32;
for blk in 0..blocks_per_row {
let block = &row_data[blk * 17..blk * 17 + 17];
let dq = dequantize_mxfp4_block(block);
let inp_off = blk * QK_MXFP4;
for i in 0..QK_MXFP4 {
acc += dq[i] * input[inp_off + i];
}
}
output[row] = acc;
}
}
fn mxfp4_matvec_dispatched(
input: &[f32],
data: &[u8],
rows: usize,
cols: usize,
output: &mut [f32],
) {
assert!(
cols.is_multiple_of(QK_MXFP4),
"cols {cols} must be a multiple of QK_MXFP4 = {QK_MXFP4}"
);
assert!(
input.len() >= cols,
"input length {} < cols {cols}",
input.len()
);
assert!(
output.len() >= rows,
"output length {} < rows {rows}",
output.len()
);
let blocks_per_row = cols / QK_MXFP4;
let row_bytes = blocks_per_row * 17;
assert!(
data.len() >= rows * row_bytes,
"data length {} < rows*row_bytes {}",
data.len(),
rows * row_bytes
);
#[cfg(target_arch = "aarch64")]
{
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
output[row] = unsafe { neon_dot::mxfp4_dot_row(row_data, &input[..cols]) };
}
return;
}
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
for row in 0..rows {
let row_data = &data[row * row_bytes..(row + 1) * row_bytes];
output[row] = unsafe { avx2_dot::mxfp4_dot_row(row_data, &input[..cols]) };
}
return;
}
}
#[allow(unreachable_code)]
mxfp4_matvec_fused_scalar(input, data, rows, cols, output);
}
fn q1_0_matvec_fallback(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let row_bytes = data.len() / rows;
#[cfg(target_arch = "aarch64")]
{
let n_blocks = cols / 128;
let mut block_sums = vec![0.0f32; n_blocks];
for (b, chunk) in input[..cols].as_chunks::<128>().0.iter().enumerate() {
block_sums[b] = chunk.iter().sum();
}
for r in 0..rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
output[r] =
unsafe { neon_dot::q1_0_dot_row_pos_only(row_data, &input[..cols], &block_sums) };
}
return;
}
#[allow(unreachable_code)]
{
let mut row_buf = vec![0.0f32; cols];
for r in 0..rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
dequantize_q1_0(row_data, &mut row_buf);
let mut acc = 0.0f32;
for i in 0..cols {
acc += row_buf[i] * input[i];
}
output[r] = acc;
}
}
}
fn q2_0_matvec_fallback(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
let row_bytes = data.len() / rows;
#[cfg(target_arch = "aarch64")]
{
for r in 0..rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
output[r] = unsafe { neon_dot::q2_0_dot_row(row_data, &input[..cols]) };
}
return;
}
#[allow(unreachable_code)]
{
let mut row_buf = vec![0.0f32; cols];
for r in 0..rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
dequantize_q2_0(row_data, &mut row_buf);
let mut acc = 0.0f32;
for i in 0..cols {
acc += row_buf[i] * input[i];
}
output[r] = acc;
}
}
}
#[allow(dead_code)]
fn iq1_s_matvec_fallback(input: &[f32], data: &[u8], rows: usize, cols: usize, output: &mut [f32]) {
assert!(rows > 0);
let row_bytes = data.len() / rows;
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
output.par_iter_mut().enumerate().for_each(|(r, out)| {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
*out = iq1_s_row_fused_dot(row_data, input, cols);
});
}
#[cfg(not(feature = "parallel"))]
for r in 0..rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
output[r] = iq1_s_row_fused_dot(row_data, input, cols);
}
}
#[allow(dead_code)]
fn iq1_s_row_fused_dot(row_data: &[u8], input: &[f32], cols: usize) -> f32 {
const BLOCK_ELEMS: usize = 256;
const BLOCK_BYTES: usize = 50;
let n_blocks = cols / BLOCK_ELEMS;
let mut block_buf = [0.0_f32; BLOCK_ELEMS];
let mut acc = 0.0_f64;
for b in 0..n_blocks {
let block_data = &row_data[b * BLOCK_BYTES..(b + 1) * BLOCK_BYTES];
crate::iq1_s::dequantize_iq1_s(block_data, &mut block_buf);
let input_slice = &input[b * BLOCK_ELEMS..(b + 1) * BLOCK_ELEMS];
let mut block_acc = 0.0_f32;
for i in 0..BLOCK_ELEMS {
block_acc += block_buf[i] * input_slice[i];
}
acc += f64::from(block_acc);
}
acc as f32
}
pub fn quantized_matvec_preq(
data: &[u8],
qtype: GgmlType,
rows: usize,
cols: usize,
q8_blocks: &[BlockQ8K],
output: &mut [f32],
) {
match qtype {
GgmlType::Q2_K => q2k_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::Q3_K => q3k_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::Q4_K => q4k_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::Q5_K => q5k_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::Q6_K => q6k_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::IQ4_XS => iq4_xs_matvec_preq(data, rows, cols, q8_blocks, output),
GgmlType::Q1_0 | GgmlType::Q2_0 | GgmlType::Q8_0 => {
let mut input_f32 = vec![0.0f32; cols];
for (b, block) in q8_blocks.iter().enumerate() {
let base = b * QK_K;
for i in 0..QK_K {
if base + i >= cols {
break;
}
input_f32[base + i] = block.d * block.qs[i] as f32;
}
}
quantized_matvec(&input_f32, data, qtype, rows, cols, output);
}
_ => panic!(
"quantized_matvec_preq only supports Q2_K-Q6_K, IQ4_XS, Q8_0 and Bonsai Q1_0/Q2_0, got {qtype:?}"
),
}
}
fn gpt2_byte_to_char() -> [char; 256] {
let mut table = ['\0'; 256];
let mut n = 256u32;
for b in 0u32..256 {
let is_printable = matches!(b, 33..=126 | 161..=172 | 174..=255);
let ch = if is_printable {
b
} else {
let c = n;
n += 1;
c
};
table[b as usize] = char::from_u32(ch).unwrap();
}
table
}
fn gpt2_char_to_byte() -> HashMap<char, u8> {
let table = gpt2_byte_to_char();
let mut map = HashMap::with_capacity(256);
for (b, &ch) in table.iter().enumerate() {
map.insert(ch, b as u8);
}
map
}
#[allow(dead_code)]
pub(crate) const TOKEN_TYPE_NORMAL: u32 = 1;
#[allow(dead_code)]
pub(crate) const TOKEN_TYPE_UNKNOWN: u32 = 2;
pub(crate) const TOKEN_TYPE_CONTROL: u32 = 3;
pub(crate) const TOKEN_TYPE_USER_DEFINED: u32 = 4;
#[allow(dead_code)]
pub(crate) const TOKEN_TYPE_UNUSED: u32 = 5;
#[allow(dead_code)]
pub(crate) const TOKEN_TYPE_BYTE: u32 = 6;
pub(crate) const SPM_BYTE_FALLBACK_PREFIX: &str = "<0x";
pub(crate) const SPM_BYTE_FALLBACK_SUFFIX: &str = ">";
pub(crate) const SPM_BYTE_FALLBACK_HEX_LEN: usize = 2;
pub(crate) const GPT2_SPECIAL_PREFIX: &str = "<|";
pub(crate) const GPT2_SPECIAL_SUFFIX: &str = "|>";
pub(crate) fn parse_spm_byte_fallback(s: &str) -> Option<u8> {
let hex = s
.strip_prefix(SPM_BYTE_FALLBACK_PREFIX)?
.strip_suffix(SPM_BYTE_FALLBACK_SUFFIX)?;
if hex.len() != SPM_BYTE_FALLBACK_HEX_LEN {
return None;
}
u8::from_str_radix(hex, 16).ok()
}
pub(crate) const fn is_atomic_special_token(token_type: u32) -> bool {
matches!(token_type, TOKEN_TYPE_CONTROL | TOKEN_TYPE_USER_DEFINED)
}
pub(crate) fn is_gpt2_special_marker(s: &str) -> bool {
s.starts_with(GPT2_SPECIAL_PREFIX) && s.ends_with(GPT2_SPECIAL_SUFFIX)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenizerModel {
Gpt2Bpe,
Spm,
}
pub struct GgufTokenizer {
tokens: Vec<Vec<u8>>,
merges: Vec<(Vec<u8>, Vec<u8>)>,
scores: Vec<f32>,
token_to_id: HashMap<Vec<u8>, u32>,
byte_to_id: [Option<u32>; 256],
special_tokens: Vec<(String, u32)>,
byte_encoder: [char; 256],
byte_decoder: HashMap<char, u8>,
model_type: TokenizerModel,
pub bos_id: u32,
pub eos_id: u32,
pub add_bos_token: bool,
}
#[derive(Clone, Copy)]
struct SpmSymbol {
text_start: usize,
text_len: usize,
prev: i32,
next: i32,
}
#[derive(Clone, Copy)]
struct SpmBigram {
left: usize,
right: usize,
score: f32,
size: usize,
}
impl PartialEq for SpmBigram {
fn eq(&self, other: &Self) -> bool {
self.score == other.score && self.left == other.left
}
}
impl Eq for SpmBigram {}
impl PartialOrd for SpmBigram {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SpmBigram {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.score
.partial_cmp(&other.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| other.left.cmp(&self.left))
}
}
impl GgufTokenizer {
pub fn from_gguf(gguf: &GgufFile<'_>) -> Option<Self> {
let tokens_meta = gguf.meta("tokenizer.ggml.tokens")?;
let token_strs = tokens_meta.as_str_array()?;
let model_type = match gguf.meta_str("tokenizer.ggml.model") {
Some("llama") => TokenizerModel::Spm,
_ => TokenizerModel::Gpt2Bpe,
};
let mut tokens = Vec::with_capacity(token_strs.len());
let mut token_to_id = HashMap::with_capacity(token_strs.len());
let mut special_tokens = Vec::new();
let token_types = gguf
.meta("tokenizer.ggml.token_type")
.and_then(|m| m.as_u32_array());
let mut byte_to_id: [Option<u32>; 256] = [None; 256];
for (i, t) in token_strs.iter().enumerate() {
let bytes = t.as_bytes().to_vec();
token_to_id.insert(bytes.clone(), i as u32);
tokens.push(bytes);
let is_special = match &token_types {
Some(types) => types.get(i).copied().is_some_and(is_atomic_special_token),
None => is_gpt2_special_marker(t),
};
if is_special {
special_tokens.push((t.to_string(), i as u32));
}
if model_type == TokenizerModel::Spm {
if let Some(b) = parse_spm_byte_fallback(t) {
byte_to_id[b as usize] = Some(i as u32);
}
}
}
special_tokens.sort_by_key(|t| core::cmp::Reverse(t.0.len()));
let merges = if let Some(merges_meta) = gguf.meta("tokenizer.ggml.merges") {
if let Some(merge_strs) = merges_meta.as_str_array() {
merge_strs
.iter()
.filter_map(|s| {
let parts: Vec<&str> = s.splitn(2, ' ').collect();
if parts.len() == 2 {
Some((parts[0].as_bytes().to_vec(), parts[1].as_bytes().to_vec()))
} else {
None
}
})
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
};
let scores: Vec<f32> = gguf
.meta("tokenizer.ggml.scores")
.and_then(|m| match m {
MetaValue::Array(arr) => Some(
arr.iter()
.filter_map(MetaValue::as_f32)
.collect::<Vec<f32>>(),
),
_ => None,
})
.unwrap_or_default();
let bos_id = gguf.meta_u32("tokenizer.ggml.bos_token_id").unwrap_or(1);
let eos_id = gguf.meta_u32("tokenizer.ggml.eos_token_id").unwrap_or(2);
let add_bos_token = gguf
.meta_bool("tokenizer.ggml.add_bos_token")
.unwrap_or(true);
Some(Self {
tokens,
merges,
scores,
token_to_id,
byte_to_id,
special_tokens,
byte_encoder: gpt2_byte_to_char(),
byte_decoder: gpt2_char_to_byte(),
model_type,
bos_id,
eos_id,
add_bos_token,
})
}
pub fn encode(&self, text: &str) -> Vec<u32> {
let mut result = Vec::new();
let mut remaining = text;
while !remaining.is_empty() {
let mut matched = false;
for (tok_str, tok_id) in &self.special_tokens {
if remaining.starts_with(tok_str.as_str()) {
result.push(*tok_id);
remaining = &remaining[tok_str.len()..];
matched = true;
break;
}
}
if matched {
continue;
}
let mut next_boundary = remaining.len();
for (tok_str, _) in &self.special_tokens {
if let Some(pos) = remaining.find(tok_str.as_str()) {
if pos > 0 && pos < next_boundary {
next_boundary = pos;
}
}
}
let chunk = &remaining[..next_boundary];
match self.model_type {
TokenizerModel::Gpt2Bpe => result.extend(self.bpe_encode_chunk(chunk)),
TokenizerModel::Spm => result.extend(self.spm_encode_chunk(chunk)),
}
remaining = &remaining[next_boundary..];
}
result
}
fn spm_encode_chunk(&self, text: &str) -> Vec<u32> {
if text.is_empty() {
return Vec::new();
}
let mut preprocessed = String::with_capacity(text.len() + 3);
if !text.starts_with('\u{2581}') {
preprocessed.push('\u{2581}');
}
for c in text.chars() {
if c == ' ' {
preprocessed.push('\u{2581}');
} else {
preprocessed.push(c);
}
}
let bytes = preprocessed.as_bytes();
let mut symbols: Vec<SpmSymbol> = Vec::new();
for (byte_idx, c) in preprocessed.char_indices() {
let clen = c.len_utf8();
let idx = symbols.len();
let prev = if idx == 0 { -1 } else { (idx - 1) as i32 };
symbols.push(SpmSymbol {
text_start: byte_idx,
text_len: clen,
prev,
next: -1,
});
}
let n_symbols = symbols.len();
for i in 0..n_symbols.saturating_sub(1) {
symbols[i].next = (i + 1) as i32;
}
let mut work_queue: std::collections::BinaryHeap<SpmBigram> =
std::collections::BinaryHeap::new();
let try_add = |symbols: &[SpmSymbol],
queue: &mut std::collections::BinaryHeap<SpmBigram>,
left: i32,
right: i32| {
if left < 0 || right < 0 {
return;
}
let l = left as usize;
let r = right as usize;
let start = symbols[l].text_start;
let size = symbols[l].text_len + symbols[r].text_len;
let merged = &bytes[start..start + size];
if let Some(&id) = self.token_to_id.get(merged) {
let score = self
.scores
.get(id as usize)
.copied()
.unwrap_or(f32::NEG_INFINITY);
queue.push(SpmBigram {
left: l,
right: r,
score,
size,
});
}
};
for i in 1..n_symbols {
try_add(&symbols, &mut work_queue, (i - 1) as i32, i as i32);
}
while let Some(bigram) = work_queue.pop() {
let left_len = symbols[bigram.left].text_len;
let right_len = symbols[bigram.right].text_len;
if left_len == 0 || right_len == 0 || left_len + right_len != bigram.size {
continue;
}
symbols[bigram.left].text_len += right_len;
symbols[bigram.right].text_len = 0;
symbols[bigram.left].next = symbols[bigram.right].next;
if symbols[bigram.right].next >= 0 {
let next_idx = symbols[bigram.right].next as usize;
symbols[next_idx].prev = bigram.left as i32;
}
let prev = symbols[bigram.left].prev;
let next = symbols[bigram.left].next;
try_add(&symbols, &mut work_queue, prev, bigram.left as i32);
try_add(&symbols, &mut work_queue, bigram.left as i32, next);
}
let mut result = Vec::new();
let mut idx: i32 = 0;
while idx != -1 {
let sym = symbols[idx as usize];
let segment = &bytes[sym.text_start..sym.text_start + sym.text_len];
if let Some(&id) = self.token_to_id.get(segment) {
result.push(id);
} else {
for &b in segment {
if let Some(id) = self.byte_to_id[b as usize] {
result.push(id);
}
}
}
idx = sym.next;
}
result
}
fn bpe_encode_chunk(&self, text: &str) -> Vec<u32> {
if text.is_empty() {
return Vec::new();
}
let mut pieces: Vec<Vec<u8>> = text
.bytes()
.map(|b| {
let ch = self.byte_encoder[b as usize];
let mut buf = [0u8; 4];
ch.encode_utf8(&mut buf).as_bytes().to_vec()
})
.collect();
for (left, right) in &self.merges {
let mut i = 0;
while i + 1 < pieces.len() {
if pieces[i] == *left && pieces[i + 1] == *right {
let mut merged = pieces[i].clone();
merged.extend_from_slice(&pieces[i + 1]);
pieces[i] = merged;
pieces.remove(i + 1);
} else {
i += 1;
}
}
}
let mut ids = Vec::new();
for p in &pieces {
if let Some(&id) = self.token_to_id.get(p) {
ids.push(id);
} else if let Ok(s) = std::str::from_utf8(p) {
for ch in s.chars() {
let mut buf = [0u8; 4];
let ch_bytes = ch.encode_utf8(&mut buf).as_bytes().to_vec();
if let Some(&id) = self.token_to_id.get(&ch_bytes) {
ids.push(id);
}
}
}
}
ids
}
pub fn decode(&self, ids: &[u32]) -> String {
let mut bytes = Vec::new();
for &id in ids {
let Some(token) = self.tokens.get(id as usize) else {
continue;
};
let Ok(s) = std::str::from_utf8(token) else {
bytes.extend_from_slice(token);
continue;
};
if is_gpt2_special_marker(s) {
continue;
}
if self.model_type == TokenizerModel::Spm
&& s.starts_with('<')
&& s.ends_with(SPM_BYTE_FALLBACK_SUFFIX)
&& !s.starts_with(SPM_BYTE_FALLBACK_PREFIX)
&& !s.contains('\u{2581}')
{
continue;
}
if let Some(byte_val) = parse_spm_byte_fallback(s) {
bytes.push(byte_val);
continue;
}
match self.model_type {
TokenizerModel::Spm => {
for ch in s.chars() {
if ch == '\u{2581}' {
bytes.push(b' ');
} else {
let mut buf = [0u8; 4];
bytes.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
}
}
}
TokenizerModel::Gpt2Bpe => {
for ch in s.chars() {
if let Some(&b) = self.byte_decoder.get(&ch) {
bytes.push(b);
} else {
let mut buf = [0u8; 4];
bytes.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
}
}
}
}
}
String::from_utf8_lossy(&bytes).into_owned()
}
#[must_use]
pub const fn vocab_size(&self) -> usize {
self.tokens.len()
}
}
pub struct TernaryRow {
pub pos_mask: Vec<u8>,
pub neg_mask: Vec<u8>,
pub scale: f32,
pub num_cols: usize,
}
pub struct TernaryMatrix {
pub rows: Vec<TernaryRow>,
pub num_rows: usize,
pub num_cols: usize,
}
impl TernaryRow {
pub fn from_f32(weights: &[f32], threshold_ratio: f32) -> Self {
let num_cols = weights.len();
let num_bytes = num_cols.div_ceil(8);
let mean_abs: f32 = weights.iter().map(|w| w.abs()).sum::<f32>() / num_cols as f32;
let threshold = threshold_ratio * mean_abs;
let mut pos_mask = vec![0u8; num_bytes];
let mut neg_mask = vec![0u8; num_bytes];
let mut abs_sum = 0.0f32;
let mut nonzero_count = 0usize;
for (i, &w) in weights.iter().enumerate() {
if w.abs() > threshold {
let byte_idx = i / 8;
let bit = 1u8 << (i % 8);
if w > 0.0 {
pos_mask[byte_idx] |= bit;
} else {
neg_mask[byte_idx] |= bit;
}
abs_sum += w.abs();
nonzero_count += 1;
}
}
let scale = if nonzero_count > 0 {
abs_sum / nonzero_count as f32
} else {
0.0
};
Self {
pos_mask,
neg_mask,
scale,
num_cols,
}
}
}
impl TernaryMatrix {
pub fn from_quantized(
data: &[u8],
qtype: GgmlType,
num_rows: usize,
num_cols: usize,
threshold_ratio: f32,
) -> Self {
let elems_per_block = qtype.elements_per_block();
let bytes_per_block = qtype.block_bytes();
let blocks_per_row = num_cols / elems_per_block;
let row_bytes = blocks_per_row * bytes_per_block;
let mut rows = Vec::with_capacity(num_rows);
let mut row_f32 = vec![0.0f32; num_cols];
for r in 0..num_rows {
let row_data = &data[r * row_bytes..(r + 1) * row_bytes];
match qtype {
GgmlType::Q2_K => dequantize_row_q2k(row_data, &mut row_f32),
GgmlType::Q3_K => dequantize_row_q3k(row_data, &mut row_f32),
GgmlType::Q4_K => dequantize_row_q4k(row_data, &mut row_f32),
GgmlType::Q5_K => dequantize_row_q5k(row_data, &mut row_f32),
GgmlType::Q6_K => dequantize_row_q6k(row_data, &mut row_f32),
GgmlType::Q8_0 => dequantize_row_q8_0(row_data, &mut row_f32),
GgmlType::F16 => dequantize_row_f16(row_data, &mut row_f32),
GgmlType::F32 => {
for i in 0..num_cols {
row_f32[i] = f32::from_le_bytes([
row_data[i * 4],
row_data[i * 4 + 1],
row_data[i * 4 + 2],
row_data[i * 4 + 3],
]);
}
}
_ => {
row_f32.fill(0.0);
}
}
rows.push(TernaryRow::from_f32(&row_f32, threshold_ratio));
}
Self {
rows,
num_rows,
num_cols,
}
}
}
pub fn ternary_matvec(matrix: &TernaryMatrix, input: &[f32], output: &mut [f32]) {
ternary_matvec_impl(&matrix.rows, input, output);
}
fn ternary_matvec_impl(rows: &[TernaryRow], input: &[f32], output: &mut [f32]) {
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
output.par_iter_mut().enumerate().for_each(|(r, out)| {
*out = ternary_dot_row(&rows[r], input);
});
}
#[cfg(not(feature = "parallel"))]
{
for (r, out) in output.iter_mut().enumerate() {
*out = ternary_dot_row(&rows[r], input);
}
}
}
#[inline]
fn ternary_dot_row(row: &TernaryRow, input: &[f32]) -> f32 {
#[cfg(target_arch = "x86_64")]
{
if x86_avx512bw_supported() {
let unscaled = unsafe {
avx512_dot::ternary_dot_row_unscaled(
&row.pos_mask,
&row.neg_mask,
input,
row.num_cols,
)
};
return row.scale * unscaled;
}
if x86_avx2_supported() {
let unscaled = unsafe {
avx2_dot::ternary_dot_row_unscaled(
&row.pos_mask,
&row.neg_mask,
input,
row.num_cols,
)
};
return row.scale * unscaled;
}
}
#[cfg(target_arch = "aarch64")]
{
let unscaled = unsafe {
neon_dot::ternary_dot_row_unscaled(&row.pos_mask, &row.neg_mask, input, row.num_cols)
};
return row.scale * unscaled;
}
#[allow(unreachable_code)]
ternary_dot_row_scalar(row, input)
}
#[inline]
fn ternary_dot_row_scalar(row: &TernaryRow, input: &[f32]) -> f32 {
let mut pos_sum = 0.0f32;
let mut neg_sum = 0.0f32;
let num_bytes = row.pos_mask.len();
for b in 0..num_bytes {
let pm = row.pos_mask[b];
let nm = row.neg_mask[b];
if (pm | nm) == 0 {
continue;
}
let base = b * 8;
for bit in 0..8 {
let idx = base + bit;
if idx >= row.num_cols {
break;
}
let p = f32::from((pm >> bit) & 1);
let n = f32::from((nm >> bit) & 1);
pos_sum += p * input[idx];
neg_sum += n * input[idx];
}
}
row.scale * (pos_sum - neg_sum)
}
pub const SPARSE_BLOCK: usize = 16;
pub struct SparseTernaryRow {
pub active_masks: Vec<u16>,
pub sign_masks: Vec<u16>,
pub scale: f32,
pub num_cols: usize,
pub num_blocks: usize,
}
pub struct SparseTernaryMatrix {
mask_buf: Vec<u16>,
packed_2bit: Vec<u8>,
packed_blocked: Vec<u8>,
scales: Vec<f32>,
pub num_rows: usize,
pub num_cols: usize,
pub blocks_per_row: usize,
pub target_sparsity: f32,
}
impl SparseTernaryRow {
pub fn from_ternary_row(row: &TernaryRow, original_weights: &[f32], n_keep: usize) -> Self {
let num_cols = row.num_cols;
let num_blocks = num_cols.div_ceil(SPARSE_BLOCK);
let mut active_masks = vec![0u16; num_blocks];
let mut sign_masks = vec![0u16; num_blocks];
let mut abs_sum = 0.0f64;
let mut nonzero_count = 0usize;
for blk in 0..num_blocks {
let base = blk * SPARSE_BLOCK;
let end = (base + SPARSE_BLOCK).min(num_cols);
let block_len = end - base;
let mut candidates: Vec<(usize, f32)> = Vec::new();
for i in 0..block_len {
let global = base + i;
let byte_idx = global / 8;
let bit = 1u8 << (global % 8);
let is_nonzero =
(row.pos_mask[byte_idx] & bit) != 0 || (row.neg_mask[byte_idx] & bit) != 0;
if is_nonzero {
candidates.push((i, original_weights[global].abs()));
}
}
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
candidates.truncate(n_keep.min(block_len));
for &(i, mag) in &candidates {
let global = base + i;
active_masks[blk] |= 1u16 << i;
let byte_idx = global / 8;
let bit = 1u8 << (global % 8);
if (row.neg_mask[byte_idx] & bit) != 0 {
sign_masks[blk] |= 1u16 << i;
}
abs_sum += mag as f64;
nonzero_count += 1;
}
}
let scale = if nonzero_count > 0 {
(abs_sum / nonzero_count as f64) as f32
} else {
row.scale
};
Self {
active_masks,
sign_masks,
scale,
num_cols,
num_blocks,
}
}
pub fn from_f32(weights: &[f32], threshold_ratio: f32, n_keep: usize) -> Self {
let row = TernaryRow::from_f32(weights, threshold_ratio);
Self::from_ternary_row(&row, weights, n_keep)
}
pub fn nnz(&self) -> usize {
self.active_masks
.iter()
.map(|m| m.count_ones() as usize)
.sum()
}
pub fn effective_bits_per_param(&self) -> f32 {
if self.num_cols == 0 {
return 0.0;
}
let mask_bits = self.num_blocks as f32 * 32.0;
let total_bits = mask_bits + 32.0;
total_bits / self.num_cols as f32
}
}
#[allow(dead_code)]
impl SparseTernaryMatrix {
pub fn from_rows(
rows: Vec<SparseTernaryRow>,
num_rows: usize,
num_cols: usize,
target_sparsity: f32,
) -> Self {
let blocks_per_row = num_cols.div_ceil(SPARSE_BLOCK);
let stride = blocks_per_row * 2;
let mut mask_buf = vec![0u16; num_rows * stride];
let mut scales = Vec::with_capacity(num_rows);
let bytes_per_block = SPARSE_BLOCK / 4;
let mut packed_2bit = vec![0u8; num_rows * blocks_per_row * bytes_per_block + 16];
for (r, row) in rows.iter().enumerate() {
let base = r * stride;
mask_buf[base..base + blocks_per_row].copy_from_slice(&row.active_masks);
mask_buf[base + blocks_per_row..base + stride].copy_from_slice(&row.sign_masks);
scales.push(row.scale);
let pack_base = r * blocks_per_row * bytes_per_block;
for blk in 0..blocks_per_row {
let active = row.active_masks[blk];
let sign = row.sign_masks[blk];
for byte_idx in 0..bytes_per_block {
let bit_off = byte_idx * 4;
let mut packed_byte = 0u8;
for j in 0..4 {
let bit = bit_off + j;
let is_active = (active >> bit) & 1;
let is_sign = (sign >> bit) & 1;
let code = if is_active == 0 {
0u8
} else if is_sign == 0 {
1
} else {
3
};
packed_byte |= code << (j * 2);
}
packed_2bit[pack_base + blk * bytes_per_block + byte_idx] = packed_byte;
}
}
}
let num_groups = num_rows.div_ceil(4);
let group_bytes = 4 * blocks_per_row * bytes_per_block; let mut packed_blocked = vec![0u8; num_groups * group_bytes + 16];
for g in 0..num_groups {
let r_base = g * 4;
for blk in 0..blocks_per_row {
for lane in 0..4 {
let r = r_base + lane;
if r < num_rows {
let src_off = r * blocks_per_row * bytes_per_block + blk * bytes_per_block;
let dst_off =
g * group_bytes + blk * 4 * bytes_per_block + lane * bytes_per_block;
packed_blocked[dst_off..dst_off + bytes_per_block]
.copy_from_slice(&packed_2bit[src_off..src_off + bytes_per_block]);
}
}
}
}
Self {
mask_buf,
packed_2bit,
packed_blocked,
scales,
num_rows,
num_cols,
blocks_per_row,
target_sparsity,
}
}
pub fn from_ternary(
dense: &TernaryMatrix,
original_weights_per_row: &[Vec<f32>],
n_keep: usize,
) -> Self {
let target_sparsity = 1.0 - (n_keep as f32 / SPARSE_BLOCK as f32);
let rows: Vec<SparseTernaryRow> = dense
.rows
.iter()
.zip(original_weights_per_row.iter())
.map(|(row, orig)| SparseTernaryRow::from_ternary_row(row, orig, n_keep))
.collect();
Self::from_rows(rows, dense.num_rows, dense.num_cols, target_sparsity)
}
pub fn from_f32_weights(
weights: &[f32],
num_rows: usize,
num_cols: usize,
threshold_ratio: f32,
n_keep: usize,
) -> Self {
let target_sparsity = 1.0 - (n_keep as f32 / SPARSE_BLOCK as f32);
let mut rows = Vec::with_capacity(num_rows);
for r in 0..num_rows {
let row_data = &weights[r * num_cols..(r + 1) * num_cols];
rows.push(SparseTernaryRow::from_f32(
row_data,
threshold_ratio,
n_keep,
));
}
Self::from_rows(rows, num_rows, num_cols, target_sparsity)
}
#[inline]
fn active_masks(&self, row: usize) -> &[u16] {
let base = row * self.blocks_per_row * 2;
&self.mask_buf[base..base + self.blocks_per_row]
}
#[inline]
fn sign_masks(&self, row: usize) -> &[u16] {
let base = row * self.blocks_per_row * 2 + self.blocks_per_row;
&self.mask_buf[base..base + self.blocks_per_row]
}
#[inline]
fn packed_weights(&self, row: usize) -> &[u8] {
let bytes_per_block = SPARSE_BLOCK / 4;
let base = row * self.blocks_per_row * bytes_per_block;
let len = self.blocks_per_row * bytes_per_block;
&self.packed_2bit[base..base + len]
}
pub fn total_nnz(&self) -> usize {
let stride = self.blocks_per_row * 2;
let mut total = 0usize;
for r in 0..self.num_rows {
let base = r * stride;
for b in 0..self.blocks_per_row {
total += self.mask_buf[base + b].count_ones() as usize;
}
}
total
}
pub const fn memory_bytes(&self) -> usize {
self.mask_buf.len() * 2
+ self.packed_2bit.len()
+ self.packed_blocked.len()
+ self.scales.len() * 4
}
}
#[cfg(target_arch = "aarch64")]
fn quantize_activation_i8(input: &[f32]) -> (Vec<i8>, f32) {
let n = input.len();
let padded_len = n.div_ceil(SPARSE_BLOCK) * SPARSE_BLOCK;
#[cfg(target_arch = "aarch64")]
{
unsafe { quantize_activation_i8_neon(input, n, padded_len) }
}
#[cfg(not(target_arch = "aarch64"))]
{
let abs_max = input.iter().fold(0.0f32, |m, x| m.max(x.abs()));
if abs_max == 0.0 {
return (vec![0i8; padded_len], 1.0);
}
let inv_scale = 127.0 / abs_max;
let scale = abs_max / 127.0;
let mut quant = vec![0i8; padded_len];
for (i, &val) in input.iter().enumerate() {
quant[i] = (val * inv_scale).round().max(-127.0).min(127.0) as i8;
}
(quant, scale)
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn quantize_activation_i8_neon(
input: &[f32],
n: usize,
padded_len: usize,
) -> (Vec<i8>, f32) {
use std::arch::aarch64::*;
let mut max_vec = vdupq_n_f32(0.0);
let mut i = 0;
while i + 16 <= n {
let v0 = vabsq_f32(vld1q_f32(input.as_ptr().add(i)));
let v1 = vabsq_f32(vld1q_f32(input.as_ptr().add(i + 4)));
let v2 = vabsq_f32(vld1q_f32(input.as_ptr().add(i + 8)));
let v3 = vabsq_f32(vld1q_f32(input.as_ptr().add(i + 12)));
max_vec = vmaxq_f32(max_vec, vmaxq_f32(vmaxq_f32(v0, v1), vmaxq_f32(v2, v3)));
i += 16;
}
while i + 4 <= n {
let v = vabsq_f32(vld1q_f32(input.as_ptr().add(i)));
max_vec = vmaxq_f32(max_vec, v);
i += 4;
}
let mut abs_max = vmaxvq_f32(max_vec);
while i < n {
abs_max = abs_max.max(input[i].abs());
i += 1;
}
if abs_max == 0.0 {
return (vec![0i8; padded_len], 1.0);
}
let inv_scale = 127.0 / abs_max;
let scale = abs_max / 127.0;
let scale_vec = vdupq_n_f32(inv_scale);
let mut quant = vec![0i8; padded_len];
i = 0;
while i + 16 <= n {
let v0 = vmulq_f32(vld1q_f32(input.as_ptr().add(i)), scale_vec);
let v1 = vmulq_f32(vld1q_f32(input.as_ptr().add(i + 4)), scale_vec);
let v2 = vmulq_f32(vld1q_f32(input.as_ptr().add(i + 8)), scale_vec);
let v3 = vmulq_f32(vld1q_f32(input.as_ptr().add(i + 12)), scale_vec);
let i0 = vcvtnq_s32_f32(v0);
let i1 = vcvtnq_s32_f32(v1);
let i2 = vcvtnq_s32_f32(v2);
let i3 = vcvtnq_s32_f32(v3);
let s01 = vcombine_s16(vqmovn_s32(i0), vqmovn_s32(i1));
let s23 = vcombine_s16(vqmovn_s32(i2), vqmovn_s32(i3));
let result = vcombine_s8(vqmovn_s16(s01), vqmovn_s16(s23));
vst1q_s8(quant.as_mut_ptr().add(i), result);
i += 16;
}
while i < n {
quant[i] = (input[i] * inv_scale).round().max(-127.0).min(127.0) as i8;
i += 1;
}
(quant, scale)
}
pub fn sparse_ternary_matvec(matrix: &SparseTernaryMatrix, input: &[f32], output: &mut [f32]) {
#[cfg(target_arch = "aarch64")]
let (act_i8, act_scale) = quantize_activation_i8(input);
#[cfg(all(target_arch = "aarch64", feature = "parallel"))]
{
use rayon::prelude::*;
output
.par_chunks_mut(8)
.enumerate()
.for_each(|(chunk_idx, chunk)| {
let row_start = chunk_idx * 8;
let mut pos = 0;
while pos + 4 <= chunk.len() {
unsafe {
sparse_ternary_microkernel_4row_sdot(
matrix,
&act_i8,
act_scale,
&mut chunk[pos..pos + 4],
row_start + pos,
);
}
pos += 4;
}
while pos < chunk.len() {
unsafe {
chunk[pos] =
sparse_ternary_dot_sdot(matrix, &act_i8, act_scale, row_start + pos);
}
pos += 1;
}
});
}
#[cfg(all(target_arch = "aarch64", not(feature = "parallel")))]
{
let rows = matrix.num_rows;
let mut r = 0;
while r + 4 <= rows {
unsafe {
sparse_ternary_microkernel_4row_sdot(
matrix,
&act_i8,
act_scale,
&mut output[r..r + 4],
r,
);
}
r += 4;
}
while r < rows {
unsafe {
output[r] = sparse_ternary_dot_sdot(matrix, &act_i8, act_scale, r);
}
r += 1;
}
}
#[cfg(not(target_arch = "aarch64"))]
{
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
output.par_iter_mut().enumerate().for_each(|(r, out)| {
*out = sparse_ternary_dot_flat(matrix, r, input);
});
}
#[cfg(not(feature = "parallel"))]
{
for (r, out) in output.iter_mut().enumerate() {
*out = sparse_ternary_dot_flat(matrix, r, input);
}
}
}
}
#[inline]
#[allow(dead_code)]
fn sparse_ternary_dot_flat(matrix: &SparseTernaryMatrix, row: usize, input: &[f32]) -> f32 {
sparse_ternary_dot_scalar(matrix, row, input)
}
#[cfg(target_arch = "aarch64")]
#[inline]
#[target_feature(enable = "neon")]
unsafe fn sparse_ternary_dot_sdot(
matrix: &SparseTernaryMatrix,
act_i8: &[i8],
act_scale: f32,
row: usize,
) -> f32 {
use std::arch::aarch64::*;
let row_scale = matrix.scales[row];
let blocks = matrix.blocks_per_row;
let packed = matrix.packed_weights(row);
let mut acc = vdupq_n_s32(0);
let lut = vld1q_s8([0i8, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].as_ptr());
let replicate_idx = vld1q_u8([0u8, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3].as_ptr());
let shift_amounts =
vld1q_s8([0i8, -2, -4, -6, 0, -2, -4, -6, 0, -2, -4, -6, 0, -2, -4, -6].as_ptr());
let mask_03 = vdupq_n_u8(0x03);
let bytes_per_block = SPARSE_BLOCK / 4; for blk in 0..blocks {
let aq = vld1q_s8(act_i8.as_ptr().add(blk * SPARSE_BLOCK));
let w = expand_packed_2bit_lut(
packed.as_ptr().add(blk * bytes_per_block),
lut,
replicate_idx,
shift_amounts,
mask_03,
);
acc = sdot_s32(acc, w, aq);
}
let sum_i32 = vaddvq_s32(acc);
row_scale * act_scale * (sum_i32 as f32)
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn sparse_ternary_microkernel_4row_sdot(
matrix: &SparseTernaryMatrix,
act_i8: &[i8],
act_scale: f32,
output: &mut [f32],
row_start: usize,
) {
use std::arch::aarch64::*;
let blocks = matrix.blocks_per_row;
let bytes_per_block = SPARSE_BLOCK / 4;
let mut acc0 = vdupq_n_s32(0);
let mut acc1 = vdupq_n_s32(0);
let mut acc2 = vdupq_n_s32(0);
let mut acc3 = vdupq_n_s32(0);
let lut = vld1q_s8([0i8, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].as_ptr());
let replicate_idx = vld1q_u8([0u8, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3].as_ptr());
let shift_amounts =
vld1q_s8([0i8, -2, -4, -6, 0, -2, -4, -6, 0, -2, -4, -6, 0, -2, -4, -6].as_ptr());
let mask_03 = vdupq_n_u8(0x03);
let group = row_start / 4;
let group_bytes = 4 * blocks * bytes_per_block;
let group_ptr = matrix.packed_blocked.as_ptr().add(group * group_bytes);
let stride_4 = 4 * bytes_per_block;
for blk in 0..blocks {
let aq = vld1q_s8(act_i8.as_ptr().add(blk * SPARSE_BLOCK));
let blk_base = group_ptr.add(blk * stride_4);
let w0 = expand_packed_2bit_lut(blk_base, lut, replicate_idx, shift_amounts, mask_03);
acc0 = sdot_s32(acc0, w0, aq);
let w1 = expand_packed_2bit_lut(
blk_base.add(bytes_per_block),
lut,
replicate_idx,
shift_amounts,
mask_03,
);
acc1 = sdot_s32(acc1, w1, aq);
let w2 = expand_packed_2bit_lut(
blk_base.add(2 * bytes_per_block),
lut,
replicate_idx,
shift_amounts,
mask_03,
);
acc2 = sdot_s32(acc2, w2, aq);
let w3 = expand_packed_2bit_lut(
blk_base.add(3 * bytes_per_block),
lut,
replicate_idx,
shift_amounts,
mask_03,
);
acc3 = sdot_s32(acc3, w3, aq);
}
let s0 = matrix.scales[row_start] * act_scale;
let s1 = matrix.scales[row_start + 1] * act_scale;
let s2 = matrix.scales[row_start + 2] * act_scale;
let s3 = matrix.scales[row_start + 3] * act_scale;
output[0] = s0 * (vaddvq_s32(acc0) as f32);
output[1] = s1 * (vaddvq_s32(acc1) as f32);
output[2] = s2 * (vaddvq_s32(acc2) as f32);
output[3] = s3 * (vaddvq_s32(acc3) as f32);
}
#[cfg(all(target_arch = "aarch64", target_feature = "dotprod"))]
#[inline(always)]
unsafe fn sdot_s32(
acc: std::arch::aarch64::int32x4_t,
a: std::arch::aarch64::int8x16_t,
b: std::arch::aarch64::int8x16_t,
) -> std::arch::aarch64::int32x4_t {
let mut result = acc;
core::arch::asm!(
"sdot {acc:v}.4s, {a:v}.16b, {b:v}.16b",
acc = inout(vreg) result,
a = in(vreg) a,
b = in(vreg) b,
options(nostack, preserves_flags),
);
result
}
#[cfg(all(target_arch = "aarch64", not(target_feature = "dotprod")))]
#[inline(always)]
unsafe fn sdot_s32(
acc: std::arch::aarch64::int32x4_t,
a: std::arch::aarch64::int8x16_t,
b: std::arch::aarch64::int8x16_t,
) -> std::arch::aarch64::int32x4_t {
use std::arch::aarch64::{
vaddq_s32, vget_high_s8, vget_low_s8, vmovl_s8, vmulq_s16, vpaddlq_s16, vpaddq_s32,
};
let a_lo = vmovl_s8(vget_low_s8(a)); let a_hi = vmovl_s8(vget_high_s8(a)); let b_lo = vmovl_s8(vget_low_s8(b));
let b_hi = vmovl_s8(vget_high_s8(b));
let prod_lo = vmulq_s16(a_lo, b_lo); let prod_hi = vmulq_s16(a_hi, b_hi); let pair_lo = vpaddlq_s16(prod_lo); let pair_hi = vpaddlq_s16(prod_hi); let sum = vpaddq_s32(pair_lo, pair_hi);
vaddq_s32(acc, sum)
}
#[cfg(target_arch = "aarch64")]
#[inline(always)]
unsafe fn expand_packed_2bit_lut(
packed_ptr: *const u8,
lut: std::arch::aarch64::int8x16_t,
replicate_idx: std::arch::aarch64::uint8x16_t,
shift_amounts: std::arch::aarch64::int8x16_t,
mask_03: std::arch::aarch64::uint8x16_t,
) -> std::arch::aarch64::int8x16_t {
use std::arch::aarch64::*;
let raw = vld1q_u8(packed_ptr);
let replicated = vqtbl1q_u8(raw, replicate_idx);
let shifted = vreinterpretq_u8_s8(vshlq_s8(vreinterpretq_s8_u8(replicated), shift_amounts));
let indices = vandq_u8(shifted, mask_03);
vqtbl1q_s8(lut, indices)
}
#[inline]
#[allow(dead_code)]
fn sparse_ternary_dot_scalar(matrix: &SparseTernaryMatrix, row: usize, input: &[f32]) -> f32 {
let active = matrix.active_masks(row);
let signs = matrix.sign_masks(row);
let scale = matrix.scales[row];
let mut sum = 0.0f32;
for blk in 0..matrix.blocks_per_row {
let a = active[blk];
if a == 0 {
continue;
}
let s = signs[blk];
let base = blk * SPARSE_BLOCK;
let pos_mask = a & !s;
let neg_mask = a & s;
let mut m = pos_mask;
while m != 0 {
let bit = m.trailing_zeros() as usize;
sum += input[base + bit];
m &= m - 1;
}
m = neg_mask;
while m != 0 {
let bit = m.trailing_zeros() as usize;
sum -= input[base + bit];
m &= m - 1;
}
}
scale * sum
}
pub fn dequantize_weight_row(data: &[u8], qtype: GgmlType, out: &mut [f32]) {
match qtype {
GgmlType::Q2_K => dequantize_row_q2k(data, out),
GgmlType::Q3_K => dequantize_row_q3k(data, out),
GgmlType::Q4_K => dequantize_row_q4k(data, out),
GgmlType::Q5_K => dequantize_row_q5k(data, out),
GgmlType::Q6_K => dequantize_row_q6k(data, out),
GgmlType::Q8_0 => dequantize_row_q8_0(data, out),
GgmlType::Mxfp4 => dequantize_row_mxfp4(data, out),
GgmlType::F16 => {
for i in 0..out.len() {
let off = i * 2;
out[i] = f16_to_f32(u16::from_le_bytes([data[off], data[off + 1]]));
}
}
GgmlType::F32 => {
for i in 0..out.len() {
out[i] = f32::from_le_bytes([
data[i * 4],
data[i * 4 + 1],
data[i * 4 + 2],
data[i * 4 + 3],
]);
}
}
_ => out.fill(0.0),
}
}
fn dequantize_row_q2k(data: &[u8], out: &mut [f32]) {
dequantize_q2_k(data, out);
}
fn dequantize_row_q3k(data: &[u8], out: &mut [f32]) {
dequantize_q3_k(data, out);
}
fn dequantize_row_q5k(data: &[u8], out: &mut [f32]) {
dequantize_q5_k(data, out);
}
fn dequantize_row_q4k(data: &[u8], out: &mut [f32]) {
let blocks = out.len() / QK_K;
let block_size = 144;
for blk in 0..blocks {
let b = &data[blk * block_size..];
let d = f16_to_f32(u16::from_le_bytes([b[0], b[1]]));
let dmin = f16_to_f32(u16::from_le_bytes([b[2], b[3]]));
let scales_raw = &b[4..16];
let qs = &b[16..144];
let mut scales = [0u8; 8];
let mut mins = [0u8; 8];
for i in 0..4 {
scales[i] = scales_raw[i] & 0x3F;
mins[i] = scales_raw[i + 4] & 0x3F;
}
scales[4] = (scales_raw[8] & 0xF) | ((scales_raw[0] >> 6) << 4);
scales[5] = (scales_raw[1 + 8] & 0xF) | ((scales_raw[1] >> 6) << 4);
scales[6] = (scales_raw[2 + 8] & 0xF) | ((scales_raw[2] >> 6) << 4);
scales[7] = (scales_raw[3 + 8] & 0xF) | ((scales_raw[3] >> 6) << 4);
mins[4] = (scales_raw[8] >> 4) | ((scales_raw[4] >> 6) << 4);
mins[5] = (scales_raw[1 + 8] >> 4) | ((scales_raw[5] >> 6) << 4);
mins[6] = (scales_raw[2 + 8] >> 4) | ((scales_raw[6] >> 6) << 4);
mins[7] = (scales_raw[3 + 8] >> 4) | ((scales_raw[7] >> 6) << 4);
for is in 0..8 {
let sc = d * scales[is] as f32;
let mn = dmin * mins[is] as f32;
let off = is * 32;
for l in 0..32 {
let qi = if l < 16 {
let byte_idx = (is / 2) * 32 + l;
if is % 2 == 0 {
qs[byte_idx] & 0xF
} else {
qs[byte_idx] >> 4
}
} else {
let byte_idx = (is / 2) * 32 + 16 + (l - 16);
if is % 2 == 0 {
qs[byte_idx] & 0xF
} else {
qs[byte_idx] >> 4
}
};
out[blk * QK_K + off + l] = sc * qi as f32 - mn;
}
}
}
}
fn dequantize_row_q6k(data: &[u8], out: &mut [f32]) {
let blocks = out.len() / QK_K;
let block_bytes = 210;
for blk in 0..blocks {
let b = &data[blk * block_bytes..];
let d = f16_to_f32(u16::from_le_bytes([b[208], b[209]]));
let ql = &b[0..128];
let qh = &b[128..192];
let scales = &b[192..208];
let mut aux8 = [0i8; QK_K];
let mut a_off = 0usize;
let mut ql_off = 0usize;
let mut qh_off = 0usize;
for _ in 0..2 {
for l in 0..32 {
aux8[a_off + l] = ((ql[ql_off + l] & 0xF) | ((qh[qh_off + l] & 3) << 4)) as i8 - 32;
aux8[a_off + l + 32] =
((ql[ql_off + l + 32] & 0xF) | (((qh[qh_off + l] >> 2) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 64] =
((ql[ql_off + l] >> 4) | (((qh[qh_off + l] >> 4) & 3) << 4)) as i8 - 32;
aux8[a_off + l + 96] =
((ql[ql_off + l + 32] >> 4) | (((qh[qh_off + l] >> 6) & 3) << 4)) as i8 - 32;
}
a_off += 128;
ql_off += 64;
qh_off += 32;
}
let base = blk * QK_K;
for is in 0..16 {
let scale = d * (scales[is] as i8) as f32;
let off = is * 16;
for l in 0..16 {
out[base + off + l] = scale * aux8[off + l] as f32;
}
}
}
}
fn dequantize_row_q8_0(data: &[u8], out: &mut [f32]) {
let blocks = out.len() / QK8_0;
let block_size = 34;
for blk in 0..blocks {
let b = &data[blk * block_size..];
let d = f16_to_f32(u16::from_le_bytes([b[0], b[1]]));
for i in 0..QK8_0 {
out[blk * QK8_0 + i] = d * (b[2 + i] as i8) as f32;
}
}
}
fn dequantize_row_f16(data: &[u8], out: &mut [f32]) {
for i in 0..out.len() {
out[i] = f16_to_f32(u16::from_le_bytes([data[i * 2], data[i * 2 + 1]]));
}
}
pub const E2M1_DECODE_TABLE: [f32; 16] = [
0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0, ];
#[must_use]
pub fn decode_e8m0_scale(byte: u8) -> f32 {
if byte == 0xFF {
return f32::NAN;
}
2.0f32.powi(i32::from(byte) - 127)
}
pub fn dequantize_mxfp4_block(block: &[u8]) -> [f32; QK_MXFP4] {
assert!(
block.len() >= 17,
"MXFP4 block requires 17 bytes, got {}",
block.len()
);
let scale = decode_e8m0_scale(block[0]);
let mut out = [0.0f32; QK_MXFP4];
for i in 0..16 {
let byte = block[1 + i];
let low = usize::from(byte & 0x0F);
let high = usize::from(byte >> 4);
out[2 * i] = E2M1_DECODE_TABLE[low] * scale;
out[2 * i + 1] = E2M1_DECODE_TABLE[high] * scale;
}
out
}
pub fn dequantize_row_mxfp4(data: &[u8], out: &mut [f32]) {
let blocks = out.len() / QK_MXFP4;
for blk in 0..blocks {
let block = &data[blk * 17..blk * 17 + 17];
let dq = dequantize_mxfp4_block(block);
out[blk * QK_MXFP4..(blk + 1) * QK_MXFP4].copy_from_slice(&dq);
}
}
#[derive(Debug, Clone)]
pub struct MxfP4Row {
pub bytes: Vec<u8>,
pub n_elements: usize,
}
impl MxfP4Row {
#[must_use]
pub const fn n_blocks(&self) -> usize {
self.n_elements.div_ceil(QK_MXFP4)
}
pub fn dequantize(&self, out: &mut [f32]) {
assert!(
out.len() >= self.n_elements,
"output buffer too small: got {}, need {}",
out.len(),
self.n_elements
);
let blocks = self.n_blocks();
for blk in 0..blocks {
let start = blk * 17;
let block = &self.bytes[start..start + 17];
let dq = dequantize_mxfp4_block(block);
let out_start = blk * QK_MXFP4;
let take = QK_MXFP4.min(self.n_elements - out_start);
out[out_start..out_start + take].copy_from_slice(&dq[..take]);
}
}
}
#[derive(Debug, Clone)]
pub struct MxfP4Matrix {
pub rows: Vec<MxfP4Row>,
pub n_cols: usize,
}
pub fn mxfp4_matvec(matrix: &MxfP4Matrix, input: &[f32], output: &mut [f32]) {
assert!(
output.len() >= matrix.rows.len(),
"output buffer too small: got {}, need {}",
output.len(),
matrix.rows.len(),
);
assert!(
input.len() >= matrix.n_cols,
"input buffer too small: got {}, need {}",
input.len(),
matrix.n_cols,
);
let n_cols = matrix.n_cols;
for (row_idx, row) in matrix.rows.iter().enumerate() {
assert!(
row.bytes.len() % 17 == 0,
"row {row_idx} bytes length {} is not a multiple of 17",
row.bytes.len(),
);
let mut single_row = [0.0_f32; 1];
mxfp4_matvec_fused_scalar(input, &row.bytes, 1, n_cols, &mut single_row);
output[row_idx] = single_row[0];
}
}
pub const MXFP4_GPU_WORDS_PER_BLOCK: usize = 5;
pub const MXFP4_GPU_BYTES_PER_BLOCK: usize = MXFP4_GPU_WORDS_PER_BLOCK * 4;
#[must_use]
pub fn pack_mxfp4_for_gpu_upload(native_row: &[u8]) -> Vec<u8> {
assert!(
native_row.len().is_multiple_of(17),
"native MXFP4 row length {} is not a multiple of 17",
native_row.len()
);
let n_blocks = native_row.len() / 17;
let mut padded = vec![0u8; n_blocks * MXFP4_GPU_BYTES_PER_BLOCK];
for blk in 0..n_blocks {
let src = &native_row[blk * 17..blk * 17 + 17];
let dst_base = blk * MXFP4_GPU_BYTES_PER_BLOCK;
padded[dst_base] = src[0];
padded[dst_base + 4..dst_base + 20].copy_from_slice(&src[1..17]);
}
padded
}
#[must_use]
pub fn unpack_mxfp4_from_gpu_upload(padded_row: &[u8]) -> Vec<u8> {
assert!(
padded_row.len().is_multiple_of(MXFP4_GPU_BYTES_PER_BLOCK),
"padded MXFP4 row length {} is not a multiple of {MXFP4_GPU_BYTES_PER_BLOCK}",
padded_row.len()
);
let n_blocks = padded_row.len() / MXFP4_GPU_BYTES_PER_BLOCK;
let mut native = vec![0u8; n_blocks * 17];
for blk in 0..n_blocks {
let src_base = blk * MXFP4_GPU_BYTES_PER_BLOCK;
for offset in 1..4 {
assert_eq!(
padded_row[src_base + offset],
0,
"block {blk} padding byte at offset {offset} must be 0, got {}",
padded_row[src_base + offset]
);
}
let dst_base = blk * 17;
native[dst_base] = padded_row[src_base];
native[dst_base + 1..dst_base + 17]
.copy_from_slice(&padded_row[src_base + 4..src_base + 20]);
}
native
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_f16_to_f32_basic() {
assert_eq!(f16_to_f32(0x0000), 0.0);
assert!((f16_to_f32(0x3C00) - 1.0).abs() < 1e-6);
assert!((f16_to_f32(0xBC00) - (-1.0)).abs() < 1e-6);
assert!((f16_to_f32(0x4000) - 2.0).abs() < 1e-6);
assert!((f16_to_f32(0x3800) - 0.5).abs() < 1e-6);
}
#[test]
fn test_f16_inf_nan() {
assert!(f16_to_f32(0x7C00).is_infinite());
assert!(f16_to_f32(0x7C00) > 0.0);
assert!(f16_to_f32(0xFC00).is_infinite());
assert!(f16_to_f32(0xFC00) < 0.0);
assert!(f16_to_f32(0x7C01).is_nan());
}
#[test]
fn test_e2m1_decode_table_bit_exact() {
let expected: [f32; 16] = [
0.0, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, -0.0, -0.5, -1.0, -1.5, -2.0, -3.0, -4.0, -6.0,
];
for (i, &v) in expected.iter().enumerate() {
assert_eq!(
E2M1_DECODE_TABLE[i].to_bits(),
v.to_bits(),
"E2M1 pattern {i:04b} expected {v} but got {}",
E2M1_DECODE_TABLE[i]
);
}
}
#[test]
fn test_e2m1_no_nan_no_inf() {
for &v in &E2M1_DECODE_TABLE {
assert!(v.is_finite(), "E2M1 table contains non-finite value {v}");
}
}
#[test]
fn test_e8m0_scale_decode_endpoints() {
assert!((decode_e8m0_scale(127) - 1.0).abs() < 1e-12);
assert!((decode_e8m0_scale(128) - 2.0).abs() < 1e-12);
assert!((decode_e8m0_scale(126) - 0.5).abs() < 1e-12);
assert!(decode_e8m0_scale(0) > 0.0);
assert!(decode_e8m0_scale(0) < 1e-30);
assert!(decode_e8m0_scale(254) > 1e30);
assert!(decode_e8m0_scale(254).is_finite());
}
#[test]
fn test_e8m0_scale_nan_reserved() {
assert!(decode_e8m0_scale(0xFF).is_nan());
}
#[test]
fn test_mxfp4_block_dequant_identity_scale() {
let mut block = [0u8; 17];
block[0] = 127; for i in 0..16 {
let low = (2 * i) & 0xF;
let high = (2 * i + 1) & 0xF;
block[1 + i] = ((high as u8) << 4) | (low as u8);
}
let out = dequantize_mxfp4_block(&block);
for i in 0..32 {
let expected = E2M1_DECODE_TABLE[i & 0xF];
assert_eq!(
out[i].to_bits(),
expected.to_bits(),
"block[{i}] expected {expected} but got {}",
out[i]
);
}
}
#[test]
fn test_mxfp4_block_dequant_with_scale() {
let mut block = [0u8; 17];
block[0] = 129; for i in 0..16 {
block[1 + i] = 0x77;
}
let out = dequantize_mxfp4_block(&block);
for (i, &v) in out.iter().enumerate() {
assert!(
(v - 24.0).abs() < 1e-6,
"block[{i}]: expected 24.0, got {v}"
);
}
}
#[test]
fn test_mxfp4_block_dequant_signed_symmetric() {
let mut block = [0u8; 17];
block[0] = 127;
for i in 0..16 {
block[1 + i] = (0xB << 4) | 0x3; }
let out = dequantize_mxfp4_block(&block);
for i in 0..32 {
let expected = if i % 2 == 0 { 1.5 } else { -1.5 };
assert!(
(out[i] - expected).abs() < 1e-6,
"block[{i}]: expected {expected}, got {}",
out[i]
);
}
}
#[test]
fn test_mxfp4_row_dequant_multi_block() {
let mut data = vec![0u8; 34];
data[0] = 127;
for i in 0..16 {
data[1 + i] = 0x22;
}
data[17] = 126;
for i in 0..16 {
data[18 + i] = 0x44;
}
let mut out = vec![0.0f32; 64];
dequantize_row_mxfp4(&data, &mut out);
for (i, &v) in out.iter().enumerate() {
assert!((v - 1.0).abs() < 1e-6, "out[{i}]: expected 1.0, got {v}");
}
}
#[test]
fn test_ggml_type_mxfp4_metadata() {
let t = GgmlType::from_u32(39);
assert_eq!(t, GgmlType::Mxfp4);
assert_eq!(t.block_bytes(), 17);
assert_eq!(t.elements_per_block(), QK_MXFP4);
assert_eq!(QK_MXFP4, 32);
}
#[test]
fn test_mxfp4_row_dequantize_via_struct() {
let mut bytes = vec![0u8; 17];
bytes[0] = 127;
for i in 0..16 {
bytes[1 + i] = 0x22; }
let row = MxfP4Row {
bytes,
n_elements: 32,
};
assert_eq!(row.n_blocks(), 1);
let mut out = vec![0.0f32; 32];
row.dequantize(&mut out);
for (i, &v) in out.iter().enumerate() {
assert!((v - 1.0).abs() < 1e-6, "out[{i}]: expected 1.0, got {v}");
}
}
fn mxfp4_synthetic_tensor(rows: usize, cols: usize, scale_byte: u8, nibble: u8) -> Vec<u8> {
assert!(cols.is_multiple_of(QK_MXFP4));
assert!(nibble < 16, "E2M1 index must fit in 4 bits");
let blocks_per_row = cols / QK_MXFP4;
let row_bytes = blocks_per_row * 17;
let mut data = vec![0u8; rows * row_bytes];
let packed = (nibble << 4) | nibble;
for row in 0..rows {
for blk in 0..blocks_per_row {
let base = row * row_bytes + blk * 17;
data[base] = scale_byte;
let bump = ((row + blk) & 0xF) as u8;
let bumped_nibble = (nibble + bump) & 0xF;
let bumped_packed = (bumped_nibble << 4) | bumped_nibble;
for byte in &mut data[base + 1..base + 17] {
*byte = bumped_packed;
}
if blk == 0 {
for byte in &mut data[base + 1..base + 17] {
*byte = packed;
}
}
}
}
data
}
#[test]
fn mxfp4_matvec_fused_scalar_matches_fallback() {
let rows = 4;
let cols = 128;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x7);
let input: Vec<f32> = (0..cols).map(|i| (i as f32) * 0.01 - 0.5).collect();
let mut out_fallback = vec![0.0f32; rows];
let mut out_fused = vec![0.0f32; rows];
mxfp4_matvec_fallback(&input, &data, rows, cols, &mut out_fallback);
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out_fused);
assert_eq!(
out_fallback, out_fused,
"fused kernel must be bit-exact vs fallback"
);
}
#[test]
fn mxfp4_matvec_free_fn_matches_kernel() {
let rows = 3;
let cols = 64;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x5);
let row_bytes = (cols / QK_MXFP4) * 17;
let matrix = MxfP4Matrix {
rows: (0..rows)
.map(|r| MxfP4Row {
bytes: data[r * row_bytes..(r + 1) * row_bytes].to_vec(),
n_elements: cols,
})
.collect(),
n_cols: cols,
};
let input: Vec<f32> = (0..cols).map(|i| (i as f32).sin()).collect();
let mut out_kernel = vec![0.0f32; rows];
let mut out_wrapper = vec![0.0f32; rows];
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out_kernel);
mxfp4_matvec(&matrix, &input, &mut out_wrapper);
assert_eq!(
out_kernel, out_wrapper,
"wrapper must match kernel bit-exact"
);
}
#[test]
fn mxfp4_matvec_zero_input_returns_zero() {
let rows = 2;
let cols = 32;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x9);
let input = vec![0.0f32; cols];
let mut out = vec![f32::NAN; rows];
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out);
for (i, &v) in out.iter().enumerate() {
assert_eq!(
v, 0.0,
"output[{i}] = {v} but zero input must give zero output"
);
}
}
#[test]
fn mxfp4_matvec_unit_input_recovers_row_sum() {
let rows = 2;
let cols = 32;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x7);
let input = vec![1.0f32; cols];
let mut out = vec![0.0f32; rows];
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out);
for row in 0..rows {
let mut row_f32 = vec![0.0f32; cols];
let row_bytes = (cols / QK_MXFP4) * 17;
dequantize_row_mxfp4(&data[row * row_bytes..(row + 1) * row_bytes], &mut row_f32);
let expected: f32 = row_f32.iter().sum();
assert!(
(out[row] - expected).abs() < 1e-4,
"row {row}: kernel {} vs oracle sum {}",
out[row],
expected
);
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn mxfp4_matvec_avx2_matches_scalar() {
if !is_x86_feature_detected!("avx2") || !is_x86_feature_detected!("fma") {
eprintln!("skipping mxfp4_matvec_avx2_matches_scalar: host CPU lacks AVX2+FMA");
return;
}
let rows = 4;
let cols = 128;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x5);
let input: Vec<f32> = (0..cols).map(|i| ((i as f32) * 0.03).sin()).collect();
let mut out_scalar = vec![0.0_f32; rows];
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out_scalar);
let mut out_avx2 = vec![0.0_f32; rows];
mxfp4_matvec_dispatched(&input, &data, rows, cols, &mut out_avx2);
for row in 0..rows {
let s = out_scalar[row];
let a = out_avx2[row];
let abs_err = (s - a).abs();
let rel_err = if s.abs() > 1e-6 {
abs_err / s.abs()
} else {
abs_err
};
assert!(
rel_err < 1e-4,
"row {row}: scalar {s}, avx2 {a}, rel_err {rel_err}"
);
}
}
#[test]
#[cfg(target_arch = "aarch64")]
fn mxfp4_matvec_neon_matches_scalar() {
let rows = 4;
let cols = 128;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x5);
let input: Vec<f32> = (0..cols).map(|i| ((i as f32) * 0.03).sin()).collect();
let mut out_scalar = vec![0.0_f32; rows];
mxfp4_matvec_fused_scalar(&input, &data, rows, cols, &mut out_scalar);
let mut out_neon = vec![0.0_f32; rows];
mxfp4_matvec_dispatched(&input, &data, rows, cols, &mut out_neon);
for row in 0..rows {
let s = out_scalar[row];
let n = out_neon[row];
let abs_err = (s - n).abs();
let rel_err = if s.abs() > 1e-6 {
abs_err / s.abs()
} else {
abs_err
};
assert!(
rel_err < 1e-4,
"row {row}: scalar {s}, neon {n}, rel_err {rel_err}"
);
}
}
#[test]
fn mxfp4_matvec_dispatched_zero_input_returns_zero() {
let rows = 2;
let cols = 32;
let data = mxfp4_synthetic_tensor(rows, cols, 127, 0x9);
let input = vec![0.0_f32; cols];
let mut out = vec![f32::NAN; rows];
mxfp4_matvec_dispatched(&input, &data, rows, cols, &mut out);
for (i, &v) in out.iter().enumerate() {
assert_eq!(v, 0.0, "dispatched output[{i}] = {v} but expected 0.0");
}
}
#[test]
fn mxfp4_matvec_single_block_hand_computed() {
let cols = QK_MXFP4;
assert_eq!(E2M1_DECODE_TABLE[0x2], 1.0);
let scale_byte = 128; assert!((decode_e8m0_scale(scale_byte) - 2.0).abs() < 1e-6);
let mut data = vec![0u8; 17];
data[0] = scale_byte;
for byte in &mut data[1..17] {
*byte = 0x22;
}
let mut input = vec![0.0f32; cols];
input[0] = 1.0;
let mut out = [0.0f32; 1];
mxfp4_matvec_fused_scalar(&input, &data, 1, cols, &mut out);
assert!((out[0] - 2.0).abs() < 1e-6, "expected 2.0, got {}", out[0]);
}
#[test]
fn mxfp4_gpu_pad_constants_are_20_bytes_5_words() {
assert_eq!(MXFP4_GPU_WORDS_PER_BLOCK, 5);
assert_eq!(MXFP4_GPU_BYTES_PER_BLOCK, 20);
}
#[test]
fn pack_mxfp4_places_scale_in_word0_low_byte() {
let mut native = vec![0u8; 17];
native[0] = 42;
for byte in &mut native[1..17] {
*byte = 0xAB;
}
let padded = pack_mxfp4_for_gpu_upload(&native);
assert_eq!(padded.len(), 20);
assert_eq!(padded[0], 42);
for offset in 1..4 {
assert_eq!(padded[offset], 0, "padding byte at {offset} must be 0");
}
for offset in 4..20 {
assert_eq!(padded[offset], 0xAB, "packed byte at {offset} must be 0xAB");
}
}
#[test]
fn pack_mxfp4_roundtrip_preserves_native_layout() {
let n_blocks = 4;
let mut native = vec![0u8; n_blocks * 17];
for blk in 0..n_blocks {
let base = blk * 17;
native[base] = (blk as u8).wrapping_mul(37).wrapping_add(1);
for i in 1..17 {
native[base + i] = ((blk * 17 + i) & 0xFF) as u8;
}
}
let padded = pack_mxfp4_for_gpu_upload(&native);
assert_eq!(padded.len(), n_blocks * 20);
let recovered = unpack_mxfp4_from_gpu_upload(&padded);
assert_eq!(recovered, native, "roundtrip must preserve every byte");
}
#[test]
fn pack_mxfp4_matches_dequant_end_to_end() {
let native = mxfp4_synthetic_tensor(1, 32, 127, 0x5);
assert_eq!(native.len(), 17);
let padded = pack_mxfp4_for_gpu_upload(&native);
let recovered = unpack_mxfp4_from_gpu_upload(&padded);
let mut direct = vec![0.0f32; 32];
dequantize_row_mxfp4(&native, &mut direct);
let mut via_roundtrip = vec![0.0f32; 32];
dequantize_row_mxfp4(&recovered, &mut via_roundtrip);
assert_eq!(
direct, via_roundtrip,
"roundtrip must preserve dequant output"
);
}
#[test]
#[should_panic(expected = "not a multiple of 17")]
fn pack_mxfp4_rejects_malformed_native_length() {
let native = vec![0u8; 16]; let _ = pack_mxfp4_for_gpu_upload(&native);
}
#[test]
#[should_panic(expected = "padding byte at offset 1")]
fn unpack_mxfp4_rejects_dirty_padding() {
let mut padded = vec![0u8; 20];
padded[0] = 42; padded[1] = 0xFF; let _ = unpack_mxfp4_from_gpu_upload(&padded);
}
#[test]
fn test_ggml_type_block_sizes() {
assert_eq!(GgmlType::F32.block_bytes(), 4);
assert_eq!(GgmlType::F16.block_bytes(), 2);
assert_eq!(GgmlType::Q8_0.block_bytes(), 34);
assert_eq!(GgmlType::Q4_K.block_bytes(), 144);
assert_eq!(GgmlType::Q4_K.elements_per_block(), 256);
assert_eq!(GgmlType::Q8_0.elements_per_block(), 32);
assert_eq!(GgmlType::Q1_0.block_bytes(), 18);
assert_eq!(GgmlType::Q1_0.elements_per_block(), 128);
assert_eq!(GgmlType::Q2_0.block_bytes(), 34);
assert_eq!(GgmlType::Q2_0.elements_per_block(), 128);
assert_eq!(GgmlType::from_u32(41), GgmlType::Q1_0);
assert_eq!(GgmlType::from_u32(42), GgmlType::Q2_0);
}
#[test]
fn q1_0_dequantize_binary_ternary() {
let mut data = vec![0u8; 2 * 18];
let scale_1_f16: u16 = 0x3C00; let scale_half_f16: u16 = 0x3800;
data[0..2].copy_from_slice(&scale_1_f16.to_le_bytes());
for b in 0..16 {
data[2 + b] = 0xAA;
}
data[18..20].copy_from_slice(&scale_half_f16.to_le_bytes());
for b in 0..16 {
data[18 + 2 + b] = 0xFF;
}
let mut out = vec![0.0f32; 256];
dequantize_q1_0(&data, &mut out);
for (i, chunk) in out[0..128].as_chunks::<2>().0.iter().enumerate() {
assert!(
(chunk[0] - -1.0).abs() < 1e-4,
"b0 elem {}: got {}, expected -1",
i * 2,
chunk[0]
);
assert!(
(chunk[1] - 1.0).abs() < 1e-4,
"b0 elem {}: got {}, expected +1",
i * 2 + 1,
chunk[1]
);
}
for (i, &v) in out[128..256].iter().enumerate() {
assert!(
(v - 0.5).abs() < 1e-4,
"b1 elem {i}: got {v}, expected +0.5"
);
}
}
#[test]
fn q2_0_dequantize_ternary_values() {
let mut data = vec![0u8; 34];
let scale_2_f16: u16 = 0x4000; data[0..2].copy_from_slice(&scale_2_f16.to_le_bytes());
for b in 0..32 {
data[2 + b] = 0xE4;
}
let mut out = vec![0.0f32; 128];
dequantize_q2_0(&data, &mut out);
let expected = [-2.0, 0.0, 2.0, 4.0];
for (i, &v) in out.iter().enumerate() {
let want = expected[i % 4];
assert!(
(v - want).abs() < 1e-4,
"q2_0 elem {i}: got {v}, expected {want}"
);
}
}
#[test]
fn q2_0_matvec_fallback_sanity() {
let mut data = vec![0u8; 34];
let scale_1_f16: u16 = 0x3C00; data[0..2].copy_from_slice(&scale_1_f16.to_le_bytes());
for b in 0..32 {
data[2 + b] = 0x46;
}
let input: Vec<f32> = (1..=128).map(|i| i as f32).collect();
let mut out = vec![0.0f32; 1];
q2_0_matvec_fallback(&input, &data, 1, 128, &mut out);
assert!(
(out[0] - -64.0).abs() < 1e-3,
"q2_0 matvec: got {}, expected −64",
out[0]
);
}
#[test]
fn q1_0_matvec_matches_scalar_ref() {
let rows = 4usize;
let cols = 256usize;
let blocks_per_row = cols / 128;
let row_bytes = blocks_per_row * 18;
let mut data = vec![0u8; rows * row_bytes];
for r in 0..rows {
for b in 0..blocks_per_row {
let base = r * row_bytes + b * 18;
let scale_f16: u16 = 0x3800u16.wrapping_add((r as u16) * 0x0080);
data[base..base + 2].copy_from_slice(&scale_f16.to_le_bytes());
for k in 0..16 {
data[base + 2 + k] = ((r as u8)
.wrapping_mul(37)
.wrapping_add((b as u8) * 11 + k as u8))
^ 0xA5;
}
}
}
let input: Vec<f32> = (0..cols).map(|i| ((i as f32) * 0.037).sin()).collect();
let mut out_simd = vec![0.0f32; rows];
q1_0_matvec_fallback(&input, &data, rows, cols, &mut out_simd);
let mut out_ref = vec![0.0f32; rows];
let mut row_buf = vec![0.0f32; cols];
for r in 0..rows {
let rd = &data[r * row_bytes..(r + 1) * row_bytes];
dequantize_q1_0(rd, &mut row_buf);
let mut acc = 0.0f32;
for i in 0..cols {
acc += row_buf[i] * input[i];
}
out_ref[r] = acc;
}
for (r, (&s, &g)) in out_simd.iter().zip(out_ref.iter()).enumerate() {
let denom = g.abs().max(1e-6);
let rel = (s - g).abs() / denom;
assert!(
rel < 1e-4,
"q1_0 row {r}: simd={s} scalar={g} rel_err={rel}"
);
}
}
#[test]
fn q2_0_matvec_matches_scalar_ref() {
let rows = 4usize;
let cols = 256usize;
let blocks_per_row = cols / 128;
let row_bytes = blocks_per_row * 34;
let mut data = vec![0u8; rows * row_bytes];
for r in 0..rows {
for b in 0..blocks_per_row {
let base = r * row_bytes + b * 34;
let scale_f16: u16 = 0x3800u16.wrapping_add((r as u16) * 0x0080);
data[base..base + 2].copy_from_slice(&scale_f16.to_le_bytes());
for k in 0..32 {
data[base + 2 + k] = ((r as u8)
.wrapping_mul(53)
.wrapping_add((b as u8) * 7 + k as u8))
^ 0x3C;
}
}
}
let input: Vec<f32> = (0..cols).map(|i| ((i as f32) * 0.041).cos()).collect();
let mut out_simd = vec![0.0f32; rows];
q2_0_matvec_fallback(&input, &data, rows, cols, &mut out_simd);
let mut out_ref = vec![0.0f32; rows];
let mut row_buf = vec![0.0f32; cols];
for r in 0..rows {
let rd = &data[r * row_bytes..(r + 1) * row_bytes];
dequantize_q2_0(rd, &mut row_buf);
let mut acc = 0.0f32;
for i in 0..cols {
acc += row_buf[i] * input[i];
}
out_ref[r] = acc;
}
for (r, (&s, &g)) in out_simd.iter().zip(out_ref.iter()).enumerate() {
let denom = g.abs().max(1e-6);
let rel = (s - g).abs() / denom;
assert!(
rel < 1e-4,
"q2_0 row {r}: simd={s} scalar={g} rel_err={rel}"
);
}
}
#[test]
fn test_q8_0_dequantize_roundtrip() {
let mut block = vec![0u8; 34];
let scale_f16: u16 = 0x2E66;
block[0] = scale_f16 as u8;
block[1] = (scale_f16 >> 8) as u8;
for i in 0..32 {
block[2 + i] = i as u8;
}
let mut out = vec![0.0f32; 32];
dequantize_q8_0(&block, &mut out);
let scale = f16_to_f32(scale_f16);
for i in 0..32 {
let expected = scale * i as f32;
assert!(
(out[i] - expected).abs() < 1e-4,
"q8_0[{i}]: got {}, expected {expected}",
out[i]
);
}
}
#[test]
fn test_q8_0_fused_matvec() {
let mut data = vec![0u8; 2 * 34];
let scale: u16 = 0x3C00;
data[0] = scale as u8;
data[1] = (scale >> 8) as u8;
data[2] = 1;
data[34] = scale as u8;
data[35] = (scale >> 8) as u8;
data[36] = 0; data[37] = 1;
let mut input = vec![0.0f32; 32];
input[0] = 3.0;
input[1] = 7.0;
let mut output = vec![0.0f32; 2];
q8_0_matvec(&input, &data, 2, 32, &mut output);
assert!((output[0] - 3.0).abs() < 1e-4);
assert!((output[1] - 7.0).abs() < 1e-4);
}
#[test]
fn test_get_scale_min_k4() {
let scales = [10, 20, 30, 40, 50, 60, 70, 80, 0xAB, 0xCD, 0xEF, 0x12];
let (sc, m) = get_scale_min_k4(0, &scales);
assert_eq!(sc, 10); assert_eq!(m, 50);
let (sc, m) = get_scale_min_k4(3, &scales);
assert_eq!(sc, 40); assert_eq!(m, 16); }
#[test]
fn test_gguf_parse_invalid_magic() {
let data = [0u8; 64];
assert!(GgufFile::parse(&data).is_none());
}
#[test]
fn test_tensor_info_n_elements() {
let info = TensorInfo {
name: "test".into(),
n_dims: 2,
dims: vec![4096, 4096],
qtype: GgmlType::Q4_K,
offset: 0,
};
assert_eq!(info.n_elements(), 4096 * 4096);
}
#[test]
fn test_tensor_info_data_size() {
let info = TensorInfo {
name: "test".into(),
n_dims: 2,
dims: vec![4096, 4096],
qtype: GgmlType::Q4_K,
offset: 0,
};
let n = 4096 * 4096;
let n_blocks = n / 256;
assert_eq!(info.data_size(), n_blocks * 144);
}
#[test]
fn test_meta_value_conversions() {
let u = MetaValue::U32(42);
assert_eq!(u.as_u32(), Some(42));
assert_eq!(u.as_f32(), None);
let f = MetaValue::F32(3.14);
assert!((f.as_f32().unwrap() - 3.14).abs() < 1e-6);
let s = MetaValue::Str("hello".into());
assert_eq!(s.as_str(), Some("hello"));
}
#[test]
fn test_sparse_ternary_row_from_f32() {
let weights: Vec<f32> = (0..32).map(|i| (i as f32 - 15.5) * 0.1).collect();
let row = SparseTernaryRow::from_f32(&weights, 0.3, 8);
assert_eq!(row.num_cols, 32);
assert_eq!(row.num_blocks, 2);
assert!(row.active_masks[0].count_ones() <= 8);
assert!(row.active_masks[1].count_ones() <= 8);
assert!(row.scale > 0.0);
}
#[test]
fn test_sparse_ternary_enforces_nm_sparsity() {
let weights: Vec<f32> = vec![
1.0, -2.0, 0.5, -0.1, 3.0, -0.3, 0.8, -0.05, 0.02, -1.5, 0.7, -0.9, 0.01, -0.4, 2.5,
-0.6,
];
let row = SparseTernaryRow::from_f32(&weights, 0.1, 4);
assert_eq!(row.active_masks[0].count_ones(), 4);
assert_eq!(row.nnz(), 4);
}
#[test]
fn test_sparse_ternary_matvec_matches_dense() {
let weights = vec![
1.0, -2.0, 0.5, -0.1, 3.0, -0.3, 0.8, -0.05, 0.02, -1.5, 0.7, -0.9, 0.01, -0.4, 2.5,
-0.6, -1.0, 2.0, -0.5, 0.1, -3.0, 0.3, -0.8, 0.05, -0.02, 1.5, -0.7, 0.9, -0.01, 0.4, -2.5,
0.6,
];
let num_rows = 2;
let num_cols = 16;
let dense = TernaryMatrix {
rows: (0..num_rows)
.map(|r| TernaryRow::from_f32(&weights[r * num_cols..(r + 1) * num_cols], 0.05))
.collect(),
num_rows,
num_cols,
};
let sparse = SparseTernaryMatrix::from_f32_weights(&weights, num_rows, num_cols, 0.05, 16);
let input = vec![
1.0, 0.5, -1.0, 2.0, 0.3, -0.7, 1.5, -0.2, 0.8, -1.2, 0.4, 0.6, -0.9, 1.1, -0.3, 0.7,
];
let mut dense_out = vec![0.0f32; num_rows];
let mut sparse_out = vec![0.0f32; num_rows];
ternary_matvec(&dense, &input, &mut dense_out);
sparse_ternary_matvec(&sparse, &input, &mut sparse_out);
for i in 0..num_rows {
let tol = dense_out[i].abs().max(0.01) * 0.05;
assert!(
(dense_out[i] - sparse_out[i]).abs() < tol,
"row {i}: dense={} sparse={}",
dense_out[i],
sparse_out[i]
);
}
}
#[test]
fn test_sparse_ternary_memory_reduction() {
let weights: Vec<f32> = (0..1024).map(|i| ((i as f32 * 0.7).sin()) * 2.0).collect();
let sparse = SparseTernaryMatrix::from_f32_weights(&weights, 1, 1024, 0.5, 8);
let nnz = sparse.total_nnz();
assert!(nnz <= 1024 / 2 + 64, "nnz={nnz} should be ~512");
assert!(nnz > 0);
let mem = sparse.memory_bytes();
assert!(mem > 0 && mem < 1024 * 1024, "memory={mem} bytes");
}
#[test]
fn test_sparse_ternary_dot_correctness() {
let weights = vec![
0.0, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -3.0, 0.0, 0.0, 0.0, 0.0, 0.0,
];
let row = SparseTernaryRow::from_f32(&weights, 0.01, 2);
assert_eq!(row.nnz(), 2);
let scale = row.scale;
let matrix = SparseTernaryMatrix::from_rows(vec![row], 1, 16, 0.5);
let mut input_test = vec![0.0f32; 16];
input_test[3] = 10.0;
input_test[10] = 7.0;
let mut output = vec![0.0f32; 1];
sparse_ternary_matvec(&matrix, &input_test, &mut output);
let expected = scale * 3.0;
assert!(
(output[0] - expected).abs() < expected.abs() * 0.05,
"result={}, expected={expected}",
output[0]
);
}
#[cfg(target_arch = "aarch64")]
#[test]
fn sdot_s32_matches_manual_reference() {
use std::arch::aarch64::{vdupq_n_s32, vld1q_s8, vst1q_s32};
unsafe {
let acc = vdupq_n_s32(0);
let a_arr: [i8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let b_arr: [i8; 16] = [1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1];
let a = vld1q_s8(a_arr.as_ptr());
let b = vld1q_s8(b_arr.as_ptr());
let result = super::sdot_s32(acc, a, b);
let mut out = [0i32; 4];
vst1q_s32(out.as_mut_ptr(), result);
assert_eq!(out, [-2, -2, -2, -2]);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn sdot_s32_accumulates_onto_seed() {
use std::arch::aarch64::{vld1q_s32, vld1q_s8, vst1q_s32};
unsafe {
let seed: [i32; 4] = [100, 200, 300, 400];
let acc = vld1q_s32(seed.as_ptr());
let a_arr: [i8; 16] = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
let b_arr: [i8; 16] = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
let a = vld1q_s8(a_arr.as_ptr());
let b = vld1q_s8(b_arr.as_ptr());
let result = super::sdot_s32(acc, a, b);
let mut out = [0i32; 4];
vst1q_s32(out.as_mut_ptr(), result);
assert_eq!(out, [108, 208, 308, 408]);
}
}
#[test]
fn token_type_constants_stable() {
assert_eq!(TOKEN_TYPE_NORMAL, 1);
assert_eq!(TOKEN_TYPE_UNKNOWN, 2);
assert_eq!(TOKEN_TYPE_CONTROL, 3);
assert_eq!(TOKEN_TYPE_USER_DEFINED, 4);
assert_eq!(TOKEN_TYPE_UNUSED, 5);
assert_eq!(TOKEN_TYPE_BYTE, 6);
}
#[test]
fn is_atomic_special_token_matches_control_and_user_defined() {
assert!(is_atomic_special_token(TOKEN_TYPE_CONTROL));
assert!(is_atomic_special_token(TOKEN_TYPE_USER_DEFINED));
assert!(!is_atomic_special_token(TOKEN_TYPE_NORMAL));
assert!(!is_atomic_special_token(TOKEN_TYPE_UNKNOWN));
assert!(!is_atomic_special_token(TOKEN_TYPE_UNUSED));
assert!(!is_atomic_special_token(TOKEN_TYPE_BYTE));
}
#[test]
fn parse_spm_byte_fallback_valid() {
assert_eq!(parse_spm_byte_fallback("<0x00>"), Some(0x00));
assert_eq!(parse_spm_byte_fallback("<0xFF>"), Some(0xFF));
assert_eq!(parse_spm_byte_fallback("<0xAB>"), Some(0xAB));
assert_eq!(parse_spm_byte_fallback("<0xab>"), Some(0xAB));
assert_eq!(parse_spm_byte_fallback("<0xA5>"), Some(0xA5));
}
#[test]
fn parse_spm_byte_fallback_rejects_invalid() {
assert_eq!(parse_spm_byte_fallback("<0xG0>"), None);
assert_eq!(parse_spm_byte_fallback("<0x0>"), None);
assert_eq!(parse_spm_byte_fallback("<0x000>"), None);
assert_eq!(parse_spm_byte_fallback("0xFF"), None);
assert_eq!(parse_spm_byte_fallback("<|endoftext|>"), None);
assert_eq!(parse_spm_byte_fallback(""), None);
assert_eq!(parse_spm_byte_fallback("<unk>"), None);
}
#[test]
fn is_gpt2_special_marker_matches_expected_pattern() {
assert!(is_gpt2_special_marker("<|endoftext|>"));
assert!(is_gpt2_special_marker("<|im_start|>"));
assert!(is_gpt2_special_marker("<|im_end|>"));
assert!(!is_gpt2_special_marker("<unk>"));
assert!(!is_gpt2_special_marker("<0xFF>"));
assert!(!is_gpt2_special_marker("hello"));
assert!(!is_gpt2_special_marker(""));
}
#[cfg(target_arch = "x86_64")]
fn make_q4k_block(seed: u8) -> Vec<u8> {
let mut block = vec![0u8; 144];
block[0..2].copy_from_slice(&0x3800u16.to_le_bytes()); block[2..4].copy_from_slice(&0x3400u16.to_le_bytes()); for i in 0..12 {
block[4 + i] = seed.wrapping_add(i as u8);
}
for i in 0..128 {
block[16 + i] = seed.wrapping_mul(3).wrapping_add(i as u8);
}
block
}
#[cfg(target_arch = "x86_64")]
fn make_q6k_block(seed: u8) -> Vec<u8> {
let mut block = vec![0u8; 210];
for i in 0..128 {
block[i] = seed.wrapping_add(i as u8);
}
for i in 0..64 {
block[128 + i] = seed.wrapping_mul(5).wrapping_add(i as u8);
}
for i in 0..16 {
block[192 + i] = (i as u8).wrapping_sub(4);
}
block[208..210].copy_from_slice(&0x3800u16.to_le_bytes());
block
}
#[cfg(target_arch = "x86_64")]
fn make_q8k_block(seed: u8) -> BlockQ8K {
let mut block = BlockQ8K {
d: 0.125,
qs: [0i8; QK_K],
bsums: [0i16; 16],
};
for i in 0..QK_K {
let raw = seed.wrapping_add(i as u8);
block.qs[i] = (raw as i8).wrapping_sub(64);
}
for j in 0..16 {
let mut acc = 0i32;
for l in 0..16 {
acc += block.qs[j * 16 + l] as i32;
}
block.bsums[j] = acc as i16;
}
block
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q4k_avx2_matches_scalar_bit_exact() {
if !x86_avx2_supported() {
eprintln!("SKIP: AVX2 unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q4k = make_q4k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(13));
let scalar = q4k_q8k_dot_fallback_scalar(&q4k, &q8k);
let simd = unsafe { avx2_dot::q4k_q8k_dot(&q4k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q6k_avx2_matches_scalar_bit_exact() {
if !x86_avx2_supported() {
eprintln!("SKIP: AVX2 unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q6k = make_q6k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(11));
let scalar = q6k_q8k_dot_fallback_scalar(&q6k, &q8k);
let simd = unsafe { avx2_dot::q6k_q8k_dot(&q6k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q4k_avx512_matches_scalar_bit_exact() {
if !x86_avx512bw_supported() {
eprintln!("SKIP: AVX-512BW unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q4k = make_q4k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(13));
let scalar = q4k_q8k_dot_fallback_scalar(&q4k, &q8k);
let simd = unsafe { avx512_dot::q4k_q8k_dot(&q4k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q6k_avx512_matches_scalar_bit_exact() {
if !x86_avx512bw_supported() {
eprintln!("SKIP: AVX-512BW unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q6k = make_q6k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(11));
let scalar = q6k_q8k_dot_fallback_scalar(&q6k, &q8k);
let simd = unsafe { avx512_dot::q6k_q8k_dot(&q6k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn feature_detection_caches_result() {
let first = x86_avx2_supported();
let second = x86_avx2_supported();
assert_eq!(first, second);
let first_512 = x86_avx512bw_supported();
let second_512 = x86_avx512bw_supported();
assert_eq!(first_512, second_512);
}
#[cfg(target_arch = "x86_64")]
fn make_q5k_block(seed: u8) -> Vec<u8> {
let mut block = vec![0u8; 176];
block[0..2].copy_from_slice(&0x3800u16.to_le_bytes()); block[2..4].copy_from_slice(&0x3400u16.to_le_bytes()); for i in 0..12 {
block[4 + i] = seed.wrapping_add(i as u8);
}
for i in 0..32 {
block[16 + i] = seed.wrapping_mul(7).wrapping_add(i as u8);
}
for i in 0..128 {
block[48 + i] = seed.wrapping_mul(3).wrapping_add(i as u8);
}
block
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q5k_avx2_matches_scalar_bit_exact() {
if !x86_avx2_supported() {
eprintln!("SKIP: AVX2 unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q5k = make_q5k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(17));
let scalar = q5k_q8k_dot_fallback_scalar(&q5k, &q8k);
let simd = unsafe { avx2_dot::q5k_q8k_dot(&q5k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q5k_avx512_matches_scalar_bit_exact() {
if !x86_avx512bw_supported() {
eprintln!("SKIP: AVX-512BW unavailable on this runner");
return;
}
for seed in [0u8, 1, 7, 128, 200, 255] {
let q5k = make_q5k_block(seed);
let q8k = make_q8k_block(seed.wrapping_mul(17));
let scalar = q5k_q8k_dot_fallback_scalar(&q5k, &q8k);
let simd = unsafe { avx512_dot::q5k_q8k_dot(&q5k, &q8k) };
assert_eq!(
scalar.to_bits(),
simd.to_bits(),
"seed={seed}: scalar={scalar} simd={simd}"
);
}
}
#[cfg(target_arch = "x86_64")]
fn make_q8_0_row_bytes(seed: u8, cols: usize) -> Vec<u8> {
let blocks_per_row = cols / QK8_0;
let mut data = Vec::with_capacity(blocks_per_row * 34);
for bi in 0..blocks_per_row {
let scale_bits = 0x3800u16.wrapping_add(bi as u16); data.extend_from_slice(&scale_bits.to_le_bytes());
for l in 0..QK8_0 {
let raw = seed.wrapping_add((bi * QK8_0 + l) as u8);
data.push((raw as i8).wrapping_sub(32) as u8);
}
}
data
}
#[cfg(target_arch = "x86_64")]
fn make_q8_0_input(seed: u8, cols: usize) -> Vec<f32> {
(0..cols)
.map(|i| {
let raw = seed.wrapping_add(i as u8) as i8;
f32::from(raw) * 0.01
})
.collect()
}
#[cfg(target_arch = "x86_64")]
#[test]
fn q8_0_avx2_matches_scalar_relative_tolerance() {
if !x86_avx2_supported() {
eprintln!("SKIP: AVX2 unavailable on this runner");
return;
}
const COLS: usize = 128;
for seed in [0u8, 1, 7, 128, 200, 255] {
let data = make_q8_0_row_bytes(seed, COLS);
let input = make_q8_0_input(seed.wrapping_mul(3), COLS);
let mut scalar_out = [0.0f32];
let mut simd_out = [0.0f32];
let row_data = &data[..];
let mut acc = 0.0f32;
for bi in 0..(COLS / QK8_0) {
let off = bi * 34;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let col_base = bi * QK8_0;
for l in 0..QK8_0 {
let w = d * f32::from(row_data[off + 2 + l] as i8);
acc += w * input[col_base + l];
}
}
scalar_out[0] = acc;
q8_0_matvec(&input, row_data, 1, COLS, &mut simd_out);
let diff = (scalar_out[0] - simd_out[0]).abs();
let rel = diff / scalar_out[0].abs().max(1.0);
assert!(
rel < 1e-4,
"seed={seed}: scalar={} simd={} rel={rel}",
scalar_out[0],
simd_out[0]
);
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn make_ternary_row(seed: u8, num_cols: usize, scale: f32) -> TernaryRow {
let num_bytes = num_cols.div_ceil(8);
let mut pos_mask = vec![0u8; num_bytes];
let mut neg_mask = vec![0u8; num_bytes];
for i in 0..num_cols {
let raw = seed.wrapping_mul(3).wrapping_add(i as u8) as i8;
let byte_idx = i / 8;
let bit = 1u8 << (i % 8);
if raw > 20 {
pos_mask[byte_idx] |= bit;
} else if raw < -20 {
neg_mask[byte_idx] |= bit;
}
}
TernaryRow {
pos_mask,
neg_mask,
scale,
num_cols,
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn make_ternary_input(seed: u8, num_cols: usize) -> Vec<f32> {
(0..num_cols)
.map(|i| f32::from(seed.wrapping_add(i as u8) as i8) * 0.01)
.collect()
}
#[cfg(target_arch = "x86_64")]
#[test]
fn ternary_avx2_matches_scalar_relative_tolerance() {
if !x86_avx2_supported() {
eprintln!("SKIP: AVX2 unavailable on this runner");
return;
}
const COLS: usize = 128;
for seed in [0u8, 1, 7, 128, 200, 255] {
let row = make_ternary_row(seed, COLS, 0.125);
let input = make_ternary_input(seed.wrapping_mul(7), COLS);
let scalar = ternary_dot_row_scalar(&row, &input);
let simd = ternary_dot_row(&row, &input);
let diff = (scalar - simd).abs();
let rel = diff / scalar.abs().max(1e-6);
assert!(
rel < 1e-4,
"seed={seed}: scalar={scalar} simd={simd} rel={rel}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn ternary_avx2_handles_non_multiple_of_8_cols() {
if !x86_avx2_supported() {
return;
}
const COLS: usize = 100;
for seed in [1u8, 42, 200] {
let row = make_ternary_row(seed, COLS, 0.5);
let input = make_ternary_input(seed.wrapping_mul(11), COLS);
let scalar = ternary_dot_row_scalar(&row, &input);
let simd = ternary_dot_row(&row, &input);
let rel = (scalar - simd).abs() / scalar.abs().max(1e-6);
assert!(
rel < 1e-4,
"seed={seed}: scalar={scalar} simd={simd} rel={rel}"
);
}
}
#[cfg(target_arch = "x86_64")]
#[test]
fn ternary_avx512_matches_scalar_relative_tolerance() {
if !x86_avx512bw_supported() {
eprintln!("SKIP: AVX-512BW unavailable on this runner");
return;
}
const COLS: usize = 128;
for seed in [0u8, 1, 7, 128, 200, 255] {
let row = make_ternary_row(seed, COLS, 0.125);
let input = make_ternary_input(seed.wrapping_mul(7), COLS);
let scalar = ternary_dot_row_scalar(&row, &input);
let simd = ternary_dot_row(&row, &input);
let rel = (scalar - simd).abs() / scalar.abs().max(1e-6);
assert!(
rel < 1e-4,
"seed={seed}: scalar={scalar} simd={simd} rel={rel}"
);
}
}
#[cfg(target_arch = "aarch64")]
fn make_q4k_block_aarch64(seed: u8) -> Vec<u8> {
let mut block = vec![0u8; 144];
block[0..2].copy_from_slice(&0x3800u16.to_le_bytes());
block[2..4].copy_from_slice(&0x3400u16.to_le_bytes());
for i in 0..12 {
block[4 + i] = seed.wrapping_add(i as u8);
}
for i in 0..128 {
block[16 + i] = seed.wrapping_mul(3).wrapping_add(i as u8);
}
block
}
#[cfg(target_arch = "aarch64")]
fn make_q6k_block_aarch64(seed: u8) -> Vec<u8> {
let mut block = vec![0u8; 210];
for i in 0..128 {
block[i] = seed.wrapping_add(i as u8);
}
for i in 0..64 {
block[128 + i] = seed.wrapping_mul(5).wrapping_add(i as u8);
}
for i in 0..16 {
block[192 + i] = (i as u8).wrapping_sub(4);
}
block[208..210].copy_from_slice(&0x3800u16.to_le_bytes());
block
}
#[cfg(target_arch = "aarch64")]
fn make_q8k_block_aarch64(seed: u8) -> BlockQ8K {
let mut block = BlockQ8K {
d: 0.125,
qs: [0i8; QK_K],
bsums: [0i16; 16],
};
for i in 0..QK_K {
let raw = seed.wrapping_add(i as u8);
block.qs[i] = (raw as i8).wrapping_sub(64);
}
for j in 0..16 {
let mut acc = 0i32;
for l in 0..16 {
acc += block.qs[j * 16 + l] as i32;
}
block.bsums[j] = acc as i16;
}
block
}
#[cfg(target_arch = "aarch64")]
#[test]
fn q4k_neon_matches_scalar_bit_exact() {
for seed in [0u8, 1, 7, 128, 200, 255] {
let q4k = make_q4k_block_aarch64(seed);
let q8k = make_q8k_block_aarch64(seed.wrapping_mul(13));
let scalar = q4k_q8k_dot_fallback_scalar(&q4k, &q8k);
let neon = unsafe { neon_dot::q4k_q8k_dot(&q4k, &q8k) };
assert_eq!(
scalar.to_bits(),
neon.to_bits(),
"seed={seed}: scalar={scalar} neon={neon}"
);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn q6k_neon_matches_scalar_bit_exact() {
for seed in [0u8, 1, 7, 128, 200, 255] {
let q6k = make_q6k_block_aarch64(seed);
let q8k = make_q8k_block_aarch64(seed.wrapping_mul(11));
let scalar = q6k_q8k_dot_fallback_scalar(&q6k, &q8k);
let neon = unsafe { neon_dot::q6k_q8k_dot(&q6k, &q8k) };
assert_eq!(
scalar.to_bits(),
neon.to_bits(),
"seed={seed}: scalar={scalar} neon={neon}"
);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn ternary_neon_matches_scalar_relative_tolerance() {
const COLS: usize = 128;
for seed in [0u8, 1, 7, 128, 200, 255] {
let row = make_ternary_row(seed, COLS, 0.125);
let input = make_ternary_input(seed.wrapping_mul(7), COLS);
let scalar = ternary_dot_row_scalar(&row, &input);
let simd = ternary_dot_row(&row, &input);
let rel = (scalar - simd).abs() / scalar.abs().max(1e-6);
assert!(
rel < 1e-4,
"seed={seed}: scalar={scalar} simd={simd} rel={rel}"
);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn ternary_neon_handles_non_multiple_of_8_cols() {
const COLS: usize = 100;
for seed in [1u8, 42, 200] {
let row = make_ternary_row(seed, COLS, 0.5);
let input = make_ternary_input(seed.wrapping_mul(11), COLS);
let scalar = ternary_dot_row_scalar(&row, &input);
let simd = ternary_dot_row(&row, &input);
let rel = (scalar - simd).abs() / scalar.abs().max(1e-6);
assert!(
rel < 1e-4,
"seed={seed}: scalar={scalar} simd={simd} rel={rel}"
);
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn q8_0_neon_matches_scalar_within_tol() {
const COLS: usize = 4096;
const BLOCK_BYTES: usize = 34;
const BLOCKS: usize = COLS / QK8_0;
for seed in [0u8, 1, 7, 128, 200, 255] {
let mut row_data = vec![0u8; BLOCKS * BLOCK_BYTES];
for bi in 0..BLOCKS {
let off = bi * BLOCK_BYTES;
let d_bits = 0x3400u16.wrapping_add((seed as u16 ^ bi as u16) & 0x07FF);
row_data[off..off + 2].copy_from_slice(&d_bits.to_le_bytes());
for l in 0..QK8_0 {
row_data[off + 2 + l] = seed
.wrapping_add((bi as u8).wrapping_mul(3))
.wrapping_add(l as u8);
}
}
let input: Vec<f32> = (0..COLS)
.map(|i| (i as f32 * 0.017 + seed as f32).sin() * 0.5)
.collect();
let mut scalar_out = [0.0f32];
{
let mut acc = 0.0f32;
for bi in 0..BLOCKS {
let off = bi * BLOCK_BYTES;
let d = f16_to_f32(u16::from_le_bytes([row_data[off], row_data[off + 1]]));
let col_base = bi * QK8_0;
for l in 0..QK8_0 {
let w = d * f32::from(row_data[off + 2 + l] as i8);
acc += w * input[col_base + l];
}
}
scalar_out[0] = acc;
}
let neon = unsafe { neon_dot::q8_0_matvec_row(&input, &row_data, COLS) };
let diff = (scalar_out[0] - neon).abs();
let rel = diff / scalar_out[0].abs().max(f32::EPSILON);
assert!(
rel < 1e-4,
"seed={seed}: scalar={} neon={neon} diff={diff} rel={rel}",
scalar_out[0]
);
}
}
}