mod bert;
mod catalog;
pub mod dense;
mod entry;
mod gemma3;
mod gte_new;
mod jina_bert;
mod modernbert;
mod mpnet;
pub mod multi_vector;
mod nomic_bert;
mod qwen3;
pub mod rerank;
pub mod sparse;
mod support;
mod t4;
mod xlm_roberta;
use candle_core::DType;
pub use catalog::Catalog;
pub(crate) use catalog::other_catalog;
use entry::Entry;
use serde::Deserialize;
use serde::Serialize;
pub use support::Backend;
pub use support::BackendSupport;
pub(crate) use support::CellException;
pub use support::DtypeBackendSupport;
pub(crate) use support::DtypeSupport;
pub use support::Support;
use support::dtype_serde;
use crate::pooling::Pooling;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum Family {
Bert,
Gemma3,
GteNew,
JinaBert,
#[serde(rename = "modernbert")]
ModernBert,
Mpnet,
NomicBert,
Qwen3,
XlmRoberta,
XlmRobertaFlash,
}
impl Family {
pub fn from_model_type(model_type: &str) -> Option<Family> {
match model_type {
"bert" => Some(Family::Bert),
"gemma3_text" => Some(Family::Gemma3),
"jina_bert" => Some(Family::JinaBert),
"modernbert" => Some(Family::ModernBert),
"mpnet" => Some(Family::Mpnet),
"new" => Some(Family::GteNew),
"nomic_bert" => Some(Family::NomicBert),
"qwen3" => Some(Family::Qwen3),
"xlm-roberta" => Some(Family::XlmRoberta),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum WeightsFormat {
Safetensors,
Pickle,
Npy,
NoWeights,
}
pub fn weights_format(id: &str) -> WeightsFormat {
registered_weights_format(id).unwrap_or(WeightsFormat::Safetensors)
}
fn registered_weights_format(id: &str) -> Option<WeightsFormat> {
entry_for(id)
.map(|entry| entry.weights)
.or_else(|| sparse::lookup(id).map(|d| d.weights))
.or_else(|| multi_vector::lookup(id).map(|d| d.weights))
.or_else(|| rerank::lookup(id).map(|d| d.weights))
.or_else(|| sparse::head_source_format(id))
}
pub fn canonical_id(id: &str) -> Option<&'static str> {
entry_for(id)
.map(|entry| entry.id)
.or_else(|| sparse::canonical_id(id))
.or_else(|| multi_vector::canonical_id(id))
.or_else(|| rerank::canonical_id(id))
}
fn entry_for(id: &str) -> Option<&'static Entry> {
entries().find(|entry| entry.id.eq_ignore_ascii_case(id))
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ModelDescriptor {
pub id: String,
pub family: Family,
pub dim: usize,
pub default_max_length: usize,
pub pooling: Pooling,
pub normalize: bool,
pub query_template: Option<String>,
pub passage_template: Option<String>,
pub default_instruction: Option<String>,
pub dense_dirs: Vec<String>,
pub mrl: bool,
pub layer_norm_after_pooling: bool,
pub license: String,
pub size_gb: f32,
pub gated: bool,
pub phase: u8,
pub backends: BackendSupport,
#[serde(with = "dtype_serde")]
pub default_dtype: DType,
pub dtype_backends: DtypeBackendSupport,
}
impl ModelDescriptor {
pub fn new(id: impl Into<String>, family: Family, dim: usize, pooling: Pooling) -> Self {
Self {
id: id.into(),
family,
dim,
default_max_length: 512,
pooling,
normalize: true,
query_template: None,
passage_template: None,
default_instruction: None,
dense_dirs: Vec::new(),
mrl: false,
layer_norm_after_pooling: false,
license: "unknown".to_string(),
size_gb: 0.0,
gated: false,
phase: u8::MAX,
backends: BackendSupport::untested(),
default_dtype: DType::F32,
dtype_backends: DtypeBackendSupport::untested(),
}
}
}
fn entries() -> impl Iterator<Item = &'static Entry> {
bert::ENTRIES
.iter()
.chain(qwen3::ENTRIES.iter())
.chain(gemma3::ENTRIES.iter())
.chain(xlm_roberta::ENTRIES.iter())
.chain(mpnet::ENTRIES.iter())
.chain(jina_bert::ENTRIES.iter())
.chain(nomic_bert::ENTRIES.iter())
.chain(modernbert::ENTRIES.iter())
.chain(gte_new::ENTRIES.iter())
}
#[cfg(test)]
mod tests;