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