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