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;
43pub mod vocab;
44
45pub use crate::context::session::{LlamaStateSeqFlags, SeqState};
46
47#[cfg(feature = "common")]
48pub(crate) fn status_is_ok(status: llama_cpp_sys_2::llama_rs_status) -> bool {
49 status == llama_cpp_sys_2::LLAMA_RS_STATUS_OK
50}
51
52pub type Result<T> = std::result::Result<T, LlamaCppError>;
54
55#[derive(Debug, Eq, PartialEq, thiserror::Error)]
57pub enum LlamaCppError {
58 #[error("BackendAlreadyInitialized")]
61 BackendAlreadyInitialized,
62 #[error("{0}")]
64 ChatTemplateError(#[from] ChatTemplateError),
65 #[error("{0}")]
67 DecodeError(#[from] DecodeError),
68 #[error("{0}")]
70 EncodeError(#[from] EncodeError),
71 #[error("{0}")]
73 LlamaModelLoadError(#[from] LlamaModelLoadError),
74 #[error("{0}")]
76 LlamaContextLoadError(#[from] LlamaContextLoadError),
77 #[error["{0}"]]
79 BatchAddError(#[from] BatchAddError),
80 #[error(transparent)]
82 EmbeddingError(#[from] EmbeddingsError),
83 #[error("Backend device {0} not found")]
86 BackendDeviceNotFound(usize),
87 #[error("Max devices exceeded. Max devices is {0}")]
89 MaxDevicesExceeded(usize),
90 #[cfg(feature = "common")]
92 #[error("JsonSchemaToGrammarError: {0}")]
93 JsonSchemaToGrammarError(String),
94 #[cfg(feature = "common")]
96 #[error("{0}")]
97 FitError(#[from] crate::model::params::FitError),
98}
99
100#[derive(Debug, Eq, PartialEq, thiserror::Error)]
102pub enum ChatTemplateError {
103 #[error("chat template not found - returned null pointer")]
105 MissingTemplate,
106
107 #[error("null byte in string {0}")]
109 NullError(#[from] NulError),
110
111 #[error(transparent)]
113 Utf8Error(#[from] std::str::Utf8Error),
114}
115
116#[derive(Debug, Eq, PartialEq, thiserror::Error)]
118pub enum MetaValError {
119 #[error("null byte in string {0}")]
121 NullError(#[from] NulError),
122
123 #[error("FromUtf8Error {0}")]
125 FromUtf8Error(#[from] FromUtf8Error),
126
127 #[error("Negative return value. Likely due to a missing index or key. Got return value: {0}")]
129 NegativeReturn(i32),
130}
131
132#[derive(Debug, Eq, PartialEq, thiserror::Error)]
134pub enum LlamaContextLoadError {
135 #[error("null reference from llama.cpp")]
137 NullReturn,
138}
139
140#[derive(Debug, Eq, PartialEq, thiserror::Error)]
142pub enum StateSeqError {
143 #[error("state seq size mismatch: expected {expected}, actual {actual}")]
145 SizeMismatch {
146 expected: usize,
148 actual: usize,
150 },
151}
152
153#[derive(Debug, Eq, PartialEq, thiserror::Error)]
155pub enum DecodeError {
156 #[error("Decode Error 1: NoKvCacheSlot")]
158 NoKvCacheSlot,
159 #[error("Decode Error -1: n_tokens == 0")]
161 NTokensZero,
162 #[error("Decode Error {0}: unknown")]
164 Unknown(c_int),
165}
166
167#[derive(Debug, Eq, PartialEq, thiserror::Error)]
169pub enum EncodeError {
170 #[error("Encode Error 1: NoKvCacheSlot")]
172 NoKvCacheSlot,
173 #[error("Encode Error -1: n_tokens == 0")]
175 NTokensZero,
176 #[error("Encode Error {0}: unknown")]
178 Unknown(c_int),
179}
180
181#[derive(Debug, Eq, PartialEq, thiserror::Error)]
183pub enum EmbeddingsError {
184 #[error("Embeddings weren't enabled in the context options")]
186 NotEnabled,
187 #[error("Logits were not enabled for the given token")]
189 LogitsNotEnabled,
190 #[error("Can't use sequence embeddings with a model supporting only LLAMA_POOLING_TYPE_NONE")]
192 NonePoolType,
193}
194
195#[derive(Debug, Eq, PartialEq, thiserror::Error)]
197pub enum GrammarError {
198 #[error("Grammar root not found in grammar string")]
200 RootNotFound,
201 #[error("Trigger word contains null bytes")]
203 TriggerWordNullBytes,
204 #[error("Grammar string or root contains null bytes")]
206 GrammarNullBytes,
207 #[error("Grammar call returned null")]
209 NullGrammar,
210}
211
212impl From<NonZeroI32> for DecodeError {
214 fn from(value: NonZeroI32) -> Self {
215 match value.get() {
216 1 => DecodeError::NoKvCacheSlot,
217 -1 => DecodeError::NTokensZero,
218 i => DecodeError::Unknown(i),
219 }
220 }
221}
222
223impl From<NonZeroI32> for EncodeError {
225 fn from(value: NonZeroI32) -> Self {
226 match value.get() {
227 1 => EncodeError::NoKvCacheSlot,
228 -1 => EncodeError::NTokensZero,
229 i => EncodeError::Unknown(i),
230 }
231 }
232}
233
234#[derive(Debug, Eq, PartialEq, thiserror::Error)]
236pub enum LlamaModelLoadError {
237 #[error("null byte in string {0}")]
239 NullError(#[from] NulError),
240 #[error("null result from llama cpp")]
242 NullResult,
243 #[error("failed to convert path {0} to str")]
245 PathToStrError(PathBuf),
246}
247
248#[derive(Debug, Eq, PartialEq, thiserror::Error)]
250pub enum LlamaLoraAdapterInitError {
251 #[error("null byte in string {0}")]
253 NullError(#[from] NulError),
254 #[error("null result from llama cpp")]
256 NullResult,
257 #[error("failed to convert path {0} to str")]
259 PathToStrError(PathBuf),
260}
261
262#[derive(Debug, Eq, PartialEq, thiserror::Error)]
264pub enum LlamaLoraAdapterSetError {
265 #[error("error code from llama cpp")]
267 ErrorResult(i32),
268}
269
270#[derive(Debug, Eq, PartialEq, thiserror::Error)]
272pub enum LlamaLoraAdapterRemoveError {
273 #[error("error code from llama cpp")]
275 ErrorResult(i32),
276}
277
278#[must_use]
287pub fn llama_time_us() -> i64 {
288 unsafe { llama_cpp_sys_2::llama_time_us() }
289}
290
291#[must_use]
298pub fn max_devices() -> usize {
299 unsafe { llama_cpp_sys_2::llama_max_devices() }
300}
301
302#[must_use]
311pub fn mmap_supported() -> bool {
312 unsafe { llama_cpp_sys_2::llama_supports_mmap() }
313}
314
315#[must_use]
324pub fn mlock_supported() -> bool {
325 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
326}
327
328#[cfg(feature = "common")]
330pub fn json_schema_to_grammar(schema_json: &str) -> Result<String> {
331 let schema_cstr = CString::new(schema_json)
332 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
333 let mut out = std::ptr::null_mut();
334 let rc = unsafe {
335 llama_cpp_sys_2::llama_rs_json_schema_to_grammar(schema_cstr.as_ptr(), false, &mut out)
336 };
337
338 let result = {
339 if !status_is_ok(rc) || out.is_null() {
340 return Err(LlamaCppError::JsonSchemaToGrammarError(format!(
341 "ffi error {}",
342 rc
343 )));
344 }
345 let grammar_bytes = unsafe { CStr::from_ptr(out) }.to_bytes().to_vec();
346 let grammar = String::from_utf8(grammar_bytes)
347 .map_err(|err| LlamaCppError::JsonSchemaToGrammarError(err.to_string()))?;
348 Ok(grammar)
349 };
350
351 unsafe { llama_cpp_sys_2::llama_rs_string_free(out) };
352 result
353}
354
355#[cfg(all(test, feature = "common"))]
356mod tests {
357 use super::json_schema_to_grammar;
358
359 #[test]
360 fn json_schema_string_api_returns_grammar() {
361 let schema = r#"{
362 "type": "object",
363 "properties": {
364 "city": { "type": "string" },
365 "unit": { "enum": ["c", "f"] }
366 },
367 "required": ["city"]
368 }"#;
369
370 let grammar =
371 json_schema_to_grammar(schema).expect("string-based schema conversion should succeed");
372
373 assert!(grammar.contains("root ::="));
374 }
375}
376
377#[derive(Debug, thiserror::Error)]
379pub enum NewLlamaChatMessageError {
380 #[error("{0}")]
382 NulError(#[from] NulError),
383}
384
385#[derive(Debug, thiserror::Error)]
387pub enum ApplyChatTemplateError {
388 #[error("{0}")]
390 NulError(#[from] NulError),
391 #[error("{0}")]
393 FromUtf8Error(#[from] FromUtf8Error),
394 #[error("null result from llama.cpp")]
396 NullResult,
397 #[error("ffi error {0}")]
399 FfiError(i32),
400}
401
402#[derive(Debug, thiserror::Error)]
404pub enum SamplerAcceptError {
405 #[error("ffi error {0}")]
407 FfiError(i32),
408}
409
410#[must_use]
428pub fn ggml_time_us() -> i64 {
429 unsafe { llama_cpp_sys_2::ggml_time_us() }
430}
431
432#[must_use]
444pub fn llama_supports_mlock() -> bool {
445 unsafe { llama_cpp_sys_2::llama_supports_mlock() }
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq)]
450pub enum LlamaBackendDeviceType {
451 Cpu,
453 Accelerator,
455 Gpu,
457 IntegratedGpu,
459 Unknown,
461}
462
463#[derive(Debug, Clone)]
467pub struct LlamaBackendDevice {
468 pub index: usize,
472 pub name: String,
474 pub description: String,
476 pub backend: String,
478 pub memory_total: usize,
480 pub memory_free: usize,
482 pub device_type: LlamaBackendDeviceType,
484}
485
486#[must_use]
488pub fn list_llama_ggml_backend_devices() -> Vec<LlamaBackendDevice> {
489 let mut devices = Vec::new();
490 for i in 0..unsafe { llama_cpp_sys_2::ggml_backend_dev_count() } {
491 fn cstr_to_string(ptr: *const c_char) -> String {
492 if ptr.is_null() {
493 String::new()
494 } else {
495 unsafe { std::ffi::CStr::from_ptr(ptr) }
496 .to_string_lossy()
497 .to_string()
498 }
499 }
500 let dev = unsafe { llama_cpp_sys_2::ggml_backend_dev_get(i) };
501 let props = unsafe {
502 let mut props = std::mem::zeroed();
503 llama_cpp_sys_2::ggml_backend_dev_get_props(dev, &raw mut props);
504 props
505 };
506 let name = cstr_to_string(props.name);
507 let description = cstr_to_string(props.description);
508 let backend = unsafe { llama_cpp_sys_2::ggml_backend_dev_backend_reg(dev) };
509 let backend_name = unsafe { llama_cpp_sys_2::ggml_backend_reg_name(backend) };
510 let backend = cstr_to_string(backend_name);
511 let memory_total = props.memory_total;
512 let memory_free = props.memory_free;
513 let device_type = match props.type_ {
514 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_CPU => LlamaBackendDeviceType::Cpu,
515 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_ACCEL => LlamaBackendDeviceType::Accelerator,
516 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_GPU => LlamaBackendDeviceType::Gpu,
517 llama_cpp_sys_2::GGML_BACKEND_DEVICE_TYPE_IGPU => LlamaBackendDeviceType::IntegratedGpu,
518 _ => LlamaBackendDeviceType::Unknown,
519 };
520 devices.push(LlamaBackendDevice {
521 index: i,
522 name,
523 description,
524 backend,
525 memory_total,
526 memory_free,
527 device_type,
528 });
529 }
530 devices
531}
532
533#[derive(Default, Debug, Clone)]
535pub struct LogOptions {
536 disabled: bool,
537}
538
539impl LogOptions {
540 #[must_use]
543 pub fn with_logs_enabled(mut self, enabled: bool) -> Self {
544 self.disabled = !enabled;
545 self
546 }
547}
548
549extern "C" fn logs_to_trace(
550 level: llama_cpp_sys_2::ggml_log_level,
551 text: *const ::std::os::raw::c_char,
552 data: *mut ::std::os::raw::c_void,
553) {
554 use std::borrow::Borrow;
559
560 let log_state = unsafe { &*(data as *const log::State) };
561
562 if log_state.options.disabled {
563 return;
564 }
565
566 if !log_state.is_enabled_for_level(level) {
568 log_state.update_previous_level_for_disabled_log(level);
569 return;
570 }
571
572 let text = unsafe { std::ffi::CStr::from_ptr(text) };
573 let text = text.to_string_lossy();
574 let text: &str = text.borrow();
575
576 if level == llama_cpp_sys_2::GGML_LOG_LEVEL_CONT {
582 log_state.cont_buffered_log(text);
583 } else if text.ends_with('\n') {
584 log_state.emit_non_cont_line(level, text);
585 } else {
586 log_state.buffer_non_cont(level, text);
587 }
588}
589
590pub fn send_logs_to_tracing(options: LogOptions) {
592 static LLAMA_STATE: OnceLock<Box<log::State>> = OnceLock::new();
602 #[cfg(feature = "mtmd")]
603 static MTMD_STATE: OnceLock<Box<log::State>> = OnceLock::new();
604 static GGML_STATE: OnceLock<Box<log::State>> = OnceLock::new();
605
606 let llama_heap_state = Box::as_ref(
607 LLAMA_STATE
608 .get_or_init(|| Box::new(log::State::new(log::Module::LlamaCpp, options.clone()))),
609 ) as *const _;
610
611 #[cfg(feature = "mtmd")]
612 let mtmd_heap_state = Box::as_ref(
613 MTMD_STATE.get_or_init(|| Box::new(log::State::new(log::Module::Mtmd, options.clone()))),
614 ) as *const _;
615
616 let ggml_heap_state = Box::as_ref(
617 GGML_STATE.get_or_init(|| Box::new(log::State::new(log::Module::GGML, options))),
618 ) as *const _;
619
620 unsafe {
621 llama_cpp_sys_2::llama_log_set(Some(logs_to_trace), llama_heap_state as *mut _);
622
623 #[cfg(feature = "mtmd")]
627 llama_cpp_sys_2::mtmd_helper_log_set(Some(logs_to_trace), mtmd_heap_state as *mut _);
628
629 llama_cpp_sys_2::ggml_log_set(Some(logs_to_trace), ggml_heap_state as *mut _);
631 }
632}