erllama (erllama v0.10.0)

View Source

Public API of erllama: load llama.cpp models as supervised OTP processes, run completions, stream tokens, and reuse the KV cache.

{ok, _} = application:ensure_all_started(erllama),
{ok, Model} = erllama:load_model(#{model_path => "/srv/models/tinyllama.gguf"}),
{ok, #{reply := Reply, finish_key := Key}} = erllama:complete(Model, <<"hello">>),
{ok, #{reply := Reply2}} =
    erllama:complete(Model, <<"hello world">>, #{parent_key => Key}),
ok = erllama:unload(Model).

Every function that addresses a model returns {ok, Result} or {error, Reason} with Reason :: error_reason(); an unknown or stopped model is {error, not_loaded}, never a crash. Option maps are validated: an unknown key is {error, {unknown_option, Key}}.

Models are dynamic children of erllama_model_sup. The id returned by load_model/1 (or supplied as model_id / to load_model/2) is the handle for every other call; a pid works too. The cache subsystem is erllama_cache; observability hooks are erllama_middleware.

Summary

Types

Handle of an attached LoRA adapter; treat as opaque.

Last admission summary from last_cache_hit/1.

How the last admission found its prefix in the cache.

SHA-256 cache key of a committed context (finish_key, parent_key).

A chat message. role is system | user | assistant | tool; content is a binary or a list of content-part maps in the OpenAI shape; assistant messages may carry tool_calls, tool results tool_call_id.

Options for chat/3: tools, tool_choice (auto | required | none), parallel_tool_calls, plus any request_opts() key.

Parser handle produced by chat_apply/3 for chat_parse/3; treat as opaque.

Result of chat/3.

A tool definition: name, description, JSON-schema parameters.

Result of complete/2,3.

Every {error, Reason} the API returns.

Config map for load_model/1,2.

A middleware: fun(Request, Next) -> Response; see erllama_middleware.

A model id (binary) or the model process pid.

The registered model id returned by load_model/1,2.

Snapshot returned by model_info/1 and list_models/0.

Assistant message parsed by chat_parse/3 / returned by chat/3.

Options for prefill_only/3: parent_key, session_id, on_full, prefix_checkpoint_len.

Result of prefill_only/2,3.

Per-request statistics (stats in results, {done, Stats} in streams).

Events delivered to the to process of stream/3 and continue/3 as {erllama, Ref, Event}.

Result of collect/2: the stream folded into a map.

A token id in the model vocabulary.

Functions

How many bytes of the detokenised PromptTokens are already cached for this model, across all tiers. {ok, 0} when nothing matches. Attached adapters are honoured through the effective fingerprint.

Cancel an in-flight streaming request. Idempotent. The caller still receives the terminal {erllama, Ref, {done, Stats}} with finish_reason => cancelled. The running decode is interrupted through the backend's abort callback; an interrupt that fires recreates the context in place, which also resets co-batched requests on other seqs. The persistent cache is untouched.

One chat turn: render Messages (and the tools in Opts) through the model's template, generate, and parse the output into a structured assistant message with content, reasoning and tool calls. Messages and tools are Erlang maps; see chat_message() and chat_tool().

Render the prompt and build the output parser for one request with llama.cpp's common_chat_templates_apply. Messages and Opts are those of chat/3 (only the chat keys of Opts are used). Returns the prompt bytes and the chat_params() to hand to chat_parse/3 for this request's output; tokenise the prompt with tokenize/3 and #{add_special => false, parse_special => true}. The parser is not reusable across requests.

Parse model output into a structured assistant message (content, reasoning, tool calls) with the parser from chat_apply/3. IsPartial = true accepts a streaming prefix.

Wait for a streaming request started with stream/3 or continue/3 and fold its events into a stream_result(). {error, timeout} after Timeout milliseconds without any event (the request is cancelled and its remaining events drained).

complete/3 with default options.

Run a completion and wait for the result.

Extend a pinned session by prefilling SuffixTokens on top of its live KV cells, skipping the prefix-equality check and any cache lookup. Use it when the chat template renders history-dependent prefixes that would defeat stream/3's sticky path.

Snapshot of the cache counters; see erllama_cache:get_counters/0 for the keys.

Detokenise token ids back to text.

Draft up to max next tokens after PrefixTokens and return their ids. Shorter lists (EOS, response_tokens) are valid. Built on stream/3; a 30 s silence cancels the request and returns {error, timeout}.

Embedding vector for a text or a token list. The model must be loaded with context_opts => #{embeddings => true}.

Embedding vectors for several inputs (texts or token lists) in one round-trip to the model process. Stops at the first error.

Release a sticky session: free its KV cells and return the seq to the idle pool. Unknown session ids are a no-op.

Fire an evict save and release the model's live KV state without unloading it. Bounded by the evict_save_timeout_ms application environment key (default 30 s).

Lock-free summary of the model's most recent admission: the cache hit kind and the warm prefix length in tokens. {ok, undefined} before the first admission.

Attached adapters with their scales.

Loaded models as model_info() maps (id, status, backend, context size, quantisation).

Load a LoRA adapter from a GGUF file and attach it with scale 1.0. The adapter's sha256 is folded into the model's effective fingerprint, so cached rows never mix across adapter sets. In-flight requests keep the previous fingerprint.

Load a model. The id is taken from model_id in Config or generated (<<"erllama_model_N">>). Returns {error, {missing_config, model_path}} when no path is given for the llama backend and {error, {invalid_config, model_path, Path}} when the file does not exist; the model process is never started on a bad config.

Load a model under an explicit id. {error, already_loaded} if the id is in use.

Inspect one loaded model (same map shape as list_models/0).

Lock-free count of calls queued behind the model's current request (complete/2,3, prefill_only/2,3, stream/3).

Lock-free phase snapshot from the model's observability row; answers without crossing the model process, so it returns instantly while a decode step is in flight.

Decode a prompt into KV state and fire a finish save without sampling any tokens. Returns finish_key for a later parent_key, or undefined when the prompt is shorter than the policy's min_tokens. With parent_key set and PromptTokens extending that context, only the new suffix is prefilled.

Host or accelerator memory pressure from the scheduler's configured pressure_source (system when the scheduler is off or on noop): used and total bytes plus the source that produced them.

Pressure sources the scheduler accepts (noop, system, nvidia_smi, ...).

Number of admitted streaming requests across all loaded models.

Number of admitted streaming requests for one model.

Render a chat request through the model's built-in template with the legacy renderer and tokenise it. Request carries messages, system and tools. Fallback for models whose template the autoparser (chat_apply/2) cannot handle; {error, no_template} when the GGUF ships none.

The model serving a streaming request, or {error, not_found} once it has finished.

Admitted streaming requests across all models: the ref and the model pid and id.

Forcibly drop a session's live KV cells and fail any in-flight request on its seq (the caller gets {erllama, Ref, {error, engine_reset}}). Bounded by a 5 s timeout so it stays usable when the engine is wedged: {error, timeout} means the model process itself is unreachable. Returns {ok, recovered} or {ok, not_found}. Prefer end_session/2 for normal teardown.

Change an attached adapter's scale (also splits the cache namespace).

Fire a shutdown save and return; same bound as evict/1.

Current phase, read through the model process.

Streaming inference. Returns {ok, Ref} at once; events arrive at the to process (default the caller) as {erllama, Ref, stream_event()}. Prompt is text (tokenised with tokenize/2) or a token list.

Tokenise text (add_special => true, parse_special => false). Safe during inference.

Tokenise with explicit options. parse_special => true turns chat-template markers in Text into their special token ids; use it for prompts rendered by chat_apply/2. add_special => false skips the BOS token.

Unload a model and free its context. {error, not_loaded} if it is not running.

Detach and free an adapter. Idempotent.

Verify speculative Candidates (first K) after PrefixTokens in one forward pass. Returns the accepted prefix length and the model's own next token (eos at end of generation). The model must be idle; concurrent requests get {error, busy}. KV state is restored before returning.

Free / total / used bytes summed over the non-CPU ggml devices. {error, no_gpu} on a CPU-only build; fall back to a system memory probe in that case.

Pid of a loaded model, e.g. to erlang:monitor/2 it.

Types

adapter()

-type adapter() :: term().

Handle of an attached LoRA adapter; treat as opaque.

cache_hit()

-type cache_hit() :: #{kind := cache_hit_kind(), prefix_len := non_neg_integer()}.

Last admission summary from last_cache_hit/1.

cache_hit_kind()

-type cache_hit_kind() :: exact | partial | cold | sticky | continuation.

How the last admission found its prefix in the cache.

cache_key()

-type cache_key() :: <<_:256>>.

SHA-256 cache key of a committed context (finish_key, parent_key).

chat_message()

-type chat_message() ::
          #{role := system | user | assistant | tool | binary(),
            content := binary() | [map()] | null,
            tool_calls => [map()],
            tool_call_id => binary(),
            name => binary()}.

A chat message. role is system | user | assistant | tool; content is a binary or a list of content-part maps in the OpenAI shape; assistant messages may carry tool_calls, tool results tool_call_id.

chat_opts()

-type chat_opts() ::
          #{tools => [chat_tool()],
            tool_choice => auto | required | none,
            parallel_tool_calls => boolean(),
            response_tokens => pos_integer(),
            parent_key => cache_key() | undefined,
            session_id => term(),
            on_full => block | error,
            stop_sequences => [binary()],
            thinking => enabled | disabled,
            thinking_budget_tokens => pos_integer(),
            temperature => number(),
            top_p => number(),
            top_k => integer(),
            min_p => number(),
            repetition_penalty => number(),
            seed => non_neg_integer(),
            grammar => binary(),
            prefix_checkpoint_len => non_neg_integer(),
            middleware => [middleware()]}.

Options for chat/3: tools, tool_choice (auto | required | none), parallel_tool_calls, plus any request_opts() key.

chat_params()

-type chat_params() :: reference().

Parser handle produced by chat_apply/3 for chat_parse/3; treat as opaque.

chat_request()

-type chat_request() :: erllama_model_backend:chat_request().

chat_result()

-type chat_result() ::
          #{message := parsed_message(), prompt := binary(), reply := binary(), stats := stats()}.

Result of chat/3.

chat_tool()

-type chat_tool() :: #{name := binary(), description => binary(), parameters => map()}.

A tool definition: name, description, JSON-schema parameters.

completion_result()

-type completion_result() ::
          #{reply := binary(),
            generated := [non_neg_integer()],
            context_tokens := [non_neg_integer()],
            committed_tokens := non_neg_integer(),
            finish_key := binary() | undefined,
            cache_hit_kind := cache_hit_kind(),
            finish_reason := finish_reason(),
            cache_delta := #{read := non_neg_integer(), created := non_neg_integer()},
            stats := stats(),
            stop_sequence => binary()}.

Result of complete/2,3.

error_reason()

-type error_reason() ::
          not_loaded | already_loaded | busy | seq_capacity | sticky_busy | no_session |
          {transcript_mismatch,
           #{stored_len := non_neg_integer(),
             expected_len := non_neg_integer(),
             diverge_at := non_neg_integer()}} |
          not_supported | chat_not_supported | no_template | timeout | engine_reset | context_overflow |
          decode_timeout | decode_aborted |
          {decode_failed, integer()} |
          oom | load_failed | malformed_gguf | no_gpu | too_large | not_found | empty_prefix |
          {missing_config, atom()} |
          {invalid_config, atom(), term()} |
          {missing_option, atom()} |
          {unknown_option, atom() | {atom(), atom()}} |
          {invalid_option, atom(), term()} |
          term().

