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

/// The specials are filtered before either index path, leaving `doc_len == 2`; "cat" takes the dense path, "xyzzy" the hashed.
/// A dense index always sits below the hashed range's shift of 128 000, so the dense entry sorts first.
#[test]
fn a_row_mixing_recognized_and_unrecognized_words_uses_both_index_paths() {
  let vocab = vocab(&["cat"]);
  let stopwords = stopwords(&[]);
  let tokens = token_text(&["[CLS]", "cat", "xyzzy", "[SEP]"]);
  let matrix = Tensor::from_vec(
    vec![
      0f32, 0., 0., 0., 0., 0., 0., 0., 1.0, 0., 0., 0., 0., 0., 0., 0.,
    ],
    (2, 2, 4),
    &Device::Cpu,
  )
  .unwrap();
  let hidden_row = vec![
    vec![0.0f32, 0.0],
    vec![1.0, 0.0],
    vec![0.0, 0.0],
    vec![0.0, 0.0],
  ];

  let embedding = pooling::pool_row(
    &hidden_row,
    &[0, 1, 2, 3],
    &tokens,
    &vocab,
    &stopwords,
    &matrix,
    false,
  )
  .unwrap();

  let tf = 1.0 * (1.2 + 1.0) / (1.0 + 1.2 * (1.0 - 0.75 + 0.75 * 2.0 / 150.0));
  let dense = index::dense_index(1, 0);
  let hashed = index::hashed_index("xyzzy");

  assert!(dense < 128_000, "{dense}");
  assert!(hashed >= 128_000, "{hashed}");
  assert_eq!(embedding.indices, vec![dense, hashed]);
  for &got in &embedding.values {
    assert!((got - tf).abs() < 1e-5, "{got} vs {tf}");
  }
}

#[test]
fn a_row_of_only_unrecognized_words_produces_only_hashed_entries() {
  let vocab = vocab(&["cat"]);
  let stopwords = stopwords(&[]);
  let tokens = token_text(&["foo", "bar"]);
  let matrix = Tensor::from_vec(vec![0f32; 16], (2, 2, 4), &Device::Cpu).unwrap();
  let hidden_row = vec![vec![0.0f32, 0.0], vec![0.0, 0.0]];

  let embedding = pooling::pool_row(
    &hidden_row,
    &[0, 1],
    &tokens,
    &vocab,
    &stopwords,
    &matrix,
    false,
  )
  .unwrap();

  let tf = 1.0 * (1.2 + 1.0) / (1.0 + 1.2 * (1.0 - 0.75 + 0.75 * 2.0 / 150.0));
  let mut expected = [index::hashed_index("foo"), index::hashed_index("bar")];
  expected.sort_unstable();

  assert_eq!(embedding.indices, expected);
  assert!(
    embedding.indices.iter().all(|&i| i >= 128_000),
    "{:?}",
    embedding.indices
  );
  for &got in &embedding.values {
    assert!((got - tf).abs() < 1e-5, "{got} vs {tf}");
  }
}

/// Counted toward `doc_len`, a bare `.` would reweight every word, so "cat"'s weight is compared with a control row.
#[test]
fn a_bare_punctuation_token_never_moves_a_recognized_words_own_weight() {
  let vocab = vocab(&["cat"]);
  let stopwords = stopwords(&[]);
  let matrix = Tensor::from_vec(
    vec![
      0f32, 0., 0., 0., 0., 0., 0., 0., 1.0, 0., 0., 0., 0., 0., 0., 0.,
    ],
    (2, 2, 4),
    &Device::Cpu,
  )
  .unwrap();

  let control_tokens = token_text(&["[CLS]", "cat", "[SEP]"]);
  let control_hidden = vec![vec![0.0f32, 0.0], vec![1.0, 0.0], vec![0.0, 0.0]];
  let control = pooling::pool_row(
    &control_hidden,
    &[0, 1, 2],
    &control_tokens,
    &vocab,
    &stopwords,
    &matrix,
    false,
  )
  .unwrap();

  let punctuated_tokens = token_text(&["[CLS]", "cat", ".", "[SEP]"]);
  let punctuated_hidden = vec![
    vec![0.0f32, 0.0],
    vec![1.0, 0.0],
    vec![0.0, 0.0],
    vec![0.0, 0.0],
  ];
  let punctuated = pooling::pool_row(
    &punctuated_hidden,
    &[0, 1, 2, 3],
    &punctuated_tokens,
    &vocab,
    &stopwords,
    &matrix,
    false,
  )
  .unwrap();

  assert_eq!(
    control.indices,
    vec![index::dense_index(1, 0)],
    "control row (no punctuation)"
  );
  // "cat"'s dense index (4) sorts below every hashed index, so `values[0]` is its weight in both rows.
  assert_eq!(
    punctuated.values[0], control.values[0],
    "\"cat\"'s own weight must not move just because the row also contains a bare \".\""
  );
  assert_eq!(
    punctuated.indices, control.indices,
    "a correctly filtered punctuation token must add no index of its own"
  );

  let tf = 1.0 * (1.2 + 1.0) / (1.0 + 1.2 * (1.0 - 0.75 + 0.75 * 1.0 / 150.0));
  assert!(
    (control.values[0] - tf).abs() < 1e-5,
    "{} vs {tf}",
    control.values[0]
  );
}

#[test]
fn an_empty_row_produces_an_empty_embedding() {
  let vocab = vocab(&["cat"]);
  let stopwords = stopwords(&[]);
  let tokens = token_text(&["cat"]);
  let matrix = Tensor::from_vec(vec![0f32; 16], (2, 2, 4), &Device::Cpu).unwrap();
  let hidden_row: Vec<Vec<f32>> = vec![];

  let embedding = pooling::pool_row(
    &hidden_row,
    &[],
    &tokens,
    &vocab,
    &stopwords,
    &matrix,
    false,
  )
  .unwrap();

  assert!(embedding.is_empty());
}