use std::fs;
use candle_core::DType;
use candle_core::Device;
use super::InputTokenizer;
use super::TextCrossEncoder;
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::rerank::RerankDescriptor;
use crate::tokenizer::read_pad_token;
use crate::weights;
const DEFAULT_BATCH_SIZE: usize = 8;
pub struct TextCrossEncoderBuilder {
spec: String,
revision: String,
device: Option<Device>,
dtype: Option<DType>,
max_length: Option<usize>,
batch_size: usize,
descriptor: Option<RerankDescriptor>,
}
impl TextCrossEncoderBuilder {
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: RerankDescriptor) -> Self {
self.descriptor = Some(descriptor);
self
}
pub fn build(self) -> Result<TextCrossEncoder> {
let overridden = self.descriptor.is_some();
let descriptor = match self.descriptor {
Some(descriptor) => descriptor,
None => {
registry::rerank::lookup(&self.spec).ok_or_else(|| CanddingError::UnregisteredModel {
id: self.spec.clone(),
catalog: Catalog::Rerank,
})?
}
};
let spec = if overridden {
self.spec.as_str()
} else {
descriptor.id.as_str()
};
let device = match self.device {
Some(device) => device,
None => device::auto()?,
};
let dtype = resolve_dtype(&self.spec, self.dtype, descriptor.default_dtype, &device)?;
reject_unsupported_dtype(&self.spec, dtype, descriptor.dtype_backends, &device)?;
let source = ModelSource::parse(spec, &self.revision);
let (size_gb, stored) = (descriptor.size_gb, descriptor.default_dtype);
fit::check_download(&self.spec, &device, &source, size_gb, stored, dtype)?;
let dir = hub::resolve(&source, descriptor.weights)?;
let files = loader::discover(&dir, loader::Pipeline::Rerank)?;
fit::check_files(&self.spec, &device, &fit::at(&files.weights, dtype))?;
let config_json = fs::read_to_string(&files.config)?;
let architecture = build::architecture(&descriptor, &files.config)?;
tracing::info!(model = %self.spec, device = device::describe(&device), dtype = dtype.as_str(), "loading rerank model");
let vb = unsafe { weights::var_builder(&files.weights, dtype, &device)? };
let model = models::load_rerank(
&architecture,
&config_json,
vb,
build::conventions_from_descriptor(&descriptor),
)?;
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 =
InputTokenizer::from_file(&descriptor.input, &files.tokenizer, max_length, pad)?;
Ok(TextCrossEncoder::new(
model,
tokenizer,
descriptor,
device,
dtype,
self.batch_size,
))
}
}
#[cfg(test)]
mod tests;