erllama (erllama v0.11.0)
View SourcePublic 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: the chat-level keys below 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.
Per-token logprobs report (logprobs => N option): the sampled
token's full-vocab log-softmax logprob and the model's top-N
{TokenId, Logprob} pairs, descending. Logprobs are computed over
the raw model distribution, before any sampler stage.
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.
Options for complete/3, stream/3 and continue/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.
Snapshot returned by vocab_info/1: vocabulary size, whether the
tokenizer adds BOS/EOS, and the special / FIM token ids (undefined
when the model has no such token).
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
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.
Detokenise with options: #{remove_special => boolean(), unparse_special => boolean()} (both default false).
unparse_special renders special / control tokens (<|im_start|>,
FIM markers, ...) into the output - detokenize/2 drops them;
remove_special strips a leading BOS / trailing EOS on models
configured to add them.
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).
Fork a sticky session: duplicate SrcSessionId's live KV cells into
a fresh sequence registered as NewSessionId, so two continuations
can explore different branches without re-prefilling the shared
prefix. Works on every model family (dense KV copy / recurrent state
copy).
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.
prefill_only/3 with default options.
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.
Special / FIM vocab tokens of the loaded model: n_vocab,
add_bos / add_eos, and the token ids bos, eos, eot, sep,
nl, pad, mask, fim_pre, fim_suf, fim_mid, fim_pad,
fim_rep, fim_sep (each undefined when the model has no such
token). The FIM ids let you assemble fill-in-the-middle prompts
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
-type adapter() :: term().
Handle of an attached LoRA adapter; treat as opaque.
-type cache_hit() :: #{kind := cache_hit_kind(), prefix_len := non_neg_integer()}.
Last admission summary from last_cache_hit/1.
-type cache_hit_kind() :: exact | partial | cold | sticky | continuation.
How the last admission found its prefix in the cache.
-type cache_key() :: <<_:256>>.
SHA-256 cache key of a committed context (finish_key, parent_key).
-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.
-type chat_opts() :: #{tools => [chat_tool()], tool_choice => auto | required | none, parallel_tool_calls => boolean(), json_schema => map() | binary(), enable_thinking => boolean(), reasoning_format => deepseek | none, continue_final_message => none | auto | content | reasoning, 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: the chat-level keys below plus any
request_opts() key.
tools,tool_choice(auto | required | none),parallel_tool_calls: the tool set. With tools present the template-synthesized grammar is enforced during sampling:requiredconstrains the whole reply to a call,autoarms a lazy grammar that kicks in when the model opens a call.json_schema: response format (map or JSON binary); the reply's content is constrained to the schema. Rejected together withtools.enable_thinking(defaulttrue): templates that support thinking render (or suppress) the thinking preamble.reasoning_format(defaultdeepseek): extract thinking intoreasoning_content;noneleaves it inline incontent.continue_final_message(none | auto | content | reasoning, defaultnone): assistant prefill - the trailing assistant message becomes the beginning of the reply instead of a closed turn.
-type chat_params() :: reference().
Parser handle produced by chat_apply/3 for chat_parse/3; treat as opaque.
-type chat_request() :: erllama_model_backend:chat_request().
-type chat_result() :: #{message := parsed_message(), prompt := binary(), reply := binary(), stats := stats()}.
Result of chat/3.
A tool definition: name, description, JSON-schema parameters.
-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(), logprobs => [logprobs_entry()]}.
Result of complete/2,3.
-type error_reason() :: not_loaded | already_loaded | busy | seq_capacity | sticky_busy | no_session | session_exists | seq_cp_failed | {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 | {unsupported_model, encoder_decoder | diffusion} | 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/2with an id in use.busy,seq_capacity,sticky_busy: admission refused.no_session,session_exists,seq_cp_failed,{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.{unsupported_model, encoder_decoder | diffusion}: the GGUF loads but its architecture needs an inference mode the engine does not drive (T5-style encoders, diffusion LMs); rejected atload_model.{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.
-type finish_reason() :: stop | length | cancelled.
-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()}, chat_template => binary(), step_delay_ms => non_neg_integer(), thinking_capable => boolean()}.
Config map for load_model/1,2.
model_path(required unlessbackendiserllama_model_stub): path to a GGUF file.backend:erllama_model_llama(default) orerllama_model_stub.model_id: explicit id (load_model/1only).model_opts:n_gpu_layers,main_gpu,load_mode(auto | none | mmap | mlock | mmap_mlock | direct_io;use_mmap/use_mlockbooleans 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()}.chat_template: Jinja source that replaces the template stored in the GGUF forchat/3andchat_apply/3(for files that ship a broken or outdated template).
-type logprobs_entry() :: #{token_id := token_id(), logprob := float(), top := [{token_id(), float()}]}.
Per-token logprobs report (logprobs => N option): the sampled
token's full-vocab log-softmax logprob and the model's top-N
{TokenId, Logprob} pairs, descending. Logprobs are computed over
the raw model distribution, before any sampler stage.
-type middleware() :: erllama_middleware:middleware().
A middleware: fun(Request, Next) -> Response; see erllama_middleware.
A model id (binary) or the model process pid.
-type model_id() :: binary().
The registered model id returned by load_model/1,2.
-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(), arch => binary(), n_ctx_train => integer(), n_params => non_neg_integer(), n_embd => integer(), n_layer => integer(), n_swa => integer(), recurrent => boolean(), hybrid => boolean()}.
Snapshot returned by model_info/1 and list_models/0.
-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.
-type phase() :: idle | prefilling | generating.
-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.
-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.
-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(), grammar_lazy => boolean(), trigger_patterns => [binary()], trigger_tokens => [token_id()], grammar_prefill => binary(), typical_p => number(), top_n_sigma => number(), xtc_probability => number(), xtc_threshold => number(), dynatemp_range => number(), dynatemp_exponent => number(), min_keep => pos_integer(), frequency_penalty => number(), presence_penalty => number(), penalty_last_n => integer(), dry_multiplier => number(), dry_base => number(), dry_allowed_length => integer(), dry_penalty_last_n => integer(), dry_sequence_breakers => [binary()], mirostat => 0 | 1 | 2, mirostat_tau => number(), mirostat_eta => number(), logit_bias => [{token_id(), number()}], ignore_eos => boolean(), infill => boolean(), logprobs => non_neg_integer(), 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 previousfinish_key; resumes from that cached row.session_id: pin the KV cells to a session across turns.on_full:block(default) orerrorwhen 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.- Extended sampling (defaults mirror llama.cpp):
typical_p(< 1.0 enables),top_n_sigma(> 0 enables),xtc_probability+xtc_threshold,dynatemp_range+dynatemp_exponent(dynamic temperature),min_keep,frequency_penalty+presence_penaltypenalty_last_n,dry_multiplier+dry_base+dry_allowed_length+dry_penalty_last_n+dry_sequence_breakers(DRY anti-repetition),mirostat(1 | 2; replaces the truncation stages with the mirostat controller) +mirostat_tau+mirostat_eta,logit_bias([{TokenId, Bias}]),ignore_eos(suppress every end-of-generation token),infill(FIM-oriented final filter). Chain order follows llama.cpp: grammar -> logit_bias -> penalties -> dry -> top_n_sigma -> top_k -> typical_p -> top_p -> min_p -> xtc -> infill -> temperature -> dist (greedy when temperature is 0 or absent).
logprobs: report each token's full-vocab logprob plus the top-N alternatives (0..32) - stream event{logprobs, _}/logprobskey on the completion result.grammar_lazy,trigger_patterns,trigger_tokens,grammar_prefill: lazy / template-grammar variants ofgrammar(normally injected bychat/3or taken fromchat_apply/3'ssampler_opts, not hand-written). A lazy grammar activates only once a trigger pattern matches the output (or a trigger token id is sampled);grammar_prefillfeeds the already-prompted assistant header into a non-lazy template grammar before 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 (seeerllama_middleware).
-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).
-type stream_event() :: {token, binary()} | {token_id, token_id()} | {logprobs, logprobs_entry()} | {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{logprobs, Entry}: per-token logprobs (logprobs => Noption); precedes the corresponding{token, _}/{token_id, _}events. Seelogprobs_entry().{thinking, Bin}: extended-thinking fragment (thinking => enabled){thinking_end, Sig}: close of a thinking block with its signature{done, Stats}: completion; aftercancel/1Statscarriesfinish_reason => cancelled{error, Reason}: failure; nodonefollows
-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.
-type token_id() :: non_neg_integer().
A token id in the model vocabulary.
-type vocab_info() :: #{n_vocab := integer(), add_bos := boolean(), add_eos := boolean(), bos := token_id() | undefined, eos := token_id() | undefined, eot := token_id() | undefined, sep := token_id() | undefined, nl := token_id() | undefined, pad := token_id() | undefined, mask := token_id() | undefined, fim_pre := token_id() | undefined, fim_suf := token_id() | undefined, fim_mid := token_id() | undefined, fim_pad := token_id() | undefined, fim_rep := token_id() | undefined, fim_sep := token_id() | undefined}.
Snapshot returned by vocab_info/1: vocabulary size, whether the
tokenizer adds BOS/EOS, and the special / FIM token ids (undefined
when the model has no such token).
Functions
-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.
-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.
-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.
-spec chat_apply(model(), [chat_message()], chat_opts()) -> {ok, #{prompt := binary(), params := chat_params(), sampler_opts := map(), stop_sequences := [binary()], generation_prompt := binary(), supports_thinking := boolean(), thinking_start_tag := binary(), thinking_end_tags := [binary()]}} | {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:
prompt: the rendered bytes; tokenise withtokenize/3and#{add_special => false, parse_special => true}.params: thechat_params()to hand tochat_parse/3for this request's output. Not reusable across requests.sampler_opts: the template-synthesized constraint set (grammar,grammar_lazy,trigger_patterns,trigger_tokens,grammar_prefill) ready to merge into thestream/3request opts, andstop_sequences: the template's additional stop strings. Merge both sotool_choice => required/json_schemaare enforced during sampling, exactly aschat/3does internally:
{ok, #{prompt := P, params := Params, sampler_opts := S, stop_sequences := Stops}} =
erllama:chat_apply(M, Messages, ChatOpts),
{ok, Tokens} = erllama:tokenize(M, P, #{add_special => false, parse_special => true}),
{ok, Ref} = erllama:stream(M, Tokens, maps:merge(S, #{stop_sequences => Stops})).generation_prompt,supports_thinking,thinking_start_tag,thinking_end_tags: template metadata (informational).
-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.
-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).
-spec complete(model(), binary()) -> {ok, completion_result()} | {error, error_reason()}.
complete/3 with default options.
-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.
-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.
-spec counters() -> #{atom() => non_neg_integer()}.
Snapshot of the cache counters; see erllama_cache:get_counters/0 for the keys.
-spec detokenize(model(), [token_id()]) -> {ok, binary()} | {error, error_reason()}.
Detokenise token ids back to text.
-spec detokenize(model(), [token_id()], map()) -> {ok, binary()} | {error, error_reason()}.
Detokenise with options: #{remove_special => boolean(), unparse_special => boolean()} (both default false).
unparse_special renders special / control tokens (<|im_start|>,
FIM markers, ...) into the output - detokenize/2 drops them;
remove_special strips a leading BOS / trailing EOS on models
configured to add them.
-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}.
-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}.
-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.
Release a sticky session: free its KV cells and return the seq to the idle pool. Unknown session ids are a no-op.
-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).
-spec fork_session(model(), term(), term()) -> ok | {error, error_reason()}.
Fork a sticky session: duplicate SrcSessionId's live KV cells into
a fresh sequence registered as NewSessionId, so two continuations
can explore different branches without re-prefilling the shared
prefix. Works on every model family (dense KV copy / recurrent state
copy).
{ok, _} = erllama:complete(M, Prompt, #{session_id => a}),
ok = erllama:fork_session(M, a, b),
%% `a` and `b` now diverge independently:
{ok, _} = erllama:complete(M, <<Prompt/binary, " option one">>, #{session_id => a}),
{ok, _} = erllama:complete(M, <<Prompt/binary, " option two">>, #{session_id => b}).The copy carries no logits, so the forked session's first request
must extend the stored transcript (any normal continuation does).
Never queues: with no free sequence - after reclaiming the
least-recently-used idle pin, never the source - the reply is
{error, seq_capacity}. Other errors: no_session (unknown
source), session_exists, sticky_busy (source has an in-flight
request), seq_cp_failed (the memory refused the copy).
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.
-spec list_adapters(model()) -> {ok, [#{adapter := adapter(), scale := float()}]} | {error, not_loaded | timeout}.
Attached adapters with their scales.
-spec list_models() -> [model_info()].
Loaded models as model_info() maps (id, status, backend, context size, quantisation).
-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.
-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.
Loading blocks for the duration of the GGUF read. Pass
progress_to => Pid to receive progress while it runs:
{erllama_load_progress, ModelId, Progress} messages with
Progress :: float() in [0.0, 1.0], non-decreasing, throttled to
whole-percent steps, ending with exactly 1.0. The stub backend
sends none.
-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.
-spec model_info(model()) -> {ok, model_info()} | {error, not_loaded | timeout}.
Inspect one loaded model (same map shape as list_models/0).
-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).
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.
-spec prefill_only(model(), [token_id()]) -> {ok, prefill_result()} | {error, error_reason()}.
prefill_only/3 with default options.
-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.
-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.
-spec pressure_sources() -> [erllama_pressure:source()].
Pressure sources the scheduler accepts (noop, system, nvidia_smi, ...).
-spec queue_depth() -> non_neg_integer().
Number of admitted streaming requests across all loaded models.
-spec queue_depth(model_id()) -> {ok, non_neg_integer()} | {error, not_loaded}.
Number of admitted streaming requests for one model.
-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.
-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.
Admitted streaming requests across all models: the ref and the model pid and id.
-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.
-spec set_adapter_scale(model(), adapter(), number()) -> ok | {error, error_reason()}.
Change an attached adapter's scale (also splits the cache namespace).
-spec shutdown(model()) -> ok | {error, not_loaded | timeout}.
Fire a shutdown save and return; same bound as evict/1.
Current phase, read through the model process.
-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.
-spec tokenize(model(), binary()) -> {ok, [token_id()]} | {error, error_reason()}.
Tokenise text (add_special => true, parse_special => false). Safe during inference.
-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.
-spec unload(model()) -> ok | {error, not_loaded}.
Unload a model and free its context. {error, not_loaded} if it is not running.
-spec unload_adapter(model(), adapter()) -> ok | {error, error_reason()}.
Detach and free an adapter. Idempotent.
-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.
-spec vocab_info(model()) -> {ok, vocab_info()} | {error, error_reason()}.
Special / FIM vocab tokens of the loaded model: n_vocab,
add_bos / add_eos, and the token ids bos, eos, eot, sep,
nl, pad, mask, fim_pre, fim_suf, fim_mid, fim_pad,
fim_rep, fim_sep (each undefined when the model has no such
token). The FIM ids let you assemble fill-in-the-middle prompts:
{ok, #{fim_pre := Pre, fim_suf := Suf, fim_mid := Mid}} = erllama:vocab_info(M),
Tokens = [Pre | PrefixTokens] ++ [Suf | SuffixTokens] ++ [Mid],
{ok, Ref} = erllama:stream(M, Tokens, #{infill => true}).
-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.
Pid of a loaded model, e.g. to erlang:monitor/2 it.