candding 0.1.0

Pure candle embeddings for Rust with no ONNX: dense, sparse, multi-vector and rerank models today, image models on the roadmap
Documentation
//! One pre-norm block with two residuals; layer 0 has no attention norm, as the embeddings' norm just ran.

use candle_core::Tensor;
use candle_nn::VarBuilder;

use super::attention::ModernBertAttention;
use super::config::ModernBertConfig;
use super::mlp::ModernBertMlp;
use crate::error::Result;
use crate::layers::layer_norm::LayerNorm;
use crate::layers::mask::AttentionMask;
use crate::layers::rope::RotaryEmbedding;

pub struct ModernBertLayer {
  attn_norm: Option<LayerNorm>,
  attention: ModernBertAttention,
  mlp_norm: LayerNorm,
  mlp: ModernBertMlp,
}

impl ModernBertLayer {
  /// `vb` is `layers.{i}`.
  pub fn load(config: &ModernBertConfig, vb: VarBuilder, layer: usize) -> Result<Self> {
    let hidden = config.hidden_size;
    let attn_norm = if layer == 0 {
      None
    } else {
      Some(LayerNorm::load_without_bias(
        vb.pp("attn_norm"),
        hidden,
        config.norm_eps,
      )?)
    };
    Ok(Self {
      attn_norm,
      attention: ModernBertAttention::load(config, vb.clone())?,
      mlp_norm: LayerNorm::load_without_bias(vb.pp("mlp_norm"), hidden, config.norm_eps)?,
      mlp: ModernBertMlp::load(config, vb)?,
    })
  }

  pub fn forward(
    &self,
    hidden: &Tensor,
    mask: &AttentionMask,
    rope: &RotaryEmbedding,
  ) -> Result<Tensor> {
    let normed = match &self.attn_norm {
      Some(norm) => norm.forward(hidden)?,
      None => hidden.clone(),
    };
    let hidden = (hidden + self.attention.forward(&normed, mask, rope)?)?;
    let projected = self.mlp.forward(&self.mlp_norm.forward(&hidden)?)?;
    Ok((hidden + projected)?)
  }
}