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
//! Checkpoints builder tests write to a temp directory, so `build` runs end to end with nothing downloaded:
//! a zero-layer BERT over a tiny tokenizer fixture plus whatever tensors a head adds.

use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use candle_core::DType;
use candle_core::Device;
use candle_core::Tensor;
use tempfile::TempDir;
use tempfile::tempdir;

/// The hidden size of every synthetic BERT here.
pub(crate) const HIDDEN: usize = 4;

/// `config.json` of a zero-layer BERT of hidden size `HIDDEN` over `vocab` ids.
pub(crate) fn bert_config(vocab: usize) -> String {
  format!(
    r#"{{"model_type":"bert","vocab_size":{vocab},"hidden_size":{HIDDEN},"num_hidden_layers":0,
    "num_attention_heads":1,"intermediate_size":1,"max_position_embeddings":32,"type_vocab_size":2,
    "layer_norm_eps":1e-12,"pad_token_id":0}}"#
  )
}

pub(crate) fn zeros(shape: &[usize]) -> Tensor {
  Tensor::zeros(shape, DType::F32, &Device::Cpu).unwrap()
}

/// A zero-layer BERT's tensors over `vocab` ids, every one zero, each name under `prefix` (`""` or `"bert."`).
pub(crate) fn zero_bert(prefix: &str, vocab: usize) -> HashMap<String, Tensor> {
  [
    ("embeddings.word_embeddings.weight", vec![vocab, HIDDEN]),
    ("embeddings.position_embeddings.weight", vec![32, HIDDEN]),
    ("embeddings.token_type_embeddings.weight", vec![2, HIDDEN]),
    ("embeddings.LayerNorm.weight", vec![HIDDEN]),
    ("embeddings.LayerNorm.bias", vec![HIDDEN]),
  ]
  .into_iter()
  .map(|(name, shape)| (format!("{prefix}{name}"), zeros(&shape)))
  .collect()
}

/// `config`, a copy of the tokenizer fixture named `tokenizer` and `tensors` as `model.safetensors`, in a fresh
/// temporary directory.
pub(crate) fn checkpoint(
  config: &str,
  tokenizer: &str,
  tensors: &HashMap<String, Tensor>,
) -> TempDir {
  let dir = tempdir().unwrap();
  fs::write(dir.path().join("config.json"), config).unwrap();
  let fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
    .join("tests/fixtures")
    .join(tokenizer);
  fs::copy(fixture, dir.path().join("tokenizer.json")).unwrap();
  candle_core::safetensors::save(tensors, dir.path().join("model.safetensors")).unwrap();
  dir
}