use super::{TensorIndexEntry, V2FormatError, ALIGNMENT, MAX_TENSOR_NAME_LEN};
impl TensorIndexEntry {
#[must_use]
pub fn new(
name: impl Into<String>,
dtype: TensorDType,
shape: Vec<usize>,
offset: u64,
size: u64,
) -> Self {
Self {
name: name.into(),
dtype,
shape,
offset,
size,
}
}
#[must_use]
pub fn element_count(&self) -> usize {
self.shape.iter().product()
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
let name_bytes = self.name.as_bytes();
let name_len = name_bytes.len().min(MAX_TENSOR_NAME_LEN) as u16;
buf.extend_from_slice(&name_len.to_le_bytes());
buf.extend_from_slice(&name_bytes[..name_len as usize]);
buf.push(self.dtype as u8);
let ndim = self.shape.len().min(8) as u8;
buf.push(ndim);
for &dim in self.shape.iter().take(8) {
buf.extend_from_slice(&(dim as u64).to_le_bytes());
}
buf.extend_from_slice(&self.offset.to_le_bytes());
buf.extend_from_slice(&self.size.to_le_bytes());
buf
}
pub fn from_bytes(buf: &[u8]) -> Result<(Self, usize), V2FormatError> {
if buf.len() < 4 {
return Err(V2FormatError::InvalidTensorIndex(
"buffer too small".to_string(),
));
}
let mut pos = 0;
let name_len = u16::from_le_bytes([buf[pos], buf[pos + 1]]) as usize;
pos += 2;
if buf.len() < pos + name_len + 18 {
return Err(V2FormatError::InvalidTensorIndex(
"buffer too small for name".to_string(),
));
}
let name = String::from_utf8_lossy(&buf[pos..pos + name_len]).to_string();
pos += name_len;
let dtype = TensorDType::from_u8(buf[pos])
.ok_or_else(|| V2FormatError::InvalidTensorIndex("invalid dtype".to_string()))?;
pos += 1;
let ndim = buf[pos] as usize;
pos += 1;
let mut shape = Vec::with_capacity(ndim);
for _ in 0..ndim {
if buf.len() < pos + 8 {
return Err(V2FormatError::InvalidTensorIndex(
"buffer too small for shape".to_string(),
));
}
let dim = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8])) as usize;
shape.push(dim);
pos += 8;
}
if buf.len() < pos + 16 {
return Err(V2FormatError::InvalidTensorIndex(
"buffer too small for offset/size".to_string(),
));
}
let offset = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8]));
pos += 8;
let size = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap_or([0; 8]));
pos += 8;
Ok((
Self {
name,
dtype,
shape,
offset,
size,
},
pos,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TensorDType {
F32 = 0,
F16 = 1,
BF16 = 30,
F64 = 3,
I32 = 4,
I64 = 5,
I8 = 6,
U8 = 7,
AprQ4 = 128,
AprQ8 = 129,
Q4K = 12,
Q6K = 14,
}
impl std::fmt::Display for TensorDType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::F32 => "F32",
Self::F16 => "F16",
Self::BF16 => "BF16",
Self::F64 => "F64",
Self::I32 => "I32",
Self::I64 => "I64",
Self::I8 => "I8",
Self::U8 => "U8",
Self::AprQ4 => "APR_Q4",
Self::AprQ8 => "APR_Q8",
Self::Q4K => "Q4_K",
Self::Q6K => "Q6_K",
};
f.write_str(name)
}
}
const _: () = assert!(TensorDType::F32 as u8 == 0, "F32 must be GGML type 0");
const _: () = assert!(TensorDType::F16 as u8 == 1, "F16 must be GGML type 1");
const _: () = assert!(TensorDType::BF16 as u8 == 30, "BF16 must be GGML type 30");
const _: () = assert!(
TensorDType::Q4K as u8 == 12,
"Q4K must be GGML type 12 (Q4_K)"
);
const _: () = assert!(
TensorDType::Q6K as u8 == 14,
"Q6K must be GGML type 14 (Q6_K)"
);
const _: () = assert!(
TensorDType::AprQ4 as u8 >= 128,
"AprQ4 must be outside GGML range"
);
const _: () = assert!(
TensorDType::AprQ8 as u8 >= 128,
"AprQ8 must be outside GGML range"
);
impl TensorDType {
#[must_use]
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::F32),
1 => Some(Self::F16),
30 => Some(Self::BF16),
3 => Some(Self::F64),
4 => Some(Self::I32),
5 => Some(Self::I64),
6 => Some(Self::I8),
7 => Some(Self::U8),
8 | 128 => Some(Self::AprQ4),
9 | 129 => Some(Self::AprQ8),
12 => Some(Self::Q4K),
14 => Some(Self::Q6K),
_ => None,
}
}
#[must_use]
pub const fn bytes_per_element(self) -> usize {
match self {
Self::F32 | Self::I32 => 4,
Self::F16 | Self::BF16 => 2,
Self::F64 | Self::I64 => 8,
Self::I8 | Self::U8 | Self::AprQ8 => 1,
Self::AprQ4 | Self::Q4K | Self::Q6K => 0, }
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::F32 => "f32",
Self::F16 => "f16",
Self::BF16 => "bf16",
Self::F64 => "f64",
Self::I32 => "i32",
Self::I64 => "i64",
Self::I8 => "i8",
Self::U8 => "u8",
Self::AprQ4 => "q4",
Self::AprQ8 => "q8",
Self::Q4K => "q4_k",
Self::Q6K => "q6_k",
}
}
}
#[must_use]
pub const fn align_up(value: usize, alignment: usize) -> usize {
(value + alignment - 1) & !(alignment - 1)
}
#[must_use]
pub const fn align_64(value: usize) -> usize {
align_up(value, ALIGNMENT)
}
#[must_use]
pub const fn padding_to_align(value: usize, alignment: usize) -> usize {
let aligned = align_up(value, alignment);
aligned - value
}
#[must_use]
pub const fn is_aligned_64(value: usize) -> bool {
value.is_multiple_of(ALIGNMENT)
}