llama_cpp_4/sampling.rs
1//! Safe wrapper around `llama_sampler`.
2
3use std::borrow::Borrow;
4use std::ffi::{c_char, CString};
5use std::fmt::{Debug, Formatter};
6use std::ptr::NonNull;
7
8use llama_cpp_sys_4::{
9 common::common_sampler_params, llama_logit_bias, llama_sampler, llama_sampler_chain_add,
10 llama_sampler_chain_default_params, llama_sampler_chain_init, llama_sampler_chain_n,
11 llama_sampler_chain_remove, llama_sampler_clone, llama_sampler_copy, llama_sampler_free,
12 llama_sampler_get_seed, llama_sampler_init_adaptive_p, llama_sampler_init_dist,
13 llama_sampler_init_dry, llama_sampler_init_grammar, llama_sampler_init_grammar_lazy_patterns,
14 llama_sampler_init_greedy, llama_sampler_init_infill, llama_sampler_init_logit_bias,
15 llama_sampler_init_min_p, llama_sampler_init_mirostat, llama_sampler_init_mirostat_v2,
16 llama_sampler_init_penalties, llama_sampler_init_temp, llama_sampler_init_temp_ext,
17 llama_sampler_init_top_k, llama_sampler_init_top_n_sigma, llama_sampler_init_top_p,
18 llama_sampler_init_typical, llama_sampler_init_xtc, llama_sampler_name, llama_sampler_reset,
19};
20
21use crate::context::LlamaContext;
22use crate::model::LlamaModel;
23use crate::token::data_array::LlamaTokenDataArray;
24use crate::token::LlamaToken;
25
26/// Why a sampler could not be constructed.
27///
28/// The panicking constructors — [`LlamaSampler::grammar`] and friends — are
29/// kept for the common case where the grammar is a literal or came from
30/// [`ChatParams::grammar`](crate::chat::ChatParams), and a failure really is a
31/// bug. These exist for the case they handle badly: a grammar, rule name or
32/// trigger pattern that arrived from a request, where an interior NUL is bad
33/// *input* rather than a broken invariant.
34#[derive(Debug, PartialEq, Eq, thiserror::Error)]
35pub enum SamplerInitError {
36 /// A caller-supplied string contained an interior NUL, so it cannot cross
37 /// the FFI boundary. `field` names which argument.
38 #[error("{field} contains an interior NUL byte")]
39 InteriorNul {
40 /// Name of the offending argument.
41 field: &'static str,
42 /// The underlying conversion error.
43 #[source]
44 source: std::ffi::NulError,
45 },
46 /// llama.cpp returned null. For a grammar sampler this is what an
47 /// unparseable GBNF grammar looks like; it logs the parse error itself.
48 #[error("llama.cpp could not build the {0} sampler (see the llama.cpp log)")]
49 NullReturn(&'static str),
50 /// More distinct DRY sequence breakers than llama.cpp will accept. It
51 /// silently drops the surplus, so this is reported rather than passed on.
52 #[error("{distinct} distinct DRY sequence breakers, but llama.cpp accepts at most {max}")]
53 TooManyDrySequenceBreakers {
54 /// How many distinct breakers the caller supplied, counted the way
55 /// llama.cpp counts them.
56 distinct: usize,
57 /// The upstream limit, [`DRY_MAX_SEQ_BREAKERS`].
58 max: usize,
59 },
60}
61
62/// Longest DRY sequence breaker llama.cpp keeps; it truncates anything longer.
63///
64/// Truncation happens *before* de-duplication, so two breakers that differ only
65/// past this point count as one.
66pub const DRY_MAX_BREAKER_LEN: usize = 40;
67
68/// Most distinct DRY sequence breakers llama.cpp will process.
69///
70/// Mirrors `LLAMA_DRY_MAX_SEQ_BREAKERS`. Past this, llama.cpp logs a warning
71/// and ignores the rest — the sampler is then quietly not the one that was
72/// asked for, which is why [`LlamaSampler::try_dry`] refuses instead. Each
73/// breaker scans the whole vocabulary during init, so the cost is
74/// `O(breakers x n_vocab)`.
75pub const DRY_MAX_SEQ_BREAKERS: usize = llama_cpp_sys_4::LLAMA_DRY_MAX_SEQ_BREAKERS as usize;
76
77/// Count breakers the way llama.cpp does: truncate to [`DRY_MAX_BREAKER_LEN`]
78/// bytes, drop empties, then de-duplicate.
79fn distinct_dry_breakers(breakers: &[CString]) -> usize {
80 breakers
81 .iter()
82 .map(|b| {
83 let bytes = b.as_bytes();
84 &bytes[..bytes.len().min(DRY_MAX_BREAKER_LEN)]
85 })
86 .filter(|b| !b.is_empty())
87 .collect::<std::collections::BTreeSet<_>>()
88 .len()
89}
90
91/// Convert a caller string, naming the argument so the error says which one.
92fn c_string(field: &'static str, value: &str) -> Result<CString, SamplerInitError> {
93 CString::new(value).map_err(|source| SamplerInitError::InteriorNul { field, source })
94}
95
96/// A safe wrapper around `llama_sampler`.
97pub struct LlamaSampler {
98 pub(crate) sampler: NonNull<llama_sampler>,
99}
100
101impl Debug for LlamaSampler {
102 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
103 f.debug_struct("LlamaSamplerChain").finish()
104 }
105}
106#[derive(Debug, Clone)]
107#[allow(
108 missing_docs,
109 clippy::struct_excessive_bools,
110 clippy::module_name_repetitions,
111 dead_code
112)]
113pub struct LlamaSamplerParams {
114 top_k: i32,
115 top_p: f32,
116 temp: f32,
117 seed: u32,
118}
119
120impl LlamaSamplerParams {
121 /// Set the seed of the context
122 ///
123 /// # Examples
124 ///
125 /// ```rust
126 /// use llama_cpp_4::sampling::LlamaSamplerParams;
127 /// let params = LlamaSamplerParams::default();
128 /// let params = params.with_seed(1234);
129 /// assert_eq!(params.seed(), 1234);
130 /// ```
131 #[must_use]
132 pub fn with_seed(mut self, seed: u32) -> Self {
133 self.seed = seed;
134 self
135 }
136
137 /// Get the seed of the context
138 ///
139 /// # Examples
140 ///
141 /// ```rust
142 /// use llama_cpp_4::sampling::LlamaSamplerParams;
143 /// let params = LlamaSamplerParams::default()
144 /// .with_seed(1234);
145 /// assert_eq!(params.seed(), 1234);
146 /// ```
147 #[must_use]
148 pub fn seed(&self) -> u32 {
149 self.seed
150 }
151}
152
153impl Default for LlamaSamplerParams {
154 fn default() -> Self {
155 Self {
156 top_k: 50,
157 top_p: 0.9,
158 temp: 0.8,
159 seed: 1234,
160 }
161 }
162}
163
164impl Default for LlamaSampler {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170impl LlamaSampler {
171 /// Create new sampler with default params.
172 ///
173 /// # Panics
174 ///
175 /// Panics if llama.cpp returns a null pointer.
176 #[must_use]
177 pub fn new() -> Self {
178 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
179 // the library owns whatever it returns.
180 let sparams = unsafe { llama_sampler_chain_default_params() };
181
182 Self {
183 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
184 // the library owns whatever it returns.
185 sampler: NonNull::new(unsafe { llama_sampler_chain_init(sparams) }).unwrap(),
186 }
187 }
188
189 /// Sample and accept a token from the idx-th output of the last evaluation
190 #[must_use]
191 /// # Panics
192 ///
193 /// Panics if llama.cpp's grammar code rejects the state — see
194 /// [`Self::try_sample`], which returns the error instead. This *panics*
195 /// rather than aborting because the call is routed through a guard; calling
196 /// `llama_sampler_sample` directly would let a C++ exception unwind into
197 /// Rust and kill the process.
198 pub fn sample(&self, ctx: &LlamaContext, idx: i32) -> LlamaToken {
199 self.try_sample(ctx, idx)
200 .unwrap_or_else(|e| panic!("sampling failed: {e}"))
201 }
202
203 /// Sample a token, reporting llama.cpp's failures instead of panicking.
204 ///
205 /// The realistic failure is a grammar that can no longer accept anything:
206 /// llama.cpp raises *"Unexpected empty grammar stack"* when a model's
207 /// vocabulary cannot satisfy the constraint — a JSON schema against a
208 /// vocabulary with no `{`, say. That is a property of the model/grammar
209 /// pair, not a bug, so it is worth handling.
210 ///
211 /// # Errors
212 ///
213 /// Returns [`ShimError::Failed`](crate::shim::ShimError::Failed) carrying
214 /// llama.cpp's message.
215 pub fn try_sample(
216 &self,
217 ctx: &LlamaContext,
218 idx: i32,
219 ) -> Result<LlamaToken, crate::shim::ShimError> {
220 let mut status = llama_cpp_sys_4::LLAMA_SHIM_OK;
221 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
222 // arguments are scalars the library validates itself.
223 let token = unsafe {
224 llama_cpp_sys_4::common_shim_sampler_sample_raw(
225 self.sampler.as_ptr(),
226 ctx.context.as_ptr(),
227 idx,
228 &raw mut status,
229 )
230 };
231 crate::shim::check_status(status)?;
232 Ok(LlamaToken(token))
233 }
234
235 /// Applies this sampler to a [`LlamaTokenDataArray`].
236 pub fn apply(&mut self, data_array: &mut LlamaTokenDataArray) {
237 data_array.apply_sampler(self);
238 }
239
240 /// Accepts a token from the sampler, possibly updating the internal state of certain samplers
241 /// (e.g. grammar, repetition, etc.)
242 /// # Panics
243 ///
244 /// Panics if the token leaves a grammar with nothing it can accept — see
245 /// [`Self::try_accept`].
246 pub fn accept(&mut self, token: LlamaToken) {
247 self.try_accept(token)
248 .unwrap_or_else(|e| panic!("accepting token {}: {e}", token.0));
249 }
250
251 /// Accept a token, reporting llama.cpp's failures instead of panicking.
252 ///
253 /// # Errors
254 ///
255 /// Returns [`ShimError::Failed`](crate::shim::ShimError::Failed) when the
256 /// grammar cannot accept this token — see [`Self::try_sample`].
257 pub fn try_accept(&mut self, token: LlamaToken) -> Result<(), crate::shim::ShimError> {
258 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
259 // arguments are scalars the library validates itself.
260 let status = unsafe {
261 llama_cpp_sys_4::common_shim_sampler_accept_raw(self.sampler.as_ptr(), token.0)
262 };
263 crate::shim::check_status(status)
264 }
265
266 /// Accepts several tokens from the sampler or context, possibly updating the internal state of
267 /// certain samplers (e.g. grammar, repetition, etc.)
268 /// # Panics
269 ///
270 /// Panics on the first token the grammar cannot accept — see
271 /// [`Self::try_accept`].
272 pub fn accept_many(&mut self, tokens: impl IntoIterator<Item = impl Borrow<LlamaToken>>) {
273 for token in tokens {
274 self.accept(*token.borrow());
275 }
276 }
277
278 /// Accepts several tokens from the sampler or context, possibly updating the internal state of
279 /// certain samplers (e.g. grammar, repetition, etc.)
280 #[must_use]
281 pub fn with_tokens(
282 mut self,
283 tokens: impl IntoIterator<Item = impl Borrow<LlamaToken>>,
284 ) -> Self {
285 self.accept_many(tokens);
286 self
287 }
288
289 /// Combines a list of samplers into a single sampler that applies each component sampler one
290 /// after another.
291 ///
292 /// If you are using a chain to select a token, the chain should always end with one of
293 /// [`LlamaSampler::greedy`], [`LlamaSampler::dist`], [`LlamaSampler::mirostat`], and
294 /// [`LlamaSampler::mirostat_v2`].
295 ///
296 /// # Panics
297 ///
298 /// Panics if llama.cpp returns a null pointer.
299 #[must_use]
300 pub fn chain(samplers: impl IntoIterator<Item = Self>, no_perf: bool) -> Self {
301 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
302 // the library owns whatever it returns.
303 unsafe {
304 let mut params = llama_sampler_chain_default_params();
305 params.no_perf = no_perf;
306 let chain = llama_sampler_chain_init(params);
307
308 for sampler in samplers {
309 llama_sampler_chain_add(chain, sampler.sampler.as_ptr());
310
311 // Do not call `llama_sampler_free` on the sampler, as the internal sampler is now
312 // owned by the chain
313 std::mem::forget(sampler);
314 }
315
316 Self {
317 sampler: NonNull::new(chain).unwrap(),
318 }
319 }
320 }
321
322 /// Same as [`Self::chain`] with `no_perf = false`.
323 ///
324 /// # Panics
325 ///
326 /// Panics if llama.cpp returns a null pointer.
327 ///
328 /// # Example
329 /// ```rust
330 /// use llama_cpp_4::token::{
331 /// LlamaToken,
332 /// data::LlamaTokenData,
333 /// data_array::LlamaTokenDataArray
334 /// };
335 /// use llama_cpp_4::sampling::LlamaSampler;
336 ///
337 /// let mut data_array = LlamaTokenDataArray::new(vec![
338 /// LlamaTokenData::new(LlamaToken(0), 0., 0.),
339 /// LlamaTokenData::new(LlamaToken(1), 1., 0.),
340 /// LlamaTokenData::new(LlamaToken(2), 2., 0.),
341 /// ], false);
342 ///
343 /// data_array.apply_sampler(&mut LlamaSampler::chain_simple([
344 /// LlamaSampler::temp(0.5),
345 /// LlamaSampler::greedy(),
346 /// ]));
347 ///
348 /// assert_eq!(data_array.data[0].logit(), 0.);
349 /// assert_eq!(data_array.data[1].logit(), 2.);
350 /// assert_eq!(data_array.data[2].logit(), 4.);
351 ///
352 /// assert_eq!(data_array.data.len(), 3);
353 /// assert_eq!(data_array.selected_token(), Some(LlamaToken(2)));
354 /// ```
355 #[must_use]
356 pub fn chain_simple(samplers: impl IntoIterator<Item = Self>) -> Self {
357 Self::chain(samplers, false)
358 }
359
360 /// Updates the logits `l_i`' = `l_i/t`. When `t <= 0.0`, the maximum logit is kept at its original
361 /// value, the rest are set to -inf.
362 ///
363 /// # Panics
364 ///
365 /// Panics if llama.cpp returns a null pointer.
366 ///
367 /// # Example:
368 /// ```rust
369 /// use llama_cpp_4::token::{
370 /// LlamaToken,
371 /// data::LlamaTokenData,
372 /// data_array::LlamaTokenDataArray
373 /// };
374 /// use llama_cpp_4::sampling::LlamaSampler;
375 ///
376 /// let mut data_array = LlamaTokenDataArray::new(vec![
377 /// LlamaTokenData::new(LlamaToken(0), 0., 0.),
378 /// LlamaTokenData::new(LlamaToken(1), 1., 0.),
379 /// LlamaTokenData::new(LlamaToken(2), 2., 0.),
380 /// ], false);
381 ///
382 /// data_array.apply_sampler(&mut LlamaSampler::temp(0.5));
383 ///
384 /// assert_eq!(data_array.data[0].logit(), 0.);
385 /// assert_eq!(data_array.data[1].logit(), 2.);
386 /// assert_eq!(data_array.data[2].logit(), 4.);
387 /// ```
388 #[must_use]
389 pub fn temp(t: f32) -> Self {
390 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
391 // the library owns whatever it returns.
392 let sampler = unsafe { llama_sampler_init_temp(t) };
393 Self {
394 sampler: NonNull::new(sampler).unwrap(),
395 }
396 }
397
398 /// Dynamic temperature implementation (a.k.a. entropy) described in the paper
399 /// <https://arxiv.org/abs/2309.02772>.
400 ///
401 /// # Panics
402 ///
403 /// Panics if llama.cpp returns a null pointer.
404 #[must_use]
405 pub fn temp_ext(t: f32, delta: f32, exponent: f32) -> Self {
406 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
407 // the library owns whatever it returns.
408 let sampler = unsafe { llama_sampler_init_temp_ext(t, delta, exponent) };
409 Self {
410 sampler: NonNull::new(sampler).unwrap(),
411 }
412 }
413
414 /// Top-K sampling described in academic paper "The Curious Case of Neural Text Degeneration"
415 /// <https://arxiv.org/abs/1904.09751>.
416 ///
417 /// # Panics
418 ///
419 /// Panics if llama.cpp returns a null pointer.
420 ///
421 /// # Example:
422 /// ```rust
423 /// use llama_cpp_4::token::{
424 /// LlamaToken,
425 /// data::LlamaTokenData,
426 /// data_array::LlamaTokenDataArray
427 /// };
428 /// use llama_cpp_4::sampling::LlamaSampler;
429 ///
430 /// let mut data_array = LlamaTokenDataArray::new(vec![
431 /// LlamaTokenData::new(LlamaToken(0), 0., 0.),
432 /// LlamaTokenData::new(LlamaToken(1), 1., 0.),
433 /// LlamaTokenData::new(LlamaToken(2), 2., 0.),
434 /// LlamaTokenData::new(LlamaToken(3), 3., 0.),
435 /// ], false);
436 ///
437 /// data_array.apply_sampler(&mut LlamaSampler::top_k(2));
438 ///
439 /// assert_eq!(data_array.data.len(), 2);
440 /// assert_eq!(data_array.data[0].id(), LlamaToken(3));
441 /// assert_eq!(data_array.data[1].id(), LlamaToken(2));
442 /// ```
443 #[must_use]
444 pub fn top_k(k: i32) -> Self {
445 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
446 // the library owns whatever it returns.
447 let sampler = unsafe { llama_sampler_init_top_k(k) };
448 Self {
449 sampler: NonNull::new(sampler).unwrap(),
450 }
451 }
452
453 /// Locally Typical Sampling implementation described in the paper <https://arxiv.org/abs/2202.00666>.
454 ///
455 /// # Panics
456 ///
457 /// Panics if llama.cpp returns a null pointer.
458 #[must_use]
459 pub fn typical(p: f32, min_keep: usize) -> Self {
460 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
461 // the library owns whatever it returns.
462 let sampler = unsafe { llama_sampler_init_typical(p, min_keep) };
463 Self {
464 sampler: NonNull::new(sampler).unwrap(),
465 }
466 }
467
468 /// Nucleus sampling described in academic paper "The Curious Case of Neural Text Degeneration"
469 /// <https://arxiv.org/abs/1904.09751>.
470 ///
471 /// # Panics
472 ///
473 /// Panics if llama.cpp returns a null pointer.
474 #[must_use]
475 pub fn top_p(p: f32, min_keep: usize) -> Self {
476 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
477 // the library owns whatever it returns.
478 let sampler = unsafe { llama_sampler_init_top_p(p, min_keep) };
479 Self {
480 sampler: NonNull::new(sampler).unwrap(),
481 }
482 }
483
484 /// Minimum P sampling as described in <https://github.com/ggerganov/llama.cpp/pull/3841>.
485 ///
486 /// # Panics
487 ///
488 /// Panics if llama.cpp returns a null pointer.
489 #[must_use]
490 pub fn min_p(p: f32, min_keep: usize) -> Self {
491 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
492 // the library owns whatever it returns.
493 let sampler = unsafe { llama_sampler_init_min_p(p, min_keep) };
494 Self {
495 sampler: NonNull::new(sampler).unwrap(),
496 }
497 }
498
499 /// XTC sampler as described in <https://github.com/oobabooga/text-generation-webui/pull/6335>.
500 ///
501 /// # Panics
502 ///
503 /// Panics if llama.cpp returns a null pointer.
504 #[must_use]
505 pub fn xtc(p: f32, t: f32, min_keep: usize, seed: u32) -> Self {
506 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
507 // the library owns whatever it returns.
508 let sampler = unsafe { llama_sampler_init_xtc(p, t, min_keep, seed) };
509 Self {
510 sampler: NonNull::new(sampler).unwrap(),
511 }
512 }
513
514 /// Grammar sampler
515 ///
516 /// # Panics
517 /// - If either of `grammar_str` or `grammar_root` contain null bytes.
518 /// - If llama.cpp returns a null pointer, which is also what an
519 /// unparseable GBNF grammar looks like.
520 ///
521 /// Prefer [`Self::try_grammar`] when the grammar did not come from your own
522 /// source — a grammar built from a request is input, and both failures above
523 /// are then reachable without a bug.
524 #[must_use]
525 pub fn grammar(model: &LlamaModel, grammar_str: &str, grammar_root: &str) -> Self {
526 Self::try_grammar(model, grammar_str, grammar_root)
527 .unwrap_or_else(|e| panic!("building grammar sampler: {e}"))
528 }
529
530 /// [`Self::grammar`], reporting llama.cpp's and the conversion's failures
531 /// instead of panicking.
532 ///
533 /// # Errors
534 ///
535 /// [`SamplerInitError::InteriorNul`] if either string contains a NUL, or
536 /// [`SamplerInitError::NullReturn`] if llama.cpp rejected the grammar.
537 pub fn try_grammar(
538 model: &LlamaModel,
539 grammar_str: &str,
540 grammar_root: &str,
541 ) -> Result<Self, SamplerInitError> {
542 let grammar_str = c_string("grammar_str", grammar_str)?;
543 let grammar_root = c_string("grammar_root", grammar_root)?;
544
545 // SAFETY: both pointers come from live `CString`s that outlast the call,
546 // and llama.cpp only reads them. The vocab reference is borrowed from
547 // `model` for the duration.
548 let sampler = unsafe {
549 llama_sampler_init_grammar(
550 model.get_vocab().vocab.as_ref(),
551 grammar_str.as_ptr(),
552 grammar_root.as_ptr(),
553 )
554 };
555 NonNull::new(sampler)
556 .map(|sampler| Self { sampler })
557 .ok_or(SamplerInitError::NullReturn("grammar"))
558 }
559
560 /// DRY sampler, designed by p-e-w, as described in:
561 /// <https://github.com/oobabooga/text-generation-webui/pull/5677>, porting Koboldcpp
562 /// implementation authored by pi6am: <https://github.com/LostRuins/koboldcpp/pull/982>
563 ///
564 /// # Panics
565 /// - If any string in `seq_breakers` contains null bytes.
566 /// - If llama.cpp returns a null pointer.
567 #[allow(clippy::too_many_arguments)]
568 #[must_use]
569 pub fn dry(
570 &self,
571 model: &LlamaModel,
572 multiplier: f32,
573 base: f32,
574 allowed_length: i32,
575 penalty_last_n: i32,
576 seq_breakers: impl IntoIterator<Item = impl AsRef<[u8]>>,
577 ) -> Self {
578 // A NUL in a sequence breaker is the same class of input error the
579 // grammar constructors have a `try_` variant for; `dry` keeps the panic
580 // because its breakers are conventionally literals (`"\n"`, `":"`).
581 let seq_breakers: Vec<CString> = seq_breakers
582 .into_iter()
583 .map(|s| CString::new(s.as_ref()).expect("sequence breaker contains an interior NUL"))
584 .collect();
585 // CString::as_ptr() returns *const c_char, which matches what the binding
586 // expects on every platform (signed on macOS/x86 Linux, unsigned on musl ARM).
587 let mut seq_breaker_pointers: Vec<*const c_char> =
588 seq_breakers.iter().map(|s| s.as_ptr()).collect();
589
590 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
591 // the library owns whatever it returns.
592 let sampler = unsafe {
593 llama_sampler_init_dry(
594 model.get_vocab().vocab.as_ref(),
595 multiplier,
596 base,
597 allowed_length,
598 penalty_last_n,
599 seq_breaker_pointers.as_mut_ptr(),
600 seq_breaker_pointers.len(),
601 )
602 };
603
604 Self {
605 sampler: NonNull::new(sampler).unwrap(),
606 }
607 }
608
609 /// [`Self::dry`], but reporting the two things it cannot.
610 ///
611 /// `dry` panics on a breaker containing an interior NUL, and — worse —
612 /// cannot react at all when there are too many breakers: llama.cpp keeps
613 /// the first [`DRY_MAX_SEQ_BREAKERS`] distinct ones, logs a warning, and
614 /// returns a perfectly valid sampler that simply is not the one you asked
615 /// for. Prefer this whenever the breakers come from configuration or user
616 /// input rather than from literals.
617 ///
618 /// # Errors
619 ///
620 /// - [`SamplerInitError::InteriorNul`] if a breaker contains a NUL byte.
621 /// - [`SamplerInitError::TooManyDrySequenceBreakers`] if more than
622 /// [`DRY_MAX_SEQ_BREAKERS`] remain after llama.cpp's own truncation to
623 /// [`DRY_MAX_BREAKER_LEN`] bytes and de-duplication.
624 /// - [`SamplerInitError::NullReturn`] if llama.cpp returns null.
625 pub fn try_dry(
626 &self,
627 model: &LlamaModel,
628 multiplier: f32,
629 base: f32,
630 allowed_length: i32,
631 penalty_last_n: i32,
632 seq_breakers: impl IntoIterator<Item = impl AsRef<[u8]>>,
633 ) -> Result<Self, SamplerInitError> {
634 let seq_breakers: Vec<CString> = seq_breakers
635 .into_iter()
636 .map(|s| {
637 CString::new(s.as_ref()).map_err(|source| SamplerInitError::InteriorNul {
638 field: "sequence breaker",
639 source,
640 })
641 })
642 .collect::<Result<_, _>>()?;
643
644 let distinct = distinct_dry_breakers(&seq_breakers);
645 if distinct > DRY_MAX_SEQ_BREAKERS {
646 return Err(SamplerInitError::TooManyDrySequenceBreakers {
647 distinct,
648 max: DRY_MAX_SEQ_BREAKERS,
649 });
650 }
651
652 let mut seq_breaker_pointers: Vec<*const c_char> =
653 seq_breakers.iter().map(|s| s.as_ptr()).collect();
654
655 // SAFETY: the vocab outlives the call, and the pointer array is valid
656 // for `len` entries whose backing `CString`s are still alive here.
657 let sampler = unsafe {
658 llama_sampler_init_dry(
659 model.get_vocab().vocab.as_ref(),
660 multiplier,
661 base,
662 allowed_length,
663 penalty_last_n,
664 seq_breaker_pointers.as_mut_ptr(),
665 seq_breaker_pointers.len(),
666 )
667 };
668
669 NonNull::new(sampler)
670 .map(|sampler| Self { sampler })
671 .ok_or(SamplerInitError::NullReturn("dry"))
672 }
673
674 /// Penalizes tokens for being present in the context.
675 ///
676 /// Parameters:
677 /// - `n_vocab`: [`LlamaModel::n_vocab`]
678 /// - `penalty_last_n`: last n tokens to penalize (0 = disable penalty)
679 /// - `penalty_repeat`: repetition penalty (must be > 0.0, 1.0 = disabled)
680 /// - `penalty_freq`: frequency penalty (must be finite, 0.0 = disabled)
681 /// - `penalty_present`: presence penalty (must be finite, 0.0 = disabled)
682 ///
683 /// If `penalty_last_n` is `0`, or every penalty sits at its disabled value,
684 /// llama.cpp returns a no-op sampler named `"?penalties"` — see
685 /// [`Self::name`].
686 ///
687 /// # Panics
688 ///
689 /// Panics if llama.cpp returns a null pointer.
690 #[allow(clippy::too_many_arguments)]
691 #[must_use]
692 pub fn penalties(
693 n_vocab: i32,
694 penalty_last_n: i32,
695 penalty_repeat: f32,
696 penalty_freq: f32,
697 penalty_present: f32,
698 ) -> Self {
699 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
700 // the library owns whatever it returns.
701 let sampler = unsafe {
702 llama_sampler_init_penalties(
703 n_vocab,
704 penalty_last_n,
705 penalty_repeat,
706 penalty_freq,
707 penalty_present,
708 )
709 };
710 Self {
711 sampler: NonNull::new(sampler).unwrap(),
712 }
713 }
714
715 /// Same as [`Self::penalties`] with sensible defaults:
716 /// `penalty_freq = 0.0` and `penalty_present = 0.0`.
717 ///
718 /// Parameters:
719 /// - `n_vocab`: [`LlamaModel::n_vocab`]
720 /// - `penalty_last_n`: last n tokens to penalize (0 = disable)
721 /// - `penalty_repeat`: repetition penalty (must be > 0.0, 1.0 = disabled)
722 ///
723 /// # Panics
724 ///
725 /// Panics if llama.cpp returns a null pointer.
726 #[must_use]
727 pub fn penalties_simple(n_vocab: i32, penalty_last_n: i32, penalty_repeat: f32) -> Self {
728 Self::penalties(
729 n_vocab,
730 #[allow(clippy::cast_precision_loss)]
731 {
732 penalty_last_n
733 },
734 #[allow(clippy::cast_precision_loss)]
735 {
736 penalty_repeat
737 },
738 #[allow(clippy::cast_precision_loss)]
739 {
740 0.0_f32
741 },
742 #[allow(clippy::cast_precision_loss)]
743 {
744 0.0_f32
745 },
746 )
747 }
748
749 /// Mirostat 1.0 algorithm described in the paper <https://arxiv.org/abs/2007.14966>. Uses tokens instead of words.
750 ///
751 /// # Panics
752 ///
753 /// Panics if llama.cpp returns a null pointer.
754 ///
755 /// # Parameters:
756 /// - `n_vocab`: [`LlamaModel::n_vocab`]
757 /// - `seed`: Seed to initialize random generation with.
758 /// - `tau`: The target cross-entropy (or surprise) value you want to achieve for the
759 /// generated text. A higher value corresponds to more surprising or less predictable text,
760 /// while a lower value corresponds to less surprising or more predictable text.
761 /// - `eta`: The learning rate used to update `mu` based on the error between the target and
762 /// observed surprisal of the sampled word. A larger learning rate will cause `mu` to be
763 /// updated more quickly, while a smaller learning rate will result in slower updates.
764 /// - `m`: The number of tokens considered in the estimation of `s_hat`. This is an arbitrary
765 /// value that is used to calculate `s_hat`, which in turn helps to calculate the value of `k`.
766 /// In the paper, they use `m = 100`, but you can experiment with different values to see how
767 /// it affects the performance of the algorithm.
768 #[must_use]
769 pub fn mirostat(n_vocab: i32, seed: u32, tau: f32, eta: f32, m: i32) -> Self {
770 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
771 // the library owns whatever it returns.
772 let sampler = unsafe { llama_sampler_init_mirostat(n_vocab, seed, tau, eta, m) };
773 Self {
774 sampler: NonNull::new(sampler).unwrap(),
775 }
776 }
777
778 /// Mirostat 2.0 algorithm described in the paper <https://arxiv.org/abs/2007.14966>. Uses tokens instead of words.
779 ///
780 /// # Panics
781 ///
782 /// Panics if llama.cpp returns a null pointer.
783 ///
784 /// # Parameters:
785 /// - `seed`: Seed to initialize random generation with.
786 /// - `tau`: The target cross-entropy (or surprise) value you want to achieve for the
787 /// generated text. A higher value corresponds to more surprising or less predictable text,
788 /// while a lower value corresponds to less surprising or more predictable text.
789 /// - `eta`: The learning rate used to update `mu` based on the error between the target and
790 /// observed surprisal of the sampled word. A larger learning rate will cause `mu` to be
791 /// updated more quickly, while a smaller learning rate will result in slower updates.
792 #[must_use]
793 pub fn mirostat_v2(seed: u32, tau: f32, eta: f32) -> Self {
794 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
795 // the library owns whatever it returns.
796 let sampler = unsafe { llama_sampler_init_mirostat_v2(seed, tau, eta) };
797 Self {
798 sampler: NonNull::new(sampler).unwrap(),
799 }
800 }
801
802 /// Selects a token at random based on each token's probabilities.
803 ///
804 /// # Panics
805 ///
806 /// Panics if llama.cpp returns a null pointer.
807 #[must_use]
808 pub fn dist(seed: u32) -> Self {
809 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
810 // the library owns whatever it returns.
811 let sampler = unsafe { llama_sampler_init_dist(seed) };
812 Self {
813 sampler: NonNull::new(sampler).unwrap(),
814 }
815 }
816
817 /// Selects the most likely token.
818 ///
819 /// # Panics
820 ///
821 /// Panics if llama.cpp returns a null pointer.
822 ///
823 /// # Example:
824 /// ```rust
825 /// use llama_cpp_4::token::{
826 /// LlamaToken,
827 /// data::LlamaTokenData,
828 /// data_array::LlamaTokenDataArray
829 /// };
830 /// use llama_cpp_4::sampling::LlamaSampler;
831 ///
832 /// let mut data_array = LlamaTokenDataArray::new(vec![
833 /// LlamaTokenData::new(LlamaToken(0), 0., 0.),
834 /// LlamaTokenData::new(LlamaToken(1), 1., 0.),
835 /// ], false);
836 ///
837 /// data_array.apply_sampler(&mut LlamaSampler::greedy());
838 ///
839 /// assert_eq!(data_array.data.len(), 2);
840 /// assert_eq!(data_array.selected_token(), Some(LlamaToken(1)));
841 /// ```
842 #[must_use]
843 pub fn greedy() -> Self {
844 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
845 // the library owns whatever it returns.
846 let sampler = unsafe { llama_sampler_init_greedy() };
847 Self {
848 sampler: NonNull::new(sampler).unwrap(),
849 }
850 }
851
852 /// Top-N sigma sampling.
853 ///
854 /// Keeps tokens within N standard deviations of the maximum logit.
855 ///
856 /// # Panics
857 ///
858 /// Panics if llama.cpp returns a null pointer.
859 #[must_use]
860 pub fn top_n_sigma(n: f32) -> Self {
861 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
862 // the library owns whatever it returns.
863 let sampler = unsafe { llama_sampler_init_top_n_sigma(n) };
864 Self {
865 sampler: NonNull::new(sampler).unwrap(),
866 }
867 }
868
869 /// Adaptive P sampling.
870 ///
871 /// # Panics
872 ///
873 /// Panics if llama.cpp returns a null pointer.
874 ///
875 /// # Parameters
876 /// - `target`: Target probability.
877 /// - `decay`: Decay rate.
878 /// - `seed`: Random seed.
879 #[must_use]
880 pub fn adaptive_p(target: f32, decay: f32, seed: u32) -> Self {
881 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
882 // the library owns whatever it returns.
883 let sampler = unsafe { llama_sampler_init_adaptive_p(target, decay, seed) };
884 Self {
885 sampler: NonNull::new(sampler).unwrap(),
886 }
887 }
888
889 /// Logit bias sampler.
890 ///
891 /// Applies additive bias to specific token logits before sampling.
892 ///
893 /// # Panics
894 ///
895 /// Panics if llama.cpp returns a null pointer.
896 ///
897 /// # Parameters
898 /// - `n_vocab`: Number of tokens in the vocabulary ([`LlamaModel::n_vocab`]).
899 /// - `biases`: Slice of `(token_id, bias)` pairs.
900 #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
901 #[must_use]
902 pub fn logit_bias(n_vocab: i32, biases: &[(LlamaToken, f32)]) -> Self {
903 let logit_biases: Vec<llama_logit_bias> = biases
904 .iter()
905 .map(|(token, bias)| llama_logit_bias {
906 token: token.0,
907 bias: *bias,
908 })
909 .collect();
910
911 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
912 // the library owns whatever it returns.
913 let sampler = unsafe {
914 llama_sampler_init_logit_bias(n_vocab, logit_biases.len() as i32, logit_biases.as_ptr())
915 };
916 Self {
917 sampler: NonNull::new(sampler).unwrap(),
918 }
919 }
920
921 /// Infill sampler.
922 ///
923 /// Reorders token probabilities for fill-in-the-middle tasks.
924 ///
925 /// # Panics
926 ///
927 /// Panics if llama.cpp returns a null pointer.
928 #[must_use]
929 pub fn infill(model: &LlamaModel) -> Self {
930 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
931 // the library owns whatever it returns.
932 let sampler = unsafe { llama_sampler_init_infill(model.get_vocab().vocab.as_ref()) };
933 Self {
934 sampler: NonNull::new(sampler).unwrap(),
935 }
936 }
937
938 /// Get the seed of the sampler.
939 ///
940 /// Returns `LLAMA_DEFAULT_SEED` if the sampler is not seeded.
941 #[must_use]
942 pub fn get_seed(&self) -> u32 {
943 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
944 // arguments are scalars the library validates itself.
945 unsafe { llama_sampler_get_seed(self.sampler.as_ptr()) }
946 }
947
948 /// Get the name of the sampler.
949 ///
950 /// # Disabled samplers
951 ///
952 /// When a constructor is handed parameters that make it a no-op — e.g.
953 /// [`Self::temp`] with `1.0`, or [`Self::penalties`] with `penalty_last_n =
954 /// 0` — llama.cpp does not build that sampler. It substitutes an identity
955 /// sampler whose name carries a `?` prefix (`"?temp"`, `"?penalties"`).
956 /// Construction still succeeds, so this name is the only signal that the
957 /// sampler will not do anything. Affects `temp`, `temp_ext`, `top_k`,
958 /// `top_p`, `min_p`, `typical`, `xtc`, `top_n_sigma`, `dry`, and
959 /// `penalties`.
960 ///
961 /// # Panics
962 ///
963 /// Panics if the name is not valid UTF-8.
964 #[must_use]
965 pub fn name(&self) -> String {
966 // SAFETY: the pointer is non-null and points at a NUL-terminated string the
967 // library owns, which outlives the borrow taken here.
968 let c_str = unsafe { llama_sampler_name(self.sampler.as_ptr()) };
969 // SAFETY: the pointer is non-null and points at a NUL-terminated string the
970 // library owns, which outlives the borrow taken here.
971 let c_str = unsafe { std::ffi::CStr::from_ptr(c_str) };
972 c_str
973 .to_str()
974 .expect("sampler name is not valid UTF-8")
975 .to_owned()
976 }
977
978 /// Reset the sampler state (e.g. grammar, repetition penalties).
979 pub fn reset(&mut self) {
980 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
981 // arguments are scalars the library validates itself.
982 unsafe { llama_sampler_reset(self.sampler.as_ptr()) }
983 }
984
985 /// Get the number of samplers in a chain.
986 ///
987 /// Returns 0 if this sampler is not a chain.
988 #[must_use]
989 pub fn chain_n(&self) -> i32 {
990 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
991 // arguments are scalars the library validates itself.
992 unsafe { llama_sampler_chain_n(self.sampler.as_ptr()) }
993 }
994
995 /// Remove and return the sampler at position `i` from a chain.
996 ///
997 /// The returned sampler is owned by the caller and will be freed on drop.
998 ///
999 /// # Panics
1000 ///
1001 /// Panics if `i` is out of range or if llama.cpp returns a null pointer.
1002 #[must_use]
1003 pub fn chain_remove(&mut self, i: i32) -> Self {
1004 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1005 // arguments are scalars the library validates itself.
1006 let sampler = unsafe { llama_sampler_chain_remove(self.sampler.as_ptr(), i) };
1007 Self {
1008 sampler: NonNull::new(sampler).expect("chain_remove returned null"),
1009 }
1010 }
1011
1012 /// Grammar sampler with lazy activation via regex patterns.
1013 ///
1014 /// The grammar is only activated when one of the trigger patterns or trigger tokens matches.
1015 ///
1016 /// # Panics
1017 /// - If `grammar_str` or `grammar_root` contain null bytes.
1018 /// - If any trigger pattern contains null bytes.
1019 /// - If llama.cpp returns a null pointer.
1020 ///
1021 /// Prefer [`Self::try_grammar_lazy_patterns`] when any of those strings came
1022 /// from a request rather than your own source.
1023 #[must_use]
1024 pub fn grammar_lazy_patterns(
1025 model: &LlamaModel,
1026 grammar_str: &str,
1027 grammar_root: &str,
1028 trigger_patterns: &[&str],
1029 trigger_tokens: &[LlamaToken],
1030 ) -> Self {
1031 Self::try_grammar_lazy_patterns(
1032 model,
1033 grammar_str,
1034 grammar_root,
1035 trigger_patterns,
1036 trigger_tokens,
1037 )
1038 .unwrap_or_else(|e| panic!("building lazy grammar sampler: {e}"))
1039 }
1040
1041 /// [`Self::grammar_lazy_patterns`], reporting failures instead of panicking.
1042 ///
1043 /// # Errors
1044 ///
1045 /// [`SamplerInitError::InteriorNul`] for a NUL in the grammar, the rule name
1046 /// or any trigger pattern, or [`SamplerInitError::NullReturn`] if llama.cpp
1047 /// rejected the grammar.
1048 pub fn try_grammar_lazy_patterns(
1049 model: &LlamaModel,
1050 grammar_str: &str,
1051 grammar_root: &str,
1052 trigger_patterns: &[&str],
1053 trigger_tokens: &[LlamaToken],
1054 ) -> Result<Self, SamplerInitError> {
1055 let grammar_str = c_string("grammar_str", grammar_str)?;
1056 let grammar_root = c_string("grammar_root", grammar_root)?;
1057 let pattern_cstrings = trigger_patterns
1058 .iter()
1059 .map(|pattern| c_string("trigger_patterns", pattern))
1060 .collect::<Result<Vec<CString>, _>>()?;
1061 let mut pattern_ptrs: Vec<*const c_char> =
1062 pattern_cstrings.iter().map(|s| s.as_ptr()).collect();
1063
1064 // SAFETY: every pointer is borrowed from a `CString`/slice that outlives
1065 // the call, and the two lengths describe exactly those buffers.
1066 // `trigger_tokens` is `#[repr(transparent)]` over `llama_token`, so the
1067 // cast is a layout no-op. llama.cpp copies what it needs.
1068 let sampler = unsafe {
1069 llama_sampler_init_grammar_lazy_patterns(
1070 model.get_vocab().vocab.as_ref(),
1071 grammar_str.as_ptr(),
1072 grammar_root.as_ptr(),
1073 pattern_ptrs.as_mut_ptr(),
1074 pattern_ptrs.len(),
1075 trigger_tokens.as_ptr().cast(),
1076 trigger_tokens.len(),
1077 )
1078 };
1079 NonNull::new(sampler)
1080 .map(|sampler| Self { sampler })
1081 .ok_or(SamplerInitError::NullReturn("lazy grammar"))
1082 }
1083
1084 /// Clone this sampler.
1085 ///
1086 /// Creates an independent copy of this sampler with the same state.
1087 ///
1088 /// # Panics
1089 ///
1090 /// Panics if llama.cpp returns a null pointer.
1091 #[must_use]
1092 pub fn clone_sampler(&self) -> Self {
1093 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1094 // arguments are scalars the library validates itself.
1095 let sampler = unsafe { llama_sampler_clone(self.sampler.as_ptr()) };
1096 Self {
1097 sampler: NonNull::new(sampler).expect("sampler_clone returned null"),
1098 }
1099 }
1100
1101 /// Copy mutable state from `src` into this sampler, in place.
1102 ///
1103 /// Unlike [`Self::clone_sampler`], which allocates a new sampler, this
1104 /// overwrites the state of an existing one and so is the cheap way to
1105 /// rewind a sampler to a checkpoint in a loop. Added upstream in
1106 /// llama.cpp `b10470` (`llama_sampler_copy`).
1107 ///
1108 /// # Safety and preconditions
1109 ///
1110 /// llama.cpp requires `src` and `self` to be **the same sampler type with
1111 /// the same configuration** — e.g. two `dist` samplers, or two chains built
1112 /// the same way. Copying between mismatched samplers is undefined
1113 /// behaviour upstream and is not checked here, so treat the pairing as the
1114 /// caller's contract. A sampler produced by `src.clone_sampler()` always
1115 /// satisfies it.
1116 pub fn copy_state_from(&mut self, src: &Self) {
1117 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1118 // arguments are scalars the library validates itself.
1119 unsafe { llama_sampler_copy(src.sampler.as_ptr(), self.sampler.as_ptr()) }
1120 }
1121
1122 /// Print sampler performance data.
1123 pub fn perf_print(&self) {
1124 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1125 // arguments are scalars the library validates itself.
1126 unsafe { llama_cpp_sys_4::llama_perf_sampler_print(self.sampler.as_ptr()) }
1127 }
1128
1129 /// Reset sampler performance counters.
1130 pub fn perf_reset(&mut self) {
1131 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1132 // arguments are scalars the library validates itself.
1133 unsafe { llama_cpp_sys_4::llama_perf_sampler_reset(self.sampler.as_ptr()) }
1134 }
1135
1136 /// Get sampler performance data.
1137 #[must_use]
1138 pub fn perf_data(&self) -> llama_cpp_sys_4::llama_perf_sampler_data {
1139 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
1140 // arguments are scalars the library validates itself.
1141 unsafe { llama_cpp_sys_4::llama_perf_sampler(self.sampler.as_ptr()) }
1142 }
1143
1144 /// Get a non-owning reference to the `i`th sampler in a chain.
1145 ///
1146 /// # Safety
1147 ///
1148 /// The returned pointer is owned by the chain. Do not free it or use it
1149 /// after the chain is dropped or modified.
1150 #[must_use]
1151 pub unsafe fn chain_get_ptr(&self, i: i32) -> *mut llama_sampler {
1152 llama_cpp_sys_4::llama_sampler_chain_get(self.sampler.as_ptr(), i)
1153 }
1154
1155 /// Create a sampler from a raw interface and context.
1156 ///
1157 /// # Safety
1158 ///
1159 /// The caller must ensure that `iface` and `ctx` are valid and that the
1160 /// interface functions properly manage the context lifetime.
1161 ///
1162 /// # Panics
1163 ///
1164 /// Panics if llama.cpp returns a null pointer.
1165 #[must_use]
1166 pub unsafe fn from_raw(
1167 iface: *mut llama_cpp_sys_4::llama_sampler_i,
1168 ctx: llama_cpp_sys_4::llama_sampler_context_t,
1169 ) -> Self {
1170 let sampler = llama_cpp_sys_4::llama_sampler_init(iface, ctx);
1171 Self {
1172 sampler: NonNull::new(sampler).expect("sampler_init returned null"),
1173 }
1174 }
1175
1176 /// Adopt a raw `llama_sampler *`, taking ownership.
1177 ///
1178 /// # Safety
1179 ///
1180 /// `raw` must come from a llama.cpp entry point documented as returning a
1181 /// sampler the caller owns and releases with `llama_sampler_free`, and must
1182 /// not be owned by anything else — [`Drop`] frees it.
1183 pub(crate) unsafe fn from_raw_ptr(raw: NonNull<llama_sampler>) -> Self {
1184 Self { sampler: raw }
1185 }
1186
1187 /// The underlying `llama_sampler *`. Borrowed; still owned by `self`.
1188 pub(crate) fn as_ptr(&self) -> *mut llama_sampler {
1189 self.sampler.as_ptr()
1190 }
1191
1192 /// Creates a new instance of `LlamaSampler` with common sampling parameters.
1193 ///
1194 /// This function initializes a `LlamaSampler` using default values from `common_sampler_params`
1195 /// and configures it with common settings such as `top_k`, `top_p`, `temperature`, and `seed` values.
1196 ///
1197 /// # Panics
1198 ///
1199 /// Panics if llama.cpp returns a null pointer.
1200 ///
1201 /// # Returns
1202 /// A `LlamaSampler` instance configured with the common sampling parameters.
1203 #[must_use]
1204 pub fn common() -> Self {
1205 let params = common_sampler_params::default();
1206
1207 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
1208 // the library owns whatever it returns.
1209 let sampler = unsafe {
1210 let mut sparams = llama_sampler_chain_default_params();
1211 sparams.no_perf = false;
1212
1213 let smpl = llama_sampler_chain_init(sparams);
1214
1215 llama_sampler_chain_add(smpl, llama_sampler_init_top_k(params.top_k));
1216 llama_sampler_chain_add(
1217 smpl,
1218 #[allow(clippy::cast_sign_loss)]
1219 llama_sampler_init_top_p(params.top_p, params.min_keep as usize),
1220 );
1221 llama_sampler_chain_add(smpl, llama_sampler_init_temp(params.temp));
1222 #[allow(clippy::cast_sign_loss)]
1223 llama_sampler_chain_add(smpl, llama_sampler_init_dist(params.seed));
1224
1225 smpl
1226 };
1227
1228 Self {
1229 sampler: NonNull::new(sampler).unwrap(),
1230 }
1231 }
1232}
1233
1234impl Drop for LlamaSampler {
1235 fn drop(&mut self) {
1236 // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
1237 // afterwards.
1238 unsafe {
1239 llama_sampler_free(self.sampler.as_ptr());
1240 }
1241 }
1242}