use std::sync::Arc;
use anyhow::{Context, Result};
use crate::gguf::GgufFile;
use crate::tensor::DType;
#[derive(Clone)]
enum Storage {
Mmap {
gguf: Arc<GgufFile>,
offset: usize,
nbytes: usize,
},
Owned(Vec<u8>),
}
#[derive(Clone)]
pub struct MmapWeight {
storage: Storage,
pub dtype: DType,
pub rows: usize,
pub cols: usize,
}
impl std::fmt::Debug for MmapWeight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MmapWeight")
.field("dtype", &self.dtype)
.field("rows", &self.rows)
.field("cols", &self.cols)
.finish()
}
}
impl MmapWeight {
pub fn from_gguf(gguf: &Arc<GgufFile>, name: &str) -> Result<Self> {
let (offset, nbytes) = gguf
.tensor_offset_len(name)
.with_context(|| format!("loading {name}"))?;
let (_off, rows, cols, dtype) = gguf
.tensor_meta(name)
.with_context(|| format!("loading metadata for {name}"))?;
Ok(Self {
storage: Storage::Mmap {
gguf: gguf.clone(),
offset,
nbytes,
},
dtype,
rows,
cols,
})
}
#[doc(hidden)]
pub fn from_owned_f32(data: Vec<f32>, rows: usize, cols: usize) -> Self {
debug_assert_eq!(
data.len(),
rows * cols,
"from_owned_f32: data length {} != rows*cols {}",
data.len(),
rows * cols,
);
let bytes: Vec<u8> = bytemuck::cast_slice(&data).to_vec();
Self {
storage: Storage::Owned(bytes),
dtype: DType::F32,
rows,
cols,
}
}
#[doc(hidden)]
pub fn from_owned_bytes(data: Vec<u8>, dtype: DType, rows: usize, cols: usize) -> Self {
Self {
storage: Storage::Owned(data),
dtype,
rows,
cols,
}
}
pub fn data(&self) -> &[u8] {
match &self.storage {
Storage::Mmap {
gguf,
offset,
nbytes,
} => &gguf.mmap_data()[*offset..*offset + *nbytes],
Storage::Owned(bytes) => bytes,
}
}
pub fn as_f32(&self) -> &[f32] {
assert_eq!(
self.dtype,
DType::F32,
"MmapWeight::as_f32 called on non-F32 tensor — \
use dequantize_row(idx, dst) for per-row reads or \
try_as_f32() if a None-on-quantised return is acceptable"
);
bytemuck::cast_slice(self.data())
}
pub fn try_as_f32(&self) -> Option<&[f32]> {
if self.dtype == DType::F32 {
bytemuck::try_cast_slice(self.data()).ok()
} else {
None
}
}
pub fn to_dense_f32(&self) -> Vec<f32> {
if let Some(f) = self.try_as_f32() {
return f.to_vec();
}
if self.cols == 0 {
return Vec::new();
}
let mut out = vec![0f32; self.rows * self.cols];
for (r, chunk) in out.chunks_exact_mut(self.cols).enumerate() {
self.dequantize_row(r, chunk);
}
out
}
pub fn gemv(&self, x: &[f32], y: &mut [f32]) {
assert_eq!(x.len(), self.cols);
assert_eq!(y.len(), self.rows);
crate::backend::cpu::gemv_dispatch(
self.dtype,
self.data(),
x,
y,
self.rows,
self.cols,
None,
);
}
pub fn batched_matmul(&self, x: &[f32], y: &mut [f32], n_tokens: usize) {
self.batched_matmul_with_scratch(x, y, n_tokens, None);
}
pub fn batched_matmul_with_scratch(
&self,
x: &[f32],
y: &mut [f32],
n_tokens: usize,
scratch: Option<&mut [f32]>,
) {
assert_eq!(x.len(), n_tokens * self.cols);
assert_eq!(y.len(), n_tokens * self.rows);
if n_tokens == 0 || self.rows == 0 || self.cols == 0 {
return;
}
if self.dtype == DType::F32
&& let Some(w_f32) = self.try_as_f32()
{
for r in 0..self.rows {
let w_row = &w_f32[r * self.cols..(r + 1) * self.cols];
for t in 0..n_tokens {
let x_row = &x[t * self.cols..(t + 1) * self.cols];
y[t * self.rows + r] = crate::backend::cpu::dot_f32(x_row, w_row);
}
}
return;
}
let mut local_buf;
let row_buf: &mut [f32] = match scratch {
Some(buf) if buf.len() >= self.cols => &mut buf[..self.cols],
_ => {
local_buf = vec![0.0f32; self.cols];
&mut local_buf[..]
}
};
for r in 0..self.rows {
self.dequantize_row(r, row_buf);
for t in 0..n_tokens {
let x_row = &x[t * self.cols..(t + 1) * self.cols];
y[t * self.rows + r] = crate::backend::cpu::dot_f32(x_row, row_buf);
}
}
}
pub fn dequantize_row(&self, row_idx: usize, dst: &mut [f32]) {
assert_eq!(dst.len(), self.cols);
assert!(row_idx < self.rows);
let block_size = self.dtype.block_size();
assert_eq!(
self.cols % block_size,
0,
"dequantize_row: cols ({}) must be a multiple of dtype block_size ({block_size})",
self.cols,
);
let row_bytes = (self.cols / block_size) * self.dtype.block_bytes();
let offset = row_idx * row_bytes;
let bytes = &self.data()[offset..offset + row_bytes];
match self.dtype {
DType::F32 => {
if let Ok(src) = bytemuck::try_cast_slice::<u8, f32>(bytes) {
dst.copy_from_slice(src);
} else {
bytemuck::cast_slice_mut::<f32, u8>(dst).copy_from_slice(bytes);
}
}
DType::F16 => {
if let Ok(src) = bytemuck::try_cast_slice::<u8, half::f16>(bytes) {
for (d, &s) in dst.iter_mut().zip(src) {
*d = crate::quant::f16_to_f32(s.to_bits());
}
} else {
for (i, d) in dst.iter_mut().enumerate() {
let bits = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
*d = crate::quant::f16_to_f32(bits);
}
}
}
DType::BF16 => {
if let Ok(src) = bytemuck::try_cast_slice::<u8, half::bf16>(bytes) {
for (d, &s) in dst.iter_mut().zip(src) {
*d = crate::quant::bf16_to_f32(s.to_bits());
}
} else {
for (i, d) in dst.iter_mut().enumerate() {
let bits = u16::from_le_bytes([bytes[i * 2], bytes[i * 2 + 1]]);
*d = crate::quant::bf16_to_f32(bits);
}
}
}
DType::Q4_0 => crate::quant::dequantize_q4_0_row(bytes, dst),
DType::Q4_1 => crate::quant::dequantize_q4_1_row(bytes, dst),
DType::Q8_0 => crate::quant::dequantize_q8_0_row(bytes, dst),
DType::Q4KM => crate::quant::dequantize_q4_k_m_row(bytes, dst),
DType::Q5KM => crate::quant::dequantize_q5_k_row(bytes, dst),
DType::Q6K => crate::quant::dequantize_q6_k_row(bytes, dst),
other => panic!("MmapWeight::dequantize_row: unsupported dtype {other:?}"),
}
}
pub fn gemv_rows(&self, x: &[f32], y: &mut [f32], row_start: usize, n_rows: usize) {
assert_eq!(x.len(), self.cols);
assert_eq!(y.len(), n_rows);
assert!(row_start + n_rows <= self.rows);
let row_bytes = (self.cols / self.dtype.block_size()) * self.dtype.block_bytes();
let offset = row_start * row_bytes;
let bytes = self.data();
let slice = &bytes[offset..offset + n_rows * row_bytes];
crate::backend::cpu::gemv_dispatch(self.dtype, slice, x, y, n_rows, self.cols, None);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quant::{BlockQ4_0, BlockQ8_0};
#[test]
fn dequantize_row_f32_round_trip() {
let rows = 5;
let cols = 8;
let mut data = Vec::with_capacity(rows * cols);
for r in 0..rows {
for c in 0..cols {
data.push((r * 100 + c) as f32);
}
}
let w = MmapWeight::from_owned_f32(data, rows, cols);
let mut row = vec![0f32; cols];
w.dequantize_row(3, &mut row);
let expected: Vec<f32> = (0..cols).map(|c| (3 * 100 + c) as f32).collect();
assert_eq!(row, expected);
}
#[test]
fn dequantize_row_f16_widens_correctly() {
let cols = 4;
let bytes: Vec<u8> = [1.5f32, -0.25, 100.0, 0.0]
.iter()
.flat_map(|&v| half::f16::from_f32(v).to_bits().to_le_bytes())
.collect();
let w = MmapWeight::from_owned_bytes(bytes, DType::F16, 1, cols);
let mut row = vec![0f32; cols];
w.dequantize_row(0, &mut row);
assert!((row[0] - 1.5).abs() < 1e-3);
assert!((row[1] - -0.25).abs() < 1e-3);
assert!((row[2] - 100.0).abs() < 1e-1);
assert_eq!(row[3], 0.0);
}
#[test]
fn dequantize_row_bf16_widens_correctly() {
let cols = 4;
let bytes: Vec<u8> = [1.0f32, -2.0, 0.5, -0.5]
.iter()
.flat_map(|&v| half::bf16::from_f32(v).to_bits().to_le_bytes())
.collect();
let w = MmapWeight::from_owned_bytes(bytes, DType::BF16, 1, cols);
let mut row = vec![0f32; cols];
w.dequantize_row(0, &mut row);
assert!((row[0] - 1.0).abs() < 1e-2);
assert!((row[1] - -2.0).abs() < 1e-2);
assert!((row[2] - 0.5).abs() < 1e-2);
assert!((row[3] - -0.5).abs() < 1e-2);
}
#[test]
fn dequantize_row_q4_0_dispatch() {
let cols = 32;
let d = half::f16::from_f32(0.5);
let block = BlockQ4_0 {
d: d.to_bits(),
qs: {
let mut q = [0x88u8; 16];
q[0] = 0x09;
q[1] = 0xff;
q
},
};
let mut bytes = Vec::with_capacity(std::mem::size_of::<BlockQ4_0>());
bytes.extend_from_slice(&block.d.to_le_bytes());
bytes.extend_from_slice(&block.qs);
let w = MmapWeight::from_owned_bytes(bytes, DType::Q4_0, 1, cols);
let mut row = vec![0f32; cols];
w.dequantize_row(0, &mut row);
assert!((row[0] - 0.5).abs() < 1e-4, "row[0] = {}", row[0]); assert!((row[16] - -4.0).abs() < 1e-4, "row[16] = {}", row[16]); assert!((row[1] - 3.5).abs() < 1e-4, "row[1] = {}", row[1]); assert!((row[17] - 3.5).abs() < 1e-4, "row[17] = {}", row[17]); for &v in &row[2..16] {
assert!(v.abs() < 1e-4);
}
for &v in &row[18..32] {
assert!(v.abs() < 1e-4);
}
}
#[test]
fn dequantize_row_q8_0_dispatch() {
let cols = 32;
let d = half::f16::from_f32(1.0);
let mut quants = [0i8; 32];
quants[0] = -128;
quants[1] = -1;
quants[2] = 0;
for (i, q) in quants.iter_mut().enumerate().skip(3) {
*q = (i as i8) - 3; }
let block = BlockQ8_0 {
delta: d.to_bits(),
quants,
};
let mut bytes = Vec::with_capacity(std::mem::size_of::<BlockQ8_0>());
bytes.extend_from_slice(&block.delta.to_le_bytes());
bytes.extend_from_slice(bytemuck::cast_slice::<i8, u8>(&block.quants));
let w = MmapWeight::from_owned_bytes(bytes, DType::Q8_0, 1, cols);
let mut row = vec![0f32; cols];
w.dequantize_row(0, &mut row);
assert!((row[0] - -128.0).abs() < 1e-3);
assert!((row[1] - -1.0).abs() < 1e-3);
assert!((row[2] - 0.0).abs() < 1e-3);
for (i, &v) in row.iter().enumerate().skip(3) {
assert!((v - ((i - 3) as f32)).abs() < 1e-3);
}
}
#[test]
#[should_panic(expected = "must be a multiple of dtype block_size")]
fn dequantize_row_panics_on_unaligned_cols() {
let bytes = vec![0u8; std::mem::size_of::<BlockQ4_0>()];
let w = MmapWeight::from_owned_bytes(bytes, DType::Q4_0, 1, 30);
let mut row = vec![0f32; 30];
w.dequantize_row(0, &mut row);
}
#[test]
fn try_as_f32_dispatches_on_dtype() {
let f32_w = MmapWeight::from_owned_f32(vec![1.0, 2.0, 3.0, 4.0], 1, 4);
assert!(f32_w.try_as_f32().is_some());
let q4_bytes = vec![0u8; std::mem::size_of::<BlockQ4_0>()];
let q4_w = MmapWeight::from_owned_bytes(q4_bytes, DType::Q4_0, 1, 32);
assert!(q4_w.try_as_f32().is_none());
}
}