use crate::bytes::le_u32;
use crate::error::UrnaError;
pub const INT4_PAYLOAD_VERSION: u32 = 1;
pub const INT4_SCALE_KIND_PER_GROUP: u32 = 1;
pub const INT4_PREFIX_SIZE: usize = 8;
pub const INT4_BLOCK: usize = 64;
#[inline]
pub fn int4_blocks_per_row(dim: usize) -> usize {
dim / INT4_BLOCK
}
pub fn quantize_f32_to_i4(values: &[f32], dim: usize) -> (Vec<half::f16>, Vec<i8>) {
let blocks = int4_blocks_per_row(dim);
let mut scales: Vec<half::f16> = Vec::with_capacity(blocks);
let mut codes: Vec<i8> = Vec::with_capacity(dim);
for g in 0..blocks {
let blk = &values[g * INT4_BLOCK..(g + 1) * INT4_BLOCK];
let max_abs = blk.iter().fold(0.0f32, |acc, &v| acc.max(v.abs()));
let scale_f16 = half::f16::from_f32(if max_abs == 0.0 { 1.0 } else { max_abs / 7.0 });
let scale = scale_f16.to_f32();
let inv = if scale > 0.0 { 1.0 / scale } else { 0.0 };
for &v in blk {
let q = (v * inv).round().clamp(-7.0, 7.0);
codes.push(q as i8);
}
scales.push(scale_f16);
}
(scales, codes)
}
#[inline]
pub fn pack_nibbles(codes: &[i8]) -> Vec<u8> {
let mut out = Vec::with_capacity(codes.len().div_ceil(2));
for pair in codes.chunks(2) {
let lo = (pair[0] as u8) & 0x0F;
let hi = pair.get(1).map(|&c| (c as u8) & 0x0F).unwrap_or(0);
out.push(lo | (hi << 4));
}
out
}
#[inline]
pub fn nibble_to_i4(b: u8) -> i8 {
let n = b & 0x0F;
if n & 0x08 != 0 {
(n | 0xF0) as i8
} else {
n as i8
}
}
pub fn encode_int4_embeddings(embeddings: &[f32], n: usize, dim: usize) -> crate::Result<Vec<u8>> {
if dim == 0 || dim % INT4_BLOCK != 0 {
return Err(UrnaError::InvalidInput(format!(
"encode_int4_embeddings: dim={dim} must be a nonzero multiple of {INT4_BLOCK}"
)));
}
if embeddings.len() != n * dim {
return Err(UrnaError::InvalidInput(format!(
"encode_int4_embeddings: got {} f32 values for n={n} dim={dim}",
embeddings.len()
)));
}
let blocks = int4_blocks_per_row(dim);
let mut out = Vec::with_capacity(INT4_PREFIX_SIZE + n * blocks * 2 + n * dim / 2);
out.extend_from_slice(&INT4_PAYLOAD_VERSION.to_le_bytes());
out.extend_from_slice(&INT4_SCALE_KIND_PER_GROUP.to_le_bytes());
let mut scale_bytes: Vec<u8> = Vec::with_capacity(n * blocks * 2);
let mut body: Vec<u8> = Vec::with_capacity(n * dim / 2);
for i in 0..n {
let row = &embeddings[i * dim..(i + 1) * dim];
let (scales, codes) = quantize_f32_to_i4(row, dim);
for s in &scales {
scale_bytes.extend_from_slice(&s.to_le_bytes());
}
body.extend_from_slice(&pack_nibbles(&codes));
}
out.extend_from_slice(&scale_bytes);
out.extend_from_slice(&body);
Ok(out)
}
pub struct Int4EmbeddingsView<'a> {
pub scales: &'a [u8],
pub codes: &'a [u8],
pub n: usize,
pub dim: usize,
pub blocks: usize,
}
impl<'a> Int4EmbeddingsView<'a> {
pub fn parse(bytes: &'a [u8], n: usize, dim: usize) -> crate::Result<Self> {
if dim == 0 || dim % INT4_BLOCK != 0 {
return Err(UrnaError::MalformedSectionPayload {
section_id: crate::layout::SECTION_EMBEDDINGS,
reason: format!("int4 dim={dim} must be a nonzero multiple of {INT4_BLOCK}"),
});
}
let blocks = int4_blocks_per_row(dim);
let want = super::expected_embeddings_size("int4", n, dim).unwrap_or(usize::MAX);
if bytes.len() != want {
return Err(UrnaError::EmbeddingSizeMismatch {
expected: want,
got: bytes.len(),
});
}
let version = le_u32(&bytes[0..4])?;
if version != INT4_PAYLOAD_VERSION {
return Err(UrnaError::UnsupportedSectionVersion {
section_id: crate::layout::SECTION_EMBEDDINGS,
version,
});
}
let kind = le_u32(&bytes[4..8])?;
if kind != INT4_SCALE_KIND_PER_GROUP {
return Err(UrnaError::MalformedSectionPayload {
section_id: crate::layout::SECTION_EMBEDDINGS,
reason: format!("int4 scale_kind {kind} not supported"),
});
}
let scales_end = INT4_PREFIX_SIZE + n * blocks * 2;
Ok(Self {
scales: &bytes[INT4_PREFIX_SIZE..scales_end],
codes: &bytes[scales_end..],
n,
dim,
blocks,
})
}
#[inline]
pub fn group_scale(&self, i: usize, g: usize) -> f32 {
let off = (i * self.blocks + g) * 2;
half::f16::from_le_bytes([self.scales[off], self.scales[off + 1]]).to_f32()
}
#[inline]
pub fn row_codes(&self, i: usize) -> &'a [u8] {
let rs = self.dim / 2;
let start = i * rs;
&self.codes[start..start + rs]
}
#[inline]
pub fn row_scales_f32(&self, i: usize) -> Vec<f32> {
(0..self.blocks).map(|g| self.group_scale(i, g)).collect()
}
#[inline]
pub fn row_scales_into(&self, i: usize, out: &mut [f32]) {
assert_eq!(out.len(), self.blocks, "int4: one scale slot per block");
for (g, slot) in out.iter_mut().enumerate() {
*slot = self.group_scale(i, g);
}
}
}