Every {error, Reason} the API returns.

  • not_loaded: no model with that id or pid.
  • already_loaded: load_model/2 with an id in use.
  • busy, seq_capacity, sticky_busy: admission refused.
  • no_session, {transcript_mismatch, _}: session errors (continue/3, end_session/2).
  • not_supported, chat_not_supported, no_template: the backend or model lacks the feature.
  • timeout, engine_reset, decode_timeout, decode_aborted, {decode_failed, Rc}, context_overflow: runtime failures.
  • oom, load_failed, malformed_gguf, no_gpu, too_large, not_found: NIF-level failures.
  • {missing_config, Key}, {invalid_config, Key, Value}, {missing_option, Key}, {unknown_option, Key}, {invalid_option, Key, Value}: validation.

Backends may add their own atoms; they are documented on the backend.

finish_reason()

-type finish_reason() :: stop | length | cancelled.

load_config()

-type load_config() ::
          #{model_path => file:name_all(),
            backend => module(),
            model_id => model_id(),
            model_opts => map(),
            context_opts => map(),
            fingerprint => <<_:256>>,
            fingerprint_mode => safe | gguf_chunked | fast_unsafe,
            quant_type => atom(),
            quant_bits => pos_integer(),
            ctx_params_hash => <<_:256>>,
            context_size => pos_integer(),
            tier => ram | ram_file | disk,
            tier_srv => atom(),
            policy => map(),
            thinking_markers => #{start := binary(), 'end' := binary()},
            step_delay_ms => non_neg_integer(),
            thinking_capable => boolean()}.

