use std::collections::HashSet;
use candle_core::DType;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::Module;
use candle_nn::VarBuilder;
use crate::error::CanddingError;
use crate::error::Result;
use crate::models::Encoder;
use crate::models::EncoderInput;
use crate::models::MultiVectorConventions;
use crate::models::MultiVectorModel;
use crate::models::bert::BertConfig;
use crate::models::bert::BertModel;
use crate::models::multi_vector::l2_normalize_rows;
use crate::multi_vector::MultiVectorEmbedding;
pub struct StanfordColbert {
encoder: BertModel,
weight: Tensor,
bias: Option<Tensor>,
punctuation_ids: HashSet<u32>,
dim: usize,
normalize: bool,
keeps_cls_row: bool,
}
impl StanfordColbert {
pub fn load(
config: BertConfig,
vb: VarBuilder,
punctuation_ids: HashSet<u32>,
conventions: MultiVectorConventions,
) -> Result<Self> {
if conventions.projection_in != config.hidden_size {
return Err(CanddingError::ProjectionInMismatch {
declared: conventions.projection_in,
actual: config.hidden_size,
});
}
let weight = vb.get(
(conventions.projection_out, config.hidden_size),
"linear.weight",
)?;
let bias = if conventions.projection_bias {
Some(vb.get(conventions.projection_out, "linear.bias")?)
} else {
None
};
let encoder = BertModel::load(config, vb)?;
Ok(Self {
encoder,
weight,
bias,
punctuation_ids,
dim: conventions.projection_out,
normalize: conventions.normalize,
keeps_cls_row: conventions.keeps_cls_row,
})
}
fn project(&self, hidden: &Tensor) -> Result<Vec<Vec<Vec<f32>>>> {
let linear = Linear::new(self.weight.clone(), self.bias.clone());
let projected_raw = linear.forward(hidden)?;
let projected = if self.normalize {
l2_normalize_rows(&projected_raw)?
} else {
projected_raw
};
Ok(projected.to_dtype(DType::F32)?.to_vec3::<f32>()?)
}
fn select_document_rows(
&self,
hidden: &Tensor,
ids: &[Vec<u32>],
lengths: &[usize],
) -> Result<Vec<MultiVectorEmbedding>> {
let batch = self.project(hidden)?;
Ok(
batch
.into_iter()
.zip(ids.iter().cloned())
.zip(lengths)
.map(|((positions, row_ids), &real_len)| {
let kept = positions
.into_iter()
.zip(row_ids)
.enumerate()
.filter(|(position, (_, id))| {
*position < real_len
&& !self.punctuation_ids.contains(id)
&& (self.keeps_cls_row || *position != 0)
})
.map(|(_, (row, _))| row);
MultiVectorEmbedding::from_rows(self.dim, kept)
})
.collect(),
)
}
fn select_query_rows(&self, hidden: &Tensor) -> Result<Vec<MultiVectorEmbedding>> {
let batch = self.project(hidden)?;
Ok(
batch
.into_iter()
.map(|positions| MultiVectorEmbedding::from_rows(self.dim, positions))
.collect(),
)
}
}
impl MultiVectorModel for StanfordColbert {
fn forward(&self, input: &EncoderInput) -> Result<Vec<MultiVectorEmbedding>> {
let hidden = self.encoder.forward(input)?;
let ids = input.input_ids.to_vec2::<u32>()?;
self.select_document_rows(&hidden, &ids, input.lengths)
}
fn forward_query(&self, input: &EncoderInput) -> Result<Vec<MultiVectorEmbedding>> {
let hidden = self.encoder.forward(input)?;
self.select_query_rows(&hidden)
}
fn dim(&self) -> usize {
self.dim
}
fn max_position_embeddings(&self) -> usize {
self.encoder.max_position_embeddings()
}
fn weights_dtype(&self) -> DType {
self.encoder.weights_dtype()
}
}
#[cfg(test)]
mod tests;