1use super::gguf_skip_value;
6use std::collections::HashMap;
7use std::sync::OnceLock;
8
9mod decode;
10mod pretokenizer;
11pub use pretokenizer::{PretokenError, PretokenSpan};
12
13fn gpt2_byte_to_unicode(byte: u8) -> char {
15 static TABLE: OnceLock<[char; 256]> = OnceLock::new();
16 TABLE.get_or_init(|| {
17 let mut bs: Vec<u32> = (b'!'..=b'~')
18 .chain(b'\xA1'..=b'\xAC')
19 .chain(b'\xAE'..=b'\xFF')
20 .map(|b| b as u32)
21 .collect();
22 let mut cs = bs.clone();
23 let mut n = 0u32;
24 for b in 0u32..256 {
25 if !bs.contains(&b) {
26 bs.push(b);
27 cs.push(256 + n);
28 n += 1;
29 }
30 }
31 let mut out = ['\0'; 256];
32 for (b, c) in bs.into_iter().zip(cs) {
33 out[b as usize] = char::from_u32(c).unwrap_or('\u{FFFD}');
34 }
35 out
36 })[byte as usize]
37}
38
39fn gpt2_unicode_to_byte(symbol: char) -> Option<u8> {
40 (0u16..=255)
41 .find(|byte| gpt2_byte_to_unicode(*byte as u8) == symbol)
42 .map(|byte| byte as u8)
43}
44
45pub const MAX_STOP_TOKEN_IDS: usize = 8;
47
48pub struct GgufTokenizer {
51 pub vocab: Vec<String>,
53 pub bos_token_id: u32,
54 pub eos_token_id: u32,
55 pub add_bos_token: bool,
57 pub pre_type: String,
59 merge_pairs: Vec<(String, String)>,
61 merge_rank_index: HashMap<u64, usize>,
64 merge_rank_collision: bool,
65 pub(super) token_to_id_map: HashMap<String, u32>,
67 special_tokens: Vec<(String, u32)>,
69 pub(super) token_to_id: Vec<(String, u32)>,
71 stop_token_ids: [u32; MAX_STOP_TOKEN_IDS],
75 stop_token_count: u8,
76}
77
78#[derive(Clone, Copy, PartialEq, Eq, Debug)]
82pub enum ChatFamily {
83 ChatMl,
85 Llama3,
87 Gemma,
89 Gemma4,
91 None,
93}
94
95impl Default for GgufTokenizer {
96 fn default() -> Self {
98 let vocab: Vec<String> = (0u32..256)
99 .map(|b| {
100 let c = b as u8;
101 if c.is_ascii_graphic() || c == b' ' {
102 (c as char).to_string()
103 } else {
104 format!("<0x{:02X}>", b)
105 }
106 })
107 .collect();
108 let mut t2id: Vec<(String, u32)> = vocab
109 .iter()
110 .enumerate()
111 .map(|(i, s)| (s.clone(), i as u32))
112 .collect();
113 t2id.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
114 let token_to_id_map: HashMap<String, u32> =
115 t2id.iter().map(|(s, id)| (s.clone(), *id)).collect();
116 let mut tok = Self {
117 vocab,
118 bos_token_id: 1,
119 eos_token_id: 2,
120 add_bos_token: true,
121 pre_type: String::new(),
122 merge_pairs: Vec::new(),
123 merge_rank_index: HashMap::new(),
124 merge_rank_collision: false,
125 token_to_id_map,
126 special_tokens: Vec::new(),
127 token_to_id: t2id,
128 stop_token_ids: [0; MAX_STOP_TOKEN_IDS],
129 stop_token_count: 0,
130 };
131 tok.rebuild_stop_token_ids();
132 tok
133 }
134}
135
136impl GgufTokenizer {
137 pub fn from_gguf(mmap: &[u8]) -> Self {
140 Self::try_parse(mmap).unwrap_or_default()
141 }
142
143 fn try_parse(mmap: &[u8]) -> Option<Self> {
144 if mmap.len() < 24 || &mmap[0..4] != b"GGUF" {
145 return None;
146 }
147 let version = u32::from_le_bytes(mmap[4..8].try_into().ok()?);
148 if version < 2 {
149 return None;
150 } let kv_count = u64::from_le_bytes(mmap[16..24].try_into().ok()?);
152 let mut pos = 24usize;
153 let mut vocab: Option<Vec<String>> = None;
154 let mut merges_raw: Option<Vec<String>> = None;
155 let mut bos_id: Option<u32> = None;
156 let mut eos_id: Option<u32> = None;
157 let mut add_bos: Option<bool> = None;
158 let mut pre_type: Option<String> = None;
159
160 for _ in 0..kv_count {
161 if pos + 8 > mmap.len() {
162 break;
163 }
164 let klen = u64::from_le_bytes(mmap[pos..pos + 8].try_into().ok()?) as usize;
165 pos += 8;
166 if pos + klen > mmap.len() {
167 break;
168 }
169 let key = std::str::from_utf8(&mmap[pos..pos + klen]).unwrap_or("");
170 pos += klen;
171 if pos + 4 > mmap.len() {
172 break;
173 }
174 let vtype = u32::from_le_bytes(mmap[pos..pos + 4].try_into().ok()?);
175 pos += 4;
176 match key {
177 "tokenizer.ggml.tokens" => {
178 vocab = Self::read_string_array(mmap, &mut pos, vtype);
179 }
180 "tokenizer.ggml.merges" => {
181 merges_raw = Self::read_string_array(mmap, &mut pos, vtype);
182 }
183 "tokenizer.ggml.bos_token_id" => {
184 bos_id = Self::read_u32_val(mmap, &mut pos, vtype);
185 }
186 "tokenizer.ggml.eos_token_id" => {
187 eos_id = Self::read_u32_val(mmap, &mut pos, vtype);
188 }
189 "tokenizer.ggml.add_bos_token" => {
190 add_bos = Self::read_bool_val(mmap, &mut pos, vtype);
191 }
192 "tokenizer.ggml.pre" => {
193 pre_type = Self::read_string_val(mmap, &mut pos, vtype);
194 }
195 _ => {
196 if Self::skip_value(mmap, &mut pos, vtype).is_none() {
197 break;
198 }
199 }
200 }
201 }
202
203 let v = vocab?;
204 let bos = bos_id.unwrap_or(1);
205 let eos = eos_id.unwrap_or(2);
206 let mut t2id: Vec<(String, u32)> = v
207 .iter()
208 .enumerate()
209 .map(|(i, s)| (s.clone(), i as u32))
210 .collect();
211 t2id.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
212 let token_to_id_map: HashMap<String, u32> =
213 t2id.iter().map(|(s, id)| (s.clone(), *id)).collect();
214 let mut special_tokens: Vec<(String, u32)> = v
215 .iter()
216 .enumerate()
217 .filter(|(_, s)| s.starts_with('<') && s.ends_with('>'))
218 .map(|(i, s)| (s.clone(), i as u32))
219 .collect();
220 special_tokens.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
221 let merge_pairs = Self::parse_merge_pairs(merges_raw.as_deref());
222 let (merge_rank_index, merge_rank_collision) = Self::build_merge_rank_index(&merge_pairs);
223 let mut tok = Self {
224 vocab: v,
225 bos_token_id: bos,
226 eos_token_id: eos,
227 add_bos_token: add_bos.unwrap_or(true),
228 pre_type: pre_type.unwrap_or_default(),
229 merge_pairs,
230 merge_rank_index,
231 merge_rank_collision,
232 token_to_id_map,
233 special_tokens,
234 token_to_id: t2id,
235 stop_token_ids: [0; MAX_STOP_TOKEN_IDS],
236 stop_token_count: 0,
237 };
238 tok.rebuild_stop_token_ids();
239 Some(tok)
240 }
241
242 pub fn to_p64_section(&self) -> Vec<u8> {
247 let mut out = Vec::with_capacity(1 << 20);
248 out.extend_from_slice(b"Q42T");
249 out.extend_from_slice(&2u16.to_le_bytes()); out.extend_from_slice(&0u16.to_le_bytes()); out.extend_from_slice(&self.bos_token_id.to_le_bytes());
252 out.extend_from_slice(&self.eos_token_id.to_le_bytes());
253 out.push(self.add_bos_token as u8);
254 out.extend_from_slice(&[0u8; 3]);
255 let put_str = |o: &mut Vec<u8>, s: &str| {
256 o.extend_from_slice(&(s.len() as u32).to_le_bytes());
257 o.extend_from_slice(s.as_bytes());
258 };
259 put_str(&mut out, &self.pre_type);
260 out.extend_from_slice(&(self.vocab.len() as u32).to_le_bytes());
261 for t in &self.vocab {
262 put_str(&mut out, t);
263 }
264 out.extend_from_slice(&(self.merge_pairs.len() as u32).to_le_bytes());
265 for (l, r) in &self.merge_pairs {
266 put_str(&mut out, l);
267 put_str(&mut out, r);
268 }
269 out.push(self.stop_token_count);
271 out.extend_from_slice(&[0u8; 3]);
272 for id in &self.stop_token_ids {
273 out.extend_from_slice(&id.to_le_bytes());
274 }
275 out
276 }
277
278 #[deprecated(note = "use to_p64_section")]
280 pub fn to_q42_section(&self) -> Vec<u8> {
281 self.to_p64_section()
282 }
283
284 pub fn from_p64_section(data: &[u8]) -> Option<Self> {
288 let mut p = 0usize;
289 let take = |p: &mut usize, n: usize| -> Option<&[u8]> {
290 let end = p.checked_add(n)?;
291 if end > data.len() {
292 return None;
293 }
294 let s = &data[*p..end];
295 *p = end;
296 Some(s)
297 };
298 let take_u32 = |p: &mut usize| -> Option<u32> {
299 Some(u32::from_le_bytes(take(p, 4)?.try_into().ok()?))
300 };
301 let take_str = |p: &mut usize| -> Option<String> {
302 let len = take_u32(p)? as usize;
303 Some(String::from_utf8_lossy(take(p, len)?).into_owned())
304 };
305 if take(&mut p, 4)? != b"Q42T" {
306 return None;
307 }
308 let ver = u16::from_le_bytes(take(&mut p, 2)?.try_into().ok()?);
309 let _flags = take(&mut p, 2)?;
310 let bos = take_u32(&mut p)?;
311 let eos = take_u32(&mut p)?;
312 let add_bos = take(&mut p, 1)?[0] != 0;
313 let _pad = take(&mut p, 3)?;
314 let pre_type = take_str(&mut p)?;
315 let n_vocab = take_u32(&mut p)? as usize;
316 if n_vocab > 1_000_000 {
317 return None;
318 }
319 let mut vocab = Vec::with_capacity(n_vocab);
320 for _ in 0..n_vocab {
321 vocab.push(take_str(&mut p)?);
322 }
323 let n_merges = take_u32(&mut p)? as usize;
324 if n_merges > 5_000_000 {
325 return None;
326 }
327 let mut merge_pairs = Vec::with_capacity(n_merges);
328 for _ in 0..n_merges {
329 let l = take_str(&mut p)?;
330 let r = take_str(&mut p)?;
331 merge_pairs.push((l, r));
332 }
333 let mut stored_stops: Option<([u32; MAX_STOP_TOKEN_IDS], u8)> = None;
335 if ver >= 2 && p + 4 + MAX_STOP_TOKEN_IDS * 4 <= data.len() {
336 let count = take(&mut p, 1)?[0].min(MAX_STOP_TOKEN_IDS as u8);
337 let _ = take(&mut p, 3)?;
338 let mut ids = [0u32; MAX_STOP_TOKEN_IDS];
339 for i in 0..MAX_STOP_TOKEN_IDS {
340 ids[i] = take_u32(&mut p)?;
341 }
342 stored_stops = Some((ids, count));
343 }
344 let mut t2id: Vec<(String, u32)> = vocab
346 .iter()
347 .enumerate()
348 .map(|(i, s)| (s.clone(), i as u32))
349 .collect();
350 t2id.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
351 let token_to_id_map: HashMap<String, u32> =
352 t2id.iter().map(|(s, id)| (s.clone(), *id)).collect();
353 let mut special_tokens: Vec<(String, u32)> = vocab
354 .iter()
355 .enumerate()
356 .filter(|(_, s)| s.starts_with('<') && s.ends_with('>'))
357 .map(|(i, s)| (s.clone(), i as u32))
358 .collect();
359 special_tokens.sort_by(|a, b| b.0.len().cmp(&a.0.len()));
360 let mut tok = Self {
361 vocab,
362 bos_token_id: bos,
363 eos_token_id: eos,
364 add_bos_token: add_bos,
365 pre_type,
366 merge_pairs,
367 merge_rank_index: HashMap::new(),
368 merge_rank_collision: false,
369 token_to_id_map,
370 special_tokens,
371 token_to_id: t2id,
372 stop_token_ids: [0; MAX_STOP_TOKEN_IDS],
373 stop_token_count: 0,
374 };
375 (tok.merge_rank_index, tok.merge_rank_collision) =
376 Self::build_merge_rank_index(&tok.merge_pairs);
377 if let Some((ids, count)) = stored_stops {
378 tok.stop_token_ids = ids;
379 tok.stop_token_count = count;
380 if !tok.is_stop_token(eos) {
382 tok.rebuild_stop_token_ids();
383 }
384 } else {
385 tok.rebuild_stop_token_ids();
386 }
387 Some(tok)
388 }
389
390 pub fn rebuild_stop_token_ids(&mut self) {
393 let mut ids = [0u32; MAX_STOP_TOKEN_IDS];
394 let mut n = 0usize;
395 let mut push = |id: u32| {
396 if n >= MAX_STOP_TOKEN_IDS {
397 return;
398 }
399 if ids[..n].contains(&id) {
400 return;
401 }
402 ids[n] = id;
403 n += 1;
404 };
405 push(self.eos_token_id);
406 const CHAT_ENDS: &[&str] = &[
409 "<|endoftext|>",
410 "<|eot_id|>",
411 "<|im_end|>",
412 "<end_of_turn>",
413 "<turn|>", "<|end_of_text|>",
415 "</s>",
416 "<|end|>",
417 ];
418 for s in CHAT_ENDS {
419 if let Some(&id) = self.token_to_id_map.get(*s) {
420 push(id);
421 }
422 }
423 let looks_like_chat_end = |n: &str| -> bool {
426 let l = n.to_ascii_lowercase();
427 l == "<|im_end|>"
428 || l == "<|eot_id|>"
429 || l == "<|endoftext|>"
430 || l == "<end_of_turn>"
431 || l == "</s>"
432 || l.ends_with("im_end|>")
433 || l.ends_with("eot_id|>")
434 || l.contains("end_of_turn")
435 || l.contains("im_end")
436 || l.contains("eot_id")
437 };
438 for (name, id) in &self.special_tokens {
439 if looks_like_chat_end(name) {
440 push(*id);
441 }
442 }
443 for (name, id) in &self.token_to_id_map {
444 if looks_like_chat_end(name) {
445 push(*id);
446 }
447 }
448 self.stop_token_ids = ids;
449 self.stop_token_count = n as u8;
450 }
451
452 #[inline]
454 pub fn is_stop_token(&self, id: u32) -> bool {
455 let n = self.stop_token_count as usize;
456 self.stop_token_ids[..n].contains(&id)
457 }
458
459 pub fn stop_tokens(&self) -> &[u32] {
461 &self.stop_token_ids[..self.stop_token_count as usize]
462 }
463
464 pub fn merge_stop_token_ids(&mut self, extra: &[u32]) {
467 let mut n = self.stop_token_count as usize;
468 for &id in extra {
469 if n >= MAX_STOP_TOKEN_IDS {
470 break;
471 }
472 if self.stop_token_ids[..n].contains(&id) {
473 continue;
474 }
475 self.stop_token_ids[n] = id;
476 n += 1;
477 }
478 if !self.stop_token_ids[..n].contains(&self.eos_token_id) && n < MAX_STOP_TOKEN_IDS {
480 self.stop_token_ids[n] = self.eos_token_id;
481 n += 1;
482 }
483 self.stop_token_count = n as u8;
484 }
485
486 pub fn encode_prompt(&self, text: &str) -> Vec<u32> {
488 let mut ids = self.encode(text);
489 if self.add_bos_token && ids.first().copied() != Some(self.bos_token_id) {
490 let mut with_bos = Vec::with_capacity(ids.len().saturating_add(1));
491 with_bos.push(self.bos_token_id);
492 with_bos.append(&mut ids);
493 with_bos
494 } else {
495 ids
496 }
497 }
498
499 pub fn chat_family(&self) -> ChatFamily {
501 if self.token_to_id_map.contains_key("<|im_start|>") {
502 ChatFamily::ChatMl
503 } else if self.token_to_id_map.contains_key("<|start_header_id|>") {
504 ChatFamily::Llama3
505 } else if self.token_to_id_map.contains_key("<|turn>")
506 || self.token_to_id_map.contains_key("<turn|>")
507 {
508 ChatFamily::Gemma4
510 } else if self.token_to_id_map.contains_key("<start_of_turn>") {
511 ChatFamily::Gemma
512 } else {
513 ChatFamily::None
514 }
515 }
516
517 pub fn apply_chat_template(&self, system: Option<&str>, user: &str) -> String {
522 match self.chat_family() {
523 ChatFamily::ChatMl => {
524 let mut s = String::new();
525 if let Some(sys) = system {
526 s.push_str("<|im_start|>system\n");
527 s.push_str(sys);
528 s.push_str("<|im_end|>\n");
529 }
530 s.push_str("<|im_start|>user\n");
531 s.push_str(user);
532 s.push_str("<|im_end|>\n<|im_start|>assistant\n");
533 s
534 }
535 ChatFamily::Llama3 => {
536 let mut s = String::new();
537 if let Some(sys) = system {
538 s.push_str("<|start_header_id|>system<|end_header_id|>\n\n");
539 s.push_str(sys);
540 s.push_str("<|eot_id|>");
541 }
542 s.push_str("<|start_header_id|>user<|end_header_id|>\n\n");
543 s.push_str(user);
544 s.push_str("<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n");
545 s
546 }
547 ChatFamily::Gemma => {
548 let mut s = String::from("<start_of_turn>user\n");
550 if let Some(sys) = system {
551 s.push_str(sys);
552 s.push_str("\n\n");
553 }
554 s.push_str(user);
555 s.push_str("<end_of_turn>\n<start_of_turn>model\n");
556 s
557 }
558 ChatFamily::Gemma4 => {
559 let mut s = String::from("<|turn>user\n");
562 if let Some(sys) = system {
563 s.push_str(sys);
564 s.push_str("\n\n");
565 }
566 s.push_str(user);
567 s.push_str("<turn|><|turn>model\n");
568 s
569 }
570 ChatFamily::None => user.to_string(),
571 }
572 }
573
574 pub fn encode_chat_prompt(&self, user: &str) -> Vec<u32> {
578 let templated = self.apply_chat_template(None, user);
579 self.encode_prompt(&templated)
580 }
581
582 pub fn format_ids_for_log(ids: &[u32]) -> String {
584 let mut s = String::from("[");
585 for (i, &id) in ids.iter().enumerate() {
586 if i > 0 {
587 s.push_str(", ");
588 }
589 if i >= 64 {
590 s.push_str("…");
591 break;
592 }
593 s.push_str(&id.to_string());
594 }
595 s.push(']');
596 s
597 }
598
599 pub fn encode(&self, text: &str) -> Vec<u32> {
601 if self.uses_bpe() {
602 return self.encode_bpe(text);
603 }
604 self.encode_greedy(text)
605 }
606
607 fn uses_bpe(&self) -> bool {
608 !self.merge_pairs.is_empty()
609 || matches!(
610 self.pre_type.as_str(),
611 "smollm" | "gpt2" | "mpt" | "olmo" | "jais" | "llama3"
612 )
613 }
614
615 fn encode_greedy(&self, text: &str) -> Vec<u32> {
616 let mut ids = Vec::new();
617 let mut remaining = text;
618 while !remaining.is_empty() {
619 let mut matched = false;
620 for (token, id) in &self.token_to_id {
621 if remaining.starts_with(token.as_str()) {
622 ids.push(*id);
623 remaining = &remaining[token.len()..];
624 matched = true;
625 break;
626 }
627 }
628 if !matched {
629 let b = remaining.as_bytes()[0];
630 ids.push(b as u32);
631 let step = remaining.chars().next().map(|c| c.len_utf8()).unwrap_or(1);
632 remaining = &remaining[step..];
633 }
634 }
635 ids
636 }
637
638 fn encode_bpe(&self, text: &str) -> Vec<u32> {
640 let mut ids = Vec::new();
641 let mut spans = vec![PretokenSpan::default(); text.len().max(1)];
643 let mut remaining = text;
644 while !remaining.is_empty() {
645 let mut matched_special = false;
646 for (tok, id) in &self.special_tokens {
647 if remaining.starts_with(tok.as_str()) {
648 ids.push(*id);
649 remaining = &remaining[tok.len()..];
650 matched_special = true;
651 break;
652 }
653 }
654 if matched_special {
655 continue;
656 }
657 let mut next_special = remaining.len();
658 for (tok, _) in &self.special_tokens {
659 if let Some(pos) = remaining.find(tok.as_str()) {
660 next_special = next_special.min(pos);
661 }
662 }
663 let segment = &remaining[..next_special];
664 if !segment.is_empty() {
665 let count = pretokenizer::pretokenize_into(segment, &mut spans)
666 .expect("span workspace covers the segment byte length");
667 for span in &spans[..count] {
668 ids.extend(self.bpe_piece(span.get(segment).unwrap()));
669 }
670 }
671 remaining = &remaining[next_special..];
672 }
673 ids
674 }
675
676 pub fn pretokenize_into(
678 &self,
679 text: &str,
680 out: &mut [PretokenSpan],
681 ) -> Result<usize, PretokenError> {
682 pretokenizer::pretokenize_into(text, out)
683 }
684
685 fn bpe_piece(&self, piece: &str) -> Vec<u32> {
686 if piece.is_empty() {
687 return Vec::new();
688 }
689 if let Some(&id) = self.token_to_id_map.get(piece) {
690 return vec![id];
691 }
692 let word: String = piece.bytes().map(gpt2_byte_to_unicode).collect();
693 if let Some(&id) = self.token_to_id_map.get(word.as_str()) {
694 return vec![id];
695 }
696 let mut symbols: Vec<String> = word.chars().map(|c| c.to_string()).collect();
697 if symbols.is_empty() {
698 return Vec::new();
699 }
700 loop {
701 let mut best_rank: Option<usize> = None;
702 let mut best_idx = 0usize;
703 for i in 0..symbols.len().saturating_sub(1) {
704 if let Some(rank) = self.merge_rank_str(&symbols[i], &symbols[i + 1]) {
705 if best_rank.is_none() || rank < best_rank.unwrap() {
706 best_rank = Some(rank);
707 best_idx = i;
708 }
709 }
710 }
711 let Some(_rank) = best_rank else { break };
712 let merged = format!("{}{}", symbols[best_idx], symbols[best_idx + 1]);
713 symbols[best_idx] = merged;
714 symbols.remove(best_idx + 1);
715 }
716 let mut ids = Vec::with_capacity(symbols.len());
717 for sym in symbols {
718 if let Some(&id) = self.token_to_id_map.get(sym.as_str()) {
719 ids.push(id);
720 } else {
721 for ch in sym.chars() {
722 let s = ch.to_string();
723 if let Some(&id) = self.token_to_id_map.get(s.as_str()) {
724 ids.push(id);
725 }
726 }
727 }
728 }
729 ids
730 }
731
732 fn merge_rank_str(&self, left: &str, right: &str) -> Option<usize> {
733 if !self.merge_rank_collision {
734 let fingerprint = Self::merge_pair_fingerprint(left, right);
735 if let Some(&rank) = self.merge_rank_index.get(&fingerprint) {
736 let pair = self.merge_pairs.get(rank)?;
737 if pair.0 == left && pair.1 == right {
738 return Some(rank);
739 }
740 return self
742 .merge_pairs
743 .iter()
744 .position(|(l, r)| l == left && r == right);
745 }
746 return None;
747 }
748 self.merge_pairs
749 .iter()
750 .position(|(l, r)| l == left && r == right)
751 }
752
753 fn merge_pair_fingerprint(left: &str, right: &str) -> u64 {
754 let mut hash = 0xcbf29ce484222325u64;
755 for byte in (left.len() as u64)
756 .to_le_bytes()
757 .into_iter()
758 .chain(left.bytes())
759 .chain((right.len() as u64).to_le_bytes())
760 .chain(right.bytes())
761 {
762 hash ^= byte as u64;
763 hash = hash.wrapping_mul(0x100000001b3);
764 }
765 hash
766 }
767
768 fn build_merge_rank_index(pairs: &[(String, String)]) -> (HashMap<u64, usize>, bool) {
769 let mut index: HashMap<u64, usize> = HashMap::with_capacity(pairs.len());
770 let mut collision = false;
771 for (rank, (left, right)) in pairs.iter().enumerate() {
772 let fingerprint = Self::merge_pair_fingerprint(left, right);
773 if let Some(&existing) = index.get(&fingerprint) {
774 if pairs[existing].0 != *left || pairs[existing].1 != *right {
775 collision = true;
776 }
777 continue;
778 }
779 index.insert(fingerprint, rank);
780 }
781 (index, collision)
782 }
783
784 fn parse_merge_pairs(merges: Option<&[String]>) -> Vec<(String, String)> {
785 let Some(merges) = merges else {
786 return Vec::new();
787 };
788 let mut pairs = Vec::with_capacity(merges.len());
789 for merge in merges {
790 if let Some((a, b)) = merge.split_once(' ') {
791 pairs.push((a.to_string(), b.to_string()));
792 }
793 }
794 pairs
795 }
796
797 fn uses_gpt2_byte_decoder(&self) -> bool {
798 matches!(
799 self.pre_type.to_ascii_lowercase().as_str(),
800 "gpt2"
801 | "smollm"
802 | "qwen2"
803 | "llama-bpe"
804 | "deepseek-llm"
805 | "deepseek-coder"
806 | "falcon"
807 | "starcoder"
808 )
809 }
810
811 fn append_decoded_token_bytes(&self, out: &mut Vec<u8>, s: &str) {
812 if s.len() == 6 && s.starts_with("<0x") && s.ends_with('>') {
813 if let Ok(byte) = u8::from_str_radix(&s[3..5], 16) {
814 out.push(byte);
815 }
816 } else if self.uses_gpt2_byte_decoder() {
817 for symbol in s.chars() {
818 if let Some(byte) = gpt2_unicode_to_byte(symbol) {
819 out.push(byte);
820 } else {
821 let mut encoded = [0u8; 4];
822 out.extend_from_slice(symbol.encode_utf8(&mut encoded).as_bytes());
823 }
824 }
825 } else if let Some(rest) = s.strip_prefix('\u{2581}') {
826 out.push(b' ');
827 out.extend_from_slice(rest.as_bytes());
828 } else if let Some(rest) = s.strip_prefix('\u{0120}') {
829 out.push(b' ');
830 out.extend_from_slice(rest.as_bytes());
831 } else {
832 out.extend_from_slice(s.as_bytes());
833 }
834 }
835
836 #[allow(dead_code)]
838 fn append_decoded_token(out: &mut String, s: &str) {
839 if let Some(rest) = s.strip_prefix('\u{2581}') {
840 out.push(' ');
841 out.push_str(rest);
842 } else if let Some(rest) = s.strip_prefix('\u{0120}') {
843 out.push(' ');
845 out.push_str(rest);
846 } else if s.len() == 6 && s.starts_with("<0x") && s.ends_with('>') {
847 if let Ok(b) = u8::from_str_radix(&s[3..5], 16) {
848 out.push(b as char);
849 }
850 } else {
851 out.push_str(s);
852 }
853 }
854
855 pub fn decode(&self, ids: &[u32]) -> String {
858 let mut out = Vec::new();
859 for &id in ids {
860 let s = self
861 .vocab
862 .get(id as usize)
863 .map(|s| s.as_str())
864 .unwrap_or("");
865 self.append_decoded_token_bytes(&mut out, s);
866 }
867 String::from_utf8_lossy(&out).into_owned()
868 }
869
870 pub fn decode_token_bytes_cold(&self, id: u32) -> Vec<u8> {
875 let mut out = Vec::new();
876 if let Some(token) = self.vocab.get(id as usize) {
877 self.append_decoded_token_bytes(&mut out, token);
878 }
879 out
880 }
881
882 pub fn vocab_len(&self) -> u32 {
883 self.vocab.len() as u32
884 }
885
886 pub fn merge_count(&self) -> usize {
888 self.merge_pairs.len()
889 }
890
891 fn read_string_array(mmap: &[u8], pos: &mut usize, vtype: u32) -> Option<Vec<String>> {
894 if vtype != 9 {
895 Self::skip_value(mmap, pos, vtype)?;
896 return None;
897 }
898 if *pos + 12 > mmap.len() {
899 return None;
900 }
901 let etype = u32::from_le_bytes(mmap[*pos..*pos + 4].try_into().ok()?);
902 *pos += 4;
903 let count = u64::from_le_bytes(mmap[*pos..*pos + 8].try_into().ok()?) as usize;
904 *pos += 8;
905 if etype != 8 {
906 return None;
907 } let mut result = Vec::with_capacity(count.min(256_000));
909 for _ in 0..count {
910 if *pos + 8 > mmap.len() {
911 break;
912 }
913 let slen = u64::from_le_bytes(mmap[*pos..*pos + 8].try_into().ok()?) as usize;
914 *pos += 8;
915 if *pos + slen > mmap.len() {
916 break;
917 }
918 let s = std::str::from_utf8(&mmap[*pos..*pos + slen])
919 .unwrap_or("<?>")
920 .to_string();
921 *pos += slen;
922 result.push(s);
923 }
924 Some(result)
925 }
926
927 fn read_u32_val(mmap: &[u8], pos: &mut usize, vtype: u32) -> Option<u32> {
928 if vtype == 4 {
929 if *pos + 4 > mmap.len() {
930 return None;
931 }
932 let v = u32::from_le_bytes(mmap[*pos..*pos + 4].try_into().ok()?);
933 *pos += 4;
934 Some(v)
935 } else {
936 Self::skip_value(mmap, pos, vtype)?;
937 None
938 }
939 }
940
941 fn read_string_val(mmap: &[u8], pos: &mut usize, vtype: u32) -> Option<String> {
942 if vtype == 8 {
943 if *pos + 8 > mmap.len() {
944 return None;
945 }
946 let slen = u64::from_le_bytes(mmap[*pos..*pos + 8].try_into().ok()?) as usize;
947 *pos += 8;
948 if *pos + slen > mmap.len() {
949 return None;
950 }
951 let s = std::str::from_utf8(&mmap[*pos..*pos + slen])
952 .unwrap_or("")
953 .to_string();
954 *pos += slen;
955 Some(s)
956 } else {
957 Self::skip_value(mmap, pos, vtype)?;
958 None
959 }
960 }
961
962 fn read_bool_val(mmap: &[u8], pos: &mut usize, vtype: u32) -> Option<bool> {
963 if vtype == 7 {
964 if *pos + 1 > mmap.len() {
965 return None;
966 }
967 let b = mmap[*pos];
968 *pos += 1;
969 Some(b != 0)
970 } else {
971 Self::skip_value(mmap, pos, vtype)?;
972 None
973 }
974 }
975
976 fn skip_value(mmap: &[u8], pos: &mut usize, vtype: u32) -> Option<()> {
977 gguf_skip_value(mmap, pos, vtype)
978 }
979}