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::Device;
use candle_nn::VarBuilder;
use candle_nn::VarMap;

use super::*;

/// Never parsed, since the `None` short-circuit returns before `config_json` is read; real shape in case the order changes.
const MINIMAL_XLM_ROBERTA: &str = r#"{"vocab_size":8,"hidden_size":4,"num_hidden_layers":1,"num_attention_heads":1,"intermediate_size":4,"max_position_embeddings":8}"#;

#[test]
fn load_sparse_refuses_a_resolved_head_with_no_sparse_linear_path() {
  let varmap = VarMap::new();
  let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
  // `Box<dyn SparseModel>` is not `Debug`, so this matches rather than calling `unwrap_err`.
  match load_sparse(
    SparseHead::M3Linear,
    MINIMAL_XLM_ROBERTA,
    vb,
    Path::new("unused"),
    None,
    None,
  ) {
    Err(CanddingError::MissingFile(path)) => {
      assert_eq!(path, PathBuf::from("sparse_linear.pt"));
    }
    Ok(_) => panic!("a resolved head with no sparse_linear path must be refused"),
    Err(other) => panic!("unexpected error: {other}"),
  }
}

/// No in-crate caller reaches the `Bm25` arm, but `load_sparse` is `pub`.
#[test]
fn load_sparse_refuses_bm25_by_name_with_no_encoder_to_build() {
  let varmap = VarMap::new();
  let vb = VarBuilder::from_varmap(&varmap, DType::F32, &Device::Cpu);
  match load_sparse(SparseHead::Bm25, "", vb, Path::new("unused"), None, None) {
    Err(CanddingError::ComputedSparseModel { hint }) => {
      assert_eq!(hint, "Bm25TextEmbedding::builder()");
    }
    Ok(_) => panic!("SparseHead::Bm25 must be refused, not built"),
    Err(other) => panic!("unexpected error: {other}"),
  }
}