use std::fs;
use candle_core::DType;
use candle_core::Device;
use super::SparseTextEmbedding;
use super::build;
use crate::device;
use crate::embed::builder::reject_unsupported_dtype;
use crate::embed::builder::resolve_dtype;
use crate::embed::fit;
use crate::error::CanddingError;
use crate::error::Result;
use crate::hub;
use crate::hub::ModelSource;
use crate::loader;
use crate::models;
use crate::registry;
use crate::registry::Catalog;
use crate::registry::sparse::SparseDescriptor;
use crate::tokenizer::TextTokenizer;
use crate::tokenizer::read_pad_token;
use crate::weights;
const DEFAULT_BATCH_SIZE: usize = 8;
pub struct SparseTextEmbeddingBuilder {
spec: String,
revision: String,
device: Option<Device>,
dtype: Option<DType>,
max_length: Option<usize>,
batch_size: usize,
descriptor: Option<SparseDescriptor>,
}
impl SparseTextEmbeddingBuilder {
pub(crate) fn new(spec: impl Into<String>) -> Self {
Self {
spec: spec.into(),
revision: "main".to_string(),
device: None,
dtype: None,
max_length: None,
batch_size: DEFAULT_BATCH_SIZE,
descriptor: None,
}
}
pub fn device(mut self, device: Device) -> Self {
self.device = Some(device);
self
}
pub fn dtype(mut self, dtype: DType) -> Self {
self.dtype = Some(dtype);
self
}
pub fn max_length(mut self, max_length: usize) -> Self {
self.max_length = Some(max_length);
self
}
pub fn batch_size(mut self, batch_size: usize) -> Self {
self.batch_size = batch_size.max(1);
self
}
pub fn revision(mut self, revision: impl Into<String>) -> Self {
self.revision = revision.into();
self
}
pub fn descriptor(mut self, descriptor: SparseDescriptor) -> Self {
self.descriptor = Some(descriptor);
self
}
pub fn build(self) -> Result<SparseTextEmbedding> {
let overridden = self.descriptor.is_some();
let descriptor = match self.descriptor {
Some(descriptor) => descriptor,
None => {
registry::sparse::lookup(&self.spec).ok_or_else(|| CanddingError::UnregisteredModel {
id: self.spec.clone(),
catalog: Catalog::Sparse,
})?
}
};
let spec = if overridden {
self.spec.as_str()
} else {
descriptor.id.as_str()
};
build::reject_computed_entry(&descriptor)?;
let device = match self.device {
Some(device) => device,
None => device::auto()?,
};
let dtype = resolve_dtype(&self.spec, self.dtype, DType::F32, &device)?;
reject_unsupported_dtype(&self.spec, dtype, descriptor.dtype_backends, &device)?;
let source = ModelSource::parse(spec, &self.revision);
let size_gb = descriptor.size_gb;
fit::check_download(&self.spec, &device, &source, size_gb, DType::F32, dtype)?;
let dir = hub::resolve(&source, descriptor.weights)?;
let files = loader::discover(&dir, loader::Pipeline::Sparse)?;
let config_json = fs::read_to_string(&files.config)?;
let minicoil_side = build::resolve_minicoil_side(&descriptor)?;
let weight_files = build::weight_files(&descriptor, &files, minicoil_side.as_ref(), dtype);
fit::check_files(&self.spec, &device, &weight_files)?;
tracing::info!(model = %self.spec, device = device::describe(&device), dtype = dtype.as_str(), "loading sparse model");
let vb = unsafe { weights::var_builder(&files.weights, dtype, &device)? };
let minicoil_files = minicoil_side.as_ref().map(|side| models::MiniCoilFiles {
matrix: &side.matrix,
vocab: &side.vocab,
stopwords: &side.stopwords,
});
let model = models::load_sparse(
descriptor.head,
&config_json,
vb,
&files.tokenizer,
files.sparse_linear.as_deref(),
minicoil_files,
)?;
let max_length = self
.max_length
.unwrap_or(descriptor.default_max_length)
.min(model.max_position_embeddings());
let pad = read_pad_token(&config_json, files.tokenizer_config.as_deref())?;
let tokenizer = TextTokenizer::from_file(&files.tokenizer, max_length, pad)?;
Ok(SparseTextEmbedding::new(
model,
tokenizer,
descriptor,
device,
dtype,
self.batch_size,
))
}
}
#[cfg(test)]
mod tests;