1use std::ffi::{c_char, CStr, CString};
3use std::os::raw::c_int;
4use std::path::Path;
5use std::str::Utf8Error;
6
7use crate::context::params::LlamaContextParams;
8use crate::context::LlamaContext;
9use crate::llama_backend::LlamaBackend;
10use crate::model::params::LlamaModelParams;
11use crate::ptr::Ptr;
12use crate::sampling::LlamaSampler;
13use crate::token::LlamaToken;
14use crate::vocab::LlamaVocab;
15use crate::{
16 ApplyChatTemplateError, ChatTemplateError, LlamaContextLoadError, LlamaLoraAdapterInitError,
17 LlamaModelLoadError, MetaValError, NewLlamaChatMessageError,
18};
19
20pub mod params;
21pub use crate::vocab::{LlamaTokenTypeFromIntError, VocabType};
23
24#[derive(Debug)]
26#[repr(transparent)]
27#[allow(clippy::module_name_repetitions)]
28pub struct LlamaModel {
29 pub(crate) model: Ptr<llama_cpp_sys_2::llama_model>,
30}
31
32unsafe impl Send for LlamaModel {}
36unsafe impl Sync for LlamaModel {}
38
39#[derive(Debug)]
41#[repr(transparent)]
42#[allow(clippy::module_name_repetitions)]
43pub struct LlamaLoraAdapter {
44 pub(crate) lora_adapter: Ptr<llama_cpp_sys_2::llama_adapter_lora>,
45}
46
47unsafe impl Send for LlamaLoraAdapter {}
49unsafe impl Sync for LlamaLoraAdapter {}
51
52#[derive(Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
57pub struct LlamaChatTemplate(CString);
58
59impl LlamaChatTemplate {
60 pub fn new(template: &str) -> Result<Self, std::ffi::NulError> {
63 Ok(Self(CString::new(template)?))
64 }
65
66 pub fn as_c_str(&self) -> &CStr {
68 &self.0
69 }
70
71 pub fn to_str(&self) -> Result<&str, Utf8Error> {
73 self.0.to_str()
74 }
75
76 pub fn to_string(&self) -> Result<String, Utf8Error> {
78 self.to_str().map(str::to_string)
79 }
80}
81
82impl std::fmt::Debug for LlamaChatTemplate {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 self.0.fmt(f)
85 }
86}
87
88#[derive(Debug, Eq, PartialEq, Clone)]
90pub struct LlamaChatMessage {
91 role: CString,
92 content: CString,
93}
94
95impl LlamaChatMessage {
96 pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
101 Ok(Self {
102 role: CString::new(role)?,
103 content: CString::new(content)?,
104 })
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum RopeType {
111 Norm,
113 NeoX,
115 MRope,
117 IMRope,
119 Vision,
121}
122
123impl LlamaModel {
124 #[must_use]
126 pub fn vocab(&self) -> LlamaVocab<'_> {
127 let ptr = unsafe { llama_cpp_sys_2::llama_model_get_vocab(self.model.as_ptr()) };
128 LlamaVocab::new(ptr).expect("model must have vocabulary")
129 }
130
131 #[must_use]
138 pub fn n_ctx_train(&self) -> u32 {
139 let n_ctx_train = unsafe { llama_cpp_sys_2::llama_n_ctx_train(self.model.as_ptr()) };
140 u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
141 }
142
143 pub fn tokens(&self, decode_special: bool) -> impl Iterator<Item = (LlamaToken, Vec<u8>)> + '_ {
145 let vocab = self.vocab();
146 vocab.tokens().map(move |llama_token| {
147 let bytes = vocab.token_to_piece(llama_token, decode_special, None);
148 (llama_token, bytes)
149 })
150 }
151
152 #[must_use]
154 pub fn decode_start_token(&self) -> LlamaToken {
155 let token =
156 unsafe { llama_cpp_sys_2::llama_model_decoder_start_token(self.model.as_ptr()) };
157 LlamaToken(token)
158 }
159
160 #[must_use]
165 pub fn n_vocab(&self) -> i32 {
166 self.vocab().n_tokens()
167 }
168
169 #[must_use]
172 pub fn n_embd(&self) -> c_int {
173 unsafe { llama_cpp_sys_2::llama_n_embd(self.model.as_ptr()) }
174 }
175
176 #[must_use]
181 pub fn n_embd_out(&self) -> c_int {
182 unsafe { llama_cpp_sys_2::llama_model_n_embd_out(self.model.as_ptr()) }
183 }
184
185 #[must_use]
188 pub fn n_cls_out(&self) -> u32 {
189 unsafe { llama_cpp_sys_2::llama_model_n_cls_out(self.model.as_ptr()) }
190 }
191
192 pub fn size(&self) -> u64 {
194 unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
195 }
196
197 pub fn n_params(&self) -> u64 {
199 unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
200 }
201
202 pub fn is_recurrent(&self) -> bool {
204 unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
205 }
206
207 pub fn is_hybrid(&self) -> bool {
212 unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
213 }
214
215 pub fn n_layer(&self) -> u32 {
217 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
220 }
221
222 pub fn n_head(&self) -> u32 {
224 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
227 }
228
229 pub fn n_head_kv(&self) -> u32 {
231 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
234 .unwrap()
235 }
236
237 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
239 let key_cstring = CString::new(key)?;
240 let key_ptr = key_cstring.as_ptr();
241
242 extract_meta_string(
243 |buf_ptr, buf_len| unsafe {
244 llama_cpp_sys_2::llama_model_meta_val_str(
245 self.model.as_ptr(),
246 key_ptr,
247 buf_ptr,
248 buf_len,
249 )
250 },
251 256,
252 )
253 }
254
255 pub fn meta_count(&self) -> i32 {
257 unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
258 }
259
260 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
262 extract_meta_string(
263 |buf_ptr, buf_len| unsafe {
264 llama_cpp_sys_2::llama_model_meta_key_by_index(
265 self.model.as_ptr(),
266 index,
267 buf_ptr,
268 buf_len,
269 )
270 },
271 256,
272 )
273 }
274
275 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
277 extract_meta_string(
278 |buf_ptr, buf_len| unsafe {
279 llama_cpp_sys_2::llama_model_meta_val_str_by_index(
280 self.model.as_ptr(),
281 index,
282 buf_ptr,
283 buf_len,
284 )
285 },
286 256,
287 )
288 }
289
290 pub fn rope_type(&self) -> Option<RopeType> {
292 match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
293 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
294 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
295 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
296 llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
297 llama_cpp_sys_2::LLAMA_ROPE_TYPE_IMROPE => Some(RopeType::IMRope),
298 llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
299 rope_type => {
300 tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
301 None
302 }
303 }
304 }
305
306 pub fn chat_template(
320 &self,
321 name: Option<&str>,
322 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
323 let name_cstr = name.map(CString::new);
324 let name_ptr = match name_cstr {
325 Some(Ok(name)) => name.as_ptr(),
326 _ => std::ptr::null(),
327 };
328 let result =
329 unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
330
331 if result.is_null() {
333 Err(ChatTemplateError::MissingTemplate)
334 } else {
335 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
336 let chat_template = CString::new(chat_template_cstr.to_bytes())?;
337 Ok(LlamaChatTemplate(chat_template))
338 }
339 }
340
341 #[tracing::instrument(skip_all, fields(params))]
347 pub fn load_from_file(
348 _: &LlamaBackend,
349 path: impl AsRef<Path>,
350 params: &LlamaModelParams,
351 ) -> Result<Self, LlamaModelLoadError> {
352 let path = path.as_ref();
353 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
354 let path = path
355 .to_str()
356 .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
357
358 let cstr = CString::new(path)?;
359 let llama_model =
360 unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
361
362 let model = Ptr::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
363
364 tracing::debug!(?path, "Loaded model");
365 Ok(LlamaModel { model })
366 }
367
368 pub fn lora_adapter_init(
374 &mut self,
375 path: impl AsRef<Path>,
376 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
377 let path = path.as_ref();
378 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
379
380 let path = path
381 .to_str()
382 .ok_or(LlamaLoraAdapterInitError::PathToStrError(
383 path.to_path_buf(),
384 ))?;
385
386 let cstr = CString::new(path)?;
387 let adapter = unsafe {
388 llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_mut_ptr(), cstr.as_ptr())
389 };
390
391 let adapter = Ptr::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
392
393 tracing::debug!(?path, "Initialized lora adapter");
394 Ok(LlamaLoraAdapter {
395 lora_adapter: adapter,
396 })
397 }
398
399 #[allow(clippy::needless_pass_by_value)]
406 pub fn new_context<'a>(
407 &'a self,
408 _: &LlamaBackend,
409 params: LlamaContextParams,
410 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
411 let context_params = params.context_params;
412 let context = unsafe {
418 llama_cpp_sys_2::llama_new_context_with_model(
419 self.model.as_mut_ptr_unsound(),
420 context_params,
421 )
422 };
423 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
424
425 Ok(LlamaContext::new(context))
426 }
427
428 #[allow(clippy::needless_pass_by_value)]
438 pub fn new_context_with_ctx_other<'a>(
439 &'a self,
440 _: &LlamaBackend,
441 params: LlamaContextParams,
442 ctx_other: &LlamaContext<'_>,
443 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
444 let mut context_params = params.context_params;
445 context_params.ctx_other = unsafe { ctx_other.context.as_mut_ptr_unsound() };
448 let context = unsafe {
450 llama_cpp_sys_2::llama_new_context_with_model(
451 self.model.as_mut_ptr_unsound(),
452 context_params,
453 )
454 };
455 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
456
457 Ok(LlamaContext::new(context))
458 }
459
460 #[allow(clippy::needless_pass_by_value)]
487 pub fn new_context_with_samplers<'a>(
488 &'a self,
489 _: &LlamaBackend,
490 params: LlamaContextParams,
491 samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
492 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
493 let mut samplers: Vec<_> = samplers.into_iter().collect();
494 let mut context_params = params.context_params;
495
496 let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
497 .iter_mut()
498 .map(
499 |(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
500 seq_id: *seq_id,
501 sampler: sampler.sampler.as_mut_ptr(),
502 },
503 )
504 .collect();
505
506 if !sampler_configs.is_empty() {
507 context_params.samplers = sampler_configs.as_mut_ptr();
508 context_params.n_samplers = sampler_configs.len();
509 }
510
511 let context = unsafe {
513 llama_cpp_sys_2::llama_new_context_with_model(
514 self.model.as_mut_ptr_unsound(),
515 context_params,
516 )
517 };
518 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
519
520 Ok(LlamaContext::with_samplers(context, samplers))
521 }
522
523 #[tracing::instrument(skip_all)]
541 pub fn apply_chat_template(
542 &self,
543 tmpl: &LlamaChatTemplate,
544 chat: &[LlamaChatMessage],
545 add_ass: bool,
546 ) -> Result<String, ApplyChatTemplateError> {
547 let message_length = chat.iter().fold(0, |acc, c| {
549 acc + c.role.to_bytes().len() + c.content.to_bytes().len()
550 });
551 let mut buff: Vec<u8> = vec![0; message_length * 2];
552
553 let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
555 .iter()
556 .map(|c| llama_cpp_sys_2::llama_chat_message {
557 role: c.role.as_ptr(),
558 content: c.content.as_ptr(),
559 })
560 .collect();
561
562 let tmpl_ptr = tmpl.0.as_ptr();
563
564 let res = unsafe {
565 llama_cpp_sys_2::llama_chat_apply_template(
566 tmpl_ptr,
567 chat.as_ptr(),
568 chat.len(),
569 add_ass,
570 buff.as_mut_ptr().cast::<c_char>(),
571 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
572 )
573 };
574
575 if res < 0 {
576 return Err(ApplyChatTemplateError::FfiError(res));
577 }
578
579 if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
580 buff.resize(res.try_into().expect("res is negative"), 0);
581
582 let res = unsafe {
583 llama_cpp_sys_2::llama_chat_apply_template(
584 tmpl_ptr,
585 chat.as_ptr(),
586 chat.len(),
587 add_ass,
588 buff.as_mut_ptr().cast::<c_char>(),
589 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
590 )
591 };
592 if res < 0 {
593 return Err(ApplyChatTemplateError::FfiError(res));
594 }
595 assert_eq!(Ok(res), buff.len().try_into());
596 }
597 buff.truncate(res.try_into().expect("res is negative"));
598 Ok(String::from_utf8(buff)?)
599 }
600}
601
602fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
608where
609 F: Fn(*mut c_char, usize) -> i32,
610{
611 let mut buffer = vec![0u8; capacity];
612
613 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
615 if result < 0 {
616 return Err(MetaValError::NegativeReturn(result));
617 }
618
619 let returned_len = result as usize;
621 if returned_len >= capacity {
622 return extract_meta_string(c_function, returned_len + 1);
624 }
625
626 debug_assert_eq!(
628 buffer.get(returned_len),
629 Some(&0),
630 "should end with null byte"
631 );
632
633 buffer.truncate(returned_len);
635 Ok(String::from_utf8(buffer)?)
636}
637
638impl Drop for LlamaModel {
639 fn drop(&mut self) {
640 unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_mut_ptr()) }
641 }
642}