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::XlmRobertaFlashConfig;
use super::embeddings::XlmRobertaFlashEmbeddings;
use super::layer::XlmRobertaFlashLayer;
use super::position::PositionScheme;
use crate::error::Result;
use crate::layers::mask::AttentionMask;
use crate::layers::rope::RotaryEmbedding;
use crate::models::Encoder;
use crate::models::EncoderInput;

pub struct XlmRobertaFlashModel {
  embeddings: XlmRobertaFlashEmbeddings,
  layers: Vec<XlmRobertaFlashLayer>,
  /// The rotary scheme's angles; `None` under the absolute scheme, whose positions the embeddings add.
  rope: Option<RotaryEmbedding>,
  scheme: PositionScheme,
  config: XlmRobertaFlashConfig,
}

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

impl XlmRobertaFlashModel {
  /// `declared` is the registry entry's position scheme; a `config.json` naming another is refused before any read.
  /// Tensors may sit under `roberta.`; bare names are probed first.
  pub fn load(
    config: XlmRobertaFlashConfig,
    vb: VarBuilder,
    declared: Option<PositionScheme>,
  ) -> Result<Self> {
    config.ensure_supported()?;
    let scheme = PositionScheme::resolve(declared, config.position_embedding_type.as_deref())?;
    let vb = if vb.contains_tensor("embeddings.word_embeddings.weight") {
      vb
    } else {
      vb.pp("roberta")
    };
    let embeddings = XlmRobertaFlashEmbeddings::load(&config, vb.clone(), scheme)?;
    let layers = (0..config.num_hidden_layers)
      .map(|i| XlmRobertaFlashLayer::load(&config, vb.pp(format!("encoder.layers.{i}"))))
      .collect::<Result<Vec<_>>>()?;
    let rope = match scheme {
      PositionScheme::Rotary => Some(RotaryEmbedding::new(
        config.head_dim(),
        config.max_position_embeddings,
        config.rotary_emb_base,
        vb.dtype(),
        vb.device(),
      )?),
      PositionScheme::Absolute => None,
    };
    Ok(Self {
      embeddings,
      layers,
      rope,
      scheme,
      config,
    })
  }
}

impl Encoder for XlmRobertaFlashModel {
  fn forward(&self, input: &EncoderInput) -> Result<Tensor> {
    let mut 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(),
    )?;
    for layer in &self.layers {
      hidden = layer.forward(&hidden, &mask, self.rope.as_ref())?;
    }
    Ok(hidden)
  }

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

  /// Rotary: the declared maximum, since nothing indexes a table. Absolute: the table's sequence limit, since a
  /// longer request would index past it.
  fn max_position_embeddings(&self) -> usize {
    match self.scheme {
      PositionScheme::Rotary => self.config.max_position_embeddings,
      PositionScheme::Absolute => self.config.sequence_limit(),
    }
  }

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

#[cfg(test)]
mod tests;