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::Module;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::VarBuilder;
use candle_nn::linear_no_bias;

use super::config::Qwen3Config;
use crate::error::Result;
use crate::layers::attention::scaled_dot_product;
use crate::layers::mask::AttentionMask;
use crate::layers::rms_norm::RmsNorm;
use crate::layers::rms_norm::RmsNormStyle;
use crate::layers::rope::RotaryEmbedding;

pub struct Qwen3Attention {
  q_proj: Linear,
  k_proj: Linear,
  v_proj: Linear,
  o_proj: Linear,
  q_norm: RmsNorm,
  k_norm: RmsNorm,
  num_heads: usize,
  num_kv_heads: usize,
  head_dim: usize,
}

impl Qwen3Attention {
  pub fn load(config: &Qwen3Config, vb: VarBuilder) -> Result<Self> {
    let hidden = config.hidden_size;
    let heads = config.num_attention_heads;
    let kv_heads = config.num_key_value_heads;
    let head_dim = config.head_dim;
    Ok(Self {
      q_proj: linear_no_bias(hidden, heads * head_dim, vb.pp("q_proj"))?,
      k_proj: linear_no_bias(hidden, kv_heads * head_dim, vb.pp("k_proj"))?,
      v_proj: linear_no_bias(hidden, kv_heads * head_dim, vb.pp("v_proj"))?,
      o_proj: linear_no_bias(heads * head_dim, hidden, vb.pp("o_proj"))?,
      q_norm: RmsNorm::load(
        vb.pp("q_norm"),
        head_dim,
        config.rms_norm_eps,
        RmsNormStyle::Scale,
      )?,
      k_norm: RmsNorm::load(
        vb.pp("k_norm"),
        head_dim,
        config.rms_norm_eps,
        RmsNormStyle::Scale,
      )?,
      num_heads: heads,
      num_kv_heads: kv_heads,
      head_dim,
    })
  }

  /// Queries and keys are normalized per head before the rotation, as in transformers' `Qwen3Attention`.
  pub fn forward(
    &self,
    hidden: &Tensor,
    rope: &RotaryEmbedding,
    mask: &AttentionMask,
  ) -> Result<Tensor> {
    let (batch, seq, _) = hidden.dims3()?;
    let q = self
      .q_norm
      .forward(&self.q_proj.forward(hidden)?.reshape((
        batch,
        seq,
        self.num_heads,
        self.head_dim,
      ))?)?
      .transpose(1, 2)?;
    let k = self
      .k_norm
      .forward(&self.k_proj.forward(hidden)?.reshape((
        batch,
        seq,
        self.num_kv_heads,
        self.head_dim,
      ))?)?
      .transpose(1, 2)?;
    let v = self
      .v_proj
      .forward(hidden)?
      .reshape((batch, seq, self.num_kv_heads, self.head_dim))?
      .transpose(1, 2)?
      .contiguous()?;
    let (q, k) = rope.apply(&q, &k)?;
    let scale = 1.0 / (self.head_dim as f64).sqrt();
    let context = scaled_dot_product(&q, &k, &v, mask, scale)?
      .transpose(1, 2)?
      .reshape((batch, seq, self.num_heads * self.head_dim))?;
    Ok(self.o_proj.forward(&context)?)
  }
}