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,
112 NeoX,
113 MRope,
114 Vision,
115}
116
117impl LlamaModel {
118 #[must_use]
120 pub fn vocab(&self) -> LlamaVocab<'_> {
121 let ptr = unsafe { llama_cpp_sys_2::llama_model_get_vocab(self.model.as_ptr()) };
122 LlamaVocab::new(ptr).expect("model must have vocabulary")
123 }
124
125 #[must_use]
132 pub fn n_ctx_train(&self) -> u32 {
133 let n_ctx_train = unsafe { llama_cpp_sys_2::llama_n_ctx_train(self.model.as_ptr()) };
134 u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
135 }
136
137 pub fn tokens(&self, decode_special: bool) -> impl Iterator<Item = (LlamaToken, Vec<u8>)> + '_ {
139 let vocab = self.vocab();
140 vocab.tokens().map(move |llama_token| {
141 let bytes = vocab.token_to_piece(llama_token, decode_special, None);
142 (llama_token, bytes)
143 })
144 }
145
146 #[must_use]
148 pub fn decode_start_token(&self) -> LlamaToken {
149 let token =
150 unsafe { llama_cpp_sys_2::llama_model_decoder_start_token(self.model.as_ptr()) };
151 LlamaToken(token)
152 }
153
154 #[must_use]
159 pub fn n_vocab(&self) -> i32 {
160 self.vocab().n_tokens()
161 }
162
163 #[must_use]
166 pub fn n_embd(&self) -> c_int {
167 unsafe { llama_cpp_sys_2::llama_n_embd(self.model.as_ptr()) }
168 }
169
170 #[must_use]
175 pub fn n_embd_out(&self) -> c_int {
176 unsafe { llama_cpp_sys_2::llama_model_n_embd_out(self.model.as_ptr()) }
177 }
178
179 #[must_use]
182 pub fn n_cls_out(&self) -> u32 {
183 unsafe { llama_cpp_sys_2::llama_model_n_cls_out(self.model.as_ptr()) }
184 }
185
186 pub fn size(&self) -> u64 {
188 unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
189 }
190
191 pub fn n_params(&self) -> u64 {
193 unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
194 }
195
196 pub fn is_recurrent(&self) -> bool {
198 unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
199 }
200
201 pub fn is_hybrid(&self) -> bool {
206 unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
207 }
208
209 pub fn n_layer(&self) -> u32 {
211 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
214 }
215
216 pub fn n_head(&self) -> u32 {
218 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
221 }
222
223 pub fn n_head_kv(&self) -> u32 {
225 u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
228 .unwrap()
229 }
230
231 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
233 let key_cstring = CString::new(key)?;
234 let key_ptr = key_cstring.as_ptr();
235
236 extract_meta_string(
237 |buf_ptr, buf_len| unsafe {
238 llama_cpp_sys_2::llama_model_meta_val_str(
239 self.model.as_ptr(),
240 key_ptr,
241 buf_ptr,
242 buf_len,
243 )
244 },
245 256,
246 )
247 }
248
249 pub fn meta_count(&self) -> i32 {
251 unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
252 }
253
254 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
256 extract_meta_string(
257 |buf_ptr, buf_len| unsafe {
258 llama_cpp_sys_2::llama_model_meta_key_by_index(
259 self.model.as_ptr(),
260 index,
261 buf_ptr,
262 buf_len,
263 )
264 },
265 256,
266 )
267 }
268
269 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
271 extract_meta_string(
272 |buf_ptr, buf_len| unsafe {
273 llama_cpp_sys_2::llama_model_meta_val_str_by_index(
274 self.model.as_ptr(),
275 index,
276 buf_ptr,
277 buf_len,
278 )
279 },
280 256,
281 )
282 }
283
284 pub fn rope_type(&self) -> Option<RopeType> {
286 match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
287 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
288 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
289 llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
290 llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
291 llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
292 rope_type => {
293 tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
294 None
295 }
296 }
297 }
298
299 pub fn chat_template(
313 &self,
314 name: Option<&str>,
315 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
316 let name_cstr = name.map(CString::new);
317 let name_ptr = match name_cstr {
318 Some(Ok(name)) => name.as_ptr(),
319 _ => std::ptr::null(),
320 };
321 let result =
322 unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
323
324 if result.is_null() {
326 Err(ChatTemplateError::MissingTemplate)
327 } else {
328 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
329 let chat_template = CString::new(chat_template_cstr.to_bytes())?;
330 Ok(LlamaChatTemplate(chat_template))
331 }
332 }
333
334 #[tracing::instrument(skip_all, fields(params))]
340 pub fn load_from_file(
341 _: &LlamaBackend,
342 path: impl AsRef<Path>,
343 params: &LlamaModelParams,
344 ) -> Result<Self, LlamaModelLoadError> {
345 let path = path.as_ref();
346 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
347 let path = path
348 .to_str()
349 .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
350
351 let cstr = CString::new(path)?;
352 let llama_model =
353 unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
354
355 let model = Ptr::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
356
357 tracing::debug!(?path, "Loaded model");
358 Ok(LlamaModel { model })
359 }
360
361 pub fn lora_adapter_init(
367 &mut self,
368 path: impl AsRef<Path>,
369 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
370 let path = path.as_ref();
371 debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
372
373 let path = path
374 .to_str()
375 .ok_or(LlamaLoraAdapterInitError::PathToStrError(
376 path.to_path_buf(),
377 ))?;
378
379 let cstr = CString::new(path)?;
380 let adapter = unsafe {
381 llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_mut_ptr(), cstr.as_ptr())
382 };
383
384 let adapter = Ptr::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
385
386 tracing::debug!(?path, "Initialized lora adapter");
387 Ok(LlamaLoraAdapter {
388 lora_adapter: adapter,
389 })
390 }
391
392 #[allow(clippy::needless_pass_by_value)]
399 pub fn new_context<'a>(
400 &'a self,
401 _: &LlamaBackend,
402 params: LlamaContextParams,
403 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
404 let context_params = params.context_params;
405 let context = unsafe {
411 llama_cpp_sys_2::llama_new_context_with_model(
412 self.model.as_mut_ptr_unsound(),
413 context_params,
414 )
415 };
416 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
417
418 Ok(LlamaContext::new(context))
419 }
420
421 #[allow(clippy::needless_pass_by_value)]
431 pub fn new_context_with_ctx_other<'a>(
432 &'a self,
433 _: &LlamaBackend,
434 params: LlamaContextParams,
435 ctx_other: &LlamaContext<'_>,
436 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
437 let mut context_params = params.context_params;
438 context_params.ctx_other = unsafe { ctx_other.context.as_mut_ptr_unsound() };
441 let context = unsafe {
443 llama_cpp_sys_2::llama_new_context_with_model(
444 self.model.as_mut_ptr_unsound(),
445 context_params,
446 )
447 };
448 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
449
450 Ok(LlamaContext::new(context))
451 }
452
453 #[allow(clippy::needless_pass_by_value)]
480 pub fn new_context_with_samplers<'a>(
481 &'a self,
482 _: &LlamaBackend,
483 params: LlamaContextParams,
484 samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
485 ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
486 let mut samplers: Vec<_> = samplers.into_iter().collect();
487 let mut context_params = params.context_params;
488
489 let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
490 .iter_mut()
491 .map(
492 |(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
493 seq_id: *seq_id,
494 sampler: sampler.sampler.as_mut_ptr(),
495 },
496 )
497 .collect();
498
499 if !sampler_configs.is_empty() {
500 context_params.samplers = sampler_configs.as_mut_ptr();
501 context_params.n_samplers = sampler_configs.len();
502 }
503
504 let context = unsafe {
506 llama_cpp_sys_2::llama_new_context_with_model(
507 self.model.as_mut_ptr_unsound(),
508 context_params,
509 )
510 };
511 let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
512
513 Ok(LlamaContext::with_samplers(context, samplers))
514 }
515
516 #[tracing::instrument(skip_all)]
534 pub fn apply_chat_template(
535 &self,
536 tmpl: &LlamaChatTemplate,
537 chat: &[LlamaChatMessage],
538 add_ass: bool,
539 ) -> Result<String, ApplyChatTemplateError> {
540 let message_length = chat.iter().fold(0, |acc, c| {
542 acc + c.role.to_bytes().len() + c.content.to_bytes().len()
543 });
544 let mut buff: Vec<u8> = vec![0; message_length * 2];
545
546 let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
548 .iter()
549 .map(|c| llama_cpp_sys_2::llama_chat_message {
550 role: c.role.as_ptr(),
551 content: c.content.as_ptr(),
552 })
553 .collect();
554
555 let tmpl_ptr = tmpl.0.as_ptr();
556
557 let res = unsafe {
558 llama_cpp_sys_2::llama_chat_apply_template(
559 tmpl_ptr,
560 chat.as_ptr(),
561 chat.len(),
562 add_ass,
563 buff.as_mut_ptr().cast::<c_char>(),
564 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
565 )
566 };
567
568 if res < 0 {
569 return Err(ApplyChatTemplateError::FfiError(res));
570 }
571
572 if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
573 buff.resize(res.try_into().expect("res is negative"), 0);
574
575 let res = unsafe {
576 llama_cpp_sys_2::llama_chat_apply_template(
577 tmpl_ptr,
578 chat.as_ptr(),
579 chat.len(),
580 add_ass,
581 buff.as_mut_ptr().cast::<c_char>(),
582 buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
583 )
584 };
585 if res < 0 {
586 return Err(ApplyChatTemplateError::FfiError(res));
587 }
588 assert_eq!(Ok(res), buff.len().try_into());
589 }
590 buff.truncate(res.try_into().expect("res is negative"));
591 Ok(String::from_utf8(buff)?)
592 }
593}
594
595fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
601where
602 F: Fn(*mut c_char, usize) -> i32,
603{
604 let mut buffer = vec![0u8; capacity];
605
606 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
608 if result < 0 {
609 return Err(MetaValError::NegativeReturn(result));
610 }
611
612 let returned_len = result as usize;
614 if returned_len >= capacity {
615 return extract_meta_string(c_function, returned_len + 1);
617 }
618
619 debug_assert_eq!(
621 buffer.get(returned_len),
622 Some(&0),
623 "should end with null byte"
624 );
625
626 buffer.truncate(returned_len);
628 Ok(String::from_utf8(buffer)?)
629}
630
631impl Drop for LlamaModel {
632 fn drop(&mut self) {
633 unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_mut_ptr()) }
634 }
635}