1use std::ffi::CString;
13use std::os::raw::c_char;
14use std::ptr::NonNull;
15
16use ik_llama_cpp_sys as sys;
17
18use crate::context::LlamaContext;
19use crate::grammar::GrammarInitError;
20use crate::model::LlamaModel;
21use crate::token::LlamaToken;
22
23pub use crate::token::data_array::LlamaTokenDataArray;
26
27#[derive(Debug, Clone)]
31struct Rng {
32 state: u64,
33}
34
35impl Rng {
36 fn new(seed: u32) -> Self {
37 let mut z = u64::from(seed).wrapping_add(0x9E37_79B9_7F4A_7C15);
38 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
39 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
40 Self {
41 state: (z ^ (z >> 31)) | 1,
42 }
43 }
44
45 fn next_u64(&mut self) -> u64 {
46 let mut x = self.state;
47 x ^= x >> 12;
48 x ^= x << 25;
49 x ^= x >> 27;
50 self.state = x;
51 x.wrapping_mul(0x2545_F491_4F6C_DD1D)
52 }
53
54 fn next_f32(&mut self) -> f32 {
56 let bits = (self.next_u64() >> 40) as u32; (bits as f32) / (1u32 << 24) as f32
58 }
59}
60
61#[derive(Debug)]
62enum Stage {
63 TopK(i32),
64 TopP {
65 p: f32,
66 min_keep: usize,
67 },
68 MinP {
69 p: f32,
70 min_keep: usize,
71 },
72 Typical {
73 p: f32,
74 min_keep: usize,
75 },
76 TopNSigma(f32),
77 TailFree {
78 z: f32,
79 min_keep: usize,
80 },
81 Temp(f32),
82 TempExt {
83 t: f32,
84 delta: f32,
85 exponent: f32,
86 },
87 Penalties {
88 last_n: i32,
89 repeat: f32,
90 freq: f32,
91 presence: f32,
92 },
93 Dist(Rng),
95 Greedy,
97 Grammar {
100 grammar: NonNull<sys::llama_grammar>,
101 vocab: *const sys::llama_vocab,
102 },
103}
104
105impl Drop for Stage {
106 fn drop(&mut self) {
107 if let Stage::Grammar { grammar, .. } = self {
108 unsafe { sys::llama_grammar_free(grammar.as_ptr()) };
111 }
112 }
113}
114
115impl Stage {
116 fn run(&mut self, arr: &mut LlamaTokenDataArray, hist: &[sys::llama_token]) {
117 let null = std::ptr::null_mut();
118 match self {
119 Stage::TopK(k) => {
120 unsafe {
122 arr.modify_as_c_llama_token_data_array(|c| {
123 sys::llama_sample_top_k(null, c, *k, 1)
124 });
125 }
126 }
127 Stage::TopP { p, min_keep } => unsafe {
128 arr.modify_as_c_llama_token_data_array(|c| {
129 sys::llama_sample_top_p(null, c, *p, *min_keep)
130 });
131 },
132 Stage::MinP { p, min_keep } => unsafe {
133 arr.modify_as_c_llama_token_data_array(|c| {
134 sys::llama_sample_min_p(null, c, *p, *min_keep)
135 });
136 },
137 Stage::Typical { p, min_keep } => unsafe {
138 arr.modify_as_c_llama_token_data_array(|c| {
139 sys::llama_sample_typical(null, c, *p, *min_keep)
140 });
141 },
142 Stage::TopNSigma(n) => unsafe {
143 arr.modify_as_c_llama_token_data_array(|c| {
144 sys::llama_sample_top_n_sigma(null, c, *n)
145 });
146 },
147 Stage::TailFree { z, min_keep } => unsafe {
148 arr.modify_as_c_llama_token_data_array(|c| {
149 sys::llama_sample_tail_free(null, c, *z, *min_keep)
150 });
151 },
152 Stage::Temp(t) => unsafe {
153 arr.modify_as_c_llama_token_data_array(|c| sys::llama_sample_temp(null, c, *t));
154 },
155 Stage::TempExt { t, delta, exponent } => unsafe {
156 arr.modify_as_c_llama_token_data_array(|c| {
157 sys::llama_sample_entropy(null, c, *t - *delta, *t + *delta, *exponent);
158 });
159 },
160 Stage::Penalties {
161 last_n,
162 repeat,
163 freq,
164 presence,
165 } => {
166 let use_n = (*last_n).max(0) as usize;
167 let use_n = use_n.min(hist.len());
168 let start = hist.len() - use_n;
169 let window = &hist[start..];
170 unsafe {
172 arr.modify_as_c_llama_token_data_array(|c| {
173 sys::llama_sample_repetition_penalties(
174 null,
175 c,
176 window.as_ptr(),
177 window.len(),
178 *repeat,
179 *freq,
180 *presence,
181 );
182 });
183 }
184 }
185 Stage::Dist(rng) => {
186 let r = rng.next_f32();
187 unsafe {
190 arr.modify_as_c_llama_token_data_array(|c| {
191 if c.size == 0 {
194 c.selected = -1;
195 return;
196 }
197 sys::llama_sample_softmax(null, c);
198 let data = std::slice::from_raw_parts(c.data, c.size);
199 let mut cum = 0.0_f32;
200 let mut chosen = (c.size - 1) as i64;
201 for (i, d) in data.iter().enumerate() {
202 cum += d.p;
203 if r < cum {
204 chosen = i as i64;
205 break;
206 }
207 }
208 c.selected = chosen;
209 });
210 }
211 }
212 Stage::Greedy => {
213 unsafe {
215 arr.modify_as_c_llama_token_data_array(|c| {
216 if c.size == 0 {
217 c.selected = -1;
218 return;
219 }
220 let data = std::slice::from_raw_parts(c.data, c.size);
221 let mut best = 0i64;
222 let mut best_logit = f32::NEG_INFINITY;
223 for (i, d) in data.iter().enumerate() {
224 if d.logit > best_logit {
225 best_logit = d.logit;
226 best = i as i64;
227 }
228 }
229 c.selected = best;
230 });
231 }
232 }
233 Stage::Grammar { grammar, vocab } => {
234 let g = grammar.as_ptr();
235 let v = *vocab;
236 unsafe {
239 arr.modify_as_c_llama_token_data_array(|c| {
240 sys::ik_llama_rs_grammar_apply(g, v, c);
241 });
242 }
243 }
244 }
245 }
246}
247
248#[derive(Debug)]
261pub struct LlamaSampler {
262 stages: Vec<Stage>,
263 history: Vec<LlamaToken>,
264}
265
266impl LlamaSampler {
267 fn single(stage: Stage) -> Self {
268 Self {
269 stages: vec![stage],
270 history: Vec::new(),
271 }
272 }
273
274 #[must_use]
276 pub fn chain_simple(samplers: impl IntoIterator<Item = LlamaSampler>) -> Self {
277 let mut stages = Vec::new();
278 for s in samplers {
279 stages.extend(s.stages);
280 }
281 Self {
282 stages,
283 history: Vec::new(),
284 }
285 }
286
287 #[must_use]
289 pub fn greedy() -> Self {
290 Self::single(Stage::Greedy)
291 }
292
293 #[must_use]
295 pub fn dist(seed: u32) -> Self {
296 Self::single(Stage::Dist(Rng::new(seed)))
297 }
298
299 #[must_use]
301 pub fn temp(t: f32) -> Self {
302 Self::single(Stage::Temp(t))
303 }
304
305 #[must_use]
307 pub fn temp_ext(t: f32, delta: f32, exponent: f32) -> Self {
308 Self::single(Stage::TempExt { t, delta, exponent })
309 }
310
311 #[must_use]
313 pub fn top_k(k: i32) -> Self {
314 Self::single(Stage::TopK(k))
315 }
316
317 #[must_use]
319 pub fn top_p(p: f32, min_keep: usize) -> Self {
320 Self::single(Stage::TopP { p, min_keep })
321 }
322
323 #[must_use]
325 pub fn min_p(p: f32, min_keep: usize) -> Self {
326 Self::single(Stage::MinP { p, min_keep })
327 }
328
329 #[must_use]
331 pub fn typical(p: f32, min_keep: usize) -> Self {
332 Self::single(Stage::Typical { p, min_keep })
333 }
334
335 #[must_use]
337 pub fn top_n_sigma(n: f32) -> Self {
338 Self::single(Stage::TopNSigma(n))
339 }
340
341 #[must_use]
343 pub fn tail_free(z: f32, min_keep: usize) -> Self {
344 Self::single(Stage::TailFree { z, min_keep })
345 }
346
347 pub fn grammar(
368 model: &LlamaModel,
369 grammar_str: &str,
370 root: &str,
371 ) -> Result<Self, GrammarInitError> {
372 let c_gbnf = CString::new(grammar_str)?;
373 let c_root = CString::new(root)?;
374 let vocab = unsafe { sys::llama_model_get_vocab(model.model.as_ptr()) };
376 let raw =
378 unsafe { sys::llama_sampler_init_grammar(vocab, c_gbnf.as_ptr(), c_root.as_ptr()) };
379 let grammar = NonNull::new(raw).ok_or(GrammarInitError::Parse)?;
380 Ok(Self::single(Stage::Grammar { grammar, vocab }))
381 }
382
383 pub fn grammar_lazy(
391 model: &LlamaModel,
392 grammar_str: &str,
393 root: &str,
394 trigger_words: impl IntoIterator<Item = impl AsRef<[u8]>>,
395 trigger_tokens: &[LlamaToken],
396 ) -> Result<Self, GrammarInitError> {
397 let c_gbnf = CString::new(grammar_str)?;
398 let c_root = CString::new(root)?;
399 let words: Vec<CString> = trigger_words
400 .into_iter()
401 .map(|w| CString::new(w.as_ref()))
402 .collect::<Result<_, _>>()?;
403 let mut word_ptrs: Vec<*const c_char> = words.iter().map(|c| c.as_ptr()).collect();
404 let tokens: Vec<sys::llama_token> = trigger_tokens.iter().map(|t| t.0).collect();
405 let vocab = unsafe { sys::llama_model_get_vocab(model.model.as_ptr()) };
407 let raw = unsafe {
410 sys::llama_sampler_init_grammar_lazy(
411 vocab,
412 c_gbnf.as_ptr(),
413 c_root.as_ptr(),
414 word_ptrs.as_mut_ptr(),
415 word_ptrs.len(),
416 tokens.as_ptr(),
417 tokens.len(),
418 )
419 };
420 let grammar = NonNull::new(raw).ok_or(GrammarInitError::Parse)?;
421 Ok(Self::single(Stage::Grammar { grammar, vocab }))
422 }
423
424 #[must_use]
426 pub fn penalties(
427 penalty_last_n: i32,
428 penalty_repeat: f32,
429 penalty_freq: f32,
430 penalty_present: f32,
431 ) -> Self {
432 Self::single(Stage::Penalties {
433 last_n: penalty_last_n,
434 repeat: penalty_repeat,
435 freq: penalty_freq,
436 presence: penalty_present,
437 })
438 }
439
440 pub fn apply(&mut self, arr: &mut LlamaTokenDataArray) {
447 if arr.data.is_empty() {
448 return;
449 }
450 let hist: Vec<sys::llama_token> = self.history.iter().map(|t| t.0).collect();
451 for stage in &mut self.stages {
452 stage.run(arr, &hist);
453 }
454 }
455
456 pub fn accept(&mut self, token: LlamaToken) {
464 self.history.push(token);
465 for stage in &mut self.stages {
466 if let Stage::Grammar { grammar, vocab } = stage {
467 unsafe { sys::ik_llama_rs_grammar_accept(grammar.as_ptr(), *vocab, token.0) };
470 }
471 }
472 }
473
474 pub fn reset(&mut self) {
476 self.history.clear();
477 }
478
479 pub fn sample(&mut self, ctx: &LlamaContext, idx: i32) -> LlamaToken {
483 let mut arr = ctx.token_data_array_ith(idx);
484 self.apply(&mut arr);
485 arr.selected_token().unwrap_or_else(|| {
486 arr.data
487 .iter()
488 .max_by(|a, b| {
489 a.logit()
490 .partial_cmp(&b.logit())
491 .unwrap_or(std::cmp::Ordering::Equal)
492 })
493 .map_or(LlamaToken(0), crate::token::data::LlamaTokenData::id)
494 })
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use crate::token::data::LlamaTokenData;
502
503 #[test]
504 fn greedy_selects_argmax() {
505 let mut arr = LlamaTokenDataArray::from_logits(&[0.1, 2.5, -1.0, 2.4, 0.0]);
506 let mut s = LlamaSampler::greedy();
507 s.apply(&mut arr);
508 assert_eq!(s.stages.len(), 1);
509 assert_eq!(arr.selected_token(), Some(LlamaToken(1)));
510 }
511
512 #[test]
513 fn dist_is_deterministic_for_a_seed() {
514 let logits = [0.2_f32, 1.0, 0.5, 3.0, -0.5];
515 let mut a = LlamaSampler::dist(1234);
516 let mut b = LlamaSampler::dist(1234);
517 let mut arr_a = LlamaTokenDataArray::from_logits(&logits);
518 let mut arr_b = LlamaTokenDataArray::from_logits(&logits);
519 a.apply(&mut arr_a);
520 b.apply(&mut arr_b);
521 assert!(arr_a.selected_token().is_some());
522 assert_eq!(arr_a.selected_token(), arr_b.selected_token());
523 }
524
525 #[test]
526 fn apply_on_empty_array_is_noop_not_abort() {
527 let mut arr = LlamaTokenDataArray::new(Vec::new(), false);
530 let mut s = LlamaSampler::chain_simple([
531 LlamaSampler::top_k(3),
532 LlamaSampler::top_p(0.9, 1),
533 LlamaSampler::temp(0.8),
534 LlamaSampler::dist(1),
535 ]);
536 s.apply(&mut arr);
537 assert_eq!(arr.selected_token(), None);
538 }
539
540 #[test]
541 fn chain_composes_stages() {
542 let s = LlamaSampler::chain_simple([
543 LlamaSampler::top_k(3),
544 LlamaSampler::temp(0.8),
545 LlamaSampler::greedy(),
546 ]);
547 assert_eq!(s.stages.len(), 3);
548 let mut s = s;
550 s.accept(LlamaTokenData::new(LlamaToken(5), 0.0, 0.0).id());
551 assert_eq!(s.history, vec![LlamaToken(5)]);
552 }
553}