llama_cpp_4/context/memory_breakdown.rs
1//! Per-buffer-type memory usage reported by llama.cpp.
2//!
3//! [`MemoryBreakdownEntry`] values are produced by
4//! [`crate::context::LlamaContext::memory_breakdown`] and classify bytes into
5//! model weights, KV / recurrent cache, and temporary compute buffers for each
6//! ggml backend buffer type (e.g. `CUDA0`, `Metal`, `Host`).
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use llama_cpp_4::prelude::*;
12//!
13//! fn main() {
14//! let backend = LlamaBackend::init().unwrap();
15//! let model = LlamaModel::load_from_file(&backend, "model.gguf", &LlamaModelParams::default()).unwrap();
16//! let ctx = model.new_context(&backend, LlamaContextParams::default()).unwrap();
17//! for entry in ctx.memory_breakdown() {
18//! println!("{}: {} bytes total", entry.buft_name, entry.total());
19//! }
20//! }
21//! ```
22
23use std::ffi::CStr;
24
25/// Memory attributed to a single backend buffer type (e.g. CUDA0, Host).
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct MemoryBreakdownEntry {
28 /// Human-readable buffer-type name from ggml.
29 pub buft_name: String,
30 /// Bytes used by model weights on this buffer type.
31 pub model: usize,
32 /// Bytes used by the KV / recurrent context cache.
33 pub context: usize,
34 /// Bytes used by temporary compute buffers.
35 pub compute: usize,
36}
37
38impl MemoryBreakdownEntry {
39 /// Sum of model, context, and compute bytes.
40 #[must_use]
41 pub fn total(&self) -> usize {
42 self.model + self.context + self.compute
43 }
44}
45
46fn raw_entry_to_rust(
47 entry: &llama_cpp_sys_4::llama_memory_breakdown_entry,
48) -> MemoryBreakdownEntry {
49 // SAFETY: `buft_name` is a fixed-size `c_char` array inside the entry, so
50 // its own pointer and length describe exactly that array.
51 let bytes: &[u8] = unsafe {
52 std::slice::from_raw_parts(entry.buft_name.as_ptr().cast(), entry.buft_name.len())
53 };
54 let name = CStr::from_bytes_until_nul(bytes)
55 .map(|c| c.to_string_lossy().into_owned())
56 .unwrap_or_default();
57 MemoryBreakdownEntry {
58 buft_name: name,
59 model: entry.model,
60 context: entry.context,
61 compute: entry.compute,
62 }
63}
64
65/// Collect memory breakdown entries for a live context.
66///
67/// Wraps the `ext_shim` helper around `llama_get_memory_breakdown`. Grows the
68/// output buffer until every entry fits. Returns an empty vector when the
69/// context pointer is invalid or no buffers are registered yet.
70///
71/// Prefer [`crate::context::LlamaContext::memory_breakdown`] in application code.
72#[must_use]
73pub(crate) fn collect_memory_breakdown(
74 ctx: *const llama_cpp_sys_4::llama_context,
75) -> Vec<MemoryBreakdownEntry> {
76 if ctx.is_null() {
77 return Vec::new();
78 }
79
80 let mut capacity = 16usize;
81 loop {
82 let mut raw = vec![
83 llama_cpp_sys_4::llama_memory_breakdown_entry {
84 buft_name: [0; 128],
85 model: 0,
86 context: 0,
87 compute: 0,
88 };
89 capacity
90 ];
91
92 // SAFETY: `ctx` is a live context, and `raw` is a Rust buffer whose
93 // capacity is passed alongside its pointer, so the shim cannot overrun it.
94 let n = unsafe {
95 llama_cpp_sys_4::llama_memory_breakdown_collect(ctx, raw.as_mut_ptr(), capacity)
96 };
97
98 if n < capacity {
99 return raw
100 .into_iter()
101 .take(n)
102 .map(|e| raw_entry_to_rust(&e))
103 .collect();
104 }
105
106 capacity = capacity.saturating_mul(2);
107 if capacity > 4096 {
108 return raw.into_iter().map(|e| raw_entry_to_rust(&e)).collect();
109 }
110 }
111}