1use serde::Deserialize;
6use std::collections::HashMap;
7use std::fs;
8use std::path::Path;
9use std::sync::Arc;
10
11#[derive(Debug, Clone)]
13pub struct Tokenizer {
14 pub vocab: HashMap<String, u32>,
16 pub id_to_token: HashMap<u32, String>,
18 pub bos_token_id: u32,
20 pub eos_token_id: u32,
22 pub pad_token_id: u32,
24 pub hf_tokenizer: Option<Arc<tokenizers::Tokenizer>>,
26}
27
28#[derive(Deserialize)]
29struct TokenizerJson {
30 model: TokenizerModel,
31}
32
33#[derive(Deserialize)]
34struct TokenizerModel {
35 vocab: HashMap<String, u32>,
36}
37
38impl Tokenizer {
39 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, String> {
41 if let Ok(hf_tokenizer) = tokenizers::Tokenizer::from_file(path.as_ref()) {
42 let vocab = hf_tokenizer.get_vocab(true);
43 let id_to_token: HashMap<u32, String> =
44 vocab.iter().map(|(k, v)| (*v, k.clone())).collect();
45 let bos_token_id = hf_tokenizer
46 .token_to_id("<|im_start|>")
47 .or_else(|| hf_tokenizer.token_to_id("<s>"))
48 .unwrap_or(1);
49 let eos_token_id = hf_tokenizer
50 .token_to_id("<|im_end|>")
51 .or_else(|| hf_tokenizer.token_to_id("</s>"))
52 .unwrap_or(2);
53 let pad_token_id = hf_tokenizer.token_to_id("<pad>").unwrap_or(0);
54
55 return Ok(Self {
56 vocab,
57 id_to_token,
58 bos_token_id,
59 eos_token_id,
60 pad_token_id,
61 hf_tokenizer: Some(Arc::new(hf_tokenizer)),
62 });
63 }
64
65 let content = fs::read_to_string(path)
66 .map_err(|e| format!("Failed to read tokenizer.json: {}", e))?;
67
68 let parsed: TokenizerJson = serde_json::from_str(&content)
69 .map_err(|e| format!("Failed to parse tokenizer.json: {}", e))?;
70
71 let vocab = parsed.model.vocab;
72 let id_to_token: HashMap<u32, String> =
73 vocab.iter().map(|(k, v)| (*v, k.clone())).collect();
74
75 let bos_token_id = *vocab
77 .get("<|im_start|>")
78 .or_else(|| vocab.get("<s>"))
79 .unwrap_or(&1);
80 let eos_token_id = *vocab
81 .get("<|im_end|>")
82 .or_else(|| vocab.get("</s>"))
83 .unwrap_or(&2);
84 let pad_token_id = *vocab.get("<pad>").unwrap_or(&0);
85
86 Ok(Self {
87 vocab,
88 id_to_token,
89 bos_token_id,
90 eos_token_id,
91 pad_token_id,
92 hf_tokenizer: None,
93 })
94 }
95
96 pub fn encode(&self, text: &str) -> Vec<u32> {
99 self.encode_with(text, true)
100 }
101
102 pub fn encode_continuation(&self, text: &str) -> Vec<u32> {
105 self.encode_with(text, false)
106 }
107
108 fn encode_with(&self, text: &str, add_special_tokens: bool) -> Vec<u32> {
109 if let Some(tokenizer) = &self.hf_tokenizer {
110 if let Ok(encoding) = tokenizer.encode(text, add_special_tokens) {
111 return encoding.get_ids().to_vec();
112 }
113 }
114
115 let mut tokens = if add_special_tokens {
116 vec![self.bos_token_id]
117 } else {
118 Vec::new()
119 };
120
121 for word in text.split_whitespace() {
124 if let Some(&id) = self.vocab.get(word) {
126 tokens.push(id);
127 } else {
128 let word_with_space = format!("Ġ{}", word);
130 if let Some(&id) = self.vocab.get(&word_with_space) {
131 tokens.push(id);
132 } else {
133 for c in word.chars() {
135 if let Some(&id) = self.vocab.get(&c.to_string()) {
136 tokens.push(id);
137 } else {
138 tokens.push(self.vocab.get("<unk>").copied().unwrap_or(0));
140 }
141 }
142 }
143 }
144 }
145
146 tokens
147 }
148
149 pub fn decode(&self, tokens: &[u32]) -> String {
151 if let Some(tokenizer) = &self.hf_tokenizer {
152 if let Ok(decoded) = tokenizer.decode(tokens, true) {
153 return decoded;
154 }
155 }
156
157 let mut result = String::new();
158 for &id in tokens {
159 if id == self.bos_token_id || id == self.eos_token_id || id == self.pad_token_id {
160 continue;
161 }
162 if let Some(token) = self.id_to_token.get(&id) {
163 if let Some(word) = token.strip_prefix('Ġ') {
165 if !result.is_empty() {
166 result.push(' ');
167 }
168 result.push_str(word);
169 } else {
170 result.push_str(token);
171 }
172 }
173 }
174 result
175 }
176
177 pub fn chat_prompt(system: Option<&str>, user: &str) -> String {
181 let mut prompt = String::new();
182 if let Some(sys) = system {
183 prompt.push_str("<|im_start|>system\n");
184 prompt.push_str(sys);
185 prompt.push_str("<|im_end|>\n");
186 }
187 prompt.push_str("<|im_start|>user\n");
188 prompt.push_str(user);
189 prompt.push_str("<|im_end|>\n<|im_start|>assistant\n");
190 prompt
191 }
192
193 pub fn encode_chat(&self, system: Option<&str>, user: &str) -> Vec<u32> {
195 self.encode(&Self::chat_prompt(system, user))
196 }
197}
198
199pub struct Sampler {
204 pub temperature: f32,
206 pub top_k: usize,
208 pub top_p: f32,
210 rng: rand::rngs::StdRng,
211}
212
213impl Default for Sampler {
214 fn default() -> Self {
215 Self::new(0.7, 0, 0.9, None)
216 }
217}
218
219impl Sampler {
220 pub fn new(temperature: f32, top_k: usize, top_p: f32, seed: Option<u64>) -> Self {
222 use rand::SeedableRng;
223 let rng = match seed {
224 Some(seed) => rand::rngs::StdRng::seed_from_u64(seed),
225 None => rand::rngs::StdRng::from_entropy(),
226 };
227 Self {
228 temperature,
229 top_k,
230 top_p,
231 rng,
232 }
233 }
234
235 pub fn sample(&mut self, logits: &[f32]) -> u32 {
237 use rand::Rng;
238 if logits.is_empty() {
239 return 0;
240 }
241 if self.temperature <= 0.0 || self.top_k == 1 {
242 return argmax(logits);
243 }
244
245 let mut order: Vec<usize> = (0..logits.len()).collect();
247 order.sort_unstable_by(|&a, &b| logits[b].total_cmp(&logits[a]));
248 if self.top_k > 0 {
249 order.truncate(self.top_k);
250 }
251
252 let max = logits[order[0]];
253 let mut probs: Vec<f32> = order
254 .iter()
255 .map(|&i| ((logits[i] - max) / self.temperature).exp())
256 .collect();
257 let total: f32 = probs.iter().sum();
258 probs.iter_mut().for_each(|p| *p /= total);
259
260 if self.top_p < 1.0 {
261 let mut cumsum = 0.0;
262 let mut keep = probs.len();
263 for (i, p) in probs.iter().enumerate() {
264 cumsum += p;
265 if cumsum >= self.top_p {
266 keep = i + 1;
267 break;
268 }
269 }
270 order.truncate(keep);
271 probs.truncate(keep);
272 }
273
274 let total: f32 = probs.iter().sum();
275 let mut r = self.rng.gen::<f32>() * total;
276 for (&idx, &p) in order.iter().zip(&probs) {
277 if r < p {
278 return idx as u32;
279 }
280 r -= p;
281 }
282 order[order.len() - 1] as u32
283 }
284}
285
286pub fn argmax(logits: &[f32]) -> u32 {
288 logits
289 .iter()
290 .enumerate()
291 .max_by(|a, b| a.1.total_cmp(b.1))
292 .map(|(i, _)| i as u32)
293 .unwrap_or(0)
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn greedy_picks_argmax() {
302 let mut s = Sampler::new(0.0, 0, 1.0, Some(1));
303 assert_eq!(s.sample(&[0.1, 3.0, -1.0, 2.9]), 1);
304 }
305
306 #[test]
307 fn top_k_one_is_greedy() {
308 let mut s = Sampler::new(1.5, 1, 1.0, Some(7));
309 for _ in 0..20 {
310 assert_eq!(s.sample(&[0.0, 0.5, 0.4]), 1);
311 }
312 }
313
314 #[test]
315 fn top_k_never_returns_tokens_outside_the_k_best() {
316 let mut s = Sampler::new(5.0, 2, 1.0, Some(3));
317 for _ in 0..200 {
318 let t = s.sample(&[1.0, 4.0, 3.9, 0.0, -2.0]);
319 assert!(t == 1 || t == 2, "sampled {t}");
320 }
321 }
322
323 #[test]
324 fn top_p_keeps_only_the_nucleus() {
325 let mut s = Sampler::new(1.0, 0, 0.5, Some(9));
327 for _ in 0..50 {
328 assert_eq!(s.sample(&[10.0, 0.0, 0.0, 0.0]), 0);
329 }
330 }
331
332 #[test]
333 fn seeded_sampling_is_reproducible() {
334 let logits = [0.3, 0.2, 0.1, 0.25, 0.15];
335 let mut a = Sampler::new(1.0, 0, 1.0, Some(42));
336 let mut b = Sampler::new(1.0, 0, 1.0, Some(42));
337 let xs: Vec<u32> = (0..32).map(|_| a.sample(&logits)).collect();
338 let ys: Vec<u32> = (0..32).map(|_| b.sample(&logits)).collect();
339 assert_eq!(xs, ys);
340 }
341
342 #[test]
343 fn chat_prompt_matches_chatml() {
344 assert_eq!(
345 Tokenizer::chat_prompt(Some("Be brief."), "Hi"),
346 "<|im_start|>system\nBe brief.<|im_end|>\n<|im_start|>user\nHi<|im_end|>\n<|im_start|>assistant\n"
347 );
348 }
349}