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
use serde::Deserialize;

use crate::error::CanddingError;
use crate::error::Result;

/// The `ModernBertConfig` fields this family reads, as `Alibaba-NLP/gte-modernbert-base` publishes them.
#[derive(Clone, Debug, Deserialize)]
pub struct ModernBertConfig {
  pub hidden_size: usize,
  pub num_attention_heads: usize,
  pub num_hidden_layers: usize,
  pub intermediate_size: usize,
  #[serde(default = "default_hidden_activation")]
  pub hidden_activation: String,
  #[serde(default = "default_global_attn_every_n_layers")]
  pub global_attn_every_n_layers: usize,
  #[serde(default = "default_local_attention")]
  pub local_attention: usize,
  #[serde(default = "default_global_rope_theta")]
  pub global_rope_theta: f64,
  #[serde(default = "default_local_rope_theta")]
  pub local_rope_theta: f64,
  #[serde(default = "default_attention_bias")]
  pub attention_bias: bool,
  #[serde(default = "default_mlp_bias")]
  pub mlp_bias: bool,
  #[serde(default = "default_norm_bias")]
  pub norm_bias: bool,
  #[serde(default = "default_norm_eps")]
  pub norm_eps: f64,
  pub vocab_size: usize,
  pub max_position_embeddings: usize,
}

fn default_hidden_activation() -> String {
  "gelu".to_string()
}

/// Upstream reads it from kwargs with this default; it is not a declared config field.
fn default_global_attn_every_n_layers() -> usize {
  3
}

fn default_local_attention() -> usize {
  128
}

/// Upstream's `default_theta["global"]`, read from kwargs; not a declared config field.
fn default_global_rope_theta() -> f64 {
  160_000.0
}

/// Upstream's `default_theta["local"]`, the sliding layers' base.
fn default_local_rope_theta() -> f64 {
  10_000.0
}

fn default_attention_bias() -> bool {
  false
}

fn default_mlp_bias() -> bool {
  false
}

fn default_norm_bias() -> bool {
  false
}

fn default_norm_eps() -> f64 {
  1e-5
}

impl ModernBertConfig {
  pub fn head_dim(&self) -> usize {
    self.hidden_size / self.num_attention_heads
  }

  pub fn is_global(&self, layer: usize) -> bool {
    layer.is_multiple_of(self.global_attn_every_n_layers)
  }

  /// The reference keeps keys with `|i - j| <= local_attention / 2`; `AttentionMask::sliding` is exclusive,
  /// so the window is `local_attention / 2 + 1` (65 for the published 128).
  pub fn attention_window(&self) -> usize {
    self.local_attention / 2 + 1
  }

  /// Refuses configs whose activation, biases, layer pattern or window this port does not implement.
  pub fn ensure_supported(&self) -> Result<()> {
    if self.hidden_activation != "gelu" {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: hidden_activation {} is not supported; the port applies the erf GELU",
        self.hidden_activation
      )));
    }
    if self.attention_bias {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: attention_bias {} is not supported; no attention bias tensor is loaded",
        self.attention_bias
      )));
    }
    if self.mlp_bias {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: mlp_bias {} is not supported; no MLP bias tensor is loaded",
        self.mlp_bias
      )));
    }
    if self.norm_bias {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: norm_bias {} is not supported; every norm is loaded weight-only",
        self.norm_bias
      )));
    }
    if self.global_attn_every_n_layers == 0 {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: global_attn_every_n_layers {} is not supported; a modulus of zero",
        self.global_attn_every_n_layers
      )));
    }
    if !self.local_attention.is_multiple_of(2) {
      return Err(CanddingError::UnsupportedArchitecture(format!(
        "modernbert: local_attention {} is not supported; the reference halves it with integer division; the \
         port would round",
        self.local_attention
      )));
    }
    Ok(())
  }
}

#[cfg(test)]
mod tests;