mod decode_stream;
#[cfg(feature = "hub")]
mod hub;
mod serialization;
pub use decode_stream::DecodeStream;
#[cfg(feature = "hub")]
#[cfg_attr(docsrs, doc(cfg(feature = "hub")))]
pub use hub::FromPretrainedParameters;
use std::borrow::Cow;
use std::collections::HashMap;
use std::io::{BufRead, Write};
use std::path::Path;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::added_vocabulary::{AddedToken, AddedVocabulary};
use crate::decoders::DecoderWrapper;
use crate::encoding::{Encoding, PaddingDirection, TruncationDirection};
use crate::error::{Error, Result};
use crate::models::ModelWrapper;
use crate::normalized_string::NormalizedString;
use crate::normalizers::NormalizerWrapper;
use crate::pre_tokenized_string::{OffsetType, PreTokenizedString};
use crate::pre_tokenizers::PreTokenizerWrapper;
use crate::processors::PostProcessorWrapper;
use crate::trainers::TrainerWrapper;
use crate::traits::{Decoder, Model, Normalizer, PostProcessor, PreTokenizer, Trainer};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum InputSequence<'s> {
Raw(Cow<'s, str>),
PreTokenized(Vec<Cow<'s, str>>),
}
impl<'s> From<&'s str> for InputSequence<'s> {
fn from(s: &'s str) -> Self {
InputSequence::Raw(Cow::Borrowed(s))
}
}
impl<'s> From<&'s String> for InputSequence<'s> {
fn from(s: &'s String) -> Self {
InputSequence::Raw(Cow::Borrowed(s.as_str()))
}
}
impl From<String> for InputSequence<'_> {
fn from(s: String) -> Self {
InputSequence::Raw(Cow::Owned(s))
}
}
impl<'s> From<&'s [&'s str]> for InputSequence<'s> {
fn from(words: &'s [&'s str]) -> Self {
InputSequence::PreTokenized(words.iter().map(|w| Cow::Borrowed(*w)).collect())
}
}
impl<'s> From<Vec<&'s str>> for InputSequence<'s> {
fn from(words: Vec<&'s str>) -> Self {
InputSequence::PreTokenized(words.into_iter().map(Cow::Borrowed).collect())
}
}
impl From<Vec<String>> for InputSequence<'_> {
fn from(words: Vec<String>) -> Self {
InputSequence::PreTokenized(words.into_iter().map(Cow::Owned).collect())
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum EncodeInput<'s> {
Single(InputSequence<'s>),
Dual(InputSequence<'s>, InputSequence<'s>),
}
impl<'s, I: Into<InputSequence<'s>>> From<I> for EncodeInput<'s> {
fn from(input: I) -> Self {
EncodeInput::Single(input.into())
}
}
impl<'s, I1, I2> From<(I1, I2)> for EncodeInput<'s>
where
I1: Into<InputSequence<'s>>,
I2: Into<InputSequence<'s>>,
{
fn from((a, b): (I1, I2)) -> Self {
EncodeInput::Dual(a.into(), b.into())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TruncationStrategy {
#[default]
LongestFirst,
OnlyFirst,
OnlySecond,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TruncationParams {
#[serde(default)]
pub direction: TruncationDirection,
pub max_length: usize,
pub strategy: TruncationStrategy,
pub stride: usize,
}
impl Default for TruncationParams {
fn default() -> Self {
Self {
direction: TruncationDirection::Right,
max_length: 512,
strategy: TruncationStrategy::LongestFirst,
stride: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PaddingStrategy {
#[default]
BatchLongest,
Fixed(usize),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaddingParams {
pub strategy: PaddingStrategy,
pub direction: PaddingDirection,
pub pad_to_multiple_of: Option<usize>,
pub pad_id: u32,
pub pad_type_id: u32,
pub pad_token: String,
}
impl Default for PaddingParams {
fn default() -> Self {
Self {
strategy: PaddingStrategy::BatchLongest,
direction: PaddingDirection::Right,
pad_to_multiple_of: None,
pad_id: 0,
pad_type_id: 0,
pad_token: "[PAD]".to_owned(),
}
}
}
pub fn truncate_encodings(
mut encoding: Encoding,
mut pair: Option<Encoding>,
params: &TruncationParams,
) -> Result<(Encoding, Option<Encoding>)> {
if params.max_length == 0 {
encoding.truncate(0, params.stride, params.direction);
if let Some(p) = pair.as_mut() {
p.truncate(0, params.stride, params.direction);
}
return Ok((encoding, pair));
}
let total = encoding.len() + pair.as_ref().map_or(0, Encoding::len);
if total <= params.max_length {
return Ok((encoding, pair));
}
let to_remove = total - params.max_length;
let check_stride = |len: usize| {
if len > 0 && params.stride >= len {
Err(Error::Truncation(format!(
"stride ({}) must be less than the truncated length ({len})",
params.stride
)))
} else {
Ok(())
}
};
match params.strategy {
TruncationStrategy::LongestFirst => match pair.as_mut() {
Some(p) => {
let (mut n1, mut n2) = (encoding.len(), p.len());
let swap = n1 > n2;
if swap {
std::mem::swap(&mut n1, &mut n2);
}
n2 = if n1 > params.max_length {
n1
} else {
n1.max(params.max_length - n1)
};
if n1 + n2 > params.max_length {
n1 = params.max_length / 2;
n2 = n1 + params.max_length % 2;
}
if swap {
std::mem::swap(&mut n1, &mut n2);
}
if n1 < encoding.len() {
check_stride(n1)?;
}
if n2 < p.len() {
check_stride(n2)?;
}
encoding.truncate(n1, params.stride, params.direction);
p.truncate(n2, params.stride, params.direction);
}
None => {
check_stride(total - to_remove)?;
encoding.truncate(total - to_remove, params.stride, params.direction);
}
},
TruncationStrategy::OnlyFirst | TruncationStrategy::OnlySecond => {
let target = if params.strategy == TruncationStrategy::OnlyFirst {
&mut encoding
} else {
pair.as_mut().ok_or_else(|| {
Error::Truncation("OnlySecond strategy requires a pair".into())
})?
};
let len = target.len();
if len <= to_remove {
return Err(Error::Truncation(
"sequence to truncate is too short to respect max_length".into(),
));
}
check_stride(len - to_remove)?;
target.truncate(len - to_remove, params.stride, params.direction);
}
}
Ok((encoding, pair))
}
pub fn pad_encodings(encodings: &mut [Encoding], params: &PaddingParams) -> Result<()> {
if encodings.is_empty() {
return Ok(());
}
let mut target = match params.strategy {
PaddingStrategy::Fixed(n) => n,
PaddingStrategy::BatchLongest => encodings.iter().map(Encoding::len).max().unwrap_or(0),
};
if let Some(m) = params.pad_to_multiple_of {
if m > 0 && target % m > 0 {
target += m - target % m;
}
}
encodings.par_iter_mut().for_each(|e| {
e.pad(
target,
params.pad_id,
params.pad_type_id,
¶ms.pad_token,
params.direction,
)
});
Ok(())
}
#[derive(Debug, Clone)]
pub struct Tokenizer {
normalizer: Option<NormalizerWrapper>,
pre_tokenizer: Option<PreTokenizerWrapper>,
model: ModelWrapper,
post_processor: Option<PostProcessorWrapper>,
decoder: Option<DecoderWrapper>,
added_vocabulary: AddedVocabulary,
truncation: Option<TruncationParams>,
padding: Option<PaddingParams>,
}
impl Tokenizer {
pub fn new(model: impl Into<ModelWrapper>) -> Self {
Self {
normalizer: None,
pre_tokenizer: None,
model: model.into(),
post_processor: None,
decoder: None,
added_vocabulary: AddedVocabulary::new(),
truncation: None,
padding: None,
}
}
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self> {
Ok(serde_json::from_slice(bytes.as_ref())?)
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let bytes = std::fs::read(path)?;
Self::from_bytes(bytes)
}
#[cfg(feature = "hub")]
#[cfg_attr(docsrs, doc(cfg(feature = "hub")))]
pub fn from_pretrained(
identifier: &str,
params: Option<hub::FromPretrainedParameters>,
) -> Result<Self> {
Self::from_file(hub::from_pretrained(identifier, params)?)
}
pub fn to_json(&self, pretty: bool) -> Result<String> {
Ok(if pretty {
serde_json::to_string_pretty(self)?
} else {
serde_json::to_string(self)?
})
}
pub fn save(&self, path: impl AsRef<Path>, pretty: bool) -> Result<()> {
let contents = self.to_json(pretty)?;
let path = path.as_ref();
let parent = path
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(Path::new("."));
let mut file = tempfile::NamedTempFile::new_in(parent)?;
file.write_all(contents.as_bytes())?;
if let Ok(metadata) = std::fs::metadata(path) {
file.as_file().set_permissions(metadata.permissions())?;
}
file.as_file().sync_all()?;
file.persist(path).map_err(|e| e.error)?;
Ok(())
}
#[must_use]
pub fn with_normalizer(mut self, normalizer: impl Into<NormalizerWrapper>) -> Self {
self.set_normalizer(Some(normalizer.into()))
.expect("refreshing added tokens cannot fail for a valid normalizer");
self
}
#[must_use]
pub fn with_pre_tokenizer(mut self, pre_tokenizer: impl Into<PreTokenizerWrapper>) -> Self {
self.pre_tokenizer = Some(pre_tokenizer.into());
self
}
#[must_use]
pub fn with_post_processor(mut self, post_processor: impl Into<PostProcessorWrapper>) -> Self {
self.post_processor = Some(post_processor.into());
self
}
#[must_use]
pub fn with_decoder(mut self, decoder: impl Into<DecoderWrapper>) -> Self {
self.decoder = Some(decoder.into());
self
}
pub fn set_normalizer(&mut self, normalizer: Option<NormalizerWrapper>) -> Result<()> {
self.normalizer = normalizer;
self.added_vocabulary
.refresh(self.normalizer.as_ref().map(|n| n as &dyn Normalizer))
}
pub fn set_pre_tokenizer(&mut self, pre_tokenizer: Option<PreTokenizerWrapper>) {
self.pre_tokenizer = pre_tokenizer;
}
pub fn set_model(&mut self, model: impl Into<ModelWrapper>) {
self.model = model.into();
}
pub fn set_post_processor(&mut self, post_processor: Option<PostProcessorWrapper>) {
self.post_processor = post_processor;
}
pub fn set_decoder(&mut self, decoder: Option<DecoderWrapper>) {
self.decoder = decoder;
}
pub fn set_truncation(&mut self, truncation: Option<TruncationParams>) -> Result<()> {
if let Some(t) = &truncation {
let effective = t.max_length.saturating_sub(self.n_added_tokens(false));
if t.stride >= effective && effective > 0 {
return Err(Error::Truncation(format!(
"stride {} must be less than the effective max length {effective} \
({} minus {} special tokens)",
t.stride,
t.max_length,
self.n_added_tokens(false)
)));
}
}
self.truncation = truncation;
Ok(())
}
pub fn set_padding(&mut self, padding: Option<PaddingParams>) {
self.padding = padding;
}
pub fn normalizer(&self) -> Option<&NormalizerWrapper> {
self.normalizer.as_ref()
}
pub fn pre_tokenizer(&self) -> Option<&PreTokenizerWrapper> {
self.pre_tokenizer.as_ref()
}
pub fn model(&self) -> &ModelWrapper {
&self.model
}
pub fn post_processor(&self) -> Option<&PostProcessorWrapper> {
self.post_processor.as_ref()
}
pub fn decoder(&self) -> Option<&DecoderWrapper> {
self.decoder.as_ref()
}
pub fn truncation(&self) -> Option<&TruncationParams> {
self.truncation.as_ref()
}
pub fn padding(&self) -> Option<&PaddingParams> {
self.padding.as_ref()
}
pub fn added_vocabulary(&self) -> &AddedVocabulary {
&self.added_vocabulary
}
pub fn set_encode_special_tokens(&mut self, value: bool) {
self.added_vocabulary.set_encode_special_tokens(value);
}
pub fn vocab(&self, with_added_tokens: bool) -> HashMap<String, u32> {
let mut vocab = self.model.vocab();
if with_added_tokens {
for (token, id) in self.added_vocabulary.vocab() {
vocab.insert(token.clone(), *id);
}
}
vocab
}
pub fn vocab_size(&self, with_added_tokens: bool) -> usize {
let base = self.model.vocab_size();
if !with_added_tokens {
return base;
}
let added = self.added_vocabulary.vocab();
let overlapping = added
.keys()
.filter(|t| self.model.token_to_id(t).is_some())
.count();
base + added.len() - overlapping
}
pub fn token_to_id(&self, token: &str) -> Option<u32> {
self.added_vocabulary.token_to_id(token, &self.model)
}
pub fn id_to_token(&self, id: u32) -> Option<String> {
self.added_vocabulary
.simple_id_to_token(id)
.or_else(|| self.model.id_to_token(id))
}
pub fn add_tokens(&mut self, tokens: &[AddedToken]) -> Result<usize> {
self.added_vocabulary.add_tokens(
tokens,
&self.model,
self.normalizer.as_ref().map(|n| n as &dyn Normalizer),
)
}
pub fn add_special_tokens(&mut self, tokens: &[AddedToken]) -> Result<usize> {
let tokens: Vec<AddedToken> = tokens.iter().cloned().map(|t| t.special(true)).collect();
self.add_tokens(&tokens)
}
fn n_added_tokens(&self, is_pair: bool) -> usize {
self.post_processor
.as_ref()
.map_or(0, |pp| pp.added_tokens(is_pair))
}
pub fn normalize(&self, text: &str) -> Result<NormalizedString> {
let mut n = NormalizedString::from(text);
if let Some(normalizer) = &self.normalizer {
normalizer.normalize(&mut n)?;
}
Ok(n)
}
fn pre_tokenize(&self, mut pretokenized: PreTokenizedString) -> Result<PreTokenizedString> {
if let Some(pt) = &self.pre_tokenizer {
pt.pre_tokenize(&mut pretokenized)?;
}
Ok(pretokenized)
}
fn encode_text(
&self,
text: &str,
word_idx: Option<u32>,
type_id: u32,
offset_type: OffsetType,
) -> Result<Encoding> {
let pretokenized = self
.added_vocabulary
.extract_and_normalize(self.normalizer.as_ref().map(|n| n as &dyn Normalizer), text)?;
let mut pretokenized = self.pre_tokenize(pretokenized)?;
pretokenized.tokenize(|n| self.model.tokenize(n.get()))?;
pretokenized.into_encoding(word_idx, type_id, offset_type)
}
fn encode_sequence(
&self,
sequence: &InputSequence<'_>,
type_id: u32,
offset_type: OffsetType,
) -> Result<Encoding> {
match sequence {
InputSequence::Raw(text) => self.encode_text(text, None, type_id, offset_type),
InputSequence::PreTokenized(words) => {
let parts = words
.iter()
.enumerate()
.map(|(i, w)| self.encode_text(w, Some(i as u32), type_id, offset_type))
.collect::<Result<Vec<_>>>()?;
Ok(Encoding::merge(parts, false))
}
}
}
fn encode_with<'s>(
&self,
input: impl Into<EncodeInput<'s>>,
add_special_tokens: bool,
offset_type: OffsetType,
) -> Result<Encoding> {
let (first, second) = match input.into() {
EncodeInput::Single(s) => (s, None),
EncodeInput::Dual(a, b) => (a, Some(b)),
};
let encoding = self.encode_sequence(&first, 0, offset_type)?;
let pair = second
.map(|s| self.encode_sequence(&s, 1, offset_type))
.transpose()?;
self.post_process(encoding, pair, add_special_tokens)
}
pub fn encode<'s>(
&self,
input: impl Into<EncodeInput<'s>>,
add_special_tokens: bool,
) -> Result<Encoding> {
self.encode_with(input, add_special_tokens, OffsetType::Byte)
}
pub fn encode_char_offsets<'s>(
&self,
input: impl Into<EncodeInput<'s>>,
add_special_tokens: bool,
) -> Result<Encoding> {
self.encode_with(input, add_special_tokens, OffsetType::Char)
}
fn encode_batch_with<'s, E>(
&self,
inputs: Vec<E>,
add_special_tokens: bool,
offset_type: OffsetType,
) -> Result<Vec<Encoding>>
where
E: Into<EncodeInput<'s>> + Send,
{
let mut encodings = inputs
.into_par_iter()
.map(|i| self.encode_with(i, add_special_tokens, offset_type))
.collect::<Result<Vec<_>>>()?;
if let Some(params) = &self.padding {
pad_encodings(&mut encodings, params)?;
}
Ok(encodings)
}
pub fn encode_batch<'s, E>(
&self,
inputs: Vec<E>,
add_special_tokens: bool,
) -> Result<Vec<Encoding>>
where
E: Into<EncodeInput<'s>> + Send,
{
self.encode_batch_with(inputs, add_special_tokens, OffsetType::Byte)
}
pub fn encode_batch_char_offsets<'s, E>(
&self,
inputs: Vec<E>,
add_special_tokens: bool,
) -> Result<Vec<Encoding>>
where
E: Into<EncodeInput<'s>> + Send,
{
self.encode_batch_with(inputs, add_special_tokens, OffsetType::Char)
}
pub fn post_process(
&self,
encoding: Encoding,
pair: Option<Encoding>,
add_special_tokens: bool,
) -> Result<Encoding> {
let (encoding, pair) = match &self.truncation {
Some(trunc) => {
let n_added = self.n_added_tokens(pair.is_some());
if add_special_tokens && n_added > 0 {
let max_length = trunc.max_length.checked_sub(n_added).ok_or_else(|| {
Error::Truncation(format!(
"max_length {} is smaller than the {n_added} required special tokens",
trunc.max_length
))
})?;
if max_length == 0
&& (!encoding.is_empty() || pair.as_ref().is_some_and(|p| !p.is_empty()))
{
return Err(Error::Truncation(
"no space for input tokens after reserving special tokens".into(),
));
}
let params = TruncationParams {
max_length,
..trunc.clone()
};
truncate_encodings(encoding, pair, ¶ms)?
} else {
truncate_encodings(encoding, pair, trunc)?
}
}
None => (encoding, pair),
};
let encoding = match &self.post_processor {
Some(pp) => pp.process(encoding, pair, add_special_tokens)?,
None => match pair {
None => encoding,
Some(pair) => {
let mut first = encoding;
first.set_sequence_id(0);
let mut second = pair;
second.set_sequence_id(1);
Encoding::merge([first, second], false)
}
},
};
let mut out = [encoding];
if let Some(params) = &self.padding {
pad_encodings(&mut out, params)?;
}
let [encoding] = out;
Ok(encoding)
}
pub fn decode(&self, ids: &[u32], skip_special_tokens: bool) -> Result<String> {
let tokens: Vec<String> = ids
.iter()
.filter_map(|&id| self.id_to_token(id))
.filter(|t| !skip_special_tokens || !self.added_vocabulary.is_special_token(t))
.collect();
match &self.decoder {
Some(d) => d.decode(tokens),
None => Ok(tokens.join(" ")),
}
}
pub fn decode_batch(
&self,
sequences: &[&[u32]],
skip_special_tokens: bool,
) -> Result<Vec<String>> {
sequences
.par_iter()
.map(|ids| self.decode(ids, skip_special_tokens))
.collect()
}
pub fn decode_stream(&self, skip_special_tokens: bool) -> DecodeStream<'_> {
DecodeStream::new(self, skip_special_tokens)
}
pub fn train<T, I, S>(&mut self, trainer: T, sequences: I) -> Result<&mut Self>
where
T: Into<TrainerWrapper>,
I: Iterator<Item = S> + Send,
S: AsRef<str> + Send,
{
self.train_fallible(trainer, sequences.map(Ok))
}
fn train_fallible<T, I, S>(&mut self, trainer: T, sequences: I) -> Result<&mut Self>
where
T: Into<TrainerWrapper>,
I: Iterator<Item = Result<S>> + Send,
S: AsRef<str> + Send,
{
let mut trainer: TrainerWrapper = trainer.into();
let mut read_error = None;
let sequences = sequences
.map_while(|sequence| match sequence {
Ok(sequence) => Some(sequence),
Err(error) => {
read_error = Some(error);
None
}
})
.fuse();
trainer.feed(sequences, |seq| {
let normalized = self.normalize(seq)?;
let pretokenized = self.pre_tokenize(PreTokenizedString::from(normalized))?;
Ok(pretokenized
.get_splits(OffsetType::Byte)
.into_iter()
.map(|(s, _, _)| s.to_owned())
.collect())
})?;
if let Some(error) = read_error {
return Err(error);
}
let mut model = self.model.clone();
let special = trainer.train(&mut model)?;
let mut added_vocabulary = AddedVocabulary::new();
added_vocabulary.set_encode_special_tokens(self.added_vocabulary.encode_special_tokens());
let mut tokens: Vec<AddedToken> = self
.added_vocabulary
.tokens_with_ids()
.into_iter()
.map(|t| t.token)
.collect();
for token in special {
if let Some(existing) = tokens.iter_mut().find(|t| t.content == token.content) {
existing.special = true;
} else {
tokens.push(token.special(true));
}
}
added_vocabulary.add_tokens(
&tokens,
&model,
self.normalizer.as_ref().map(|n| n as &dyn Normalizer),
)?;
let lookup = |token: &str| {
added_vocabulary.token_to_id(token, &model).ok_or_else(|| {
Error::Training(format!(
"configured token {token:?} is missing from the trained vocabulary; include it in the trainer's special tokens"
))
})
};
let mut post_processor = self.post_processor.clone();
if let Some(processor) = &mut post_processor {
processor.rebind_token_ids(&lookup)?;
}
let mut padding = self.padding.clone();
if let Some(padding) = &mut padding {
padding.pad_id = lookup(&padding.pad_token)?;
}
self.model = model;
self.added_vocabulary = added_vocabulary;
self.post_processor = post_processor;
self.padding = padding;
Ok(self)
}
pub fn train_from_files<T, P>(&mut self, trainer: T, files: &[P]) -> Result<&mut Self>
where
T: Into<TrainerWrapper>,
P: AsRef<Path>,
{
let mut paths = files
.iter()
.map(|p| p.as_ref().to_owned())
.collect::<Vec<_>>()
.into_iter();
let mut active: Option<(std::path::PathBuf, std::io::BufReader<std::fs::File>)> = None;
let lines = std::iter::from_fn(move || {
loop {
if active.is_none() {
let path = paths.next()?;
match std::fs::File::open(&path) {
Ok(file) => active = Some((path, std::io::BufReader::new(file))),
Err(e) => {
return Some(Err(Error::Training(format!("{}: {e}", path.display()))));
}
}
}
let (path, reader) = active.as_mut().expect("file opened above");
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => active = None,
Ok(_) => return Some(Ok(line)),
Err(e) => {
return Some(Err(Error::Training(format!("{}: {e}", path.display()))));
}
}
}
});
self.train_fallible(trainer, lines)
}
}
impl std::str::FromStr for Tokenizer {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::from_json(s)
}
}