GenServer for continuous batched multi-sequence inference.
Manages a shared model/context and serves multiple concurrent callers using a slot pool with continuous batching — one forward pass per tick with decode tokens and prefill chunks mixed in a single batch.
Example
{:ok, server} = LlamaCppEx.Server.start_link(
model_path: "model.gguf",
n_gpu_layers: -1,
n_parallel: 4,
n_ctx: 8192
)
# Sync generation
{:ok, text} = LlamaCppEx.Server.generate(server, "Once upon a time", max_tokens: 100)
# Streaming
LlamaCppEx.Server.stream(server, "Tell me a story", max_tokens: 200)
|> Enum.each(&IO.write/1)Telemetry
The server emits the following telemetry events:
[:llama_cpp_ex, :server, :tick]
Emitted after each batch forward pass.
Measurements:
:batch_size- Total tokens in the batch.:decode_tokens- Number of decode (generation) tokens.:prefill_tokens- Number of prefill (prompt) tokens.:active_slots- Slots currently prefilling or generating.:queue_depth- Requests waiting for a slot.:eval_ms- Forward pass wall time in milliseconds.
Metadata:
:server- PID of the server process.
[:llama_cpp_ex, :server, :request, :start]
Emitted when a slot is assigned to a request and prefill begins.
Measurements:
:prompt_tokens- Number of prompt tokens.:prefix_cache_tokens- Number of prompt tokens reused from the KV prefix cache (0whencache_prompt: false).
Metadata:
:server- PID of the server process.:seq_id- Slot sequence ID.:mode-:generateor:stream.
[:llama_cpp_ex, :server, :request, :done]
Emitted when a request (generate or stream) completes.
Measurements:
:prompt_tokens- Number of prompt tokens.:generated_tokens- Number of tokens generated.:duration_ms- Total request duration in milliseconds.:ttft_ms- Time to first token in milliseconds.:prompt_eval_rate- Prompt evaluation speed (tokens/sec).:generation_rate- Generation speed (tokens/sec).:prefix_cache_tokens- Number of prompt tokens skipped via prefix cache.:prefix_cache_ratio- Ratio of cached to total prompt tokens (0.0–1.0).
Metadata:
:server- PID of the server process.:seq_id- Slot sequence ID (integer).:mode-:generateor:stream.:stop_reason-:eog(end-of-generation token sampled),:max_tokens(requestmax_tokensreached), or:cancelled(consumer died or cancelled the request).
[:llama_cpp_ex, :server, :kv_pressure]
Emitted when a forward pass hit KV-cache pressure (llama_decode == 1) and
the server recovered by purging idle slots' cached prefixes and/or splitting
the batch.
Measurements:
:purged_slots- Number of idle slots whose cached KV was dropped.:batch_splits- Number of times the batch was halved to fit.
Metadata:
:server- PID of the server process.:purged_seq_ids- Sequence IDs whose caches were purged.
[:llama_cpp_ex, :server, :prefix_instability]
Emitted when a cache-eligible request matches only 10–50% of a slot's cached history — the signature of a chat template that rewrites earlier turns (e.g. stripping thinking blocks), silently defeating prefix caching.
Measurements:
:matched_tokens- Length of the common prefix actually reusable.:cached_tokens- Length of the cached history that was expected to match.
Metadata:
:server- PID of the server process.:seq_id- Slot sequence ID.
[:llama_cpp_ex, :server, :ram_cache]
Emitted on level-2 RAM prompt cache activity (see :prompt_cache_ram_mb).
Measurements:
:bytes- Size of the entry involved.:tokens- Cached prefix length of the entry involved.:total_bytes- Cache size after the operation.:entries- Entry count after the operation.
Metadata:
:server- PID of the server process.:op-:save,:restore, or:evict.
[:llama_cpp_ex, :server, :request, :exception]
Emitted when an inference error aborts an active request (e.g. the
underlying batch_eval returns an error). Measurement shape matches
:done so handlers can aggregate them together; :stop_reason is
:error and the failure reason is in :reason.
Metadata:
:server- PID of the server process.:seq_id- Slot sequence ID.:mode-:generateor:stream.:stop_reason-:error.:reason- The underlying failure term from the NIF.
Summary
Functions
Cancels an in-flight or queued stream request by its subscription reference.
Returns a specification to start this module under a supervisor.
Like generate_tokens/3, but returns completion metadata alongside the text.
Generates text synchronously. Blocks until generation is complete.
Generates text from pre-tokenized input. Blocks until generation is complete.
Returns the model struct for external tokenization.
Returns a snapshot of the server's current state.
Starts the server.
Returns a stream of generated text chunks.
Returns a stream of generated text chunks from pre-tokenized input.
Functions
@spec cancel(GenServer.server(), reference()) :: :ok
Cancels an in-flight or queued stream request by its subscription reference.
The slot stops being scheduled immediately and is freed for other requests
(its prefix cache is retained per the request's :cache_prompt). Consumer
death is detected automatically via monitors — explicit cancel is for
consumers that stop reading without exiting. Server.stream/3 and
stream_tokens/3 call this from their cleanup, so halting those streams
early (e.g. Enum.take/2) cancels generation instead of burning batch
budget to max_tokens.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec complete_tokens(GenServer.server(), [integer()], keyword()) :: {:ok, %{ text: String.t(), completion_tokens: non_neg_integer(), finish_reason: atom() }} | {:error, term()}
Like generate_tokens/3, but returns completion metadata alongside the text.
Returns {:ok, %{text: text, completion_tokens: n, finish_reason: reason}}
where reason is :eog or :max_tokens. Used by the OpenAI-shaped
LlamaCppEx.chat_completion/3 when routed through a server.
@spec generate(GenServer.server(), String.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
Generates text synchronously. Blocks until generation is complete.
Options
:max_tokens- Maximum tokens to generate. Defaults to256.:timeout- Call timeout in ms. Defaults to60_000.:cache_prompt- Reuse/retain this request's KV prefix on the slot. Defaults to the server-level:cache_promptsetting.:session- Any term identifying a conversation. Requests with the same session are routed to the same slot whenever it is free, keeping their cached prefix intact under concurrency.- Sampling options (
:temp,:top_k,:top_p,:min_p,:seed,:penalty_repeat,:penalty_freq,:penalty_present,:grammar,:grammar_root) - override the server-level defaults for this request.
@spec generate_tokens(GenServer.server(), [integer()], keyword()) :: {:ok, String.t()} | {:error, term()}
Generates text from pre-tokenized input. Blocks until generation is complete.
Use get_model/1 to obtain the model for tokenization outside the server.
Options
:max_tokens- Maximum tokens to generate. Defaults to256.:timeout- Call timeout in ms. Defaults to60_000.
@spec get_model(GenServer.server()) :: LlamaCppEx.Model.t()
Returns the model struct for external tokenization.
The model resource is reference-counted and thread-safe for read-only
operations like tokenization. Served from a :persistent_term cache — no
round-trip through the server's mailbox.
@spec get_stats(GenServer.server()) :: map()
Returns a snapshot of the server's current state.
@spec start_link(keyword()) :: GenServer.on_start()
Starts the server.
Options
:model_path(required) - Path to the GGUF model file.:n_gpu_layers- GPU layers. Defaults to99.:n_ctx- Total context size (shared across slots). Defaults to8192.:n_parallel- Number of concurrent slots. Defaults to4.:n_batch- Max tokens per forward pass. Defaults tomin(n_ctx, 2048). Bounds worst-case tick latency: one huge prompt can occupy at mostn_batchtokens of a tick, so decode tokens of other slots are never delayed by more than onen_batch-sized pass. Raise it for pure batch throughput (fewer, larger passes); lower it (or lower:chunk_size) for smoother streaming latency under mixed load.:chunk_size- Max prefill tokens per slot per tick. Defaults to512.:max_queue- Max queued requests waiting for a slot. When the bound is hit, calls return{:error, :queue_full}immediately and streams emit a single{:error, :queue_full}element — no silent queueing until the call timeout.0for unlimited. Defaults to0;2 * n_parallelis a reasonable bound for latency-sensitive deployments.:cache_prompt- Retain KV cache between requests on the same slot for prefix reuse. Defaults totrue(matching llama-server). Overridable per request via the:cache_promptoption ongenerate/3and friends.:kv_unified- Share one KV buffer across all slots instead of splittingn_ctxevenly (n_ctx/n_paralleleach). Enables cross-slot prefix sharing: a system prompt cached by any slot is adopted by every other slot via a metadata-only copy. Slots then compete for the sharedn_ctxbudget, and idle slots' caches are purged under KV pressure. Defaults totrue. Setfalsefor strictly isolated per-slot budgets.:prompt_cache_ram_mb- Byte budget (in MB) for the level-2 RAM prompt cache: when a slot's cached prefix is about to be destroyed it is serialized to RAM and can be restored later instead of re-prefilling. State blobs are KV-sized (up to hundreds of MB for long contexts) — entries larger than the budget are never stored, so a small budget degrades to "disabled" rather than OOM. Defaults to0(off).:batch_strategy- Batch building strategy module. Defaults toLlamaCppEx.Server.Strategy.DecodeMaximal. SeeLlamaCppEx.Server.BatchStrategy.- Sampling options:
:temp,:top_k,:top_p,:min_p,:seed,:penalty_repeat,:penalty_freq,:penalty_present,:grammar,:grammar_root. - GenServer options like
:name.
@spec stream(GenServer.server(), String.t(), keyword()) :: Enumerable.t()
Returns a stream of generated text chunks.
If the request is rejected (:queue_full) or fails mid-generation, the
stream emits a single {:error, reason} element and halts — consumers that
need to distinguish errors from text should match on it.
Options
:max_tokens- Maximum tokens to generate. Defaults to256.:timeout- Per-token timeout. Defaults to30_000.
Also accepts the per-request options documented on generate/3.
@spec stream_tokens(GenServer.server(), [integer()], keyword()) :: Enumerable.t()
Returns a stream of generated text chunks from pre-tokenized input.
Options
:max_tokens- Maximum tokens to generate. Defaults to256.:timeout- Per-token timeout. Defaults to30_000.