mod bert;
mod roberta;
mod sequence;
pub mod template;
use serde::{Deserialize, Serialize};
use crate::encoding::Encoding;
use crate::error::Result;
use crate::pre_tokenizers::ByteLevel;
use crate::traits::PostProcessor;
pub use bert::BertProcessing;
pub use roberta::RobertaProcessing;
pub use sequence::Sequence;
pub use template::{
Piece, SpecialToken, Template, TemplateProcessing, TemplateProcessingBuilder, Tokens,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
#[non_exhaustive]
pub enum PostProcessorWrapper {
BertProcessing(BertProcessing),
RobertaProcessing(RobertaProcessing),
ByteLevel(ByteLevel),
TemplateProcessing(TemplateProcessing),
Sequence(Sequence),
}
impl PostProcessorWrapper {
pub(crate) fn rebind_token_ids(&mut self, lookup: &impl Fn(&str) -> Result<u32>) -> Result<()> {
match self {
Self::BertProcessing(p) => {
p.sep.1 = lookup(&p.sep.0)?;
p.cls.1 = lookup(&p.cls.0)?;
}
Self::RobertaProcessing(p) => {
p.sep.1 = lookup(&p.sep.0)?;
p.cls.1 = lookup(&p.cls.0)?;
}
Self::ByteLevel(_) => {}
Self::TemplateProcessing(p) => p.rebind_token_ids(lookup)?,
Self::Sequence(p) => {
for processor in p.processors_mut() {
processor.rebind_token_ids(lookup)?;
}
}
}
Ok(())
}
}
impl PostProcessor for PostProcessorWrapper {
fn added_tokens(&self, is_pair: bool) -> usize {
match self {
Self::BertProcessing(p) => p.added_tokens(is_pair),
Self::RobertaProcessing(p) => p.added_tokens(is_pair),
Self::ByteLevel(p) => p.added_tokens(is_pair),
Self::TemplateProcessing(p) => p.added_tokens(is_pair),
Self::Sequence(p) => p.added_tokens(is_pair),
}
}
fn process_encodings(
&self,
encodings: Vec<Encoding>,
add_special_tokens: bool,
) -> Result<Vec<Encoding>> {
match self {
Self::BertProcessing(p) => p.process_encodings(encodings, add_special_tokens),
Self::RobertaProcessing(p) => p.process_encodings(encodings, add_special_tokens),
Self::ByteLevel(p) => p.process_encodings(encodings, add_special_tokens),
Self::TemplateProcessing(p) => p.process_encodings(encodings, add_special_tokens),
Self::Sequence(p) => p.process_encodings(encodings, add_special_tokens),
}
}
}
impl From<BertProcessing> for PostProcessorWrapper {
fn from(p: BertProcessing) -> Self {
Self::BertProcessing(p)
}
}
impl From<RobertaProcessing> for PostProcessorWrapper {
fn from(p: RobertaProcessing) -> Self {
Self::RobertaProcessing(p)
}
}
impl From<ByteLevel> for PostProcessorWrapper {
fn from(p: ByteLevel) -> Self {
Self::ByteLevel(p)
}
}
impl From<TemplateProcessing> for PostProcessorWrapper {
fn from(p: TemplateProcessing) -> Self {
Self::TemplateProcessing(p)
}
}
impl From<Sequence> for PostProcessorWrapper {
fn from(p: Sequence) -> Self {
Self::Sequence(p)
}
}
pub(crate) fn special_encoding(ids: Vec<u32>, tokens: Vec<String>, type_id: u32) -> Encoding {
let n = ids.len();
Encoding::new(
ids,
vec![type_id; n],
tokens,
vec![None; n],
vec![(0, 0); n],
vec![1; n],
vec![1; n],
Vec::new(),
)
}
pub(crate) fn mark_sequence(encoding: &mut Encoding, id: usize) {
encoding.set_sequence_id(id);
for o in encoding.overflowing_mut() {
o.set_sequence_id(id);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wrapper_serde_is_tagged() {
let bert = PostProcessorWrapper::from(BertProcessing::default());
let s = serde_json::to_string(&bert).unwrap();
assert_eq!(
s,
r#"{"type":"BertProcessing","sep":["[SEP]",102],"cls":["[CLS]",101]}"#
);
assert_eq!(
serde_json::from_str::<PostProcessorWrapper>(&s).unwrap(),
bert
);
let roberta = PostProcessorWrapper::from(RobertaProcessing::default());
let s = serde_json::to_string(&roberta).unwrap();
assert_eq!(
s,
r#"{"type":"RobertaProcessing","sep":["</s>",2],"cls":["<s>",0],"trim_offsets":true,"add_prefix_space":true}"#
);
assert_eq!(
serde_json::from_str::<PostProcessorWrapper>(&s).unwrap(),
roberta
);
}
#[test]
fn missing_or_unknown_type_is_an_error() {
assert!(
serde_json::from_str::<PostProcessorWrapper>(
r#"{"sep":["[SEP]",102],"cls":["[CLS]",101]}"#
)
.is_err()
);
assert!(serde_json::from_str::<PostProcessorWrapper>(r#"{"type":"Nope"}"#).is_err());
assert!(
serde_json::from_str::<PostProcessorWrapper>(
r#"{"type":"BertProcessing","sep":["[SEP]",102]}"#
)
.is_err()
);
}
#[test]
fn real_fixture_configs_round_trip() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/hf");
let Ok(entries) = std::fs::read_dir(&dir) else {
eprintln!(
"skipping: {} missing (run scripts/fetch-hf-fixtures.sh)",
dir.display()
);
return;
};
let mut checked = 0;
for entry in entries {
let path = entry.unwrap().path();
let raw = std::fs::read_to_string(&path).unwrap();
let file: serde_json::Value = serde_json::from_str(&raw).unwrap();
let pp = &file["post_processor"];
if pp.is_null() {
continue;
}
let parsed: PostProcessorWrapper = serde_json::from_value(pp.clone())
.unwrap_or_else(|e| panic!("{}: {e}", path.display()));
if pp["type"] == "ByteLevel" {
continue;
}
assert_eq!(
&serde_json::to_value(&parsed).unwrap(),
pp,
"{} did not round-trip",
path.display()
);
checked += 1;
}
eprintln!("checked {checked} post-processor configs");
}
}