use super::layer::{rope_inv_freq, RopeTable};
use super::{layer_norm, ModernBertConfig, ModernBertEmbeddings, ModernBertError, ModernBertLayer};
#[derive(Debug, Clone)]
pub struct ModernBertEncoder {
config: ModernBertConfig,
embeddings: ModernBertEmbeddings,
layers: Vec<ModernBertLayer>,
final_norm: Vec<f32>,
inv_freq_global: Vec<f32>,
inv_freq_local: Vec<f32>,
#[cfg(test)]
window_override: Option<usize>,
}
impl ModernBertEncoder {
pub(crate) fn from_parts(
config: ModernBertConfig,
embeddings: ModernBertEmbeddings,
layers: Vec<ModernBertLayer>,
final_norm: Vec<f32>,
) -> Self {
let hd = config.head_dim();
Self {
inv_freq_global: rope_inv_freq(config.rope_theta_global(), hd),
inv_freq_local: rope_inv_freq(config.rope_theta_local(), hd),
config,
embeddings,
layers,
final_norm,
#[cfg(test)]
window_override: None,
}
}
pub fn config(&self) -> &ModernBertConfig {
&self.config
}
pub fn layers(&self) -> &[ModernBertLayer] {
&self.layers
}
#[cfg(test)]
pub(crate) fn set_window_override(&mut self, window: Option<usize>) {
self.window_override = window;
}
fn window(&self) -> usize {
#[cfg(test)]
if let Some(w) = self.window_override {
return w;
}
self.config.window()
}
#[provable_contracts_macros::contract("laya-parity-v1", equation = "per_layer_rel_rms")]
pub fn forward(
&self,
ids: &[u32],
mut tap: impl FnMut(&str, &[f32]),
) -> Result<Vec<f32>, ModernBertError> {
if ids.is_empty() {
return Err(ModernBertError::EmptyInput);
}
let (d, l) = (self.config.hidden_size(), ids.len());
let eps = self.config.norm_eps();
let window = self.window();
let mut x = self.embeddings.forward(ids, eps)?;
tap("emb", &x);
let rope_global = RopeTable::new(&self.inv_freq_global, l);
let rope_local = RopeTable::new(&self.inv_freq_local, l);
for (li, layer) in self.layers.iter().enumerate() {
let rope = if layer.is_global() {
&rope_global
} else {
&rope_local
};
layer.forward_with_rope(&mut x, l, &self.config, window, rope)?;
tap(&format!("layer{li}"), &x);
}
let x = layer_norm(&x, d, &self.final_norm, None, eps)?;
tap("final", &x);
Ok(x)
}
}