use base64::Engine;
use rustc_hash::FxHashMap;
use crate::bpe::CoreBpe;
use crate::pretokenize::{FastPath, WhitespaceRules};
const CL100K_BASE_DATA: &[u8] = include_bytes!("encodings/cl100k_base.tiktoken.zst");
const O200K_BASE_DATA: &[u8] = include_bytes!("encodings/o200k_base.tiktoken.zst");
const P50K_BASE_DATA: &[u8] = include_bytes!("encodings/p50k_base.tiktoken.zst");
const R50K_BASE_DATA: &[u8] = include_bytes!("encodings/r50k_base.tiktoken.zst");
const LLAMA3_DATA: &[u8] = include_bytes!("encodings/llama3.tiktoken.zst");
const DEEPSEEK_V3_DATA: &[u8] = include_bytes!("encodings/deepseek_v3.tiktoken.zst");
const QWEN2_DATA: &[u8] = include_bytes!("encodings/qwen2.tiktoken.zst");
const MISTRAL_V3_DATA: &[u8] = include_bytes!("encodings/mistral_v3.tiktoken.zst");
const KIMI_K2_DATA: &[u8] = include_bytes!("encodings/kimi_k2.tiktoken.zst");
const GLM4_DATA: &[u8] = include_bytes!("encodings/glm4.tiktoken.zst");
const GLM5_DATA: &[u8] = include_bytes!("encodings/glm5.tiktoken.zst");
const MINIMAX_M2_DATA: &[u8] = include_bytes!("encodings/minimax_m2.tiktoken.zst");
pub(crate) const CL100K_PATTERN: &str = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+";
pub(crate) const O200K_PATTERN: &str = concat!(
r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|\p{N}{1,3}",
r"| ?[^\s\p{L}\p{N}]+[\r\n]*",
r"|\s*[\r\n]+",
r"|\s+",
);
pub(crate) const P50K_PATTERN: &str =
r"'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+";
const LLAMA3_PATTERN: &str = CL100K_PATTERN;
pub(crate) const DEEPSEEK_V3_PATTERN: &str = concat!(
r"\p{N}{1,3}",
r"|[一-龥\x{3040}-\x{309F}\x{30A0}-\x{30FF}]+",
r"|[!-/:-@\[-`{-~][A-Za-z]+",
r"|[^\r\n\p{L}\p{P}\p{S}]?[\p{L}\p{M}]+",
r"| ?[\p{P}\p{S}]+[\r\n]*",
r"|\s*[\r\n]+",
r"|\s+",
r"|[\s\S]",
);
pub(crate) const QWEN2_PATTERN: &str = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+";
pub(crate) const KIMI_PATTERN: &str = concat!(
r"[\p{Han}]+",
r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]+[\p{Ll}\p{Lm}\p{Lo}\p{M}&&[^\p{Han}]]*",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|\p{N}{1,3}",
r"| ?[^\s\p{L}\p{N}]+[\r\n]*",
r"|\s*[\r\n]+",
r"|\s+",
);
const GLM_PATTERN: &str = CL100K_PATTERN;
pub(crate) const MINIMAX_M2_PATTERN: &str = concat!(
r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*",
r"(?i:'s|'t|'re|'ve|'m|'ll|'d)?",
r"|\p{N}{1,3}",
r"| ?[^\s\p{L}\p{N}]+[\r\n/]*",
r"|\s*[\r\n]+",
r"|\s+",
);
pub(crate) const MISTRAL_V3_PATTERN: &str = concat!(
r"[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+",
r"|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*",
r"|\p{N}",
r"| ?[^\s\p{L}\p{N}]+[\r\n/]*",
r"|\s*[\r\n]+",
r"|\s+",
);
pub(crate) fn parse_tiktoken_data(compressed: &[u8]) -> FxHashMap<Vec<u8>, u32> {
let mut decoder =
ruzstd::decoding::StreamingDecoder::new(compressed).expect("zstd decompression failed");
let mut data = Vec::new();
std::io::Read::read_to_end(&mut decoder, &mut data).expect("zstd decompression failed");
parse_tiktoken_lines(&data)
}
fn parse_tiktoken_lines(data: &[u8]) -> FxHashMap<Vec<u8>, u32> {
let engine = base64::engine::general_purpose::STANDARD;
let content = std::str::from_utf8(data).expect("tiktoken data must be valid UTF-8");
let mut ranks = FxHashMap::default();
ranks.reserve(data.len() / 20);
for line in content.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let mut parts = line.splitn(2, ' ');
let token_b64 = parts.next().expect("missing token");
let rank_str = parts.next().expect("missing rank");
let token_bytes = engine
.decode(token_b64)
.expect("invalid base64 in tiktoken data");
let rank: u32 = rank_str.parse().expect("invalid rank in tiktoken data");
ranks.insert(token_bytes, rank);
}
ranks
}
fn special_tokens(pairs: &[(&str, u32)]) -> FxHashMap<Vec<u8>, u32> {
pairs
.iter()
.map(|&(s, v)| (s.as_bytes().to_vec(), v))
.collect()
}
pub fn cl100k_base() -> CoreBpe {
let encoder = parse_tiktoken_data(CL100K_BASE_DATA);
let special = special_tokens(&[
("<|endoftext|>", 100257),
("<|fim_prefix|>", 100258),
("<|fim_middle|>", 100259),
("<|fim_suffix|>", 100260),
("<|endofprompt|>", 100276),
]);
CoreBpe::new(
encoder,
special,
CL100K_PATTERN,
FastPath::Cl100k,
WhitespaceRules::NewlineFirst,
)
}
pub fn p50k_base() -> CoreBpe {
let encoder = parse_tiktoken_data(P50K_BASE_DATA);
let special = special_tokens(&[("<|endoftext|>", 50256)]);
CoreBpe::new(
encoder,
special,
P50K_PATTERN,
FastPath::None,
WhitespaceRules::Generic,
)
}
pub fn p50k_edit() -> CoreBpe {
let encoder = parse_tiktoken_data(P50K_BASE_DATA);
let special = special_tokens(&[
("<|endoftext|>", 50256),
("<|fim_prefix|>", 50281),
("<|fim_middle|>", 50282),
("<|fim_suffix|>", 50283),
]);
CoreBpe::new(
encoder,
special,
P50K_PATTERN,
FastPath::None,
WhitespaceRules::Generic,
)
}
pub fn o200k_base() -> CoreBpe {
let encoder = parse_tiktoken_data(O200K_BASE_DATA);
let special = special_tokens(&[("<|endoftext|>", 199999), ("<|endofprompt|>", 200018)]);
CoreBpe::new(
encoder,
special,
O200K_PATTERN,
FastPath::O200k,
WhitespaceRules::NewlineFirst,
)
}
pub fn o200k_harmony() -> CoreBpe {
let encoder = parse_tiktoken_data(O200K_BASE_DATA);
let mut special: FxHashMap<Vec<u8>, u32> = FxHashMap::default();
for (name, id) in [
("<|startoftext|>", 199998_u32),
("<|endoftext|>", 199999),
("<|reserved_200000|>", 200000),
("<|reserved_200001|>", 200001),
("<|return|>", 200002),
("<|constrain|>", 200003),
("<|reserved_200004|>", 200004),
("<|channel|>", 200005),
("<|start|>", 200006),
("<|end|>", 200007),
("<|message|>", 200008),
("<|reserved_200009|>", 200009),
("<|reserved_200010|>", 200010),
("<|reserved_200011|>", 200011),
("<|call|>", 200012),
] {
special.insert(name.as_bytes().to_vec(), id);
}
for id in 200013..=201087_u32 {
special.insert(format!("<|reserved_{id}|>").into_bytes(), id);
}
CoreBpe::new(
encoder,
special,
O200K_PATTERN,
FastPath::O200k,
WhitespaceRules::NewlineFirst,
)
}
pub fn r50k_base() -> CoreBpe {
let encoder = parse_tiktoken_data(R50K_BASE_DATA);
let special = special_tokens(&[("<|endoftext|>", 50256)]);
CoreBpe::new(
encoder,
special,
P50K_PATTERN,
FastPath::None,
WhitespaceRules::Generic,
)
}
pub fn gpt2() -> CoreBpe {
r50k_base()
}
pub fn llama3() -> CoreBpe {
let encoder = parse_tiktoken_data(LLAMA3_DATA);
let special = special_tokens(&[
("<|begin_of_text|>", 128000),
("<|end_of_text|>", 128001),
("<|finetune_right_pad_id|>", 128004),
("<|start_header_id|>", 128006),
("<|end_header_id|>", 128007),
("<|eom_id|>", 128008),
("<|eot_id|>", 128009),
("<|python_tag|>", 128010),
]);
CoreBpe::new(
encoder,
special,
LLAMA3_PATTERN,
FastPath::Cl100k,
WhitespaceRules::NewlineFirst,
)
}
pub fn deepseek_v3() -> CoreBpe {
let encoder = parse_tiktoken_data(DEEPSEEK_V3_DATA);
CoreBpe::new(
encoder,
deepseek_v3_special_tokens(),
DEEPSEEK_V3_PATTERN,
FastPath::Deepseek,
WhitespaceRules::NewlineFirstSplitOnNumCjk,
)
}
fn deepseek_v3_special_tokens() -> FxHashMap<Vec<u8>, u32> {
let mut special = special_tokens(&[
("<|begin▁of▁sentence|>", 0),
("<|end▁of▁sentence|>", 1),
("<|▁pad▁|>", 2),
("<|fim▁hole|>", 128800),
("<|fim▁begin|>", 128801),
("<|fim▁end|>", 128802),
("<|User|>", 128803),
("<|Assistant|>", 128804),
("<|EOT|>", 128805),
("<|tool▁calls▁begin|>", 128806),
("<|tool▁calls▁end|>", 128807),
("<|tool▁call▁begin|>", 128808),
("<|tool▁call▁end|>", 128809),
("<|tool▁outputs▁begin|>", 128810),
("<|tool▁outputs▁end|>", 128811),
("<|tool▁output▁begin|>", 128812),
("<|tool▁output▁end|>", 128813),
("<|tool▁sep|>", 128814),
]);
for id in 128000..=128799_u32 {
let n = id - 128000;
special.insert(format!("<|place▁holder▁no▁{n}|>").into_bytes(), id);
}
special
}
pub fn qwen2() -> CoreBpe {
let encoder = parse_tiktoken_data(QWEN2_DATA);
let special = special_tokens(&[
("<|endoftext|>", 151643),
("<|im_start|>", 151644),
("<|im_end|>", 151645),
("<|object_ref_start|>", 151646),
("<|object_ref_end|>", 151647),
("<|box_start|>", 151648),
("<|box_end|>", 151649),
("<|quad_start|>", 151650),
("<|quad_end|>", 151651),
("<|vision_start|>", 151652),
("<|vision_end|>", 151653),
("<|vision_pad|>", 151654),
("<|image_pad|>", 151655),
("<|video_pad|>", 151656),
("<tool_call>", 151657),
("</tool_call>", 151658),
("<|fim_prefix|>", 151659),
("<|fim_middle|>", 151660),
("<|fim_suffix|>", 151661),
("<|fim_pad|>", 151662),
("<|repo_name|>", 151663),
("<|file_sep|>", 151664),
]);
CoreBpe::new(
encoder,
special,
QWEN2_PATTERN,
FastPath::Qwen2,
WhitespaceRules::NewlineFirst,
)
}
pub fn mistral_v3() -> CoreBpe {
let encoder = parse_tiktoken_data(MISTRAL_V3_DATA);
let special = special_tokens(&[
("<unk>", 0),
("<s>", 1),
("</s>", 2),
("[INST]", 3),
("[/INST]", 4),
("[AVAILABLE_TOOLS]", 5),
("[/AVAILABLE_TOOLS]", 6),
("[TOOL_RESULTS]", 7),
("[/TOOL_RESULTS]", 8),
("[TOOL_CALLS]", 9),
("[IMG]", 10),
("[IMG_BREAK]", 12),
("[IMG_END]", 13),
("[PREFIX]", 14),
("[MIDDLE]", 15),
("[SUFFIX]", 16),
]);
CoreBpe::new(
encoder,
special,
MISTRAL_V3_PATTERN,
FastPath::Tekken,
WhitespaceRules::NewlineFirst,
)
}
pub fn deepseek_v4() -> CoreBpe {
let encoder = parse_tiktoken_data(DEEPSEEK_V3_DATA);
let mut special = deepseek_v3_special_tokens();
for (name, id) in [
("<|begin▁of▁repo▁name|>", 128815_u32),
("<|end▁of▁repo▁name|>", 128816),
("<|begin▁of▁file▁name|>", 128817),
("<|end▁of▁file▁name|>", 128818),
("<|begin▁of▁file|>", 128819),
("<|end▁of▁file|>", 128820),
("<think>", 128821),
("</think>", 128822),
("<|place▁holder▁for▁copy|>", 128823),
("<|place▁holder▁for▁pointer▁replace|>", 128824),
("|DSML|", 128825),
("<|begin▁sys|>", 128826),
("<|end▁sys|>", 128827),
("<|latest_reminder|>", 128828),
("<|action|>", 128829),
("<|query|>", 128830),
("<|authority|>", 128831),
("<|domain|>", 128832),
("<|task|>", 128833),
("<|political|>", 128834),
("<|entity|>", 128835),
("<|title|>", 128836),
("<|safety|>", 128837),
("<|answer|>", 128838),
("<|search|>", 128839),
("<dsml:", 128840),
("</dsml:", 128841),
("<|search▁begin|>", 128842),
("<|search▁end|>", 128843),
("<|extracted_url|>", 128844),
("<|read_url|>", 128845),
("<|end_of_query|>", 128846),
("<|rl_image_pad|>", 129262),
("<|rl_image_start|>", 129263),
("<|image2|>", 129264),
("<|/table>|", 129265),
("<|table|>", 129266),
("<|/td|>", 129267),
("<|td|>", 129268),
("<|/tr|>", 129269),
("<|tr|>", 129270),
("<|/polygon|>", 129271),
("<|polygon|>", 129272),
("<|/point|>", 129273),
("<|point|>", 129274),
("<|/box|>", 129275),
("<|box|>", 129276),
("<|/ref|>", 129277),
("<|ref|>", 129278),
("<|image|>", 129279),
] {
special.insert(name.as_bytes().to_vec(), id);
}
for n in 21..=435_u32 {
special.insert(
format!("<|place_holder_mm_span_{n:04}|>").into_bytes(),
128847 + (n - 21),
);
}
CoreBpe::new(
encoder,
special,
DEEPSEEK_V3_PATTERN,
FastPath::Deepseek,
WhitespaceRules::NewlineFirstSplitOnNumCjk,
)
}
pub fn kimi_k2() -> CoreBpe {
let encoder = parse_tiktoken_data(KIMI_K2_DATA);
let special = special_tokens(&[
("[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),
("[UNK]", 163838),
("[PAD]", 163839),
]);
CoreBpe::new(
encoder,
special,
KIMI_PATTERN,
FastPath::O200k,
WhitespaceRules::NewlineFirst,
)
}
pub fn kimi_k3() -> CoreBpe {
let encoder = parse_tiktoken_data(KIMI_K2_DATA);
let special = special_tokens(&[
("[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),
]);
CoreBpe::new(
encoder,
special,
KIMI_PATTERN,
FastPath::O200k,
WhitespaceRules::NewlineFirst,
)
}
fn glm_special_tokens(base: u32) -> FxHashMap<Vec<u8>, u32> {
const NAMES: [&str; 36] = [
"<|endoftext|>",
"[MASK]",
"[gMASK]",
"[sMASK]",
"<sop>",
"<eop>",
"<|system|>",
"<|user|>",
"<|assistant|>",
"<|observation|>",
"<|begin_of_image|>",
"<|end_of_image|>",
"<|begin_of_video|>",
"<|end_of_video|>",
"<|begin_of_audio|>",
"<|end_of_audio|>",
"<|begin_of_transcription|>",
"<|end_of_transcription|>",
"<|code_prefix|>",
"<|code_middle|>",
"<|code_suffix|>",
"<think>",
"</think>",
"<tool_call>",
"</tool_call>",
"<tool_response>",
"</tool_response>",
"<arg_key>",
"</arg_key>",
"<arg_value>",
"</arg_value>",
"/nothink",
"<|begin_of_box|>",
"<|end_of_box|>",
"<|image|>",
"<|video|>",
];
NAMES
.iter()
.enumerate()
.map(|(i, s)| (s.as_bytes().to_vec(), base + i as u32))
.collect()
}
pub fn glm4() -> CoreBpe {
let encoder = parse_tiktoken_data(GLM4_DATA);
CoreBpe::new(
encoder,
glm_special_tokens(151_329),
GLM_PATTERN,
FastPath::Cl100k,
WhitespaceRules::NewlineFirst,
)
}
pub fn glm5() -> CoreBpe {
let encoder = parse_tiktoken_data(GLM5_DATA);
CoreBpe::new(
encoder,
glm_special_tokens(154_820),
GLM_PATTERN,
FastPath::Cl100k,
WhitespaceRules::NewlineFirst,
)
}
pub fn minimax_m2() -> CoreBpe {
let encoder = parse_tiktoken_data(MINIMAX_M2_DATA);
let special = special_tokens(&[
("]!p~[", 200000),
("<fim_prefix>", 200001),
("<fim_middle>", 200002),
("<fim_suffix>", 200003),
("<fim_pad>", 200004),
("<reponame>", 200005),
("<filename>", 200006),
("<gh_stars>", 200007),
("<issue_start>", 200008),
("<issue_comment>", 200009),
("<issue_closed>", 200010),
("<jupyter_start>", 200011),
("<jupyter_text>", 200012),
("<jupyter_code>", 200013),
("<jupyter_output>", 200014),
("<empty_output>", 200015),
("<commit_before>", 200016),
("<commit_msg>", 200017),
("<commit_after>", 200018),
("]~b]", 200019),
("[e~[", 200020),
("]!d~[", 200021),
("<function_call>", 200022),
("<code_interpreter>", 200023),
("]<]speech[>[", 200024),
("]<]image[>[", 200025),
("]<]video[>[", 200026),
("]<]start of speech[>[", 200027),
("]<]end of speech[>[", 200028),
("]<]start of image[>[", 200029),
("]<]end of image[>[", 200030),
("]<]start of video[>[", 200031),
("]<]end of video[>[", 200032),
("]<]vision pad[>[", 200033),
("]~!b[", 200034),
("<jupyter_error>", 200035),
("<add_file>", 200036),
("<delete_file>", 200037),
("<rename_file>", 200038),
("<edit_file>", 200039),
("<commit_message>", 200040),
("<empty_source_file>", 200041),
("<repo_struct>", 200042),
("<code_context>", 200043),
("<file_content>", 200044),
("<source_files>", 200045),
("<pr_start>", 200046),
("<review_comment>", 200047),
("<filepath>", 200048),
("<file_sep>", 200049),
("<think>", 200050),
("</think>", 200051),
("<minimax:tool_call>", 200052),
("</minimax:tool_call>", 200053),
]);
CoreBpe::new(
encoder,
special,
MINIMAX_M2_PATTERN,
FastPath::MiniMax,
WhitespaceRules::NewlineFirst,
)
}
#[cfg(test)]
pub(crate) fn parse_tiktoken_data_for_test() -> FxHashMap<Vec<u8>, u32> {
parse_tiktoken_data(CL100K_BASE_DATA)
}