pub mod lazy;
use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IrError {
#[error("Unsupported dtype for conversion: {dtype}")]
UnsupportedDtype { dtype: String },
#[error("Tensor '{name}' has invalid shape: expected {expected} elements, got {actual}")]
ShapeMismatch {
name: String,
expected: usize,
actual: usize,
},
#[allow(dead_code)]
#[error("bf16 to f16 conversion failed for tensor '{name}': {reason}")]
ConversionFailed { name: String, reason: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DType {
F32,
F16,
BF16,
I32,
I64,
U8,
U16,
U32,
Bool,
}
impl DType {
pub fn element_size(self) -> usize {
match self {
DType::F32 | DType::I32 | DType::U32 => 4,
DType::F16 | DType::BF16 | DType::U16 => 2,
DType::I64 => 8,
DType::U8 | DType::Bool => 1,
}
}
pub fn from_safetensors_str(s: &str) -> Option<DType> {
match s {
"F32" => Some(DType::F32),
"F16" => Some(DType::F16),
"BF16" => Some(DType::BF16),
"I32" => Some(DType::I32),
"I64" => Some(DType::I64),
"U8" => Some(DType::U8),
"U16" => Some(DType::U16),
"U32" => Some(DType::U32),
"BOOL" => Some(DType::Bool),
_ => None,
}
}
}
impl fmt::Display for DType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DType::F32 => write!(f, "F32"),
DType::F16 => write!(f, "F16"),
DType::BF16 => write!(f, "BF16"),
DType::I32 => write!(f, "I32"),
DType::I64 => write!(f, "I64"),
DType::U8 => write!(f, "U8"),
DType::U16 => write!(f, "U16"),
DType::U32 => write!(f, "U32"),
DType::Bool => write!(f, "BOOL"),
}
}
}
#[derive(Debug, Clone)]
pub struct TensorRef {
pub name: String,
pub shape: Vec<usize>,
pub dtype: DType,
pub data: std::sync::Arc<Vec<u8>>,
}
impl TensorRef {
pub fn numel(&self) -> usize {
self.shape.iter().product()
}
#[allow(dead_code)]
pub fn size_bytes(&self) -> usize {
self.numel() * self.dtype.element_size()
}
pub fn take_data_as_arc(&mut self) -> std::sync::Arc<Vec<u8>> {
std::mem::take(&mut self.data)
}
pub fn is_vision_tensor(&self) -> bool {
let n = &self.name;
if n.contains("vision_tower") || n.contains("embed_vision") {
return true;
}
if let Some(rest) = n.strip_prefix("language_model.") {
if rest.starts_with("vision_tower") || rest.starts_with("embed_vision") {
return true;
}
}
false
}
pub fn is_weight(&self) -> bool {
let n = &self.name;
if n.contains("layernorm") || n.contains("layer_norm") || n.contains("_norm.weight") {
return false;
}
if n.contains("bias") {
return false;
}
if n.contains("layer_scalar")
|| n.contains("router.scale")
|| n.contains("router.per_expert_scale")
{
return false;
}
if n.contains("embed_tokens") || n.contains("embedding_projection") {
return false;
}
if self.shape.len() >= 2 {
let row_dim = *self.shape.last().unwrap();
if row_dim < 32 {
return false;
}
}
if self.shape.len() >= 2 {
return n.contains("weight") || n.contains("proj") || n.contains("experts.");
}
false
}
pub fn to_f32_vec(&self) -> Result<Vec<f32>, IrError> {
let element_count = self.numel();
let expected_bytes = element_count * self.dtype.element_size();
if self.data.len() != expected_bytes {
return Err(IrError::ShapeMismatch {
name: self.name.clone(),
expected: expected_bytes,
actual: self.data.len(),
});
}
match self.dtype {
DType::F32 => {
let mut out = Vec::with_capacity(element_count);
for c in self.data.chunks_exact(4) {
out.push(f32::from_le_bytes([c[0], c[1], c[2], c[3]]));
}
Ok(out)
}
DType::F16 => {
let mut out = Vec::with_capacity(element_count);
for c in self.data.chunks_exact(2) {
let h = half::f16::from_le_bytes([c[0], c[1]]);
out.push(h.to_f32());
}
Ok(out)
}
DType::BF16 => {
let mut out = Vec::with_capacity(element_count);
for c in self.data.chunks_exact(2) {
let bf = half::bf16::from_le_bytes([c[0], c[1]]);
out.push(bf.to_f32());
}
Ok(out)
}
other => Err(IrError::UnsupportedDtype {
dtype: other.to_string(),
}),
}
}
pub fn to_f16(&self) -> Result<TensorRef, IrError> {
if self.dtype == DType::F16 {
return Ok(self.clone());
}
if self.dtype != DType::BF16 {
return Err(IrError::UnsupportedDtype {
dtype: self.dtype.to_string(),
});
}
let element_count = self.numel();
let expected_bytes = element_count * 2;
if self.data.len() != expected_bytes {
return Err(IrError::ShapeMismatch {
name: self.name.clone(),
expected: expected_bytes,
actual: self.data.len(),
});
}
let mut f16_data = Vec::with_capacity(expected_bytes);
for chunk in self.data.chunks_exact(2) {
let bf16_bits = u16::from_le_bytes([chunk[0], chunk[1]]);
let bf16_val = half::bf16::from_bits(bf16_bits);
let f32_val: f32 = bf16_val.to_f32();
let f16_val = half::f16::from_f32(f32_val);
f16_data.extend_from_slice(&f16_val.to_le_bytes());
}
Ok(TensorRef {
name: self.name.clone(),
shape: self.shape.clone(),
dtype: DType::F16,
data: std::sync::Arc::new(f16_data),
})
}
}
#[derive(Debug)]
pub struct TensorMap {
pub tensors: HashMap<String, TensorRef>,
}
impl TensorMap {
pub fn new() -> Self {
Self {
tensors: HashMap::new(),
}
}
pub fn insert(&mut self, tensor: TensorRef) {
self.tensors.insert(tensor.name.clone(), tensor);
}
#[allow(dead_code)]
pub fn get(&self, name: &str) -> Option<&TensorRef> {
self.tensors.get(name)
}
pub fn len(&self) -> usize {
self.tensors.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.tensors.is_empty()
}
#[allow(dead_code)]
pub fn iter(&self) -> impl Iterator<Item = (&String, &TensorRef)> {
self.tensors.iter()
}
pub fn total_size_bytes(&self) -> usize {
self.tensors.values().map(|t| t.data.len()).sum()
}
pub fn convert_bf16_to_f16(&mut self) -> Result<usize, IrError> {
let bf16_names: Vec<String> = self
.tensors
.iter()
.filter(|(_, t)| t.dtype == DType::BF16)
.map(|(name, _)| name.clone())
.collect();
let count = bf16_names.len();
for name in bf16_names {
if let Some(tensor) = self.tensors.remove(&name) {
let converted = tensor.to_f16()?;
self.tensors.insert(name, converted);
}
}
Ok(count)
}
}
impl Default for TensorMap {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct RopeParameters {
#[serde(default)]
pub mrope_interleaved: bool,
#[serde(default)]
pub mrope_section: Vec<u32>,
#[serde(default)]
pub rope_theta: f64,
#[serde(default)]
pub rope_type: String,
#[serde(default)]
pub partial_rotary_factor: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelMetadata {
pub architecture: String,
pub model_type: String,
pub param_count: u64,
pub hidden_size: u64,
pub num_layers: u32,
pub layer_types: Vec<String>,
pub num_attention_heads: u32,
pub num_kv_heads: Option<u32>,
pub vocab_size: u64,
pub dtype: String,
pub shard_count: u32,
pub num_experts: Option<u32>,
pub top_k_experts: Option<u32>,
pub intermediate_size: Option<u64>,
pub raw_config: serde_json::Value,
pub explicit_layer_types: Option<Vec<String>>,
pub full_attention_interval: Option<u32>,
pub attn_output_gate: Option<bool>,
pub head_dim: Option<u32>,
pub partial_rotary_factor: Option<f32>,
pub rope_parameters: Option<RopeParameters>,
pub linear_conv_kernel_dim: Option<u32>,
pub linear_key_head_dim: Option<u32>,
pub linear_num_key_heads: Option<u32>,
pub linear_value_head_dim: Option<u32>,
pub linear_num_value_heads: Option<u32>,
pub mamba_ssm_dtype: Option<String>,
pub moe_intermediate_size: Option<u32>,
pub shared_expert_intermediate_size: Option<u32>,
pub mtp_num_hidden_layers: Option<u32>,
pub mtp_use_dedicated_embeddings: Option<bool>,
pub output_router_logits: Option<bool>,
pub router_aux_loss_coef: Option<f32>,
}
impl ModelMetadata {
pub fn unique_layer_types(&self) -> Vec<String> {
let mut types: Vec<String> = self.layer_types.clone();
types.sort();
types.dedup();
types
}
pub fn is_moe(&self) -> bool {
self.num_experts.is_some() && self.num_experts.unwrap_or(0) > 1
}
pub fn resolved_layer_types(&self) -> Vec<String> {
if let Some(explicit) = &self.explicit_layer_types {
return explicit.clone();
}
if let Some(interval) = self.full_attention_interval {
let n = self.num_layers as usize;
if n > 0 && interval > 0 {
return (0..n)
.map(|i| {
if (i + 1) % interval as usize == 0 {
"full_attention".to_string()
} else {
"linear_attention".to_string()
}
})
.collect();
}
}
self.layer_types.clone()
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct QuantizedTensor {
pub name: String,
pub shape: Vec<usize>,
pub original_dtype: DType,
pub data: std::sync::Arc<Vec<u8>>,
pub quant_info: TensorQuantInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TensorQuantInfo {
pub method: String,
pub bits: u8,
pub group_size: usize,
pub preserved: bool,
pub scales: Option<Vec<u8>>,
pub biases: Option<Vec<u8>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ggml_type: Option<String>,
}
#[derive(Debug)]
pub struct QuantizedModel {
pub metadata: ModelMetadata,
pub tensors: HashMap<String, QuantizedTensor>,
pub quant_method: String,
pub group_size: usize,
pub bits: u8,
}
impl QuantizedModel {
#[allow(dead_code)]
pub fn total_size_bytes(&self) -> usize {
self.tensors.values().map(|t| t.data.len()).sum()
}
#[allow(dead_code)]
pub fn tensor_count(&self) -> usize {
self.tensors.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputManifest {
pub output_dir: String,
pub files: Vec<OutputFile>,
pub total_size_bytes: u64,
pub shard_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputFile {
pub filename: String,
pub size_bytes: u64,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FormatWarning {
pub message: String,
pub severity: WarningSeverity,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum WarningSeverity {
Info,
Warning,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dtype_element_size() {
assert_eq!(DType::F32.element_size(), 4);
assert_eq!(DType::F16.element_size(), 2);
assert_eq!(DType::BF16.element_size(), 2);
assert_eq!(DType::U8.element_size(), 1);
}
#[test]
fn test_take_data_as_arc_moves_bytes_without_clone() {
let original_bytes = vec![1u8, 2, 3, 4, 5];
let mut t = TensorRef {
name: "t".to_string(),
shape: vec![5],
dtype: DType::U8,
data: original_bytes.clone().into(),
};
let arc = t.take_data_as_arc();
assert_eq!(&**arc, original_bytes.as_slice());
assert_eq!(*t.data, Vec::<u8>::new());
assert_eq!(std::sync::Arc::strong_count(&arc), 1);
}
#[test]
fn test_take_data_as_arc_double_take_returns_empty() {
let mut t = TensorRef {
name: "t".to_string(),
shape: vec![3],
dtype: DType::U8,
data: std::sync::Arc::new(vec![10, 20, 30]),
};
let first = t.take_data_as_arc();
assert_eq!(&**first, &[10u8, 20, 30]);
let second = t.take_data_as_arc();
assert_eq!(&**second, &[] as &[u8]);
}
#[test]
fn test_dtype_from_safetensors_str() {
assert_eq!(DType::from_safetensors_str("F32"), Some(DType::F32));
assert_eq!(DType::from_safetensors_str("BF16"), Some(DType::BF16));
assert_eq!(DType::from_safetensors_str("UNKNOWN"), None);
}
#[test]
fn test_tensor_ref_numel() {
let t = TensorRef {
name: "test".to_string(),
shape: vec![3, 4, 5],
dtype: DType::F32,
data: std::sync::Arc::new(vec![0u8; 3 * 4 * 5 * 4]),
};
assert_eq!(t.numel(), 60);
assert_eq!(t.size_bytes(), 240);
}
#[test]
fn test_tensor_ref_is_weight() {
let weight = TensorRef {
name: "model.layers.0.self_attn.q_proj.weight".to_string(),
shape: vec![4096, 4096],
dtype: DType::F16,
data: std::sync::Arc::new(vec![]),
};
assert!(weight.is_weight());
let norm = TensorRef {
name: "model.layers.0.input_layernorm.weight".to_string(),
shape: vec![4096],
dtype: DType::F16,
data: std::sync::Arc::new(vec![]),
};
assert!(!norm.is_weight());
let bias = TensorRef {
name: "model.layers.0.self_attn.o_proj.bias".to_string(),
shape: vec![4096],
dtype: DType::F16,
data: std::sync::Arc::new(vec![]),
};
assert!(!bias.is_weight());
}
#[test]
fn test_bf16_to_f16_conversion() {
let bf16_one = half::bf16::from_f32(1.0);
let bytes = bf16_one.to_le_bytes();
let tensor = TensorRef {
name: "test".to_string(),
shape: vec![1],
dtype: DType::BF16,
data: bytes.to_vec().into(),
};
let converted = tensor.to_f16().unwrap();
assert_eq!(converted.dtype, DType::F16);
assert_eq!(converted.data.len(), 2);
let f16_bits = u16::from_le_bytes([converted.data[0], converted.data[1]]);
let f16_val = half::f16::from_bits(f16_bits);
assert!((f16_val.to_f32() - 1.0).abs() < 1e-3);
}
#[test]
fn test_tensor_map_operations() {
let mut map = TensorMap::new();
assert!(map.is_empty());
map.insert(TensorRef {
name: "a".to_string(),
shape: vec![2, 3],
dtype: DType::F16,
data: std::sync::Arc::new(vec![0u8; 12]),
});
assert_eq!(map.len(), 1);
assert!(map.get("a").is_some());
assert!(map.get("b").is_none());
}
#[test]
fn test_is_vision_tensor() {
let make = |name: &str| TensorRef {
name: name.to_string(),
shape: vec![4096, 4096],
dtype: DType::F16,
data: std::sync::Arc::new(vec![]),
};
assert!(
make("model.vision_tower.encoder.layers.0.self_attn.q_proj.weight").is_vision_tensor()
);
assert!(make("model.vision_tower.patch_embedder.input_proj.weight").is_vision_tensor());
assert!(make("model.embed_vision.embedding_projection.weight").is_vision_tensor());
assert!(!make("model.layers.0.self_attn.q_proj.weight").is_vision_tensor());
assert!(!make("model.embed_tokens.weight").is_vision_tensor());
}
#[test]
fn test_vision_weight_tensor_classification() {
let vt = TensorRef {
name: "model.vision_tower.encoder.layers.0.self_attn.q_proj.weight".to_string(),
shape: vec![4096, 4096],
dtype: DType::F16,
data: std::sync::Arc::new(vec![]),
};
assert!(vt.is_weight(), "vision weight should pass is_weight()");
assert!(
vt.is_vision_tensor(),
"vision weight should pass is_vision_tensor()"
);
}
#[test]
fn test_model_metadata_moe() {
let meta = ModelMetadata {
architecture: "Test".to_string(),
model_type: "test".to_string(),
param_count: 1000,
hidden_size: 256,
num_layers: 4,
layer_types: vec!["attention".to_string()],
num_attention_heads: 8,
num_kv_heads: None,
vocab_size: 32000,
dtype: "bfloat16".to_string(),
shard_count: 1,
num_experts: Some(128),
top_k_experts: Some(8),
intermediate_size: Some(512),
raw_config: serde_json::Value::Null,
explicit_layer_types: None,
full_attention_interval: None,
attn_output_gate: None,
head_dim: None,
partial_rotary_factor: None,
rope_parameters: None,
linear_conv_kernel_dim: None,
linear_key_head_dim: None,
linear_num_key_heads: None,
linear_value_head_dim: None,
linear_num_value_heads: None,
mamba_ssm_dtype: None,
moe_intermediate_size: None,
shared_expert_intermediate_size: None,
mtp_num_hidden_layers: None,
mtp_use_dedicated_embeddings: None,
output_router_logits: None,
router_aux_loss_coef: None,
};
assert!(meta.is_moe());
}
#[test]
fn to_f32_vec_round_trips_f32_bit_exactly() {
let values: Vec<f32> = vec![0.0, 1.5, -2.25, std::f32::consts::PI, -1e-10, 4.2e6];
let mut bytes = Vec::with_capacity(values.len() * 4);
for v in &values {
bytes.extend_from_slice(&v.to_le_bytes());
}
let t = TensorRef {
name: "test_f32".into(),
shape: vec![values.len()],
dtype: DType::F32,
data: std::sync::Arc::new(bytes),
};
let out = t.to_f32_vec().expect("F32 decode must succeed");
assert_eq!(out.len(), values.len());
for (a, b) in out.iter().zip(values.iter()) {
assert_eq!(a.to_bits(), b.to_bits(), "F32 round-trip not bit-exact");
}
}
#[test]
fn to_f32_vec_decodes_bf16_to_canonical_f32() {
let values_bf16: Vec<half::bf16> = vec![
half::bf16::from_f32(1.0),
half::bf16::from_f32(2.0),
half::bf16::from_f32(-1.5),
half::bf16::from_f32(0.5),
];
let mut bytes = Vec::with_capacity(values_bf16.len() * 2);
for v in &values_bf16 {
bytes.extend_from_slice(&v.to_le_bytes());
}
let t = TensorRef {
name: "test_bf16".into(),
shape: vec![values_bf16.len()],
dtype: DType::BF16,
data: std::sync::Arc::new(bytes),
};
let out = t.to_f32_vec().expect("BF16 decode must succeed");
let expected: Vec<f32> = values_bf16.iter().map(|v| v.to_f32()).collect();
for (i, (a, b)) in out.iter().zip(expected.iter()).enumerate() {
assert_eq!(a.to_bits(), b.to_bits(), "BF16[{i}] decode mismatch");
}
}
#[test]
fn to_f32_vec_decodes_f16_to_canonical_f32() {
let values_f16: Vec<half::f16> = vec![
half::f16::from_f32(1.0),
half::f16::from_f32(-2.5),
half::f16::from_f32(0.125),
];
let mut bytes = Vec::with_capacity(values_f16.len() * 2);
for v in &values_f16 {
bytes.extend_from_slice(&v.to_le_bytes());
}
let t = TensorRef {
name: "test_f16".into(),
shape: vec![values_f16.len()],
dtype: DType::F16,
data: std::sync::Arc::new(bytes),
};
let out = t.to_f32_vec().expect("F16 decode must succeed");
let expected: Vec<f32> = values_f16.iter().map(|v| v.to_f32()).collect();
for (a, b) in out.iter().zip(expected.iter()) {
assert_eq!(a.to_bits(), b.to_bits());
}
}
#[test]
fn to_f32_vec_rejects_non_float_dtypes() {
for dtype in [
DType::I32,
DType::I64,
DType::U8,
DType::U16,
DType::U32,
DType::Bool,
] {
let bytes = vec![0u8; 16];
let t = TensorRef {
name: format!("test_{dtype}"),
shape: vec![1],
dtype,
data: std::sync::Arc::new(bytes),
};
let element_count = t.data.len() / dtype.element_size();
let t = TensorRef {
name: format!("test_{dtype}"),
shape: vec![element_count],
dtype,
data: t.data.clone(),
};
let r = t.to_f32_vec();
assert!(r.is_err(), "{dtype} must be rejected");
let msg = format!("{:?}", r.err().unwrap());
assert!(
msg.contains("UnsupportedDtype"),
"{dtype}: error not UnsupportedDtype: {msg}"
);
}
}
#[test]
fn to_f32_vec_rejects_byte_len_mismatch() {
let t = TensorRef {
name: "torn".into(),
shape: vec![4],
dtype: DType::F32,
data: std::sync::Arc::new(vec![0u8; 12]),
};
let r = t.to_f32_vec();
assert!(r.is_err());
let msg = format!("{:?}", r.err().unwrap());
assert!(
msg.contains("ShapeMismatch"),
"expected ShapeMismatch, got: {msg}"
);
}
}