1use std::ffi::{c_char, CStr, CString};
3use std::num::NonZeroU16;
4use std::os::raw::c_int;
5use std::path::Path;
6use std::ptr::{self, NonNull};
7use std::slice;
8use std::str::Utf8Error;
9
10use crate::context::params::LlamaContextParams;
11use crate::context::LlamaContext;
12use crate::llama_backend::LlamaBackend;
13use crate::model::params::LlamaModelParams;
14use crate::sampling::LlamaSampler;
15use crate::token::LlamaToken;
16use crate::token_type::{LlamaTokenAttr, LlamaTokenAttrs};
17use crate::{
18 ApplyChatTemplateError, ChatTemplateError, LlamaContextLoadError, LlamaLoraAdapterInitError,
19 LlamaModelLoadError, MetaValError, NewLlamaChatMessageError, StringToTokenError,
20 TokenToStringError,
21};
22
23pub mod params;
24
25#[derive(Debug)]
27#[repr(transparent)]
28#[allow(clippy::module_name_repetitions)]
29pub struct LlamaModel {
30 pub(crate) model: NonNull<llama_cpp_sys_2::llama_model>,
31}
32
33#[derive(Debug)]
35#[repr(transparent)]
36#[allow(clippy::module_name_repetitions)]
37pub struct LlamaLoraAdapter {
38 pub(crate) lora_adapter: NonNull<llama_cpp_sys_2::llama_adapter_lora>,
39}
40
41#[derive(Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
46pub struct LlamaChatTemplate(CString);
47
48impl LlamaChatTemplate {
49 pub fn new(template: &str) -> Result<Self, std::ffi::NulError> {
52 Ok(Self(CString::new(template)?))
53 }
54
55 pub fn as_c_str(&self) -> &CStr {
57 &self.0
58 }
59
60 pub fn to_str(&self) -> Result<&str, Utf8Error> {
62 self.0.to_str()
63 }
64
65 pub fn to_string(&self) -> Result<String, Utf8Error> {
67 self.to_str().map(str::to_string)
68 }
69}
70
71impl std::fmt::Debug for LlamaChatTemplate {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 self.0.fmt(f)
74 }
75}
76
77#[derive(Debug, Eq, PartialEq, Clone)]
79pub struct LlamaChatMessage {
80 role: CString,
81 content: CString,
82}
83
84impl LlamaChatMessage {
85 pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
90 Ok(Self {
91 role: CString::new(role)?,
92 content: CString::new(content)?,
93 })
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum RopeType {
100 Norm,
101 NeoX,
102 MRope,
103 Vision,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum AddBos {
109 Always,
111 Never,
113}
114
115#[deprecated(
117 since = "0.1.0",
118 note = "This enum is a mixture of options for llama cpp providing less flexibility it only used with deprecated methods and will be removed in the future."
119)]
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum Special {
122 Tokenize,
124 Plaintext,
126}
127
128unsafe impl Send for LlamaModel {}
129
130unsafe impl Sync for LlamaModel {}
131
132impl LlamaModel {
133 pub(crate) fn vocab_ptr(&self) -> *const llama_cpp_sys_2::llama_vocab {
134 unsafe { llama_cpp_sys_2::llama_model_get_vocab(self.model.as_ptr()) }
135 }
136
137 #[must_use]
144 pub fn n_ctx_train(&self) -> u32 {
145 let n_ctx_train = unsafe { llama_cpp_sys_2::llama_n_ctx_train(self.model.as_ptr()) };
146 u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
147 }
148
149 pub fn tokens(
151 &self,
152 decode_special: bool,
153 ) -> impl Iterator<Item = (LlamaToken, Result<String, TokenToStringError>)> + '_ {
154 (0..self.n_vocab())
155 .map(LlamaToken::new)
156 .map(move |llama_token| {
157 let mut decoder = encoding_rs::UTF_8.new_decoder();
158 (
159 llama_token,
160 self.token_to_piece(llama_token, &mut decoder, decode_special, None),
161 )
162 })
163 }
164
165 #[must_use]
167 pub fn token_bos(&self) -> LlamaToken {
168 let token = unsafe { llama_cpp_sys_2::llama_token_bos(self.vocab_ptr()) };
169 LlamaToken(token)
170 }
171
172 #[must_use]
174 pub fn token_eos(&self) -> LlamaToken {
175 let token = unsafe { llama_cpp_sys_2::llama_token_eos(self.vocab_ptr()) };
176 LlamaToken(token)
177 }
178
179 #[must_use]
181 pub fn token_nl(&self) -> LlamaToken {
182 let token = unsafe { llama_cpp_sys_2::llama_token_nl(self.vocab_ptr()) };
183 LlamaToken(token)
184 }
185
186 #[must_use]
188 pub fn is_eog_token(&self, token: LlamaToken) -> bool {
189 unsafe { llama_cpp_sys_2::llama_token_is_eog(self.vocab_ptr(), token.0) }
190 }
191
192 #[must_use]
194 pub fn decode_start_token(&self) -> LlamaToken {
195 let token =
196 unsafe { llama_cpp_sys_2::llama_model_decoder_start_token(self.model.as_ptr()) };
197 LlamaToken(token)
198 }
199
200 #[must_use]
202 pub fn token_sep(&self) -> LlamaToken {
203 let token = unsafe { llama_cpp_sys_2::llama_vocab_sep(self.vocab_ptr()) };
204 LlamaToken(token)
205 }
206
207 #[deprecated(since = "0.1.0", note = "Use `token_to_piece` instead")]
213 pub fn token_to_str(
214 &self,
215 token: LlamaToken,
216 special: Special,
217 ) -> Result<String, TokenToStringError> {
218 let mut decoder = encoding_rs::UTF_8.new_decoder();
220 self.token_to_piece(
221 token,
222 &mut decoder,
223 matches!(special, Special::Tokenize),
224 None,
225 )
226 }
227
228 #[deprecated(since = "0.1.0", note = "Use `token_to_piece_bytes` instead")]
238 pub fn token_to_bytes(
239 &self,
240 token: LlamaToken,
241 special: Special,
242 ) -> Result<Vec<u8>, TokenToStringError> {
243 match self.token_to_piece_bytes(token, 8, matches!(special, Special::Tokenize), None) {
245 Err(TokenToStringError::InsufficientBufferSpace(i)) => self.token_to_piece_bytes(
246 token,
247 (-i).try_into().expect("Error buffer size is positive"),
248 matches!(special, Special::Tokenize),
249 None,
250 ),
251 x => x,
252 }
253 }
254
255 #[deprecated(
261 since = "0.1.0",
262 note = "Use `token_to_piece` for each token individually instead"
263 )]
264 pub fn tokens_to_str(
265 &self,
266 tokens: &[LlamaToken],
267 special: Special,
268 ) -> Result<String, TokenToStringError> {
269 let mut builder: Vec<u8> = Vec::with_capacity(tokens.len() * 4);
270 for piece in tokens
271 .iter()
272 .copied()
273 .map(|t| self.token_to_piece_bytes(t, 8, matches!(special, Special::Tokenize), None))
274 {
275 builder.extend_from_slice(&piece?);
276 }
277 Ok(String::from_utf8(builder)?)
278 }
279
280 pub fn str_to_token(
303 &self,
304 str: &str,
305 add_bos: AddBos,
306 ) -> Result<Vec<LlamaToken>, StringToTokenError> {
307 let add_bos = match add_bos {
308 AddBos::Always => true,
309 AddBos::Never => false,
310 };
311
312 let tokens_estimation = std::cmp::max(8, (str.len() / 2) + usize::from(add_bos));
313 let mut buffer: Vec<LlamaToken> = Vec::with_capacity(tokens_estimation);
314
315 let c_string = CString::new(str)?;
316 let buffer_capacity =
317 c_int::try_from(buffer.capacity()).expect("buffer capacity should fit into a c_int");
318
319 let size = unsafe {
320 llama_cpp_sys_2::llama_tokenize(
321 self.vocab_ptr(),
322 c_string.as_ptr(),
323 c_int::try_from(c_string.as_bytes().len())?,
324 buffer.as_mut_ptr().cast::<llama_cpp_sys_2::llama_token>(),
325 buffer_capacity,
326 add_bos,
327 true,
328 )
329 };
330
331 let size = if size.is_negative() {
334 buffer.reserve_exact(usize::try_from(-size).expect("usize's are larger "));
335 unsafe {
336 llama_cpp_sys_2::llama_tokenize(
337 self.vocab_ptr(),
338 c_string.as_ptr(),
339 c_int::try_from(c_string.as_bytes().len())?,
340 buffer.as_mut_ptr().cast::<llama_cpp_sys_2::llama_token>(),
341 -size,
342 add_bos,
343 true,
344 )
345 }
346 } else {
347 size
348 };
349
350 let size = usize::try_from(size).expect("size is positive and usize ");
351
352 unsafe { buffer.set_len(size) }
354 Ok(buffer)
355 }
356
357 #[must_use]
363 pub fn token_attr(&self, LlamaToken(id): LlamaToken) -> LlamaTokenAttrs {
364 let token_type = unsafe { llama_cpp_sys_2::llama_token_get_attr(self.vocab_ptr(), id) };
365 LlamaTokenAttrs::try_from(token_type).expect("token type is valid")
366 }
367
368 pub fn token_to_piece(
386 &self,
387 token: LlamaToken,
388 decoder: &mut encoding_rs::Decoder,
389 special: bool,
390 lstrip: Option<NonZeroU16>,
391 ) -> Result<String, TokenToStringError> {
392 let bytes = match self.token_to_piece_bytes(token, 8, special, lstrip) {
393 Err(TokenToStringError::InsufficientBufferSpace(i)) => self.token_to_piece_bytes(
396 token,
397 (-i).try_into().expect("Error buffer size is positive"),
398 special,
399 lstrip,
400 ),
401 x => x,
402 }?;
403 let mut output_piece = String::with_capacity(bytes.len());
405 let (_result, _somesize, _truthy) =
408 decoder.decode_to_string(&bytes, &mut output_piece, false);
409 Ok(output_piece)
410 }
411
412 pub fn token_to_piece_bytes(
428 &self,
429 token: LlamaToken,
430 buffer_size: usize,
431 special: bool,
432 lstrip: Option<NonZeroU16>,
433 ) -> Result<Vec<u8>, TokenToStringError> {
434 let string = CString::new(vec![b'*'; buffer_size]).expect("no null");
435 let len = string.as_bytes().len();
436 let len = c_int::try_from(len).expect("length fits into c_int");
437 let buf = string.into_raw();
438 let lstrip = lstrip.map_or(0, |it| i32::from(it.get()));
439 let size = unsafe {
440 llama_cpp_sys_2::llama_token_to_piece(
441 self.vocab_ptr(),
442 token.0,
443 buf,
444 len,
445 lstrip,
446 special,
447 )
448 };
449
450 match size {
451 0 => Err(TokenToStringError::UnknownTokenType),
452 i if i.is_negative() => Err(TokenToStringError::InsufficientBufferSpace(i)),
453 size => {
454 let string = unsafe { CString::from_raw(buf) };
455 let mut bytes = string.into_bytes();
456 let len = usize::try_from(size).expect("size is positive and fits into usize");
457 bytes.truncate(len);
458 Ok(bytes)
459 }
460 }
461 }
462
463 #[deprecated(since = "0.1.0", note = "Use `token_to_piece` instead")]
479 pub fn token_to_str_with_size(
480 &self,
481 token: LlamaToken,
482 buffer_size: usize,
483 special: Special,
484 ) -> Result<String, TokenToStringError> {
485 let bytes = self.token_to_piece_bytes(
486 token,
487 buffer_size,
488 matches!(special, Special::Tokenize),
489 None,
490 )?;
491 Ok(String::from_utf8(bytes)?)
492 }
493
494 #[deprecated(since = "0.1.0", note = "Use `token_to_piece_bytes` instead")]
509 pub fn token_to_bytes_with_size(
510 &self,
511 token: LlamaToken,
512 buffer_size: usize,
513 special: Special,
514 lstrip: Option<NonZeroU16>,
515 ) -> Result<Vec<u8>, TokenToStringError> {
516 if token == self.token_nl() {
517 return Ok(b"\n".to_vec());
518 }
519
520 let attrs = self.token_attr(token);
522 if attrs.is_empty()
523 || attrs
524 .intersects(LlamaTokenAttr::Unknown | LlamaTokenAttr::Byte | LlamaTokenAttr::Unused)
525 || attrs.contains(LlamaTokenAttr::Control)
526 && (token == self.token_bos() || token == self.token_eos())
527 {
528 return Ok(Vec::new());
529 }
530
531 let special = match special {
532 Special::Tokenize => true,
533 Special::Plaintext => false,
534 };
535
536 let string = CString::new(vec![b'*'; buffer_size]).expect("no null");
537 let len = string.as_bytes().len();
538 let len = c_int::try_from(len).expect("length fits into c_int");
539 let buf = string.into_raw();
540 let lstrip = lstrip.map_or(0, |it| i32::from(it.get()));
541 let size = unsafe {
542 llama_cpp_sys_2::llama_token_to_piece(
543 self.vocab_ptr(),
544 token.0,
545 buf,
546 len,
547 lstrip,
548 special,
549 )
550 };
551
552 match size {
553 0 => Err(TokenToStringError::UnknownTokenType),
554 i if i.is_negative() => Err(TokenToStringError::InsufficientBufferSpace(i)),
555 size => {
556 let string = unsafe { CString::from_raw(buf) };
557 let mut bytes = string.into_bytes();
558 let len = usize::try_from(size).expect("size is positive and fits into usize");
559 bytes.truncate(len);
560 Ok(bytes)
561 }
562 }
563 }
564 #[must_use]
569 pub fn n_vocab(&self) -> i32 {
570 unsafe { llama_cpp_sys_2::llama_n_vocab(self.vocab_ptr()) }
571 }
572
573 #[must_use]
579 pub fn vocab_type(&self) -> VocabType {
580 let vocab_type = unsafe { llama_cpp_sys_2::llama_vocab_type(self.vocab_ptr()) };
582 VocabType::try_from(vocab_type).expect("invalid vocab type")
583 }
584
585 #[must_use]
588 pub fn n_embd(&self) -> c_int {
589 unsafe { llama_cpp_sys_2::llama_n_embd(self.model.as_ptr()) }
590 }
591
592 pub fn size(&self) -> u64 {
594 unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
595 }
596
597 pub fn n_params(&self) -> u64 {
599 unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
600 }
601
602 pub fn is_recurrent(&self) -> bool {
604 unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
605 }
606
607 pub fn is_hybrid(&self) -> bool {
612 unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
613 }
614
615 pub fn n_layer(&self) -> u32 {
617 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
620 }
621
622 pub fn n_head(&self) -> u32 {
624 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
627 }
628
629 pub fn n_head_kv(&self) -> u32 {
631 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
634 .unwrap()
635 }
636
637 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
639 let key_cstring = CString::new(key)?;
640 let key_ptr = key_cstring.as_ptr();
641
642 extract_meta_string(
643 |buf_ptr, buf_len| unsafe {
644 llama_cpp_sys_2::llama_model_meta_val_str(
645 self.model.as_ptr(),
646 key_ptr,
647 buf_ptr,
648 buf_len,
649 )
650 },
651 256,
652 )
653 }
654
655 pub fn meta_count(&self) -> i32 {
657 unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
658 }
659
660 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
662 extract_meta_string(
663 |buf_ptr, buf_len| unsafe {
664 llama_cpp_sys_2::llama_model_meta_key_by_index(
665 self.model.as_ptr(),
666 index,
667 buf_ptr,
668 buf_len,
669 )
670 },
671 256,
672 )
673 }
674
675 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
677 extract_meta_string(
678 |buf_ptr, buf_len| unsafe {
679 llama_cpp_sys_2::llama_model_meta_val_str_by_index(
680 self.model.as_ptr(),
681 index,
682 buf_ptr,
683 buf_len,
684 )
685 },
686 256,
687 )
688 }
689
690 pub fn rope_type(&self) -> Option<RopeType> {
692 match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
693 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
694 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
695 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
696 llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
697 llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
698 rope_type => {
699 tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
700 None
701 }
702 }
703 }
704
705 pub fn chat_template(
719 &self,
720 name: Option<&str>,
721 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
722 let name_cstr = name.map(CString::new);
723 let name_ptr = match name_cstr {
724 Some(Ok(name)) => name.as_ptr(),
725 _ => std::ptr::null(),
726 };
727 let result =
728 unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
729
730 if result.is_null() {
732 Err(ChatTemplateError::MissingTemplate)
733 } else {
734 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
735 let chat_template = CString::new(chat_template_cstr.to_bytes())?;
736 Ok(LlamaChatTemplate(chat_template))
737 }
738 }
739
740 #[tracing::instrument(skip_all, fields(params))]
746 pub fn load_from_file(
747 _: &LlamaBackend,
748 path: impl AsRef<Path>,
749 params: &LlamaModelParams,
750 ) -> Result<Self, LlamaModelLoadError> {
751 let path = path.as_ref();
752 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
753 let path = path
754 .to_str()
755 .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
756
757 let cstr = CString::new(path)?;
758 let llama_model =
759 unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
760
761 let model = NonNull::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
762
763 tracing::debug!(?path, "Loaded model");
764 Ok(LlamaModel { model })
765 }
766
767 pub fn lora_adapter_init(
773 &self,
774 path: impl AsRef<Path>,
775 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
776 let path = path.as_ref();
777 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
778
779 let path = path
780 .to_str()
781 .ok_or(LlamaLoraAdapterInitError::PathToStrError(
782 path.to_path_buf(),
783 ))?;
784
785 let cstr = CString::new(path)?;
786 let adapter =
787 unsafe { llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_ptr(), cstr.as_ptr()) };
788
789 let adapter = NonNull::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
790
791 tracing::debug!(?path, "Initialized lora adapter");
792 Ok(LlamaLoraAdapter {
793 lora_adapter: adapter,
794 })
795 }
796
797 #[allow(clippy::needless_pass_by_value)]
804 pub fn new_context<'a>(
805 &'a self,
806 _: &LlamaBackend,
807 params: LlamaContextParams,
808 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
809 let context_params = params.context_params;
810 let context = unsafe {
811 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
812 };
813 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
814
815 Ok(LlamaContext::new(self, context, params.embeddings()))
816 }
817
818 #[allow(clippy::needless_pass_by_value)]
828 pub fn new_context_with_ctx_other<'a>(
829 &'a self,
830 _: &LlamaBackend,
831 params: LlamaContextParams,
832 ctx_other: &LlamaContext<'_>,
833 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
834 let mut context_params = params.context_params;
835 context_params.ctx_other = ctx_other.context.as_ptr();
836 let context = unsafe {
837 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
838 };
839 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
840
841 Ok(LlamaContext::new(self, context, params.embeddings()))
842 }
843
844 #[allow(clippy::needless_pass_by_value)]
871 pub fn new_context_with_samplers<'a>(
872 &'a self,
873 _: &LlamaBackend,
874 params: LlamaContextParams,
875 samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
876 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
877 let samplers: Vec<_> = samplers.into_iter().collect();
878 let mut context_params = params.context_params;
879
880 let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
881 .iter()
882 .map(|(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
883 seq_id: *seq_id,
884 sampler: sampler.sampler,
885 })
886 .collect();
887
888 if !sampler_configs.is_empty() {
889 context_params.samplers = sampler_configs.as_mut_ptr();
890 context_params.n_samplers = sampler_configs.len();
891 }
892
893 let context = unsafe {
894 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
895 };
896 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
897
898 Ok(LlamaContext::with_samplers(self, context, params.embeddings(), samplers))
899 }
900
901 #[tracing::instrument(skip_all)]
919 pub fn apply_chat_template(
920 &self,
921 tmpl: &LlamaChatTemplate,
922 chat: &[LlamaChatMessage],
923 add_ass: bool,
924 ) -> Result<String, ApplyChatTemplateError> {
925 let message_length = chat.iter().fold(0, |acc, c| {
927 acc + c.role.to_bytes().len() + c.content.to_bytes().len()
928 });
929 let mut buff: Vec<u8> = vec![0; message_length * 2];
930
931 let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
933 .iter()
934 .map(|c| llama_cpp_sys_2::llama_chat_message {
935 role: c.role.as_ptr(),
936 content: c.content.as_ptr(),
937 })
938 .collect();
939
940 let tmpl_ptr = tmpl.0.as_ptr();
941
942 let res = unsafe {
943 llama_cpp_sys_2::llama_chat_apply_template(
944 tmpl_ptr,
945 chat.as_ptr(),
946 chat.len(),
947 add_ass,
948 buff.as_mut_ptr().cast::<c_char>(),
949 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
950 )
951 };
952
953 if res < 0 {
954 return Err(ApplyChatTemplateError::FfiError(res));
955 }
956
957 if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
958 buff.resize(res.try_into().expect("res is negative"), 0);
959
960 let res = unsafe {
961 llama_cpp_sys_2::llama_chat_apply_template(
962 tmpl_ptr,
963 chat.as_ptr(),
964 chat.len(),
965 add_ass,
966 buff.as_mut_ptr().cast::<c_char>(),
967 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
968 )
969 };
970 if res < 0 {
971 return Err(ApplyChatTemplateError::FfiError(res));
972 }
973 assert_eq!(Ok(res), buff.len().try_into());
974 }
975 buff.truncate(res.try_into().expect("res is negative"));
976 Ok(String::from_utf8(buff)?)
977 }
978}
979
980fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
986where
987 F: Fn(*mut c_char, usize) -> i32,
988{
989 let mut buffer = vec![0u8; capacity];
990
991 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
993 if result < 0 {
994 return Err(MetaValError::NegativeReturn(result));
995 }
996
997 let returned_len = result as usize;
999 if returned_len >= capacity {
1000 return extract_meta_string(c_function, returned_len + 1);
1002 }
1003
1004 debug_assert_eq!(
1006 buffer.get(returned_len),
1007 Some(&0),
1008 "should end with null byte"
1009 );
1010
1011 buffer.truncate(returned_len);
1013 Ok(String::from_utf8(buffer)?)
1014}
1015
1016impl Drop for LlamaModel {
1017 fn drop(&mut self) {
1018 unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_ptr()) }
1019 }
1020}
1021
1022#[repr(u32)]
1024#[derive(Debug, Eq, Copy, Clone, PartialEq)]
1025pub enum VocabType {
1026 BPE = llama_cpp_sys_2::LLAMA_VOCAB_TYPE_BPE as _,
1028 SPM = llama_cpp_sys_2::LLAMA_VOCAB_TYPE_SPM as _,
1030}
1031
1032#[derive(thiserror::Error, Debug, Eq, PartialEq)]
1034pub enum LlamaTokenTypeFromIntError {
1035 #[error("Unknown Value {0}")]
1037 UnknownValue(llama_cpp_sys_2::llama_vocab_type),
1038}
1039
1040impl TryFrom<llama_cpp_sys_2::llama_vocab_type> for VocabType {
1041 type Error = LlamaTokenTypeFromIntError;
1042
1043 fn try_from(value: llama_cpp_sys_2::llama_vocab_type) -> Result<Self, Self::Error> {
1044 match value {
1045 llama_cpp_sys_2::LLAMA_VOCAB_TYPE_BPE => Ok(VocabType::BPE),
1046 llama_cpp_sys_2::LLAMA_VOCAB_TYPE_SPM => Ok(VocabType::SPM),
1047 unknown => Err(LlamaTokenTypeFromIntError::UnknownValue(unknown)),
1048 }
1049 }
1050}