Config map for load_model/1,2.

  • model_path (required unless backend is erllama_model_stub): path to a GGUF file.
  • backend: erllama_model_llama (default) or erllama_model_stub.
  • model_id: explicit id (load_model/1 only).
  • model_opts: n_gpu_layers, main_gpu, load_mode (auto | none | mmap | mlock | mmap_mlock | direct_io; use_mmap / use_mlock booleans map onto it), vocab_only, split_mode, tensor_split.

  • context_opts: n_ctx, n_batch, n_ubatch, n_seq_max, n_threads, n_threads_batch, embeddings, offload_kqv, kv_unified, flash_attn, type_k, type_v, decode_budget_ms.
  • fingerprint, fingerprint_mode, quant_type, quant_bits, ctx_params_hash, context_size: cache-key inputs; see the loading guide.
  • tier, tier_srv: cache tier for this model's saves (default RAM).
  • policy: cache policy overrides (min_tokens, cold_min_tokens, ...).
  • thinking_markers: #{start := binary(), 'end' := binary()}.

middleware()

-type middleware() :: erllama_middleware:middleware().

A middleware: fun(Request, Next) -> Response; see erllama_middleware.

model()

-type model() :: model_id() | pid().

A model id (binary) or the model process pid.

model_id()

-type model_id() :: binary().

