use super::{Linear, ModernBertConfig, ModernBertEmbeddings, ModernBertEncoder, ModernBertLayer};
use crate::format::v2::{AprV2ReaderRef, TensorDType};
use crate::format::AprV2DequantExt;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModernBertLoadError {
MissingTensor {
name: String,
},
ShapeMismatch {
name: String,
expected: Vec<usize>,
observed: Vec<usize>,
},
Undecodable {
name: String,
dtype: String,
},
NonFinite {
name: String,
},
}
impl fmt::Display for ModernBertLoadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingTensor { name } => write!(f, "modernbert load: missing tensor {name}"),
Self::ShapeMismatch {
name,
expected,
observed,
} => write!(
f,
"modernbert load: tensor {name} has shape {observed:?}, config implies {expected:?}"
),
Self::Undecodable { name, dtype } => write!(
f,
"modernbert load: tensor {name} ({dtype}) cannot be widened to f32"
),
Self::NonFinite { name } => {
write!(f, "modernbert load: tensor {name} holds a non-finite value")
}
}
}
}
impl std::error::Error for ModernBertLoadError {}
fn expected_tensors(config: &ModernBertConfig, prefix: &str) -> Vec<(String, Vec<usize>)> {
let d = config.hidden_size();
let inter = config.intermediate_size();
let mut t = vec![
(
format!("{prefix}embeddings.tok_embeddings.weight"),
vec![config.vocab_size(), d],
),
(format!("{prefix}embeddings.norm.weight"), vec![d]),
];
for i in 0..config.num_hidden_layers() {
let p = format!("{prefix}layers.{i}.");
if i > 0 {
t.push((format!("{p}attn_norm.weight"), vec![d]));
}
t.push((format!("{p}attn.Wqkv.weight"), vec![3 * d, d]));
t.push((format!("{p}attn.Wo.weight"), vec![d, d]));
t.push((format!("{p}mlp_norm.weight"), vec![d]));
t.push((format!("{p}mlp.Wi.weight"), vec![2 * inter, d]));
t.push((format!("{p}mlp.Wo.weight"), vec![d, inter]));
}
t.push((format!("{prefix}final_norm.weight"), vec![d]));
t
}
#[must_use]
pub fn expected_modernbert_tensor_names(config: &ModernBertConfig, prefix: &str) -> Vec<String> {
expected_tensors(config, prefix)
.into_iter()
.map(|(n, _)| n)
.collect()
}
pub fn load_tensor(
reader: &AprV2ReaderRef<'_>,
name: &str,
shape: &[usize],
) -> Result<Vec<f32>, ModernBertLoadError> {
let entry = reader
.get_tensor(name)
.ok_or_else(|| ModernBertLoadError::MissingTensor {
name: name.to_string(),
})?;
if entry.shape != shape {
return Err(ModernBertLoadError::ShapeMismatch {
name: name.to_string(),
expected: shape.to_vec(),
observed: entry.shape.clone(),
});
}
let undecodable = || ModernBertLoadError::Undecodable {
name: name.to_string(),
dtype: format!("{:?}", entry.dtype),
};
let width: u64 = match entry.dtype {
TensorDType::F16 => 2,
TensorDType::F32 => 4,
_ => return Err(undecodable()),
};
let elements = shape.iter().try_fold(1usize, |a, &b| a.checked_mul(b));
let expected_bytes = elements
.and_then(|n| u64::try_from(n).ok())
.and_then(|n| n.checked_mul(width));
if expected_bytes != Some(entry.size) {
return Err(undecodable());
}
let data = reader.get_tensor_as_f32(name).ok_or_else(undecodable)?;
if Some(data.len()) != elements {
return Err(undecodable());
}
if !data.iter().all(|v| v.is_finite()) {
return Err(ModernBertLoadError::NonFinite {
name: name.to_string(),
});
}
Ok(data)
}
impl ModernBertEncoder {
#[provable_contracts_macros::contract("laya-parity-v1", equation = "embeddings_abs")]
pub fn from_apr(
reader: &AprV2ReaderRef<'_>,
prefix: &str,
config: &ModernBertConfig,
) -> Result<Self, ModernBertLoadError> {
let mut tensors = expected_tensors(config, prefix).into_iter();
let mut next = || -> Result<(Vec<f32>, Vec<usize>), ModernBertLoadError> {
let (name, shape) =
tensors
.next()
.ok_or_else(|| ModernBertLoadError::MissingTensor {
name: format!("{prefix}<load order exhausted>"),
})?;
let data = load_tensor(reader, &name, &shape)?;
Ok((data, shape))
};
let lin = |(w, s): (Vec<f32>, Vec<usize>)| Linear {
w,
b: None,
out: s[0],
inp: s[1],
};
let d = config.hidden_size();
let tok = next()?.0;
let emb_norm = next()?.0;
let embeddings = ModernBertEmbeddings::from_parts(tok, emb_norm, config.vocab_size(), d);
let mut layers = Vec::with_capacity(config.num_hidden_layers());
for (i, &global) in config.layer_is_global().iter().enumerate() {
let attn_norm = if i == 0 { None } else { Some(next()?.0) };
let wqkv = lin(next()?);
let wo = lin(next()?);
let mlp_norm = next()?.0;
let wi = lin(next()?);
let wo_mlp = lin(next()?);
layers.push(ModernBertLayer::from_parts(
attn_norm, wqkv, wo, mlp_norm, wi, wo_mlp, global,
));
}
let final_norm = next()?.0;
Ok(Self::from_parts(
config.clone(),
embeddings,
layers,
final_norm,
))
}
}
#[cfg(test)]
mod tests {
use super::{expected_modernbert_tensor_names, ModernBertLoadError};
use crate::models::modernbert::test_support::{
fixture_config, fixture_tensors, load, write_apr, RawTensor,
};
#[test]
fn expected_names_match_the_hf_fixture() {
let cfg = fixture_config();
let mut want = expected_modernbert_tensor_names(&cfg, "");
want.sort();
let got: Vec<String> = fixture_tensors().into_iter().map(|t| t.0).collect();
assert_eq!(want, got);
assert!(expected_modernbert_tensor_names(&cfg, "encoder.")
.iter()
.all(|n| n.starts_with("encoder.")));
assert!(
!want.contains(&"layers.0.attn_norm.weight".to_string()),
"layer 0 is Identity"
);
}
fn without(name: &str) -> Vec<RawTensor> {
fixture_tensors()
.into_iter()
.filter(|t| t.0 != name)
.collect()
}
#[test]
fn refuses_missing_tensor_by_name() {
let cfg = fixture_config();
let missing = "layers.1.mlp.Wi.weight";
let err = load(&write_apr(&without(missing), ""), "", &cfg).expect_err("refused");
assert_eq!(
err,
ModernBertLoadError::MissingTensor {
name: missing.to_string()
}
);
let err =
load(&write_apr(&without(missing), "encoder."), "encoder.", &cfg).expect_err("refused");
assert_eq!(
err,
ModernBertLoadError::MissingTensor {
name: format!("encoder.{missing}")
}
);
}
#[test]
fn refuses_shape_mismatch() {
let cfg = fixture_config();
let name = "layers.0.attn.Wo.weight";
let tensors: Vec<RawTensor> = fixture_tensors()
.into_iter()
.map(|(n, s, raw)| {
if n == name {
(n, vec![16, 64], raw)
} else {
(n, s, raw)
}
})
.collect();
let err = load(&write_apr(&tensors, ""), "", &cfg).expect_err("refused");
assert_eq!(
err,
ModernBertLoadError::ShapeMismatch {
name: name.to_string(),
expected: vec![32, 32],
observed: vec![16, 64],
}
);
}
#[test]
fn refuses_non_finite() {
let cfg = fixture_config();
let name = "layers.2.mlp.Wo.weight";
for bits in [0x7E00u16, 0x7C00u16] {
let tensors: Vec<RawTensor> = fixture_tensors()
.into_iter()
.map(|(n, s, mut raw)| {
if n == name {
raw[10..12].copy_from_slice(&bits.to_le_bytes());
}
(n, s, raw)
})
.collect();
let err = load(&write_apr(&tensors, ""), "", &cfg).expect_err("refused");
assert_eq!(
err,
ModernBertLoadError::NonFinite {
name: name.to_string()
},
"f16 bits {bits:#06x}"
);
}
}
#[test]
fn refuses_foreign_dtype_and_truncated_payload() {
use crate::format::v2::{AprV2Metadata, AprV2Writer, TensorDType};
let cfg = fixture_config();
let name = "final_norm.weight";
let build = |dtype: TensorDType, cut: usize| {
let mut w = AprV2Writer::new(AprV2Metadata::default());
for (n, shape, raw) in fixture_tensors() {
if n == name {
let keep = raw.len().saturating_sub(cut);
let payload = if dtype == TensorDType::F16 {
raw[..keep].to_vec()
} else {
Vec::new()
};
w.add_tensor(n, dtype, shape, payload);
} else {
w.add_tensor(n, TensorDType::F16, shape, raw);
}
}
w.write().expect("write in-memory .apr")
};
for (label, apr) in [
("empty APR-Q4", build(TensorDType::AprQ4, 0)),
("F16 two bytes short", build(TensorDType::F16, 2)),
] {
let err = load(&apr, "", &cfg).expect_err(label);
assert!(
matches!(&err, ModernBertLoadError::Undecodable { name: n, .. } if n == name),
"{label}: {err:?}"
);
}
}
}