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;
37mod ptr;
38pub mod sampling;
39#[cfg(feature = "common")]
40pub mod speculative;
41pub mod timing;
42pub mod token;
43pub mod token_type;
44pub mod vocab;
45
46pub use crate::context::session::{LlamaStateSeqFlags, SeqState};
47
48#[cfg(feature = "common")]
49pub(crate) fn status_is_ok(status: llama_cpp_sys_2::llama_rs_status) -> bool {
50 status == llama_cpp_sys_2::LLAMA_RS_STATUS_OK
51}
52
53pub type Result<T> = std::result::Result<T, LlamaCppError>;
55
56#[derive(Debug, Eq, PartialEq, thiserror::Error)]
58pub enum LlamaCppError {
59 #[error("BackendAlreadyInitialized")]
62 BackendAlreadyInitialized,
63 #[error("{0}")]
65 ChatTemplateError(#[from] ChatTemplateError),
66 #[error("{0}")]
68 DecodeError(#[from] DecodeError),
69 #[error("{0}")]
71 EncodeError(#[from] EncodeError),
72 #[error("{0}")]
74 LlamaModelLoadError(#[from] LlamaModelLoadError),
75 #[error("{0}")]
77 LlamaContextLoadError(#[from] LlamaContextLoadError),
78 #[error["{0}"]]
80 BatchAddError(#[from] BatchAddError),
81 #[error(transparent)]
83 EmbeddingError(#[from] EmbeddingsError),
84 #[error("Backend device {0} not found")]
87 BackendDeviceNotFound(usize),
88 #[error("Max devices exceeded. Max devices is {0}")]
90 MaxDevicesExceeded(usize),
91 #[cfg(feature = "common")]
93 #[error("JsonSchemaToGrammarError: {0}")]
94 JsonSchemaToGrammarError(String),
95 #[cfg(feature = "common")]
97 #[error("{0}")]
98 FitError(#[from] crate::model::params::FitError),
99}
100
101#[derive(Debug, Eq, PartialEq, thiserror::Error)]
103pub enum ChatTemplateError {
104 #[error("chat template not found - returned null pointer")]
106 MissingTemplate,
107
108 #[error("null byte in string {0}")]
110 NullError(#[from] NulError),
111
112 #[error(transparent)]
114 Utf8Error(#[from] std::str::Utf8Error),
115}
116
117#[derive(Debug, Eq, PartialEq, thiserror::Error)]
119pub enum MetaValError {
120 #[error("null byte in string {0}")]
122 NullError(#[from] NulError),
123
124 #[error("FromUtf8Error {0}")]
126 FromUtf8Error(#[from] FromUtf8Error),
127
128 #[error("Negative return value. Likely due to a missing index or key. Got return value: {0}")]
130 NegativeReturn(i32),
131}
132
133#[derive(Debug, Eq, PartialEq, thiserror::Error)]
135pub enum LlamaContextLoadError {
136 #[error("null reference from llama.cpp")]
138 NullReturn,
139}
140
141#[derive(Debug, Eq, PartialEq, thiserror::Error)]
143pub enum StateSeqError {
144 #[error("state seq size mismatch: expected {expected}, actual {actual}")]
146 SizeMismatch {
147 expected: usize,
149 actual: usize,
151 },
152}
153
154#[derive(Debug, Eq, PartialEq, thiserror::Error)]
156pub enum DecodeError {
157 #[error("Decode Error 1: NoKvCacheSlot")]
159 NoKvCacheSlot,
160 #[error("Decode Error -1: n_tokens == 0")]
162 NTokensZero,
163 #[error("Decode Error {0}: unknown")]
165 Unknown(c_int),
166}
167
168#[derive(Debug, Eq, PartialEq, thiserror::Error)]
170pub enum EncodeError {
171 #[error("Encode Error 1: NoKvCacheSlot")]
173 NoKvCacheSlot,
174 #[error("Encode Error -1: n_tokens == 0")]
176 NTokensZero,
177 #[error("Encode Error {0}: unknown")]
179 Unknown(c_int),
180}
181
182#[derive(Debug, Eq, PartialEq, thiserror::Error)]
184#[error("Embeddings or logits weren't enabled")]
185pub struct EmbeddingsError(());
186
187#[derive(Debug, Eq, PartialEq, thiserror::Error)]
189#[error("Embeddings or logits weren't enabled, or the model was configured with LLAMA_POOLING_TYPE_NONE")]
190pub struct EmbeddingsSeqError(());
191
192#[derive(Debug, Eq, PartialEq, thiserror::Error)]
194pub enum GrammarError {
195 #[error("Grammar root not found in grammar string")]
197 RootNotFound,
198 #[error("Trigger word contains null bytes")]
200 TriggerWordNullBytes,
201 #[error("Grammar string or root contains null bytes")]
203 GrammarNullBytes,
204 #[error("Grammar call returned null")]
206 NullGrammar,
207}
208
209impl From<NonZeroI32> for DecodeError {
211 fn from(value: NonZeroI32) -> Self {
212 match value.get() {
213 1 => DecodeError::NoKvCacheSlot,
214 -1 => DecodeError::NTokensZero,
215 i => DecodeError::Unknown(i),
216 }
217 }
218}
219
220impl From<NonZeroI32> for EncodeError {
222 fn from(value: NonZeroI32) -> Self {
223 match value.get() {
224 1 => EncodeError::NoKvCacheSlot,
225 -1 => EncodeError::NTokensZero,
226 i => EncodeError::Unknown(i),
227 }
228 }
229}
230
231#[derive(Debug, Eq, PartialEq, thiserror::Error)]
233pub enum LlamaModelLoadError {
234 #[error("null byte in string {0}")]
236 NullError(#[from] NulError),
237 #[error("null result from llama cpp")]
239 NullResult,
240 #[error("failed to convert path {0} to str")]
242 PathToStrError(PathBuf),
243}
244
245#[derive(Debug, Eq, PartialEq, thiserror::Error)]
247pub enum LlamaLoraAdapterInitError {
248 #[error("null byte in string {0}")]
250 NullError(#[from] NulError),
251 #[error("null result from llama cpp")]
253 NullResult,
254 #[error("failed to convert path {0} to str")]
256 PathToStrError(PathBuf),
257}
258
259#[derive(Debug, Eq, PartialEq, thiserror::Error)]
261pub enum LlamaLoraAdapterSetError {
262 #[error("error code from llama cpp")]
264 ErrorResult(i32),
265}
266
267#[derive(Debug, Eq, PartialEq, thiserror::Error)]
269pub enum LlamaLoraAdapterRemoveError {
270 #[error("error code from llama cpp")]
272 ErrorResult(i32),
273}
274
275#[must_use]
284pub fn llama_time_us() -> i64 {
285 unsafe { llama_cpp_sys_2::llama_time_us() }
286}
287
288#[must_use]
295pub fn max_devices() -> usize {
296 unsafe { llama_cpp_sys_2::llama_max_devices() }
297}
298
299#[must_use]
308pub fn mmap_supported() -> bool {
309 unsafe { llama_cpp_sys_2::llama_supports_mmap() }
310}
311
312#[must_use]
321pub fn mlock_supported() -> bool {
322 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
323}
324
325#[cfg(feature = "common")]
327pub fn json_schema_to_grammar(schema_json: &str) -> Result<String> {
328 let schema_cstr = CString::new(schema_json)
329 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
330 let mut out = std::ptr::null_mut();
331 let rc = unsafe {
332 llama_cpp_sys_2::llama_rs_json_schema_to_grammar(schema_cstr.as_ptr(), false, &mut out)
333 };
334
335 let result = {
336 if !status_is_ok(rc) || out.is_null() {
337 return Err(LlamaCppError::JsonSchemaToGrammarError(format!(
338 "ffi error {}",
339 rc
340 )));
341 }
342 let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
343 let grammar = String::from_utf8(grammar_bytes)
344 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
345 Ok(grammar)
346 };
347
348 unsafe { llama_cpp_sys_2::llama_rs_string_free(out) };
349 result
350}
351
352#[cfg(all(test, feature = "common"))]
353mod tests {
354 use super::json_schema_to_grammar;
355
356 #[test]
357 fn json_schema_string_api_returns_grammar() {
358 let schema = r#"{
359 "type": "object",
360 "properties": {
361 "city": { "type": "string" },
362 "unit": { "enum": ["c", "f"] }
363 },
364 "required": ["city"]
365 }"#;
366
367 let grammar =
368 json_schema_to_grammar(schema).expect("string-based schema conversion should succeed");
369
370 assert!(grammar.contains("root ::="));
371 }
372}
373
374#[derive(Debug, thiserror::Error)]
376pub enum NewLlamaChatMessageError {
377 #[error("{0}")]
379 NulError(#[from] NulError),
380}
381
382#[derive(Debug, thiserror::Error)]
384pub enum ApplyChatTemplateError {
385 #[error("{0}")]
387 NulError(#[from] NulError),
388 #[error("{0}")]
390 FromUtf8Error(#[from] FromUtf8Error),
391 #[error("null result from llama.cpp")]
393 NullResult,
394 #[error("ffi error {0}")]
396 FfiError(i32),
397}
398
399#[derive(Debug, thiserror::Error)]
401pub enum SamplerAcceptError {
402 #[error("ffi error {0}")]
404 FfiError(i32),
405}
406
407#[must_use]
425pub fn ggml_time_us() -> i64 {
426 unsafe { llama_cpp_sys_2::ggml_time_us() }
427}
428
429#[must_use]
441pub fn llama_supports_mlock() -> bool {
442 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
443}
444
445#[derive(Debug, Clone, Copy, PartialEq, Eq)]
447pub enum LlamaBackendDeviceType {
448 Cpu,
450 Accelerator,
452 Gpu,
454 IntegratedGpu,
456 Unknown,
458}
459
460#[derive(Debug, Clone)]
464pub struct LlamaBackendDevice {
465 pub index: usize,
469 pub name: String,
471 pub description: String,
473 pub backend: String,
475 pub memory_total: usize,
477 pub memory_free: usize,
479 pub device_type: LlamaBackendDeviceType,
481}
482
483#[must_use]
485pub fn list_llama_ggml_backend_devices() -> Vec<LlamaBackendDevice> {
486 let mut devices = Vec::new();
487 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
488 fn cstr_to_string(ptr: *const c_char) -> String {
489 if ptr.is_null() {
490 String::new()
491 } else {
492 unsafe { std::ffi::CStr::from_ptr(ptr) }
493 .to_string_lossy()
494 .to_string()
495 }
496 }
497 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
498 let props = unsafe {
499 let mut props = std::mem::zeroed();
500 llama_cpp_sys_2::ggml_backend_dev_get_props(dev, &raw mut props);
501 props
502 };
503 let name = cstr_to_string(props.name);
504 let description = cstr_to_string(props.description);
505 let backend = unsafe { llama_cpp_sys_2::ggml_backend_dev_backend_reg(dev) };
506 let backend_name = unsafe { llama_cpp_sys_2::ggml_backend_reg_name(backend) };
507 let backend = cstr_to_string(backend_name);
508 let memory_total = props.memory_total;
509 let memory_free = props.memory_free;
510 let device_type = match props.type_ {
511 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_CPU => LlamaBackendDeviceType::Cpu,
512 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_ACCEL => LlamaBackendDeviceType::Accelerator,
513 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_GPU => LlamaBackendDeviceType::Gpu,
514 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_IGPU => LlamaBackendDeviceType::IntegratedGpu,
515 _ => LlamaBackendDeviceType::Unknown,
516 };
517 devices.push(LlamaBackendDevice {
518 index: i,
519 name,
520 description,
521 backend,
522 memory_total,
523 memory_free,
524 device_type,
525 });
526 }
527 devices
528}
529
530#[derive(Default, Debug, Clone)]
532pub struct LogOptions {
533 disabled: bool,
534}
535
536impl LogOptions {
537 #[must_use]
540 pub fn with_logs_enabled(mut self, enabled: bool) -> Self {
541 self.disabled = !enabled;
542 self
543 }
544}
545
546extern "C" fn logs_to_trace(
547 level: llama_cpp_sys_2::ggml_log_level,
548 text: *const ::std::os::raw::c_char,
549 data: *mut ::std::os::raw::c_void,
550) {
551 use std::borrow::Borrow;
556
557 let log_state = unsafe { &*(data as *const log::State) };
558
559 if log_state.options.disabled {
560 return;
561 }
562
563 if !log_state.is_enabled_for_level(level) {
565 log_state.update_previous_level_for_disabled_log(level);
566 return;
567 }
568
569 let text = unsafe { std::ffi::CStr::from_ptr(text) };
570 let text = text.to_string_lossy();
571 let text: &str = text.borrow();
572
573 if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
579 log_state.cont_buffered_log(text);
580 } else if text.ends_with('\n') {
581 log_state.emit_non_cont_line(level, text);
582 } else {
583 log_state.buffer_non_cont(level, text);
584 }
585}
586
587pub fn send_logs_to_tracing(options: LogOptions) {
589 static LLAMA_STATE: OnceLock<Box<log::State>> = OnceLock::new();
599 #[cfg(feature = "mtmd")]
600 static MTMD_STATE: OnceLock<Box<log::State>> = OnceLock::new();
601 static GGML_STATE: OnceLock<Box<log::State>> = OnceLock::new();
602
603 let llama_heap_state = Box::as_ref(
604 LLAMA_STATE
605 .get_or_init(|| Box::new(log::State::new(log::Module::LlamaCpp, options.clone()))),
606 ) as *const _;
607
608 #[cfg(feature = "mtmd")]
609 let mtmd_heap_state = Box::as_ref(
610 MTMD_STATE.get_or_init(|| Box::new(log::State::new(log::Module::Mtmd, options.clone()))),
611 ) as *const _;
612
613 let ggml_heap_state = Box::as_ref(
614 GGML_STATE.get_or_init(|| Box::new(log::State::new(log::Module::GGML, options))),
615 ) as *const _;
616
617 unsafe {
618 llama_cpp_sys_2::llama_log_set(Some(logs_to_trace), llama_heap_state as *mut _);
619
620 #[cfg(feature = "mtmd")]
624 llama_cpp_sys_2::mtmd_helper_log_set(Some(logs_to_trace), mtmd_heap_state as *mut _);
625
626 llama_cpp_sys_2::ggml_log_set(Some(logs_to_trace), ggml_heap_state as *mut _);
628 }
629}