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
//! The encoder: an optional `new.` prefix probe, embeddings, post-norm blocks over one rotary table, no final norm.

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

use super::config::GteNewConfig;
use super::embeddings::GteNewEmbeddings;
use super::layer::GteNewLayer;
use crate::error::Result;
use crate::layers::mask::AttentionMask;
use crate::layers::rope::RotaryEmbedding;
use crate::models::Encoder;
use crate::models::EncoderInput;

pub struct GteNewModel {
  embeddings: GteNewEmbeddings,
  layers: Vec<GteNewLayer>,
  rope: RotaryEmbedding,
  config: GteNewConfig,
}

/// Manual because the blocks don't derive `Debug`; the tests need it for `unwrap_err`.
impl std::fmt::Debug for GteNewModel {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    f.debug_struct("GteNewModel").finish_non_exhaustive()
  }
}

impl GteNewModel {
  /// mGTE's tensors sit under `new.` (its backbone field name); en-v1.5's are bare. The probe reads tensor names.
  pub fn load(config: GteNewConfig, vb: VarBuilder) -> Result<Self> {
    let vb = if vb.contains_tensor("new.embeddings.word_embeddings.weight") {
      vb.pp("new")
    } else {
      vb
    };
    config.ensure_supported()?;
    let embeddings = GteNewEmbeddings::load(&config, vb.clone())?;
    let layers = (0..config.num_hidden_layers)
      .map(|i| GteNewLayer::load(&config, vb.pp(format!("encoder.layer.{i}"))))
      .collect::<Result<Vec<_>>>()?;
    let rope = RotaryEmbedding::with_frequency_scale(
      config.head_dim(),
      config.max_position_embeddings,
      config.rope_theta_effective(),
      config.rope_frequency_scale(),
      vb.dtype(),
      vb.device(),
    )?;
    Ok(Self {
      embeddings,
      layers,
      rope,
      config,
    })
  }

  /// `forward` reads the table through this accessor so it stays live outside tests.
  pub(super) fn rope(&self) -> &RotaryEmbedding {
    &self.rope
  }
}

impl Encoder for GteNewModel {
  /// `token_type_ids` is ignored; the config alone decides the token-type term.
  fn forward(&self, input: &EncoderInput) -> Result<Tensor> {
    let mut hidden = self.embeddings.forward(input.input_ids)?;
    let dtype = hidden.dtype();
    let mask = AttentionMask::padding(
      input.attention_mask,
      input.lengths,
      self.config.num_attention_heads,
      self.config.head_dim(),
      dtype,
    )?;
    for layer in &self.layers {
      hidden = layer.forward(&hidden, &mask, self.rope())?;
    }
    Ok(hidden)
  }

  fn hidden_size(&self) -> usize {
    self.config.hidden_size
  }

  /// The rotary table's size; a longer request would fail in `narrow`.
  fn max_position_embeddings(&self) -> usize {
    self.config.max_position_embeddings
  }

  fn weights_dtype(&self) -> DType {
    self.embeddings.weights_dtype()
  }
}

#[cfg(test)]
mod tests;