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 super::*;

fn bidirectional_config() -> Qwen3Config {
  let mut config = tiny_config();
  config.is_causal = false;
  config
}

fn bidirectional_model() -> Qwen3Model {
  let varmap = VarMap::new();
  let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
  Qwen3Model::load(bidirectional_config(), vb).unwrap()
}

#[test]
fn bidirectional_branch_lets_earlier_positions_attend_to_later_ones() {
  let model = bidirectional_model();
  let a = run(&model, &[vec![2, 5, 7]]);
  let b = run(&model, &[vec![2, 5, 9]]);
  let earlier_position_moved = (0..2).any(|pos| {
    a[0][pos]
      .iter()
      .zip(&b[0][pos])
      .any(|(x, y)| (x - y).abs() > 1e-5)
  });
  assert!(
    earlier_position_moved,
    "an earlier position must see the changed later token under bidirectional attention"
  );
}

/// Unlike the causal case, only `AttentionMask::padding` keeps padding from a real token here.
#[test]
fn bidirectional_branch_excludes_padded_positions() {
  let model = bidirectional_model();
  let alone = run(&model, &[vec![2, 9, 3]]);
  let batched = run(&model, &[vec![2, 5, 7, 8, 3], vec![2, 9, 3]]);
  for (pos, token) in alone[0].iter().enumerate() {
    for (a, b) in token.iter().zip(&batched[1][pos]) {
      assert!((a - b).abs() < 1e-5, "position {pos}: {a} vs {b}");
    }
  }
}