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, ¶ms)?;
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}