use alloc::vec;
use alloc::vec::Vec;
use crate::error::TokenizerError;
use crate::tokenizer::{TokenId, Tokenizer};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Padding {
#[default]
None,
Fixed(usize),
Longest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Truncation {
#[default]
None,
Fixed(usize),
}
#[derive(Debug, Clone, Default)]
pub struct EncodeConfig {
bos: Option<TokenId>,
eos: Option<TokenId>,
pad: Option<TokenId>,
padding: Padding,
truncation: Truncation,
add_special_tokens: bool,
}
impl EncodeConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_bos(mut self, id: TokenId) -> Self {
self.bos = Some(id);
self.add_special_tokens = true;
self
}
#[must_use]
pub fn with_eos(mut self, id: TokenId) -> Self {
self.eos = Some(id);
self.add_special_tokens = true;
self
}
#[must_use]
pub fn with_pad(mut self, id: TokenId) -> Self {
self.pad = Some(id);
self
}
#[must_use]
pub fn add_special_tokens(mut self, yes: bool) -> Self {
self.add_special_tokens = yes;
self
}
#[must_use]
pub fn padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
#[must_use]
pub fn truncation(mut self, truncation: Truncation) -> Self {
self.truncation = truncation;
self
}
fn num_special(&self) -> usize {
if !self.add_special_tokens {
return 0;
}
usize::from(self.bos.is_some()) + usize::from(self.eos.is_some())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Encoding {
pub ids: Vec<TokenId>,
pub attention_mask: Vec<u32>,
}
impl Encoding {
#[must_use]
pub fn len(&self) -> usize {
self.ids.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
}
pub(crate) fn assemble(mut ids: Vec<TokenId>, cfg: &EncodeConfig) -> Encoding {
if let Truncation::Fixed(max) = cfg.truncation {
let room = max.saturating_sub(cfg.num_special());
if ids.len() > room {
ids.truncate(room);
}
}
let mut out = Vec::with_capacity(ids.len() + cfg.num_special());
if cfg.add_special_tokens {
if let Some(bos) = cfg.bos {
out.push(bos);
}
}
out.extend(ids);
if cfg.add_special_tokens {
if let Some(eos) = cfg.eos {
out.push(eos);
}
}
let attention_mask = vec![1u32; out.len()];
Encoding {
ids: out,
attention_mask,
}
}
pub(crate) fn pad_to(enc: &mut Encoding, target: usize, pad_id: TokenId) {
if enc.ids.len() >= target {
return;
}
let deficit = target - enc.ids.len();
enc.ids.extend(core::iter::repeat(pad_id).take(deficit));
enc.attention_mask
.extend(core::iter::repeat(0u32).take(deficit));
}
pub(crate) fn apply_padding(
encodings: &mut [Encoding],
cfg: &EncodeConfig,
) -> Result<(), TokenizerError> {
let target = match cfg.padding {
Padding::None => return Ok(()),
Padding::Fixed(n) => n,
Padding::Longest => encodings.iter().map(Encoding::len).max().unwrap_or(0),
};
let Some(pad_id) = cfg.pad else {
return Err(TokenizerError::MalformedFile(alloc::string::String::from(
"padding requested without a pad token id (call EncodeConfig::with_pad)",
)));
};
for enc in encodings.iter_mut() {
pad_to(enc, target, pad_id);
}
Ok(())
}
impl<T: Tokenizer + ?Sized> TokenizerExt for T {}
pub trait TokenizerExt: Tokenizer {
fn encode_with(&self, text: &str, cfg: &EncodeConfig) -> Result<Encoding, TokenizerError> {
let ids = self.encode(text)?;
let mut enc = assemble(ids, cfg);
if let Padding::Fixed(n) = cfg.padding {
let Some(pad_id) = cfg.pad else {
return Err(TokenizerError::MalformedFile(alloc::string::String::from(
"padding requested without a pad token id (call EncodeConfig::with_pad)",
)));
};
pad_to(&mut enc, n, pad_id);
}
Ok(enc)
}
fn encode_batch(
&self,
texts: &[&str],
cfg: &EncodeConfig,
) -> Result<Vec<Encoding>, TokenizerError> {
let mut encodings = Vec::with_capacity(texts.len());
for text in texts {
let ids = self.encode(text)?;
encodings.push(assemble(ids, cfg));
}
apply_padding(&mut encodings, cfg)?;
Ok(encodings)
}
}