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 candle_core::DType;
use candle_core::Tensor;
use candle_nn::VarBuilder;

use super::config::BertConfig;
use super::embeddings::BertEmbeddings;
use super::layer::BertLayer;
use crate::error::Result;
use crate::layers::mask::AttentionMask;
use crate::models::Encoder;
use crate::models::EncoderInput;

pub struct BertModel {
  embeddings: BertEmbeddings,
  layers: Vec<BertLayer>,
  config: BertConfig,
}

impl BertModel {
  /// Accepts checkpoints with or without the `bert.` prefix.
  pub fn load(config: BertConfig, vb: VarBuilder) -> Result<Self> {
    let vb = if vb.contains_tensor("embeddings.word_embeddings.weight") {
      vb
    } else {
      vb.pp("bert")
    };
    let embeddings = BertEmbeddings::load(&config, vb.pp("embeddings"))?;
    let layers = (0..config.num_hidden_layers)
      .map(|i| BertLayer::load(&config, vb.pp(format!("encoder.layer.{i}"))))
      .collect::<Result<Vec<_>>>()?;
    Ok(Self {
      embeddings,
      layers,
      config,
    })
  }

  /// The `(vocab, hidden)` token embeddings, reused by a tied sparse head instead of loading a second copy.
  pub(crate) fn embed_tokens(&self) -> &Tensor {
    self.embeddings.word_embeddings()
  }

  /// Embedding lookup and padding mask, shared by both forwards so they start from the same state.
  fn embed_and_mask(&self, input: &EncoderInput) -> Result<(Tensor, AttentionMask)> {
    let hidden = self
      .embeddings
      .forward(input.input_ids, input.token_type_ids)?;
    let mask = AttentionMask::padding(
      input.attention_mask,
      input.lengths,
      self.config.num_attention_heads,
      self.config.head_dim(),
      hidden.dtype(),
    )?;
    Ok((hidden, mask))
  }

  /// `forward` plus the last layer's attention weights, `(batch, heads, seq, seq)`, for a sparse head's CLS row.
  /// Only the last layer takes the weight-returning matmul path.
  pub fn forward_with_attention(&self, input: &EncoderInput) -> Result<(Tensor, Tensor)> {
    let (mut hidden, mask) = self.embed_and_mask(input)?;
    let (last, rest) = self
      .layers
      .split_last()
      .expect("a loaded BertModel always has at least one layer");
    for layer in rest {
      hidden = layer.forward(&hidden, &mask)?;
    }
    last.forward_with_weights(&hidden, &mask)
  }
}

impl Encoder for BertModel {
  fn forward(&self, input: &EncoderInput) -> Result<Tensor> {
    let (mut hidden, mask) = self.embed_and_mask(input)?;
    for layer in &self.layers {
      hidden = layer.forward(&hidden, &mask)?;
    }
    Ok(hidden)
  }

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

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

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

#[cfg(test)]
mod tests;