use crate::bert::{Dense, LayerNorm, gelu_erf};
use crate::kernels::*;
use crate::model::{Quant, Table, linear_multi};
use crate::safetensors::ModelFiles;
use anyhow::{Context, Result, bail};
use serde::Deserialize;
use std::path::Path;
fn d_eps() -> f64 {
1e-12
}
fn d_pad() -> usize {
1
}
fn d_buckets() -> usize {
32
}
const MAX_DISTANCE: usize = 128;
#[derive(Debug, Clone, Deserialize)]
pub struct MpnetConfig {
pub hidden_size: usize,
pub num_hidden_layers: usize,
pub num_attention_heads: usize,
pub intermediate_size: usize,
pub vocab_size: usize,
pub max_position_embeddings: usize,
#[serde(default = "d_eps")]
pub layer_norm_eps: f64,
#[serde(default = "d_pad")]
pub pad_token_id: usize,
#[serde(default = "d_buckets")]
pub relative_attention_num_buckets: usize,
#[serde(default)]
pub hidden_act: Option<String>,
}
impl MpnetConfig {
pub fn load(dir: &Path) -> Result<Self> {
let cfg: MpnetConfig = serde_json::from_slice(&crate::fsx::read(&dir.join("config.json"))?).context("parse MPNet config")?;
if let Some(a) = &cfg.hidden_act
&& a != "gelu"
{
bail!("hidden_act {a} is not supported (only gelu)");
}
if !cfg.hidden_size.is_multiple_of(cfg.num_attention_heads) {
bail!(
"hidden_size {} is not a multiple of {} heads",
cfg.hidden_size,
cfg.num_attention_heads
);
}
Ok(cfg)
}
}
fn relative_bucket(i: usize, j: usize, num_buckets: usize) -> usize {
let half = num_buckets / 2;
let (base, n) = if j > i { (half, j - i) } else { (0, i - j) };
let exact = half / 2;
if n < exact {
return base + n;
}
let log = (n as f32 / exact as f32).ln() / (MAX_DISTANCE as f32 / exact as f32).ln() * (half - exact) as f32;
base + (exact + log as usize).min(half - 1)
}
struct MpnetLayer<'a> {
q: Dense<'a>,
k: Dense<'a>,
v: Dense<'a>,
o: Dense<'a>,
ln_attn: LayerNorm,
up: Dense<'a>,
down: Dense<'a>,
ln_out: LayerNorm,
}
pub struct MpnetModel<'a> {
pub cfg: MpnetConfig,
pub quant: Quant,
word: Table<'a>,
pos: Table<'a>,
ln_embed: LayerNorm,
rel_bias: Vec<f32>,
layers: Vec<MpnetLayer<'a>>,
}
impl<'a> MpnetModel<'a> {
pub fn load(cfg: MpnetConfig, files: &'a ModelFiles, quant: Quant) -> Result<Self> {
let prefix = ["", "mpnet.", "model."]
.into_iter()
.find(|p| files.has(&format!("{p}embeddings.word_embeddings.weight")))
.context("cannot find embeddings.word_embeddings in checkpoint")?;
let (h, inter) = (cfg.hidden_size, cfg.intermediate_size);
let n = |s: &str| format!("{prefix}{s}");
let word = Table::load(files, &n("embeddings.word_embeddings.weight"), cfg.vocab_size, h, Quant::Bf16)?;
let pos = Table::load(
files,
&n("embeddings.position_embeddings.weight"),
cfg.max_position_embeddings,
h,
Quant::Bf16,
)?;
let ln_embed = LayerNorm::load(files, &n("embeddings.LayerNorm"), h)?;
let rel_bias = files.f32(&n("encoder.relative_attention_bias.weight"))?;
if rel_bias.len() != cfg.relative_attention_num_buckets * cfg.num_attention_heads {
bail!(
"relative_attention_bias: expected {} x {} values",
cfg.relative_attention_num_buckets,
cfg.num_attention_heads
);
}
let mut layers = Vec::with_capacity(cfg.num_hidden_layers);
for l in 0..cfg.num_hidden_layers {
let p = |s: &str| n(&format!("encoder.layer.{l}.{s}"));
let d = |s: &str, o: usize, i: usize| Dense::load(files, &p(s), o, i, quant);
layers.push(MpnetLayer {
q: d("attention.attn.q", h, h)?,
k: d("attention.attn.k", h, h)?,
v: d("attention.attn.v", h, h)?,
o: d("attention.attn.o", h, h)?,
ln_attn: LayerNorm::load(files, &p("attention.LayerNorm"), h)?,
up: d("intermediate.dense", inter, h)?,
down: d("output.dense", h, inter)?,
ln_out: LayerNorm::load(files, &p("output.LayerNorm"), h)?,
});
}
Ok(Self {
cfg,
quant,
word,
pos,
ln_embed,
rel_bias,
layers,
})
}
pub fn forward(&self, tokens: &[u32]) -> Vec<f32> {
let cfg = &self.cfg;
let (h, nh) = (cfg.hidden_size, cfg.num_attention_heads);
let hd = h / nh;
let off = cfg.pad_token_id + 1;
let t = tokens.len().min(cfg.max_position_embeddings - off);
let tokens = &tokens[..t];
let eps = cfg.layer_norm_eps as f32;
let mut x = vec![0f32; t * h];
let mut pe = vec![0f32; h];
for (i, row) in x.chunks_mut(h).enumerate() {
self.word.row(tokens[i] as usize, row);
self.pos.row(off + i, &mut pe);
add_inplace(row, &pe);
}
self.ln_embed.apply_rows(&mut x, eps);
let nb = cfg.relative_attention_num_buckets;
let mut bias = vec![0f32; nh * t * t];
for i in 0..t {
for j in 0..t {
let b = relative_bucket(i, j, nb);
for hh in 0..nh {
bias[(hh * t + i) * t + j] = self.rel_bias[b * nh + hh];
}
}
}
let scale = (hd as f32).powf(-0.5);
let (mut q, mut k, mut v) = (vec![0f32; t * h], vec![0f32; t * h], vec![0f32; t * h]);
let mut att = vec![0f32; t * h];
let mut o = vec![0f32; t * h];
let mut u = vec![0f32; t * cfg.intermediate_size];
for layer in &self.layers {
linear_multi(
&mut [
(&layer.q.w, h, &mut q[..]),
(&layer.k.w, h, &mut k[..]),
(&layer.v.w, h, &mut v[..]),
],
h,
&x,
t,
);
layer.q.add_bias(&mut q);
layer.k.add_bias(&mut k);
layer.v.add_bias(&mut v);
scale_inplace(&mut q, scale);
attend(&q, &k, &v, &bias, nh, hd, t, &mut att);
layer.o.forward(&att, t, &mut o);
add_inplace(&mut x, &o);
layer.ln_attn.apply_rows(&mut x, eps);
layer.up.forward(&x, t, &mut u);
let par = u.len() > 8192;
for_chunks(&mut u, 4096, par, |_, c| {
for e in c.iter_mut() {
*e = gelu_erf(*e);
}
});
layer.down.forward(&u, t, &mut o);
add_inplace(&mut x, &o);
layer.ln_out.apply_rows(&mut x, eps);
}
x
}
}
#[allow(clippy::too_many_arguments)]
fn attend(q: &[f32], k: &[f32], v: &[f32], bias: &[f32], nh: usize, hd: usize, t: usize, out: &mut [f32]) {
let h = nh * hd;
for_chunks(out, h, t > 1, |i, orow| {
let mut s = vec![0f32; t];
orow.fill(0.0);
for hh in 0..nh {
let qh = &q[i * h + hh * hd..][..hd];
let b = &bias[(hh * t + i) * t..][..t];
for (j, sj) in s.iter_mut().enumerate() {
*sj = dot(qh, &k[j * h + hh * hd..][..hd]) + b[j];
}
softmax_inplace(&mut s);
let oh = &mut orow[hh * hd..][..hd];
for (j, &p) in s.iter().enumerate() {
axpy(oh, p, &v[j * h + hh * hd..][..hd]);
}
}
});
}
#[cfg(test)]
mod tests {
use super::relative_bucket;
#[test]
fn buckets_match_reference() {
let edges = [
(-520, 15),
(-90, 14),
(-63, 13),
(-45, 12),
(-31, 11),
(-22, 10),
(-15, 9),
(-11, 8),
(-7, 7),
(-6, 6),
(-5, 5),
(-4, 4),
(-3, 3),
(-2, 2),
(-1, 1),
(0, 0),
(1, 17),
(2, 18),
(3, 19),
(4, 20),
(5, 21),
(6, 22),
(7, 23),
(8, 24),
(12, 25),
(16, 26),
(23, 27),
(32, 28),
(46, 29),
(64, 30),
(91, 31),
];
let mut want = 0;
let mut e = edges.iter().peekable();
for r in -520i64..=520 {
if let Some(&&(at, b)) = e.peek()
&& at == r
{
want = b;
e.next();
}
let (i, j) = if r < 0 { ((-r) as usize, 0) } else { (0, r as usize) };
assert_eq!(relative_bucket(i, j, 32), want, "relative position {r}");
}
}
}