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

#[test]
fn keeps_cls_row_true_keeps_the_classification_row() {
  let weight = Tensor::eye(2, DType::F32, &Device::Cpu).unwrap();
  let bias = Tensor::zeros(2, DType::F32, &Device::Cpu).unwrap();
  let model = head(weight, bias, 2, true);
  let hidden = Tensor::new(
    &[[[1f32, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, -1.0]]],
    &Device::Cpu,
  )
  .unwrap();
  let rows = model.project_rows(&hidden, &[4]).unwrap();

  assert_eq!(
    rows[0].row_count(),
    4,
    "keeps_cls_row true must keep all 4 positions, CLS included"
  );
  let root2 = 2f32.sqrt();
  // CLS (kept), A, B and SEP.
  let expected = [
    vec![1.0f32, 0.0],
    vec![0.0, 1.0],
    vec![1.0 / root2, 1.0 / root2],
    vec![1.0 / root2, -1.0 / root2],
  ];
  for (got, want) in rows[0].rows().zip(expected.iter()) {
    for (g, w) in got.iter().zip(want) {
      assert!(
        (g - w).abs() < 1e-6,
        "{:?} vs {expected:?}",
        rows[0].rows().collect::<Vec<_>>()
      );
    }
  }
}

#[test]
fn normalize_false_and_no_bias_leave_the_raw_projection_unchanged() {
  let weight = Tensor::new(&[[3f32, 0.0], [0.0, 4.0]], &Device::Cpu).unwrap();
  let model = M3Colbert {
    encoder: dummy_encoder(2),
    weight,
    bias: None,
    dim: 2,
    normalize: false,
    keeps_cls_row: true,
  };
  let hidden = Tensor::new(&[[[1f32, 1.0]]], &Device::Cpu).unwrap();
  let rows = model.project_rows(&hidden, &[1]).unwrap();
  let row: Vec<f32> = rows[0].rows().next().unwrap().to_vec();
  assert!(
    (row[0] - 3.0).abs() < 1e-6 && (row[1] - 4.0).abs() < 1e-6,
    "with normalize:false and no bias, [1,1] through diag(3,4) must stay [3,4]: {row:?}"
  );
}