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