use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::te::config::TeConfig;
use crate::te::error::{TeError, TeResult};
use crate::te::mlx4bit::Mlx4bitModel;
#[derive(Debug, Clone)]
pub struct Tensor {
pub data: Vec<f32>,
pub shape: Vec<usize>,
}
impl Tensor {
pub fn numel(&self) -> usize {
self.shape.iter().product()
}
}
enum Source {
NpyDir(PathBuf),
Mlx4bit(Box<Mlx4bitModel>),
}
pub struct TeWeights {
source: Source,
config: TeConfig,
cache: RefCell<HashMap<String, Rc<Tensor>>>,
resident: Cell<bool>,
}
impl TeWeights {
pub fn open(dir: &Path) -> TeResult<Self> {
if !dir.is_dir() {
return Err(TeError::Io {
path: dir.display().to_string(),
source: std::io::Error::new(
std::io::ErrorKind::NotFound,
"weights directory not found",
),
});
}
let config = TeConfig::from_manifest_dir(dir).unwrap_or_default();
Ok(Self {
source: Source::NpyDir(dir.to_path_buf()),
config,
cache: RefCell::new(HashMap::new()),
resident: Cell::new(false),
})
}
pub fn open_mlx_4bit(safetensors_path: &Path) -> TeResult<Self> {
let model = Mlx4bitModel::open(safetensors_path)?;
Ok(Self {
source: Source::Mlx4bit(Box::new(model)),
config: TeConfig::default(),
cache: RefCell::new(HashMap::new()),
resident: Cell::new(false),
})
}
pub fn config(&self) -> &TeConfig {
&self.config
}
pub fn set_resident(&self, on: bool) {
self.resident.set(on);
if !on {
self.cache.borrow_mut().clear();
}
}
pub fn get(&self, name: &str) -> TeResult<Rc<Tensor>> {
match &self.source {
Source::NpyDir(dir) => {
if let Some(t) = self.cache.borrow().get(name) {
return Ok(t.clone());
}
let path = dir.join(format!("{name}.npy"));
if !path.exists() {
return Err(TeError::MissingWeight {
name: name.to_string(),
});
}
let t = Rc::new(read_npy_f32(&path)?);
self.cache.borrow_mut().insert(name.to_string(), t.clone());
Ok(t)
}
Source::Mlx4bit(model) => {
if self.resident.get() {
if let Some(t) = self.cache.borrow().get(name) {
return Ok(t.clone());
}
let t = Rc::new(model.load_tensor(name)?);
self.cache.borrow_mut().insert(name.to_string(), t.clone());
Ok(t)
} else {
Ok(Rc::new(model.load_tensor(name)?))
}
}
}
}
pub fn linear(&self, name: &str, out: usize, in_: usize) -> TeResult<Rc<Tensor>> {
let t = self.get(name)?;
if t.shape != [out, in_] {
return Err(TeError::Shape(format!(
"{name}: expected [{out}, {in_}], got {:?}",
t.shape
)));
}
Ok(t)
}
pub fn vec1(&self, name: &str, len: usize) -> TeResult<Rc<Tensor>> {
let t = self.get(name)?;
if t.shape != [len] {
return Err(TeError::Shape(format!(
"{name}: expected [{len}], got {:?}",
t.shape
)));
}
Ok(t)
}
pub fn embed_gather(&self, name: &str, ids: &[u32], cols: usize) -> TeResult<Vec<f32>> {
match &self.source {
Source::Mlx4bit(model) => {
let vocab = self.config.vocab_size;
let mut rows = Vec::with_capacity(ids.len());
for &id in ids {
let row = id as usize;
if row >= vocab {
return Err(TeError::Shape(format!(
"token id {row} >= vocab_size {vocab}"
)));
}
rows.push(row);
}
model.gather_quant_rows(name, &rows, cols)
}
Source::NpyDir(_) => {
let table = self.get(name)?;
let vocab = self.config.vocab_size;
if table.shape != [vocab, cols] {
return Err(TeError::Shape(format!(
"{name}: expected [{vocab}, {cols}], got {:?}",
table.shape
)));
}
let mut out = vec![0.0f32; ids.len() * cols];
for (t, &id) in ids.iter().enumerate() {
let row = id as usize;
if row >= vocab {
return Err(TeError::Shape(format!(
"token id {row} >= vocab_size {vocab}"
)));
}
out[t * cols..(t + 1) * cols]
.copy_from_slice(&table.data[row * cols..(row + 1) * cols]);
}
Ok(out)
}
}
}
}
pub fn read_npy_f32(path: &Path) -> TeResult<Tensor> {
let bytes = std::fs::read(path).map_err(|e| TeError::Io {
path: path.display().to_string(),
source: e,
})?;
let npy = |reason: String| TeError::Npy {
path: path.display().to_string(),
reason,
};
if bytes.len() < 10 || &bytes[..6] != b"\x93NUMPY" {
return Err(npy("bad npy magic".to_string()));
}
let major = bytes[6];
let (header_start, header_len) = if major >= 2 {
if bytes.len() < 12 {
return Err(npy("truncated v2 header length".to_string()));
}
let len = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as usize;
(12usize, len)
} else {
let len = u16::from_le_bytes([bytes[8], bytes[9]]) as usize;
(10usize, len)
};
if header_start + header_len > bytes.len() {
return Err(npy("header extends past file".to_string()));
}
let header = std::str::from_utf8(&bytes[header_start..header_start + header_len])
.map_err(|e| npy(format!("header utf8: {e}")))?;
if !header.contains("'<f4'") {
return Err(npy(format!("descr is not '<f4': {header}")));
}
let fortran = header.contains("'fortran_order': True");
let s_idx = header
.find("'shape':")
.ok_or_else(|| npy("no shape key".to_string()))?;
let open = header[s_idx..]
.find('(')
.map(|o| s_idx + o + 1)
.ok_or_else(|| npy("no shape open paren".to_string()))?;
let close = header[open..]
.find(')')
.map(|c| open + c)
.ok_or_else(|| npy("no shape close paren".to_string()))?;
let shape: Vec<usize> = header[open..close]
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| {
s.parse::<usize>()
.map_err(|e| npy(format!("shape parse: {e}")))
})
.collect::<Result<_, _>>()?;
let data_start = header_start + header_len;
let payload = &bytes[data_start..];
if payload.len() % 4 != 0 {
return Err(npy("payload not f32-aligned".to_string()));
}
let numel: usize = shape.iter().product();
if payload.len() / 4 < numel {
return Err(npy(format!(
"payload short ({} < {})",
payload.len() / 4,
numel
)));
}
let raw: Vec<f32> = payload[..numel * 4]
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let data = if fortran && shape.len() > 1 {
fortran_to_c(&raw, &shape)
} else {
raw
};
Ok(Tensor { data, shape })
}
fn fortran_to_c(src: &[f32], shape: &[usize]) -> Vec<f32> {
let ndim = shape.len();
let numel: usize = shape.iter().product();
let mut f_stride = vec![1usize; ndim];
for d in 1..ndim {
f_stride[d] = f_stride[d - 1] * shape[d - 1];
}
let mut out = vec![0.0f32; numel];
for (c_pos, slot) in out.iter_mut().enumerate() {
let mut rem = c_pos;
let mut f_off = 0usize;
for d in 0..ndim {
let stride_c: usize = shape[d + 1..].iter().product();
let idx = rem / stride_c;
rem %= stride_c;
f_off += idx * f_stride[d];
}
*slot = src[f_off];
}
out
}