Skip to main content

ferric_load/
lib.rs

1//! Ferric weight loading — a pure-Rust `safetensors` reader. Parses the HF safetensors container
2//! (8-byte little-endian header length, a JSON header of name → {dtype, shape, data_offsets}, then a
3//! flat data blob) and dequantizes F32/F16/BF16/F64 tensors to f32, ready to upload to a Ferric
4//! `Context`. No Python, no C++ — this is how real Llama/SmolVLA checkpoints enter the ecosystem.
5
6use half::{bf16, f16};
7use std::collections::HashMap;
8
9/// One tensor decoded to f32 plus its shape.
10pub struct STensor {
11    pub data: Vec<f32>,
12    pub shape: Vec<usize>,
13}
14
15/// Parse a safetensors byte buffer into name → f32 tensor. `__metadata__` is skipped.
16pub fn safetensors(bytes: &[u8]) -> Result<HashMap<String, STensor>, String> {
17    if bytes.len() < 8 {
18        return Err("safetensors: too short".into());
19    }
20    let hlen = u64::from_le_bytes(bytes[0..8].try_into().unwrap()) as usize;
21    let base = 8 + hlen;
22    if bytes.len() < base {
23        return Err("safetensors: header exceeds buffer".into());
24    }
25    let header: serde_json::Value =
26        serde_json::from_slice(&bytes[8..base]).map_err(|e| format!("safetensors header json: {e}"))?;
27    let obj = header.as_object().ok_or("safetensors: header not an object")?;
28
29    let mut out = HashMap::new();
30    for (name, v) in obj {
31        if name == "__metadata__" {
32            continue;
33        }
34        let dtype = v["dtype"].as_str().ok_or("missing dtype")?;
35        let shape: Vec<usize> = v["shape"]
36            .as_array()
37            .ok_or("missing shape")?
38            .iter()
39            .map(|d| d.as_u64().unwrap() as usize)
40            .collect();
41        let off = v["data_offsets"].as_array().ok_or("missing data_offsets")?;
42        let (s, e) = (off[0].as_u64().unwrap() as usize, off[1].as_u64().unwrap() as usize);
43        let raw = &bytes[base + s..base + e];
44        let data = dequant(dtype, raw)?;
45        let n: usize = shape.iter().product();
46        if data.len() != n {
47            return Err(format!("{name}: {} elems for shape {shape:?} ({n})", data.len()));
48        }
49        out.insert(name.clone(), STensor { data, shape });
50    }
51    Ok(out)
52}
53
54/// Dequantize a raw dtype slice to f32.
55fn dequant(dtype: &str, raw: &[u8]) -> Result<Vec<f32>, String> {
56    Ok(match dtype {
57        "F32" => raw.chunks_exact(4).map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect(),
58        "F16" => raw.chunks_exact(2).map(|b| f16::from_le_bytes([b[0], b[1]]).to_f32()).collect(),
59        "BF16" => raw.chunks_exact(2).map(|b| bf16::from_le_bytes([b[0], b[1]]).to_f32()).collect(),
60        "F64" => raw.chunks_exact(8).map(|b| f64::from_le_bytes(b.try_into().unwrap()) as f32).collect(),
61        other => return Err(format!("unsupported safetensors dtype '{other}'")),
62    })
63}