LlamaCppEx.Server (LlamaCppEx v0.8.33)

Copy Markdown View Source

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 (0 when cache_prompt: false).

Metadata:

  • :server - PID of the server process.
  • :seq_id - Slot sequence ID.
  • :mode - :generate or :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 - :generate or :stream.
  • :stop_reason - :eog (end-of-generation token sampled), :max_tokens (request max_tokens reached), 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 - :generate or :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

cancel(server, ref)

@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.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

complete_tokens(server, token_ids, opts \\ [])

@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.

generate(server, prompt, opts \\ [])

@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 to 256.
  • :timeout - Call timeout in ms. Defaults to 60_000.
  • :cache_prompt - Reuse/retain this request's KV prefix on the slot. Defaults to the server-level :cache_prompt setting.
  • :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.

generate_tokens(server, token_ids, opts \\ [])

@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 to 256.
  • :timeout - Call timeout in ms. Defaults to 60_000.

get_model(server)

@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.

get_stats(server)

@spec get_stats(GenServer.server()) :: map()

Returns a snapshot of the server's current state.

start_link(opts)

@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 to 99.
  • :n_ctx - Total context size (shared across slots). Defaults to 8192.
  • :n_parallel - Number of concurrent slots. Defaults to 4.
  • :n_batch - Max tokens per forward pass. Defaults to min(n_ctx, 2048). Bounds worst-case tick latency: one huge prompt can occupy at most n_batch tokens of a tick, so decode tokens of other slots are never delayed by more than one n_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 to 512.
  • :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. 0 for unlimited. Defaults to 0; 2 * n_parallel is a reasonable bound for latency-sensitive deployments.
  • :cache_prompt - Retain KV cache between requests on the same slot for prefix reuse. Defaults to true (matching llama-server). Overridable per request via the :cache_prompt option on generate/3 and friends.
  • :kv_unified - Share one KV buffer across all slots instead of splitting n_ctx evenly (n_ctx/n_parallel each). 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 shared n_ctx budget, and idle slots' caches are purged under KV pressure. Defaults to true. Set false for 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 to 0 (off).
  • :batch_strategy - Batch building strategy module. Defaults to LlamaCppEx.Server.Strategy.DecodeMaximal. See LlamaCppEx.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.

stream(server, prompt, opts \\ [])

@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 to 256.
  • :timeout - Per-token timeout. Defaults to 30_000.

Also accepts the per-request options documented on generate/3.

stream_tokens(server, token_ids, opts \\ [])

@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 to 256.
  • :timeout - Per-token timeout. Defaults to 30_000.