const LLAMA3: &str = r"(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+";
const QWEN2: &str = r"(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+";
const GPT4O: &str = r"[^\r\n\p{L}\p{N}]?((?=[\p{L}])([^a-z]))*((?=[\p{L}])([^A-Z]))+(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|[^\r\n\p{L}\p{N}]?((?=[\p{L}])([^a-z]))+((?=[\p{L}])([^A-Z]))*(?:'[sS]|'[tT]|'[rR][eE]|'[vV][eE]|'[mM]|'[lL][lL]|'[dD])?|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n/]*|\s*[\r\n]+|\s+(?!\S)|\s+";
const GPT2: &str = r"'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)";
const NEWLINES: &str = r"[^\n]+|[\n]+";
fn compile(pattern: &str) -> fancy_regex::Regex {
fancy_regex::Regex::new(pattern).expect("pre-tokenization patterns are fixed and valid")
}
pub(super) fn regex_for(pre: &str) -> fancy_regex::Regex {
let pattern = match pre {
"llama3" | "llama-v3" | "llama-bpe" | "falcon3" | "falcon-h1" | "pixtral" | "midm-2.0"
| "lfm2" | "jina-v5-nano" | "deepseek-llm" | "deepseek-coder" | "deepseek-v3"
| "chatglm-bpe" | "glm4" => LLAMA3,
"qwen2" | "deepseek-r1-qwen" | "kormo" | "f2llmv2" | "megrez" | "stablelm2" | "hunyuan"
| "solar-open" => QWEN2,
"gpt-4o" | "llama4" | "kanana2" | "talkie" | "minimax-m2" => GPT4O,
_ => GPT2,
};
compile(pattern)
}
pub(super) fn newline_regex() -> fancy_regex::Regex {
compile(NEWLINES)
}
pub(super) fn split_with_gaps<'t>(re: &fancy_regex::Regex, text: &'t str) -> Vec<&'t str> {
let mut chunks = Vec::new();
let mut cursor = 0usize;
for m in re.find_iter(text) {
let Ok(m) = m else { break };
if m.start() > cursor {
chunks.push(&text[cursor..m.start()]);
}
chunks.push(m.as_str());
cursor = m.end();
}
if cursor < text.len() {
chunks.push(&text[cursor..]);
}
chunks
}
#[cfg(test)]
mod tests {
use super::*;
fn split(pre: &str, text: &str) -> Vec<String> {
split_with_gaps(®ex_for(pre), text)
.into_iter()
.map(str::to_string)
.collect()
}
#[test]
fn the_llama3_pretokenizer_groups_digits_and_keeps_uppercase_contractions() {
assert_eq!(split("llama3", "1234567"), vec!["123", "456", "7"]);
assert_eq!(split("llama3", "DON'T"), vec!["DON", "'T"]);
assert_eq!(
split("llama3", "hello world"),
vec!["hello", " ", " world"]
);
}
#[test]
fn qwen2_splits_digits_singly_where_llama3_groups_them() {
assert_eq!(split("qwen2", "1234"), vec!["1", "2", "3", "4"]);
assert_eq!(split("llama3", "1234"), vec!["123", "4"]);
assert_eq!(
split("qwen2", "DON'T"),
split("llama3", "DON'T"),
"the contraction case rule does not show on uppercase input"
);
}
#[test]
fn the_deepseek_r1_qwen_group_is_the_qwen2_arm_not_the_gpt2_fallback() {
let text = "para 1234\n\n end";
assert_eq!(
split("qwen2", text),
vec!["para", " ", "1", "2", "3", "4", "\n\n", " ", " end"]
);
assert_ne!(
split("qwen2", text),
split("some-pre-with-no-arm", text),
"the two arms must differ, or this test proves nothing"
);
for pre in ["deepseek-r1-qwen", "kormo", "f2llmv2"] {
assert_eq!(
split(pre, text),
split("qwen2", text),
"{pre} is the qwen2 pre-tokenizer upstream"
);
}
}
#[test]
fn gpt_4o_is_its_own_arm_and_not_the_qwen2_one() {
assert_eq!(split("gpt-4o", "1234567"), vec!["123", "456", "7"]);
assert_eq!(
split("qwen2", "1234567"),
vec!["1", "2", "3", "4", "5", "6", "7"]
);
assert_eq!(split("gpt-4o", "It's"), vec!["It's"]);
assert_eq!(split("qwen2", "It's"), vec!["It", "'s"]);
assert_eq!(split("gpt-4o", "a.\n/b"), vec!["a", ".\n/", "b"]);
assert_eq!(split("qwen2", "a.\n/b"), vec!["a", ".\n", "/b"]);
for pre in ["llama4", "kanana2", "talkie"] {
assert_eq!(split(pre, "It's 1234567"), split("gpt-4o", "It's 1234567"));
}
}
#[test]
fn no_arm_can_drop_the_text_between_its_matches() {
let hostile = "a\tb\u{a0}c\nd\u{c}e\u{7}f \u{2009}\r\n\tg\t";
for pre in [
"olmo",
"gpt-2",
"llama3",
"qwen2",
"gpt-4o",
"something-nobody-has-heard-of",
] {
let chunks = split(pre, hostile);
assert_eq!(
chunks.concat(),
hostile,
"the {pre} arm lost input: {chunks:?}"
);
}
assert_eq!(split("olmo", "a\tb"), vec!["a", "\t", "b"]);
assert_eq!(
split("olmo", "para\n\nnext"),
vec!["para", "\n", "\n", "next"]
);
assert_eq!(split("olmo", "a\u{a0}b"), vec!["a", "\u{a0}", "b"]);
}
#[test]
fn gpt2_olmo_and_the_fallback_are_one_arm() {
let text = "hi there\tand\n\nmore ";
let expected = split("gpt-2", text);
assert_eq!(split("olmo", text), expected);
assert_eq!(split("jais", text), expected);
assert_eq!(split("something-nobody-has-heard-of", text), expected);
assert_eq!(
split("gpt-2", "hi "),
vec!["hi", " "],
"trailing whitespace is its own run, which is what `\\s+(?!\\S)` is for"
);
}
#[test]
fn the_gemma4_pattern_only_carves_on_newlines() {
let chunks = split_with_gaps(&newline_regex(), "one two\n\nthree");
assert_eq!(chunks, vec!["one two", "\n\n", "three"]);
}
}