The registered model id returned by load_model/1,2.

model_info()

-type model_info() ::
          #{id := binary(),
            model_id := binary(),
            pid := pid(),
            status := idle | prefilling | generating,
            backend := module(),
            context_size := non_neg_integer(),
            quant_type := atom(),
            quant_bits := non_neg_integer(),
            quant_tag := binary(),
            tier := ram | disk | ram_file,
            fingerprint := binary(),
            loaded_at_monotonic := integer(),
            vram_estimate_b := non_neg_integer(),
            n_seq_max := pos_integer(),
            available_seqs := non_neg_integer(),
            pinned_idle_seqs := non_neg_integer()}.

Snapshot returned by model_info/1 and list_models/0.

parsed_message()

-type parsed_message() ::
          #{role := binary(),
            content := binary(),
            reasoning_content := binary() | undefined,
            tool_calls := [#{name := binary(), arguments := map(), id := binary() | undefined}]}.

Assistant message parsed by chat_parse/3 / returned by chat/3.

phase()

-type phase() :: idle | prefilling | generating.

prefill_opts()

-type prefill_opts() ::
          #{parent_key => cache_key() | undefined,
            session_id => term(),
            on_full => block | error,
            prefix_checkpoint_len => non_neg_integer(),
            middleware => [middleware()]}.

Options for prefill_only/3: parent_key, session_id, on_full, prefix_checkpoint_len.

prefill_result()

-type prefill_result() ::
          #{context_tokens := [non_neg_integer()],
            committed_tokens := non_neg_integer(),
            finish_key := binary() | undefined,
            cache_hit_kind := cache_hit_kind(),
            cache_delta := #{read := non_neg_integer(), created := non_neg_integer()}}.

Result of prefill_only/2,3.

request_opts()

