1use std::ffi::{c_char, CStr, CString};
3use std::os::raw::c_int;
4use std::path::Path;
5use std::ptr::NonNull;
6use std::str::Utf8Error;
7
8use crate::context::params::LlamaContextParams;
9use crate::context::LlamaContext;
10use crate::llama_backend::LlamaBackend;
11use crate::model::params::LlamaModelParams;
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: NonNull<llama_cpp_sys_2::llama_model>,
30}
31
32#[derive(Debug)]
34#[repr(transparent)]
35#[allow(clippy::module_name_repetitions)]
36pub struct LlamaLoraAdapter {
37 pub(crate) lora_adapter: NonNull<llama_cpp_sys_2::llama_adapter_lora>,
38}
39
40#[derive(Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
45pub struct LlamaChatTemplate(CString);
46
47impl LlamaChatTemplate {
48 pub fn new(template: &str) -> Result<Self, std::ffi::NulError> {
51 Ok(Self(CString::new(template)?))
52 }
53
54 pub fn as_c_str(&self) -> &CStr {
56 &self.0
57 }
58
59 pub fn to_str(&self) -> Result<&str, Utf8Error> {
61 self.0.to_str()
62 }
63
64 pub fn to_string(&self) -> Result<String, Utf8Error> {
66 self.to_str().map(str::to_string)
67 }
68}
69
70impl std::fmt::Debug for LlamaChatTemplate {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 self.0.fmt(f)
73 }
74}
75
76#[derive(Debug, Eq, PartialEq, Clone)]
78pub struct LlamaChatMessage {
79 role: CString,
80 content: CString,
81}
82
83impl LlamaChatMessage {
84 pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
89 Ok(Self {
90 role: CString::new(role)?,
91 content: CString::new(content)?,
92 })
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum RopeType {
99 Norm,
100 NeoX,
101 MRope,
102 Vision,
103}
104
105unsafe impl Send for LlamaModel {}
106
107unsafe impl Sync for LlamaModel {}
108
109#[allow(deprecated)]
110impl LlamaModel {
111 #[must_use]
113 pub fn vocab(&self) -> LlamaVocab<'_> {
114 let ptr = unsafe { llama_cpp_sys_2::llama_model_get_vocab(self.model.as_ptr()) };
115 LlamaVocab::new(ptr).expect("model must have vocabulary")
116 }
117
118 #[must_use]
125 pub fn n_ctx_train(&self) -> u32 {
126 let n_ctx_train = unsafe { llama_cpp_sys_2::llama_n_ctx_train(self.model.as_ptr()) };
127 u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
128 }
129
130 pub fn tokens(&self, decode_special: bool) -> impl Iterator<Item = (LlamaToken, Vec<u8>)> + '_ {
132 let vocab = self.vocab();
133 vocab.tokens().map(move |llama_token| {
134 let bytes = vocab.token_to_piece(llama_token, decode_special, None);
135 (llama_token, bytes)
136 })
137 }
138
139 #[must_use]
141 pub fn decode_start_token(&self) -> LlamaToken {
142 let token =
143 unsafe { llama_cpp_sys_2::llama_model_decoder_start_token(self.model.as_ptr()) };
144 LlamaToken(token)
145 }
146
147 #[must_use]
152 pub fn n_vocab(&self) -> i32 {
153 self.vocab().n_tokens()
154 }
155
156 #[must_use]
159 pub fn n_embd(&self) -> c_int {
160 unsafe { llama_cpp_sys_2::llama_n_embd(self.model.as_ptr()) }
161 }
162
163 #[must_use]
168 pub fn n_embd_out(&self) -> c_int {
169 unsafe { llama_cpp_sys_2::llama_model_n_embd_out(self.model.as_ptr()) }
170 }
171
172 #[must_use]
175 pub fn n_cls_out(&self) -> u32 {
176 unsafe { llama_cpp_sys_2::llama_model_n_cls_out(self.model.as_ptr()) }
177 }
178
179 pub fn size(&self) -> u64 {
181 unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
182 }
183
184 pub fn n_params(&self) -> u64 {
186 unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
187 }
188
189 pub fn is_recurrent(&self) -> bool {
191 unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
192 }
193
194 pub fn is_hybrid(&self) -> bool {
199 unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
200 }
201
202 pub fn n_layer(&self) -> u32 {
204 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
207 }
208
209 pub fn n_head(&self) -> u32 {
211 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
214 }
215
216 pub fn n_head_kv(&self) -> u32 {
218 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
221 .unwrap()
222 }
223
224 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
226 let key_cstring = CString::new(key)?;
227 let key_ptr = key_cstring.as_ptr();
228
229 extract_meta_string(
230 |buf_ptr, buf_len| unsafe {
231 llama_cpp_sys_2::llama_model_meta_val_str(
232 self.model.as_ptr(),
233 key_ptr,
234 buf_ptr,
235 buf_len,
236 )
237 },
238 256,
239 )
240 }
241
242 pub fn meta_count(&self) -> i32 {
244 unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
245 }
246
247 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
249 extract_meta_string(
250 |buf_ptr, buf_len| unsafe {
251 llama_cpp_sys_2::llama_model_meta_key_by_index(
252 self.model.as_ptr(),
253 index,
254 buf_ptr,
255 buf_len,
256 )
257 },
258 256,
259 )
260 }
261
262 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
264 extract_meta_string(
265 |buf_ptr, buf_len| unsafe {
266 llama_cpp_sys_2::llama_model_meta_val_str_by_index(
267 self.model.as_ptr(),
268 index,
269 buf_ptr,
270 buf_len,
271 )
272 },
273 256,
274 )
275 }
276
277 pub fn rope_type(&self) -> Option<RopeType> {
279 match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
280 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
281 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
282 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
283 llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
284 llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
285 rope_type => {
286 tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
287 None
288 }
289 }
290 }
291
292 pub fn chat_template(
306 &self,
307 name: Option<&str>,
308 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
309 let name_cstr = name.map(CString::new);
310 let name_ptr = match name_cstr {
311 Some(Ok(name)) => name.as_ptr(),
312 _ => std::ptr::null(),
313 };
314 let result =
315 unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
316
317 if result.is_null() {
319 Err(ChatTemplateError::MissingTemplate)
320 } else {
321 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
322 let chat_template = CString::new(chat_template_cstr.to_bytes())?;
323 Ok(LlamaChatTemplate(chat_template))
324 }
325 }
326
327 #[tracing::instrument(skip_all, fields(params))]
333 pub fn load_from_file(
334 _: &LlamaBackend,
335 path: impl AsRef<Path>,
336 params: &LlamaModelParams,
337 ) -> Result<Self, LlamaModelLoadError> {
338 let path = path.as_ref();
339 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
340 let path = path
341 .to_str()
342 .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
343
344 let cstr = CString::new(path)?;
345 let llama_model =
346 unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
347
348 let model = NonNull::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
349
350 tracing::debug!(?path, "Loaded model");
351 Ok(LlamaModel { model })
352 }
353
354 pub fn lora_adapter_init(
360 &self,
361 path: impl AsRef<Path>,
362 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
363 let path = path.as_ref();
364 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
365
366 let path = path
367 .to_str()
368 .ok_or(LlamaLoraAdapterInitError::PathToStrError(
369 path.to_path_buf(),
370 ))?;
371
372 let cstr = CString::new(path)?;
373 let adapter =
374 unsafe { llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_ptr(), cstr.as_ptr()) };
375
376 let adapter = NonNull::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
377
378 tracing::debug!(?path, "Initialized lora adapter");
379 Ok(LlamaLoraAdapter {
380 lora_adapter: adapter,
381 })
382 }
383
384 #[allow(clippy::needless_pass_by_value)]
391 pub fn new_context<'a>(
392 &'a self,
393 _: &LlamaBackend,
394 params: LlamaContextParams,
395 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
396 let context_params = params.context_params;
397 let context = unsafe {
398 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
399 };
400 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
401
402 Ok(LlamaContext::new(self, context, params.embeddings()))
403 }
404
405 #[allow(clippy::needless_pass_by_value)]
415 pub fn new_context_with_ctx_other<'a>(
416 &'a self,
417 _: &LlamaBackend,
418 params: LlamaContextParams,
419 ctx_other: &LlamaContext<'_>,
420 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
421 let mut context_params = params.context_params;
422 context_params.ctx_other = ctx_other.context.as_ptr();
423 let context = unsafe {
424 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
425 };
426 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
427
428 Ok(LlamaContext::new(self, context, params.embeddings()))
429 }
430
431 #[allow(clippy::needless_pass_by_value)]
458 pub fn new_context_with_samplers<'a>(
459 &'a self,
460 _: &LlamaBackend,
461 params: LlamaContextParams,
462 samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
463 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
464 let samplers: Vec<_> = samplers.into_iter().collect();
465 let mut context_params = params.context_params;
466
467 let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
468 .iter()
469 .map(
470 |(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
471 seq_id: *seq_id,
472 sampler: sampler.sampler,
473 },
474 )
475 .collect();
476
477 if !sampler_configs.is_empty() {
478 context_params.samplers = sampler_configs.as_mut_ptr();
479 context_params.n_samplers = sampler_configs.len();
480 }
481
482 let context = unsafe {
483 llama_cpp_sys_2::llama_new_context_with_model(self.model.as_ptr(), context_params)
484 };
485 let context = NonNull::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
486
487 Ok(LlamaContext::with_samplers(
488 self,
489 context,
490 params.embeddings(),
491 samplers,
492 ))
493 }
494
495 #[tracing::instrument(skip_all)]
513 pub fn apply_chat_template(
514 &self,
515 tmpl: &LlamaChatTemplate,
516 chat: &[LlamaChatMessage],
517 add_ass: bool,
518 ) -> Result<String, ApplyChatTemplateError> {
519 let message_length = chat.iter().fold(0, |acc, c| {
521 acc + c.role.to_bytes().len() + c.content.to_bytes().len()
522 });
523 let mut buff: Vec<u8> = vec![0; message_length * 2];
524
525 let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
527 .iter()
528 .map(|c| llama_cpp_sys_2::llama_chat_message {
529 role: c.role.as_ptr(),
530 content: c.content.as_ptr(),
531 })
532 .collect();
533
534 let tmpl_ptr = tmpl.0.as_ptr();
535
536 let res = unsafe {
537 llama_cpp_sys_2::llama_chat_apply_template(
538 tmpl_ptr,
539 chat.as_ptr(),
540 chat.len(),
541 add_ass,
542 buff.as_mut_ptr().cast::<c_char>(),
543 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
544 )
545 };
546
547 if res < 0 {
548 return Err(ApplyChatTemplateError::FfiError(res));
549 }
550
551 if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
552 buff.resize(res.try_into().expect("res is negative"), 0);
553
554 let res = unsafe {
555 llama_cpp_sys_2::llama_chat_apply_template(
556 tmpl_ptr,
557 chat.as_ptr(),
558 chat.len(),
559 add_ass,
560 buff.as_mut_ptr().cast::<c_char>(),
561 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
562 )
563 };
564 if res < 0 {
565 return Err(ApplyChatTemplateError::FfiError(res));
566 }
567 assert_eq!(Ok(res), buff.len().try_into());
568 }
569 buff.truncate(res.try_into().expect("res is negative"));
570 Ok(String::from_utf8(buff)?)
571 }
572}
573
574fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
580where
581 F: Fn(*mut c_char, usize) -> i32,
582{
583 let mut buffer = vec![0u8; capacity];
584
585 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
587 if result < 0 {
588 return Err(MetaValError::NegativeReturn(result));
589 }
590
591 let returned_len = result as usize;
593 if returned_len >= capacity {
594 return extract_meta_string(c_function, returned_len + 1);
596 }
597
598 debug_assert_eq!(
600 buffer.get(returned_len),
601 Some(&0),
602 "should end with null byte"
603 );
604
605 buffer.truncate(returned_len);
607 Ok(String::from_utf8(buffer)?)
608}
609
610impl Drop for LlamaModel {
611 fn drop(&mut self) {
612 unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_ptr()) }
613 }
614}