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: embeddings, `n_layer` post-norm blocks sharing a rotary table, no pooler.

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

use super::config::NomicBertConfig;
use super::embeddings::NomicBertEmbeddings;
use super::layer::NomicBertLayer;
use super::rotary::NomicRotary;
use crate::error::Result;
use crate::layers::mask::AttentionMask;
use crate::models::Encoder;
use crate::models::EncoderInput;

pub struct NomicBertModel {
  embeddings: NomicBertEmbeddings,
  layers: Vec<NomicBertLayer>,
  rotary: NomicRotary,
  config: NomicBertConfig,
}

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

impl NomicBertModel {
  pub fn load(config: NomicBertConfig, vb: VarBuilder) -> Result<Self> {
    config.ensure_supported()?;
    let embeddings = NomicBertEmbeddings::load(&config, vb.clone())?;
    let layers = (0..config.n_layer)
      .map(|i| NomicBertLayer::load(&config, vb.pp(format!("encoder.layers.{i}"))))
      .collect::<Result<Vec<_>>>()?;
    let rotary = NomicRotary::new(&config, vb.dtype(), vb.device())?;
    Ok(Self {
      embeddings,
      layers,
      rotary,
      config,
    })
  }
}

impl Encoder for NomicBertModel {
  fn forward(&self, input: &EncoderInput) -> Result<Tensor> {
    let mut hidden = self
      .embeddings
      .forward(input.input_ids, input.token_type_ids)?;
    let seq = hidden.dim(1)?;
    let mask = AttentionMask::padding(
      input.attention_mask,
      input.lengths,
      self.config.n_head,
      self.config.head_dim(),
      hidden.dtype(),
    )?;
    let table = self.rotary.for_length(seq)?;
    let rope = table.as_rope();
    for layer in &self.layers {
      hidden = layer.forward(&hidden, &mask, rope)?;
    }
    Ok(hidden)
  }

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

  /// Unbounded: rotary embeddings index no table, and the reference extrapolates past `v1.5`'s stated 2048,
  /// so a cap would truncate where it embeds. The effective length comes from the entry or sentence-bert config.
  fn max_position_embeddings(&self) -> usize {
    usize::MAX
  }

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

#[cfg(test)]
mod tests;