-type request_opts() ::
          #{response_tokens => pos_integer(),
            parent_key => cache_key() | undefined,
            session_id => term(),
            on_full => block | error,
            stop_sequences => [binary()],
            thinking => enabled | disabled,
            thinking_budget_tokens => pos_integer(),
            temperature => number(),
            top_p => number(),
            top_k => integer(),
            min_p => number(),
            repetition_penalty => number(),
            seed => non_neg_integer(),
            grammar => binary(),
            prefix_checkpoint_len => non_neg_integer(),
            to => pid(),
            expect_committed => [token_id()],
            middleware => [middleware()]}.

Options for complete/3, stream/3 and continue/3.

  • response_tokens (default 64): cap on generated tokens.
  • parent_key: a previous finish_key; resumes from that cached row.
  • session_id: pin the KV cells to a session across turns.
  • on_full: block (default) or error when no seq is free.
  • stop_sequences: stop strings; the match is trimmed from the reply.
  • thinking, thinking_budget_tokens: extended-thinking control.
  • temperature, top_p, top_k, min_p, repetition_penalty, seed, grammar: sampling.
  • prefix_checkpoint_len: pin the first N tokens as a static prefix checkpoint.
  • to: process receiving the stream events (stream/3, continue/3).
  • middleware: per-call middleware chain (see erllama_middleware).

stats()

-type stats() ::
          #{prompt_tokens := non_neg_integer(),
            completion_tokens := non_neg_integer(),
            generated := [token_id()],
            prefill_ms := non_neg_integer(),
            generation_ms := non_neg_integer(),
            cache_hit_kind := cache_hit_kind(),
            finish_reason := finish_reason(),
            cancelled := boolean(),
            finish_key := binary() | undefined,
            committed_tokens := non_neg_integer(),
            cache_delta := #{read := non_neg_integer(), created := non_neg_integer()},
            stop_sequence => binary()}.

Per-request statistics (stats in results, {done, Stats} in streams).

stream_event()

-type stream_event() ::
          {token, binary()} |
          {token_id, token_id()} |
          {thinking, binary()} |
          {thinking_end, binary()} |
          {done, stats()} |
          {error, error_reason()}.

Events delivered to the to process of stream/3 and continue/3 as {erllama, Ref, Event}.

  • {token, Bin}: text fragment (omitted when empty)
  • {token_id, Id}: every generated token id, in order
  • {thinking, Bin}: extended-thinking fragment (thinking => enabled)
  • {thinking_end, Sig}: close of a thinking block with its signature
  • {done, Stats}: completion; after cancel/1 Stats carries finish_reason => cancelled
  • {error, Reason}: failure; no done follows

stream_result()

-type stream_result() ::
          #{reply := binary(),
            thinking := binary(),
            generated := [token_id()],
            committed_tokens := non_neg_integer(),
            finish_key := cache_key() | undefined,
            cache_hit_kind := cache_hit_kind(),
            finish_reason := finish_reason(),
            cache_delta := #{read := non_neg_integer(), created := non_neg_integer()},
            stats := stats(),
            stop_sequence => binary()}.

Result of collect/2: the stream folded into a map.

token_id()

-type token_id() :: non_neg_integer().

A token id in the model vocabulary.

Functions

cached_prefix_len/2

-spec cached_prefix_len(model(), [token_id()]) -> {ok, non_neg_integer()} | {error, error_reason()}.

How many bytes of the detokenised PromptTokens are already cached for this model, across all tiers. {ok, 0} when nothing matches. Attached adapters are honoured through the effective fingerprint.

cancel(Ref)

-spec cancel(reference()) -> ok.

Cancel an in-flight streaming request. Idempotent. The caller still receives the terminal {erllama, Ref, {done, Stats}} with finish_reason => cancelled. The running decode is interrupted through the backend's abort callback; an interrupt that fires recreates the context in place, which also resets co-batched requests on other seqs. The persistent cache is untouched.

chat(Model, Messages, Opts)

-spec chat(model(), [chat_message()], chat_opts()) -> {ok, chat_result()} | {error, error_reason()}.

One chat turn: render Messages (and the tools in Opts) through the model's template, generate, and parse the output into a structured assistant message with content, reasoning and tool calls. Messages and tools are Erlang maps; see chat_message() and chat_tool().

