use candle_core::DType;
use candle_core::Module;
use candle_core::Tensor;
use candle_nn::Linear;
use candle_nn::VarBuilder;
use candle_nn::linear;
use crate::error::Result;
use crate::layers::activation::Activation;
use crate::layers::layer_norm::LayerNorm;
use crate::models::Encoder;
use crate::models::EncoderInput;
use crate::models::SparseModel;
use crate::models::bert::BertConfig;
use crate::models::bert::BertModel;
use crate::sparse::SparseEmbedding;
const CHUNK: usize = 64;
pub struct SpladeSparse {
bert: BertModel,
transform: Linear,
transform_norm: LayerNorm,
decoder_weight: Tensor,
decoder_bias: Tensor,
vocab_size: usize,
chunk: usize,
}
impl SpladeSparse {
pub fn load(config: BertConfig, vb: VarBuilder) -> Result<Self> {
let hidden_size = config.hidden_size;
let vocab_size = config.vocab_size;
let eps = config.layer_norm_eps;
let bert = BertModel::load(config, vb.pp("bert"))?;
let transform_vb = vb.pp("cls").pp("predictions").pp("transform");
let transform = linear(hidden_size, hidden_size, transform_vb.pp("dense"))?;
let transform_norm = LayerNorm::load(transform_vb.pp("LayerNorm"), hidden_size, eps)?;
let decoder_weight = bert.embed_tokens().clone();
let decoder_bias = vb.pp("cls").pp("predictions").get(vocab_size, "bias")?;
Ok(Self {
bert,
transform,
transform_norm,
decoder_weight,
decoder_bias,
vocab_size,
chunk: CHUNK,
})
}
fn chunk_logits(&self, h_chunk: &Tensor) -> Result<Tensor> {
let t = self.transform.forward(h_chunk)?;
let t = Activation::Gelu.forward(&t)?;
let t = self.transform_norm.forward(&t)?;
let decoder = Linear::new(self.decoder_weight.clone(), Some(self.decoder_bias.clone()));
Ok(decoder.forward(&t)?)
}
fn pool_logits(&self, hidden: &Tensor, attention_mask: &Tensor) -> Result<Vec<SparseEmbedding>> {
let (_, seq, _) = hidden.dims3()?;
let mut pooled: Option<Tensor> = None;
let mut start = 0;
while start < seq {
let len = self.chunk.min(seq - start);
let h_chunk = hidden.narrow(1, start, len)?;
let mask_chunk = attention_mask
.narrow(1, start, len)?
.to_dtype(hidden.dtype())?
.unsqueeze(2)?;
let masked = self.chunk_logits(&h_chunk)?.broadcast_mul(&mask_chunk)?;
let weight = masked.relu()?.affine(1.0, 1.0)?.log()?;
let chunk_max = weight.max(1)?;
pooled = Some(match pooled {
Some(prev) => prev.broadcast_maximum(&chunk_max)?,
None => chunk_max,
});
start += len;
}
let pooled =
pooled.expect("hidden carries at least one position: every EncoderInput has a CLS token");
let rows = pooled.to_dtype(DType::F32)?.to_vec2::<f32>()?;
Ok(
rows
.into_iter()
.map(|row| {
SparseEmbedding::from_pairs(row.into_iter().enumerate().map(|(i, v)| (i as u32, v)))
})
.collect(),
)
}
}
impl SparseModel for SpladeSparse {
fn forward(&self, input: &EncoderInput) -> Result<Vec<SparseEmbedding>> {
let hidden = self.bert.forward(input)?;
self.pool_logits(&hidden, input.attention_mask)
}
fn vocab_size(&self) -> usize {
self.vocab_size
}
fn max_position_embeddings(&self) -> usize {
self.bert.max_position_embeddings()
}
fn weights_dtype(&self) -> DType {
self.bert.weights_dtype()
}
}
#[cfg(test)]
mod tests;