Skip to main content

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}