use burn::tensor::{Device, Int, Tensor, TensorData, backend::Backend};
use crate::metadata::ModelMetadata;
use crate::tokenizer::TokenizerSpec;
use crate::{FormatError, Result};
pub trait ModelSource: Send + Sync {
fn metadata(&self) -> &ModelMetadata;
fn tensor_names(&self) -> Vec<String>;
fn open_tensor(&self, name: &str) -> Result<TensorReader<'_>>;
fn tokenizer(&self) -> Result<TokenizerSpec>;
fn sampler_defaults(&self) -> Option<SamplerConfig>;
fn open_tensor_quant(&self, _name: &str) -> Result<Option<QuantTensor<'_>>> {
Ok(None)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuantFormat {
Q4_0,
Q5_0,
Q8_0,
Q4K,
Q5K,
Q6K,
}
pub struct QuantTensor<'a> {
pub format: QuantFormat,
pub shape: Vec<usize>,
pub data: std::borrow::Cow<'a, [u8]>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TensorDtype {
F64,
F32,
F16,
BF16,
I64,
I32,
I16,
I8,
U64,
U32,
U16,
U8,
Bool,
}
impl TensorDtype {
pub fn size(&self) -> usize {
match self {
TensorDtype::F64 | TensorDtype::I64 | TensorDtype::U64 => 8,
TensorDtype::F32 | TensorDtype::I32 | TensorDtype::U32 => 4,
TensorDtype::F16 | TensorDtype::BF16 | TensorDtype::I16 | TensorDtype::U16 => 2,
TensorDtype::I8 | TensorDtype::U8 | TensorDtype::Bool => 1,
}
}
}
impl std::fmt::Display for TensorDtype {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TensorDtype::F64 => write!(f, "F64"),
TensorDtype::F32 => write!(f, "F32"),
TensorDtype::F16 => write!(f, "F16"),
TensorDtype::BF16 => write!(f, "BF16"),
TensorDtype::I64 => write!(f, "I64"),
TensorDtype::I32 => write!(f, "I32"),
TensorDtype::I16 => write!(f, "I16"),
TensorDtype::I8 => write!(f, "I8"),
TensorDtype::U64 => write!(f, "U64"),
TensorDtype::U32 => write!(f, "U32"),
TensorDtype::U16 => write!(f, "U16"),
TensorDtype::U8 => write!(f, "U8"),
TensorDtype::Bool => write!(f, "Bool"),
}
}
}
pub struct TensorReader<'a> {
name: String,
shape: Vec<usize>,
dtype: TensorDtype,
data: std::borrow::Cow<'a, [u8]>,
}
impl<'a> TensorReader<'a> {
pub fn new(name: String, shape: Vec<usize>, dtype: TensorDtype, data: &'a [u8]) -> Self {
TensorReader {
name,
shape,
dtype,
data: std::borrow::Cow::Borrowed(data),
}
}
pub fn owned(name: String, shape: Vec<usize>, data: Vec<u8>) -> Self {
TensorReader {
name,
shape,
dtype: TensorDtype::F32,
data: std::borrow::Cow::Owned(data),
}
}
pub fn owned_with_dtype(
name: String,
shape: Vec<usize>,
dtype: TensorDtype,
data: Vec<u8>,
) -> Self {
TensorReader {
name,
shape,
dtype,
data: std::borrow::Cow::Owned(data),
}
}
pub fn shape(&self) -> &[usize] {
&self.shape
}
pub fn dtype(&self) -> TensorDtype {
self.dtype
}
pub fn num_elements(&self) -> usize {
self.shape.iter().product()
}
pub fn load_data(&self) -> Result<TensorData> {
let values: Vec<f32> = match self.dtype {
TensorDtype::F64 => self
.data
.chunks_exact(8)
.map(|c| f64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]) as f32)
.collect(),
TensorDtype::F32 => self
.data
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect(),
TensorDtype::F16 => self
.data
.chunks_exact(2)
.map(|c| half::f16::from_le_bytes([c[0], c[1]]).to_f32())
.collect(),
TensorDtype::BF16 => self
.data
.chunks_exact(2)
.map(|c| half::bf16::from_le_bytes([c[0], c[1]]).to_f32())
.collect(),
TensorDtype::I64 => self
.data
.chunks_exact(8)
.map(|c| i64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]) as f32)
.collect(),
TensorDtype::I32 => self
.data
.chunks_exact(4)
.map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]) as f32)
.collect(),
TensorDtype::I16 => self
.data
.chunks_exact(2)
.map(|c| i16::from_le_bytes([c[0], c[1]]) as f32)
.collect(),
TensorDtype::I8 => self
.data
.iter()
.map(|&b| b as i8 as f32)
.collect(),
TensorDtype::U64 => self
.data
.chunks_exact(8)
.map(|c| u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]) as f32)
.collect(),
TensorDtype::U32 => self
.data
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]) as f32)
.collect(),
TensorDtype::U16 => self
.data
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]) as f32)
.collect(),
TensorDtype::U8 => self.data.iter().map(|&b| b as f32).collect(),
TensorDtype::Bool => self.data.iter().map(|&b| (b != 0) as i32 as f32).collect(),
};
if values.len() != self.num_elements() {
return Err(FormatError::Safetensors(format!(
"tensor {}: expected {} elements, got {} (dtype {})",
self.name,
self.num_elements(),
values.len(),
self.dtype
)));
}
Ok(TensorData::new(values, self.shape.clone()))
}
pub fn load_to_tensor<B: Backend, const D: usize>(
&self,
device: &Device<B>,
) -> Result<Tensor<B, D>> {
let data = self.load_data()?;
if self.shape.len() != D {
return Err(FormatError::Safetensors(format!(
"tensor {}: expected rank {D}, got {}",
self.name,
self.shape.len()
)));
}
Ok(Tensor::from_data(data, device))
}
pub fn load_int_tensor<B: Backend, const D: usize>(
&self,
device: &Device<B>,
) -> Result<Tensor<B, D, Int>> {
if self.dtype != TensorDtype::U8 {
return Err(FormatError::Safetensors(format!(
"tensor {}: load_int_tensor requires U8, got {}",
self.name, self.dtype
)));
}
if self.shape.len() != D {
return Err(FormatError::Safetensors(format!(
"tensor {}: expected rank {D}, got {}",
self.name,
self.shape.len()
)));
}
let values: Vec<i32> = self.data.iter().map(|&b| b as i32).collect();
Ok(Tensor::from_data(
TensorData::new(values, self.shape.clone()),
device,
))
}
pub fn raw_bytes(&self) -> &[u8] {
&self.data
}
}
impl<T: ModelSource + ?Sized> ModelSource for Box<T> {
fn metadata(&self) -> &crate::ModelMetadata {
(**self).metadata()
}
fn tensor_names(&self) -> Vec<String> {
(**self).tensor_names()
}
fn open_tensor(&self, name: &str) -> crate::Result<TensorReader<'_>> {
(**self).open_tensor(name)
}
fn tokenizer(&self) -> crate::Result<TokenizerSpec> {
(**self).tokenizer()
}
fn sampler_defaults(&self) -> Option<SamplerConfig> {
(**self).sampler_defaults()
}
fn open_tensor_quant(&self, name: &str) -> Result<Option<QuantTensor<'_>>> {
(**self).open_tensor_quant(name)
}
}
#[derive(Debug, Clone, Default)]
pub struct SamplerConfig { pub temperature: Option<f32>,
pub top_p: Option<f32>,
pub top_k: Option<usize>,
pub repetition_penalty: Option<f32>,
pub max_new_tokens: Option<usize>,
}