llama_cpp_4/ggml.rs
1//! Safe wrappers around core ggml graph computation APIs.
2//!
3//! This module provides the building blocks for creating and executing tensor computation
4//! graphs using ggml backends. It's used for operations like `LoRA` merging, importance
5//! matrix computation, and control vector generation.
6//!
7//! # Example
8//!
9//! ```rust,ignore
10//! use llama_cpp_4::ggml::*;
11//!
12//! // Create a backend and context
13//! let backend = GgmlBackend::cpu()?;
14//! let mut ctx = GgmlContext::new(1024 * 1024, true)?;
15//!
16//! // Create tensors
17//! let a = ctx.new_tensor_2d(GgmlType::F32, 4, 4);
18//! let b = ctx.new_tensor_2d(GgmlType::F32, 4, 4);
19//!
20//! // Build computation graph
21//! let sum = ctx.add(&a, &b);
22//! let mut graph = ctx.new_graph();
23//! graph.build_forward(&sum);
24//!
25//! // Allocate and compute
26//! let mut alloc = GgmlAllocr::new(&backend);
27//! alloc.alloc_graph(&mut graph);
28//! // ... set tensor data ...
29//! backend.graph_compute(&mut graph);
30//! // ... get results ...
31//! ```
32
33use std::ffi::CStr;
34use std::ptr::NonNull;
35
36/// Re-export the raw ggml types for advanced usage.
37pub use llama_cpp_sys_4::ggml_type;
38
39/// A safe wrapper around `ggml_context`.
40#[derive(Debug)]
41pub struct GgmlContext {
42 ctx: NonNull<llama_cpp_sys_4::ggml_context>,
43}
44
45impl GgmlContext {
46 /// Create a new ggml context.
47 ///
48 /// # Parameters
49 /// - `mem_size`: Memory pool size in bytes for tensor metadata.
50 /// - `no_alloc`: If true, tensor data is not allocated (use with backend allocation).
51 ///
52 /// # Sizing the pool
53 ///
54 /// Every tensor and graph created from this context is carved out of
55 /// `mem_size`, and **running out is not a recoverable error**:
56 ///
57 /// - a *release* build of ggml returns null, which the constructors here
58 /// turn into a panic;
59 /// - a *debug* build of ggml calls `GGML_ABORT` and kills the process
60 /// before returning, so no amount of Rust-side care helps.
61 ///
62 /// So size it up front rather than guessing. [`Self::sized_for`] does the
63 /// arithmetic; [`Self::used_mem`] and [`Self::mem_size`] let you check
64 /// headroom while building a graph.
65 ///
66 /// # Panics
67 ///
68 /// Panics if ggml returns a null pointer.
69 #[must_use]
70 pub fn new(mem_size: usize, no_alloc: bool) -> Self {
71 let params = llama_cpp_sys_4::ggml_init_params {
72 mem_size,
73 mem_buffer: std::ptr::null_mut(),
74 no_alloc,
75 };
76 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
77 // the library owns whatever it returns.
78 let ctx = unsafe { llama_cpp_sys_4::ggml_init(params) };
79 Self {
80 ctx: NonNull::new(ctx).expect("ggml_init returned null"),
81 }
82 }
83
84 /// Create a context sized for `n_tensors` tensors and `n_graphs` graphs.
85 ///
86 /// Turns "pick a number and hope" into arithmetic: the pool holds metadata,
87 /// whose per-item cost ggml reports through [`tensor_overhead`] and
88 /// [`graph_overhead`]. A little slack is added for ggml's own bookkeeping.
89 ///
90 /// Views, reshapes and every intermediate an operation produces are tensors
91 /// too — count graph *nodes*, not just the tensors you name.
92 ///
93 /// ```
94 /// # use llama_cpp_4::ggml::GgmlContext;
95 /// // Room for 64 tensors and one graph.
96 /// let ctx = GgmlContext::sized_for(64, 1, true);
97 /// assert!(ctx.mem_size() >= 64 * llama_cpp_4::ggml::tensor_overhead());
98 /// ```
99 ///
100 /// # Panics
101 ///
102 /// Panics if ggml returns a null pointer.
103 #[must_use]
104 pub fn sized_for(n_tensors: usize, n_graphs: usize, no_alloc: bool) -> Self {
105 // The +1 tensor and the 1 KiB tail cover ggml's per-object headers and
106 // alignment padding, which are not part of the published overheads.
107 let mem_size = (n_tensors + 1) * tensor_overhead() + n_graphs * graph_overhead() + 1024;
108 Self::new(mem_size, no_alloc)
109 }
110
111 /// Bytes of the pool used so far.
112 ///
113 /// Compare with [`Self::mem_size`] while building a graph to see whether it
114 /// will fit, rather than finding out by aborting.
115 #[must_use]
116 pub fn used_mem(&self) -> usize {
117 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
118 // arguments are scalars the library validates itself.
119 unsafe { llama_cpp_sys_4::ggml_used_mem(self.ctx.as_ptr()) }
120 }
121
122 /// Total size of the pool, as passed to [`Self::new`].
123 #[must_use]
124 pub fn mem_size(&self) -> usize {
125 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
126 // arguments are scalars the library validates itself.
127 unsafe { llama_cpp_sys_4::ggml_get_mem_size(self.ctx.as_ptr()) }
128 }
129
130 /// Bytes still available in the pool.
131 #[must_use]
132 pub fn free_mem(&self) -> usize {
133 self.mem_size().saturating_sub(self.used_mem())
134 }
135
136 /// Get the raw context pointer.
137 #[must_use]
138 pub fn as_ptr(&self) -> *mut llama_cpp_sys_4::ggml_context {
139 self.ctx.as_ptr()
140 }
141
142 // ── Tensor creation ──────────────────────────────────────
143
144 /// Create a 1D tensor.
145 ///
146 /// # Panics
147 ///
148 /// Panics if ggml returns null, which in a release build means the
149 /// context's memory pool is exhausted. A debug build of ggml aborts
150 /// before returning — see [`GgmlContext::new`] for how to size the pool.
151 #[must_use]
152 pub fn new_tensor_1d(&self, typ: ggml_type, ne0: i64) -> GgmlTensor {
153 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
154 // arguments are scalars the library validates itself.
155 let t = unsafe { llama_cpp_sys_4::ggml_new_tensor_1d(self.ctx.as_ptr(), typ, ne0) };
156 GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_1d returned null"))
157 }
158
159 /// Create a 2D tensor.
160 ///
161 /// # Panics
162 ///
163 /// Panics if ggml returns null, which in a release build means the
164 /// context's memory pool is exhausted. A debug build of ggml aborts
165 /// before returning — see [`GgmlContext::new`] for how to size the pool.
166 #[must_use]
167 pub fn new_tensor_2d(&self, typ: ggml_type, ne0: i64, ne1: i64) -> GgmlTensor {
168 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
169 // arguments are scalars the library validates itself.
170 let t = unsafe { llama_cpp_sys_4::ggml_new_tensor_2d(self.ctx.as_ptr(), typ, ne0, ne1) };
171 GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_2d returned null"))
172 }
173
174 /// Create a 3D tensor.
175 ///
176 /// # Panics
177 ///
178 /// Panics if ggml returns null, which in a release build means the
179 /// context's memory pool is exhausted. A debug build of ggml aborts
180 /// before returning — see [`GgmlContext::new`] for how to size the pool.
181 #[must_use]
182 pub fn new_tensor_3d(&self, typ: ggml_type, ne0: i64, ne1: i64, ne2: i64) -> GgmlTensor {
183 let t =
184 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
185 // arguments are scalars the library validates itself.
186 unsafe { llama_cpp_sys_4::ggml_new_tensor_3d(self.ctx.as_ptr(), typ, ne0, ne1, ne2) };
187 GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_3d returned null"))
188 }
189
190 /// Create a 4D tensor.
191 ///
192 /// # Panics
193 ///
194 /// Panics if ggml returns null, which in a release build means the
195 /// context's memory pool is exhausted. A debug build of ggml aborts
196 /// before returning — see [`GgmlContext::new`] for how to size the pool.
197 #[must_use]
198 pub fn new_tensor_4d(
199 &self,
200 typ: ggml_type,
201 ne0: i64,
202 ne1: i64,
203 ne2: i64,
204 ne3: i64,
205 ) -> GgmlTensor {
206 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
207 // arguments are scalars the library validates itself.
208 let t = unsafe {
209 llama_cpp_sys_4::ggml_new_tensor_4d(self.ctx.as_ptr(), typ, ne0, ne1, ne2, ne3)
210 };
211 GgmlTensor(NonNull::new(t).expect("ggml_new_tensor_4d returned null"))
212 }
213
214 /// Create a tensor with the same shape and type as another.
215 ///
216 /// # Panics
217 ///
218 /// Panics if ggml returns null, which in a release build means the
219 /// context's memory pool is exhausted. A debug build of ggml aborts
220 /// before returning — see [`GgmlContext::new`] for how to size the pool.
221 #[must_use]
222 pub fn dup_tensor(&self, src: &GgmlTensor) -> GgmlTensor {
223 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
224 // arguments are scalars the library validates itself.
225 let t = unsafe { llama_cpp_sys_4::ggml_dup_tensor(self.ctx.as_ptr(), src.0.as_ptr()) };
226 GgmlTensor(NonNull::new(t).expect("ggml_dup_tensor returned null"))
227 }
228
229 /// Create a new tensor with arbitrary dimensions.
230 ///
231 /// # Panics
232 ///
233 /// Panics if ggml returns null, which in a release build means the
234 /// context's memory pool is exhausted. A debug build of ggml aborts
235 /// before returning — see [`GgmlContext::new`] for how to size the pool.
236 #[must_use]
237 pub fn new_tensor(&self, typ: ggml_type, ne: &[i64]) -> GgmlTensor {
238 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
239 // arguments are scalars the library validates itself.
240 let t = unsafe {
241 llama_cpp_sys_4::ggml_new_tensor(
242 self.ctx.as_ptr(),
243 typ,
244 // ggml caps dimensions at GGML_MAX_DIMS (4); anything longer is
245 // rejected by ggml itself, so this cannot truncate meaningfully.
246 i32::try_from(ne.len()).unwrap_or(i32::MAX),
247 ne.as_ptr(),
248 )
249 };
250 GgmlTensor(NonNull::new(t).expect("ggml_new_tensor returned null"))
251 }
252
253 // ── Tensor operations (build graph nodes) ────────────────
254
255 /// Element-wise addition: `a + b`
256 ///
257 /// # Panics
258 ///
259 /// Panics if ggml returns null, which in a release build means the
260 /// context's memory pool is exhausted. A debug build of ggml aborts
261 /// before returning — see [`GgmlContext::new`] for how to size the pool.
262 #[must_use]
263 pub fn add(&self, a: &GgmlTensor, b: &GgmlTensor) -> GgmlTensor {
264 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
265 // arguments are scalars the library validates itself.
266 let t = unsafe { llama_cpp_sys_4::ggml_add(self.ctx.as_ptr(), a.0.as_ptr(), b.0.as_ptr()) };
267 GgmlTensor(NonNull::new(t).expect("ggml_add returned null"))
268 }
269
270 /// Matrix multiplication: `a @ b`
271 ///
272 /// # Panics
273 ///
274 /// Panics if ggml returns null, which in a release build means the
275 /// context's memory pool is exhausted. A debug build of ggml aborts
276 /// before returning — see [`GgmlContext::new`] for how to size the pool.
277 #[must_use]
278 pub fn mul_mat(&self, a: &GgmlTensor, b: &GgmlTensor) -> GgmlTensor {
279 let t =
280 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
281 // arguments are scalars the library validates itself.
282 unsafe { llama_cpp_sys_4::ggml_mul_mat(self.ctx.as_ptr(), a.0.as_ptr(), b.0.as_ptr()) };
283 GgmlTensor(NonNull::new(t).expect("ggml_mul_mat returned null"))
284 }
285
286 /// Scale tensor: `a * s`
287 ///
288 /// # Panics
289 ///
290 /// Panics if ggml returns null, which in a release build means the
291 /// context's memory pool is exhausted. A debug build of ggml aborts
292 /// before returning — see [`GgmlContext::new`] for how to size the pool.
293 #[must_use]
294 pub fn scale(&self, a: &GgmlTensor, s: f32) -> GgmlTensor {
295 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
296 // arguments are scalars the library validates itself.
297 let t = unsafe { llama_cpp_sys_4::ggml_scale(self.ctx.as_ptr(), a.0.as_ptr(), s) };
298 GgmlTensor(NonNull::new(t).expect("ggml_scale returned null"))
299 }
300
301 /// Cast tensor to a different type.
302 ///
303 /// # Panics
304 ///
305 /// Panics if ggml returns null, which in a release build means the
306 /// context's memory pool is exhausted. A debug build of ggml aborts
307 /// before returning — see [`GgmlContext::new`] for how to size the pool.
308 #[must_use]
309 pub fn cast(&self, a: &GgmlTensor, typ: ggml_type) -> GgmlTensor {
310 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
311 // arguments are scalars the library validates itself.
312 let t = unsafe { llama_cpp_sys_4::ggml_cast(self.ctx.as_ptr(), a.0.as_ptr(), typ) };
313 GgmlTensor(NonNull::new(t).expect("ggml_cast returned null"))
314 }
315
316 /// Make tensor contiguous in memory.
317 ///
318 /// # Panics
319 ///
320 /// Panics if ggml returns null, which in a release build means the
321 /// context's memory pool is exhausted. A debug build of ggml aborts
322 /// before returning — see [`GgmlContext::new`] for how to size the pool.
323 #[must_use]
324 pub fn cont(&self, a: &GgmlTensor) -> GgmlTensor {
325 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
326 // arguments are scalars the library validates itself.
327 let t = unsafe { llama_cpp_sys_4::ggml_cont(self.ctx.as_ptr(), a.0.as_ptr()) };
328 GgmlTensor(NonNull::new(t).expect("ggml_cont returned null"))
329 }
330
331 /// Transpose a tensor.
332 ///
333 /// # Panics
334 ///
335 /// Panics if ggml returns null, which in a release build means the
336 /// context's memory pool is exhausted. A debug build of ggml aborts
337 /// before returning — see [`GgmlContext::new`] for how to size the pool.
338 #[must_use]
339 pub fn transpose(&self, a: &GgmlTensor) -> GgmlTensor {
340 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
341 // arguments are scalars the library validates itself.
342 let t = unsafe { llama_cpp_sys_4::ggml_transpose(self.ctx.as_ptr(), a.0.as_ptr()) };
343 GgmlTensor(NonNull::new(t).expect("ggml_transpose returned null"))
344 }
345
346 /// Reshape to 1D.
347 ///
348 /// # Panics
349 ///
350 /// Panics if ggml returns null, which in a release build means the
351 /// context's memory pool is exhausted. A debug build of ggml aborts
352 /// before returning — see [`GgmlContext::new`] for how to size the pool.
353 #[must_use]
354 pub fn reshape_1d(&self, a: &GgmlTensor, ne0: i64) -> GgmlTensor {
355 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
356 // arguments are scalars the library validates itself.
357 let t = unsafe { llama_cpp_sys_4::ggml_reshape_1d(self.ctx.as_ptr(), a.0.as_ptr(), ne0) };
358 GgmlTensor(NonNull::new(t).expect("ggml_reshape_1d returned null"))
359 }
360
361 /// Reshape to 2D.
362 ///
363 /// # Panics
364 ///
365 /// Panics if ggml returns null, which in a release build means the
366 /// context's memory pool is exhausted. A debug build of ggml aborts
367 /// before returning — see [`GgmlContext::new`] for how to size the pool.
368 #[must_use]
369 pub fn reshape_2d(&self, a: &GgmlTensor, ne0: i64, ne1: i64) -> GgmlTensor {
370 let t =
371 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
372 // arguments are scalars the library validates itself.
373 unsafe { llama_cpp_sys_4::ggml_reshape_2d(self.ctx.as_ptr(), a.0.as_ptr(), ne0, ne1) };
374 GgmlTensor(NonNull::new(t).expect("ggml_reshape_2d returned null"))
375 }
376
377 /// Create a 1D view of a tensor.
378 ///
379 /// # Panics
380 ///
381 /// Panics if ggml returns null, which in a release build means the
382 /// context's memory pool is exhausted. A debug build of ggml aborts
383 /// before returning — see [`GgmlContext::new`] for how to size the pool.
384 #[must_use]
385 pub fn view_1d(&self, a: &GgmlTensor, ne0: i64, offset: usize) -> GgmlTensor {
386 let t =
387 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
388 // arguments are scalars the library validates itself.
389 unsafe { llama_cpp_sys_4::ggml_view_1d(self.ctx.as_ptr(), a.0.as_ptr(), ne0, offset) };
390 GgmlTensor(NonNull::new(t).expect("ggml_view_1d returned null"))
391 }
392
393 // ── Graph creation ───────────────────────────────────────
394
395 /// Create a new computation graph.
396 ///
397 /// # Panics
398 ///
399 /// Panics if ggml returns null, which in a release build means the
400 /// context's memory pool is exhausted. A debug build of ggml aborts
401 /// before returning — see [`GgmlContext::new`] for how to size the pool.
402 #[must_use]
403 pub fn new_graph(&self) -> GgmlGraph {
404 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
405 // arguments are scalars the library validates itself.
406 let g = unsafe { llama_cpp_sys_4::ggml_new_graph(self.ctx.as_ptr()) };
407 GgmlGraph(NonNull::new(g).expect("ggml_new_graph returned null"))
408 }
409
410 // ── Tensor iteration ─────────────────────────────────────
411
412 /// Get the first tensor in this context.
413 #[must_use]
414 pub fn first_tensor(&self) -> Option<GgmlTensor> {
415 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
416 // arguments are scalars the library validates itself.
417 let t = unsafe { llama_cpp_sys_4::ggml_get_first_tensor(self.ctx.as_ptr()) };
418 NonNull::new(t).map(GgmlTensor)
419 }
420
421 /// Get the next tensor after `tensor` in this context.
422 #[must_use]
423 pub fn next_tensor(&self, tensor: &GgmlTensor) -> Option<GgmlTensor> {
424 let t =
425 // SAFETY: the handle is non-null and kept alive by this borrow, and the remaining
426 // arguments are scalars the library validates itself.
427 unsafe { llama_cpp_sys_4::ggml_get_next_tensor(self.ctx.as_ptr(), tensor.0.as_ptr()) };
428 NonNull::new(t).map(GgmlTensor)
429 }
430}
431
432impl Drop for GgmlContext {
433 fn drop(&mut self) {
434 // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
435 // afterwards.
436 unsafe { llama_cpp_sys_4::ggml_free(self.ctx.as_ptr()) }
437 }
438}
439
440// ── Tensor ──────────────────────────────────────────────────
441
442/// A wrapper around a `ggml_tensor` pointer.
443///
444/// Tensors are owned by their `GgmlContext` and must not outlive it.
445/// This wrapper does NOT free the tensor on drop.
446#[derive(Clone, Copy)]
447pub struct GgmlTensor(pub(crate) NonNull<llama_cpp_sys_4::ggml_tensor>);
448
449impl GgmlTensor {
450 /// Get the raw tensor pointer.
451 #[must_use]
452 pub fn as_ptr(&self) -> *mut llama_cpp_sys_4::ggml_tensor {
453 self.0.as_ptr()
454 }
455
456 /// Set the tensor's name.
457 ///
458 /// # Panics
459 ///
460 /// Panics if ggml returns null, which in a release build means the
461 /// context's memory pool is exhausted. A debug build of ggml aborts
462 /// before returning — see [`GgmlContext::new`] for how to size the pool.
463 pub fn set_name(&self, name: &str) {
464 let c_name = std::ffi::CString::new(name).expect("name contains null bytes");
465 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
466 // the library owns whatever it returns.
467 unsafe { llama_cpp_sys_4::ggml_set_name(self.0.as_ptr(), c_name.as_ptr()) };
468 }
469
470 /// Get the number of elements.
471 #[must_use]
472 pub fn nelements(&self) -> i64 {
473 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
474 // the library owns whatever it returns.
475 unsafe { llama_cpp_sys_4::ggml_nelements(self.0.as_ptr()) }
476 }
477
478 /// Get the total size in bytes.
479 #[must_use]
480 pub fn nbytes(&self) -> usize {
481 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
482 // the library owns whatever it returns.
483 unsafe { llama_cpp_sys_4::ggml_nbytes(self.0.as_ptr()) }
484 }
485
486 /// Get the element size in bytes.
487 #[must_use]
488 pub fn element_size(&self) -> usize {
489 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
490 // the library owns whatever it returns.
491 unsafe { llama_cpp_sys_4::ggml_element_size(self.0.as_ptr()) }
492 }
493
494 /// Get the tensor type.
495 #[must_use]
496 pub fn typ(&self) -> ggml_type {
497 // SAFETY: `GgmlTensor` wraps a non-null `ggml_tensor` owned by the context that
498 // created it, and the type's docs require the tensor not to outlive that
499 // context. Reading a field is a plain load from that live allocation.
500 unsafe { (*self.0.as_ptr()).type_ }
501 }
502
503 /// Get the tensor dimensions (ne).
504 #[must_use]
505 pub fn ne(&self) -> [i64; 4] {
506 // SAFETY: `GgmlTensor` wraps a non-null `ggml_tensor` owned by the context that
507 // created it, and the type's docs require the tensor not to outlive that
508 // context. Reading a field is a plain load from that live allocation.
509 unsafe { (*self.0.as_ptr()).ne }
510 }
511
512 /// Get the tensor name.
513 #[must_use]
514 pub fn name(&self) -> &str {
515 // SAFETY: the pointer is non-null and points at a NUL-terminated string the
516 // library owns, which outlives the borrow taken here.
517 unsafe {
518 let ptr = (*self.0.as_ptr()).name.as_ptr();
519 CStr::from_ptr(ptr).to_str().unwrap_or("")
520 }
521 }
522}
523
524impl std::fmt::Debug for GgmlTensor {
525 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
526 let ne = self.ne();
527 write!(
528 f,
529 "GgmlTensor({:?}, [{}, {}, {}, {}], {} bytes)",
530 self.name(),
531 ne[0],
532 ne[1],
533 ne[2],
534 ne[3],
535 self.nbytes()
536 )
537 }
538}
539
540// ── Graph ───────────────────────────────────────────────────
541
542/// A wrapper around `ggml_cgraph`.
543///
544/// Graphs are owned by their `GgmlContext` and must not outlive it.
545pub struct GgmlGraph(NonNull<llama_cpp_sys_4::ggml_cgraph>);
546
547impl std::fmt::Debug for GgmlGraph {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 f.debug_struct("GgmlGraph").finish()
550 }
551}
552
553impl GgmlGraph {
554 /// Get the raw graph pointer.
555 pub fn as_ptr(&mut self) -> *mut llama_cpp_sys_4::ggml_cgraph {
556 self.0.as_ptr()
557 }
558
559 /// Add a tensor and its dependencies to the forward computation graph.
560 pub fn build_forward(&mut self, tensor: &GgmlTensor) {
561 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
562 // the library owns whatever it returns.
563 unsafe { llama_cpp_sys_4::ggml_build_forward_expand(self.0.as_ptr(), tensor.0.as_ptr()) }
564 }
565
566 /// Get a node (output tensor) by index. Use -1 for the last node.
567 ///
568 /// # Panics
569 ///
570 /// Panics if ggml returns null, which in a release build means the
571 /// context's memory pool is exhausted. A debug build of ggml aborts
572 /// before returning — see [`GgmlContext::new`] for how to size the pool.
573 #[must_use]
574 pub fn node(&mut self, i: i32) -> GgmlTensor {
575 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
576 // the library owns whatever it returns.
577 let t = unsafe { llama_cpp_sys_4::ggml_graph_node(self.0.as_ptr(), i) };
578 GgmlTensor(NonNull::new(t).expect("graph_node returned null"))
579 }
580}
581
582// ── Backend ─────────────────────────────────────────────────
583
584/// A safe wrapper around `ggml_backend`.
585#[derive(Debug)]
586pub struct GgmlBackend {
587 backend: llama_cpp_sys_4::ggml_backend_t,
588}
589
590impl GgmlBackend {
591 /// Create a CPU backend.
592 ///
593 /// # Panics
594 ///
595 /// Panics if ggml cannot initialise the CPU backend, which would mean the
596 /// build has no CPU backend compiled in.
597 #[must_use]
598 pub fn cpu() -> Self {
599 // SAFETY: no borrowed pointer is involved beyond arguments alive for the call;
600 // the returned handle is checked for null before being wrapped.
601 let backend = unsafe { llama_cpp_sys_4::ggml_backend_cpu_init() };
602 assert!(!backend.is_null(), "ggml_backend_cpu_init returned null");
603 Self { backend }
604 }
605
606 /// Set the number of threads for the CPU backend.
607 pub fn set_n_threads(&self, n_threads: i32) {
608 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
609 // the library owns whatever it returns.
610 unsafe { llama_cpp_sys_4::ggml_backend_cpu_set_n_threads(self.backend, n_threads) }
611 }
612
613 /// Allocate all tensors in a context on this backend.
614 ///
615 /// Returns the buffer handle which must be kept alive.
616 #[must_use]
617 pub fn alloc_ctx_tensors(
618 &self,
619 ctx: &GgmlContext,
620 ) -> *mut llama_cpp_sys_4::ggml_backend_buffer {
621 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
622 // the library owns whatever it returns.
623 unsafe { llama_cpp_sys_4::ggml_backend_alloc_ctx_tensors(ctx.as_ptr(), self.backend) }
624 }
625
626 /// Compute a graph.
627 pub fn graph_compute(&self, graph: &mut GgmlGraph) {
628 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
629 // the library owns whatever it returns.
630 unsafe { llama_cpp_sys_4::ggml_backend_graph_compute(self.backend, graph.as_ptr()) };
631 }
632
633 /// Get the default buffer type for this backend.
634 #[must_use]
635 pub fn default_buffer_type(&self) -> llama_cpp_sys_4::ggml_backend_buffer_type_t {
636 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
637 // the library owns whatever it returns.
638 unsafe { llama_cpp_sys_4::ggml_backend_get_default_buffer_type(self.backend) }
639 }
640
641 /// Get the raw backend pointer.
642 #[must_use]
643 pub fn as_ptr(&self) -> llama_cpp_sys_4::ggml_backend_t {
644 self.backend
645 }
646}
647
648impl Drop for GgmlBackend {
649 fn drop(&mut self) {
650 // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
651 // afterwards.
652 unsafe { llama_cpp_sys_4::ggml_backend_free(self.backend) }
653 }
654}
655
656// ── Graph allocator ─────────────────────────────────────────
657
658/// A safe wrapper around `ggml_gallocr`.
659#[derive(Debug)]
660pub struct GgmlAllocr {
661 alloc: llama_cpp_sys_4::ggml_gallocr_t,
662}
663
664impl GgmlAllocr {
665 /// Create a new graph allocator for the given backend.
666 ///
667 /// # Panics
668 ///
669 /// Panics if ggml cannot allocate the graph allocator.
670 #[must_use]
671 pub fn new(backend: &GgmlBackend) -> Self {
672 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
673 // the library owns whatever it returns.
674 let alloc = unsafe { llama_cpp_sys_4::ggml_gallocr_new(backend.default_buffer_type()) };
675 assert!(!alloc.is_null(), "ggml_gallocr_new returned null");
676 Self { alloc }
677 }
678
679 /// Allocate all tensors in a graph.
680 pub fn alloc_graph(&self, graph: &mut GgmlGraph) -> bool {
681 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
682 // the library owns whatever it returns.
683 unsafe { llama_cpp_sys_4::ggml_gallocr_alloc_graph(self.alloc, graph.as_ptr()) }
684 }
685}
686
687impl Drop for GgmlAllocr {
688 fn drop(&mut self) {
689 // SAFETY: this runs once from `Drop` on a non-null handle, and nothing reads it
690 // afterwards.
691 unsafe { llama_cpp_sys_4::ggml_gallocr_free(self.alloc) }
692 }
693}
694
695// ── Utility functions ───────────────────────────────────────
696
697/// Set tensor data from a byte slice.
698///
699/// # Safety
700///
701/// The tensor must be allocated and the data must be the correct size.
702pub unsafe fn tensor_set(tensor: &GgmlTensor, data: &[u8]) {
703 llama_cpp_sys_4::ggml_backend_tensor_set(
704 tensor.0.as_ptr(),
705 data.as_ptr().cast(),
706 0,
707 data.len(),
708 );
709}
710
711/// Get tensor data into a byte slice.
712///
713/// # Safety
714///
715/// The tensor must be allocated and the buffer must be large enough.
716pub unsafe fn tensor_get(tensor: &GgmlTensor, data: &mut [u8]) {
717 llama_cpp_sys_4::ggml_backend_tensor_get(
718 tensor.0.as_ptr(),
719 data.as_mut_ptr().cast(),
720 0,
721 data.len(),
722 );
723}
724
725/// Free a backend buffer.
726///
727/// # Safety
728///
729/// The buffer must be valid and not already freed.
730pub unsafe fn buffer_free(buffer: *mut llama_cpp_sys_4::ggml_backend_buffer) {
731 llama_cpp_sys_4::ggml_backend_buffer_free(buffer);
732}
733
734/// Get the overhead in bytes for tensor metadata.
735#[must_use]
736pub fn tensor_overhead() -> usize {
737 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
738 // the library owns whatever it returns.
739 unsafe { llama_cpp_sys_4::ggml_tensor_overhead() }
740}
741
742/// Get the overhead in bytes for a computation graph.
743#[must_use]
744pub fn graph_overhead() -> usize {
745 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
746 // the library owns whatever it returns.
747 unsafe { llama_cpp_sys_4::ggml_graph_overhead() }
748}
749
750/// Check if a type is quantized.
751#[must_use]
752pub fn is_quantized(typ: ggml_type) -> bool {
753 // SAFETY: the arguments are either scalars or pointers that outlive the call, and
754 // the library owns whatever it returns.
755 unsafe { llama_cpp_sys_4::ggml_is_quantized(typ) }
756}
757
758/// Get the name of a ggml type.
759#[must_use]
760pub fn type_name(typ: ggml_type) -> &'static str {
761 // SAFETY: `ggml_type_name` takes a plain enum and returns a pointer to a
762 // static NUL-terminated string compiled into ggml, so it outlives any
763 // borrow. The null case is handled below rather than assumed away.
764 unsafe {
765 let ptr = llama_cpp_sys_4::ggml_type_name(typ);
766 if ptr.is_null() {
767 "unknown"
768 } else {
769 CStr::from_ptr(ptr).to_str().unwrap_or("unknown")
770 }
771 }
772}