1use std::ffi::{c_char, CStr, CString, NulError};
18use std::fmt::Debug;
19use std::num::NonZeroI32;
20use std::sync::OnceLock;
21
22use crate::llama_batch::BatchAddError;
23use std::os::raw::c_int;
24use std::path::PathBuf;
25use std::string::FromUtf8Error;
26
27pub mod context;
28pub mod gguf;
29pub mod llama_backend;
30pub mod llama_batch;
31#[cfg(feature = "llguidance")]
32pub(crate) mod llguidance_sampler;
33mod log;
34pub mod model;
35#[cfg(feature = "mtmd")]
36pub mod mtmd;
37pub mod sampling;
38#[cfg(feature = "common")]
39pub mod speculative;
40pub mod timing;
41pub mod token;
42pub mod token_type;
43
44pub use crate::context::session::{LlamaStateSeqFlags, SeqState};
45
46#[cfg(feature = "common")]
47pub(crate) fn status_is_ok(status: llama_cpp_sys_2::llama_rs_status) -> bool {
48 status == llama_cpp_sys_2::LLAMA_RS_STATUS_OK
49}
50
51pub type Result<T> = std::result::Result<T, LlamaCppError>;
53
54#[derive(Debug, Eq, PartialEq, thiserror::Error)]
56pub enum LlamaCppError {
57 #[error("BackendAlreadyInitialized")]
60 BackendAlreadyInitialized,
61 #[error("{0}")]
63 ChatTemplateError(#[from] ChatTemplateError),
64 #[error("{0}")]
66 DecodeError(#[from] DecodeError),
67 #[error("{0}")]
69 EncodeError(#[from] EncodeError),
70 #[error("{0}")]
72 LlamaModelLoadError(#[from] LlamaModelLoadError),
73 #[error("{0}")]
75 LlamaContextLoadError(#[from] LlamaContextLoadError),
76 #[error["{0}"]]
78 BatchAddError(#[from] BatchAddError),
79 #[error(transparent)]
81 EmbeddingError(#[from] EmbeddingsError),
82 #[error("Backend device {0} not found")]
85 BackendDeviceNotFound(usize),
86 #[error("Max devices exceeded. Max devices is {0}")]
88 MaxDevicesExceeded(usize),
89 #[cfg(feature = "common")]
91 #[error("JsonSchemaToGrammarError: {0}")]
92 JsonSchemaToGrammarError(String),
93 #[cfg(feature = "common")]
95 #[error("{0}")]
96 FitError(#[from] crate::model::params::FitError),
97}
98
99#[derive(Debug, Eq, PartialEq, thiserror::Error)]
101pub enum ChatTemplateError {
102 #[error("chat template not found - returned null pointer")]
104 MissingTemplate,
105
106 #[error("null byte in string {0}")]
108 NullError(#[from] NulError),
109
110 #[error(transparent)]
112 Utf8Error(#[from] std::str::Utf8Error),
113}
114
115#[derive(Debug, Eq, PartialEq, thiserror::Error)]
117pub enum MetaValError {
118 #[error("null byte in string {0}")]
120 NullError(#[from] NulError),
121
122 #[error("FromUtf8Error {0}")]
124 FromUtf8Error(#[from] FromUtf8Error),
125
126 #[error("Negative return value. Likely due to a missing index or key. Got return value: {0}")]
128 NegativeReturn(i32),
129}
130
131#[derive(Debug, Eq, PartialEq, thiserror::Error)]
133pub enum LlamaContextLoadError {
134 #[error("null reference from llama.cpp")]
136 NullReturn,
137}
138
139#[derive(Debug, Eq, PartialEq, thiserror::Error)]
141pub enum StateSeqError {
142 #[error("state seq size mismatch: expected {expected}, actual {actual}")]
144 SizeMismatch {
145 expected: usize,
147 actual: usize,
149 },
150}
151
152#[derive(Debug, Eq, PartialEq, thiserror::Error)]
154pub enum DecodeError {
155 #[error("Decode Error 1: NoKvCacheSlot")]
157 NoKvCacheSlot,
158 #[error("Decode Error -1: n_tokens == 0")]
160 NTokensZero,
161 #[error("Decode Error {0}: unknown")]
163 Unknown(c_int),
164}
165
166#[derive(Debug, Eq, PartialEq, thiserror::Error)]
168pub enum EncodeError {
169 #[error("Encode Error 1: NoKvCacheSlot")]
171 NoKvCacheSlot,
172 #[error("Encode Error -1: n_tokens == 0")]
174 NTokensZero,
175 #[error("Encode Error {0}: unknown")]
177 Unknown(c_int),
178}
179
180#[derive(Debug, Eq, PartialEq, thiserror::Error)]
182pub enum EmbeddingsError {
183 #[error("Embeddings weren't enabled in the context options")]
185 NotEnabled,
186 #[error("Logits were not enabled for the given token")]
188 LogitsNotEnabled,
189 #[error("Can't use sequence embeddings with a model supporting only LLAMA_POOLING_TYPE_NONE")]
191 NonePoolType,
192}
193
194#[derive(Debug, Eq, PartialEq, thiserror::Error)]
196pub enum GrammarError {
197 #[error("Grammar root not found in grammar string")]
199 RootNotFound,
200 #[error("Trigger word contains null bytes")]
202 TriggerWordNullBytes,
203 #[error("Grammar string or root contains null bytes")]
205 GrammarNullBytes,
206 #[error("Grammar call returned null")]
208 NullGrammar,
209}
210
211impl From<NonZeroI32> for DecodeError {
213 fn from(value: NonZeroI32) -> Self {
214 match value.get() {
215 1 => DecodeError::NoKvCacheSlot,
216 -1 => DecodeError::NTokensZero,
217 i => DecodeError::Unknown(i),
218 }
219 }
220}
221
222impl From<NonZeroI32> for EncodeError {
224 fn from(value: NonZeroI32) -> Self {
225 match value.get() {
226 1 => EncodeError::NoKvCacheSlot,
227 -1 => EncodeError::NTokensZero,
228 i => EncodeError::Unknown(i),
229 }
230 }
231}
232
233#[derive(Debug, Eq, PartialEq, thiserror::Error)]
235pub enum LlamaModelLoadError {
236 #[error("null byte in string {0}")]
238 NullError(#[from] NulError),
239 #[error("null result from llama cpp")]
241 NullResult,
242 #[error("failed to convert path {0} to str")]
244 PathToStrError(PathBuf),
245}
246
247#[derive(Debug, Eq, PartialEq, thiserror::Error)]
249pub enum LlamaLoraAdapterInitError {
250 #[error("null byte in string {0}")]
252 NullError(#[from] NulError),
253 #[error("null result from llama cpp")]
255 NullResult,
256 #[error("failed to convert path {0} to str")]
258 PathToStrError(PathBuf),
259}
260
261#[derive(Debug, Eq, PartialEq, thiserror::Error)]
263pub enum LlamaLoraAdapterSetError {
264 #[error("error code from llama cpp")]
266 ErrorResult(i32),
267}
268
269#[derive(Debug, Eq, PartialEq, thiserror::Error)]
271pub enum LlamaLoraAdapterRemoveError {
272 #[error("error code from llama cpp")]
274 ErrorResult(i32),
275}
276
277#[must_use]
286pub fn llama_time_us() -> i64 {
287 unsafe { llama_cpp_sys_2::llama_time_us() }
288}
289
290#[must_use]
297pub fn max_devices() -> usize {
298 unsafe { llama_cpp_sys_2::llama_max_devices() }
299}
300
301#[must_use]
310pub fn mmap_supported() -> bool {
311 unsafe { llama_cpp_sys_2::llama_supports_mmap() }
312}
313
314#[must_use]
323pub fn mlock_supported() -> bool {
324 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
325}
326
327#[cfg(feature = "common")]
329pub fn json_schema_to_grammar(schema_json: &str) -> Result<String> {
330 let schema_cstr = CString::new(schema_json)
331 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
332 let mut out = std::ptr::null_mut();
333 let rc = unsafe {
334 llama_cpp_sys_2::llama_rs_json_schema_to_grammar(schema_cstr.as_ptr(), false, &mut out)
335 };
336
337 let result = {
338 if !status_is_ok(rc) || out.is_null() {
339 return Err(LlamaCppError::JsonSchemaToGrammarError(format!(
340 "ffi error {}",
341 rc
342 )));
343 }
344 let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
345 let grammar = String::from_utf8(grammar_bytes)
346 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
347 Ok(grammar)
348 };
349
350 unsafe { llama_cpp_sys_2::llama_rs_string_free(out) };
351 result
352}
353
354#[cfg(all(test, feature = "common"))]
355mod tests {
356 use super::json_schema_to_grammar;
357
358 #[test]
359 fn json_schema_string_api_returns_grammar() {
360 let schema = r#"{
361 "type": "object",
362 "properties": {
363 "city": { "type": "string" },
364 "unit": { "enum": ["c", "f"] }
365 },
366 "required": ["city"]
367 }"#;
368
369 let grammar =
370 json_schema_to_grammar(schema).expect("string-based schema conversion should succeed");
371
372 assert!(grammar.contains("root ::="));
373 }
374}
375
376#[derive(Debug, thiserror::Error, Clone)]
378#[non_exhaustive]
379pub enum TokenToStringError {
380 #[error("Unknown Token Type")]
382 UnknownTokenType,
383 #[error("Insufficient Buffer Space {0}")]
385 InsufficientBufferSpace(c_int),
386 #[error("FromUtf8Error {0}")]
388 FromUtf8Error(#[from] FromUtf8Error),
389}
390
391#[derive(Debug, thiserror::Error)]
393pub enum StringToTokenError {
394 #[error("{0}")]
396 NulError(#[from] NulError),
397 #[error("{0}")]
398 CIntConversionError(#[from] std::num::TryFromIntError),
400}
401
402#[derive(Debug, thiserror::Error)]
404pub enum NewLlamaChatMessageError {
405 #[error("{0}")]
407 NulError(#[from] NulError),
408}
409
410#[derive(Debug, thiserror::Error)]
412pub enum ApplyChatTemplateError {
413 #[error("{0}")]
415 NulError(#[from] NulError),
416 #[error("{0}")]
418 FromUtf8Error(#[from] FromUtf8Error),
419 #[error("null result from llama.cpp")]
421 NullResult,
422 #[error("ffi error {0}")]
424 FfiError(i32),
425}
426
427#[derive(Debug, thiserror::Error)]
429pub enum SamplerAcceptError {
430 #[error("ffi error {0}")]
432 FfiError(i32),
433}
434
435#[must_use]
453pub fn ggml_time_us() -> i64 {
454 unsafe { llama_cpp_sys_2::ggml_time_us() }
455}
456
457#[must_use]
469pub fn llama_supports_mlock() -> bool {
470 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq)]
475pub enum LlamaBackendDeviceType {
476 Cpu,
478 Accelerator,
480 Gpu,
482 IntegratedGpu,
484 Unknown,
486}
487
488#[derive(Debug, Clone)]
492pub struct LlamaBackendDevice {
493 pub index: usize,
497 pub name: String,
499 pub description: String,
501 pub backend: String,
503 pub memory_total: usize,
505 pub memory_free: usize,
507 pub device_type: LlamaBackendDeviceType,
509}
510
511#[must_use]
513pub fn list_llama_ggml_backend_devices() -> Vec<LlamaBackendDevice> {
514 let mut devices = Vec::new();
515 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
516 fn cstr_to_string(ptr: *const c_char) -> String {
517 if ptr.is_null() {
518 String::new()
519 } else {
520 unsafe { std::ffi::CStr::from_ptr(ptr) }
521 .to_string_lossy()
522 .to_string()
523 }
524 }
525 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
526 let props = unsafe {
527 let mut props = std::mem::zeroed();
528 llama_cpp_sys_2::ggml_backend_dev_get_props(dev, &raw mut props);
529 props
530 };
531 let name = cstr_to_string(props.name);
532 let description = cstr_to_string(props.description);
533 let backend = unsafe { llama_cpp_sys_2::ggml_backend_dev_backend_reg(dev) };
534 let backend_name = unsafe { llama_cpp_sys_2::ggml_backend_reg_name(backend) };
535 let backend = cstr_to_string(backend_name);
536 let memory_total = props.memory_total;
537 let memory_free = props.memory_free;
538 let device_type = match props.type_ {
539 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_CPU => LlamaBackendDeviceType::Cpu,
540 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_ACCEL => LlamaBackendDeviceType::Accelerator,
541 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_GPU => LlamaBackendDeviceType::Gpu,
542 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_IGPU => LlamaBackendDeviceType::IntegratedGpu,
543 _ => LlamaBackendDeviceType::Unknown,
544 };
545 devices.push(LlamaBackendDevice {
546 index: i,
547 name,
548 description,
549 backend,
550 memory_total,
551 memory_free,
552 device_type,
553 });
554 }
555 devices
556}
557
558#[derive(Default, Debug, Clone)]
560pub struct LogOptions {
561 disabled: bool,
562}
563
564impl LogOptions {
565 #[must_use]
568 pub fn with_logs_enabled(mut self, enabled: bool) -> Self {
569 self.disabled = !enabled;
570 self
571 }
572}
573
574extern "C" fn logs_to_trace(
575 level: llama_cpp_sys_2::ggml_log_level,
576 text: *const ::std::os::raw::c_char,
577 data: *mut ::std::os::raw::c_void,
578) {
579 use std::borrow::Borrow;
584
585 let log_state = unsafe { &*(data as *const log::State) };
586
587 if log_state.options.disabled {
588 return;
589 }
590
591 if !log_state.is_enabled_for_level(level) {
593 log_state.update_previous_level_for_disabled_log(level);
594 return;
595 }
596
597 let text = unsafe { std::ffi::CStr::from_ptr(text) };
598 let text = text.to_string_lossy();
599 let text: &str = text.borrow();
600
601 if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
607 log_state.cont_buffered_log(text);
608 } else if text.ends_with('\n') {
609 log_state.emit_non_cont_line(level, text);
610 } else {
611 log_state.buffer_non_cont(level, text);
612 }
613}
614
615pub fn send_logs_to_tracing(options: LogOptions) {
617 static LLAMA_STATE: OnceLock<Box<log::State>> = OnceLock::new();
627 #[cfg(feature = "mtmd")]
628 static MTMD_STATE: OnceLock<Box<log::State>> = OnceLock::new();
629 static GGML_STATE: OnceLock<Box<log::State>> = OnceLock::new();
630
631 let llama_heap_state = Box::as_ref(
632 LLAMA_STATE
633 .get_or_init(|| Box::new(log::State::new(log::Module::LlamaCpp, options.clone()))),
634 ) as *const _;
635
636 #[cfg(feature = "mtmd")]
637 let mtmd_heap_state = Box::as_ref(
638 MTMD_STATE.get_or_init(|| Box::new(log::State::new(log::Module::Mtmd, options.clone()))),
639 ) as *const _;
640
641 let ggml_heap_state = Box::as_ref(
642 GGML_STATE.get_or_init(|| Box::new(log::State::new(log::Module::GGML, options))),
643 ) as *const _;
644
645 unsafe {
646 llama_cpp_sys_2::llama_log_set(Some(logs_to_trace), llama_heap_state as *mut _);
647
648 #[cfg(feature = "mtmd")]
652 llama_cpp_sys_2::mtmd_helper_log_set(Some(logs_to_trace), mtmd_heap_state as *mut _);
653
654 llama_cpp_sys_2::ggml_log_set(Some(logs_to_trace), ggml_heap_state as *mut _);
656 }
657}