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
//! The sparse catalog: `all`, `lookup` and `by_phase` over its entries, each a `SparseDescriptor`.

mod bge_m3;
mod bm25;
mod bm42;
mod entry;
mod minicoil;
mod splade_code;
mod splade_pp;

use bge_m3::BGE_M3_SPARSE;
use bm25::BM25;
use bm42::BM42;
use candle_core::DType;
pub use entry::SparseComputation;
pub use entry::SparseDescriptor;
pub(super) use entry::SparseEntry;
use minicoil::MINICOIL_V1;
use splade_code::NAVER_SPLADE_CODE_06B;
use splade_pp::SPLADE_PP_EN_V1;

use crate::registry::WeightsFormat;

/// Entries land already proven by a golden run, except `BM25`, which has no device or dtype axis and is pinned by
/// unit tests; `registry::tests::verified` fails for an entry with no `Verified` cell.
pub(super) const ENTRIES: &[SparseEntry] = &[
  SPLADE_PP_EN_V1,
  BGE_M3_SPARSE,
  NAVER_SPLADE_CODE_06B,
  MINICOIL_V1,
  BM42,
  BM25,
];

/// Every registered sparse entry, in registration order.
pub fn all() -> Vec<SparseDescriptor> {
  ENTRIES.iter().map(SparseEntry::descriptor).collect()
}

fn entry_for(id: &str) -> Option<&'static SparseEntry> {
  ENTRIES
    .iter()
    .find(|entry| entry.id.eq_ignore_ascii_case(id))
}

/// This registry's own spelling of an id it carries, for `registry::canonical_id`.
pub(crate) fn canonical_id(id: &str) -> Option<&'static str> {
  entry_for(id).map(|entry| entry.id)
}

/// Case-insensitive lookup by repository id.
pub fn lookup(id: &str) -> Option<SparseDescriptor> {
  entry_for(id).map(SparseEntry::descriptor)
}

/// Whether a registered sparse id computes `dtype` correctly only on an accelerator, read from the row its builder
/// refuses from; `false` for an id outside the sparse catalog.
pub fn dtype_needs_accelerator(id: &str, dtype: DType) -> bool {
  entry_for(id).is_some_and(|entry| {
    entry
      .descriptor()
      .dtype_backends
      .row(dtype)
      .needs_accelerator()
  })
}

/// Every registered sparse entry for a phase, in registration order.
pub fn by_phase(phase: u8) -> Vec<SparseDescriptor> {
  ENTRIES
    .iter()
    .filter(|entry| entry.phase == phase)
    .map(SparseEntry::descriptor)
    .collect()
}

/// The weight format of a sparse head's second repository, such as `Qdrant/minicoil-v1`, keyed by its id.
pub fn head_source_format(id: &str) -> Option<WeightsFormat> {
  ENTRIES
    .iter()
    .find(|entry| {
      entry
        .head_source
        .is_some_and(|source| source.eq_ignore_ascii_case(id))
    })
    .map(|_| WeightsFormat::Npy)
}

/// The commit a sparse head's second repository is pinned to, keyed by that repository's id.
pub fn head_source_revision(id: &str) -> Option<&'static str> {
  ENTRIES
    .iter()
    .find(|entry| {
      entry
        .head_source
        .is_some_and(|source| source.eq_ignore_ascii_case(id))
    })
    .and_then(|entry| entry.head_revision)
}