use std::collections::HashMap;
use crate::model::Model;
use crate::ops;
#[cfg(not(feature = "lean-embed"))]
use crate::trace::DType;
#[cfg(fast_gemm)]
use {crate::gemm::PreparedB, std::cell::RefCell, std::sync::OnceLock};
#[cfg(fast_gemm)]
struct AffineWeight {
pb: PreparedB,
correction: Vec<f32>,
bias: OnceLock<Vec<f32>>,
qa: f32,
unquant: f32,
}
#[cfg(fast_gemm)]
#[derive(Default)]
struct GemmScratch {
a_u8: Vec<u8>,
wemb_row: Vec<i8>,
}
#[cfg(fast_gemm)]
thread_local! {
static GEMM_SCRATCH: RefCell<GemmScratch> = RefCell::new(GemmScratch::default());
}
#[derive(Clone, Copy, Debug)]
pub struct Config {
pub dim_emb: usize,
pub heads: usize,
pub enc_depth: usize,
pub dec_depth: usize,
pub dim_ffn: usize,
pub vocab: usize,
}
impl Config {
fn parse(yaml: &str) -> Config {
let get = |key: &str, default: usize| -> usize {
for line in yaml.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix(key) {
if let Some(v) = rest.trim().strip_prefix(':') {
if let Ok(n) = v.trim().parse() {
return n;
}
}
}
}
default
};
Config {
dim_emb: get("dim-emb", 384),
heads: get("transformer-heads", 8),
enc_depth: get("enc-depth", 6),
dec_depth: get("dec-depth", 4),
dim_ffn: get("transformer-dim-ffn", 1536),
vocab: get("dim-vocabs", 0),
}
}
}
pub struct Weights {
model: Model,
config: Config,
trg_vocab: usize,
dim: usize,
layer_norms: HashMap<String, (Vec<f32>, Option<Vec<f32>>)>,
trg_wemb_param: &'static str,
#[cfg(not(feature = "lean-embed"))]
trg_wemb: Vec<f32>,
#[cfg(not(feature = "lean-embed"))]
src_wemb: Option<Vec<f32>>,
#[cfg(feature = "lean-embed")]
src_wemb_param: &'static str,
#[cfg(feature = "lean-embed")]
src_inv_qmult: f32,
#[cfg(feature = "lean-embed")]
trg_inv_qmult: f32,
#[cfg(feature = "lean-embed")]
proj_bias: Vec<f32>,
#[cfg(feature = "lean-embed")]
proj_qa: f32,
#[cfg(feature = "lean-embed")]
proj_unquant: f32,
#[cfg(fast_gemm)]
affine_cache: HashMap<String, AffineWeight>,
#[cfg(all(fast_gemm, feature = "lean-embed"))]
proj_pb: Option<PreparedB>,
}
#[cfg(fast_gemm)]
fn prepare_affines(model: &mut Model, embed_param: &str) -> HashMap<String, AffineWeight> {
let names: Vec<String> = model.items.iter().map(|it| it.name.clone()).collect();
let mut cache = HashMap::new();
let mut dropped: Vec<String> = Vec::new();
for name in &names {
if name.ends_with("_QuantMultA") || name == embed_param {
continue;
}
let qa_name = format!("{name}_QuantMultA");
let it = match model.get(name) {
Some(it) if it.shape.len() >= 2 => it,
_ => continue,
};
let (k, n) = (it.shape[0] as usize, it.shape[1] as usize);
let b = match it.int8_transposed() {
Ok(b) => b,
Err(_) => continue,
};
let qb = match it.quant_mult() {
Ok(q) => q,
Err(_) => continue,
};
let qa = match model.get(&qa_name).and_then(|i| i.to_f32().ok()) {
Some(v) if !v.is_empty() => v[0],
_ => continue, };
let pb = match PreparedB::new(b, n, k) {
Some(pb) => pb, None => continue, };
let unquant = 1.0 / (qa * qb);
let correction = ops::prepare_bias(b, n, k, &vec![0.0; n], unquant);
cache.insert(
name.clone(),
AffineWeight {
pb,
correction,
bias: OnceLock::new(),
qa,
unquant,
},
);
dropped.push(name.clone());
}
for it in model.items.iter_mut() {
if dropped.iter().any(|d| d == &it.name) {
it.data = crate::model::Bytes::Owned(Vec::new());
}
}
cache
}
#[cfg(not(feature = "lean-embed"))]
fn load_embedding(model: &Model, name: &str) -> Result<Vec<f32>, String> {
let item = model
.get(name)
.ok_or_else(|| format!("model has no {name}"))?;
match item.dtype {
DType::Float32 => item.to_f32().map_err(|e| e.to_string()),
_ => {
let inv = 1.0 / item.quant_mult().map_err(|e| e.to_string())?;
let raw = item.int8_transposed().map_err(|e| e.to_string())?;
Ok(raw.iter().map(|&b| b as f32 * inv).collect())
}
}
}
fn read_output_qa(model: &Model) -> f32 {
if let Some(v) = model.get("none_QuantMultA").and_then(|it| it.to_f32().ok()) {
return v[0];
}
if let Some(it) = model.get("decoder_Wemb_QuantMultA") {
let raw = it.int8_transposed().expect("intgemm8 alpha scalar")[0] as f32;
let qmult = it.quant_mult().expect("intgemm8 alpha quant mult");
return raw / qmult;
}
panic!("model has no output-projection QuantMultA");
}
impl Weights {
pub fn load(path: impl AsRef<std::path::Path>) -> Result<Weights, String> {
let model = Model::load(path).map_err(|e| e.to_string())?;
Weights::new(model)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Weights, String> {
let model = Model::from_bytes(bytes).map_err(|e| e.to_string())?;
Weights::new(model)
}
#[cfg(feature = "mmap")]
pub fn load_mmapped(path: impl AsRef<std::path::Path>) -> Result<Weights, String> {
let model = Model::load_mmapped(path).map_err(|e| e.to_string())?;
Weights::new(model)
}
#[cfg_attr(not(fast_gemm), allow(unused_mut))]
pub fn new(mut model: Model) -> Result<Weights, String> {
let yaml = model
.get("special:model.yml")
.map(|it| String::from_utf8_lossy(&it.data).into_owned())
.unwrap_or_default();
let mut config = Config::parse(&yaml);
let (trg_wemb_param, src_wemb_param): (&'static str, &'static str) =
if model.get("Wemb").is_some() {
("Wemb", "Wemb")
} else {
("decoder_Wemb", "encoder_Wemb")
};
let trg_item = model
.get(trg_wemb_param)
.ok_or_else(|| format!("model has no {trg_wemb_param}"))?;
let dim = *trg_item.shape.last().ok_or("embedding has no shape")? as usize;
let trg_vocab = trg_item.num_elements() / dim;
if config.vocab == 0 {
config.vocab = trg_vocab;
}
let mut layer_norms: HashMap<String, (Vec<f32>, Option<Vec<f32>>)> = HashMap::new();
for it in &model.items {
if let Some(base) = it.name.strip_suffix("_ln_scale") {
if let Ok(v) = it.to_f32() {
layer_norms.entry(base.to_string()).or_default().0 = v;
}
} else if let Some(base) = it.name.strip_suffix("_ln_bias") {
if let Ok(v) = it.to_f32() {
layer_norms.entry(base.to_string()).or_default().1 = Some(v);
}
}
}
#[cfg(not(feature = "lean-embed"))]
{
let trg_wemb = load_embedding(&model, trg_wemb_param)?;
let src_wemb = if src_wemb_param == trg_wemb_param {
None
} else {
Some(load_embedding(&model, src_wemb_param)?)
};
#[cfg(fast_gemm)]
let affine_cache = prepare_affines(&mut model, trg_wemb_param);
Ok(Weights {
model,
config,
trg_vocab,
dim,
layer_norms,
trg_wemb_param,
trg_wemb,
src_wemb,
#[cfg(fast_gemm)]
affine_cache,
})
}
#[cfg(feature = "lean-embed")]
{
let qwemb = trg_item.quant_mult().map_err(|e| e.to_string())?;
let src_inv_qmult = 1.0
/ model
.get(src_wemb_param)
.ok_or_else(|| format!("model has no {src_wemb_param}"))?
.quant_mult()
.map_err(|e| e.to_string())?;
let proj_qa = read_output_qa(&model);
let proj_unquant = 1.0 / (proj_qa * qwemb);
let raw = trg_item.int8_transposed().map_err(|e| e.to_string())?;
let raw_bias = model
.get("decoder_ff_logit_out_b")
.and_then(|it| it.to_f32().ok())
.unwrap_or_else(|| vec![0.0; trg_vocab]);
let proj_bias = ops::prepare_bias(raw, trg_vocab, dim, &raw_bias, proj_unquant);
#[cfg(fast_gemm)]
let affine_cache = prepare_affines(&mut model, trg_wemb_param);
#[cfg(fast_gemm)]
let proj_pb = {
let packed = {
let raw = model
.get(trg_wemb_param)
.expect("target embedding")
.int8_transposed()
.map_err(|e| e.to_string())?;
PreparedB::new(raw, trg_vocab, dim)
};
if packed.is_some() {
if let Some(it) = model.items.iter_mut().find(|it| it.name == trg_wemb_param) {
it.data = crate::model::Bytes::Owned(Vec::new());
}
}
packed
};
Ok(Weights {
model,
config,
trg_vocab,
dim,
layer_norms,
trg_wemb_param,
src_wemb_param,
src_inv_qmult,
trg_inv_qmult: 1.0 / qwemb,
proj_bias,
proj_qa,
proj_unquant,
#[cfg(fast_gemm)]
affine_cache,
#[cfg(fast_gemm)]
proj_pb,
})
}
}
pub fn config(&self) -> Config {
self.config
}
pub fn f32(&self, name: &str) -> Option<Vec<f32>> {
self.model.get(name).and_then(|it| it.to_f32().ok())
}
pub fn output_vocab(&self) -> usize {
self.trg_vocab
}
pub fn full_logits(&self, h: &[f32]) -> Vec<f32> {
self.full_logits_batch(h, 1)
}
pub fn full_logits_batch(&self, h: &[f32], m: usize) -> Vec<f32> {
let mut out = Vec::new();
self.full_logits_batch_into(h, m, &mut out);
out
}
#[cfg(not(feature = "lean-embed"))]
pub fn full_logits_batch_into(&self, h: &[f32], m: usize, out: &mut Vec<f32>) {
let d = self.dim;
let vocab = self.trg_vocab;
let bias = self
.f32("decoder_ff_logit_out_b")
.unwrap_or_else(|| vec![0.0; vocab]);
out.clear();
out.resize(m * vocab, 0.0);
for row in 0..m {
let hr = &h[row * d..(row + 1) * d];
for v in 0..vocab {
let w = &self.trg_wemb[v * d..(v + 1) * d];
let mut acc = 0.0f32;
for c in 0..d {
acc += hr[c] * w[c];
}
out[row * vocab + v] = acc + bias[v];
}
}
}
#[cfg(feature = "lean-embed")]
pub fn full_logits_batch_into(&self, h: &[f32], m: usize, out: &mut Vec<f32>) {
#[cfg(fast_gemm)]
{
if let Some(pb) = self.proj_pb.as_ref() {
GEMM_SCRATCH.with_borrow_mut(|s| {
ops::prepare_a_into(h, self.proj_qa, &mut s.a_u8);
pb.matmul_into(&s.a_u8, m, self.proj_unquant, &self.proj_bias, out);
});
return;
}
}
let raw = self
.model
.get(self.trg_wemb_param)
.expect("target embedding")
.int8_transposed()
.expect("int8 embedding");
let a = ops::prepare_a(h, self.proj_qa);
*out = ops::intgemm_affine(
&a,
m,
self.dim,
raw,
self.trg_vocab,
self.proj_unquant,
&self.proj_bias,
);
}
pub fn layer_norm(&self, base: &str) -> Option<(&[f32], Option<&[f32]>)> {
self.layer_norms
.get(base)
.map(|(g, b)| (g.as_slice(), b.as_deref()))
}
#[cfg(not(feature = "lean-embed"))]
pub fn src_embed_row_into(&self, id: u32, dst: &mut [f32]) {
let d = self.dim;
let wemb = self.src_wemb.as_deref().unwrap_or(&self.trg_wemb);
dst.copy_from_slice(&wemb[id as usize * d..(id as usize + 1) * d]);
}
#[cfg(feature = "lean-embed")]
pub fn src_embed_row_into(&self, id: u32, dst: &mut [f32]) {
self.dequant_row_into(self.src_wemb_param, self.src_inv_qmult, id, dst);
}
#[cfg(not(feature = "lean-embed"))]
pub fn trg_embed_row_into(&self, id: u32, dst: &mut [f32]) {
let d = self.dim;
dst.copy_from_slice(&self.trg_wemb[id as usize * d..(id as usize + 1) * d]);
}
#[cfg(feature = "lean-embed")]
pub fn trg_embed_row_into(&self, id: u32, dst: &mut [f32]) {
self.dequant_row_into(self.trg_wemb_param, self.trg_inv_qmult, id, dst);
}
#[cfg(feature = "lean-embed")]
fn dequant_row_into(&self, param: &str, inv: f32, id: u32, dst: &mut [f32]) {
let d = self.dim;
#[cfg(fast_gemm)]
if param == self.trg_wemb_param {
if let Some(pb) = self.proj_pb.as_ref() {
GEMM_SCRATCH.with_borrow_mut(|s| {
s.wemb_row.resize(d, 0);
pb.read_row(id as usize, &mut s.wemb_row);
for (o, &b) in dst.iter_mut().zip(&s.wemb_row) {
*o = b as f32 * inv;
}
});
return;
}
}
let raw = self
.model
.get(param)
.expect("embedding param")
.int8_transposed()
.expect("int8 embedding");
for (o, &b) in dst
.iter_mut()
.zip(&raw[id as usize * d..(id as usize + 1) * d])
{
*o = b as f32 * inv;
}
}
pub fn output_wemb_qmult(&self) -> Option<f32> {
#[cfg(feature = "lean-embed")]
{
Some(1.0 / self.trg_inv_qmult)
}
#[cfg(not(feature = "lean-embed"))]
{
self.model.get(self.trg_wemb_param)?.quant_mult().ok()
}
}
pub fn output_wemb_int8_row(&self, id: u32, out: &mut [i8]) -> bool {
#[cfg(all(feature = "lean-embed", fast_gemm))]
if let Some(pb) = self.proj_pb.as_ref() {
pb.read_row(id as usize, out);
return true;
}
let d = self.dim;
match self
.model
.get(self.trg_wemb_param)
.and_then(|it| it.int8_transposed().ok())
{
Some(raw) => {
out.copy_from_slice(&raw[id as usize * d..(id as usize + 1) * d]);
true
}
None => false,
}
}
pub fn output_qa(&self) -> f32 {
read_output_qa(&self.model)
}
pub fn affine(&self, base: &str, x: &[f32], m: usize, bias_name: Option<&str>) -> Vec<f32> {
#[cfg(fast_gemm)]
{
if let Some(aw) = self.affine_cache.get(base) {
let bias = aw.bias.get_or_init(|| {
let mut bias = aw.correction.clone();
if let Some(bn) = bias_name {
if let Some(rb) = self.f32(bn) {
for (b, r) in bias.iter_mut().zip(rb.iter()) {
*b += *r;
}
}
}
bias
});
return GEMM_SCRATCH.with_borrow_mut(|s| {
ops::prepare_a_into(x, aw.qa, &mut s.a_u8);
let mut out = Vec::new();
aw.pb.matmul_into(&s.a_u8, m, aw.unquant, bias, &mut out);
out
});
}
}
let w = self
.model
.get(base)
.unwrap_or_else(|| panic!("missing weight {base}"));
let k = w.shape[0] as usize;
let n = w.shape[1] as usize;
let b = w.int8_transposed().expect("int8 weight");
debug_assert_eq!(b.len(), n * k);
let qb = w.quant_mult().expect("weight quant mult");
let qa = self
.f32(&format!("{base}_QuantMultA"))
.unwrap_or_else(|| panic!("missing {base}_QuantMultA"))[0];
let unquant = 1.0 / (qa * qb);
let raw_bias = match bias_name {
Some(bn) => self.f32(bn).unwrap_or_else(|| panic!("missing bias {bn}")),
None => vec![0.0; n],
};
let prepared = ops::prepare_bias(b, n, k, &raw_bias, unquant);
let a = ops::prepare_a(x, qa);
ops::intgemm_affine(&a, m, k, b, n, unquant, &prepared)
}
}