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::alibi::AlibiBias;
use super::alibi::AlibiSlopeVariant;
use super::config::JinaBertConfig;
use super::embeddings::JinaBertEmbeddings;
use super::layer::JinaBertLayer;
use super::variant::Variant;
use crate::error::Result;
use crate::layers::mask::AttentionMask;
use crate::models::Encoder;
use crate::models::EncoderInput;

pub struct JinaBertModel {
  embeddings: JinaBertEmbeddings,
  alibi: AlibiBias,
  layers: Vec<JinaBertLayer>,
  config: JinaBertConfig,
}

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

impl JinaBertModel {
  /// The block shape is probed once from layer 0's header, so a layer that disagrees fails the load.
  /// `alibi_variant` is unreadable from any file, so every caller passes it explicitly.
  pub fn load(
    config: JinaBertConfig,
    vb: VarBuilder,
    alibi_variant: AlibiSlopeVariant,
  ) -> Result<Self> {
    config.ensure_supported()?;
    let variant = Variant::probe(&vb);
    let embeddings = JinaBertEmbeddings::load(&config, vb.pp("embeddings"))?;
    let layers = (0..config.num_hidden_layers)
      .map(|i| JinaBertLayer::load(&config, variant, vb.pp(format!("encoder.layer.{i}"))))
      .collect::<Result<Vec<_>>>()?;
    Ok(Self {
      embeddings,
      alibi: AlibiBias::new(config.num_attention_heads, alibi_variant),
      layers,
      config,
    })
  }
}

impl Encoder for JinaBertModel {
  /// ALiBi is built once per forward at the batch's length and reaches every layer inside the mask.
  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 bias = self.alibi.forward(seq, hidden.device())?;
    let mask = AttentionMask::padding_with_bias(
      input.attention_mask,
      input.lengths,
      &bias,
      self.config.num_attention_heads,
      self.config.head_dim(),
      hidden.dtype(),
    )?;
    for layer in &self.layers {
      hidden = layer.forward(&hidden, &mask)?;
    }
    Ok(hidden)
  }

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

  /// 8192 for all six; ALiBi indexes no table, so this caps memory and the trained range, not bounds.
  fn max_position_embeddings(&self) -> usize {
    self.config.max_position_embeddings
  }

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

#[cfg(test)]
mod tests;