{ok, #{message := #{content := Text, tool_calls := Calls}}} =
    erllama:chat(Model, [#{role => user, content => <<"hi">>}],
                 #{tools => [#{name => <<"weather">>, parameters => Schema}]}).

Streaming callers use chat_apply/2 + stream/3 + chat_parse/3.

chat_apply(Model, Messages, Opts)

-spec chat_apply(model(), [chat_message()], chat_opts()) ->
                    {ok, #{prompt := binary(), params := chat_params()}} | {error, error_reason()}.

Render the prompt and build the output parser for one request with llama.cpp's common_chat_templates_apply. Messages and Opts are those of chat/3 (only the chat keys of Opts are used). Returns the prompt bytes and the chat_params() to hand to chat_parse/3 for this request's output; tokenise the prompt with tokenize/3 and #{add_special => false, parse_special => true}. The parser is not reusable across requests.

chat_parse(Params, Input, IsPartial)

-spec chat_parse(chat_params(), binary(), boolean()) -> {ok, parsed_message()} | {error, error_reason()}.

Parse model output into a structured assistant message (content, reasoning, tool calls) with the parser from chat_apply/3. IsPartial = true accepts a streaming prefix.

collect(Ref, Timeout)

-spec collect(reference(), timeout()) -> {ok, stream_result()} | {error, error_reason()}.

Wait for a streaming request started with stream/3 or continue/3 and fold its events into a stream_result(). {error, timeout} after Timeout milliseconds without any event (the request is cancelled and its remaining events drained).

complete(Model, Prompt)

-spec complete(model(), binary()) -> {ok, completion_result()} | {error, error_reason()}.

complete/3 with default options.

complete(Model, Prompt, Opts)

-spec complete(model(), binary(), request_opts()) -> {ok, completion_result()} | {error, error_reason()}.

Run a completion and wait for the result.

Result carries reply (detokenised text, trimmed at a matched stop string), generated (token ids produced), context_tokens (prompt ++ generated), committed_tokens, finish_key (cache key of the full context, or undefined when the finish save was suppressed), cache_hit_kind, finish_reason (stop | length | cancelled), cache_delta (#{read := N, created := N}), stop_sequence (only when one fired) and stats.

Pass the previous turn's finish_key as parent_key to resume from the cached row instead of walking the longest cached prefix. See request_opts() for every option.

continue(Model, SuffixTokens, Opts)

-spec continue(model(), [token_id()], request_opts()) -> {ok, reference()} | {error, error_reason()}.

Extend a pinned session by prefilling SuffixTokens on top of its live KV cells, skipping the prefix-equality check and any cache lookup. Use it when the chat template renders history-dependent prefixes that would defeat stream/3's sticky path.

Opts must carry session_id; events go to to (default the caller). expect_committed => [token_id()] makes the engine verify the session's stored tokens first and fail with {error, {transcript_mismatch, #{stored_len, expected_len, diverge_at}}} on divergence, leaving the session pinned for a retry. parent_key is ignored. Events are those of stream/3; Stats.cache_hit_kind is continuation.

counters()

-spec counters() -> #{atom() => non_neg_integer()}.

Snapshot of the cache counters; see erllama_cache:get_counters/0 for the keys.

detokenize(Model, Tokens)

-spec detokenize(model(), [token_id()]) -> {ok, binary()} | {error, error_reason()}.

Detokenise token ids back to text.

draft_tokens/3

-spec draft_tokens(model(), [token_id()], #{max => pos_integer()}) ->
                      {ok, [token_id()]} | {error, error_reason()}.

Draft up to max next tokens after PrefixTokens and return their ids. Shorter lists (EOS, response_tokens) are valid. Built on stream/3; a 30 s silence cancels the request and returns {error, timeout}.

embed(Model, Input)

-spec embed(model(), binary() | [token_id()]) -> {ok, [float()]} | {error, error_reason()}.

Embedding vector for a text or a token list. The model must be loaded with context_opts => #{embeddings => true}.

embed_batch(Model, Inputs)

-spec embed_batch(model(), [binary() | [token_id()]]) -> {ok, [[float()]]} | {error, error_reason()}.

Embedding vectors for several inputs (texts or token lists) in one round-trip to the model process. Stops at the first error.

end_session(Model, SessionId)

-spec end_session(model(), term()) -> ok | {error, not_loaded | timeout}.

Release a sticky session: free its KV cells and return the seq to the idle pool. Unknown session ids are a no-op.

evict(Model)

-spec evict(model()) -> ok | {error, not_loaded | timeout}.

Fire an evict save and release the model's live KV state without unloading it. Bounded by the evict_save_timeout_ms application environment key (default 30 s).

last_cache_hit(ModelId)

-spec last_cache_hit(model_id()) -> {ok, cache_hit() | undefined} | {error, not_loaded}.

Lock-free summary of the model's most recent admission: the cache hit kind and the warm prefix length in tokens. {ok, undefined} before the first admission.

list_adapters(Model)

-spec list_adapters(model()) ->
                       {ok, [#{adapter := adapter(), scale := float()}]} | {error, not_loaded | timeout}.

Attached adapters with their scales.

list_models()

-spec list_models() -> [model_info()].

Loaded models as model_info() maps (id, status, backend, context size, quantisation).

load_adapter(Model, Path)

-spec load_adapter(model(), file:name_all()) -> {ok, adapter()} | {error, error_reason()}.

Load a LoRA adapter from a GGUF file and attach it with scale 1.0. The adapter's sha256 is folded into the model's effective fingerprint, so cached rows never mix across adapter sets. In-flight requests keep the previous fingerprint.

load_model(Config)

-spec load_model(load_config()) -> {ok, model_id()} | {error, error_reason()}.

Load a model. The id is taken from model_id in Config or generated (<<"erllama_model_N">>). Returns {error, {missing_config, model_path}} when no path is given for the llama backend and {error, {invalid_config, model_path, Path}} when the file does not exist; the model process is never started on a bad config.

load_model(ModelId, Config)

-spec load_model(model_id(), load_config()) -> {ok, model_id()} | {error, error_reason()}.

Load a model under an explicit id. {error, already_loaded} if the id is in use.

model_info(Model)

-spec model_info(model()) -> {ok, model_info()} | {error, not_loaded | timeout}.

Inspect one loaded model (same map shape as list_models/0).

pending_len(ModelId)

-spec pending_len(model_id()) -> {ok, non_neg_integer()} | {error, not_loaded}.

Lock-free count of calls queued behind the model's current request (complete/2,3, prefill_only/2,3, stream/3).

phase(ModelId)

-spec phase(model_id()) -> {ok, phase()} | {error, not_loaded}.

Lock-free phase snapshot from the model's observability row; answers without crossing the model process, so it returns instantly while a decode step is in flight.

prefill_only(Model, PromptTokens)

-spec prefill_only(model(), [token_id()]) -> {ok, prefill_result()} | {error, error_reason()}.

prefill_only/3 with default options.

prefill_only(Model, PromptTokens, Opts)

-spec prefill_only(model(), [token_id()], prefill_opts()) ->
                      {ok, prefill_result()} | {error, error_reason()}.

Decode a prompt into KV state and fire a finish save without sampling any tokens. Returns finish_key for a later parent_key, or undefined when the prompt is shorter than the policy's min_tokens. With parent_key set and PromptTokens extending that context, only the new suffix is prefilled.

pressure()

-spec pressure() ->
                  {ok,
                   #{source := erllama_pressure:source(),
                     used_b := non_neg_integer(),
                     total_b := non_neg_integer()}} |
                  {error, term()}.

Host or accelerator memory pressure from the scheduler's configured pressure_source (system when the scheduler is off or on noop): used and total bytes plus the source that produced them.

pressure_sources()

-spec pressure_sources() -> [erllama_pressure:source()].

Pressure sources the scheduler accepts (noop, system, nvidia_smi, ...).

queue_depth()

-spec queue_depth() -> non_neg_integer().

Number of admitted streaming requests across all loaded models.

queue_depth(ModelId)

-spec queue_depth(model_id()) -> {ok, non_neg_integer()} | {error, not_loaded}.

Number of admitted streaming requests for one model.

render_chat_template(Model, Request)

-spec render_chat_template(model(), chat_request()) -> {ok, [token_id()]} | {error, error_reason()}.

Render a chat request through the model's built-in template with the legacy renderer and tokenise it. Request carries messages, system and tools. Fallback for models whose template the autoparser (chat_apply/2) cannot handle; {error, no_template} when the GGUF ships none.

request_info(Ref)

-spec request_info(reference()) ->
                      {ok, #{ref := reference(), pid := pid(), model := model_id() | undefined}} |
                      {error, not_found}.

The model serving a streaming request, or {error, not_found} once it has finished.

requests()

-spec requests() -> [#{ref := reference(), pid := pid(), model := model_id() | undefined}].

Admitted streaming requests across all models: the ref and the model pid and id.

reset_session(Model, SessionId)

-spec reset_session(model(), term()) -> {ok, recovered | not_found} | {error, not_loaded | timeout}.

Forcibly drop a session's live KV cells and fail any in-flight request on its seq (the caller gets {erllama, Ref, {error, engine_reset}}). Bounded by a 5 s timeout so it stays usable when the engine is wedged: {error, timeout} means the model process itself is unreachable. Returns {ok, recovered} or {ok, not_found}. Prefer end_session/2 for normal teardown.

set_adapter_scale(Model, Adapter, Scale)

-spec set_adapter_scale(model(), adapter(), number()) -> ok | {error, error_reason()}.

Change an attached adapter's scale (also splits the cache namespace).

shutdown(Model)

-spec shutdown(model()) -> ok | {error, not_loaded | timeout}.

Fire a shutdown save and return; same bound as evict/1.

status(Model)

-spec status(model()) -> {ok, phase()} | {error, not_loaded | timeout}.

Current phase, read through the model process.

stream(Model, Prompt, Opts)

-spec stream(model(), binary() | [token_id()], request_opts()) ->
                {ok, reference()} | {error, error_reason()}.

Streaming inference. Returns {ok, Ref} at once; events arrive at the to process (default the caller) as {erllama, Ref, stream_event()}. Prompt is text (tokenised with tokenize/2) or a token list.

session_id pins the KV cells to a session so the next request with the same id extends them in place; release it with end_session/2. A concurrent request on a pinned session returns {error, sticky_busy}; on_full => error returns {error, seq_capacity} instead of queueing when no seq is free. Stats.generated in the done event is the exact generated token list, usable as the suffix for continue/3. Use collect/2 to wait for the result without writing the receive loop.

tokenize(Model, Text)

-spec tokenize(model(), binary()) -> {ok, [token_id()]} | {error, error_reason()}.

Tokenise text (add_special => true, parse_special => false). Safe during inference.

tokenize(Model, Text, Opts)

-spec tokenize(model(), binary(), #{add_special => boolean(), parse_special => boolean()}) ->
                  {ok, [token_id()]} | {error, error_reason()}.

Tokenise with explicit options. parse_special => true turns chat-template markers in Text into their special token ids; use it for prompts rendered by chat_apply/2. add_special => false skips the BOS token.

unload(Model)

-spec unload(model()) -> ok | {error, not_loaded}.

Unload a model and free its context. {error, not_loaded} if it is not running.

unload_adapter(Model, Adapter)

-spec unload_adapter(model(), adapter()) -> ok | {error, error_reason()}.

Detach and free an adapter. Idempotent.

verify(Model, PrefixTokens, Candidates, K)

-spec verify(model(), [token_id()], [token_id()], pos_integer()) ->
                {ok, #{accepted := non_neg_integer(), next := token_id() | eos}} |
                {error, error_reason()}.

Verify speculative Candidates (first K) after PrefixTokens in one forward pass. Returns the accepted prefix length and the model's own next token (eos at end of generation). The model must be idle; concurrent requests get {error, busy}. KV state is restored before returning.

vram_info()

-spec vram_info() ->
                   {ok,
                    #{total_b := non_neg_integer(),
                      free_b := non_neg_integer(),
                      used_b := non_neg_integer()}} |
                   {error, no_gpu | error_reason()}.

Free / total / used bytes summed over the non-CPU ggml devices. {error, no_gpu} on a CPU-only build; fall back to a system memory probe in that case.

whereis/1

-spec whereis(model()) -> {ok, pid()} | {error, not_loaded}.

Pid of a loaded model, e.g. to erlang:monitor/2 it.