Skip to main content

llama_cpp_4/
model.rs

1//! A safe wrapper around `llama_model`.
2use std::ffi::CStr;
3use std::ffi::CString;
4use std::fmt;
5use std::num::NonZeroU16;
6use std::os::raw::{c_char, c_int};
7use std::path::Path;
8use std::ptr::NonNull;
9use std::slice;
10
11use llama_cpp_sys_4::{
12    llama_adapter_lora, llama_adapter_lora_init, llama_chat_apply_template,
13    llama_chat_builtin_templates, llama_chat_message, llama_detokenize, llama_init_from_model,
14    llama_model, llama_model_cls_label, llama_model_decoder_start_token, llama_model_desc,
15    llama_model_free, llama_model_get_device, llama_model_get_vocab, llama_model_has_decoder,
16    llama_model_has_encoder, llama_model_is_diffusion, llama_model_is_hybrid,
17    llama_model_is_recurrent, llama_model_load_from_file, llama_model_load_from_splits,
18    llama_model_meta_count, llama_model_meta_key_by_index, llama_model_meta_val_str,
19    llama_model_meta_val_str_by_index, llama_model_n_cls_out, llama_model_n_ctx_train,
20    llama_model_n_devices, llama_model_n_embd, llama_model_n_embd_inp, llama_model_n_embd_out,
21    llama_model_n_expert, llama_model_n_head, llama_model_n_head_kv, llama_model_n_layer,
22    llama_model_n_layer_nextn, llama_model_n_params, llama_model_n_swa,
23    llama_model_rope_freq_scale_train, llama_model_rope_type, llama_model_save_to_file,
24    llama_model_size, llama_model_target_layer_ids, llama_model_target_layer_ids_n,
25    llama_split_path, llama_split_prefix, llama_token_to_piece, llama_tokenize, llama_vocab,
26    llama_vocab_type, LLAMA_VOCAB_TYPE_BPE, LLAMA_VOCAB_TYPE_SPM,
27};
28
29use crate::context::params::LlamaContextParams;
30use crate::context::LlamaContext;
31use crate::llama_backend::LlamaBackend;
32use crate::model::params::LlamaModelParams;
33use crate::token::LlamaToken;
34use crate::token_type::{LlamaTokenAttr, LlamaTokenAttrs};
35use crate::{
36    ApplyChatTemplateError, ChatTemplateError, LlamaContextLoadError, LlamaLoraAdapterInitError,
37    LlamaModelLoadError, NewLlamaChatMessageError, StringFromModelError, StringToTokenError,
38    TokenToStringError,
39};
40
41pub mod params;
42
43/// Opaque ggml backend device handle returned by [`LlamaModel::get_device`].
44///
45/// Use [`Self::name`], [`Self::description`], [`Self::device_type`], and
46/// [`Self::memory`] to inspect the device. The handle is valid for the lifetime
47/// of the parent [`LlamaModel`].
48#[derive(Debug, Copy, Clone, PartialEq, Eq)]
49pub struct LlamaBackendDevice {
50    pub(crate) dev: llama_cpp_sys_4::ggml_backend_dev_t,
51}
52
53/// Backend device class (CPU, discrete GPU, integrated GPU, …).
54//
55// `GGML_BACKEND_DEVICE_TYPE_*` are `c_uint` under clang/gcc and `c_int` under
56// MSVC. `as i32` compiles on both; the `cast_possible_wrap` allow covers the
57// clang/gcc case (the device-type values are small and never wrap).
58#[allow(clippy::cast_possible_wrap)]
59#[repr(i32)]
60#[derive(Copy, Clone, Debug, PartialEq, Eq)]
61pub enum LlamaBackendDeviceType {
62    /// Host CPU backend.
63    Cpu = llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_CPU as i32,
64    /// Discrete GPU.
65    Gpu = llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_GPU as i32,
66    /// Integrated GPU.
67    IntegratedGpu = llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_IGPU as i32,
68    /// Accelerator device (e.g. BLAS / Hexagon).
69    Accel = llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_ACCEL as i32,
70    /// Meta / placeholder device entry.
71    Meta = llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_META as i32,
72}
73
74impl From<llama_cpp_sys_4::ggml_backend_dev_type> for LlamaBackendDeviceType {
75    fn from(value: llama_cpp_sys_4::ggml_backend_dev_type) -> Self {
76        match value {
77            llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_CPU => Self::Cpu,
78            llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_GPU => Self::Gpu,
79            llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_IGPU => Self::IntegratedGpu,
80            llama_cpp_sys_4::GGML_BACKEND_DEVICE_TYPE_ACCEL => Self::Accel,
81            _ => Self::Meta,
82        }
83    }
84}
85
86impl LlamaBackendDevice {
87    /// Human-readable device name (e.g. `CUDA0`, `Metal`).
88    ///
89    /// # Errors
90    ///
91    /// Returns an error when the name pointer is null or not valid UTF-8.
92    pub fn name(&self) -> Result<&str, StringFromModelError> {
93        let ptr = unsafe { llama_cpp_sys_4::ggml_backend_dev_name(self.dev) };
94        if ptr.is_null() {
95            return Err(StringFromModelError::ReturnedError(-1));
96        }
97        let cstr = unsafe { CStr::from_ptr(ptr) };
98        cstr.to_str().map_err(StringFromModelError::Utf8Error)
99    }
100
101    /// Longer device description (often includes hardware name).
102    ///
103    /// # Errors
104    ///
105    /// Returns an error when the description pointer is null or not valid UTF-8.
106    pub fn description(&self) -> Result<&str, StringFromModelError> {
107        let ptr = unsafe { llama_cpp_sys_4::ggml_backend_dev_description(self.dev) };
108        if ptr.is_null() {
109            return Err(StringFromModelError::ReturnedError(-1));
110        }
111        let cstr = unsafe { CStr::from_ptr(ptr) };
112        cstr.to_str().map_err(StringFromModelError::Utf8Error)
113    }
114
115    /// Device class (CPU, GPU, integrated GPU, …).
116    #[must_use]
117    pub fn device_type(&self) -> LlamaBackendDeviceType {
118        unsafe { llama_cpp_sys_4::ggml_backend_dev_type(self.dev).into() }
119    }
120
121    /// Device memory `(free_bytes, total_bytes)`.
122    #[must_use]
123    pub fn memory(&self) -> (usize, usize) {
124        let mut free = 0usize;
125        let mut total = 0usize;
126        unsafe {
127            llama_cpp_sys_4::ggml_backend_dev_memory(self.dev, &raw mut free, &raw mut total);
128        }
129        (free, total)
130    }
131}
132
133/// Iterator over [`LlamaBackendDevice`] handles for a loaded model.
134///
135/// # Examples
136///
137/// ```no_run
138/// use llama_cpp_4::prelude::*;
139///
140/// fn main() {
141///     let backend = LlamaBackend::init().unwrap();
142///     let model = LlamaModel::load_from_file(&backend, "model.gguf", &LlamaModelParams::default()).unwrap();
143///     for dev in model.devices() {
144///         let (free, total) = dev.memory();
145///         println!("{}: {} / {} bytes free", dev.name().unwrap(), free, total);
146///     }
147/// }
148/// ```
149#[derive(Debug, Clone, Copy)]
150pub struct LlamaBackendDevices<'a> {
151    model: &'a LlamaModel,
152    next: i32,
153}
154
155#[allow(clippy::copy_iterator)]
156impl Iterator for LlamaBackendDevices<'_> {
157    type Item = LlamaBackendDevice;
158
159    fn next(&mut self) -> Option<Self::Item> {
160        let dev = self.model.get_device(self.next)?;
161        self.next += 1;
162        Some(dev)
163    }
164
165    fn size_hint(&self) -> (usize, Option<usize>) {
166        let remaining = usize::try_from((self.model.n_devices() - self.next).max(0)).unwrap_or(0);
167        (remaining, Some(remaining))
168    }
169}
170
171impl ExactSizeIterator for LlamaBackendDevices<'_> {}
172
173/// A safe wrapper around `llama_model`.
174#[derive(Debug)]
175#[repr(transparent)]
176#[allow(clippy::module_name_repetitions)]
177pub struct LlamaModel {
178    pub(crate) model: NonNull<llama_model>,
179}
180
181/// A safe wrapper around `llama_vocab`.
182#[derive(Debug)]
183#[repr(transparent)]
184#[allow(clippy::module_name_repetitions)]
185pub struct LlamaVocab {
186    pub(crate) vocab: NonNull<llama_vocab>,
187}
188
189impl LlamaVocab {
190    /// Get the number of tokens in the vocabulary.
191    #[must_use]
192    pub fn n_tokens(&self) -> i32 {
193        unsafe { llama_cpp_sys_4::llama_vocab_n_tokens(self.vocab.as_ref()) }
194    }
195
196    /// Get the vocabulary type.
197    ///
198    /// # Panics
199    ///
200    /// Panics if the C API returns a vocabulary type that does not fit in `u32`.
201    #[must_use]
202    pub fn vocab_type(&self) -> u32 {
203        unsafe { llama_cpp_sys_4::llama_vocab_type(self.vocab.as_ref()) as u32 }
204    }
205
206    /// Get the BOS token.
207    #[must_use]
208    pub fn bos(&self) -> LlamaToken {
209        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_bos(self.vocab.as_ref()) })
210    }
211
212    /// Get the EOS token.
213    #[must_use]
214    pub fn eos(&self) -> LlamaToken {
215        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_eos(self.vocab.as_ref()) })
216    }
217
218    /// Get the EOT (end of turn) token.
219    #[must_use]
220    pub fn eot(&self) -> LlamaToken {
221        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_eot(self.vocab.as_ref()) })
222    }
223
224    /// Get the CLS (classification) token.
225    #[must_use]
226    pub fn cls(&self) -> LlamaToken {
227        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_cls(self.vocab.as_ref()) })
228    }
229
230    /// Get the SEP (separator) token.
231    #[must_use]
232    pub fn sep(&self) -> LlamaToken {
233        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_sep(self.vocab.as_ref()) })
234    }
235
236    /// Get the NL (newline) token.
237    #[must_use]
238    pub fn nl(&self) -> LlamaToken {
239        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_nl(self.vocab.as_ref()) })
240    }
241
242    /// Get the PAD (padding) token.
243    #[must_use]
244    pub fn pad(&self) -> LlamaToken {
245        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_pad(self.vocab.as_ref()) })
246    }
247
248    /// Get the FIM prefix token.
249    #[must_use]
250    pub fn fim_pre(&self) -> LlamaToken {
251        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_pre(self.vocab.as_ref()) })
252    }
253
254    /// Get the FIM suffix token.
255    #[must_use]
256    pub fn fim_suf(&self) -> LlamaToken {
257        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_suf(self.vocab.as_ref()) })
258    }
259
260    /// Get the FIM middle token.
261    #[must_use]
262    pub fn fim_mid(&self) -> LlamaToken {
263        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_mid(self.vocab.as_ref()) })
264    }
265
266    /// Get the FIM padding token.
267    #[must_use]
268    pub fn fim_pad(&self) -> LlamaToken {
269        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_pad(self.vocab.as_ref()) })
270    }
271
272    /// Get the FIM repository token.
273    #[must_use]
274    pub fn fim_rep(&self) -> LlamaToken {
275        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_rep(self.vocab.as_ref()) })
276    }
277
278    /// Get the FIM separator token.
279    #[must_use]
280    pub fn fim_sep(&self) -> LlamaToken {
281        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_fim_sep(self.vocab.as_ref()) })
282    }
283
284    /// Check whether BOS should be added.
285    #[must_use]
286    pub fn get_add_bos(&self) -> bool {
287        unsafe { llama_cpp_sys_4::llama_vocab_get_add_bos(self.vocab.as_ref()) }
288    }
289
290    /// Check whether EOS should be added.
291    #[must_use]
292    pub fn get_add_eos(&self) -> bool {
293        unsafe { llama_cpp_sys_4::llama_vocab_get_add_eos(self.vocab.as_ref()) }
294    }
295
296    /// Check whether SEP should be added.
297    #[must_use]
298    pub fn get_add_sep(&self) -> bool {
299        unsafe { llama_cpp_sys_4::llama_vocab_get_add_sep(self.vocab.as_ref()) }
300    }
301
302    /// Get the text representation of a token.
303    ///
304    /// # Errors
305    ///
306    /// Returns an error if the text pointer is null or not valid UTF-8.
307    pub fn get_text(&self, token: LlamaToken) -> Result<&str, StringFromModelError> {
308        let bytes = self.get_text_bytes(token)?;
309        std::str::from_utf8(bytes).map_err(StringFromModelError::Utf8Error)
310    }
311
312    /// Get the exact byte representation stored for a vocabulary token.
313    ///
314    /// Unlike [`Self::get_text`], this method does not require the token text
315    /// to be valid UTF-8.
316    ///
317    /// # Errors
318    ///
319    /// Returns an error if llama.cpp reports a null text pointer.
320    pub fn get_text_bytes(&self, token: LlamaToken) -> Result<&[u8], StringFromModelError> {
321        let ptr = unsafe { llama_cpp_sys_4::llama_vocab_get_text(self.vocab.as_ref(), token.0) };
322        if ptr.is_null() {
323            return Err(StringFromModelError::ReturnedError(-1));
324        }
325        let cstr = unsafe { CStr::from_ptr(ptr) };
326        Ok(cstr.to_bytes())
327    }
328
329    /// Get the score of a token.
330    #[must_use]
331    pub fn get_score(&self, token: LlamaToken) -> f32 {
332        unsafe { llama_cpp_sys_4::llama_vocab_get_score(self.vocab.as_ref(), token.0) }
333    }
334
335    /// Get the attributes of a token.
336    ///
337    /// # Panics
338    ///
339    /// Panics if the C API returns attributes that do not fit in `u32`.
340    #[must_use]
341    pub fn get_attr(&self, token: LlamaToken) -> u32 {
342        unsafe { llama_cpp_sys_4::llama_vocab_get_attr(self.vocab.as_ref(), token.0) as u32 }
343    }
344
345    /// Check if a token is a control token.
346    #[must_use]
347    pub fn is_control(&self, token: LlamaToken) -> bool {
348        unsafe { llama_cpp_sys_4::llama_vocab_is_control(self.vocab.as_ref(), token.0) }
349    }
350
351    /// Check if a token is an end-of-generation token.
352    #[must_use]
353    pub fn is_eog(&self, token: LlamaToken) -> bool {
354        unsafe { llama_cpp_sys_4::llama_vocab_is_eog(self.vocab.as_ref(), token.0) }
355    }
356
357    /// Get the token mask value for the vocabulary.
358    #[must_use]
359    pub fn mask(&self) -> LlamaToken {
360        LlamaToken(unsafe { llama_cpp_sys_4::llama_vocab_mask(self.vocab.as_ref()) })
361    }
362}
363
364/// A safe wrapper around `llama_adapter_lora`.
365#[derive(Debug)]
366#[repr(transparent)]
367#[allow(clippy::module_name_repetitions)]
368pub struct LlamaLoraAdapter {
369    pub(crate) lora_adapter: NonNull<llama_adapter_lora>,
370}
371
372impl LlamaLoraAdapter {
373    /// Get the number of metadata key-value pairs in the adapter.
374    #[must_use]
375    pub fn meta_count(&self) -> i32 {
376        unsafe { llama_cpp_sys_4::llama_adapter_meta_count(self.lora_adapter.as_ptr()) }
377    }
378
379    /// Get a metadata key by index.
380    ///
381    /// # Errors
382    ///
383    /// Returns an error if the index is out of range or the key is not valid UTF-8.
384    #[allow(clippy::cast_sign_loss)]
385    pub fn meta_key_by_index(
386        &self,
387        index: i32,
388        buf_size: usize,
389    ) -> Result<String, StringFromModelError> {
390        let mut buf = vec![0u8; buf_size];
391        let ret = unsafe {
392            llama_cpp_sys_4::llama_adapter_meta_key_by_index(
393                self.lora_adapter.as_ptr(),
394                index,
395                buf.as_mut_ptr().cast::<c_char>(),
396                buf_size,
397            )
398        };
399        if ret < 0 {
400            return Err(StringFromModelError::ReturnedError(ret));
401        }
402        let len = ret as usize;
403        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
404        Ok(s.to_owned())
405    }
406
407    /// Get a metadata value by key name.
408    ///
409    /// # Errors
410    ///
411    /// Returns an error if the key is not found or the value is not valid UTF-8.
412    #[allow(clippy::cast_sign_loss)]
413    pub fn meta_val_str(&self, key: &str, buf_size: usize) -> Result<String, StringFromModelError> {
414        let c_key = CString::new(key).map_err(|_| StringFromModelError::ReturnedError(-1))?;
415        let mut buf = vec![0u8; buf_size];
416        let ret = unsafe {
417            llama_cpp_sys_4::llama_adapter_meta_val_str(
418                self.lora_adapter.as_ptr(),
419                c_key.as_ptr(),
420                buf.as_mut_ptr().cast::<c_char>(),
421                buf_size,
422            )
423        };
424        if ret < 0 {
425            return Err(StringFromModelError::ReturnedError(ret));
426        }
427        let len = ret as usize;
428        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
429        Ok(s.to_owned())
430    }
431
432    /// Get a metadata value by index.
433    ///
434    /// # Errors
435    ///
436    /// Returns an error if the index is out of range or the value is not valid UTF-8.
437    #[allow(clippy::cast_sign_loss)]
438    pub fn meta_val_str_by_index(
439        &self,
440        index: i32,
441        buf_size: usize,
442    ) -> Result<String, StringFromModelError> {
443        let mut buf = vec![0u8; buf_size];
444        let ret = unsafe {
445            llama_cpp_sys_4::llama_adapter_meta_val_str_by_index(
446                self.lora_adapter.as_ptr(),
447                index,
448                buf.as_mut_ptr().cast::<c_char>(),
449                buf_size,
450            )
451        };
452        if ret < 0 {
453            return Err(StringFromModelError::ReturnedError(ret));
454        }
455        let len = ret as usize;
456        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
457        Ok(s.to_owned())
458    }
459
460    /// Get all metadata as a list of `(key, value)` pairs.
461    ///
462    /// # Errors
463    ///
464    /// Returns an error if any key or value cannot be read or is not valid UTF-8.
465    #[allow(clippy::cast_sign_loss)]
466    pub fn metadata(&self) -> Result<Vec<(String, String)>, StringFromModelError> {
467        let count = self.meta_count();
468        let mut result = Vec::with_capacity(count as usize);
469        for i in 0..count {
470            let key = self.meta_key_by_index(i, 256)?;
471            let val = self.meta_val_str_by_index(i, 4096)?;
472            result.push((key, val));
473        }
474        Ok(result)
475    }
476
477    /// Get the number of invocation tokens for this adapter.
478    #[must_use]
479    pub fn n_invocation_tokens(&self) -> u64 {
480        unsafe {
481            llama_cpp_sys_4::llama_adapter_get_alora_n_invocation_tokens(self.lora_adapter.as_ptr())
482        }
483    }
484
485    /// Get the invocation tokens for this adapter.
486    ///
487    /// Returns an empty slice if there are no invocation tokens.
488    #[must_use]
489    #[allow(clippy::cast_possible_truncation)]
490    pub fn invocation_tokens(&self) -> &[LlamaToken] {
491        let n = self.n_invocation_tokens() as usize;
492        if n == 0 {
493            return &[];
494        }
495        let ptr = unsafe {
496            llama_cpp_sys_4::llama_adapter_get_alora_invocation_tokens(self.lora_adapter.as_ptr())
497        };
498        if ptr.is_null() {
499            return &[];
500        }
501        // LlamaToken is repr(transparent) over llama_token (i32), so this cast is safe
502        unsafe { std::slice::from_raw_parts(ptr.cast::<LlamaToken>(), n) }
503    }
504}
505
506impl Drop for LlamaLoraAdapter {
507    fn drop(&mut self) {
508        unsafe {
509            llama_cpp_sys_4::llama_adapter_lora_free(self.lora_adapter.as_ptr());
510        }
511    }
512}
513
514/// A Safe wrapper around `llama_chat_message`
515#[derive(Debug, Eq, PartialEq, Clone)]
516pub struct LlamaChatMessage {
517    role: CString,
518    content: CString,
519}
520
521impl LlamaChatMessage {
522    /// Create a new `LlamaChatMessage`.
523    ///
524    /// # Errors
525    ///
526    /// Returns [`NewLlamaChatMessageError`] if the role or content contains a null byte.
527    pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
528        Ok(Self {
529            role: CString::new(role)?,
530            content: CString::new(content)?,
531        })
532    }
533}
534
535/// How to determine if we should prepend a bos token to tokens
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537pub enum AddBos {
538    /// Add the beginning of stream token to the start of the string.
539    Always,
540    /// Do not add the beginning of stream token to the start of the string.
541    Never,
542}
543
544/// How to determine if we should tokenize special tokens
545#[derive(Debug, Clone, Copy, PartialEq, Eq)]
546pub enum Special {
547    /// Allow tokenizing special and/or control tokens which otherwise are not exposed and treated as plaintext. Does not insert a leading space.
548    Tokenize,
549    /// Treat special and/or control tokens as plaintext.
550    Plaintext,
551}
552
553unsafe impl Send for LlamaModel {}
554
555unsafe impl Sync for LlamaModel {}
556
557impl LlamaModel {
558    /// Retrieves the vocabulary associated with the current Llama model.
559    ///
560    /// This method fetches the vocabulary from the underlying model using an unsafe
561    /// FFI call. The returned `LlamaVocab` struct contains a non-null pointer to
562    /// the vocabulary data, which is wrapped in a `NonNull` for safety.
563    ///
564    /// # Safety
565    /// This method uses an unsafe block to call a C function (`llama_model_get_vocab`),
566    /// which is assumed to return a valid pointer to the vocabulary. The caller should
567    /// ensure that the model object is properly initialized and valid before calling
568    /// this method, as dereferencing invalid pointers can lead to undefined behavior.
569    ///
570    /// # Returns
571    /// A `LlamaVocab` struct containing the vocabulary of the model.
572    ///
573    /// # Panics
574    ///
575    /// Panics if the underlying C function returns a null pointer.
576    ///
577    /// # Example
578    /// ```rust,ignore
579    /// let vocab = model.get_vocab();
580    /// ```
581    #[must_use]
582    pub fn get_vocab(&self) -> LlamaVocab {
583        let llama_vocab = unsafe { llama_model_get_vocab(self.model.as_ptr()) }.cast_mut();
584
585        LlamaVocab {
586            vocab: NonNull::new(llama_vocab).unwrap(),
587        }
588    }
589    /// Get the number of tokens the model was trained on.
590    ///
591    /// This function returns the number of tokens that the model was trained on, represented as a `u32`.
592    ///
593    /// # Panics
594    ///
595    /// This function will panic if the number of tokens the model was trained on does not fit into a `u32`.
596    /// This should be impossible on most platforms since llama.cpp returns a `c_int` (i32 on most platforms),
597    /// which is almost certainly positive.
598    #[must_use]
599    pub fn n_ctx_train(&self) -> u32 {
600        let n_ctx_train = unsafe { llama_model_n_ctx_train(self.model.as_ptr()) };
601        u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
602    }
603
604    /// Get all tokens in the model.
605    ///
606    /// This function returns an iterator over all the tokens in the model. Each item in the iterator is a tuple
607    /// containing a `LlamaToken` and its corresponding string representation (or an error if the conversion fails).
608    ///
609    /// # Parameters
610    ///
611    /// - `special`: The `Special` value that determines how special tokens (like BOS, EOS, etc.) are handled.
612    pub fn tokens(
613        &self,
614        special: Special,
615    ) -> impl Iterator<Item = (LlamaToken, Result<String, TokenToStringError>)> + '_ {
616        (0..self.n_vocab())
617            .map(LlamaToken::new)
618            .map(move |llama_token| (llama_token, self.token_to_str(llama_token, special)))
619    }
620
621    /// Get the beginning of stream token.
622    ///
623    /// This function returns the token that represents the beginning of a stream (BOS token).
624    #[must_use]
625    pub fn token_bos(&self) -> LlamaToken {
626        self.get_vocab().bos()
627    }
628
629    /// Get the end of stream token.
630    ///
631    /// This function returns the token that represents the end of a stream (EOS token).
632    #[must_use]
633    pub fn token_eos(&self) -> LlamaToken {
634        self.get_vocab().eos()
635    }
636
637    /// Get the newline token.
638    ///
639    /// This function returns the token that represents a newline character.
640    #[must_use]
641    pub fn token_nl(&self) -> LlamaToken {
642        self.get_vocab().nl()
643    }
644
645    /// Check if a token represents the end of generation (end of turn, end of sequence, etc.).
646    ///
647    /// This function returns `true` if the provided token signifies the end of generation or end of sequence,
648    /// such as EOS or other special tokens.
649    ///
650    /// # Parameters
651    ///
652    /// - `token`: The `LlamaToken` to check.
653    ///
654    /// # Returns
655    ///
656    /// - `true` if the token is an end-of-generation token, otherwise `false`.
657    #[must_use]
658    pub fn is_eog_token(&self, token: LlamaToken) -> bool {
659        self.get_vocab().is_eog(token)
660    }
661
662    /// Get the classification token.
663    #[must_use]
664    pub fn token_cls(&self) -> LlamaToken {
665        self.get_vocab().cls()
666    }
667
668    /// Get the end-of-turn token.
669    #[must_use]
670    pub fn token_eot(&self) -> LlamaToken {
671        self.get_vocab().eot()
672    }
673
674    /// Get the padding token.
675    #[must_use]
676    pub fn token_pad(&self) -> LlamaToken {
677        self.get_vocab().pad()
678    }
679
680    /// Get the separator token.
681    #[must_use]
682    pub fn token_sep(&self) -> LlamaToken {
683        self.get_vocab().sep()
684    }
685
686    /// Get the fill-in-the-middle prefix token.
687    #[must_use]
688    pub fn token_fim_pre(&self) -> LlamaToken {
689        self.get_vocab().fim_pre()
690    }
691
692    /// Get the fill-in-the-middle suffix token.
693    #[must_use]
694    pub fn token_fim_suf(&self) -> LlamaToken {
695        self.get_vocab().fim_suf()
696    }
697
698    /// Get the fill-in-the-middle middle token.
699    #[must_use]
700    pub fn token_fim_mid(&self) -> LlamaToken {
701        self.get_vocab().fim_mid()
702    }
703
704    /// Get the fill-in-the-middle padding token.
705    #[must_use]
706    pub fn token_fim_pad(&self) -> LlamaToken {
707        self.get_vocab().fim_pad()
708    }
709
710    /// Get the fill-in-the-middle repository token.
711    #[must_use]
712    pub fn token_fim_rep(&self) -> LlamaToken {
713        self.get_vocab().fim_rep()
714    }
715
716    /// Get the fill-in-the-middle separator token.
717    #[must_use]
718    pub fn token_fim_sep(&self) -> LlamaToken {
719        self.get_vocab().fim_sep()
720    }
721
722    /// Check if a token is a control token.
723    #[must_use]
724    pub fn token_is_control(&self, token: LlamaToken) -> bool {
725        self.get_vocab().is_control(token)
726    }
727
728    /// Get the score of a token.
729    #[must_use]
730    pub fn token_get_score(&self, token: LlamaToken) -> f32 {
731        self.get_vocab().get_score(token)
732    }
733
734    /// Get the raw text of a token.
735    ///
736    /// # Errors
737    ///
738    /// Returns an error if the token text is null or not valid UTF-8.
739    pub fn token_get_text(&self, token: LlamaToken) -> Result<&str, StringFromModelError> {
740        let ptr = unsafe {
741            llama_cpp_sys_4::llama_vocab_get_text(self.get_vocab().vocab.as_ref(), token.0)
742        };
743        if ptr.is_null() {
744            return Err(StringFromModelError::ReturnedError(-1));
745        }
746        let cstr = unsafe { CStr::from_ptr(ptr) };
747        cstr.to_str().map_err(StringFromModelError::Utf8Error)
748    }
749
750    /// Check if a BOS token should be added when tokenizing.
751    #[must_use]
752    pub fn add_bos_token(&self) -> bool {
753        self.get_vocab().get_add_bos()
754    }
755
756    /// Check if an EOS token should be added when tokenizing.
757    #[must_use]
758    pub fn add_eos_token(&self) -> bool {
759        self.get_vocab().get_add_eos()
760    }
761
762    /// Get the decoder start token.
763    ///
764    /// This function returns the token used to signal the start of decoding (i.e., the token used at the start
765    /// of a sequence generation).
766    #[must_use]
767    pub fn decode_start_token(&self) -> LlamaToken {
768        let token = unsafe { llama_model_decoder_start_token(self.model.as_ptr()) };
769        LlamaToken(token)
770    }
771
772    /// Convert a single token to a string.
773    ///
774    /// This function converts a `LlamaToken` into its string representation.
775    ///
776    /// # Errors
777    ///
778    /// This function returns an error if the token cannot be converted to a string. For more details, refer to
779    /// [`TokenToStringError`].
780    ///
781    /// # Parameters
782    ///
783    /// - `token`: The `LlamaToken` to convert.
784    /// - `special`: The `Special` value used to handle special tokens.
785    pub fn token_to_str(
786        &self,
787        token: LlamaToken,
788        special: Special,
789    ) -> Result<String, TokenToStringError> {
790        self.token_to_str_with_size(token, 32, special)
791    }
792
793    /// Convert a single token to bytes.
794    ///
795    /// This function converts a `LlamaToken` into a byte representation.
796    ///
797    /// # Errors
798    ///
799    /// This function returns an error if the token cannot be converted to bytes. For more details, refer to
800    /// [`TokenToStringError`].
801    ///
802    /// # Parameters
803    ///
804    /// - `token`: The `LlamaToken` to convert.
805    /// - `special`: The `Special` value used to handle special tokens.
806    pub fn token_to_bytes(
807        &self,
808        token: LlamaToken,
809        special: Special,
810    ) -> Result<Vec<u8>, TokenToStringError> {
811        self.token_to_bytes_with_size(token, 32, special, None)
812    }
813
814    /// Convert a single token to its raw llama.cpp piece bytes.
815    ///
816    /// Unlike [`LlamaModel::token_to_bytes`], this does not discard tokens based
817    /// on token attributes before calling llama.cpp. This is useful for runtimes
818    /// that must preserve control, byte, or other model-specific pieces exactly.
819    ///
820    /// This convenience form sizes the buffer automatically: it attempts a small
821    /// default buffer and, if llama.cpp reports it is too small, retries once
822    /// with the exact size llama.cpp requires. Use
823    /// [`LlamaModel::token_to_raw_bytes_with_size`] when you want explicit
824    /// control over the buffer (for example to reuse an allocation).
825    ///
826    /// # Errors
827    ///
828    /// This function returns an error if llama.cpp cannot convert the token.
829    pub fn token_to_raw_bytes(
830        &self,
831        token: LlamaToken,
832        special: Special,
833    ) -> Result<Vec<u8>, TokenToStringError> {
834        let mut buffer = Vec::with_capacity(32);
835        self.token_to_raw_bytes_into(token, special, &mut buffer)?;
836        Ok(buffer)
837    }
838
839    /// Converts one token to raw piece bytes in caller-owned reusable storage.
840    ///
841    /// # Errors
842    ///
843    /// Returns an error if llama.cpp reports an unknown token or an excessive
844    /// required buffer length.
845    pub fn token_to_raw_bytes_into(
846        &self,
847        token: LlamaToken,
848        special: Special,
849        buffer: &mut Vec<u8>,
850    ) -> Result<(), TokenToStringError> {
851        let special = matches!(special, Special::Tokenize);
852        buffer.clear();
853        if buffer.capacity() < 32 {
854            buffer.reserve(32);
855        }
856        loop {
857            let capacity = buffer.capacity();
858            buffer.resize(capacity, 0);
859            let native_capacity = c_int::try_from(capacity)
860                .map_err(|_| TokenToStringError::BufferCapacityExceeded(capacity))?;
861            let size = unsafe {
862                llama_token_to_piece(
863                    self.get_vocab().vocab.as_ref(),
864                    token.0,
865                    buffer.as_mut_ptr().cast::<c_char>(),
866                    native_capacity,
867                    0,
868                    special,
869                )
870            };
871            match size {
872                0 => {
873                    buffer.clear();
874                    return Err(TokenToStringError::UnknownTokenType);
875                }
876                needed if needed.is_negative() => {
877                    let required = usize::try_from(-needed)
878                        .map_err(|_| TokenToStringError::InsufficientBufferSpace(needed))?;
879                    buffer.clear();
880                    if required <= capacity {
881                        return Err(TokenToStringError::InsufficientBufferSpace(needed));
882                    }
883                    buffer.reserve_exact(required);
884                }
885                written => {
886                    let written = usize::try_from(written).map_err(|_| {
887                        TokenToStringError::NativePieceLength {
888                            returned: written,
889                            capacity,
890                        }
891                    })?;
892                    if written > capacity {
893                        buffer.clear();
894                        return Err(TokenToStringError::NativePieceLength {
895                            returned: size,
896                            capacity,
897                        });
898                    }
899                    buffer.truncate(written);
900                    return Ok(());
901                }
902            }
903        }
904    }
905
906    /// Convert a slice of tokens to their concatenated raw llama.cpp piece bytes.
907    ///
908    /// This is the batch counterpart to [`LlamaModel::token_to_raw_bytes`]: it
909    /// forwards each token directly to `llama_token_to_piece` without the
910    /// token-attribute filtering applied by [`LlamaModel::tokens_to_str`] /
911    /// [`LlamaModel::detokenize`], preserving control, byte, and other
912    /// model-specific pieces exactly. The bytes are not guaranteed to be valid
913    /// UTF-8 on their own, since a single codepoint may be split across several
914    /// byte-fallback tokens; see [`crate::token::detokenizer`] for incremental,
915    /// UTF-8-aware decoding.
916    ///
917    /// # Errors
918    ///
919    /// Returns an error if any token cannot be converted (see
920    /// [`LlamaModel::token_to_raw_bytes`]).
921    pub fn tokens_to_raw_bytes(
922        &self,
923        tokens: &[LlamaToken],
924        special: Special,
925    ) -> Result<Vec<u8>, TokenToStringError> {
926        let mut bytes = Vec::new();
927        for &token in tokens {
928            bytes.extend_from_slice(&self.token_to_raw_bytes(token, special)?);
929        }
930        Ok(bytes)
931    }
932
933    /// Convert a vector of tokens to a single string.
934    ///
935    /// This function takes a slice of `LlamaToken`s and converts them into a single string, concatenating their
936    /// string representations.
937    ///
938    /// # Errors
939    ///
940    /// This function returns an error if any token cannot be converted to a string. For more details, refer to
941    /// [`TokenToStringError`].
942    ///
943    /// # Parameters
944    ///
945    /// - `tokens`: A slice of `LlamaToken`s to convert.
946    /// - `special`: The `Special` value used to handle special tokens.
947    pub fn tokens_to_str(
948        &self,
949        tokens: &[LlamaToken],
950        special: Special,
951    ) -> Result<String, TokenToStringError> {
952        let mut builder = String::with_capacity(tokens.len() * 4);
953        for str in tokens
954            .iter()
955            .copied()
956            .map(|t| self.token_to_str(t, special))
957        {
958            builder += &str?;
959        }
960        Ok(builder)
961    }
962
963    /// Convert a string to a vector of tokens.
964    ///
965    /// This function converts a string into a vector of `LlamaToken`s. The function will tokenize the string
966    /// and return the corresponding tokens.
967    ///
968    /// # Errors
969    ///
970    /// - This function will return an error if the input string contains a null byte.
971    ///
972    /// # Panics
973    ///
974    /// - This function will panic if the number of tokens exceeds `usize::MAX`.
975    ///
976    /// # Example
977    ///
978    /// ```no_run
979    /// use llama_cpp_4::model::LlamaModel;
980    ///
981    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
982    /// use std::path::Path;
983    /// use llama_cpp_4::model::AddBos;
984    /// let backend = llama_cpp_4::llama_backend::LlamaBackend::init()?;
985    /// let model = LlamaModel::load_from_file(&backend, Path::new("path/to/model"), &Default::default())?;
986    /// let tokens = model.str_to_token("Hello, World!", AddBos::Always)?;
987    /// # Ok(())
988    /// # }
989    /// ```
990    pub fn str_to_token(
991        &self,
992        str: &str,
993        add_bos: AddBos,
994    ) -> Result<Vec<LlamaToken>, StringToTokenError> {
995        let mut buffer = Vec::new();
996        self.str_to_token_into(str, add_bos, &mut buffer)?;
997        Ok(buffer)
998    }
999
1000    /// Tokenizes into caller-owned storage so repeated operations can reuse
1001    /// their allocation.
1002    ///
1003    /// # Errors
1004    ///
1005    /// Returns an error when the text contains NUL or its length exceeds the
1006    /// native integer representation.
1007    pub fn str_to_token_into(
1008        &self,
1009        str: &str,
1010        add_bos: AddBos,
1011        buffer: &mut Vec<LlamaToken>,
1012    ) -> Result<(), StringToTokenError> {
1013        let add_bos = match add_bos {
1014            AddBos::Always => true,
1015            AddBos::Never => false,
1016        };
1017        if let Some(position) = str.as_bytes().iter().position(|byte| *byte == 0) {
1018            return Err(StringToTokenError::InteriorNul(position));
1019        }
1020        let tokens_estimation = std::cmp::max(8, (str.len() / 2) + usize::from(add_bos));
1021        buffer.clear();
1022        if buffer.capacity() < tokens_estimation {
1023            buffer.reserve(tokens_estimation);
1024        }
1025        let buffer_capacity = c_int::try_from(buffer.capacity())?;
1026        let text_length = c_int::try_from(str.len())?;
1027        let size = unsafe {
1028            llama_tokenize(
1029                self.get_vocab().vocab.as_ref(),
1030                str.as_ptr().cast(),
1031                text_length,
1032                buffer.as_mut_ptr().cast(),
1033                buffer_capacity,
1034                add_bos,
1035                true,
1036            )
1037        };
1038
1039        // if we fail the first time we can resize the vector to the correct size and try again. This should never fail.
1040        // as a result - size is guaranteed to be positive here.
1041        let size = if size.is_negative() {
1042            let required = usize::try_from(size.unsigned_abs())?;
1043            if buffer.capacity() < required {
1044                buffer.reserve_exact(required);
1045            }
1046            let retry_capacity = c_int::try_from(buffer.capacity())?;
1047            unsafe {
1048                llama_tokenize(
1049                    self.get_vocab().vocab.as_ref(),
1050                    str.as_ptr().cast(),
1051                    text_length,
1052                    buffer.as_mut_ptr().cast(),
1053                    retry_capacity,
1054                    add_bos,
1055                    true,
1056                )
1057            }
1058        } else {
1059            size
1060        };
1061
1062        let native_size = size;
1063        let size = usize::try_from(native_size)?;
1064        if size > buffer.capacity() {
1065            return Err(StringToTokenError::NativeTokenCount {
1066                returned: native_size,
1067                capacity: buffer.capacity(),
1068            });
1069        }
1070
1071        // Safety: `size <= capacity` and llama.cpp initialized elements up to `size`.
1072        unsafe { buffer.set_len(size) }
1073        Ok(())
1074    }
1075
1076    /// Counts tokens without materializing token identifiers.
1077    ///
1078    /// # Errors
1079    ///
1080    /// Returns an error when the text contains NUL or its length/count cannot
1081    /// be represented by the native API.
1082    pub fn str_token_count(&self, str: &str, add_bos: AddBos) -> Result<usize, StringToTokenError> {
1083        let add_bos = matches!(add_bos, AddBos::Always);
1084        if let Some(position) = str.as_bytes().iter().position(|byte| *byte == 0) {
1085            return Err(StringToTokenError::InteriorNul(position));
1086        }
1087        let size = unsafe {
1088            llama_tokenize(
1089                self.get_vocab().vocab.as_ref(),
1090                str.as_ptr().cast(),
1091                c_int::try_from(str.len())?,
1092                std::ptr::null_mut(),
1093                0,
1094                add_bos,
1095                true,
1096            )
1097        };
1098        Ok(usize::try_from(i64::from(size).unsigned_abs())?)
1099    }
1100
1101    /// Get the type of a token.
1102    ///
1103    /// This function retrieves the attributes associated with a given token. The attributes are typically used to
1104    /// understand whether the token represents a special type of token (e.g., beginning-of-sequence (BOS), end-of-sequence (EOS),
1105    /// control tokens, etc.).
1106    ///
1107    /// # Panics
1108    ///
1109    /// - This function will panic if the token type is unknown or cannot be converted to a valid `LlamaTokenAttrs`.
1110    ///
1111    /// # Example
1112    ///
1113    /// ```no_run
1114    /// use llama_cpp_4::model::LlamaModel;
1115    /// use llama_cpp_4::model::params::LlamaModelParams;
1116    /// use llama_cpp_4::llama_backend::LlamaBackend;
1117    /// use llama_cpp_4::token::LlamaToken;
1118    ///
1119    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1120    /// let backend = LlamaBackend::init()?;
1121    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1122    /// let token = LlamaToken::new(42);
1123    /// let token_attrs = model.token_attr(token);
1124    /// # Ok(())
1125    /// # }
1126    /// ```
1127    #[must_use]
1128    pub fn token_attr(&self, LlamaToken(id): LlamaToken) -> LlamaTokenAttrs {
1129        let token_type =
1130            unsafe { llama_cpp_sys_4::llama_vocab_get_attr(self.get_vocab().vocab.as_ref(), id) };
1131        LlamaTokenAttrs::try_from(token_type).expect("token type is valid")
1132    }
1133
1134    /// Detokenize a slice of tokens into a string.
1135    ///
1136    /// This is the inverse of [`str_to_token`](Self::str_to_token).
1137    ///
1138    /// # Parameters
1139    ///
1140    /// - `tokens`: The tokens to detokenize.
1141    /// - `remove_special`: If `true`, special tokens are removed from the output.
1142    /// - `unparse_special`: If `true`, special tokens are rendered as their text representation.
1143    ///
1144    /// # Errors
1145    ///
1146    /// Returns an error if the detokenized text is not valid UTF-8.
1147    #[allow(
1148        clippy::cast_possible_truncation,
1149        clippy::cast_possible_wrap,
1150        clippy::cast_sign_loss
1151    )]
1152    pub fn detokenize(
1153        &self,
1154        tokens: &[LlamaToken],
1155        remove_special: bool,
1156        unparse_special: bool,
1157    ) -> Result<String, StringFromModelError> {
1158        // First call with empty buffer to get required size
1159        let n_tokens = tokens.len() as i32;
1160        let token_ptr = tokens.as_ptr().cast::<llama_cpp_sys_4::llama_token>();
1161        let needed = unsafe {
1162            llama_detokenize(
1163                self.get_vocab().vocab.as_ref(),
1164                token_ptr,
1165                n_tokens,
1166                std::ptr::null_mut(),
1167                0,
1168                remove_special,
1169                unparse_special,
1170            )
1171        };
1172        // llama_detokenize returns negative required size when buffer is too small
1173        let buf_size = if needed < 0 {
1174            (-needed) as usize
1175        } else {
1176            needed as usize
1177        };
1178        let mut buf = vec![0u8; buf_size];
1179        let ret = unsafe {
1180            llama_detokenize(
1181                self.get_vocab().vocab.as_ref(),
1182                token_ptr,
1183                n_tokens,
1184                buf.as_mut_ptr().cast::<c_char>(),
1185                buf_size as i32,
1186                remove_special,
1187                unparse_special,
1188            )
1189        };
1190        if ret < 0 {
1191            return Err(StringFromModelError::ReturnedError(ret));
1192        }
1193        let len = ret as usize;
1194        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
1195        Ok(s.to_owned())
1196    }
1197
1198    /// Convert a token to a string with a specified buffer size.
1199    ///
1200    /// This function allows you to convert a token into a string, with the ability to specify a buffer size for the operation.
1201    /// It is generally recommended to use `LlamaModel::token_to_str` instead, as 8 bytes is typically sufficient for most tokens,
1202    /// and the extra buffer size doesn't usually matter.
1203    ///
1204    /// # Errors
1205    ///
1206    /// - If the token type is unknown, an error will be returned.
1207    /// - If the resultant token exceeds the provided `buffer_size`, an error will occur.
1208    /// - If the token string returned by `llama-cpp` is not valid UTF-8, it will return an error.
1209    ///
1210    /// # Panics
1211    ///
1212    /// - This function will panic if the `buffer_size` does not fit into a `c_int`.
1213    /// - It will also panic if the size returned from `llama-cpp` does not fit into a `usize`, which should typically never happen.
1214    ///
1215    /// # Example
1216    ///
1217    /// ```no_run
1218    /// use llama_cpp_4::model::{LlamaModel, Special};
1219    /// use llama_cpp_4::model::params::LlamaModelParams;
1220    /// use llama_cpp_4::llama_backend::LlamaBackend;
1221    /// use llama_cpp_4::token::LlamaToken;
1222    ///
1223    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1224    /// let backend = LlamaBackend::init()?;
1225    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1226    /// let token = LlamaToken::new(42);
1227    /// let token_string = model.token_to_str_with_size(token, 32, Special::Plaintext)?;
1228    /// # Ok(())
1229    /// # }
1230    /// ```
1231    pub fn token_to_str_with_size(
1232        &self,
1233        token: LlamaToken,
1234        buffer_size: usize,
1235        special: Special,
1236    ) -> Result<String, TokenToStringError> {
1237        let bytes = self.token_to_bytes_with_size(token, buffer_size, special, None)?;
1238        Ok(String::from_utf8(bytes)?)
1239    }
1240
1241    /// Convert a token to bytes with a specified buffer size.
1242    ///
1243    /// Generally you should use [`LlamaModel::token_to_bytes`] instead as 8 bytes is enough for most words and
1244    /// the extra bytes do not really matter.
1245    ///
1246    /// # Errors
1247    ///
1248    /// - if the token type is unknown
1249    /// - the resultant token is larger than `buffer_size`.
1250    ///
1251    /// # Panics
1252    ///
1253    /// - This function will panic if `buffer_size` cannot fit into a `c_int`.
1254    /// - It will also panic if the size returned from `llama-cpp` cannot be converted to `usize` (which should not happen).
1255    ///
1256    /// # Example
1257    ///
1258    /// ```no_run
1259    /// use llama_cpp_4::model::{LlamaModel, Special};
1260    /// use llama_cpp_4::model::params::LlamaModelParams;
1261    /// use llama_cpp_4::llama_backend::LlamaBackend;
1262    /// use llama_cpp_4::token::LlamaToken;
1263    ///
1264    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1265    /// let backend = LlamaBackend::init()?;
1266    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1267    /// let token = LlamaToken::new(42);
1268    /// let token_bytes = model.token_to_bytes_with_size(token, 32, Special::Plaintext, None)?;
1269    /// # Ok(())
1270    /// # }
1271    /// ```
1272    pub fn token_to_bytes_with_size(
1273        &self,
1274        token: LlamaToken,
1275        buffer_size: usize,
1276        special: Special,
1277        lstrip: Option<NonZeroU16>,
1278    ) -> Result<Vec<u8>, TokenToStringError> {
1279        if token == self.token_nl() {
1280            return Ok(String::from("\n").into_bytes());
1281        }
1282
1283        // unsure what to do with this in the face of the 'special' arg + attr changes
1284        let attrs = self.token_attr(token);
1285        if (attrs.contains(LlamaTokenAttr::Control)
1286            && (token == self.token_bos() || token == self.token_eos()))
1287            || attrs.is_empty()
1288            || attrs
1289                .intersects(LlamaTokenAttr::Unknown | LlamaTokenAttr::Byte | LlamaTokenAttr::Unused)
1290        {
1291            return Ok(Vec::new());
1292        }
1293
1294        let special = match special {
1295            Special::Tokenize => true,
1296            Special::Plaintext => false,
1297        };
1298
1299        let string = CString::new(vec![b'*'; buffer_size]).expect("no null");
1300        let len = string.as_bytes().len();
1301        let len = c_int::try_from(len).expect("length fits into c_int");
1302        let buf = string.into_raw();
1303        let lstrip = lstrip.map_or(0, |it| i32::from(it.get()));
1304        let size = unsafe {
1305            llama_token_to_piece(
1306                self.get_vocab().vocab.as_ref(),
1307                token.0,
1308                buf,
1309                len,
1310                lstrip,
1311                special,
1312            )
1313        };
1314
1315        match size {
1316            0 => Err(TokenToStringError::UnknownTokenType),
1317            i if i.is_negative() => Err(TokenToStringError::InsufficientBufferSpace(i)),
1318            size => {
1319                let string = unsafe { CString::from_raw(buf) };
1320                let mut bytes = string.into_bytes();
1321                let len = usize::try_from(size).expect("size is positive and fits into usize");
1322                bytes.truncate(len);
1323                Ok(bytes)
1324            }
1325        }
1326    }
1327
1328    /// Convert a token to raw llama.cpp piece bytes with a specified buffer size.
1329    ///
1330    /// This intentionally bypasses the token-attribute filtering in
1331    /// [`LlamaModel::token_to_bytes_with_size`] and forwards directly to
1332    /// `llama_token_to_piece`.
1333    ///
1334    /// # Errors
1335    ///
1336    /// - if llama.cpp reports an unknown token type.
1337    /// - if the resultant token is larger than `buffer_size`.
1338    ///
1339    /// # Panics
1340    ///
1341    /// This function will panic if `buffer_size` cannot fit into a `c_int`.
1342    pub fn token_to_raw_bytes_with_size(
1343        &self,
1344        token: LlamaToken,
1345        buffer_size: usize,
1346        special: Special,
1347        lstrip: Option<NonZeroU16>,
1348    ) -> Result<Vec<u8>, TokenToStringError> {
1349        let special = match special {
1350            Special::Tokenize => true,
1351            Special::Plaintext => false,
1352        };
1353        let mut buffer = vec![0_u8; buffer_size];
1354        let len = c_int::try_from(buffer.len()).expect("length fits into c_int");
1355        let lstrip = lstrip.map_or(0, |it| i32::from(it.get()));
1356        let size = unsafe {
1357            llama_token_to_piece(
1358                self.get_vocab().vocab.as_ref(),
1359                token.0,
1360                buffer.as_mut_ptr().cast::<c_char>(),
1361                len,
1362                lstrip,
1363                special,
1364            )
1365        };
1366
1367        match size {
1368            0 => Err(TokenToStringError::UnknownTokenType),
1369            i if i.is_negative() => Err(TokenToStringError::InsufficientBufferSpace(i)),
1370            size => {
1371                let len = usize::try_from(size).expect("size is positive and fits into usize");
1372                buffer.truncate(len);
1373                Ok(buffer)
1374            }
1375        }
1376    }
1377    /// The number of tokens the model was trained on.
1378    ///
1379    /// This function returns the number of tokens the model was trained on. It is returned as a `c_int` for maximum
1380    /// compatibility with the underlying llama-cpp library, though it can typically be cast to an `i32` without issue.
1381    ///
1382    /// # Example
1383    ///
1384    /// ```no_run
1385    /// use llama_cpp_4::model::LlamaModel;
1386    /// use llama_cpp_4::model::params::LlamaModelParams;
1387    /// use llama_cpp_4::llama_backend::LlamaBackend;
1388    ///
1389    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1390    /// let backend = LlamaBackend::init()?;
1391    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1392    /// let n_vocab = model.n_vocab();
1393    /// # Ok(())
1394    /// # }
1395    /// ```
1396    #[must_use]
1397    pub fn n_vocab(&self) -> i32 {
1398        self.get_vocab().n_tokens()
1399    }
1400
1401    /// The type of vocab the model was trained on.
1402    ///
1403    /// This function returns the type of vocabulary used by the model, such as whether it is based on byte-pair encoding (BPE),
1404    /// word-level tokens, or another tokenization scheme.
1405    ///
1406    /// # Panics
1407    ///
1408    /// - This function will panic if `llama-cpp` emits a vocab type that is not recognized or is invalid for this library.
1409    ///
1410    /// # Example
1411    ///
1412    /// ```no_run
1413    /// use llama_cpp_4::model::LlamaModel;
1414    /// use llama_cpp_4::model::params::LlamaModelParams;
1415    /// use llama_cpp_4::llama_backend::LlamaBackend;
1416    ///
1417    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1418    /// let backend = LlamaBackend::init()?;
1419    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1420    /// let vocab_type = model.vocab_type();
1421    /// # Ok(())
1422    /// # }
1423    /// ```
1424    #[must_use]
1425    pub fn vocab_type(&self) -> VocabType {
1426        let vocab_type = unsafe { llama_vocab_type(self.get_vocab().vocab.as_ref()) };
1427        VocabType::try_from(vocab_type).expect("invalid vocab type")
1428    }
1429
1430    /// Returns the number of embedding dimensions for the model.
1431    ///
1432    /// This function retrieves the number of embeddings (or embedding dimensions) used by the model. It is typically
1433    /// used for analyzing model architecture and setting up context parameters or other model configuration aspects.
1434    ///
1435    /// # Panics
1436    ///
1437    /// - This function may panic if the underlying `llama-cpp` library returns an invalid embedding dimension value.
1438    ///
1439    /// # Example
1440    ///
1441    /// ```no_run
1442    /// use llama_cpp_4::model::LlamaModel;
1443    /// use llama_cpp_4::model::params::LlamaModelParams;
1444    /// use llama_cpp_4::llama_backend::LlamaBackend;
1445    ///
1446    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1447    /// let backend = LlamaBackend::init()?;
1448    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1449    /// let n_embd = model.n_embd();
1450    /// # Ok(())
1451    /// # }
1452    /// ```
1453    #[must_use]
1454    pub fn n_embd(&self) -> c_int {
1455        unsafe { llama_model_n_embd(self.model.as_ptr()) }
1456    }
1457
1458    /// Get the number of transformer layers in the model.
1459    #[must_use]
1460    pub fn n_layer(&self) -> c_int {
1461        unsafe { llama_model_n_layer(self.model.as_ptr()) }
1462    }
1463
1464    /// Get the number of `NextN` / MTP prediction heads bundled with the model.
1465    ///
1466    /// Returns `0` when the checkpoint has no `NextN` blocks. Multi-head models
1467    /// (e.g. Step3.5) return values greater than `1`; pair with
1468    /// [`crate::context::LlamaContext::set_nextn_layer_offset`] on the draft
1469    /// context. See [`crate::mtp`] for the speculative-decoding workflow.
1470    ///
1471    /// # Examples
1472    ///
1473    /// ```no_run
1474    /// # use llama_cpp_4::llama_backend::LlamaBackend;
1475    /// # use llama_cpp_4::model::{LlamaModel, params::LlamaModelParams};
1476    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1477    /// # let backend = LlamaBackend::init()?;
1478    /// # let model = LlamaModel::load_from_file(&backend, "model.gguf", &LlamaModelParams::default())?;
1479    /// if model.n_layer_nextn() > 0 {
1480    ///     println!("MTP model with {} NextN heads", model.n_layer_nextn());
1481    /// }
1482    /// # Ok(())
1483    /// # }
1484    /// ```
1485    #[must_use]
1486    pub fn n_layer_nextn(&self) -> c_int {
1487        unsafe { llama_model_n_layer_nextn(self.model.as_ptr()) }
1488    }
1489
1490    /// Get the number of mixture-of-experts (`MoE`) layers in the model.
1491    ///
1492    /// Returns `0` for dense (non-MoE) checkpoints.
1493    #[must_use]
1494    pub fn n_expert(&self) -> c_int {
1495        unsafe { llama_model_n_expert(self.model.as_ptr()) }
1496    }
1497
1498    /// Number of backend devices the model tensors are spread across.
1499    ///
1500    /// Use with [`Self::get_device`] to inspect each device. Returns `0` when
1501    /// the model is not yet loaded onto any device.
1502    #[must_use]
1503    pub fn n_devices(&self) -> c_int {
1504        unsafe { llama_model_n_devices(self.model.as_ptr()) }
1505    }
1506
1507    /// Get the backend device at `index`.
1508    ///
1509    /// Valid indices satisfy `0 <= index < n_devices()`. Returns `None` for
1510    /// out-of-range indices or when the device pointer is null.
1511    #[must_use]
1512    pub fn get_device(&self, index: i32) -> Option<LlamaBackendDevice> {
1513        if index < 0 || index >= self.n_devices() {
1514            return None;
1515        }
1516        let dev = unsafe { llama_model_get_device(self.model.as_ptr(), index) };
1517        if dev.is_null() {
1518            None
1519        } else {
1520            Some(LlamaBackendDevice { dev })
1521        }
1522    }
1523
1524    /// Iterate backend devices the model tensors are spread across.
1525    ///
1526    /// Equivalent to calling [`Self::get_device`] for `0..self.n_devices()`.
1527    /// Use [`LlamaBackendDevice::memory`] to inspect free/total bytes per device.
1528    #[must_use]
1529    pub fn devices(&self) -> LlamaBackendDevices<'_> {
1530        LlamaBackendDevices {
1531            model: self,
1532            next: 0,
1533        }
1534    }
1535
1536    /// Target-model layer indices stored in this checkpoint.
1537    ///
1538    /// Populated for EAGLE / distillation draft models that record which target
1539    /// layers they were trained against. Returns an empty slice when the
1540    /// metadata is absent.
1541    #[must_use]
1542    pub fn target_layer_ids(&self) -> &[i32] {
1543        let n = unsafe { llama_model_target_layer_ids_n(self.model.as_ptr()) };
1544        if n == 0 {
1545            return &[];
1546        }
1547        let ptr = unsafe { llama_model_target_layer_ids(self.model.as_ptr()) };
1548        if ptr.is_null() {
1549            &[]
1550        } else {
1551            unsafe { slice::from_raw_parts(ptr, n as usize) }
1552        }
1553    }
1554
1555    /// Get the number of attention heads in the model.
1556    #[must_use]
1557    pub fn n_head(&self) -> c_int {
1558        unsafe { llama_model_n_head(self.model.as_ptr()) }
1559    }
1560
1561    /// Get the number of key-value attention heads in the model.
1562    #[must_use]
1563    pub fn n_head_kv(&self) -> c_int {
1564        unsafe { llama_model_n_head_kv(self.model.as_ptr()) }
1565    }
1566
1567    /// Get the input embedding size of the model.
1568    #[must_use]
1569    pub fn n_embd_inp(&self) -> c_int {
1570        unsafe { llama_model_n_embd_inp(self.model.as_ptr()) }
1571    }
1572
1573    /// Get the output embedding size of the model.
1574    #[must_use]
1575    pub fn n_embd_out(&self) -> c_int {
1576        unsafe { llama_model_n_embd_out(self.model.as_ptr()) }
1577    }
1578
1579    /// Get the sliding window attention size of the model.
1580    /// Returns 0 if the model does not use sliding window attention.
1581    #[must_use]
1582    pub fn n_swa(&self) -> c_int {
1583        unsafe { llama_model_n_swa(self.model.as_ptr()) }
1584    }
1585
1586    /// Get the `RoPE` type used by the model.
1587    #[must_use]
1588    pub fn rope_type(&self) -> i32 {
1589        unsafe { llama_model_rope_type(self.model.as_ptr()) }
1590    }
1591
1592    /// Get the `RoPE` frequency scale used during training.
1593    #[must_use]
1594    pub fn rope_freq_scale_train(&self) -> f32 {
1595        unsafe { llama_model_rope_freq_scale_train(self.model.as_ptr()) }
1596    }
1597
1598    /// Get the model size in bytes.
1599    #[must_use]
1600    pub fn model_size(&self) -> u64 {
1601        unsafe { llama_model_size(self.model.as_ptr()) }
1602    }
1603
1604    /// Get the number of parameters in the model.
1605    #[must_use]
1606    pub fn n_params(&self) -> u64 {
1607        unsafe { llama_model_n_params(self.model.as_ptr()) }
1608    }
1609
1610    /// Get the number of classification outputs.
1611    #[must_use]
1612    pub fn n_cls_out(&self) -> u32 {
1613        unsafe { llama_model_n_cls_out(self.model.as_ptr()) }
1614    }
1615
1616    /// Get the classification label for the given index.
1617    ///
1618    /// # Errors
1619    ///
1620    /// Returns an error if the label is null or not valid UTF-8.
1621    pub fn cls_label(&self, index: u32) -> Result<&str, StringFromModelError> {
1622        let ptr = unsafe { llama_model_cls_label(self.model.as_ptr(), index) };
1623        if ptr.is_null() {
1624            return Err(StringFromModelError::ReturnedError(-1));
1625        }
1626        let cstr = unsafe { CStr::from_ptr(ptr) };
1627        cstr.to_str().map_err(StringFromModelError::Utf8Error)
1628    }
1629
1630    /// Get the number of metadata key-value pairs.
1631    #[must_use]
1632    pub fn meta_count(&self) -> c_int {
1633        unsafe { llama_model_meta_count(self.model.as_ptr()) }
1634    }
1635
1636    /// Get a model description string.
1637    ///
1638    /// The `buf_size` parameter specifies the maximum buffer size for the description.
1639    /// A default of 256 bytes is usually sufficient.
1640    ///
1641    /// # Errors
1642    ///
1643    /// Returns an error if the description could not be retrieved or is not valid UTF-8.
1644    #[allow(clippy::cast_sign_loss)]
1645    pub fn desc(&self, buf_size: usize) -> Result<String, StringFromModelError> {
1646        let mut buf = vec![0u8; buf_size];
1647        let ret = unsafe {
1648            llama_model_desc(
1649                self.model.as_ptr(),
1650                buf.as_mut_ptr().cast::<c_char>(),
1651                buf_size,
1652            )
1653        };
1654        if ret < 0 {
1655            return Err(StringFromModelError::ReturnedError(ret));
1656        }
1657        let len = ret as usize;
1658        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
1659        Ok(s.to_owned())
1660    }
1661
1662    /// Get a metadata key by index.
1663    ///
1664    /// The `buf_size` parameter specifies the maximum buffer size for the key.
1665    /// A default of 256 bytes is usually sufficient.
1666    ///
1667    /// # Errors
1668    ///
1669    /// Returns an error if the index is out of range or the key is not valid UTF-8.
1670    #[allow(clippy::cast_sign_loss)]
1671    pub fn meta_key_by_index(
1672        &self,
1673        index: i32,
1674        buf_size: usize,
1675    ) -> Result<String, StringFromModelError> {
1676        let mut buf = vec![0u8; buf_size];
1677        let ret = unsafe {
1678            llama_model_meta_key_by_index(
1679                self.model.as_ptr(),
1680                index,
1681                buf.as_mut_ptr().cast::<c_char>(),
1682                buf_size,
1683            )
1684        };
1685        if ret < 0 {
1686            return Err(StringFromModelError::ReturnedError(ret));
1687        }
1688        let len = ret as usize;
1689        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
1690        Ok(s.to_owned())
1691    }
1692
1693    /// Get a metadata value string by index.
1694    ///
1695    /// The `buf_size` parameter specifies the maximum buffer size for the value.
1696    /// Values can be large (e.g. chat templates, token lists), so 4096+ may be needed.
1697    ///
1698    /// # Errors
1699    ///
1700    /// Returns an error if the index is out of range or the value is not valid UTF-8.
1701    #[allow(clippy::cast_sign_loss)]
1702    pub fn meta_val_str_by_index(
1703        &self,
1704        index: i32,
1705        buf_size: usize,
1706    ) -> Result<String, StringFromModelError> {
1707        let mut buf = vec![0u8; buf_size];
1708        let ret = unsafe {
1709            llama_model_meta_val_str_by_index(
1710                self.model.as_ptr(),
1711                index,
1712                buf.as_mut_ptr().cast::<c_char>(),
1713                buf_size,
1714            )
1715        };
1716        if ret < 0 {
1717            return Err(StringFromModelError::ReturnedError(ret));
1718        }
1719        let len = ret as usize;
1720        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
1721        Ok(s.to_owned())
1722    }
1723
1724    /// Get a metadata value by key name.
1725    ///
1726    /// This is more convenient than iterating metadata by index when you know the key.
1727    /// The `buf_size` parameter specifies the maximum buffer size for the value.
1728    ///
1729    /// # Errors
1730    ///
1731    /// Returns an error if the key is not found, contains a null byte, or the value is not valid UTF-8.
1732    #[allow(clippy::cast_sign_loss)]
1733    pub fn meta_val_str(&self, key: &str, buf_size: usize) -> Result<String, StringFromModelError> {
1734        let c_key = CString::new(key).map_err(|_| StringFromModelError::ReturnedError(-1))?;
1735        let mut buf = vec![0u8; buf_size];
1736        let ret = unsafe {
1737            llama_model_meta_val_str(
1738                self.model.as_ptr(),
1739                c_key.as_ptr(),
1740                buf.as_mut_ptr().cast::<c_char>(),
1741                buf_size,
1742            )
1743        };
1744        if ret < 0 {
1745            return Err(StringFromModelError::ReturnedError(ret));
1746        }
1747        let len = ret as usize;
1748        let s = std::str::from_utf8(&buf[..len]).map_err(StringFromModelError::Utf8Error)?;
1749        Ok(s.to_owned())
1750    }
1751
1752    /// Get all metadata as a list of `(key, value)` pairs.
1753    ///
1754    /// This is a convenience method that iterates over all metadata entries.
1755    /// Keys use a buffer of 256 bytes and values use 4096 bytes.
1756    /// For values that may be larger (e.g. token lists), use
1757    /// [`meta_val_str_by_index`](Self::meta_val_str_by_index) directly with a larger buffer.
1758    ///
1759    /// # Errors
1760    ///
1761    /// Returns an error if any key or value cannot be read or is not valid UTF-8.
1762    #[allow(clippy::cast_sign_loss)]
1763    pub fn metadata(&self) -> Result<Vec<(String, String)>, StringFromModelError> {
1764        let count = self.meta_count();
1765        let mut result = Vec::with_capacity(count as usize);
1766        for i in 0..count {
1767            let key = self.meta_key_by_index(i, 256)?;
1768            let val = self.meta_val_str_by_index(i, 4096)?;
1769            result.push((key, val));
1770        }
1771        Ok(result)
1772    }
1773
1774    /// Check if the model has an encoder.
1775    #[must_use]
1776    pub fn has_encoder(&self) -> bool {
1777        unsafe { llama_model_has_encoder(self.model.as_ptr()) }
1778    }
1779
1780    /// Check if the model has a decoder.
1781    #[must_use]
1782    pub fn has_decoder(&self) -> bool {
1783        unsafe { llama_model_has_decoder(self.model.as_ptr()) }
1784    }
1785
1786    /// Check if the model is recurrent (e.g. Mamba, RWKV).
1787    #[must_use]
1788    pub fn is_recurrent(&self) -> bool {
1789        unsafe { llama_model_is_recurrent(self.model.as_ptr()) }
1790    }
1791
1792    /// Check if the model is a hybrid model.
1793    #[must_use]
1794    pub fn is_hybrid(&self) -> bool {
1795        unsafe { llama_model_is_hybrid(self.model.as_ptr()) }
1796    }
1797
1798    /// Check if the model is a diffusion model.
1799    #[must_use]
1800    pub fn is_diffusion(&self) -> bool {
1801        unsafe { llama_model_is_diffusion(self.model.as_ptr()) }
1802    }
1803
1804    /// Get chat template from model.
1805    ///
1806    /// # Errors
1807    ///
1808    /// - If the model does not have a chat template, it will return an error.
1809    /// - If the chat template is not a valid `CString`, it will return an error.
1810    ///
1811    /// # Example
1812    ///
1813    /// ```no_run
1814    /// use llama_cpp_4::model::LlamaModel;
1815    /// use llama_cpp_4::model::params::LlamaModelParams;
1816    /// use llama_cpp_4::llama_backend::LlamaBackend;
1817    ///
1818    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1819    /// let backend = LlamaBackend::init()?;
1820    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1821    /// let chat_template = model.get_chat_template(1024)?;
1822    /// # Ok(())
1823    /// # }
1824    /// ```
1825    #[allow(clippy::missing_panics_doc)] // We statically know this will not panic as long as the buffer size is sufficient
1826    pub fn get_chat_template(&self, buf_size: usize) -> Result<String, ChatTemplateError> {
1827        // longest known template is about 1200 bytes from llama.cpp
1828        let chat_temp = CString::new(vec![b'*'; buf_size]).expect("no null");
1829        let chat_ptr = chat_temp.into_raw();
1830        let chat_name = CString::new("tokenizer.chat_template").expect("no null bytes");
1831
1832        let ret = unsafe {
1833            llama_model_meta_val_str(self.model.as_ptr(), chat_name.as_ptr(), chat_ptr, buf_size)
1834        };
1835
1836        if ret < 0 {
1837            return Err(ChatTemplateError::MissingTemplate(ret));
1838        }
1839
1840        let template_c = unsafe { CString::from_raw(chat_ptr) };
1841        let template = template_c.to_str()?;
1842
1843        let ret: usize = ret.try_into().unwrap();
1844        if template.len() < ret {
1845            return Err(ChatTemplateError::BuffSizeError(ret + 1));
1846        }
1847
1848        Ok(template.to_owned())
1849    }
1850
1851    /// Loads a model from a file.
1852    ///
1853    /// This function loads a model from a specified file path and returns the corresponding `LlamaModel` instance.
1854    ///
1855    /// # Errors
1856    ///
1857    /// - If the path cannot be converted to a string or if the model file does not exist, it will return an error.
1858    /// - If the model cannot be loaded (e.g., due to an invalid or corrupted model file), it will return a `LlamaModelLoadError`.
1859    ///
1860    /// # Example
1861    ///
1862    /// ```no_run
1863    /// use llama_cpp_4::model::LlamaModel;
1864    /// use llama_cpp_4::model::params::LlamaModelParams;
1865    /// use llama_cpp_4::llama_backend::LlamaBackend;
1866    ///
1867    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1868    /// let backend = LlamaBackend::init()?;
1869    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
1870    /// # Ok(())
1871    /// # }
1872    /// ```
1873    #[tracing::instrument(skip_all, fields(params))]
1874    pub fn load_from_file(
1875        _: &LlamaBackend,
1876        path: impl AsRef<Path>,
1877        params: &LlamaModelParams,
1878    ) -> Result<Self, LlamaModelLoadError> {
1879        let path = path.as_ref();
1880        debug_assert!(
1881            Path::new(path).exists(),
1882            "{} does not exist",
1883            path.display()
1884        );
1885        let path = path
1886            .to_str()
1887            .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
1888
1889        let cstr = CString::new(path)?;
1890        let llama_model = unsafe { llama_model_load_from_file(cstr.as_ptr(), params.params) };
1891
1892        let model = NonNull::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
1893
1894        tracing::debug!(?path, "Loaded model");
1895        Ok(LlamaModel { model })
1896    }
1897
1898    /// Load a model from multiple split files.
1899    ///
1900    /// This function loads a model that has been split across multiple files. This is useful for
1901    /// very large models that exceed filesystem limitations or need to be distributed across
1902    /// multiple storage devices.
1903    ///
1904    /// # Arguments
1905    ///
1906    /// * `paths` - A slice of paths to the split model files
1907    /// * `params` - The model parameters
1908    ///
1909    /// # Errors
1910    ///
1911    /// Returns an error if:
1912    /// - Any of the paths cannot be converted to a C string
1913    /// - The model fails to load from the splits
1914    /// - Any path doesn't exist or isn't accessible
1915    ///
1916    /// # Example
1917    ///
1918    /// ```no_run
1919    /// use llama_cpp_4::model::{LlamaModel, params::LlamaModelParams};
1920    /// use llama_cpp_4::llama_backend::LlamaBackend;
1921    ///
1922    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1923    /// let backend = LlamaBackend::init()?;
1924    /// let params = LlamaModelParams::default();
1925    ///
1926    /// let paths = vec![
1927    ///     "model-00001-of-00003.gguf",
1928    ///     "model-00002-of-00003.gguf",
1929    ///     "model-00003-of-00003.gguf",
1930    /// ];
1931    ///
1932    /// let model = LlamaModel::load_from_splits(&backend, &paths, &params)?;
1933    /// # Ok(())
1934    /// # }
1935    /// ```
1936    #[tracing::instrument(skip_all)]
1937    pub fn load_from_splits(
1938        _: &LlamaBackend,
1939        paths: &[impl AsRef<Path>],
1940        params: &LlamaModelParams,
1941    ) -> Result<Self, LlamaModelLoadError> {
1942        // Convert paths to C strings
1943        let c_strings: Vec<CString> = paths
1944            .iter()
1945            .map(|p| {
1946                let path = p.as_ref();
1947                debug_assert!(path.exists(), "{} does not exist", path.display());
1948                let path_str = path
1949                    .to_str()
1950                    .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
1951                CString::new(path_str).map_err(LlamaModelLoadError::from)
1952            })
1953            .collect::<Result<Vec<_>, _>>()?;
1954
1955        // Create array of pointers to C strings
1956        let c_ptrs: Vec<*const c_char> = c_strings.iter().map(|s| s.as_ptr()).collect();
1957
1958        // Load the model from splits
1959        let llama_model = unsafe {
1960            llama_model_load_from_splits(c_ptrs.as_ptr().cast_mut(), c_ptrs.len(), params.params)
1961        };
1962
1963        let model = NonNull::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
1964
1965        tracing::debug!("Loaded model from {} splits", paths.len());
1966        Ok(LlamaModel { model })
1967    }
1968
1969    /// Load a model from a `FILE` pointer.
1970    ///
1971    /// # Safety
1972    ///
1973    /// The `file` pointer must be a valid, open `FILE*`.
1974    ///
1975    /// # Errors
1976    ///
1977    /// Returns an error if the model cannot be loaded.
1978    pub unsafe fn load_from_file_ptr(
1979        file: *mut llama_cpp_sys_4::FILE,
1980        params: &LlamaModelParams,
1981    ) -> Result<Self, LlamaModelLoadError> {
1982        let model = llama_cpp_sys_4::llama_model_load_from_file_ptr(file, params.params);
1983        let model = NonNull::new(model).ok_or(LlamaModelLoadError::NullResult)?;
1984        Ok(LlamaModel { model })
1985    }
1986
1987    /// Initialize a model from user-provided data.
1988    ///
1989    /// # Safety
1990    ///
1991    /// The metadata, callback, and user data must be valid.
1992    ///
1993    /// # Errors
1994    ///
1995    /// Returns an error if the model cannot be initialized.
1996    pub unsafe fn init_from_user(
1997        metadata: *mut llama_cpp_sys_4::gguf_context,
1998        set_tensor_data: llama_cpp_sys_4::llama_model_set_tensor_data_t,
1999        set_tensor_data_ud: *mut std::ffi::c_void,
2000        params: &LlamaModelParams,
2001    ) -> Result<Self, LlamaModelLoadError> {
2002        let model = llama_cpp_sys_4::llama_model_init_from_user(
2003            metadata,
2004            set_tensor_data,
2005            set_tensor_data_ud,
2006            params.params,
2007        );
2008        let model = NonNull::new(model).ok_or(LlamaModelLoadError::NullResult)?;
2009        Ok(LlamaModel { model })
2010    }
2011
2012    /// Save the model to a file.
2013    ///
2014    /// # Panics
2015    ///
2016    /// Panics if the path contains null bytes.
2017    pub fn save_to_file(&self, path: impl AsRef<Path>) {
2018        let path = path.as_ref();
2019        let path_str = path.to_str().expect("path is not valid UTF-8");
2020        let c_path = CString::new(path_str).expect("path contains null bytes");
2021        unsafe {
2022            llama_model_save_to_file(self.model.as_ptr(), c_path.as_ptr());
2023        }
2024    }
2025
2026    /// Get the list of built-in chat templates.
2027    ///
2028    /// Returns the names of all chat templates that are built into llama.cpp.
2029    ///
2030    /// # Panics
2031    ///
2032    /// Panics if any template name is not valid UTF-8.
2033    #[allow(clippy::cast_sign_loss)]
2034    #[must_use]
2035    pub fn chat_builtin_templates() -> Vec<String> {
2036        // First call to get count
2037        let count = unsafe { llama_chat_builtin_templates(std::ptr::null_mut(), 0) };
2038        if count <= 0 {
2039            return Vec::new();
2040        }
2041        let count = count as usize;
2042        let mut ptrs: Vec<*const c_char> = vec![std::ptr::null(); count];
2043        unsafe {
2044            llama_chat_builtin_templates(ptrs.as_mut_ptr(), count);
2045        }
2046        ptrs.iter()
2047            .map(|&p| {
2048                let cstr = unsafe { CStr::from_ptr(p) };
2049                cstr.to_str()
2050                    .expect("template name is not valid UTF-8")
2051                    .to_owned()
2052            })
2053            .collect()
2054    }
2055
2056    /// Initializes a lora adapter from a file.
2057    ///
2058    /// This function initializes a Lora adapter, which is a model extension used to adapt or fine-tune the existing model
2059    /// to a specific domain or task. The adapter file is typically in the form of a binary or serialized file that can be applied
2060    /// to the model for improved performance on specialized tasks.
2061    ///
2062    /// # Errors
2063    ///
2064    /// - If the adapter file path cannot be converted to a string or if the adapter cannot be initialized, it will return an error.
2065    ///
2066    /// # Example
2067    ///
2068    /// ```no_run
2069    /// use llama_cpp_4::model::LlamaModel;
2070    /// use llama_cpp_4::model::params::LlamaModelParams;
2071    /// use llama_cpp_4::llama_backend::LlamaBackend;
2072    ///
2073    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2074    /// let backend = LlamaBackend::init()?;
2075    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
2076    /// let adapter = model.lora_adapter_init("path/to/lora/adapter")?;
2077    /// # Ok(())
2078    /// # }
2079    /// ```
2080    pub fn lora_adapter_init(
2081        &self,
2082        path: impl AsRef<Path>,
2083    ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
2084        let path = path.as_ref();
2085        debug_assert!(
2086            Path::new(path).exists(),
2087            "{} does not exist",
2088            path.display()
2089        );
2090
2091        let path = path
2092            .to_str()
2093            .ok_or(LlamaLoraAdapterInitError::PathToStrError(
2094                path.to_path_buf(),
2095            ))?;
2096
2097        let cstr = CString::new(path)?;
2098        let adapter = unsafe { llama_adapter_lora_init(self.model.as_ptr(), cstr.as_ptr()) };
2099
2100        let adapter = NonNull::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
2101
2102        tracing::debug!(?path, "Initialized lora adapter");
2103        Ok(LlamaLoraAdapter {
2104            lora_adapter: adapter,
2105        })
2106    }
2107
2108    /// Create a new context from this model.
2109    ///
2110    /// This function creates a new context for the model, which is used to manage and perform computations for inference,
2111    /// including token generation, embeddings, and other tasks that the model can perform. The context allows fine-grained
2112    /// control over model parameters for a specific task.
2113    ///
2114    /// # Errors
2115    ///
2116    /// - There are various potential failures such as invalid parameters or a failure to allocate the context. See [`LlamaContextLoadError`]
2117    ///   for more detailed error descriptions.
2118    ///
2119    /// # Example
2120    ///
2121    /// ```no_run
2122    /// use llama_cpp_4::model::LlamaModel;
2123    /// use llama_cpp_4::model::params::LlamaModelParams;
2124    /// use llama_cpp_4::context::params::LlamaContextParams;
2125    /// use llama_cpp_4::llama_backend::LlamaBackend;
2126    ///
2127    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2128    /// let backend = LlamaBackend::init()?;
2129    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
2130    /// let context = model.new_context(&backend, LlamaContextParams::default())?;
2131    /// # Ok(())
2132    /// # }
2133    /// ```
2134    #[allow(clippy::needless_pass_by_value)]
2135    pub fn new_context(
2136        &self,
2137        _: &LlamaBackend,
2138        mut params: LlamaContextParams,
2139    ) -> Result<LlamaContext<'_>, LlamaContextLoadError> {
2140        // Apply TurboQuant attn-rotation preference before the KV cache is
2141        // initialised inside llama_init_from_model.
2142        let prev_rot_var = std::env::var("LLAMA_ATTN_ROT_DISABLE").ok();
2143        if params.attn_rot_disabled {
2144            // SAFETY: we restore the value right after the call.
2145            #[allow(unused_unsafe)]
2146            unsafe {
2147                std::env::set_var("LLAMA_ATTN_ROT_DISABLE", "1");
2148            }
2149        } else if std::env::var("LLAMA_ATTN_ROT_DISABLE").is_ok() {
2150            // params say "enabled" – only clear if it was previously unset
2151            // (respect explicit user env var).
2152        }
2153
2154        let context_type = params.ctx_type();
2155        let context_params = params.context_params;
2156        let embeddings_enabled = params.embeddings();
2157        let context = unsafe { llama_init_from_model(self.model.as_ptr(), context_params) };
2158
2159        // Restore the env-var to its previous state.
2160        #[allow(unused_unsafe)]
2161        match prev_rot_var {
2162            Some(v) => unsafe { std::env::set_var("LLAMA_ATTN_ROT_DISABLE", v) },
2163            None if params.attn_rot_disabled => unsafe {
2164                std::env::remove_var("LLAMA_ATTN_ROT_DISABLE");
2165            },
2166            None => {}
2167        }
2168
2169        let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
2170        let tensor_transactions = params.tensor_transactions.take();
2171        Ok(LlamaContext::new(
2172            self,
2173            context,
2174            embeddings_enabled,
2175            context_type,
2176            tensor_transactions,
2177        ))
2178    }
2179
2180    /// Apply the model's chat template to a sequence of messages.
2181    ///
2182    /// This function applies the model's chat template to the provided chat messages, formatting them accordingly. The chat
2183    /// template determines the structure or style of conversation between the system and user, such as token formatting,
2184    /// role separation, and more. The template can be customized by providing an optional template string, or if `None`
2185    /// is provided, the default template used by `llama.cpp` will be applied.
2186    ///
2187    /// For more information on supported templates, visit:
2188    /// <https://github.com/ggerganov/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template>
2189    ///
2190    /// # Arguments
2191    ///
2192    /// - `tmpl`: An optional custom template string. If `None`, the default template will be used.
2193    /// - `chat`: A vector of `LlamaChatMessage` instances, which represent the conversation between the system and user.
2194    /// - `add_ass`: A boolean flag indicating whether additional system-specific instructions (like "assistant") should be included.
2195    ///
2196    /// # Errors
2197    ///
2198    /// There are several possible points of failure when applying the chat template:
2199    /// - Insufficient buffer size to hold the formatted chat (this will return `ApplyChatTemplateError::BuffSizeError`).
2200    /// - If the template or messages cannot be processed properly, various errors from `ApplyChatTemplateError` may occur.
2201    ///
2202    /// # Example
2203    ///
2204    /// ```no_run
2205    /// use llama_cpp_4::model::{LlamaModel, LlamaChatMessage};
2206    /// use llama_cpp_4::model::params::LlamaModelParams;
2207    /// use llama_cpp_4::llama_backend::LlamaBackend;
2208    ///
2209    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2210    /// let backend = LlamaBackend::init()?;
2211    /// let model = LlamaModel::load_from_file(&backend, "path/to/model", &LlamaModelParams::default())?;
2212    /// let chat = vec![
2213    ///     LlamaChatMessage::new("user".to_string(), "Hello!".to_string())?,
2214    ///     LlamaChatMessage::new("assistant".to_string(), "Hi! How can I assist you today?".to_string())?,
2215    /// ];
2216    /// let formatted_chat = model.apply_chat_template(None, &chat, true)?;
2217    /// # Ok(())
2218    /// # }
2219    /// ```
2220    ///
2221    /// # Notes
2222    ///
2223    /// The provided buffer is twice the length of the messages by default, which is recommended by the `llama.cpp` documentation.
2224    /// # Panics
2225    ///
2226    /// Panics if the buffer length exceeds `i32::MAX`.
2227    #[tracing::instrument(skip_all)]
2228    pub fn apply_chat_template(
2229        &self,
2230        tmpl: Option<&str>,
2231        chat: &[LlamaChatMessage],
2232        add_ass: bool,
2233    ) -> Result<String, ApplyChatTemplateError> {
2234        // Compute raw message byte total from the original LlamaChatMessage vec
2235        // *before* we shadow `chat` with the sys-type vec below.
2236        let message_length = chat.iter().fold(0usize, |acc, c| {
2237            acc + c.role.to_bytes().len() + c.content.to_bytes().len()
2238        });
2239
2240        // Build our llama_cpp_sys chat messages (raw pointers into CStrings).
2241        let chat_sys: Vec<llama_chat_message> = chat
2242            .iter()
2243            .map(|c| llama_chat_message {
2244                role: c.role.as_ptr(),
2245                content: c.content.as_ptr(),
2246            })
2247            .collect();
2248
2249        // Set the tmpl pointer.
2250        let tmpl_cstring = tmpl.map(CString::new).transpose()?;
2251        let tmpl_ptr = tmpl_cstring
2252            .as_ref()
2253            .map_or(std::ptr::null(), |s| s.as_ptr());
2254
2255        // `message_length * 4` is far too small for models whose built-in chat
2256        // template adds a long default system prompt (e.g. Qwen3.5 prepends
2257        // ~80+ chars of markup even for a one-word user message).  Start with
2258        // at least 4 KiB so short inputs like "hi" always have room.
2259        //
2260        // `llama_chat_apply_template` returns the number of bytes it *actually*
2261        // needed when the buffer was too small, so we retry exactly once with
2262        // that precise size rather than giving up immediately.
2263        let mut buf_size = message_length.saturating_mul(4).max(4096);
2264
2265        for _ in 0..2 {
2266            // Use u8 so that as_mut_ptr()/as_ptr() match the binding (*mut u8 / *const u8).
2267            let mut buff = vec![0u8; buf_size];
2268            let res = unsafe {
2269                llama_chat_apply_template(
2270                    tmpl_ptr,
2271                    chat_sys.as_ptr(),
2272                    chat_sys.len(),
2273                    add_ass,
2274                    buff.as_mut_ptr().cast(),
2275                    i32::try_from(buff.len()).expect("buffer length fits in i32"),
2276                )
2277            };
2278
2279            if res < 0 {
2280                return Err(ApplyChatTemplateError::BuffSizeError);
2281            }
2282
2283            #[allow(clippy::cast_sign_loss)]
2284            let needed = res as usize;
2285            if needed > buf_size {
2286                // Buffer was too small — retry with the exact size llama.cpp reported.
2287                buf_size = needed + 1; // +1 for null terminator
2288                continue;
2289            }
2290
2291            // SAFETY: llama_chat_apply_template wrote a NUL-terminated string
2292            // into `buff`; `needed` bytes were used.
2293            let formatted = unsafe {
2294                CStr::from_ptr(buff.as_ptr().cast())
2295                    .to_string_lossy()
2296                    .into_owned()
2297            };
2298            return Ok(formatted);
2299        }
2300
2301        Err(ApplyChatTemplateError::BuffSizeError)
2302    }
2303
2304    /// Build a split GGUF file path for a specific chunk.
2305    ///
2306    /// This utility function creates the standardized filename for a split model chunk
2307    /// following the pattern: `{prefix}-{split_no:05d}-of-{split_count:05d}.gguf`
2308    ///
2309    /// # Arguments
2310    ///
2311    /// * `path_prefix` - The base path and filename prefix
2312    /// * `split_no` - The split number (1-indexed)
2313    /// * `split_count` - The total number of splits
2314    ///
2315    /// # Returns
2316    ///
2317    /// Returns the formatted split path as a String
2318    ///
2319    /// # Example
2320    ///
2321    /// ```
2322    /// use llama_cpp_4::model::LlamaModel;
2323    ///
2324    /// let path = LlamaModel::split_path("/models/llama", 1, 4);
2325    /// assert_eq!(path, "/models/llama-00002-of-00004.gguf");
2326    /// ```
2327    ///
2328    /// # Panics
2329    ///
2330    /// Panics if the path prefix contains a null byte.
2331    #[must_use]
2332    pub fn split_path(path_prefix: &str, split_no: i32, split_count: i32) -> String {
2333        let mut buffer = vec![0u8; 1024];
2334        let len = unsafe {
2335            llama_split_path(
2336                buffer.as_mut_ptr().cast::<c_char>(),
2337                buffer.len(),
2338                CString::new(path_prefix).unwrap().as_ptr(),
2339                split_no,
2340                split_count,
2341            )
2342        };
2343
2344        let len = usize::try_from(len).expect("split_path length fits in usize");
2345        buffer.truncate(len);
2346        String::from_utf8(buffer).unwrap_or_default()
2347    }
2348
2349    /// Extract the path prefix from a split filename.
2350    ///
2351    /// This function extracts the base path prefix from a split model filename,
2352    /// but only if the `split_no` and `split_count` match the pattern in the filename.
2353    ///
2354    /// # Arguments
2355    ///
2356    /// * `split_path` - The full path to the split file
2357    /// * `split_no` - The expected split number
2358    /// * `split_count` - The expected total number of splits
2359    ///
2360    /// # Returns
2361    ///
2362    /// Returns the path prefix if the pattern matches, or None if it doesn't
2363    ///
2364    /// # Example
2365    ///
2366    /// ```
2367    /// use llama_cpp_4::model::LlamaModel;
2368    ///
2369    /// let prefix = LlamaModel::split_prefix("/models/llama-00002-of-00004.gguf", 1, 4);
2370    /// assert_eq!(prefix, Some("/models/llama".to_string()));
2371    /// ```
2372    ///
2373    /// # Panics
2374    ///
2375    /// Panics if the split path contains a null byte.
2376    #[must_use]
2377    pub fn split_prefix(split_path: &str, split_no: i32, split_count: i32) -> Option<String> {
2378        let mut buffer = vec![0u8; 1024];
2379        let len = unsafe {
2380            llama_split_prefix(
2381                buffer.as_mut_ptr().cast::<c_char>(),
2382                buffer.len(),
2383                CString::new(split_path).unwrap().as_ptr(),
2384                split_no,
2385                split_count,
2386            )
2387        };
2388
2389        if len > 0 {
2390            let len = usize::try_from(len).expect("split_prefix length fits in usize");
2391            buffer.truncate(len);
2392            String::from_utf8(buffer).ok()
2393        } else {
2394            None
2395        }
2396    }
2397}
2398
2399#[allow(clippy::cast_precision_loss)]
2400impl fmt::Display for LlamaModel {
2401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2402        let desc = self.desc(256).unwrap_or_else(|_| "unknown".to_string());
2403        write!(
2404            f,
2405            "{desc} | {layers}L {heads}H {embd}E | {params} params | {size:.1} MiB",
2406            layers = self.n_layer(),
2407            heads = self.n_head(),
2408            embd = self.n_embd(),
2409            params = self.n_params(),
2410            size = self.model_size() as f64 / (1024.0 * 1024.0),
2411        )
2412    }
2413}
2414
2415impl Drop for LlamaModel {
2416    fn drop(&mut self) {
2417        unsafe { llama_model_free(self.model.as_ptr()) }
2418    }
2419}
2420
2421/// Defines the possible types of vocabulary used by the model.
2422///
2423/// The model may use different types of vocabulary depending on the tokenization method chosen during training.
2424/// This enum represents these types, specifically `BPE` (Byte Pair Encoding) and `SPM` (`SentencePiece`).
2425///
2426/// # Variants
2427///
2428/// - `BPE`: Byte Pair Encoding, a common tokenization method used in NLP tasks.
2429/// - `SPM`: `SentencePiece`, another popular tokenization method for NLP models.
2430///
2431/// # Example
2432///
2433/// ```no_run
2434/// use llama_cpp_4::model::VocabType;
2435///
2436/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2437/// let vocab_type = VocabType::BPE;
2438/// match vocab_type {
2439///     VocabType::BPE => println!("The model uses Byte Pair Encoding (BPE)"),
2440///     VocabType::SPM => println!("The model uses SentencePiece (SPM)"),
2441/// }
2442/// # Ok(())
2443/// # }
2444/// ```
2445#[repr(u32)]
2446#[derive(Debug, Eq, Copy, Clone, PartialEq)]
2447pub enum VocabType {
2448    /// Byte Pair Encoding
2449    BPE = LLAMA_VOCAB_TYPE_BPE as _,
2450    /// Sentence Piece Tokenizer
2451    SPM = LLAMA_VOCAB_TYPE_SPM as _,
2452}
2453
2454/// Error that occurs when trying to convert a `llama_vocab_type` to a `VocabType`.
2455///
2456/// This error is raised when the integer value returned by the system does not correspond to a known vocabulary type.
2457///
2458/// # Variants
2459///
2460/// - `UnknownValue`: The error is raised when the value is not a valid `llama_vocab_type`. The invalid value is returned with the error.
2461///
2462/// # Example
2463///
2464/// ```no_run
2465/// use llama_cpp_4::model::LlamaTokenTypeFromIntError;
2466///
2467/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
2468/// let invalid_value = 999; // Not a valid vocabulary type
2469/// let error = LlamaTokenTypeFromIntError::UnknownValue(invalid_value);
2470/// println!("Error: {}", error);
2471/// # Ok(())
2472/// # }
2473/// ```
2474#[derive(thiserror::Error, Debug, Eq, PartialEq)]
2475pub enum LlamaTokenTypeFromIntError {
2476    /// The value is not a valid `llama_token_type`. Contains the int value that was invalid.
2477    #[error("Unknown Value {0}")]
2478    UnknownValue(llama_vocab_type),
2479}
2480
2481impl TryFrom<llama_vocab_type> for VocabType {
2482    type Error = LlamaTokenTypeFromIntError;
2483
2484    fn try_from(value: llama_vocab_type) -> Result<Self, Self::Error> {
2485        match value {
2486            LLAMA_VOCAB_TYPE_BPE => Ok(VocabType::BPE),
2487            LLAMA_VOCAB_TYPE_SPM => Ok(VocabType::SPM),
2488            unknown => Err(LlamaTokenTypeFromIntError::UnknownValue(unknown)),
2489        }
2490    }
2491}