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 #[must_use]
597 pub fn n_embd_out(&self) -> c_int {
598 unsafe { llama_cpp_sys_2::llama_model_n_embd_out(self.model.as_ptr()) }
599 }
600
601 #[must_use]
604 pub fn n_cls_out(&self) -> u32 {
605 unsafe { llama_cpp_sys_2::llama_model_n_cls_out(self.model.as_ptr()) }
606 }
607
608 pub fn size(&self) -> u64 {
610 unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
611 }
612
613 pub fn n_params(&self) -> u64 {
615 unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
616 }
617
618 pub fn is_recurrent(&self) -> bool {
620 unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
621 }
622
623 pub fn is_hybrid(&self) -> bool {
628 unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
629 }
630
631 pub fn n_layer(&self) -> u32 {
633 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
636 }
637
638 pub fn n_head(&self) -> u32 {
640 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
643 }
644
645 pub fn n_head_kv(&self) -> u32 {
647 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
650 .unwrap()
651 }
652
653 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
655 let key_cstring = CString::new(key)?;
656 let key_ptr = key_cstring.as_ptr();
657
658 extract_meta_string(
659 |buf_ptr, buf_len| unsafe {
660 llama_cpp_sys_2::llama_model_meta_val_str(
661 self.model.as_ptr(),
662 key_ptr,
663 buf_ptr,
664 buf_len,
665 )
666 },
667 256,
668 )
669 }
670
671 pub fn meta_count(&self) -> i32 {
673 unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
674 }
675
676 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
678 extract_meta_string(
679 |buf_ptr, buf_len| unsafe {
680 llama_cpp_sys_2::llama_model_meta_key_by_index(
681 self.model.as_ptr(),
682 index,
683 buf_ptr,
684 buf_len,
685 )
686 },
687 256,
688 )
689 }
690
691 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
693 extract_meta_string(
694 |buf_ptr, buf_len| unsafe {
695 llama_cpp_sys_2::llama_model_meta_val_str_by_index(
696 self.model.as_ptr(),
697 index,
698 buf_ptr,
699 buf_len,
700 )
701 },
702 256,
703 )
704 }
705
706 pub fn rope_type(&self) -> Option<RopeType> {
708 match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
709 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
710 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
711 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
712 llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
713 llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
714 rope_type => {
715 tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
716 None
717 }
718 }
719 }
720
721 pub fn chat_template(
735 &self,
736 name: Option<&str>,
737 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
738 let name_cstr = name.map(CString::new);
739 let name_ptr = match name_cstr {
740 Some(Ok(name)) => name.as_ptr(),
741 _ => std::ptr::null(),
742 };
743 let result =
744 unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
745
746 if result.is_null() {
748 Err(ChatTemplateError::MissingTemplate)
749 } else {
750 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
751 let chat_template = CString::new(chat_template_cstr.to_bytes())?;
752 Ok(LlamaChatTemplate(chat_template))
753 }
754 }
755
756 #[tracing::instrument(skip_all, fields(params))]
762 pub fn load_from_file(
763 _: &LlamaBackend,
764 path: impl AsRef<Path>,
765 params: &LlamaModelParams,
766 ) -> Result<Self, LlamaModelLoadError> {
767 let path = path.as_ref();
768 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
769 let path = path
770 .to_str()
771 .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
772
773 let cstr = CString::new(path)?;
774 let llama_model =
775 unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
776
777 let model = NonNull::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
778
779 tracing::debug!(?path, "Loaded model");
780 Ok(LlamaModel { model })
781 }
782
783 pub fn lora_adapter_init(
789 &self,
790 path: impl AsRef<Path>,
791 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
792 let path = path.as_ref();
793 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
794
795 let path = path
796 .to_str()
797 .ok_or(LlamaLoraAdapterInitError::PathToStrError(
798 path.to_path_buf(),
799 ))?;
800
801 let cstr = CString::new(path)?;
802 let adapter =
803 unsafe { llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_ptr(), cstr.as_ptr()) };
804
805 let adapter = NonNull::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
806
807 tracing::debug!(?path, "Initialized lora adapter");
808 Ok(LlamaLoraAdapter {
809 lora_adapter: adapter,
810 })
811 }
812
813 #[allow(clippy::needless_pass_by_value)]
820 pub fn new_context<'a>(
821 &'a self,
822 _: &LlamaBackend,
823 params: LlamaContextParams,
824 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
825 let context_params = params.context_params;
826 let context = unsafe {
827 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
828 };
829 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
830
831 Ok(LlamaContext::new(self, context, params.embeddings()))
832 }
833
834 #[allow(clippy::needless_pass_by_value)]
844 pub fn new_context_with_ctx_other<'a>(
845 &'a self,
846 _: &LlamaBackend,
847 params: LlamaContextParams,
848 ctx_other: &LlamaContext<'_>,
849 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
850 let mut context_params = params.context_params;
851 context_params.ctx_other = ctx_other.context.as_ptr();
852 let context = unsafe {
853 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
854 };
855 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
856
857 Ok(LlamaContext::new(self, context, params.embeddings()))
858 }
859
860 #[allow(clippy::needless_pass_by_value)]
887 pub fn new_context_with_samplers<'a>(
888 &'a self,
889 _: &LlamaBackend,
890 params: LlamaContextParams,
891 samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
892 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
893 let samplers: Vec<_> = samplers.into_iter().collect();
894 let mut context_params = params.context_params;
895
896 let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
897 .iter()
898 .map(|(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
899 seq_id: *seq_id,
900 sampler: sampler.sampler,
901 })
902 .collect();
903
904 if !sampler_configs.is_empty() {
905 context_params.samplers = sampler_configs.as_mut_ptr();
906 context_params.n_samplers = sampler_configs.len();
907 }
908
909 let context = unsafe {
910 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
911 };
912 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
913
914 Ok(LlamaContext::with_samplers(self, context, params.embeddings(), samplers))
915 }
916
917 #[tracing::instrument(skip_all)]
935 pub fn apply_chat_template(
936 &self,
937 tmpl: &LlamaChatTemplate,
938 chat: &[LlamaChatMessage],
939 add_ass: bool,
940 ) -> Result<String, ApplyChatTemplateError> {
941 let message_length = chat.iter().fold(0, |acc, c| {
943 acc + c.role.to_bytes().len() + c.content.to_bytes().len()
944 });
945 let mut buff: Vec<u8> = vec![0; message_length * 2];
946
947 let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
949 .iter()
950 .map(|c| llama_cpp_sys_2::llama_chat_message {
951 role: c.role.as_ptr(),
952 content: c.content.as_ptr(),
953 })
954 .collect();
955
956 let tmpl_ptr = tmpl.0.as_ptr();
957
958 let res = unsafe {
959 llama_cpp_sys_2::llama_chat_apply_template(
960 tmpl_ptr,
961 chat.as_ptr(),
962 chat.len(),
963 add_ass,
964 buff.as_mut_ptr().cast::<c_char>(),
965 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
966 )
967 };
968
969 if res < 0 {
970 return Err(ApplyChatTemplateError::FfiError(res));
971 }
972
973 if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
974 buff.resize(res.try_into().expect("res is negative"), 0);
975
976 let res = unsafe {
977 llama_cpp_sys_2::llama_chat_apply_template(
978 tmpl_ptr,
979 chat.as_ptr(),
980 chat.len(),
981 add_ass,
982 buff.as_mut_ptr().cast::<c_char>(),
983 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
984 )
985 };
986 if res < 0 {
987 return Err(ApplyChatTemplateError::FfiError(res));
988 }
989 assert_eq!(Ok(res), buff.len().try_into());
990 }
991 buff.truncate(res.try_into().expect("res is negative"));
992 Ok(String::from_utf8(buff)?)
993 }
994}
995
996fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
1002where
1003 F: Fn(*mut c_char, usize) -> i32,
1004{
1005 let mut buffer = vec![0u8; capacity];
1006
1007 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
1009 if result < 0 {
1010 return Err(MetaValError::NegativeReturn(result));
1011 }
1012
1013 let returned_len = result as usize;
1015 if returned_len >= capacity {
1016 return extract_meta_string(c_function, returned_len + 1);
1018 }
1019
1020 debug_assert_eq!(
1022 buffer.get(returned_len),
1023 Some(&0),
1024 "should end with null byte"
1025 );
1026
1027 buffer.truncate(returned_len);
1029 Ok(String::from_utf8(buffer)?)
1030}
1031
1032impl Drop for LlamaModel {
1033 fn drop(&mut self) {
1034 unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_ptr()) }
1035 }
1036}
1037
1038#[repr(u32)]
1040#[derive(Debug, Eq, Copy, Clone, PartialEq)]
1041pub enum VocabType {
1042 BPE = llama_cpp_sys_2::LLAMA_VOCAB_TYPE_BPE as _,
1044 SPM = llama_cpp_sys_2::LLAMA_VOCAB_TYPE_SPM as _,
1046}
1047
1048#[derive(thiserror::Error, Debug, Eq, PartialEq)]
1050pub enum LlamaTokenTypeFromIntError {
1051 #[error("Unknown Value {0}")]
1053 UnknownValue(llama_cpp_sys_2::llama_vocab_type),
1054}
1055
1056impl TryFrom<llama_cpp_sys_2::llama_vocab_type> for VocabType {
1057 type Error = LlamaTokenTypeFromIntError;
1058
1059 fn try_from(value: llama_cpp_sys_2::llama_vocab_type) -> Result<Self, Self::Error> {
1060 match value {
1061 llama_cpp_sys_2::LLAMA_VOCAB_TYPE_BPE => Ok(VocabType::BPE),
1062 llama_cpp_sys_2::LLAMA_VOCAB_TYPE_SPM => Ok(VocabType::SPM),
1063 unknown => Err(LlamaTokenTypeFromIntError::UnknownValue(unknown)),
1064 }
1065 }
1066}