1use alloc::collections::{BTreeMap, BinaryHeap};
4use alloc::{
5 format,
6 string::{String, ToString},
7 vec,
8 vec::Vec,
9};
10use core::cmp::Reverse;
11
12use crate::error::TokenizerError;
13use crate::pretokenize::{decode_byte_level, encode_byte_level, gpt2_split};
14use crate::tokenizer::{split_words, TokenId, Tokenizer};
15
16const UNK: &str = "<unk>";
18
19#[derive(Debug, Clone)]
40pub struct BpeTokenizer {
41 vocab: BTreeMap<String, TokenId>,
42 id_to_token: BTreeMap<TokenId, String>,
43 merge_ranks: BTreeMap<(String, String), usize>,
44 byte_level: bool,
45 special_tokens: BTreeMap<String, TokenId>,
46 special_id_to_token: BTreeMap<TokenId, String>,
47 #[cfg(feature = "normalization")]
48 normalize: Option<crate::normalize::NormalizationForm>,
49}
50
51impl BpeTokenizer {
52 #[must_use]
56 pub fn from_vocab_merges(
57 vocab: BTreeMap<String, TokenId>,
58 merges: Vec<(String, String)>,
59 ) -> Self {
60 let merge_ranks = merges
61 .into_iter()
62 .enumerate()
63 .map(|(rank, pair)| (pair, rank))
64 .collect();
65 let id_to_token = vocab
66 .iter()
67 .map(|(token, &id)| (id, token.clone()))
68 .collect();
69 Self {
70 vocab,
71 id_to_token,
72 merge_ranks,
73 byte_level: false,
74 special_tokens: BTreeMap::new(),
75 special_id_to_token: BTreeMap::new(),
76 #[cfg(feature = "normalization")]
77 normalize: None,
78 }
79 }
80
81 #[must_use]
83 pub fn with_byte_level(mut self) -> Self {
84 self.byte_level = true;
85 self
86 }
87
88 #[cfg(feature = "normalization")]
92 #[must_use]
93 pub fn with_normalization(mut self, form: crate::normalize::NormalizationForm) -> Self {
94 self.normalize = Some(form);
95 self
96 }
97
98 #[must_use]
101 pub fn with_special_tokens(mut self, specials: BTreeMap<String, TokenId>) -> Self {
102 self.special_id_to_token = specials.iter().map(|(t, &id)| (id, t.clone())).collect();
103 self.special_tokens = specials;
104 self
105 }
106
107 #[must_use]
110 pub fn vocab_size(&self) -> usize {
111 self.vocab.len()
112 }
113
114 fn tokenize_word(&self, word: &str) -> Vec<String> {
123 let mut symbols: Vec<String> = word.chars().map(|c| c.to_string()).collect();
124 if symbols.len() < 2 {
125 return symbols;
126 }
127 let n = symbols.len();
128
129 let mut prev: Vec<Option<usize>> = (0..n).map(|i| i.checked_sub(1)).collect();
131 let mut next: Vec<Option<usize>> = (0..n).map(|i| (i + 1 < n).then_some(i + 1)).collect();
132 let mut alive = vec![true; n];
133
134 let mut heap: BinaryHeap<Reverse<(usize, usize)>> = BinaryHeap::new();
137 for i in 0..n - 1 {
138 if let Some(&rank) = self
139 .merge_ranks
140 .get(&(symbols[i].clone(), symbols[i + 1].clone()))
141 {
142 heap.push(Reverse((rank, i)));
143 }
144 }
145
146 while let Some(Reverse((rank, i))) = heap.pop() {
147 if !alive[i] {
149 continue;
150 }
151 let Some(j) = next[i] else { continue };
152 if !alive[j] {
153 continue;
154 }
155 match self
156 .merge_ranks
157 .get(&(symbols[i].clone(), symbols[j].clone()))
158 {
159 Some(&cur) if cur == rank => {}
160 _ => continue, }
162
163 symbols[i] = format!("{}{}", symbols[i], symbols[j]);
165 alive[j] = false;
166 next[i] = next[j];
167 if let Some(k) = next[j] {
168 prev[k] = Some(i);
169 }
170
171 if let Some(p) = prev[i] {
173 if let Some(&r) = self
174 .merge_ranks
175 .get(&(symbols[p].clone(), symbols[i].clone()))
176 {
177 heap.push(Reverse((r, p)));
178 }
179 }
180 if let Some(k) = next[i] {
181 if let Some(&r) = self
182 .merge_ranks
183 .get(&(symbols[i].clone(), symbols[k].clone()))
184 {
185 heap.push(Reverse((r, i)));
186 }
187 }
188 }
189
190 let mut out = Vec::new();
192 let mut cur = Some(0usize);
193 while let Some(i) = cur {
194 if alive[i] {
195 out.push(core::mem::take(&mut symbols[i]));
196 }
197 cur = next[i];
198 }
199 out
200 }
201
202 fn push_symbol(&self, sub: String, ids: &mut Vec<TokenId>) -> Result<(), TokenizerError> {
204 match self.vocab.get(&sub) {
205 Some(&id) => ids.push(id),
206 None => match self.vocab.get(UNK) {
207 Some(&unk) => ids.push(unk),
208 None => return Err(TokenizerError::UnknownToken(sub)),
209 },
210 }
211 Ok(())
212 }
213
214 fn encode_text(&self, text: &str, ids: &mut Vec<TokenId>) -> Result<(), TokenizerError> {
216 if self.byte_level {
217 for piece in gpt2_split(text) {
218 let encoded = encode_byte_level(&piece);
219 for sub in self.tokenize_word(&encoded) {
220 self.push_symbol(sub, ids)?;
221 }
222 }
223 } else {
224 for word in split_words(text) {
225 for sub in self.tokenize_word(word) {
226 self.push_symbol(sub, ids)?;
227 }
228 }
229 }
230 Ok(())
231 }
232
233 fn split_specials<'a>(&self, text: &'a str) -> Vec<Segment<'a>> {
236 if self.special_tokens.is_empty() {
237 return vec![Segment::Text(text)];
238 }
239 let mut specials: Vec<(&String, &TokenId)> = self.special_tokens.iter().collect();
241 specials.sort_by_key(|s| core::cmp::Reverse(s.0.len()));
242
243 let mut segments = Vec::new();
244 let mut cursor = 0usize;
245 let mut run_start = 0usize;
246 while cursor < text.len() {
247 let mut matched = None;
248 for (tok, &id) in &specials {
249 if text[cursor..].starts_with(tok.as_str()) {
250 matched = Some((tok.len(), id));
251 break;
252 }
253 }
254 if let Some((len, id)) = matched {
255 if run_start < cursor {
256 segments.push(Segment::Text(&text[run_start..cursor]));
257 }
258 segments.push(Segment::Special(id));
259 cursor += len;
260 run_start = cursor;
261 } else {
262 cursor += text[cursor..].chars().next().map_or(1, char::len_utf8);
264 }
265 }
266 if run_start < text.len() {
267 segments.push(Segment::Text(&text[run_start..]));
268 }
269 segments
270 }
271
272 #[cfg(feature = "std")]
280 pub fn from_files(vocab_path: &str, merges_path: &str) -> Result<Self, TokenizerError> {
281 let vocab_text = std::fs::read_to_string(vocab_path)?;
282 let merges_text = std::fs::read_to_string(merges_path)?;
283
284 let vocab = crate::tokenizer::parse_vocab_lines(&vocab_text);
285
286 let mut merges = Vec::new();
287 for line in merges_text.lines() {
288 if line.starts_with("#version") || line.trim().is_empty() {
289 continue;
290 }
291 let parts: Vec<&str> = line.split_whitespace().collect();
292 if parts.len() != 2 {
293 continue; }
295 merges.push((parts[0].to_string(), parts[1].to_string()));
296 }
297
298 Ok(Self::from_vocab_merges(vocab, merges))
299 }
300}
301
302enum Segment<'a> {
304 Text(&'a str),
306 Special(TokenId),
308}
309
310impl Tokenizer for BpeTokenizer {
311 fn encode(&self, text: &str) -> Result<Vec<TokenId>, TokenizerError> {
312 #[cfg(feature = "normalization")]
313 let normalized;
314 #[cfg(feature = "normalization")]
315 let text = if let Some(form) = self.normalize {
316 normalized = crate::normalize::normalize(text, form);
317 normalized.as_str()
318 } else {
319 text
320 };
321
322 let mut ids = Vec::new();
323 for segment in self.split_specials(text) {
324 match segment {
325 Segment::Special(id) => ids.push(id),
326 Segment::Text(run) => self.encode_text(run, &mut ids)?,
327 }
328 }
329 Ok(ids)
330 }
331
332 fn decode(&self, ids: &[TokenId]) -> Result<String, TokenizerError> {
338 let mut out = String::new();
339 let mut buf = String::new();
342 let flush = |buf: &mut String, out: &mut String| -> Result<(), TokenizerError> {
343 if buf.is_empty() {
344 return Ok(());
345 }
346 if self.byte_level {
347 let decoded = decode_byte_level(buf).ok_or_else(|| {
348 TokenizerError::MalformedFile("invalid byte-level sequence".to_string())
349 })?;
350 out.push_str(&decoded);
351 } else {
352 out.push_str(buf);
353 }
354 buf.clear();
355 Ok(())
356 };
357
358 for &id in ids {
359 if let Some(special) = self.special_id_to_token.get(&id) {
360 flush(&mut buf, &mut out)?;
361 out.push_str(special);
362 continue;
363 }
364 match self.id_to_token.get(&id) {
365 Some(token) => buf.push_str(token),
366 None => return Err(TokenizerError::UnknownToken(id.to_string())),
367 }
368 }
369 flush(&mut buf, &mut out)?;
370 Ok(out)
371 }
372}