llama-cpp-4
Safe Rust bindings to llama.cpp. Tracks upstream closely — designed to stay current rather than provide a thick abstraction layer.
llama.cpp version: 5c8854fd88 (b11557, incl. v0.6.0) · Crate version: 0.8.0
Add to your project
[]
= "0.8.0"
# GPU support (pick one or more)
# llama-cpp-4 = { version = "0.8.0", features = ["cuda"] }
# llama-cpp-4 = { version = "0.8.0", features = ["metal"] }
# llama-cpp-4 = { version = "0.8.0", features = ["vulkan"] }
Prelude
Import the common inference types in one line:
use *;
The prelude re-exports backend, model, context, batching, sampling, errors, fit/memory
helpers, speculative-decoding types, and quantization symbols. The same core types are
also at the crate root (llama_cpp_4::LlamaModel, etc.) if you prefer explicit paths.
| Category | Types |
|---|---|
| Inference | LlamaBackend, LlamaModel, LlamaContext, LlamaBatch, LlamaSampler, LlamaSamplerParams, LlamaToken, LlamaTokenDataArray |
| Tokenising | AddBos, Special |
| Chat | LlamaChatMessage |
| Model introspection | LlamaBackendDevice, LlamaBackendDeviceType |
| Context tuning | LlamaFlashAttnType, LlamaContextType, LlamaAttentionType, RopeScalingType, ParamsCloneError |
| KV overrides | ParamOverrideValue |
| Memory / fit | get_device_memory_data, fit_params, FitParams, MemoryBreakdownEntry |
| Tensor capture | TensorCapture, CapturedTensor |
| Speculative | MtpSession, Eagle3Session (+ configs) |
| Quantization | QuantizeParams, TensorTypeOverride, GgmlType, LlamaFtype, model_quantize |
See prelude on docs.rs for runnable examples (generation, chat, embeddings, memory estimation).
Feature flags
| Feature | Default | Description |
|---|---|---|
openmp |
✅ | Multi-threaded CPU inference via OpenMP |
mtmd |
✅ | Multimodal (vision / audio) via libmtmd |
dynamic-link |
✅ | Link llama.cpp as a shared library |
cuda |
NVIDIA GPU via CUDA | |
metal |
Apple GPU via Metal | |
vulkan |
Cross-platform GPU via Vulkan | |
native |
CPU auto-tune for current arch (AVX2, NEON, …) | |
rpc |
Remote compute backend |
API overview
All snippets below assume use llama_cpp_4::prelude::*;.
Backend
// Initialise once per process. Configures hardware backends (CUDA, Metal, …).
let backend = init?;
Loading a model
use pin;
let mut params = default.with_n_gpu_layers;
let params = pin!;
let model = load_from_file?;
println!;
println!;
println!;
// Multi-GPU / MoE introspection
println!;
println!;
for dev in model.devices
For models that ship MTP (multi-token prediction) weights — e.g. DeepSeek V4 — load the MTP layers to drive MTP / DSpark speculative decoding:
let params = default.with_load_mtp;
Once loaded, capture and restore the speculative state through the
speculative module.
Memory estimation (before full load)
use Path;
let report = get_device_memory_data?;
for entry in &report.entries
Auto-fit parameters to device memory
use ;
let backend = init?;
let fitted = fit_params?;
let model = load_from_file?;
let ctx = model.new_context?;
Tokenising
let tokens = model.str_to_token?;
let text = model.token_to_str?;
let bytes = model.token_to_bytes?;
Chat template
let messages = vec!;
let prompt = model.apply_chat_template?;
Tool calling and structured output
apply_chat_template renders a prompt. The chat module goes further: it
returns the prompt plus the grammar and parser that make tool calls reliable.
let templates = from_model?;
let applied = templates.apply?;
// `grammar_lazy` is the important bit: constrain from token zero and a thinking
// model can never open its <think> block. A lazy grammar waits for a trigger.
if applied.grammar_lazy
// After generating, parse with the template's own parser rather than scraping:
let message_json = applied.parse?; // {"role","content","tool_calls",…}
Constrain output to a JSON Schema — response_format: json_schema, enforced
rather than requested:
let gbnf = json_schema_to_grammar?;
let sampler = grammar;
Creating a context
use NonZeroU32;
let params = default
.with_n_ctx
.with_n_batch
.with_n_threads
.with_flash_attn_type;
let mut ctx = model.new_context?;
Batched decode (prefill + generation)
let mut batch = new;
for in tokens.iter.enumerate
ctx.decode?;
batch.clear;
batch.add?;
ctx.decode?;
Entry-wise batches (embeddings, M-RoPE, MTP state)
LlamaBatch is llama_batch: parallel arrays plus one flat embedding block for
the whole batch, so every entry has to be the same shape. LlamaBatchExt wraps
upstream's llama_batch_ext, where each entry owns its token id, positions,
input embedding and state embedding — the only way to mix embeddings with
tokens, to give an entry M-RoPE's [t, y, x, z] positions, or to attach the
target hidden state an MTP draft context reads.
let mut batch = new?;
// A token entry: one position, set explicitly (upstream leaves it undefined).
let entry = batch.add_token?;
batch.set_pos?;
batch.set_output?;
// An embedding entry, e.g. a vision encoder's output.
let entry = batch.add_embedding?;
batch.set_positions?; // or set_pos on a non-M-RoPE model
ctx.process?;
let logits = ctx.get_logits_ith;
For a plain token batch there is nothing to gain: LlamaBatch with
ctx.decode is the same work with less ceremony, and it is the path this
crate's decode lifecycle hooks cover. ctx.process returns
ProcessError::TensorTransactionsUnsupported on a context that owns tensor
transactions, because llama_process does not run those hooks.
Sampling
let sampler = chain_simple;
let token = sampler.sample;
if model.is_eog_token
let bytes = model.token_to_bytes?;
KV cache
ctx.clear_kv_cache_seq?; // clear sequence 0
ctx.clear_kv_cache; // clear all sequences
Embeddings
use NonZeroU32;
let params = default
.with_embeddings
.with_n_ctx;
let mut ctx = model.new_context?;
// ... fill batch, decode ...
let vec = ctx.embeddings_seq_ith?;
Runtime memory breakdown
for entry in ctx.memory_breakdown
Checked tensor capture (hidden states)
Attach owned, bounded exact selectors to copy per-layer outputs ("l_out-N")
or other named graph nodes during decode:
use *;
let selector = layer_output?;
let transactions = capture?;
let ctx_params =
default.with_tensor_transactions;
let mut ctx = model.new_context?;
// ... fill batch, decode ...
ctx.decode?;
if let Some = ctx
.tensor_transactions
.and_then
See also context::tensor_capture and
examples/eagle (EAGLE-3 uses specific anchor layers).
Checked speculative sessions
MtpSession and Eagle3Session exclusively borrow their target and draft
contexts and are neither Send nor Sync. Construction validates context
types, sequence capacity, recurrent-state capacity where required, hidden-row
widths, and EAGLE-3 extraction-layer metadata before entering the native shim.
Prompt copies, proposal bounds, and opaque continuation state are bounded.
The shim contains C++ exceptions at every MTP/EAGLE-3 lifecycle entry point. Begin, process, draft, accept, and state failures return typed Rust errors instead of unwinding across the C ABI. Exact continuation state can be read or restored only after every proposal has been resolved.
LoRA adapters
let mut adapter = model.lora_adapter_init?;
ctx.lora_adapter_set?;
ctx.lora_adapter_remove?;
For an adapter GGUF embedded at a byte offset inside a larger file — appended to an executable, packed into an archive — pass the offset instead; the path-based loader always reads from byte zero:
let mut adapter = model.lora_adapter_init_at_offset?;
Performance counters
let perf = ctx.timings;
println!;
ctx.perf_context_reset;
Full example: text generation
use *;
use NonZeroU32;
Safety
This crate wraps a C++ library via FFI. The safe API prevents most misuse, but some patterns (e.g. using a context after its model is dropped) can still cause UB. File an issue if you spot any.
One precondition is deliberately unchecked and worth knowing: LlamaBatch::add
writes as many sequence ids as you pass, while LlamaBatch::new(n_tokens, n_seq_max) sized each row to n_seq_max — so passing more ids than that writes
past the row. LlamaBatchExt checks its equivalents.
If you are contributing: clippy::undocumented_unsafe_blocks is being adopted
module by module, and a module that carries
#![deny(clippy::undocumented_unsafe_blocks)] must stay at full coverage. See
the note on the lint in the workspace Cargo.toml for which modules are done
and what a SAFETY: comment is expected to say;
llama-cpp-4/src/context/tensor_transaction.rs is the reference.
Examples in this repo
| Crate | Description |
|---|---|
simple |
Single-turn completion |
chat |
Interactive multi-turn REPL |
openai-server |
OpenAI-compatible HTTP API |
mtp |
MTP speculative decoding |
eagle |
EAGLE-3 speculative decoding |
incremental-chat |
Incremental prefill while typing |
fit-params |
Auto-fit n_ctx / GPU layers to device memory |
Requirements
- Rust 1.75+
clang(for bindgen at build time)- A C++17 compiler (GCC 9+, Clang 10+, MSVC 2019+)
- For CUDA: CUDA toolkit 11.8+
- For Metal: Xcode 14+
Testing
Unit tests run without a model (vocab-only fixtures from the build tree when available):
End-to-end integration tests load a real GGUF and exercise decode, generation, embeddings, memory breakdown, fit helpers, and tensor capture:
Or point at any local checkpoint:
LLAMA_TEST_MODEL=/path/to/model.gguf \
Use --test-threads=1 because llama_decode is not safe to exercise in parallel across
contexts in the same process.