use rustc_hash::{FxHashMap, FxHashSet};
use super::added::{AddedToken, AddedTokenSet};
use super::any_tokenizer::{AnyTokenizer, Backend};
use super::normalizer::{NormOp, Normalizer};
use super::policy::SpecialPolicy;
use super::spm::{SpmPrefixScheme, SpmTokenizer, NEVER_MERGE};
use super::tokenizer::{
Tokenizer, TokenizerError, CL100K_BASE_PATTERN, DEEPSEEK_V3_PATTERNS, GPT2_PATTERN,
KIMI_PATTERN, LLAMA3_PATTERN, MISTRAL_V3_PATTERN, NO_SPLIT_PATTERN, O200K_BASE_PATTERN,
QWEN2_PATTERN,
};
use super::vocab::{load_spm_vocab, place_special_pieces, SpmVocab};
use super::whisper::{whisper_special_tokens, WhisperVariant};
#[cfg(feature = "vocab-cl100k")]
pub use splintr_vocab_cl100k::CL100K_BASE_VOCAB_PACKED;
#[cfg(feature = "vocab-deepseek")]
pub use splintr_vocab_deepseek::DEEPSEEK_V3_VOCAB_PACKED;
#[cfg(feature = "vocab-gemma2")]
pub use splintr_vocab_gemma2::GEMMA2_SPM_VOCAB;
#[cfg(feature = "vocab-gemma3")]
pub use splintr_vocab_gemma3::GEMMA3_SPM_VOCAB;
#[cfg(feature = "vocab-gemma4")]
pub use splintr_vocab_gemma4::{GEMMA4_MERGES_PACKED, GEMMA4_VOCAB_PACKED};
#[cfg(feature = "vocab-glm")]
pub use splintr_vocab_glm::GLM4_VOCAB_PACKED;
#[cfg(feature = "vocab-kimi")]
pub use splintr_vocab_kimi::KIMI_VOCAB_PACKED;
#[cfg(feature = "vocab-llama2")]
pub use splintr_vocab_llama2::{CODELLAMA_SPM_VOCAB, LLAMA2_SPM_VOCAB};
#[cfg(feature = "vocab-llama3")]
pub use splintr_vocab_llama3::LLAMA3_VOCAB_PACKED;
#[cfg(feature = "vocab-mistral")]
pub use splintr_vocab_mistral::{MISTRAL_SPM_VOCAB, MISTRAL_V2_SPM_VOCAB, MISTRAL_V3_VOCAB_PACKED};
#[cfg(feature = "vocab-modernbert")]
pub use splintr_vocab_modernbert::MODERNBERT_VOCAB_PACKED;
#[cfg(feature = "vocab-o200k")]
pub use splintr_vocab_o200k::O200K_BASE_VOCAB_PACKED;
#[cfg(feature = "vocab-qwen")]
pub use splintr_vocab_qwen::QWEN3_VOCAB_PACKED;
#[cfg(feature = "vocab-whisper")]
pub use splintr_vocab_whisper::WHISPER_VOCAB_PACKED;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PretrainedVocab {
Cl100kBase,
O200kBase,
Llama3,
DeepseekV3,
Qwen3,
Glm4,
GptOss,
Phi4,
Olmo2,
Llama2,
CodeLlama,
ModernBert,
Gemma2,
Gemma3,
Gemma4,
KimiK2,
KimiK3,
MistralV1,
MistralV2,
MistralV3,
WhisperV1,
WhisperV2,
WhisperV3,
}
impl PretrainedVocab {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"cl100k_base" => Some(Self::Cl100kBase),
"o200k_base" => Some(Self::O200kBase),
"llama3" | "llama3.1" | "llama3.2" | "llama3.3" => Some(Self::Llama3),
"deepseek_v3" | "deepseek-v3" => Some(Self::DeepseekV3),
"qwen" | "qwen2" | "qwen3" | "qwen2.5" | "baichuan_m2" => Some(Self::Qwen3),
"glm" | "glm4" | "glm-4" | "glm4.5" | "glm-4.5" => Some(Self::Glm4),
"gpt-oss" | "gpt_oss" | "o200k_harmony" => Some(Self::GptOss),
"phi4" | "phi-4" => Some(Self::Phi4),
"olmo2" | "olmo-2" => Some(Self::Olmo2),
"llama2" | "llama-2" | "tinyllama" | "vicuna" => Some(Self::Llama2),
"codellama" | "code_llama" | "code-llama" => Some(Self::CodeLlama),
"modernbert" | "modern-bert" => Some(Self::ModernBert),
"gemma2" | "gemma-2" => Some(Self::Gemma2),
"gemma3" | "gemma-3" | "embeddinggemma" => Some(Self::Gemma3),
"gemma4" | "gemma-4" => Some(Self::Gemma4),
"kimi" | "kimi_k2" | "kimi-k2" | "kimi_k2.5" | "kimi-k2.5" | "kimi_linear" => {
Some(Self::KimiK2)
}
"kimi_k3" | "kimi-k3" => Some(Self::KimiK3),
"mistral" | "mistral_v1" => Some(Self::MistralV1),
"mistral_v2" => Some(Self::MistralV2),
"mistral_v3" => Some(Self::MistralV3),
"whisper_v1" | "whisper-v1" | "whisper-multilingual-v1" => Some(Self::WhisperV1),
"whisper" | "whisper_v2" | "whisper-v2" | "whisper-multilingual" => {
Some(Self::WhisperV2)
}
"whisper_v3" | "whisper-v3" | "whisper-large-v3" => Some(Self::WhisperV3),
_ => None,
}
}
pub fn supported_names() -> &'static [&'static str] {
&[
"cl100k_base",
"o200k_base",
"llama3",
"llama3.1",
"llama3.2",
"llama3.3",
"deepseek_v3",
"deepseek-v3",
"qwen",
"qwen2",
"qwen2.5",
"qwen3",
"baichuan_m2",
"glm",
"glm4",
"glm-4",
"glm4.5",
"glm-4.5",
"gpt-oss",
"gpt_oss",
"o200k_harmony",
"phi4",
"phi-4",
"olmo2",
"olmo-2",
"llama2",
"llama-2",
"tinyllama",
"vicuna",
"codellama",
"code_llama",
"code-llama",
"modernbert",
"modern-bert",
"gemma2",
"gemma-2",
"gemma3",
"gemma-3",
"embeddinggemma",
"gemma4",
"gemma-4",
"kimi",
"kimi_k2",
"kimi-k2",
"kimi_k2.5",
"kimi-k2.5",
"kimi_linear",
"kimi_k3",
"kimi-k3",
"mistral",
"mistral_v1",
"mistral_v2",
"mistral_v3",
"whisper",
"whisper_v1",
"whisper_v2",
"whisper_v3",
]
}
}
pub fn from_pretrained(name: &str) -> Result<AnyTokenizer, TokenizerError> {
from_vocab(resolve_vocab(name)?)
}
fn resolve_vocab(name: &str) -> Result<PretrainedVocab, TokenizerError> {
PretrainedVocab::from_name(name).ok_or_else(|| {
TokenizerError::UnknownPretrained(format!(
"{}. Supported: {}",
name,
PretrainedVocab::supported_names().join(", ")
))
})
}
fn vocab_bytes(vocab: PretrainedVocab) -> Result<&'static [u8], TokenizerError> {
macro_rules! bundled {
($feature:literal, $konst:ident, $name:literal) => {{
#[cfg(feature = $feature)]
{
Ok($konst)
}
#[cfg(not(feature = $feature))]
{
Err(TokenizerError::VocabNotBundled($name, $feature))
}
}};
}
match vocab {
PretrainedVocab::Cl100kBase => {
bundled!("vocab-cl100k", CL100K_BASE_VOCAB_PACKED, "cl100k_base")
}
PretrainedVocab::O200kBase => {
bundled!("vocab-o200k", O200K_BASE_VOCAB_PACKED, "o200k_base")
}
PretrainedVocab::GptOss => bundled!("vocab-gpt-oss", O200K_BASE_VOCAB_PACKED, "gpt-oss"),
PretrainedVocab::Phi4 => bundled!("vocab-phi4", CL100K_BASE_VOCAB_PACKED, "phi4"),
PretrainedVocab::Olmo2 => bundled!("vocab-olmo2", CL100K_BASE_VOCAB_PACKED, "olmo2"),
PretrainedVocab::Llama3 => bundled!("vocab-llama3", LLAMA3_VOCAB_PACKED, "llama3"),
PretrainedVocab::Llama2 => bundled!("vocab-llama2", LLAMA2_SPM_VOCAB, "llama2"),
PretrainedVocab::CodeLlama => {
bundled!("vocab-llama2", CODELLAMA_SPM_VOCAB, "codellama")
}
PretrainedVocab::ModernBert => {
bundled!("vocab-modernbert", MODERNBERT_VOCAB_PACKED, "modernbert")
}
PretrainedVocab::Gemma2 => bundled!("vocab-gemma2", GEMMA2_SPM_VOCAB, "gemma2"),
PretrainedVocab::Gemma3 => bundled!("vocab-gemma3", GEMMA3_SPM_VOCAB, "gemma3"),
PretrainedVocab::Gemma4 => bundled!("vocab-gemma4", GEMMA4_VOCAB_PACKED, "gemma4"),
PretrainedVocab::DeepseekV3 => {
bundled!("vocab-deepseek", DEEPSEEK_V3_VOCAB_PACKED, "deepseek_v3")
}
PretrainedVocab::Qwen3 => bundled!("vocab-qwen", QWEN3_VOCAB_PACKED, "qwen3"),
PretrainedVocab::Glm4 => bundled!("vocab-glm", GLM4_VOCAB_PACKED, "glm4"),
PretrainedVocab::KimiK2 => bundled!("vocab-kimi", KIMI_VOCAB_PACKED, "kimi_k2"),
PretrainedVocab::KimiK3 => bundled!("vocab-kimi", KIMI_VOCAB_PACKED, "kimi_k3"),
PretrainedVocab::MistralV1 => bundled!("vocab-mistral", MISTRAL_SPM_VOCAB, "mistral"),
PretrainedVocab::MistralV2 => bundled!("vocab-mistral", MISTRAL_V2_SPM_VOCAB, "mistral_v2"),
PretrainedVocab::MistralV3 => {
bundled!("vocab-mistral", MISTRAL_V3_VOCAB_PACKED, "mistral_v3")
}
PretrainedVocab::WhisperV1 | PretrainedVocab::WhisperV2 | PretrainedVocab::WhisperV3 => {
bundled!("vocab-whisper", WHISPER_VOCAB_PACKED, "whisper")
}
}
}
fn byte_fallback_from_pieces(
pieces: &crate::core::decode_table::Decoder,
unk_id: u32,
) -> Option<crate::core::tokenizer::ByteFallback> {
let mut byte_ids: [Option<u32>; 256] = [None; 256];
let mut found = 0usize;
for (id, bytes) in pieces.iter() {
let [b'<', b'0', b'x', hi, lo, b'>'] = bytes else {
continue;
};
let (Some(hi), Some(lo)) = (
(*hi as char)
.to_digit(16)
.filter(|_| !hi.is_ascii_lowercase()),
(*lo as char)
.to_digit(16)
.filter(|_| !lo.is_ascii_lowercase()),
) else {
continue;
};
let byte = (hi * 16 + lo) as usize;
if byte_ids[byte].is_none() {
byte_ids[byte] = Some(id);
found += 1;
}
}
(found > 0).then(|| crate::core::tokenizer::ByteFallback::new(byte_ids, Some(unk_id)))
}
fn merges_bytes(vocab: PretrainedVocab) -> Result<&'static [u8], TokenizerError> {
match vocab {
PretrainedVocab::Gemma4 => {
#[cfg(feature = "vocab-gemma4")]
{
Ok(GEMMA4_MERGES_PACKED)
}
#[cfg(not(feature = "vocab-gemma4"))]
{
Err(TokenizerError::VocabNotBundled("gemma4", "vocab-gemma4"))
}
}
_ => Err(TokenizerError::UnknownPretrained(
"this vocabulary states no separate merge order".to_owned(),
)),
}
}
pub fn from_vocab(vocab: PretrainedVocab) -> Result<AnyTokenizer, TokenizerError> {
let special = special_tokens(vocab);
let named = special.clone();
let skipped = special_decode_ids(vocab, &special);
match vocab {
PretrainedVocab::MistralV1
| PretrainedVocab::MistralV2
| PretrainedVocab::Llama2
| PretrainedVocab::CodeLlama
| PretrainedVocab::Gemma2
| PretrainedVocab::Gemma3 => {
return spm_from_vocab(vocab_bytes(vocab)?, vocab, special, named, skipped)
}
_ => {}
}
let pats = patterns(vocab).unwrap_or(&[]);
let data = vocab_bytes(vocab)?;
let special = added_token_set(vocab, special);
let tokenizer = match vocab {
PretrainedVocab::Cl100kBase
| PretrainedVocab::O200kBase
| PretrainedVocab::GptOss
| PretrainedVocab::Phi4
| PretrainedVocab::Olmo2
| PretrainedVocab::Llama3
| PretrainedVocab::Qwen3
| PretrainedVocab::Glm4
| PretrainedVocab::ModernBert
| PretrainedVocab::KimiK2
| PretrainedVocab::KimiK3 => Tokenizer::from_packed_chain(data, pats, special),
PretrainedVocab::DeepseekV3
| PretrainedVocab::MistralV3
| PretrainedVocab::WhisperV1
| PretrainedVocab::WhisperV2
| PretrainedVocab::WhisperV3 => {
Tokenizer::from_packed_byte_level_chain(data, pats, special)
}
PretrainedVocab::Gemma4 => {
let decoder = crate::core::vocab::decoder_from_packed(data)?;
let rules =
crate::core::vocab::load_packed_merge_rules(merges_bytes(vocab)?, &decoder)?;
let ranks = crate::core::bpe::merge_ranks_bytes(
rules.iter().map(|rule| rule.result),
rules.len(),
decoder.iter().map(|(_, bytes)| bytes),
);
let encoder = crate::core::vocab::load_packed_bpe_without(
data,
&crate::core::vocab::orphan_ids(&decoder, &rules, &ranks),
)?;
let byte_fallback = byte_fallback_from_pieces(&decoder, 3);
Tokenizer::from_encoder_with_metaspace_decoder(encoder, special, pats[0]).map(|t| {
t.with_merge_ranks(ranks)
.with_decode_table(decoder)
.with_byte_fallback(byte_fallback)
.with_prefix_space(false)
.with_metaspace_split(false)
})
}
PretrainedVocab::MistralV1
| PretrainedVocab::MistralV2
| PretrainedVocab::Llama2
| PretrainedVocab::CodeLlama
| PretrainedVocab::Gemma2
| PretrainedVocab::Gemma3 => {
return Err(TokenizerError::UnknownPretrained(
"SentencePiece vocabularies take the SPM backend and are routed earlier".to_owned(),
))
}
}?;
let tokenizer = match normalizer(vocab) {
Some(n) => tokenizer.with_normalizer(n),
None => tokenizer,
};
Ok(AnyTokenizer::new(
Backend::Bpe(
tokenizer
.with_added_token_matching(true)
.with_special_decode_ids(skipped),
),
{
let (prefix, suffix) = boundary_ids(vocab);
SpecialPolicy::boundary(prefix, suffix, Some(eos_token_id(vocab)), named)
},
))
}
fn added_token_set(vocab: PretrainedVocab, special: FxHashMap<String, u32>) -> AddedTokenSet {
match vocab {
PretrainedVocab::Phi4 => special
.into_iter()
.map(|(name, id)| {
(
name,
AddedToken {
id,
lstrip: true,
rstrip: true,
},
)
})
.collect(),
PretrainedVocab::ModernBert => special
.into_iter()
.map(|(name, id)| {
let token = AddedToken {
id,
lstrip: id == MODERNBERT_MASK,
rstrip: false,
};
(name, token)
})
.collect(),
_ => special.into(),
}
}
fn normalizer(vocab: PretrainedVocab) -> Option<Normalizer> {
match vocab {
PretrainedVocab::ModernBert => Some(Normalizer::new(vec![NormOp::Nfc])),
PretrainedVocab::Gemma4 => Some(Normalizer::new(vec![NormOp::ReplaceStr {
from: " ".to_string(),
to: "\u{2581}".to_string(),
}])),
_ => None,
}
}
fn boundary_ids(vocab: PretrainedVocab) -> (Option<u32>, Option<u32>) {
match vocab {
PretrainedVocab::ModernBert => (Some(MODERNBERT_CLS), Some(MODERNBERT_SEP)),
_ => (None, None),
}
}
fn spm_from_vocab(
data: &[u8],
vocab: PretrainedVocab,
special: FxHashMap<String, u32>,
named: FxHashMap<String, u32>,
skipped: FxHashSet<u32>,
) -> Result<AnyTokenizer, TokenizerError> {
let SpmVocab {
mut pieces,
mut scores,
user_defined,
} = load_spm_vocab(data)?;
let special = fold_user_defined(&pieces, &user_defined, special);
place_special_pieces(&mut pieces, &special)?;
scores.resize(pieces.len(), NEVER_MERGE);
let eos = eos_token_id(vocab);
let tokenizer = SpmTokenizer::new(pieces, scores, bos_token_id(vocab), Some(eos))?
.with_prefix_space(spm_prefix_space(vocab))
.with_prefix_scheme(spm_prefix_scheme(vocab))
.with_added_tokens(&special)?
.with_special_decode_ids(skipped);
Ok(AnyTokenizer::new(
Backend::Spm(tokenizer),
SpecialPolicy::boundary(None, None, Some(eos), named),
))
}
fn fold_user_defined(
pieces: &[String],
user_defined: &[bool],
mut special: FxHashMap<String, u32>,
) -> FxHashMap<String, u32> {
for (id, piece) in pieces.iter().enumerate() {
if user_defined.get(id).copied().unwrap_or(false) {
special.entry(piece.clone()).or_insert(id as u32);
}
}
special
}
fn spm_prefix_space(vocab: PretrainedVocab) -> bool {
!matches!(vocab, PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3)
}
fn spm_prefix_scheme(vocab: PretrainedVocab) -> SpmPrefixScheme {
match vocab {
PretrainedVocab::MistralV1 => SpmPrefixScheme::AfterEachSpecial,
_ => SpmPrefixScheme::Once,
}
}
pub fn patterns(vocab: PretrainedVocab) -> Option<&'static [&'static str]> {
match vocab {
PretrainedVocab::Cl100kBase => Some(&[CL100K_BASE_PATTERN]),
PretrainedVocab::O200kBase => Some(&[O200K_BASE_PATTERN]),
PretrainedVocab::Llama3 => Some(&[LLAMA3_PATTERN]),
PretrainedVocab::DeepseekV3 => Some(DEEPSEEK_V3_PATTERNS),
PretrainedVocab::Qwen3 => Some(&[QWEN2_PATTERN]),
PretrainedVocab::Glm4 => Some(&[LLAMA3_PATTERN]),
PretrainedVocab::GptOss => Some(&[O200K_BASE_PATTERN]),
PretrainedVocab::Phi4 | PretrainedVocab::Olmo2 => Some(&[LLAMA3_PATTERN]),
PretrainedVocab::ModernBert => Some(&[GPT2_PATTERN]),
PretrainedVocab::Llama2 | PretrainedVocab::CodeLlama => None,
PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3 => None,
PretrainedVocab::Gemma4 => Some(&[NO_SPLIT_PATTERN]),
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => Some(&[KIMI_PATTERN]),
PretrainedVocab::MistralV1 | PretrainedVocab::MistralV2 => None,
PretrainedVocab::MistralV3 => Some(&[MISTRAL_V3_PATTERN]),
PretrainedVocab::WhisperV1 | PretrainedVocab::WhisperV2 | PretrainedVocab::WhisperV3 => {
Some(&[GPT2_PATTERN])
}
}
}
pub fn uses_byte_level(vocab: PretrainedVocab) -> bool {
matches!(
vocab,
PretrainedVocab::DeepseekV3
| PretrainedVocab::WhisperV1
| PretrainedVocab::WhisperV2
| PretrainedVocab::WhisperV3
)
}
pub fn eos_token_id(vocab: PretrainedVocab) -> u32 {
match vocab {
PretrainedVocab::Cl100kBase => 100257, PretrainedVocab::O200kBase => 199999, PretrainedVocab::Llama3 => 128001, PretrainedVocab::DeepseekV3 => 1, PretrainedVocab::Qwen3 => 151645, PretrainedVocab::Glm4 => 151329, PretrainedVocab::GptOss => 200002, PretrainedVocab::Phi4 => 100257, PretrainedVocab::Olmo2 => 100257, PretrainedVocab::Llama2 | PretrainedVocab::CodeLlama => 2, PretrainedVocab::ModernBert => MODERNBERT_SEP,
PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3 | PretrainedVocab::Gemma4 => 1,
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => 163585,
PretrainedVocab::MistralV1 | PretrainedVocab::MistralV2 | PretrainedVocab::MistralV3 => 2, PretrainedVocab::WhisperV1 => WhisperVariant::V1Multilingual.eos_token_id(),
PretrainedVocab::WhisperV2 => WhisperVariant::V2Multilingual.eos_token_id(),
PretrainedVocab::WhisperV3 => WhisperVariant::V3Multilingual.eos_token_id(),
}
}
pub fn eos_token_id_by_name(name: &str) -> u32 {
PretrainedVocab::from_name(name)
.map(eos_token_id)
.unwrap_or(0)
}
pub fn bos_token_id(vocab: PretrainedVocab) -> Option<u32> {
match vocab {
PretrainedVocab::Cl100kBase => None, PretrainedVocab::O200kBase => None, PretrainedVocab::Llama3 => Some(128000), PretrainedVocab::DeepseekV3 => Some(0), PretrainedVocab::Qwen3 | PretrainedVocab::Glm4 | PretrainedVocab::GptOss => None,
PretrainedVocab::Phi4 | PretrainedVocab::Olmo2 => None,
PretrainedVocab::Llama2 | PretrainedVocab::CodeLlama => Some(1), PretrainedVocab::ModernBert => Some(MODERNBERT_CLS),
PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3 | PretrainedVocab::Gemma4 => Some(2),
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => Some(163584),
PretrainedVocab::MistralV1 | PretrainedVocab::MistralV2 | PretrainedVocab::MistralV3 => {
Some(1)
} PretrainedVocab::WhisperV1 | PretrainedVocab::WhisperV2 | PretrainedVocab::WhisperV3 => {
None
}
}
}
pub fn bos_token_id_by_name(name: &str) -> Option<u32> {
PretrainedVocab::from_name(name).and_then(bos_token_id)
}
pub fn pad_token_id(vocab: PretrainedVocab) -> Option<u32> {
match vocab {
PretrainedVocab::Cl100kBase => Some(100316), PretrainedVocab::O200kBase => Some(200058), PretrainedVocab::Llama3 => Some(128339), PretrainedVocab::DeepseekV3 => Some(2), PretrainedVocab::Qwen3 => Some(QWEN3_BASE_VOCAB_SIZE + 39), PretrainedVocab::Glm4 => Some(GLM4_BASE_VOCAB_SIZE + 39), PretrainedVocab::GptOss => Some(200058), PretrainedVocab::Phi4 => Some(PHI4_BASE_VOCAB_SIZE + 39), PretrainedVocab::Olmo2 => Some(100277), PretrainedVocab::Llama2 => Some(LLAMA2_BASE_VOCAB_SIZE + 39), PretrainedVocab::CodeLlama => Some(CODELLAMA_BASE_VOCAB_SIZE + 39), PretrainedVocab::ModernBert => Some(MODERNBERT_PAD), PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3 | PretrainedVocab::Gemma4 => Some(0),
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => Some(163839),
PretrainedVocab::MistralV1 => Some(32039), PretrainedVocab::MistralV2 => Some(32807), PretrainedVocab::MistralV3 => Some(131111), PretrainedVocab::WhisperV1 | PretrainedVocab::WhisperV2 | PretrainedVocab::WhisperV3 => {
None
}
}
}
pub fn base_vocab_size(vocab: PretrainedVocab) -> u32 {
match vocab {
PretrainedVocab::Cl100kBase => CL100K_BASE_BASE_VOCAB_SIZE,
PretrainedVocab::O200kBase => O200K_BASE_BASE_VOCAB_SIZE,
PretrainedVocab::Llama3 => LLAMA3_BASE_VOCAB_SIZE,
PretrainedVocab::DeepseekV3 => DEEPSEEK_V3_BASE_VOCAB_SIZE,
PretrainedVocab::Qwen3 => QWEN3_BASE_VOCAB_SIZE,
PretrainedVocab::Glm4 => GLM4_BASE_VOCAB_SIZE,
PretrainedVocab::GptOss => O200K_BASE_BASE_VOCAB_SIZE,
PretrainedVocab::Phi4 => PHI4_BASE_VOCAB_SIZE,
PretrainedVocab::Olmo2 => OLMO2_BASE_VOCAB_SIZE,
PretrainedVocab::Llama2 => LLAMA2_BASE_VOCAB_SIZE,
PretrainedVocab::CodeLlama => CODELLAMA_BASE_VOCAB_SIZE,
PretrainedVocab::ModernBert => MODERNBERT_BASE_VOCAB_SIZE,
PretrainedVocab::Gemma2 => GEMMA2_BASE_VOCAB_SIZE,
PretrainedVocab::Gemma3 => GEMMA3_BASE_VOCAB_SIZE,
PretrainedVocab::Gemma4 => GEMMA4_BASE_VOCAB_SIZE,
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => KIMI_BASE_VOCAB_SIZE,
PretrainedVocab::MistralV1 => MISTRAL_V1_BASE_VOCAB_SIZE,
PretrainedVocab::MistralV2 => MISTRAL_V2_BASE_VOCAB_SIZE,
PretrainedVocab::MistralV3 => MISTRAL_V3_BASE_VOCAB_SIZE,
PretrainedVocab::WhisperV1 => WhisperVariant::V1Multilingual.vocab_size() as u32,
PretrainedVocab::WhisperV2 => WhisperVariant::V2Multilingual.vocab_size() as u32,
PretrainedVocab::WhisperV3 => WhisperVariant::V3Multilingual.vocab_size() as u32,
}
}
pub fn base_vocab_size_by_name(name: &str) -> Result<u32, TokenizerError> {
resolve_vocab(name).map(base_vocab_size)
}
fn special_decode_ids(vocab: PretrainedVocab, special: &FxHashMap<String, u32>) -> FxHashSet<u32> {
let all = || special.values().copied().collect::<FxHashSet<u32>>();
match vocab {
PretrainedVocab::Cl100kBase | PretrainedVocab::O200kBase => FxHashSet::default(),
PretrainedVocab::Llama3 => all(),
PretrainedVocab::MistralV1 | PretrainedVocab::MistralV2 => all(),
PretrainedVocab::MistralV3 => all(),
PretrainedVocab::DeepseekV3 => {
let rendered: FxHashSet<u32> = (128800..=128804).chain(128806..=128814).collect();
all().difference(&rendered).copied().collect()
}
PretrainedVocab::Qwen3 => {
let rendered: FxHashSet<u32> = (151657..=151668).collect();
all().difference(&rendered).copied().collect()
}
PretrainedVocab::Glm4 => {
let rendered: FxHashSet<u32> = (151350..=151359).chain(151361..=151364).collect();
all().difference(&rendered).copied().collect()
}
PretrainedVocab::GptOss => all(),
PretrainedVocab::Phi4 => all(),
PretrainedVocab::Olmo2 => {
let rendered: FxHashSet<u32> = [100256]
.into_iter()
.chain(100261..=100263)
.chain(100266..=100275)
.collect();
all().difference(&rendered).copied().collect()
}
PretrainedVocab::Llama2 | PretrainedVocab::CodeLlama => all(),
PretrainedVocab::Gemma2 | PretrainedVocab::Gemma3 | PretrainedVocab::Gemma4 => all(),
PretrainedVocab::ModernBert => [
1,
50279,
50280,
MODERNBERT_CLS,
MODERNBERT_SEP,
MODERNBERT_PAD,
50284,
]
.into_iter()
.collect(),
PretrainedVocab::KimiK2 | PretrainedVocab::KimiK3 => all(),
PretrainedVocab::WhisperV1 | PretrainedVocab::WhisperV2 | PretrainedVocab::WhisperV3 => {
let variant = match vocab {
PretrainedVocab::WhisperV2 => WhisperVariant::V2Multilingual,
PretrainedVocab::WhisperV3 => WhisperVariant::V3Multilingual,
_ => WhisperVariant::V1Multilingual,
};
let first_timestamp = variant.first_timestamp_token_id();
special
.values()
.copied()
.filter(|&id| id < first_timestamp)
.collect()
}
}
}
pub fn special_tokens(vocab: PretrainedVocab) -> FxHashMap<String, u32> {
match vocab {
PretrainedVocab::Cl100kBase => cl100k_base_special_tokens(),
PretrainedVocab::O200kBase => o200k_base_special_tokens(),
PretrainedVocab::Llama3 => llama3_special_tokens(),
PretrainedVocab::DeepseekV3 => deepseek_v3_special_tokens(),
PretrainedVocab::Qwen3 => qwen3_special_tokens(),
PretrainedVocab::Glm4 => glm4_special_tokens(),
PretrainedVocab::GptOss => gpt_oss_special_tokens(),
PretrainedVocab::Phi4 => phi4_special_tokens(),
PretrainedVocab::Olmo2 => olmo2_special_tokens(),
PretrainedVocab::Llama2 => llama2_special_tokens(),
PretrainedVocab::CodeLlama => codellama_special_tokens(),
PretrainedVocab::ModernBert => modernbert_special_tokens(),
PretrainedVocab::Gemma2 => gemma_special_tokens(GEMMA2_BASE_VOCAB_SIZE),
PretrainedVocab::Gemma3 => gemma_special_tokens(GEMMA3_BASE_VOCAB_SIZE),
PretrainedVocab::Gemma4 => gemma4_special_tokens(),
PretrainedVocab::KimiK2 => kimi_k2_special_tokens(),
PretrainedVocab::KimiK3 => kimi_k3_special_tokens(),
PretrainedVocab::MistralV1 => mistral_v1_special_tokens(),
PretrainedVocab::MistralV2 => mistral_v2_special_tokens(),
PretrainedVocab::MistralV3 => mistral_v3_special_tokens(),
PretrainedVocab::WhisperV1 => whisper_special_tokens(WhisperVariant::V1Multilingual),
PretrainedVocab::WhisperV2 => whisper_special_tokens(WhisperVariant::V2Multilingual),
PretrainedVocab::WhisperV3 => whisper_special_tokens(WhisperVariant::V3Multilingual),
}
}
const CL100K_BASE_BASE_VOCAB_SIZE: u32 = 100277;
const CL100K_RANK_COUNT: u32 = 100256;
const O200K_BASE_BASE_VOCAB_SIZE: u32 = 200019;
pub fn cl100k_base_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|endoftext|>".to_string(), 100257);
special.insert("<|fim_prefix|>".to_string(), 100258);
special.insert("<|fim_middle|>".to_string(), 100259);
special.insert("<|fim_suffix|>".to_string(), 100260);
special.insert(
"<|endofprompt|>".to_string(),
CL100K_BASE_BASE_VOCAB_SIZE - 1,
);
insert_agent_tokens(&mut special, CL100K_BASE_BASE_VOCAB_SIZE);
special
}
pub fn o200k_base_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|endoftext|>".to_string(), 199999);
special.insert(
"<|endofprompt|>".to_string(),
O200K_BASE_BASE_VOCAB_SIZE - 1,
);
insert_agent_tokens(&mut special, O200K_BASE_BASE_VOCAB_SIZE);
special
}
const LLAMA3_BASE_VOCAB_SIZE: u32 = 128256;
pub fn llama3_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|begin_of_text|>".to_string(), 128000);
special.insert("<|end_of_text|>".to_string(), 128001);
special.insert("<|reserved_special_token_0|>".to_string(), 128002);
special.insert("<|reserved_special_token_1|>".to_string(), 128003);
special.insert("<|finetune_right_pad_id|>".to_string(), 128004);
special.insert("<|step_id|>".to_string(), 128005);
special.insert("<|start_header_id|>".to_string(), 128006);
special.insert("<|end_header_id|>".to_string(), 128007);
special.insert("<|eom_id|>".to_string(), 128008);
special.insert("<|eot_id|>".to_string(), 128009);
special.insert("<|python_tag|>".to_string(), 128010);
special.insert("<|image|>".to_string(), LLAMA3_BASE_VOCAB_SIZE);
special.insert("<|/image|>".to_string(), LLAMA3_BASE_VOCAB_SIZE + 1);
special.insert("<|audio|>".to_string(), LLAMA3_BASE_VOCAB_SIZE + 2);
special.insert("<|/audio|>".to_string(), LLAMA3_BASE_VOCAB_SIZE + 3);
special.insert("<|video|>".to_string(), LLAMA3_BASE_VOCAB_SIZE + 4);
special.insert("<|/video|>".to_string(), LLAMA3_BASE_VOCAB_SIZE + 5);
insert_agent_tokens_llama3(&mut special, 128300);
special
}
const DEEPSEEK_V3_BASE_VOCAB_SIZE: u32 = 128815;
pub fn deepseek_v3_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|begin▁of▁sentence|>".to_string(), 0);
special.insert("<|end▁of▁sentence|>".to_string(), 1);
special.insert("<|▁pad▁|>".to_string(), 2);
special.insert("<think>".to_string(), 128798);
special.insert("</think>".to_string(), 128799);
special.insert("<|fim▁hole|>".to_string(), 128800);
special.insert("<|fim▁begin|>".to_string(), 128801);
special.insert("<|fim▁end|>".to_string(), 128802);
special.insert("<|User|>".to_string(), 128803);
special.insert("<|Assistant|>".to_string(), 128804);
special.insert("<|EOT|>".to_string(), 128805);
special.insert("<|tool▁calls▁begin|>".to_string(), 128806);
special.insert("<|tool▁calls▁end|>".to_string(), 128807);
special.insert("<|tool▁call▁begin|>".to_string(), 128808);
special.insert("<|tool▁call▁end|>".to_string(), 128809);
special.insert("<|tool▁outputs▁begin|>".to_string(), 128810);
special.insert("<|tool▁outputs▁end|>".to_string(), 128811);
special.insert("<|tool▁output▁begin|>".to_string(), 128812);
special.insert("<|tool▁output▁end|>".to_string(), 128813);
special.insert(
"<|tool▁sep|>".to_string(),
DEEPSEEK_V3_BASE_VOCAB_SIZE - 1,
);
insert_agent_tokens(&mut special, 128900);
special
}
const QWEN3_BASE_VOCAB_SIZE: u32 = 151669;
pub fn qwen3_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|endoftext|>".to_string(), 151643);
special.insert("<|im_start|>".to_string(), 151644);
special.insert("<|im_end|>".to_string(), 151645);
special.insert("<|object_ref_start|>".to_string(), 151646);
special.insert("<|object_ref_end|>".to_string(), 151647);
special.insert("<|box_start|>".to_string(), 151648);
special.insert("<|box_end|>".to_string(), 151649);
special.insert("<|quad_start|>".to_string(), 151650);
special.insert("<|quad_end|>".to_string(), 151651);
special.insert("<|vision_start|>".to_string(), 151652);
special.insert("<|vision_end|>".to_string(), 151653);
special.insert("<|vision_pad|>".to_string(), 151654);
special.insert("<|image_pad|>".to_string(), 151655);
special.insert("<|video_pad|>".to_string(), 151656);
special.insert("<tool_call>".to_string(), 151657);
special.insert("</tool_call>".to_string(), 151658);
special.insert("<|fim_prefix|>".to_string(), 151659);
special.insert("<|fim_middle|>".to_string(), 151660);
special.insert("<|fim_suffix|>".to_string(), 151661);
special.insert("<|fim_pad|>".to_string(), 151662);
special.insert("<|repo_name|>".to_string(), 151663);
special.insert("<|file_sep|>".to_string(), 151664);
special.insert("<tool_response>".to_string(), 151665);
special.insert("</tool_response>".to_string(), 151666);
special.insert("<think>".to_string(), 151667);
special.insert("</think>".to_string(), QWEN3_BASE_VOCAB_SIZE - 1);
insert_agent_tokens(&mut special, QWEN3_BASE_VOCAB_SIZE);
special
}
const GLM4_BASE_VOCAB_SIZE: u32 = 151365;
pub fn glm4_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|endoftext|>".to_string(), 151329);
special.insert("[MASK]".to_string(), 151330);
special.insert("[gMASK]".to_string(), 151331);
special.insert("[sMASK]".to_string(), 151332);
special.insert("<sop>".to_string(), 151333);
special.insert("<eop>".to_string(), 151334);
special.insert("<|system|>".to_string(), 151335);
special.insert("<|user|>".to_string(), 151336);
special.insert("<|assistant|>".to_string(), 151337);
special.insert("<|observation|>".to_string(), 151338);
special.insert("<|begin_of_image|>".to_string(), 151339);
special.insert("<|end_of_image|>".to_string(), 151340);
special.insert("<|begin_of_video|>".to_string(), 151341);
special.insert("<|end_of_video|>".to_string(), 151342);
special.insert("<|begin_of_audio|>".to_string(), 151343);
special.insert("<|end_of_audio|>".to_string(), 151344);
special.insert("<|begin_of_transcription|>".to_string(), 151345);
special.insert("<|end_of_transcription|>".to_string(), 151346);
special.insert("<|code_prefix|>".to_string(), 151347);
special.insert("<|code_middle|>".to_string(), 151348);
special.insert("<|code_suffix|>".to_string(), 151349);
special.insert("<think>".to_string(), 151350);
special.insert("</think>".to_string(), 151351);
special.insert("<tool_call>".to_string(), 151352);
special.insert("</tool_call>".to_string(), 151353);
special.insert("<tool_response>".to_string(), 151354);
special.insert("</tool_response>".to_string(), 151355);
special.insert("<arg_key>".to_string(), 151356);
special.insert("</arg_key>".to_string(), 151357);
special.insert("<arg_value>".to_string(), 151358);
special.insert("</arg_value>".to_string(), 151359);
special.insert("/nothink".to_string(), 151360);
special.insert("<|begin_of_box|>".to_string(), 151361);
special.insert("<|end_of_box|>".to_string(), 151362);
special.insert("<|image|>".to_string(), 151363);
special.insert("<|video|>".to_string(), GLM4_BASE_VOCAB_SIZE - 1);
insert_agent_tokens(&mut special, GLM4_BASE_VOCAB_SIZE);
special
}
pub fn gpt_oss_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<|startoftext|>".to_string(), 199998);
special.insert("<|endoftext|>".to_string(), 199999);
special.insert("<|reserved_200000|>".to_string(), 200000);
special.insert("<|reserved_200001|>".to_string(), 200001);
special.insert("<|return|>".to_string(), 200002);
special.insert("<|constrain|>".to_string(), 200003);
special.insert("<|reserved_200004|>".to_string(), 200004);
special.insert("<|channel|>".to_string(), 200005);
special.insert("<|start|>".to_string(), 200006);
special.insert("<|end|>".to_string(), 200007);
special.insert("<|message|>".to_string(), 200008);
special.insert("<|reserved_200009|>".to_string(), 200009);
special.insert("<|reserved_200010|>".to_string(), 200010);
special.insert("<|reserved_200011|>".to_string(), 200011);
special.insert("<|call|>".to_string(), 200012);
special.insert("<|reserved_200013|>".to_string(), 200013);
special.insert("<|reserved_200014|>".to_string(), 200014);
special.insert("<|reserved_200015|>".to_string(), 200015);
special.insert("<|reserved_200016|>".to_string(), 200016);
special.insert("<|reserved_200017|>".to_string(), 200017);
special.insert(
"<|endofprompt|>".to_string(),
O200K_BASE_BASE_VOCAB_SIZE - 1,
);
insert_agent_tokens(&mut special, O200K_BASE_BASE_VOCAB_SIZE);
special
}
const KIMI_BASE_VOCAB_SIZE: u32 = 163840;
const KIMI_SPECIAL_BASE: u32 = 163584;
fn insert_kimi_specials(special: &mut FxHashMap<String, u32>, named: &[(&str, u32)]) {
for (name, id) in named {
special.insert((*name).to_string(), *id);
}
let named_ids: FxHashSet<u32> = named.iter().map(|(_, id)| *id).collect();
for id in KIMI_SPECIAL_BASE..KIMI_BASE_VOCAB_SIZE {
if named_ids.contains(&id) {
continue;
}
special.insert(format!("<|reserved_token_{id}|>"), id);
}
insert_agent_tokens(special, KIMI_BASE_VOCAB_SIZE);
}
pub fn kimi_k2_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
insert_kimi_specials(
&mut special,
&[
("[BOS]", 163584),
("[EOS]", 163585),
("<|im_end|>", 163586),
("<|im_user|>", 163587),
("<|im_assistant|>", 163588),
("<|start_header_id|>", 163590),
("<|end_header_id|>", 163591),
("[EOT]", 163593),
("<|im_system|>", 163594),
("<|tool_calls_section_begin|>", 163595),
("<|tool_calls_section_end|>", 163596),
("<|tool_call_begin|>", 163597),
("<|tool_call_argument_begin|>", 163598),
("<|tool_call_end|>", 163599),
("<|im_middle|>", 163601),
("<|media_begin|>", 163602),
("<|media_content|>", 163603),
("<|media_end|>", 163604),
("<|media_pad|>", 163605),
("<think>", 163606),
("</think>", 163607),
("[UNK]", 163838),
("[PAD]", 163839),
],
);
special
}
pub fn kimi_k3_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
insert_kimi_specials(
&mut special,
&[
("[BOS]", 163584),
("[EOS]", 163585),
("<|end_of_msg|>", 163586),
("<|open|>", 163587),
("<|close|>", 163588),
("<|sep|>", 163589),
("[start_header_id]", 163590),
("[end_header_id]", 163591),
("[EOT]", 163593),
("<|media_begin|>", 163602),
("<|media_content|>", 163603),
("<|media_end|>", 163604),
("<|media_pad|>", 163605),
("<osagent_mode>", 163649),
("[UNK]", 163838),
("[PAD]", 163839),
],
);
special
}
const MISTRAL_V1_BASE_VOCAB_SIZE: u32 = 32000;
const MISTRAL_V2_BASE_VOCAB_SIZE: u32 = 32768;
const MISTRAL_V3_BASE_VOCAB_SIZE: u32 = 131072;
pub fn mistral_v1_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<unk>".to_string(), 0);
special.insert("<s>".to_string(), 1);
special.insert("</s>".to_string(), 2);
insert_agent_tokens(&mut special, MISTRAL_V1_BASE_VOCAB_SIZE);
special
}
pub fn mistral_v2_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<unk>".to_string(), 0);
special.insert("<s>".to_string(), 1);
special.insert("</s>".to_string(), 2);
special.insert("[INST]".to_string(), 3);
special.insert("[/INST]".to_string(), 4);
special.insert("[TOOL_CALLS]".to_string(), 5);
special.insert("[AVAILABLE_TOOLS]".to_string(), 6);
special.insert("[/AVAILABLE_TOOLS]".to_string(), 7);
special.insert("[TOOL_RESULTS]".to_string(), 8);
special.insert("[/TOOL_RESULTS]".to_string(), 9);
insert_agent_tokens(&mut special, MISTRAL_V2_BASE_VOCAB_SIZE);
special
}
pub fn mistral_v3_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<unk>".to_string(), 0);
special.insert("<s>".to_string(), 1);
special.insert("</s>".to_string(), 2);
special.insert("[INST]".to_string(), 3);
special.insert("[/INST]".to_string(), 4);
special.insert("[AVAILABLE_TOOLS]".to_string(), 5);
special.insert("[/AVAILABLE_TOOLS]".to_string(), 6);
special.insert("[TOOL_RESULTS]".to_string(), 7);
special.insert("[/TOOL_RESULTS]".to_string(), 8);
special.insert("[TOOL_CALLS]".to_string(), 9);
insert_agent_tokens(&mut special, MISTRAL_V3_BASE_VOCAB_SIZE);
special
}
const PHI4_BASE_VOCAB_SIZE: u32 = 100352;
const OLMO2_BASE_VOCAB_SIZE: u32 = 100278;
pub fn phi4_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
for (name, id) in [
("<|endoftext|>", 100257),
("<|fim_prefix|>", 100258),
("<|fim_middle|>", 100259),
("<|fim_suffix|>", 100260),
("<|im_start|>", 100264),
("<|im_end|>", 100265),
("<|im_sep|>", 100266),
("<|endofprompt|>", 100276),
] {
special.insert(name.to_string(), id);
}
let mut dummy = 0;
for id in CL100K_RANK_COUNT..PHI4_BASE_VOCAB_SIZE {
if special.values().any(|&taken| taken == id) {
continue;
}
special.insert(format!("<|dummy_{dummy}|>"), id);
dummy += 1;
}
insert_agent_tokens(&mut special, PHI4_BASE_VOCAB_SIZE);
special
}
pub fn olmo2_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
for (name, id) in [
("<|extra_id_0|>", 100256),
("<|endoftext|>", 100257),
("<|fim_prefix|>", 100258),
("<|fim_middle|>", 100259),
("<|fim_suffix|>", 100260),
("|||PHONE_NUMBER|||", 100261),
("|||EMAIL_ADDRESS|||", 100262),
("|||IP_ADDRESS|||", 100263),
("<|im_start|>", 100264),
("<|im_end|>", 100265),
("<|extra_id_1|>", 100266),
("<|extra_id_2|>", 100267),
("<|extra_id_3|>", 100268),
("<|extra_id_4|>", 100269),
("<|extra_id_5|>", 100270),
("<|extra_id_6|>", 100271),
("<|extra_id_7|>", 100272),
("<|extra_id_8|>", 100273),
("<|extra_id_9|>", 100274),
("<|extra_id_10|>", 100275),
("<|endofprompt|>", 100276),
("<|pad|>", 100277),
] {
special.insert(name.to_string(), id);
}
insert_agent_tokens(&mut special, OLMO2_BASE_VOCAB_SIZE);
special
}
const LLAMA2_BASE_VOCAB_SIZE: u32 = 32000;
const CODELLAMA_BASE_VOCAB_SIZE: u32 = 32016;
pub fn llama2_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<unk>".to_string(), 0);
special.insert("<s>".to_string(), 1);
special.insert("</s>".to_string(), 2);
insert_agent_tokens(&mut special, LLAMA2_BASE_VOCAB_SIZE);
special
}
pub fn codellama_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<unk>".to_string(), 0);
special.insert("<s>".to_string(), 1);
special.insert("</s>".to_string(), 2);
insert_agent_tokens(&mut special, CODELLAMA_BASE_VOCAB_SIZE);
special
}
const GEMMA2_BASE_VOCAB_SIZE: u32 = 256000;
const GEMMA3_BASE_VOCAB_SIZE: u32 = 262144;
const GEMMA4_BASE_VOCAB_SIZE: u32 = 262144;
fn gemma4_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<pad>".to_string(), 0);
special.insert("<eos>".to_string(), 1);
special.insert("<bos>".to_string(), 2);
special.insert("<unk>".to_string(), 3);
insert_agent_tokens(&mut special, GEMMA4_BASE_VOCAB_SIZE);
special
}
fn gemma_special_tokens(base: u32) -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("<pad>".to_string(), 0);
special.insert("<eos>".to_string(), 1);
special.insert("<bos>".to_string(), 2);
special.insert("<unk>".to_string(), 3);
insert_agent_tokens(&mut special, base);
special
}
const MODERNBERT_BASE_VOCAB_SIZE: u32 = 50368;
const MODERNBERT_CLS: u32 = 50281;
const MODERNBERT_SEP: u32 = 50282;
const MODERNBERT_PAD: u32 = 50283;
const MODERNBERT_MASK: u32 = 50284;
pub fn modernbert_special_tokens() -> FxHashMap<String, u32> {
let mut special = FxHashMap::default();
special.insert("|||IP_ADDRESS|||".to_string(), 0);
special.insert("<|padding|>".to_string(), 1);
for (offset, spaces) in (2..=24).rev().enumerate() {
special.insert(" ".repeat(spaces), 50254 + offset as u32);
}
special.insert("|||EMAIL_ADDRESS|||".to_string(), 50277);
special.insert("|||PHONE_NUMBER|||".to_string(), 50278);
special.insert("<|endoftext|>".to_string(), 50279);
special.insert("[UNK]".to_string(), 50280);
special.insert("[CLS]".to_string(), MODERNBERT_CLS);
special.insert("[SEP]".to_string(), MODERNBERT_SEP);
special.insert("[PAD]".to_string(), MODERNBERT_PAD);
special.insert("[MASK]".to_string(), MODERNBERT_MASK);
for n in 0..83u32 {
special.insert(format!("[unused{n}]"), 50285 + n);
}
insert_agent_tokens(&mut special, MODERNBERT_BASE_VOCAB_SIZE);
special
}
const AGENT_TOKENS: [(&str, u32); 54] = [
("<|system|>", 0),
("<|user|>", 1),
("<|assistant|>", 2),
("<|im_start|>", 3),
("<|im_end|>", 4),
("<|think|>", 5),
("<|/think|>", 6),
("<|plan|>", 7),
("<|/plan|>", 8),
("<|step|>", 9),
("<|/step|>", 10),
("<|act|>", 11),
("<|/act|>", 12),
("<|observe|>", 13),
("<|/observe|>", 14),
("<|function|>", 15),
("<|/function|>", 16),
("<|result|>", 17),
("<|/result|>", 18),
("<|error|>", 19),
("<|/error|>", 20),
("<|code|>", 21),
("<|/code|>", 22),
("<|output|>", 23),
("<|/output|>", 24),
("<|lang|>", 25),
("<|/lang|>", 26),
("<|context|>", 27),
("<|/context|>", 28),
("<|quote|>", 29),
("<|/quote|>", 30),
("<|cite|>", 31),
("<|/cite|>", 32),
("<|source|>", 33),
("<|/source|>", 34),
("<|memory|>", 35),
("<|/memory|>", 36),
("<|recall|>", 37),
("<|/recall|>", 38),
("<|pad|>", 39),
("<|stop|>", 40),
("<|sep|>", 41),
("<|image|>", 42),
("<|/image|>", 43),
("<|audio|>", 44),
("<|/audio|>", 45),
("<|video|>", 46),
("<|/video|>", 47),
("<|title|>", 48),
("<|/title|>", 49),
("<|section|>", 50),
("<|/section|>", 51),
("<|summary|>", 52),
("<|/summary|>", 53),
];
fn insert_agent_tokens(special: &mut FxHashMap<String, u32>, base: u32) {
insert_agent_tokens_except(special, base, &[]);
}
fn insert_agent_tokens_llama3(special: &mut FxHashMap<String, u32>, base: u32) {
insert_agent_tokens_except(special, base, &[42, 43, 44, 45, 46, 47]);
}
fn insert_agent_tokens_except(special: &mut FxHashMap<String, u32>, base: u32, skip: &[u32]) {
for (name, offset) in AGENT_TOKENS {
if skip.contains(&offset) {
continue;
}
special.entry(name.to_string()).or_insert(base + offset);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::policy::SpecialDecode;
use crate::core::Tokenize;
fn bpe(tokenizer: AnyTokenizer) -> Tokenizer {
match tokenizer.into_backend() {
Backend::Bpe(t) => t,
_ => panic!("this vocabulary does not load as byte-pair encoding"),
}
}
#[cfg(feature = "vocab-mistral")]
fn spm_piece_id(vocab_data: &[u8], piece: &str) -> u32 {
let pieces = load_spm_vocab(vocab_data).expect("vocabulary loads").pieces;
let id = pieces
.iter()
.position(|p| p == piece)
.unwrap_or_else(|| panic!("{piece:?} is not in the vocabulary"));
id as u32
}
#[test]
fn pretrained_special_decode_ids_follow_the_reference() {
let dropped: [(&str, u32); 8] = [
("mistral_v2", 3), ("mistral_v2", 4), ("llama3", 128000), ("llama3", 128009), ("deepseek_v3", 0), ("deepseek_v3", 128805), ("whisper", 50258), ("whisper", 50259), ];
for (name, id) in dropped {
let tokenizer = from_pretrained(name).expect("bundled vocabulary loads");
assert_eq!(
tokenizer.decode(&[id]).expect("a skipped id decodes"),
"",
"{name}: id {id} must render as nothing, as its reference does"
);
}
let rendered: [(&str, u32, &str); 4] = [
("deepseek_v3", 128803, "<|User|>"),
("deepseek_v3", 128804, "<|Assistant|>"),
("cl100k_base", 100257, "<|endoftext|>"),
("o200k_base", 199999, "<|endoftext|>"),
];
for (name, id, text) in rendered {
let tokenizer = from_pretrained(name).expect("bundled vocabulary loads");
assert_eq!(
tokenizer.decode(&[id]).expect("a rendered id decodes"),
text,
"{name}: id {id} must still render, as its reference does"
);
}
}
#[test]
fn mistral_v3_drops_its_markers_by_family_consistency_not_by_measurement() {
let tokenizer = from_pretrained("mistral_v3").expect("bundled vocabulary loads");
for (id, spelling) in [
(3u32, "[INST]"),
(4, "[/INST]"),
(1, "<s>"),
(131072, "<|system|>"),
] {
assert_eq!(
tokenizer.decode(&[id]).expect("a skipped id decodes"),
"",
"mistral_v3: id {id} ({spelling}) must render as nothing, as V1/V2's do"
);
assert_eq!(
tokenizer
.decode_with(&[id], SpecialDecode::Render)
.expect("a skipped id renders on request"),
spelling,
"mistral_v3: id {id} must still be reachable through Render"
);
}
}
#[test]
fn pretrained_markers_are_still_reachable_with_specials_rendered() {
for (name, id, spelling) in [
("mistral_v2", 3u32, "[INST]"),
("mistral_v2", 4, "[/INST]"),
("llama3", 128000, "<|begin_of_text|>"),
("llama3", 128009, "<|eot_id|>"),
("deepseek_v3", 0, "<|begin▁of▁sentence|>"),
("deepseek_v3", 128805, "<|EOT|>"),
("whisper", 50258, "<|startoftranscript|>"),
] {
let tokenizer = from_pretrained(name).expect("bundled vocabulary loads");
assert_eq!(
tokenizer.decode(&[id]).expect("a skipped id decodes"),
"",
"{name}: id {id} is dropped by default"
);
assert_eq!(
tokenizer
.decode_with(&[id], SpecialDecode::Render)
.expect("a rendered id decodes"),
spelling,
"{name}: id {id} must be reachable with specials rendered"
);
}
}
#[test]
fn rendering_specials_restores_the_marker_and_nothing_else() {
let spm = from_pretrained("mistral_v2").expect("bundled vocabulary loads");
let ids = [3, 7080, 29477, 2294, 4];
assert_eq!(spm.decode(&ids).expect("decodes"), "hello world");
assert_eq!(
spm.decode_with(&ids, SpecialDecode::Render)
.expect("decodes"),
"[INST] hello world[/INST]"
);
let bpe = from_pretrained("llama3").expect("bundled vocabulary loads");
let mut ids = vec![128000];
ids.extend(bpe.encode("hello"));
ids.push(128009);
assert_eq!(bpe.decode(&ids).expect("decodes"), "hello");
assert_eq!(
bpe.decode_with(&ids, SpecialDecode::Render)
.expect("decodes"),
"<|begin_of_text|>hello<|eot_id|>"
);
}
#[test]
fn whisper_timestamp_tokens_are_not_decode_skipped() {
for (name, variant) in [
("whisper_v1", WhisperVariant::V1Multilingual),
("whisper_v2", WhisperVariant::V2Multilingual),
("whisper_v3", WhisperVariant::V3Multilingual),
] {
let tokenizer = from_pretrained(name).expect("bundled vocabulary loads");
let first = variant.first_timestamp_token_id();
assert_eq!(
tokenizer.decode(&[first]).expect("a timestamp id decodes"),
"<|0.00|>",
"{name}: the first timestamp token must still render"
);
assert_eq!(
tokenizer
.decode(&[first + 1500])
.expect("a timestamp id decodes"),
"<|30.00|>",
"{name}: the last timestamp token must still render"
);
assert_eq!(
tokenizer
.decode(&[variant.notimestamps_token_id()])
.expect("a control id decodes"),
"",
"{name}: `<|notimestamps|>` is a control token and is dropped"
);
}
}
#[test]
fn test_from_pretrained_llama3() {
let tokenizer = from_pretrained("llama3").unwrap();
assert!(tokenizer.vocab_size() > 100000);
}
#[test]
fn test_from_pretrained_cl100k() {
let tokenizer = from_pretrained("cl100k_base").unwrap();
assert!(tokenizer.vocab_size() > 90000);
}
#[test]
fn test_from_pretrained_whisper_variants() {
for (name, variant) in [
("whisper_v1", WhisperVariant::V1Multilingual),
("whisper", WhisperVariant::V2Multilingual), ("whisper_v2", WhisperVariant::V2Multilingual),
("whisper-v3", WhisperVariant::V3Multilingual),
] {
let tok = from_pretrained(name).unwrap_or_else(|e| panic!("{name}: {e}"));
assert_eq!(tok.vocab_size(), variant.vocab_size(), "{name} vocab_size");
assert_eq!(bpe(tok).encoder().len(), 50257, "{name} base vocab size");
}
}
#[test]
fn test_policy_is_passthrough_but_knows_its_specials() {
let tok = from_pretrained("llama3").unwrap();
assert_eq!(tok.eos_token_id(), Some(128001));
assert!(tok.is_eos(128001));
assert_eq!(tok.special_token_id("<|eot_id|>"), Some(128009));
let text = "Hello, world!";
assert_eq!(tok.encode(text), tok.encode_raw(text));
}
#[test]
fn test_encode_batch_matches_individual() {
let tok = from_pretrained("llama3").unwrap();
let texts = ["Hello, world!", "", "<|eot_id|>after", "你好世界"];
let batch = tok.encode_batch(&texts);
assert_eq!(batch.len(), texts.len());
for (got, text) in batch.iter().zip(texts) {
assert_eq!(got, &tok.encode(text), "batch mismatch for {text:?}");
}
assert!(batch[2].starts_with(&[128009]));
}
#[test]
fn test_whisper_special_tokens_wired() {
let tok = from_pretrained("whisper_v3").unwrap();
assert_eq!(tok.encode("<|en|>"), vec![50259]);
assert_eq!(
tok.encode("<|transcribe|>"),
vec![WhisperVariant::V3Multilingual.transcribe_token_id()]
);
assert_eq!(tok.encode("<|yue|>"), vec![50259 + 99]);
}
#[test]
fn test_whisper_roundtrip() {
let tok = from_pretrained("whisper").unwrap();
let text = "Hello, world! 123 héllo";
assert_eq!(tok.decode(&tok.encode(text)).unwrap(), text);
}
#[test]
fn test_whisper_name_mapping() {
assert_eq!(
PretrainedVocab::from_name("whisper"),
Some(PretrainedVocab::WhisperV2)
);
assert_eq!(
PretrainedVocab::from_name("whisper-large-v3"),
Some(PretrainedVocab::WhisperV3)
);
assert_eq!(PretrainedVocab::from_name("whisper.en"), None);
}
#[test]
fn test_eos_token_ids() {
assert_eq!(eos_token_id(PretrainedVocab::Cl100kBase), 100257);
assert_eq!(eos_token_id(PretrainedVocab::O200kBase), 199999);
assert_eq!(eos_token_id(PretrainedVocab::Llama3), 128001);
assert_eq!(eos_token_id(PretrainedVocab::DeepseekV3), 1);
assert_eq!(eos_token_id(PretrainedVocab::MistralV1), 2);
}
#[test]
fn test_vocab_from_name() {
assert_eq!(
PretrainedVocab::from_name("llama3"),
Some(PretrainedVocab::Llama3)
);
assert_eq!(
PretrainedVocab::from_name("llama3.1"),
Some(PretrainedVocab::Llama3)
);
assert_eq!(
PretrainedVocab::from_name("deepseek_v3"),
Some(PretrainedVocab::DeepseekV3)
);
assert_eq!(
PretrainedVocab::from_name("mistral"),
Some(PretrainedVocab::MistralV1)
);
assert_eq!(PretrainedVocab::from_name("unknown"), None);
}
#[test]
fn test_from_pretrained_mistral() {
let tokenizer = from_pretrained("mistral").unwrap();
assert!(tokenizer.vocab_size() >= 31000);
}
#[test]
fn test_mistral_encode_decode() {
let tokenizer = from_pretrained("mistral").unwrap();
let text = "Hello, world!";
let tokens = tokenizer.encode(text);
assert!(!tokens.is_empty());
let decoded = tokenizer.decode(&tokens).unwrap();
assert_eq!(decoded, text, "Encoding should be reversible");
}
#[test]
#[cfg(feature = "vocab-mistral")]
fn test_mistral_never_shatters_the_word_boundary_marker() {
for (name, data) in [
("mistral", MISTRAL_SPM_VOCAB),
("mistral_v2", MISTRAL_V2_SPM_VOCAB),
] {
let shattered = [
spm_piece_id(data, "<0xE2>"),
spm_piece_id(data, "<0x96>"),
spm_piece_id(data, "<0x81>"),
];
let tokenizer = from_pretrained(name).unwrap();
let ids = tokenizer.encode("the sourdough starter rose overnight");
assert!(
!ids.windows(3).any(|w| w == shattered.as_slice()),
"{name}: word boundary shattered into byte tokens {shattered:?} in {ids:?}"
);
}
}
#[test]
#[cfg(feature = "vocab-mistral")]
fn test_mistral_reaches_whole_word_pieces() {
let the = spm_piece_id(MISTRAL_SPM_VOCAB, "▁the");
let sour = spm_piece_id(MISTRAL_SPM_VOCAB, "▁sour");
let ids = from_pretrained("mistral").unwrap().encode("the sourdough");
assert!(ids.contains(&the), "▁the ({the}) missing from {ids:?}");
assert!(ids.contains(&sour), "▁sour ({sour}) missing from {ids:?}");
}
#[test]
fn test_mistral_round_trips_a_sentence() {
let tokenizer = from_pretrained("mistral").unwrap();
let text = "The quick brown fox jumps over the lazy dog.";
let decoded = tokenizer.decode(&tokenizer.encode(text)).unwrap();
assert_eq!(decoded, text);
}
#[test]
fn test_base_vocab_size_matches_reference() {
assert_eq!(base_vocab_size(PretrainedVocab::Cl100kBase), 100277); assert_eq!(base_vocab_size(PretrainedVocab::O200kBase), 200019); assert_eq!(base_vocab_size(PretrainedVocab::Llama3), 128256); assert_eq!(base_vocab_size(PretrainedVocab::DeepseekV3), 128815); assert_eq!(base_vocab_size(PretrainedVocab::MistralV1), 32000); assert_eq!(base_vocab_size(PretrainedVocab::MistralV2), 32768); assert_eq!(base_vocab_size(PretrainedVocab::MistralV3), 131072); assert_eq!(base_vocab_size(PretrainedVocab::Phi4), 100352); assert_eq!(base_vocab_size(PretrainedVocab::Olmo2), 100278); assert_eq!(base_vocab_size(PretrainedVocab::Llama2), 32000); assert_eq!(base_vocab_size(PretrainedVocab::CodeLlama), 32016); assert_eq!(base_vocab_size(PretrainedVocab::ModernBert), 50368); assert_eq!(base_vocab_size(PretrainedVocab::Gemma2), 256000); assert_eq!(base_vocab_size(PretrainedVocab::Gemma3), 262144); assert_eq!(base_vocab_size(PretrainedVocab::Gemma4), 262144); assert_eq!(
base_vocab_size(PretrainedVocab::WhisperV1),
WhisperVariant::V1Multilingual.vocab_size() as u32
);
assert_eq!(
base_vocab_size(PretrainedVocab::WhisperV2),
WhisperVariant::V2Multilingual.vocab_size() as u32
);
assert_eq!(
base_vocab_size(PretrainedVocab::WhisperV3),
WhisperVariant::V3Multilingual.vocab_size() as u32
);
}
#[test]
fn test_base_vocab_size_never_exceeds_extended_vocab_size() {
for (name, vocab) in [
("cl100k_base", PretrainedVocab::Cl100kBase),
("o200k_base", PretrainedVocab::O200kBase),
("llama3", PretrainedVocab::Llama3),
("deepseek_v3", PretrainedVocab::DeepseekV3),
("mistral_v1", PretrainedVocab::MistralV1),
("mistral_v2", PretrainedVocab::MistralV2),
("mistral_v3", PretrainedVocab::MistralV3),
("phi4", PretrainedVocab::Phi4),
("olmo2", PretrainedVocab::Olmo2),
("llama2", PretrainedVocab::Llama2),
("codellama", PretrainedVocab::CodeLlama),
("modernbert", PretrainedVocab::ModernBert),
("whisper_v1", PretrainedVocab::WhisperV1),
("whisper_v2", PretrainedVocab::WhisperV2),
("whisper_v3", PretrainedVocab::WhisperV3),
] {
let extended = from_vocab(vocab).unwrap().vocab_size() as u32;
let base = base_vocab_size(vocab);
assert!(
base <= extended,
"{name}: base_vocab_size {base} exceeds extended vocab_size {extended}"
);
}
}
#[test]
fn test_no_agent_token_id_below_base_vocab_size() {
for (name, vocab) in [
("cl100k_base", PretrainedVocab::Cl100kBase),
("o200k_base", PretrainedVocab::O200kBase),
("llama3", PretrainedVocab::Llama3),
("deepseek_v3", PretrainedVocab::DeepseekV3),
("mistral_v1", PretrainedVocab::MistralV1),
("mistral_v2", PretrainedVocab::MistralV2),
("mistral_v3", PretrainedVocab::MistralV3),
] {
let base = base_vocab_size(vocab);
for name_and_id in agent_token_ids_in(vocab) {
let (token, id) = name_and_id;
assert!(
id >= base,
"{name}: agent token {token:?} has id {id}, below base_vocab_size {base}"
);
}
}
}
const AGENT_TOKEN_NAMES: [&str; 54] = [
"<|system|>",
"<|user|>",
"<|assistant|>",
"<|im_start|>",
"<|im_end|>",
"<|think|>",
"<|/think|>",
"<|plan|>",
"<|/plan|>",
"<|step|>",
"<|/step|>",
"<|act|>",
"<|/act|>",
"<|observe|>",
"<|/observe|>",
"<|function|>",
"<|/function|>",
"<|result|>",
"<|/result|>",
"<|error|>",
"<|/error|>",
"<|code|>",
"<|/code|>",
"<|output|>",
"<|/output|>",
"<|lang|>",
"<|/lang|>",
"<|context|>",
"<|/context|>",
"<|quote|>",
"<|/quote|>",
"<|cite|>",
"<|/cite|>",
"<|source|>",
"<|/source|>",
"<|memory|>",
"<|/memory|>",
"<|recall|>",
"<|/recall|>",
"<|pad|>",
"<|stop|>",
"<|sep|>",
"<|image|>",
"<|/image|>",
"<|audio|>",
"<|/audio|>",
"<|video|>",
"<|/video|>",
"<|title|>",
"<|/title|>",
"<|section|>",
"<|/section|>",
"<|summary|>",
"<|/summary|>",
];
#[test]
fn phi4_and_olmo2_read_cl100k_base_ranks() {
for (text, want) in [
("def f(): pass", vec![755, 282, 4658, 262, 1522]),
("a \n \n \nb", vec![64, 33006, 720, 65]),
(" \n\n x", vec![35033, 220, 865]),
] {
for name in ["cl100k_base", "phi4", "olmo2"] {
let got = from_pretrained(name).unwrap().encode(text);
assert_eq!(got, want, "{name} on {text:?}");
}
}
}
#[test]
fn phi4_markers_eat_their_whitespace_and_olmo2_markers_do_not() {
let text = "x <|endoftext|> y";
assert_eq!(
from_pretrained("phi4").unwrap().encode(text),
[87, 100257, 88]
);
assert_eq!(
from_pretrained("olmo2").unwrap().encode(text),
[87, 220, 100257, 379]
);
assert_eq!(special_tokens(PretrainedVocab::Phi4)["<|dummy_5|>"], 100268);
assert_eq!(
special_tokens(PretrainedVocab::Olmo2)["|||IP_ADDRESS|||"],
100263
);
}
#[test]
fn llama2_and_code_llama_agree_below_32000() {
let llama2 = from_pretrained("llama2").unwrap();
let codellama = from_pretrained("codellama").unwrap();
for text in ["Hello world", "<s>hi", "def fib(n):\n return n"] {
assert_eq!(llama2.encode(text), codellama.encode(text), "{text:?}");
}
assert_eq!(llama2.encode("Hello world"), [15043, 3186]);
assert_eq!(llama2.encode("<s>hi"), [1, 2918]);
assert_eq!(
from_pretrained("tinyllama").unwrap().encode("Hello world"),
[15043, 3186]
);
}
#[test]
fn modernbert_wraps_in_cls_and_sep_and_takes_indents_whole() {
let tokenizer = from_pretrained("modernbert").unwrap();
assert_eq!(
tokenizer.encode("def f(): pass"),
[50281, 1545, 269, 14850, 50274, 5858, 50282]
);
assert_eq!(
tokenizer.encode("the [MASK] sat"),
[50281, 783, 50284, 2206, 50282]
);
}
#[test]
fn gemma4_reads_its_separate_merge_order() {
let tok = from_pretrained("gemma4").unwrap();
assert_eq!(tok.encode(" field"), [148, 3593]);
assert_eq!(tok.encode("Hello world"), [9259, 1902]);
}
#[test]
fn no_gemma_generation_prepends_a_word_boundary() {
for name in ["gemma2", "gemma3", "gemma4"] {
let tok = from_pretrained(name).unwrap();
let with_space = tok.encode(" hello");
let without = tok.encode("hello");
assert_ne!(
with_space, without,
"{name}: a leading space must still be part of the input"
);
assert!(
!without.is_empty(),
"{name}: encoding produced nothing at all"
);
}
}
#[test]
fn gemma_user_defined_pieces_are_matched_whole() {
for (name, id) in [("gemma2", 191u32), ("gemma3", 190)] {
let tok = from_pretrained(name).unwrap();
assert_eq!(
tok.encode("<blockquote>"),
[id],
"{name}: a user-defined piece was merged instead of matched"
);
}
}
#[cfg(feature = "vocab-gemma4")]
#[test]
fn bundled_gemma4_agrees_with_its_own_json() {
let bundled = from_pretrained("gemma4").unwrap();
for text in [
"Hello world",
" field",
"<blockquote>",
"def f(x):\n return x",
] {
assert!(
!bundled.encode(text).is_empty(),
"{text:?} encoded to nothing"
);
}
assert_eq!(bundled.encode(" field"), [148, 3593]);
assert_eq!(bundled.encode("<blockquote>"), [236820, 37548, 236813]);
assert_eq!(bundled.decode(&[190]).unwrap(), "<blockquote>");
}
#[test]
fn the_bundled_families_resolve_by_every_name_they_claim() {
for (name, want) in [
("phi4", PretrainedVocab::Phi4),
("phi-4", PretrainedVocab::Phi4),
("olmo2", PretrainedVocab::Olmo2),
("olmo-2", PretrainedVocab::Olmo2),
("llama2", PretrainedVocab::Llama2),
("tinyllama", PretrainedVocab::Llama2),
("vicuna", PretrainedVocab::Llama2),
("codellama", PretrainedVocab::CodeLlama),
("modernbert", PretrainedVocab::ModernBert),
("gemma2", PretrainedVocab::Gemma2),
("gemma3", PretrainedVocab::Gemma3),
("embeddinggemma", PretrainedVocab::Gemma3),
("gemma4", PretrainedVocab::Gemma4),
] {
assert_eq!(PretrainedVocab::from_name(name), Some(want), "{name}");
assert!(
PretrainedVocab::supported_names().contains(&name),
"{name} resolves but is not listed in supported_names"
);
}
}
#[test]
fn only_user_defined_pieces_join_the_added_tokens() {
let pieces: Vec<String> = ["<pad>", "<0x41>", "<blockquote>", "▁the"]
.iter()
.map(|s| s.to_string())
.collect();
let flags = [false, false, true, false];
let folded = fold_user_defined(&pieces, &flags, FxHashMap::default());
assert_eq!(folded.len(), 1);
assert_eq!(folded.get("<blockquote>"), Some(&2));
}
#[test]
fn the_vocabularys_own_table_wins_over_a_user_defined_piece() {
let pieces: Vec<String> = ["a", "<mask>"].iter().map(|s| s.to_string()).collect();
let flags = [false, true];
let mut stated = FxHashMap::default();
stated.insert("<mask>".to_string(), 9999);
let folded = fold_user_defined(&pieces, &flags, stated);
assert_eq!(folded.get("<mask>"), Some(&9999));
}
#[test]
fn a_vocabulary_without_type_information_folds_nothing() {
let pieces: Vec<String> = ["a", "b"].iter().map(|s| s.to_string()).collect();
let folded = fold_user_defined(&pieces, &[], FxHashMap::default());
assert!(folded.is_empty());
}
fn agent_token_ids_in(vocab: PretrainedVocab) -> Vec<(String, u32)> {
let all = special_tokens(vocab);
AGENT_TOKEN_NAMES
.iter()
.filter_map(|name| all.get(*name).map(|&id| (name.to_string(), id)))
.collect()
}
}