use candle_core::Device;
use tokenizers::Encoding;
use tokenizers::PostProcessor;
use tokenizers::Tokenizer;
use tokenizers::TruncationDirection;
use tokenizers::pad_encodings;
use super::EncodedBatch;
use super::TextTokenizer;
use super::added_tokens;
use super::encodings_to_batch;
use super::real_tokens;
use super::tokenizer_error;
use super::too_small;
use crate::error::CanddingError;
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum PairTruncationPolicy {
LongestFirst,
PreserveQueryToThreeQuarters,
}
impl TextTokenizer {
pub fn require_pair_room(&self) -> Result<()> {
let added = added_tokens(&self.inner, true);
if self.max_length <= added {
return Err(too_small(self.max_length, added, true));
}
Ok(())
}
pub fn encode_pairs(
&self,
pairs: &[(&str, &str)],
policy: PairTruncationPolicy,
device: &Device,
) -> Result<EncodedBatch> {
self.require_pair_room()?;
let mut encodings = pairs
.iter()
.map(|&(query, document)| encode_pair(&self.inner, self.max_length, query, document, policy))
.collect::<Result<Vec<_>>>()?;
pad_encodings(&mut encodings, &self.padding).map_err(tokenizer_error)?;
encodings_to_batch(&encodings, device)
}
pub fn count_pairs(
&self,
pairs: &[(&str, &str)],
policy: PairTruncationPolicy,
) -> Result<Vec<usize>> {
self.require_pair_room()?;
pairs
.iter()
.map(|&(query, document)| {
encode_pair(&self.inner, self.max_length, query, document, policy)
.map(|encoding| real_tokens(encoding.get_attention_mask()))
})
.collect()
}
}
pub(super) fn encode_pair(
tokenizer: &Tokenizer,
max_length: usize,
query: &str,
document: &str,
policy: PairTruncationPolicy,
) -> Result<Encoding> {
match policy {
PairTruncationPolicy::LongestFirst => tokenizer
.encode((query, document), true)
.map_err(tokenizer_error),
PairTruncationPolicy::PreserveQueryToThreeQuarters => {
encode_pair_preserving_query(tokenizer, max_length, query, document)
}
}
}
fn encode_pair_preserving_query(
tokenizer: &Tokenizer,
max_length: usize,
query: &str,
document: &str,
) -> Result<Encoding> {
let mut query_encoding = tokenizer.encode(query, false).map_err(tokenizer_error)?;
query_encoding.truncate((max_length * 3) / 4, 0, TruncationDirection::Right);
let mut document_encoding = tokenizer.encode(document, false).map_err(tokenizer_error)?;
let budget = max_length.saturating_sub(added_tokens(tokenizer, true));
document_encoding.truncate(
budget.saturating_sub(query_encoding.len()),
0,
TruncationDirection::Right,
);
with_special_tokens(tokenizer, query_encoding, document_encoding)
}
pub(super) fn with_special_tokens(
tokenizer: &Tokenizer,
query: Encoding,
document: Encoding,
) -> Result<Encoding> {
match tokenizer.get_post_processor() {
Some(processor) => processor
.process(query, Some(document), true)
.map_err(tokenizer_error),
None => <dyn PostProcessor>::default_process(vec![query, document], true)
.map_err(tokenizer_error)?
.pop()
.ok_or_else(|| {
CanddingError::Tokenizer("the default post-processor merged no encoding".to_string())
}),
}
}