* chore(backends): remove the liquid-audio Python backend The backend targets liquid-audio, which loads LFM2 checkpoints only. Remove its directory, the gallery file and the LFM2.5-Audio model entries, the library and generated backend gallery entries, the backend matrix and CI job, the Makefile targets and the golden test fixtures that listed it. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactor(config): drop liquid-audio from capabilities, importers and UI With the liquid-audio backend gone, remove its capability entry, its importer, its artifact-gate entry and the realtime_audio heuristic that only matched that backend. realtime_audio is now set only through known_usecases. Update the Talk and fine-tuning pages, the docs and the swagger text that named the backend. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(llama-cpp): bump llama.cpp to e486a4b5 Move the llama.cpp pin to e486a4b559617dc88bf79978996f6c670b6bbc9d, the master head of 2026-10-28. It contains the LFM3-Audio support (ggml-org/llama.cpp#31257) and the realtime server split (ggml-org/llama.cpp#31269). The score task patch is regenerated against the new pin. It applies with git apply and with patch -p1, with no offset and no fuzz. Its hunks in server-task.h and server-context.cpp were rebased by hand next to SERVER_TASK_TYPE_REALTIME and the control dispatch. The C++ backend was built on CPU for this change: the llama, mtmd and server-context libraries and the grpc-server binary link. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): compile server-realtime.cpp into grpc-server server-context.cpp includes server-realtime.h, and its realtime slot type is defined in server-realtime.cpp. Upstream compiles that file into the server library. The gRPC server is one translation unit, so it must include the file too, or the link fails. The include is guarded by __has_include. Forks that predate the realtime split do not ship the file, so they keep building as before. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(llama-cpp): probe realtime and TTS voice support in prepare.sh prepare.sh writes llama_compat.h from probes on the checkout. Add two: LOCALAI_HAS_REALTIME (SERVER_TASK_TYPE_REALTIME in server-task.h) and LOCALAI_HAS_TTS_VOICE (set_voice in mtmd-helper.h). The probes let the gRPC server choose the realtime and voice code paths per checkout, so forks without them keep compiling. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): advertise speech recognition and speech-to-speech llama-cpp now registers the AudioTranscription and AudioToAudioStream RPCs, with the transcript and realtime_audio usecases. The default usecase stays chat, so a plain GGUF is still a chat model. The backend implements the RPCs in a follow-up change; until then the calls return UNIMPLEMENTED. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(config): add pipeline.any_to_any for speech-to-speech models A realtime_audio model that serves the whole speech loop itself sets pipeline.any_to_any. The flag is off by default, so chained pipelines and existing realtime_audio configs are unchanged. The realtime_audio heuristic stays explicit: a llama-cpp model is never a Talk-page model unless known_usecases says so. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(config): read the audio encoder and speech-to-speech projector flags Add HasAudioEncoderProjector for clip.has_audio_encoder and HasSpeechToSpeechProjector, which requires the LFM3 audio encoder family together with the gen-audio decoder. Qwen3-TTS sets both encoder flags too, so the flags alone cannot tell it apart from LFM3. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(importer): import LFM3-Audio projectors as speech-to-speech An mmproj that carries the LFM3 audio encoder and the gen-audio decoder now imports with known_usecases realtime_audio, chat, transcript and tts, pipeline.any_to_any: true, the parallel:2 option and the tokenizer template. Other gen-audio projectors keep the TTS-only result, Qwen3-TTS included. The probe runs against a local HTTP server in the tests. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): named-voice TTS and the LFM3 ASR prompt Models without a speaker reference (LFM3-Audio) take a named voice through their "tts" chat template instead of a reference file. The request is validated with needs_reference=false, the prompt is rendered with common_chat_get_tts_prompt(), and the voice is set on the gen-audio input. Qwen3-TTS Base keeps the reference-file rule. The branch is guarded by LOCALAI_HAS_TTS_VOICE, so forks without named voices keep the old behaviour. Transcription takes its prompt from common_chat_get_asr_prompt(). LFM3 gets a system turn that names the language and an empty user turn. Qwen3-ASR's preset is unchanged: fixed user text, no system turn, and a caller prompt is still appended after a newline. The preset call is guarded by a new LOCALAI_HAS_ASR_PRESET probe. The pure parts sit in tts_request_options.h and transcription_prompt.h, with standalone tests run by backend/cpp/run-unit-tests.sh. Add the test-extra-backend-llama-cpp-lfm3 target. It runs the tts, ttsstream and transcription specs against LFM3-Audio GGUF weights. It is a manual run and is not wired into CI. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): mock AudioToAudioStream and pending realtime S2S specs The mock backend serves AudioToAudioStream deterministically: session created, commit with RMS, a tool call on the first turn with tools, then transcript pieces and paced 24 kHz tone frames, with response.cancel ending a reply as cancelled. The realtime S2S specs cover the mock and real-weights variants. They skip until the /v1/realtime S2S path lands, so the mock suite stays green in CI. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): speech-to-speech AudioToAudioStream on the realtime slot Implement AudioToAudioStream on upstream's realtime slot, so a model that answers after the audio (LFM3-Audio) can hold a conversation over the existing duplex RPC. The proto schema does not change: tools travel in the config, tool calls leave as function_call_arguments.done and tool results arrive as a function_call_output item. realtime_session.h keeps what needs no model: the turn text derived from the chat template (with a tool turn), the conversation state, a filter that keeps <tool_call> blocks out of the transcript and drops audio produced inside them, and the translation of slot results into events. grpc-server.cpp wires it to the slot with control tasks and parses calls with the parser the chat template selects. The unit test uses a fake renderer. A ctest-only check renders the real LFM3 template and compares the slot text with the full render. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(gallery): add the LFM3-Audio-2B entries Two llama-cpp entries for LiquidAI/LFM3-Audio-2B-GGUF, the Q4_K_M default (lfm3-audio-2b) and the Q8_0 build (lfm3-audio-2b-q8), both with the Q8_0 audio projector. Each declares pipeline.any_to_any and the realtime_audio, chat, transcript and tts usecases the importer writes for an LFM3 projector, with parallel:2 and the tokenizer template. The Q4 entry lists the Q8 one as a variant. File sizes and sha256 come from the Hugging Face API for the pinned repo revision. Speaker reference files and the VAD model are not part of the entries: VAD stays optional. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs: describe LFM3-Audio speech-to-speech and its gallery entries Adds a speech-to-speech section to the Realtime API page (any_to_any configuration, VAD versus manual commit, tools, transcripts, barge-in, limits), a pipeline.any_to_any row, a llama.cpp speech-to-speech row in the compatibility table, and LFM3-Audio mentions on the TTS and ASR pages. The feature is marked experimental: the behaviour follows the backend session contract and has not been run with real weights. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(realtime): run any-to-any models on AudioToAudioStream A realtime_audio model with pipeline.any_to_any now opens one duplex stream to the backend and keeps it for the session, replacing the "any-to-any models not implemented" branch. The chained pipeline is unchanged. The endpoint forwards input audio (with a pre-roll from speech start when a VAD model is set, otherwise every chunk and a client commit), sends response.create, and turns the backend's events into the usual realtime events: audio and transcript deltas, tool calls through the existing function_call items, and a single response.done. A function call output is forwarded before the next response. Barge-in sends response.cancel ahead of the new speech and stops forwarding audio at once. The user's speech is transcribed on the side and reported on the user item; a failure there does not touch the response. Server VAD is on only when pipeline.vad is set. Classifier mode, compaction, streaming.llm, voice gating, sound detection and semantic_vad are rejected for these models. The mock backend now matches the llama.cpp contract (session.updated, the cancel error when idle) and its VAD reports the loud stretches of the audio, so the S2S e2e specs run a VAD turn. The specs are enabled. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): add the real-weights realtime S2S variant and the gRPC s2s spec The realtime S2S specs now check reply audio on real models: duration, loudness, clipping, and keywords found in the model transcript or in a separate ASR of the output audio. run-realtime-lfm3.sh stages the llama-cpp and sherpa-onnx backends, the LFM3-Audio files and the sherpa VAD, ASR and TTS models, then runs the real variant. It is manual and never part of CI: `make test-realtime` still filters out real-models. The backend suite gains an s2s capability (spoken turn, tool call, tool result, spoken answer over AudioToAudioStream) and optional TTS duration and RMS bounds plus a transcript keyword check, all enabled for test-extra-backend-llama-cpp-lfm3. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): expect the idle-cancel error from the mock S2S The mock now answers a response.cancel with no reply running with response_cancel_not_active, as the llama.cpp backend does. The cancel spec still treated that cancel as a no-op and read the error as extra data after the stream ended. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(talk): show any-to-any models as one speech-to-speech model /api/pipeline-models lists a pipeline.any_to_any model with any_to_any set and its optional VAD. A realtime_audio model that is not any-to-any is listed only when it has the four pipeline slots, since the session takes the chained path for it. The Talk page shows a speech-to-speech model as one model: no four-slot view, the VAD slot only when a VAD model is set, and the female and male voices offered. Without a VAD model the server does not detect turns, so the page offers Done speaking, which sends input_audio_buffer.commit and then response.create. Text-only replies stream through response.output_text.delta and .done. self_contained stays in the response for clients built before any_to_any. Assisted-by: Claude:claude-haiku-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): refuse a llama-cpp realtime model without pipeline.any_to_any llama.cpp has no VAD, transcription or TTS RPCs, so filling the empty pipeline slots with the model name failed later with UNIMPLEMENTED. Fail at session start and name the missing setting instead. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): hold manual-commit S2S audio until the commit In manual-commit mode the endpoint streamed every appended chunk to the backend, so input_audio_buffer.clear only emptied the Go buffer while the model kept the audio, and idle audio filled its context between turns. Keep the audio in the input buffer and send it, behind pending text and tool-result items, when the client commits or asks for a response. Frames and controls now go through one ordered write queue, so no gRPC send happens under the audio buffer lock. A tool result created while a VAD turn is open is refused with a clear error. When the stream ends outside a response the session reports it and closes, and a bare EOF from Send reports the status that Recv holds. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): drop the S2S readiness gate and stale comments The gate was always true. Some comments referred to work items that readers of the repository cannot see. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(realtime): add the liquid-audio migration note and the clear semantics Say that the Python liquid-audio backend is gone, which gallery entries replace it and that LFM2.5-Audio has no replacement. Describe what input_audio_buffer.clear drops for speech-to-speech sessions now that manual-commit audio is held back until the commit. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(talk): do not promise turn detection when no VAD model is set The idle note said the server notices when you stop talking. A speech-to-speech model without a VAD model needs Done speaking, so the note now says that. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): make the mock S2S backend keep the llama-cpp session limits The mock accepted what the real backend refuses and hid two endpoint bugs. It now treats output_modalities from the config as a fixed session mode and picks the default per response from the tools set at that time. A fixed text plus audio mode with tools reads the tool call out loud. After the first commit or message, session.update of instructions, voice or tools is an error that does not belong to a response, committing an empty buffer is an error, and response.create while a reply runs is refused. Errors of a refused response.create carry response_scoped in their meta. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): leave the S2S output mode to the backend unless the client sets one The stream config sent text plus audio when the session had no tools yet. The llama-cpp backend keeps a mode from the config as a fixed session mode, and tools added later by session.update never cleared it. The model then read tool calls out loud. Send output_modalities only when the client chose them. The backend picks the default of each response from the tools it has when the response starts. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(llama-cpp): mark the realtime errors that answer a response.create The error event now carries response_scoped: true when it ends the request for a response (a refused response.create, a failed commit made by it, or a stream failure). Other errors, such as a refused session.update or a commit of an empty buffer, are not about a response and one that runs keeps generating. The client needs the difference to know whether a response.done is still coming. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): end an S2S response only on the errors that belong to it Every backend error ended the response in flight, although the backend also reports errors about other things (a refused session.update, a commit of an empty buffer) and goes on generating. The rest of the reply was lost and the next response.create met either a refusal or the leftover events of the old reply. End the response only on an error marked response_scoped, forward the others to the client and keep waiting for response.done. Drop the events buffered by an earlier response when a new one starts, and skip the commit of a turn that forwarded no audio. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): run the S2S input transcription detached from the commit slot The commit waited for the side transcription of the user audio while it held the commit slot, and response.create waits for that slot. The ASR latency was added before every reply, and on a backend with few slots a busy ASR task could stall the reply. The model already has the audio, so run the transcription detached under the session context and release the slot at once. The transcript is still reported on the user item. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(realtime): do not pin the place of the input transcript in the S2S order The transcript of the user audio is reported on the side and may land before or after the first response events. Check the order of the response events without it. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): stop an S2S reader leak and a cancel that waits on the write queue The reader waited on a channel it closes itself when the events buffer was full, so it outlived the session after a turn ended on an error or timed out. It now also stops when the session closes. Stopping a response sent the cancel through the write queue and waited for it before the grace timer started. With backpressure on the stream the wait never ended and nothing read the events meanwhile. The timer now starts first and the cancel is only queued. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(realtime): let a chained pipeline run without a VAD model A pipeline that names no pipeline.vad failed to build, because the VAD slot was always loaded, and a new session still started with server_vad. The slot is now optional for chained pipelines as it already is for any-to-any models: the session starts with turn detection off and the client commits each turn. Turning server_vad on for such a session is refused, and a switch to a pipeline without a VAD drops turn detection. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(gallery): add LFM3-Audio VAD and chained-pipeline entries Add lfm3-audio-2b-vad and -q8-vad (any-to-any with silero-vad-ggml as the turn detector), and lfm3-audio-2b-pipeline, -pipeline-vad and the -q8 forms (the model chained through its own transcription, chat and speech slots, with and without the VAD). The gallery has no dependencies between models, so the VAD entries state that silero-vad-ggml must be installed first. Drop the variants list from lfm3-audio-2b: it let the installer pick the larger Q8 build on any host with enough memory, so a plain install of the Q4 entry did not get Q4. All entries are now independent. Tests cover the entry shapes, the single-build install and their acceptance by the realtime endpoint. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): run the realtime specs for any-to-any and chained shapes The realtime specs covered one any-to-any model with a VAD. They now run for any-to-any and for a chained pipeline, each with and without a VAD, on the mock backend in CI. VAD-less shapes also check that turn detection starts off and that server_vad is refused. The manual real-weights script writes one config per shape that mirrors the gallery entries and runs the four shapes in turn (LFM3_MATRIX picks a subset). Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(realtime): document the LFM3-Audio VAD and chained-pipeline entries List the eight LFM3-Audio gallery entries, explain that silero-vad-ggml must be installed before the -vad entries, describe the chained pipeline on one model and the behaviour of a pipeline without a VAD, and describe the shape matrix of the e2e specs. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(realtime): wait for the detached input transcription in the S2S specs The input transcription runs in its own goroutine, so the transcription completed and failed events can arrive after the response ends. Poll for them instead of reading the event list right after the response. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): release the S2S stream before running server-side tool calls A response held the stream slot until it returned, and it ran the tool calls from inside that scope. A server-side tool starts a follow-up response on the same stream, which then waited for the slot its parent still held, so the client never got response.done. Run the tool calls once the slot is free; the event order is unchanged. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): end an S2S session whose stream dies, and stop queuing after close A stream that died during a response left the session open: later requests failed one by one and VAD frames kept being forwarded. The response now ends the session through the transport, and enqueue drops requests once the session is closed so the write queue cannot grow. The writer is now the only caller of Send and of CloseSend. close() used to call CloseSend while the writer could be inside Send, which gRPC does not allow. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): close the open S2S turn when the VAD stops Switching turn detection to manual in the middle of a VAD turn left the turn forwarding, and the next client commit sent the whole buffer a second time. Stopping the VAD now commits the forwarded audio on the backend and drops it from the input buffer. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): keep a tool result pending while an S2S user turn is open The check that refuses a tool result during a user turn ran when the item arrived, but the item was forwarded later, and the result was marked as sent before the backend took it. If the user spoke in between, the backend refused it and it was never retried. The result is now claimed and queued under the audio lock only when no turn is open, and otherwise stays pending for the next commit or response. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(talk): empty the input buffer for models without a VAD Without voice detection the microphone fills the server's input buffer for the whole session, so Done speaking committed idle silence and the echo of the previous reply. Clear the buffer when the session starts and when a reply ends. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(importer): keep an existing parallel option on LFM3-Audio imports The speech-to-speech import added parallel:2 even when the options already carried another parallel value. Use the same check as the hardware defaults, which also covers n_parallel. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(talk): list chained pipelines that have no VAD /api/pipeline-models required a VAD for a chained pipeline, so the VAD-less LFM3-Audio pipeline entries never showed up on the Talk page. Any pipeline with transcription, LLM and speech slots now qualifies. The Talk page treats every model without a VAD as manual-turn, and the session sheet says so for chained pipelines too. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(llama-cpp): bump llama.cpp to 0ac1a7fd Move the llama.cpp pin to 0ac1a7fd35c127a18c0cbc6c57d12e256a6a385b, the master head of 2026-10-29. It contains tool calls in the realtime server (ggml-org/llama.cpp#31287). The score task patch is regenerated so that it applies with no offset and no fuzz. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(functions): keep a null tool argument null A tagged parameter whose text was "null" came out as an empty string: unmarshalling JSON null into a string succeeds and leaves it empty, so the value was taken for a string. Check the first byte of the JSON value instead. A parameter with the text "null" now becomes JSON null, as in llama.cpp. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(functions): add an lfm3 XML format preset The LFM3 chat template writes a call as <tool_call>, <function=NAME>, one <parameter=KEY> block per argument, then the closing tags. The qwen3-coder format has the same shape and auto-detection already found it, but xml_format_preset: lfm3 was not a known name. Add the preset, with tests for one call, a call without arguments, two calls, string and non-string values (numbers, booleans, null, arrays, objects), a call between text, and unfinished input. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(realtime): parse tool calls with the chat template, as upstream Since ggml-org/llama.cpp#31287 the realtime server handles tools with the parser and grammar of the chat template. Do the same in the speech-to-speech stream instead of a hard-coded tag filter and a parse of the whole text at the end. - Each response gets a common_chat_session with the tools of the session and parallel calls. Its grammar and preserved tokens go into the sampling of the response, so the slot keeps the call tokens in the text and constrains the call. - The text of the response is fed to the session piece by piece (task_result_state::update_chat_msg). Content deltas become the transcript; tool-call deltas are collected into one function_call_arguments.done per call at the end. Audio after the start of a call is dropped. - A tool_choice of auto, none or required is accepted in the params of the config, in session.update and in response.create, with the response overriding the session. - The tool turn is derived from a conversation in which the assistant called a tool, like upstream, not from a plain reply. - response.done carries raw_text, everything the model wrote with the call markup, for a caller that parses it another way. If the parser throws, the rest of the response is content and the raw text is the way out. ToolCallFilter is no longer used and goes in the next commit. The unit tests cover the splitter contract (content, call deltas, the final piece, whole characters only). realtime_chat_check feeds LFM3-format text through the session in pieces of 1, 7 and all bytes and checks the call, the transcript and the grammar. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * refactor(realtime): drop the tool-call text filter The parser of the chat template now splits content from tool calls, so ToolCallFilter, which looked for the literal <tool_call> tags, has no caller. Remove it with its tests. Every case it covered (a call between text, split tags, several calls, an unfinished call, text after a call) is handled by the parser, and realtime_chat_check runs the LFM3 format through it in pieces of different sizes. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(realtime): fall back to the raw text for S2S tool calls For an any-to-any model, the calls the backend parsed still win. When there are none and the request had tools, parse the raw text that response.done now carries the way the chained pipeline parses the text of its LLM (CleanupLLMResult, ParseFunctionCall, ParseTextContent), so a call format the backend parser does not know can still be served with function.xml_format_preset or the other function settings of the model. The content around the calls replaces the streamed transcript only if the config captures it, as in the chained path. Also pass tool_choice to the backend: the session mode with the stream config and with session.update, and the mode of a response.create override with that event. A named function cannot be forced, so it is not sent. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): carry raw_text in the mock S2S response.done Match the llama-cpp session: with tools set, response.done has raw_text, the call markup of the LFM3 template for a tool turn and the transcript for any other reply. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(functions): parse tool blocks that have text between them The iterative XML parser stopped at the first scope block that was preceded by text, so "<tool_call>A</tool_call> then <tool_call>B" gave one call. Skip text between blocks once a call was found; text before the first block still ends the parse as before. Blocks that follow each other, or only whitespace, already worked. Tests cover three calls back to back and with text between them, with the lfm3 preset and with auto-detection. They also pin the current reading of argument values: the parser has no tool schema, so a value such as 42 or true is read as JSON even when the schema types it as a string. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(gallery): read LFM3-Audio tool calls with the lfm3 preset Set function.xml_format_preset: lfm3 on the eight LFM3-Audio-2B entries and in the config the importer writes for a speech-to-speech projector, so the server parses the call format of the LFM3 chat template when the backend returns no call (and in the chained pipeline entries, which use the regular chat path). gallery/index.yaml is regenerated from the library. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(realtime): describe how S2S tool calls are read Replace the description of the hard-coded tag filter with the parser and grammar of the chat template, tool_choice, and the raw-text fallback in the server. Document function.xml_format_preset, and set it in the real-weights e2e model configs like the gallery entries do. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(functions): keep earlier calls when a later block does not parse After a valid call, text that holds the opening tag again (a prose mention, a markdown example, a malformed second block) made the parser treat it as a new block, fail, and return no calls at all. Remember the state after each complete block and fall back to it when a later one fails: the calls found so far are kept, parsing stops there and the remaining text stays in the content. Text skipped between two valid blocks now goes to the content instead of being dropped, so StripToolCallMarkup keeps it. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): do not drop audio after a tool call started In mixed mode the speech for text written before a call can still be pending when the call starts, so dropping every audio frame after the first call delta cut the speech while its transcript was sent. The llama.cpp server never drops audio when it reads a result; do the same. If a model speaks the call markup aloud, that is model behaviour to observe on real weights, not something to filter here. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): flush held-back text when a response is cancelled The parser got is_partial=true on a cancelled response, so text it held back (a possible start of a tag) never reached the transcript. The llama.cpp server parses a cancelled response with is_partial=false; do the same. A cancelled response still makes no tool call. The path where the parser throws already sends the text as content and needs no change. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(realtime): stop the fake S2S stream from sending after Close The barge-in spec failed now and then with a data race and "send on closed channel": its script pushes audio from a goroutine, and the session closes the stream while that goroutine still runs. A push now does nothing once the stream is closed, and Close waits for a push in flight. The production close order is right: the writer closes the stream only after it stops sending. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): align the S2S tool-call fallback with the backend Four gaps in how the Go side reads the tool calls of an any-to-any reply: - With tool_choice none (the response value wins over the session value) the backend builds no tool parser, yet the raw-text fallback still turned a call the model wrote into a function_call item. Skip it. - Without a capture regex the fallback never cleaned the transcript, so the stored item and transcript.done kept the raw call markup. When the call is XML, use the text left after the markup is cut out; a reply that is only a call now has an empty transcript. - A named function as tool_choice mapped to nothing, so the backend kept the previous mode. Refuse it with an error in session.update and response.create. - A response-level tools list changed what the fallback looked for, but the backend only has the tools of the session. Use those in both. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * docs(realtime): say how S2S tool_choice and response tools behave Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(e2e): use the real llama-cpp backend in the real realtime run The suite always registered the mock backend under the name llama-cpp, so a run with REALTIME_S2S_MODEL set never reached the llama-cpp backend found in REALTIME_BACKENDS_PATH. Register the mock only when that run is off. The real run converts the input audio with ffmpeg. Check for it in the suite and in run-realtime-lfm3.sh before any download or backend start, with a message that says what to install, and list it as a prerequisite in the script header and the Makefile target. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(realtime): cover the tools path of a client session.update to the S2S backend A tool call that never came back from an any-to-any model raised the question whether the tools of the session reach the backend at all. Add a spec that sends the session.update of the real-weights e2e (flat Responses API tool, no session type) and checks that the backend gets the tool in the chat-completions form. Log the number of tools sent when the stream opens and when a session.update carries them, so a run log shows it without reading the wire. The "No tools received" line of the llama-cpp backend does not show it: the server prints it for the input transcription request, which has no tools by design. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): interrupt the chained pipeline reply while it is in flight With unary stages, the default of the lfm3 pipeline gallery entries, the chained pipeline synthesizes the whole reply and sends it as one audio delta right before response.done. The barge-in spec waited for that delta and then rendered the interruption with the TTS, so the first response was already done when the speech started and ended completed instead of cancelled. Real runs on a GPU showed it with both quantizations. For a pipeline, start the interruption at response.created, which comes before the LLM and TTS stages. Render the interruption audio before the first utterance is sent, so nothing sits between the reply and the interruption. The any-to-any shape still waits for the first audio delta, which that backend streams while it generates. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): accept a number word and its homophone in the spoken-answer check The check that the reply audio says what the transcript says needs a shared word of 4+ letters. A short answer failed it: the model wrote "Four" and the ASR heard "for". Compare numbers in one spoken form, so digits, number words and their homophones match each other. A homophone only counts when the other text has the number itself, so "for" on both sides still shares nothing and the check does not pass on a stop word. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(llama-cpp): ask LFM3-Audio for function calls when a realtime session has tools The default system prompt of a realtime session was "Respond with interleaved text and audio." whatever the tools. Under that prompt LFM3-Audio answers in words ("I'll check the weather ...") and never writes the call. The model card, and the llama.cpp realtime example client, use "Respond with function calls, and interleaved text and audio." when the session has tools, and the model then calls the tool. Pick the default by the tools of the session, and treat the plain default that the LocalAI server sends back as no choice: it follows the tools too, also when they arrive with a session.update before the first audio. A prompt the client wrote is kept as it is. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): transcribe the input of an any-to-any turn between responses The input transcript of a speech-to-speech turn ran at the same time as the reply, on the same model. llama.cpp serves both from one context: the realtime slot wants embeddings on, the chat slot of the transcript wants them off, and each flip makes it reserve its compute buffers again. In a GB10 run that was two reserves of about 175 ms per step, the reply fell to 2.6 tokens per second and the transcript to 380 ms per token, until the transcript was done. When pipeline.transcription is the model of the stream, run the transcript after the response, or before one that is not started yet, and keep responses out while it runs. A separate transcription model still runs in parallel. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): send the input transcript of an any-to-any turn before its reply The previous change moved the same-model transcript after the response. A client that reads until response.done then never saw conversation.item.input_audio_transcription.completed: the transcript was made (the log shows it) but sent after the end of the window. Run it in line with the commit instead. A VAD turn transcribes and then responds. A client response.create already waits for the commit slot, so it starts once the transcript is out. The transcript event now comes before any output of the response of its turn, for a manual commit and for a VAD turn, and never overlaps the response on the llama.cpp context. A transcription model of its own still runs in parallel. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(realtime): transcribe the input of an any-to-any model only when asked The stream answers from the audio, so the transcript of the input is for the client only. On the model of the stream it is costly: llama.cpp serves the transcript request and the realtime stream from one context and rebuilds its compute buffers on every step while both are active. On a GB10 that was 26 to 29 reserves of about 175 ms per turn, against 0 to 1 on the llama.cpp server, and the reply ran at a few tokens per second. An any-to-any model no longer fills pipeline.transcription with its own name. Without it the session does not transcribe the input; with another model it does, in parallel with the reply; with the model itself named it does, before the reply. The reply transcript always comes from the stream. The gallery entries set none, so they get the fast path. The mock configs and run-realtime-lfm3.sh name a transcription model, so the real-weights spec still covers the input transcript. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(realtime): end a response as cancelled when a barge-in lands during the LLM call The chained pipeline cancels the response context on a barge-in. When that happens while the model is generating, the backend call fails with the context error, and the response ended as failed: an error event and no response.done. A client that waits for the terminal of the interrupted response then waits forever. On a GB10 the q4 model answered before the barge-in arrived, so only the slower q8 model showed it: the spec read events for 30 s and timed out. When the context is cancelled, a failed Predict call or a failed result is a cancel, as the TTS step and the streamed LLM path already treat it. The response ends with response.done{cancelled} and no error. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): wait for the input transcript that comes from a model of its own An any-to-any model now gets its input transcript from a separate ASR that runs next to the reply. The reply is often over first, so a spec that stops reading at response.done saw no transcript in the VAD shape. Keep reading, up to 30 s, until the transcript arrives. The contract is that it comes, not when. Add a guard spec for what the input transcription is handed: the whole turn, in a WAV that says the rate it was recorded at (the session rate for a client commit, 16 kHz for a VAD turn). Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): check the errors of Close in the realtime specs golangci-lint (errcheck) flagged the unchecked Close of the websocket connections, the response body and the fake stream. Use the `defer func() { _ = x.Close() }()` form the other e2e specs use. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * test(e2e): write the mock S2S specs with Ginkgo and drop the env read forbidigo only allows Ginkgo/Gomega specs and no os.Getenv outside the sanctioned places. Convert the mock S2S tests to Describe/It with a suite bootstrap (the package had none), with the same 11 cases. The mock no longer reads MOCK_S2S_FRAMES: only these specs set it, so the reply length is a package variable they change per spec and restore afterwards. Assisted-by: Claude:claude-sonnet-5-5 Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
66 KiB
+++ disableToc = false title = "Model Configuration" weight = 23 url = '/advanced/model-configuration' +++
LocalAI uses YAML configuration files to define model parameters, templates, and behavior. This page provides a complete reference for all available configuration options.
Configuration scopes and precedence
[CLI flags and environment variables]({{% relref "reference/cli-reference" %}})
configure the LocalAI server process. Model YAML files configure one model,
while supported fields in an API request can override that model's defaults
for that request. For example, a request containing temperature overrides
the model YAML parameters.temperature only for that request.
Precedence is setting-specific rather than one universal ordering. For the
overlapping threads setting, an explicit nonzero server --threads value is
applied after model YAML and therefore wins over the YAML threads value.
Most server flags have no model YAML equivalent, so consult the relevant
reference for the scope of each setting.
Overview
Model configuration files allow you to:
- Define default parameters (temperature, top_p, etc.)
- Configure prompt templates
- Specify backend settings
- Set up function calling
- Configure GPU and memory options
- And much more
Configuration File Locations
You can create model configuration files in several ways:
- Individual YAML files in the models directory (e.g.,
models/gpt-3.5-turbo.yaml) - Single config file with multiple models using
--models-config-fileorLOCALAI_MODELS_CONFIG_FILE - Remote URLs - specify a URL to a YAML configuration file at startup
Example: Basic Configuration
name: gpt-3.5-turbo
parameters:
model: luna-ai-llama2-uncensored.ggmlv3.q5_K_M.bin
temperature: 0.3
context_size: 512
threads: 10
backend: llama-cpp
template:
completion: completion
chat: chat
Example: Multiple Models in One File
When using --models-config-file, you can define multiple models as a list:
- name: model1
parameters:
model: model1.bin
context_size: 512
backend: llama-cpp
- name: model2
parameters:
model: model2.bin
context_size: 1024
backend: llama-cpp
LocalAI changes only config files that are inside the models directory. If the file from --models-config-file is outside the models directory, you cannot view, edit, pin, enable or disable its models from the web UI or the model admin API. Edit the file directly, then restart LocalAI.
Core Configuration Fields
Basic Model Settings
| Field | Type | Description | Example |
|---|---|---|---|
name |
string | Model name, used to identify the model in API calls | gpt-3.5-turbo |
backend |
string | Backend to use (e.g. llama-cpp, vllm, diffusers, whisper) |
llama-cpp |
description |
string | Human-readable description of the model | A conversational AI model |
usage |
string | Usage instructions or notes | Best for general conversation |
Model File and Downloads
| Field | Type | Description |
|---|---|---|
parameters.model |
string | Path to the model file (relative to models directory) or URL |
download_files |
array | List of files to download. Each entry has filename, uri, and optional sha256 |
Example:
parameters:
model: my-model.gguf
download_files:
- filename: my-model.gguf
uri: https://example.com/model.gguf
sha256: abc123...
Model artifacts
The artifacts section makes installation of a Hugging Face model eager and
repeatable. LocalAI resolves the requested revision to an immutable commit,
downloads the selected repository files, and commits the complete snapshot
before the model installation succeeds.
artifacts:
- name: model
target: model
source:
type: huggingface
repo: Qwen/Qwen3-ASR-1.7B
revision: main
token_env: HF_TOKEN
resolved:
endpoint: https://huggingface.co
revision: 0123456789abcdef0123456789abcdef01234567
cache_key: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
parameters:
model: Qwen/Qwen3-ASR-1.7B
Declare source when authoring a configuration. LocalAI owns the resolved
block and writes it after installation; do not choose its values manually.
For a public repository, omit token_env. For a private or gated repository,
set it to HF_TOKEN and provide that environment variable to the LocalAI
controller.
| Field | Meaning |
|---|---|
name |
Logical artifact name; model for the initial primary artifact |
target |
Binding target; only model is supported initially |
source.type |
huggingface |
source.repo |
owner/repository or hf://owner/repository |
source.revision |
Branch, tag, or commit; defaults to main and resolves to a commit |
source.token_env |
Empty or HF_TOKEN; the secret value is never persisted |
source.allow_patterns |
Optional slash-separated glob allow-list |
source.ignore_patterns |
Optional slash-separated glob deny-list |
resolved |
Installer-owned immutable endpoint, revision, and cache key |
Managed installation finishes only after every selected file is committed
locally. parameters.model remains the logical repository ID. Once
resolved.cache_key is present, LocalAI derives
.artifacts/huggingface/<cache-key>/snapshot as the runtime ModelFile.
Configurations without artifacts keep the existing lazy repository-ID
behavior.
The initially migrated backend families are transformers and its aliases,
diffusers, qwen-asr, fish-speech, nemo, voxcpm, qwen-tts,
vllm, vllm-omni, and sglang. Automatic imports add
artifact declarations only for this set. Compatible external backends may opt
in by declaring the artifact explicitly.
GGUF header cache
When a model config does not set a value itself, LocalAI reads the header of its GGUF file to choose defaults. The header gives the layer count, the trained context length, the chat template, the token IDs, and whether the file has an embedded multi-token-prediction head. Reading a large header takes seconds, so LocalAI keeps the result in a cache.
Mode: shared by file. Every frontend that uses the same data path reads and writes one cache file, header-cache.json. A frontend can therefore use the headers another frontend has already read. This mode is valid only while all frontends mount the same data volume.
Set an instance ID (--instance-id or LOCALAI_INSTANCE_ID) to give a frontend its own file, header-cache-<id>.json, instead of the shared one. It then keeps its own entries across restarts and does not share them with other frontends. An instance ID that is not a plain token (letters, digits, - and _, up to 64 characters) is hashed into the file name. In distributed mode, an ID that LocalAI generates at startup does not select a file of its own, because it changes on every start; such a frontend uses the shared file.
{{% notice warning %}} All frontends that share this cache must mount the same data volume. With a ReadWriteOnce volume, the volume attaches to one node at a time, so all frontends that share the cache must run on that one node. A frontend on another node cannot mount the volume and will not start. Sharing this cache across nodes needs a ReadWriteMany volume or a shared store, and this mode does not support it. {{% /notice %}}
What is stored
Each entry is keyed by the absolute path of the file, its size and its modification time. If any of the three changes, the entry is not used and the header is read again.
The file keeps the values that come from the file: the architecture and name, the trained context length, the chat template, the token IDs, the multi-token-prediction head, and the estimates that depend only on the file and on LocalAI's fixed defaults. The file also records the gguf-parser version and those defaults, so a LocalAI upgrade that changes them ignores the old file.
The file does not store anything about the machine: GPU and memory sizes, and your settings. Those are read again when a model loads, so the same file gives the same defaults on a different host.
The cache holds at most 256 entries. The least recently used entry is dropped first. Changes are written in the background after a short pause, never while a request waits.
How frontends share the file
- Merge on save. Before writing, a frontend reads the file and merges its entries into its own. When both hold an entry for the same model file, the one that matches the model file on disk is kept. Entries that match no current file are dropped. The 256-entry bound applies to the merged set.
- Atomic write. The new file is written to a temporary file in the same directory and renamed into place. A reader sees either the old file or the new one, never a partial file.
- Reload on miss. A frontend reads the file when it starts. Later, it checks the file only when a header lookup misses, and only when the file's modification time differs from the one it last read. It reads the file at most once per miss. A lookup that hits the cache never reads the file.
- Concurrent saves. Two frontends can save at the same moment. The later rename wins, so an entry that the other frontend saved in that moment can be lost. A lost entry costs one header parse the next time that model file is used. It never gives a wrong value, because every entry is checked against its model file before it is used.
Where the file lives
The file is in the data path (--data-path, default data/ under the LocalAI base path):
header-cache.json, in standalone mode and for every frontend that does not set an instance ID. Frontends that mount the same data path share it.header-cache-<id>.json, for a frontend that sets an instance ID (--instance-idorLOCALAI_INSTANCE_ID).- No data path: the cache is kept in memory only. Each start parses the headers again.
Files named gguf-header-cache*.json come from earlier versions. LocalAI does not read them, so you can delete them.
Clearing the cache
Stop the frontends that share the file and delete header-cache.json. It is rebuilt as models are used, one parse per model file. Deleting the file while a frontend runs is not reliable: that frontend writes its in-memory entries back after the next change.
Parameters Section
The parameters section contains all OpenAI-compatible request parameters and model-specific options.
OpenAI-Compatible Parameters
These settings will be used as defaults for all the API calls to the model.
| Field | Type | Default | Description |
|---|---|---|---|
temperature |
float | 0.9 |
Sampling temperature (0.0-2.0). Higher values make output more random |
top_p |
float | 0.95 |
Nucleus sampling: consider tokens with top_p probability mass |
top_k |
int | 40 |
Consider only the top K most likely tokens |
max_tokens |
int | 0 |
Maximum number of tokens to generate (0 = unlimited) |
frequency_penalty |
float | 0.0 |
Penalty for token frequency (-2.0 to 2.0) |
presence_penalty |
float | 0.0 |
Penalty for token presence (-2.0 to 2.0) |
repeat_penalty |
float | 1.1 |
Penalty for repeating tokens |
repeat_last_n |
int | 64 |
Number of previous tokens to consider for repeat penalty |
seed |
int | -1 |
Random seed (omit for random) |
echo |
bool | false |
Echo back the prompt in the response |
n |
int | 1 |
Number of completions to generate |
logprobs |
bool/int | false |
Return log probabilities of tokens |
top_logprobs |
int | 0 |
Number of top logprobs to return per token (0-20) |
logit_bias |
map | {} |
Map of token IDs to bias values (-100 to 100) |
typical_p |
float | 1.0 |
Typical sampling parameter |
tfz |
float | 1.0 |
Tail free z parameter |
keep |
int | 0 |
Number of tokens to keep from the prompt |
{{% notice note %}}
The DS4 backend preserves its legacy behavior for omitted or non-positive
max_tokens values by generating at most 256 tokens. Set max_tokens to a
positive value when you need a specific DS4 output limit. After processing the
prompt, DS4 clamps that limit to the available context space and reserves one
context slot for safe generation.
{{% /notice %}}
Language and Translation
| Field | Type | Description |
|---|---|---|
language |
string | Language code for transcription/translation |
translate |
bool | Whether to translate audio transcription |
Custom Parameters
| Field | Type | Description |
|---|---|---|
batch |
int | Batch size for processing |
ignore_eos |
bool | Ignore end-of-sequence tokens |
negative_prompt |
string | Negative prompt for image generation |
rope_freq_base |
float32 | RoPE frequency base |
rope_freq_scale |
float32 | RoPE frequency scale |
negative_prompt_scale |
float32 | Scale for negative prompt |
tokenizer |
string | Tokenizer to use (RWKV) |
LLM Configuration
These settings apply to most LLM backends (llama.cpp, vLLM, etc.):
Performance Settings
| Field | Type | Default | Description |
|---|---|---|---|
threads |
int | processor count |
Number of threads for parallel computation. A per-model value overrides the server-wide --threads/LOCALAI_THREADS setting |
context_size |
int | 512 |
Maximum context size in tokens. Set to -1 to auto-use the model's full trained context from GGUF metadata (raw max, no VRAM capping; a warning is logged if it may not fit detected VRAM). |
f16 |
bool | false |
Enable 16-bit floating point precision (GPU acceleration) |
gpu_layers |
int | 99999999 |
Number of layers to offload to GPU. The default requests all layers; 0 keeps model layers on CPU. See mixed CPU/GPU inference. |
Memory Management
| Field | Type | Default | Description |
|---|---|---|---|
mmap |
bool | true |
Use memory mapping for model loading (faster, less RAM) |
mmlock |
bool | false |
Lock model in memory (prevents swapping) |
low_vram |
bool | false |
Use minimal VRAM mode |
no_kv_offloading |
bool | false |
Disable KV cache offloading |
GPU Configuration
| Field | Type | Description |
|---|---|---|
tensor_split |
string | Comma-separated GPU memory allocation (e.g., "0.8,0.2" for 80%/20%) |
main_gpu |
string | Main GPU identifier for multi-GPU setups |
cuda |
bool | Explicitly enable/disable CUDA |
Placement
The model page's Configuration tab and the model editor have a Placement
section for the keys above. It edits gpu_layers, tensor_split, main_gpu
and context_size and nothing else.
- CPU only writes
gpu_layers: 0. - Auto leaves
gpu_layersunset. LocalAI then asks the backend for every layer (99999999), and thellama-cppbackend lowers that to what fits in the free device memory unless the model turns offfit_params(see GPU auto-fit settings). Other backends read an unset value as their own default. - Custom writes the number you enter. All layers writes
99999999, the value LocalAI uses when the key is unset. A slider appears when the memory estimate reports the model's layer count (block_count); today it does not, so you enter a number. - With two or more GPUs, Split across GPUs writes
tensor_splitas percentages (for example65,35; llama.cpp reads them as proportions) and Main GPU writesmain_gpu. Split by free memory sets the shares from the memory each card has free now.
The bars show, for each GPU and for system memory, what other programs use, what
this model needs (weights and working memory, and the KV cache that grows with
context_size) and what is left. They come from the device list in
/api/resources and from POST /api/models/vram-estimate, which returns one
total for the chosen context size and layer count. The per-device split of that
total follows tensor_split, so it is an approximation. The page says what the
choice means in words ("Fits in GPU", "Spills to CPU", "Too many layers for the
GPU") and does not state a speed. A usable limit is 95 percent of the free
memory.
Fit it for me searches for the largest gpu_layers whose estimate fits the
free GPU memory, by asking the estimate endpoint, and Undo puts the previous
value back. It is hidden when the estimate is unavailable (the model file is
missing, still downloading or in a format the estimate does not cover) and when
the host has no GPU. A server that schedules models onto other machines shows no
per-device bars, because the devices listed belong to the controller.
Saving from the model page sends only the keys that changed. A key set back to
Auto is sent as null. The patch endpoint merges values into the file and cannot
delete a key, so the file then reads gpu_layers: null, which LocalAI treats as
unset. To remove the line itself, edit the YAML.
Mixed CPU/GPU inference
The llama-cpp backend can run one GGUF model across CPU and GPU, using both system RAM and GPU VRAM.
Use a GPU-capable build of the backend for your hardware.
A CPU-only build cannot offload layers to the GPU.
Offload some model layers
Set gpu_layers to a positive number smaller than the model's layer count.
The remaining layers run on CPU.
Merge these settings into your existing model YAML, keeping its model path, template, and other options:
backend: llama-cpp
gpu_layers: 12
context_size: 4096
The value 12 is an example, not a memory estimate.
Reload the model after changing its configuration.
Check the backend startup log for the number of layers offloaded and the CPU/GPU buffer sizes.
Increase gpu_layers if VRAM has room; reduce it if loading runs out of GPU memory.
Set gpu_layers: 0 to keep all model layers on CPU.
Keep MoE experts on CPU
For a mixture-of-experts (MoE) model, you can keep expert weights in system RAM while offloading other tensors to the GPU:
backend: llama-cpp
gpu_layers: 99999999
context_size: 4096
options:
- cpu_moe:true
Append cpu_moe:true to any existing options list instead of replacing that list.
This option applies to the main model's expert weights.
To keep experts from only the first 12 layers on CPU, replace cpu_moe:true with n_cpu_moe:12.
Use one of these options at a time.
To speed up decoding when experts stay in host memory, you can add a GPU cache for them with moe_cache_mib:
backend: llama-cpp
gpu_layers: 99999999
context_size: 4096
options:
- cpu_moe:true
- moe_cache_mib:4096
The value is a size in MiB.
It is off by default (0).
It helps only when expert weights are in host memory, that is with cpu_moe, n_cpu_moe or a spill from GPU auto-fit.
The cache uses GPU memory, so lower gpu_layers or the auto-fit result may change when you turn it on.
The feature is experimental upstream.
A cache that is too small can thrash, because every token evicts its own experts.
A rough starting point is about 10% of the total expert size.
LocalAI sets no value on its own.
CPU execution and data transfers can reduce generation speed compared with a model that fits entirely on GPU.
RAM and VRAM do not form one interchangeable allocation pool.
Leave memory for the KV cache, compute buffers, the operating system, and other processes.
Reduce context_size if the KV cache consumes too much memory.
The GPU auto-fit settings provide a separate way to let llama.cpp choose the allocation.
Lazy tensor loading
Some models have very large tensors that are used sparsely, such as per-layer embeddings.
The lazy_mode option (also lazy-mode) sets upstream --lazy-mode: it controls whether llama.cpp reads those tensors on demand instead of loading them all at startup.
backend: llama-cpp
options:
- lazy_mode:on
| Value | Behavior |
|---|---|
auto |
Default. Read on demand only the marked tensors larger than 4 GiB. |
on |
Read on demand every marked tensor. Requires mmap (enabled by default). |
off |
Load everything at startup. |
An invalid value logs a warning and keeps the default.
The option needs a llama.cpp build that has --lazy-mode.
Sampling and Generation
| Field | Type | Default | Description |
|---|---|---|---|
mirostat |
int | 0 |
Mirostat sampling mode (0=disabled, 1=Mirostat, 2=Mirostat 2.0) |
mirostat_tau |
float | 5.0 |
Mirostat target entropy |
mirostat_eta |
float | 0.1 |
Mirostat learning rate |
LoRA Configuration
| Field | Type | Description |
|---|---|---|
lora_adapter |
string | Path to LoRA adapter file |
lora_base |
string | Base model for LoRA |
lora_scale |
float32 | LoRA scale factor |
lora_adapters |
array | Multiple LoRA adapters |
lora_scales |
array | Scales for multiple LoRA adapters |
Advanced Options
| Field | Type | Description |
|---|---|---|
no_mulmatq |
bool | Disable matrix multiplication queuing |
draft_model |
string | Draft model GGUF file for speculative decoding (see Speculative Decoding) |
n_draft |
int32 | Maximum number of draft tokens per speculative step (default: 16) |
quantization |
string | Quantization format |
load_format |
string | Model load format |
numa |
bool | Enable NUMA (Non-Uniform Memory Access) |
rms_norm_eps |
float32 | RMS normalization epsilon |
ngqa |
int32 | Natural question generation parameter |
rope_scaling |
string | RoPE scaling configuration |
type |
string | Model type/architecture |
grammar |
string | Grammar file path for constrained generation |
YARN Configuration
YARN (Yet Another RoPE extensioN) settings for context extension:
| Field | Type | Description |
|---|---|---|
yarn_ext_factor |
float32 | YARN extension factor |
yarn_attn_factor |
float32 | YARN attention factor |
yarn_beta_fast |
float32 | YARN beta fast parameter |
yarn_beta_slow |
float32 | YARN beta slow parameter |
Speculative Decoding
Speculative decoding speeds up text generation by predicting multiple tokens ahead and verifying them in a single forward pass. The output is identical to normal decoding - only faster. This feature is only available with the llama-cpp backend.
There are two approaches:
Draft Model Speculative Decoding
Uses a smaller, faster model from the same model family to draft candidate tokens, which the main model then verifies. Requires a separate GGUF file for the draft model.
name: my-model
backend: llama-cpp
parameters:
model: large-model.gguf
draft_model: small-draft-model.gguf
n_draft: 8
options:
- spec_p_min:0.8
- draft_gpu_layers:99
N-gram Self-Speculative Decoding
Uses patterns from the token history to predict future tokens - no extra model required. Works well for repetitive or structured output (code, JSON, lists).
name: my-model
backend: llama-cpp
parameters:
model: my-model.gguf
options:
- spec_type:ngram_simple
- spec_n_max:16
Speculative Decoding Options
These are set via the options: array in the model configuration (format: key:value):
Common options
| Option | Type | Default | Description |
|---|---|---|---|
spec_type / speculative_type |
string | none |
Speculative decoding type, or comma-separated list to chain multiple (see table below) |
spec_n_max / draft_max |
int | 16 | Maximum number of tokens to draft per step |
spec_n_min / draft_min |
int | 0 | Minimum draft tokens required to use speculation |
spec_p_min / draft_p_min |
float | 0.75 | Minimum probability threshold for greedy acceptance |
spec_p_split |
float | 0.1 | Split probability for tree-based branching |
Draft-model options (apply when spec_type=draft, i.e. a draft_model is configured)
| Option | Type | Default | Description |
|---|---|---|---|
draft_gpu_layers |
int | -1 | GPU layers for the draft model (-1 = use default) |
draft_threads / spec_draft_threads |
int | same as main | Threads used by the draft model (<= 0 = hardware concurrency) |
draft_threads_batch / spec_draft_threads_batch |
int | same as draft_threads |
Threads used by the draft model during batch / prompt processing |
draft_cache_type_k / spec_draft_cache_type_k |
string | f16 |
KV cache K data type for the draft model (same values as cache_type_k) |
draft_cache_type_v / spec_draft_cache_type_v |
string | f16 |
KV cache V data type for the draft model |
draft_cpu_moe / spec_draft_cpu_moe |
bool | false | Keep all MoE expert weights of the draft model on CPU |
draft_n_cpu_moe / spec_draft_n_cpu_moe |
int | 0 | Keep MoE expert weights of the first N draft-model layers on CPU |
draft_override_tensor / spec_draft_override_tensor |
string | "" | Comma-separated <tensor regex>=<buffer type> overrides for the draft model |
draft_ctx_size |
int | (ignored) | Deprecated upstream: the draft now shares the target context size. Accepted for backward compatibility but has no effect. |
ngram_simple options (used when spec_type includes ngram_simple)
| Option | Type | Default | Description |
|---|---|---|---|
spec_ngram_size_n / ngram_size_n |
int | 12 | N-gram lookup size |
spec_ngram_size_m / ngram_size_m |
int | 48 | M-gram proposal size |
spec_ngram_min_hits / ngram_min_hits |
int | 1 | Minimum hits for accepting n-gram proposals |
ngram_mod options (used when spec_type includes ngram_mod)
| Option | Type | Default | Description |
|---|---|---|---|
spec_ngram_mod_n_min |
int | 48 | Minimum number of ngram tokens to use |
spec_ngram_mod_n_max |
int | 64 | Maximum number of ngram tokens to use |
spec_ngram_mod_n_match |
int | 24 | Ngram lookup length |
ngram_map_k options (used when spec_type includes ngram_map_k)
| Option | Type | Default | Description |
|---|---|---|---|
spec_ngram_map_k_size_n |
int | 12 | N-gram lookup size |
spec_ngram_map_k_size_m |
int | 48 | M-gram proposal size |
spec_ngram_map_k_min_hits |
int | 1 | Minimum hits for accepting proposals |
ngram_map_k4v options (used when spec_type includes ngram_map_k4v)
| Option | Type | Default | Description |
|---|---|---|---|
spec_ngram_map_k4v_size_n |
int | 12 | N-gram lookup size |
spec_ngram_map_k4v_size_m |
int | 48 | M-gram proposal size |
spec_ngram_map_k4v_min_hits |
int | 1 | Minimum hits for accepting proposals |
ngram_cache lookup files
| Option | Type | Default | Description |
|---|---|---|---|
spec_lookup_cache_static / lookup_cache_static |
string | "" | Path to a static ngram lookup cache file |
spec_lookup_cache_dynamic / lookup_cache_dynamic |
string | "" | Path to a dynamic ngram lookup cache file (updated by generation) |
Speculative Type Values
The canonical names match upstream llama.cpp (dash-separated). For backward compatibility LocalAI also accepts the underscore-separated forms and the bare draft / eagle3 aliases.
| Type | Aliases accepted | Description |
|---|---|---|
none |
No speculative decoding (default) | |
draft-simple |
draft, draft_simple |
Draft model-based speculation (auto-set when draft_model is configured) |
draft-eagle3 |
eagle3, draft_eagle3 |
EAGLE3 draft model architecture |
draft-mtp |
draft_mtp |
Multi-Token Prediction. Reuses the target model's embedded MTP head; no separate draft GGUF required (draft_model can be omitted). |
ngram-simple |
ngram_simple |
Simple self-speculative using token history |
ngram-map-k |
ngram_map_k |
N-gram with key-only map |
ngram-map-k4v |
ngram_map_k4v |
N-gram with keys and 4 m-gram values |
ngram-mod |
ngram_mod |
Modified n-gram speculation |
ngram-cache |
ngram_cache |
3-level n-gram cache |
Multiple types can be chained by passing a comma-separated list to spec_type (e.g. spec_type:ngram-simple,ngram-mod). The runtime tries them in order and accepts the first proposal that meets the acceptance criteria.
{{% notice note %}}
The current LocalAI llama.cpp backend supports speculative decoding with multimodal models that load an mmproj, including MTP. LocalAI passes both configurations to llama.cpp and does not disable speculation merely because an mmproj is present. Upstream llama.cpp removed the former general multimodal/speculative restriction in ggml-org/llama.cpp#19493; ggml-org/llama.cpp#22673 later added MTP support and explicitly documented its compatibility with vision input.
Compatibility still depends on the installed backend version and the target/draft model architecture. Check the backend logs for successful projector loading and speculative-context initialization, then look for the draft acceptance statistics line and its accepted / generated counts. A representative run with zero accepted draft tokens receives no speculative speedup and can indicate that the model or settings need tuning.
{{% /notice %}}
Multi-Token Prediction (MTP)
draft-mtp enables Multi-Token Prediction (ggml-org/llama.cpp#22673). MTP uses a small prediction head trained into the target model: the head runs alongside the main forward pass and proposes the next few tokens, which the target then verifies in a single batched step. Upstream reports ~1.85x-2.1x token throughput at ~72-82% draft acceptance on Qwen3.6 27B / 35B A3B.
Auto-detection (default). When a GGUF declares an MTP head (the upstream <arch>.nextn_predict_layers metadata key, set by convert_hf_to_gguf.py for Qwen3.5/3.6 family models and similar), LocalAI auto-enables MTP with the following defaults:
options:
- spec_type:draft-mtp
- spec_n_max:6
- spec_p_min:0.75
Detection runs both at import time (the /import-model UI / POST /models/import-uri flow range-fetches the GGUF header and writes the options into the generated YAML before you save it) and at load time (every llama-cpp model start re-checks the local header and appends the options if spec_type isn't already set). To opt out, set an explicit spec_type: / speculative_type: in your YAML - auto-detection always preserves the user value, including spec_type:none.
Two ways to load the MTP head:
- Embedded in the target GGUF (the recommended path for LocalAI, and what auto-detection assumes). When
spec_typeincludesdraft-mtpanddraft_modelis empty, the backend builds the MTP draft context directly from the target model's weights. The GGUF must have been converted with the MTP tensors included. - Separate
mtp-*.ggufsibling file. If you pointdraft_modelat the separate MTP-head GGUF that ships next to the main weights on HuggingFace, the backend will load it as a draft model. Note: upstream's-hfauto-discovery ofmtp-*.ggufsiblings is not wired into LocalAI's gRPC layer - you need to download the sibling file and configuredraft_modelexplicitly.
Manual override knobs (overlap with the auto-detect defaults above):
| Option | Recommended | Notes |
|---|---|---|
spec_type |
draft-mtp |
Activates MTP. Can be chained with other types (see below). |
spec_n_max / draft_max |
2-6 |
Number of draft tokens per step. Upstream's PR suggests 2-3 for the tightest acceptance window; LocalAI's auto-default is 6 to favour throughput on models with high acceptance. |
spec_p_min |
0.75 |
Pinned because upstream marks the current default with a "change to 0.0f" TODO; locking it here keeps acceptance thresholds stable across future llama.cpp bumps. |
mmproj_use_gpu |
true for vision |
MTP does not require disabling the projector. Keep mmproj configured for image input; set this option to false to keep the projector on CPU when VRAM is tight. Remove mmproj only for text-only use when vision is not needed. |
Minimal config (override-only, since auto-detection already covers this for MTP-capable GGUFs):
name: qwen3-mtp
backend: llama-cpp
parameters:
model: qwen3-27b-with-mtp.gguf
options:
- spec_type:draft-mtp
- spec_n_max:3
With vision enabled:
name: qwen3-vision-mtp
backend: llama-cpp
known_usecases:
- chat
- vision
parameters:
model: qwen3-with-mtp.gguf
mmproj: mmproj-qwen3.gguf
options:
- spec_type:draft-mtp
- spec_n_max:3
- spec_p_min:0.75
With a separate MTP head file:
name: qwen3-mtp
backend: llama-cpp
parameters:
model: qwen3-27b.gguf
draft_model: qwen3-27b-mtp-head.gguf
options:
- spec_type:draft-mtp
- spec_n_max:3
Chaining MTP with n-gram fallback (experimental, from the PR's usage notes - useful when MTP acceptance drops on highly repetitive output):
options:
- spec_type:draft-mtp,ngram-mod
- spec_n_max:3
- spec_ngram_mod_n_match:24
Pre-converted GGUFs with MTP heads are published on the ggml-org HuggingFace org (initially Qwen3.6 27B and Qwen3.6 35B A3B).
Reasoning Models (DeepSeek-R1, Qwen3, etc.)
These load-time options control how the backend parses <think> reasoning blocks and how much budget the model is allowed for thinking. They are set per model via the options: array. For how reasoning is returned alongside tool calls and survives the tool-result round trip, see [Interleaved Thinking with Tool Calls]({{%relref "features/interleaved-thinking" %}}).
| Option | Type | Default | Description |
|---|---|---|---|
reasoning_format |
string | deepseek |
Parser for reasoning/thinking blocks. One of none, auto, deepseek, deepseek-legacy (alias deepseek_legacy). |
enable_reasoning / reasoning_budget |
int | -1 |
Reasoning budget in tokens: -1 unlimited, 0 disabled, >0 token cap for the thinking section. |
prefill_assistant |
bool | true |
When false, the trailing assistant message is not pre-filled by the chat template. |
{{% notice note %}}
This is the load-time reasoning configuration. The orthogonal per-request enable_thinking chat-template kwarg toggles thinking on/off per call without restarting the model. It can be driven either by the YAML reasoning.disable field (model default) or per request via the OpenAI reasoning_effort field on /v1/chat/completions:
reasoning_effort: "none"disables thinking for that request (enable_thinking=false) - useful to run a single reasoning model like Qwen3 for low-latency tasks while still enabling reasoning on other requests.reasoning_effort: "minimal" | "low" | "medium" | "high" | "xhigh" | "max"enables thinking, unless the model config explicitly setreasoning.disable: true(an operator's explicit disable wins and is never re-enabled by a request). {{% /notice %}}
reasoning_effort as a chat-template kwarg
reasoning_effort is also forwarded to the backend as a chat_template_kwarg, so models whose jinja chat template keys on it - e.g. gpt-oss (Harmony) or LFM2.5 - honor the level, not just the on/off enable_thinking flag. This matters for models that ignore enable_thinking entirely (LFM2.5 keeps emitting <think> for enable_thinking=false, but respects reasoning_effort).
Set a per-model default in the config so every request inherits it (a per-request reasoning_effort still overrides):
name: my-model
reasoning_effort: none # none | minimal | low | medium | high | xhigh | max
For [realtime pipelines]({{%relref "features/openai-realtime" %}}), set it on the pipeline so it applies to the pipeline's LLM without editing that model's own config:
name: gpt-realtime
pipeline:
llm: lfm2.5
reasoning_effort: none # overrides the LLM model's own reasoning_effort
Custom chat_template_kwargs
Some jinja chat templates expose extra variables beyond enable_thinking /
reasoning_effort (for example Qwen3's preserve_thinking). Set arbitrary key/values in
the model config and they are forwarded to the backend's chat_template_kwargs as-is, so
you don't need a dedicated server option per template variable:
name: qwen3
chat_template_kwargs:
preserve_thinking: true
You can also override (or add) any of these per request through the OpenAI metadata
field on /v1/chat/completions. Values are strings; "true" / "false" are coerced to
booleans, anything else is passed through as a string:
{
"model": "qwen3",
"messages": [{"role": "user", "content": "hi"}],
"metadata": { "preserve_thinking": "true", "enable_thinking": "false" }
}
Per-request metadata overrides the model config defaults and the reasoning-config levers,
and (for enable_thinking / reasoning_effort) takes effect across every backend that
reads them, not just llama.cpp. Typed (non-boolean) values are only supported through the
model YAML chat_template_kwargs, where YAML preserves the type.
Multimodal Backend Options
| Option | Type | Default | Description |
|---|---|---|---|
mmproj_use_gpu / mmproj_offload |
bool | true |
Set false to keep the multimodal projector on CPU (saves VRAM at cost of speed). |
image_min_tokens |
int | -1 |
Minimum vision tokens per image. -1 keeps the model default. |
image_max_tokens |
int | -1 |
Maximum vision tokens per image. -1 keeps the model default. |
Embedding & Reranking Backend Options
| Option | Type | Default | Description |
|---|---|---|---|
pooling_type / pooling |
string | auto | Pooling strategy for embeddings: none, mean, cls, last, rank. Reranking automatically uses rank. |
embd_normalize / embedding_normalize |
int | 2 |
Normalization: -1 none, 0 max-abs, 1 taxicab, 2 Euclidean (L2), >2 p-norm. |
Other Backend Tuning Options
These llama.cpp options are passed through the options: array.
| Option | Type | Default | Description |
|---|---|---|---|
n_ubatch / ubatch |
int | same as batch |
Physical batch size. Decouple from n_batch when an embedding/rerank workload needs a different value. |
threads_batch / n_threads_batch |
int | same as threads |
Threads used during prompt processing. <= 0 means hardware_concurrency(). |
direct_io / use_direct_io |
bool | false |
Open the model with O_DIRECT (faster cold loads on NVMe; ignored if not supported). |
verbosity |
int | 3 |
llama.cpp internal log verbosity threshold. Higher = more verbose. |
device / devices |
string | all devices | Select the llama.cpp backend devices to use. Repeat the option or pass a comma-separated list; unlisted devices are excluded. Use the names reported by llama-server --list-devices / --list-devices. |
override_tensor / tensor_buft_overrides |
string | "" | Per-tensor buffer-type overrides for the main model. Format: <tensor regex>=<buffer type>,<tensor regex>=<buffer type>,.... Mirrors the existing draft_override_tensor syntax for the draft model. |
cpu_moe |
bool | false | Keep all MoE expert weights of the main model on CPU (upstream --cpu-moe). Frees VRAM on large MoE models (DeepSeek, Qwen3 *-A3B). |
n_cpu_moe |
int | 0 | Keep MoE expert weights of the first N main-model layers on CPU (upstream --n-cpu-moe). |
Generic option passthrough
Any options: entry whose name starts with - is forwarded verbatim to
upstream llama.cpp's own llama-server argument parser. This means any flag the
bundled llama.cpp supports works without LocalAI needing a dedicated option,
even ones added after your LocalAI version was built. See the upstream
server flags reference.
Format mirrors the rest of the array - --flag for a boolean, or --flag:value
for a flag that takes a value. Everything after the first : is the value, so
embedded colons (e.g. host:port) are preserved:
options:
- "--cpu-moe" # boolean flag
- "--n-cpu-moe:4" # flag with a value
- "--override-tensor:exps=CPU"
- "devices:CUDA1,CUDA2,CUDA3" # skip CUDA0, e.g. a display GPU
Notes:
- Precedence: passthrough flags are applied last, so an explicit flag
overrides the LocalAI option it maps to (e.g.
--ctx-size:8192overridescontext_size). - Power-user territory: an invalid flag or value is rejected by the upstream
parser exactly as it would be by
llama-server, which can fail model loading. Prefer the named options above when one exists. - Flags that would terminate the process (such as
--help,--usage,--version,--license,--list-devices,--cache-list, and--completion*) are ignored.
Prompt Caching
The recommended way to enable prompt caching for the llama-cpp backend is the server-side prompt cache controlled by cache_ram / kv_unified / cache_idle_slots in the options: array (see [llama.cpp backend options]({{%relref "features/text-generation#server-side-prompt-cache-repeated-system-prompts" %}})). It's on by default since LocalAI v4.3 and is what gives repeated system prompts a near-zero prefill on the second call.
The fields below come from upstream llama.cpp's CLI completion tool and are passed through to the gRPC backend for compatibility, but the gRPC server itself does not consume them: keep them empty unless you're targeting a non-llama-cpp backend that reads them.
| Field | Type | Description |
|---|---|---|
prompt_cache_path |
string | (legacy / unused by llama-cpp gRPC server) Path to a file-backed prompt cache for upstream's CLI completion tool. |
prompt_cache_all |
bool | (legacy / unused by llama-cpp gRPC server) |
prompt_cache_ro |
bool | (legacy / unused by llama-cpp gRPC server) |
Text Processing
| Field | Type | Description |
|---|---|---|
stopwords |
array | Words or phrases that stop generation |
cutstrings |
array | Strings to cut from responses |
trimspace |
array | Strings to trim whitespace from |
trimsuffix |
array | Suffixes to trim from responses |
extract_regex |
array | Regular expressions to extract content |
System Prompt
| Field | Type | Description |
|---|---|---|
system_prompt |
string | Default system prompt for the model |
vLLM-Specific Configuration
These options apply when using the vllm backend:
The vllm backend validates sampling parameters before generation. Stop strings
work with streaming and non-streaming requests; an empty stop string is invalid.
Presence and frequency penalties must be between -2 and 2. A temperature of 0
selects greedy decoding, which ignores top_p and top_k. A seed of -1 uses
vLLM's default random seed behavior.
| Field | Type | Description |
|---|---|---|
gpu_memory_utilization |
float32 | GPU memory utilization (0.0-1.0, default 0.9) |
trust_remote_code |
bool | Trust and execute remote code |
enforce_eager |
bool | Force eager execution mode |
swap_space |
int | Swap space in GB |
max_model_len |
int | Maximum model length |
tensor_parallel_size |
int | Tensor parallelism size |
disable_log_stats |
bool | Disable logging statistics |
dtype |
string | Data type (e.g., float16, bfloat16) |
flash_attention |
string | Flash attention configuration |
cache_type_k |
string | Key cache quantization type. Maps to llama.cpp's -ctk. Accepted values for llama.cpp-family backends (llama-cpp, ik-llama-cpp, turboquant): f16, f32, q8_0, q4_0, q4_1, q5_0, q5_1. The turboquant backend additionally accepts turbo2, turbo3, turbo4 - the fork's TurboQuant KV-cache schemes. turbo3/turbo4 auto-enable flash_attention. |
cache_type_v |
string | Value cache quantization type. Maps to llama.cpp's -ctv. Same accepted values as cache_type_k. Note: any quantized V cache requires flash_attention to be enabled. |
limit_mm_per_prompt |
object | Limit multimodal content per prompt: {image: int, video: int, audio: int} |
Template Configuration
Templates use Go templates with Sprig functions.
| Field | Type | Description |
|---|---|---|
template.chat |
string | Template for chat completion endpoint |
template.chat_message |
string | Template for individual chat messages |
template.completion |
string | Template for text completion |
template.edit |
string | Template for edit operations |
template.function |
string | Template for function/tool calls |
template.multimodal |
string | Template for multimodal interactions |
template.reply_prefix |
string | Prefix to add to model replies |
template.use_tokenizer_template |
bool | Use tokenizer's built-in template (vLLM/transformers) |
template.system_messages_after_first |
string | What to do with system-role messages that appear after the leading system block: merge folds them into the first system message, user forwards them as user-role turns at their position. Unset keeps them as-is. Needed for tokenizer templates that reject late system turns (e.g. Qwen3.8) while agent frameworks append instructions mid-conversation. |
template.join_chat_messages_by_character |
string | Character to join chat messages (default: \n) |
Template Variables
Templating supports sprig functions.
Following are common variables available in templates:
{{.Input}}- User input{{.Instruction}}- Instruction for edit operations{{.System}}- System message{{.Prompt}}- Full prompt{{.Functions}}- Function definitions (for function calling){{.FunctionCall}}- Function call result
Example Template
template:
chat: |
{{.System}}
{{range .Messages}}
{{if eq .Role "user"}}User: {{.Content}}{{end}}
{{if eq .Role "assistant"}}Assistant: {{.Content}}{{end}}
{{end}}
Assistant:
Function Calling Configuration
Configure how the model handles function/tool calls:
| Field | Type | Default | Description |
|---|---|---|---|
function.disable_no_action |
bool | false |
Disable the no-action behavior |
function.no_action_function_name |
string | answer |
Name of the no-action function |
function.no_action_description_name |
string | Description for no-action function | |
function.function_name_key |
string | name |
JSON key for function name |
function.function_arguments_key |
string | arguments |
JSON key for function arguments |
function.response_regex |
array | Named regex patterns to extract function calls | |
function.argument_regex |
array | Named regex to extract function arguments | |
function.argument_regex_key_name |
string | key |
Named regex capture for argument key |
function.argument_regex_value_name |
string | value |
Named regex capture for argument value |
function.json_regex_match |
array | Regex patterns to match JSON in tool mode | |
function.replace_function_results |
array | Replace function call results with patterns | |
function.replace_llm_results |
array | Replace LLM results with patterns | |
function.capture_llm_results |
array | Capture LLM results as text (e.g., for "thinking" blocks) | |
function.xml_format_preset |
string | Force an XML tool-call format: qwen3-coder, qwen3.5, lfm3, glm-4.5, minimax-m2, kimi-k2 and others. Empty tries all of them |
Grammar Configuration
| Field | Type | Default | Description |
|---|---|---|---|
function.grammar.disable |
bool | false |
Completely disable grammar enforcement |
function.grammar.parallel_calls |
bool | false |
Allow parallel function calls |
function.grammar.mixed_mode |
bool | false |
Allow mixed-mode grammar enforcing |
function.grammar.no_mixed_free_string |
bool | false |
Disallow free strings in mixed mode |
function.grammar.disable_parallel_new_lines |
bool | false |
Disable parallel processing for new lines |
function.grammar.prefix |
string | Prefix to add before grammar rules | |
function.grammar.expect_strings_after_json |
bool | false |
Expect strings after JSON data |
Diffusers Configuration
For image generation models using the diffusers backend:
| Field | Type | Description |
|---|---|---|
diffusers.cuda |
bool | Force CUDA. By default the backend auto-detects and uses CUDA when a compatible GPU is present (ROCm builds included). Pin the CPU with options: ["device:cpu"] |
diffusers.pipeline_type |
string | Pipeline type (e.g., stable-diffusion, stable-diffusion-xl) |
diffusers.scheduler_type |
string | Scheduler type (e.g., euler, ddpm) |
diffusers.original_config_file |
string | Local path or URL to the original configuration for loading a single-file checkpoint |
diffusers.enable_parameters |
string | Comma-separated parameters to enable |
diffusers.cfg_scale |
float32 | Classifier-free guidance scale |
diffusers.img2img |
bool | Enable image-to-image transformation |
diffusers.clip_skip |
int | Number of CLIP layers to skip |
diffusers.clip_model |
string | CLIP model to use |
diffusers.clip_subfolder |
string | CLIP model subfolder |
diffusers.control_net |
string | ControlNet model to use |
step |
int | Number of diffusion steps |
TTS Configuration
For text-to-speech models:
| Field | Type | Description |
|---|---|---|
tts.voice |
string | Default backend voice ID, speaker name, or reference path. A request voice takes precedence. |
tts.audio_path |
string | Default reference-audio path for cloning backends. A request voice or saved Voice Library profile takes precedence. |
tts.voice_cloning |
bool | Optional Voice Library capability override. Omit for automatic backend/variant detection; true opts in a verified custom-named variant and false rejects saved profile references. |
For example, a custom-named model on a known cloning backend can declare support explicitly while retaining a model-wide reference fallback:
name: private-voice-model
backend: qwen3-tts-cpp
parameters:
model: private/qwen-talker-base.gguf
known_usecases:
- tts
tts:
voice_cloning: true
audio_path: voices/default-reference.wav
tts.voice_cloning: true only overrides model-variant detection. It cannot enable cloning on a backend that does not implement LocalAI's reference-audio contract.
Roles Configuration
Map conversation roles to specific strings:
roles:
user: "### Instruction:"
assistant: "### Response:"
system: "### System Instruction:"
Feature Flags
Enable or disable experimental features:
feature_flags:
feature_name: true
another_feature: false
MCP Configuration
Model Context Protocol (MCP) configuration:
| Field | Type | Description |
|---|---|---|
mcp.remote |
string | YAML string defining remote MCP servers |
mcp.stdio |
string | YAML string defining STDIO MCP servers |
Agent Configuration
Agent/autonomous agent configuration:
| Field | Type | Description |
|---|---|---|
agent.max_attempts |
int | Maximum number of attempts |
agent.max_iterations |
int | Maximum number of iterations |
agent.enable_reasoning |
bool | Enable reasoning capabilities |
agent.enable_planning |
bool | Enable planning capabilities |
agent.enable_mcp_prompts |
bool | Enable MCP prompts |
agent.enable_plan_re_evaluator |
bool | Enable plan re-evaluation |
agent.max_generation_tokens |
int | Output-token cap for each completion in the agent loop (agent jobs, /api/agent/jobs). Bounds a single completion that keeps generating, which max_iterations and loop_detection do not. A request that already carries a cap keeps it, so a length retry can still raise it. 0 (default) keeps the client default of 16384 |
Reasoning Configuration
Configure how reasoning tags are extracted and processed from model output. Reasoning tags are used by models like DeepSeek, Command-R, and others to include internal reasoning steps in their responses.
| Field | Type | Default | Description |
|---|---|---|---|
reasoning.disable |
bool | false |
When true, disables reasoning extraction entirely. The original content is returned without any processing. |
reasoning.disable_reasoning_tag_prefill |
bool | false |
When true, disables automatic prepending of thinking start tokens. Use this when your model already includes reasoning tags in its output format. |
reasoning.strip_reasoning_only |
bool | false |
When true, extracts and removes reasoning tags from content but discards the reasoning text. Useful when you want to clean reasoning tags from output without storing the reasoning content. |
reasoning.thinking_start_tokens |
array | [] |
List of custom thinking start tokens to detect in prompts. Custom tokens are checked before default tokens. |
reasoning.tag_pairs |
array | [] |
List of custom tag pairs for reasoning extraction. Each entry has start and end fields. Custom pairs are checked before default pairs. |
Reasoning Tag Formats
The reasoning extraction supports multiple tag formats used by different models:
<thinking>...</thinking>- General thinking tag<think>...</think>- DeepSeek, Granite, ExaOne, GLM models<|START_THINKING|>...<|END_THINKING|>- Command-R models<|inner_prefix|>...<|inner_suffix|>- Apertus models<seed:think>...</seed:think>- Seed models<|think|>...<|end|><|begin|>assistant<|content|>- Solar Open models[THINK]...[/THINK]- Magistral models
Examples
Disable reasoning extraction:
reasoning:
disable: true
Extract reasoning but don't prepend tags:
reasoning:
disable_reasoning_tag_prefill: true
Strip reasoning tags without storing reasoning content:
reasoning:
strip_reasoning_only: true
Complete example with reasoning configuration:
name: deepseek-model
backend: llama-cpp
parameters:
model: deepseek.gguf
reasoning:
disable: false
disable_reasoning_tag_prefill: false
strip_reasoning_only: false
Example with custom tokens and tag pairs:
name: custom-reasoning-model
backend: llama-cpp
parameters:
model: custom.gguf
reasoning:
thinking_start_tokens:
- "<custom:think>"
- "<my:reasoning>"
tag_pairs:
- start: "<custom:think>"
end: "</custom:think>"
- start: "<my:reasoning>"
end: "</my:reasoning>"
Note: Custom tokens and tag pairs are checked before the default ones, giving them priority. This allows you to override default behavior or add support for new reasoning tag formats.
Per-Request Override via Metadata
The reasoning.disable setting from model configuration can be overridden on a per-request basis using the metadata field in the OpenAI chat completion request. This allows you to enable or disable thinking for individual requests without changing the model configuration.
The metadata field accepts a map[string]string that is forwarded to the backend. The enable_thinking key controls thinking behavior:
# Enable thinking for a single request (overrides model config)
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3",
"messages": [{"role": "user", "content": "Explain quantum computing"}],
"metadata": {"enable_thinking": "true"}
}'
# Disable thinking for a single request (overrides model config)
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3",
"messages": [{"role": "user", "content": "Hello"}],
"metadata": {"enable_thinking": "false"}
}'
Priority order:
- Request-level
metadata.enable_thinking(highest priority) - Model config
reasoning.disable(fallback) - Auto-detected from model template (default)
Pipeline Configuration
Define pipelines for audio-to-audio processing and the [Realtime API]({{%relref "features/openai-realtime" %}}):
| Field | Type | Description |
|---|---|---|
pipeline.tts |
string | TTS model name |
pipeline.llm |
string | LLM model name |
pipeline.transcription |
string | Transcription model name |
pipeline.vad |
string | Voice activity detection model name |
pipeline.any_to_any |
bool | When true, a realtime_audio model runs as one speech-to-speech model (audio in, audio or text out) instead of the chained VAD, transcription, LLM and TTS path. Experimental. See [Speech-to-speech models]({{%relref "features/openai-realtime#speech-to-speech-models-experimental" %}}) |
pipeline.turn_detection |
object | Realtime turn-detection defaults. Keys: type (server_vad/semantic_vad), eagerness (low/medium/high/auto), retranscribe, vad_window_sec (widen the per-tick VAD scan window; values below the automatic floor are ignored). See [Realtime turn detection]({{%relref "features/openai-realtime" %}}) |
pipeline.classifier |
object | Realtime classifier mode: prefill-scored option selection instead of generation. Keys: enabled, threshold, normalization (raw/mean), history_items, fallback (mode: none/reply/generate, reply), options (list of id, description, reply, tool {name, arguments}), address (wake-word gate: names, mode: ignore/reply, reply), model (optional separate scoring config). See [Realtime classifier mode]({{%relref "features/openai-realtime#classifier-mode-localai-extension" %}}) |
gRPC Configuration
Backend gRPC communication settings. These control the readiness handshake
between LocalAI and a freshly spawned backend process - LocalAI polls the
backend's Health gRPC method up to grpc.attempts times, sleeping
grpc.attempts_sleep_time seconds between polls, before giving up and
terminating the backend as unresponsive.
| Field | Type | Default | Description |
|---|---|---|---|
grpc.attempts |
int | 20 | Number of health-check attempts before the backend is killed as unresponsive |
grpc.attempts_sleep_time |
int | 2 | Sleep time between health-check attempts (seconds) |
Total load window ≈ grpc.attempts × (grpc.attempts_sleep_time + per-call gRPC dial timeout).
The default of 20 × 2 s ≈ 40 s is fine for typical backends but is too
short for large models that need substantial time to become gRPC-ready
after the process starts - for example NVFP4 / FP8 models whose shard
loading and CUDA-graph capture can take several minutes, or slow storage
backends. If the backend keeps getting killed while still legitimately
loading (visible as exitCode=120 + rpc error: code = Canceled desc = context canceled in the LocalAI log, while the backend's own stderr
shows continued forward progress), raise these values.
Example configuration for a model that needs up to ~10 minutes to become gRPC-ready (large NVFP4 model, cold shard load + CUDA-graph capture):
grpc:
attempts: 140
attempts_sleep_time: 5
This gives a ~700 s window while keeping health-check polling frequent enough to detect real backend crashes quickly. The values only affect the initial readiness handshake - inference-request timeouts and the watchdog are unchanged.
Overrides
Override model configuration values at runtime (llama.cpp):
overrides:
- "qwen3moe.expert_used_count=int:10"
- "some_key=string:value"
Format: KEY=TYPE:VALUE where TYPE is int, float, string, or bool.
Known Use Cases
Specify which endpoints this model supports:
known_usecases:
- chat
- completion
- embeddings
Available flags: chat, completion, edit, embeddings, rerank, image, transcript, tts, sound_generation, tokenize, vad, video, detection, score, token_classify, decisions, llm (combination of CHAT, COMPLETION, EDIT).
decisions marks a model as a decision model for the [Decisions API]({{% relref "features/decisions" %}}) (POST /v1/systemone). It is never guessed, and a model that declares it is not listed as a chat, completion or embeddings model.
token_classify marks a model as a token-classification (NER) provider for the PII filter (e.g. an openai-privacy-filter GGUF). Declare it explicitly together with embeddings: true (the classifier loads via TOKEN_CLS pooling). It runs on the dedicated privacy-filter backend (backend/cpp/privacy-filter), a standalone GGML engine for the openai-privacy-filter family - separate from llama-cpp, which no longer carries the token-classification path.
Known input and output modalities
Use known_input_modalities and known_output_modalities when a use case does not fully describe a model's I/O. For example, both text-to-video and audio-driven avatar models use the video use case, but only the avatar model accepts audio:
known_usecases:
- video
known_input_modalities:
- text
- image
- audio
known_output_modalities:
- video
Valid modality values are text, image, audio, and video. Explicit values are combined with modalities LocalAI can infer from the model use cases and configuration. The resulting canonical, de-duplicated lists are exposed by GET /v1/models/capabilities.
PII filtering
PII redaction is NER-based and runs on the request (input) side. It has two halves:
-
Detector models are
token_classifymodels that carry the detection policy in a top-levelpii_detection:block. The policy is defined once, on the model itself:name: privacy-filter-multilingual backend: llama-cpp embeddings: true known_usecases: - token_classify pii_detection: min_score: 0.5 # drop detections below this confidence default_action: mask # mask | block | allow - applied to any detected # group with no explicit entry (empty = mask) entity_actions: # which PII to block vs mask vs allow-log PASSWORD: block CREDITCARD: block EMAIL: mask extend_to_next_word: # optional: also mask the word right after these - ZIPCODE # groups on the same line (e.g. the town after a postal code) extend_to_previous_word: # optional: also mask the letters-only word right before these - LASTNAME # groups on the same line (e.g. the first name before a surname) protected_terms: # optional: values that must pass unchanged even if tagged - Hotel Seeblick # (plus protected_terms_files: one term per line, in the models path) -
Consuming models opt in and reference one or more detectors by name - no per-consumer policy:
name: my-assistant pii: enabled: true # default: off for local backends, on for cloud-proxy detectors: - privacy-filter-multilingual
Multiple detectors union their detections; overlapping spans resolve to the strongest action (block > mask > allow). A configured detector that can't be loaded fails the request closed (HTTP 503) rather than silently skipping the check. Detections are audited at /api/pii/events (hash-prefix only, never the raw value).
The earlier regex pattern tier (
pii.patterns, the global pattern catalogue,--pii-config, and the/api/pii/patternsadmin endpoints) has been removed, along with response/streaming-side redaction. Those keys now no-op with a startup warning; migrate topii.detectors+ a detector'spii_detectionblock.
Environment Variables Configuration
Model configurations can specify environment variables passed to the backend process:
name: vllm-model
backend: vllm
parameters:
model: my-vllm-model
env:
VLLM_WORKER_MULTIPROC_METHOD: "spawn"
VLLM_CACHE_DIR: "/tmp/vllm_cache"
CUDA_VISIBLE_DEVICES: "0,1"
Environment variables are appended to the system environment variables and will override any conflicting system variables with the same name.
Complete Example
Here's a comprehensive example combining many options:
name: my-llm-model
description: A high-performance LLM model
backend: llama-cpp
parameters:
model: my-model.gguf
temperature: 0.7
top_p: 0.9
top_k: 40
max_tokens: 2048
context_size: 4096
threads: 8
f16: true
gpu_layers: 35
system_prompt: "You are a helpful AI assistant."
template:
chat: |
{{.System}}
{{range .Messages}}
{{if eq .Role "user"}}User: {{.Content}}
{{else if eq .Role "assistant"}}Assistant: {{.Content}}
{{end}}
{{end}}
Assistant:
roles:
user: "User:"
assistant: "Assistant:"
system: "System:"
stopwords:
- "\n\nUser:"
- "\n\nHuman:"
prompt_cache_path: "cache/my-model"
prompt_cache_all: true
function:
grammar:
parallel_calls: true
mixed_mode: false
feature_flags:
experimental_feature: true
Related Documentation
- See [Advanced Usage]({{%relref "advanced/advanced-usage" %}}) for other configuration options
- See [Prompt Templates]({{%relref "advanced/advanced-usage#prompt-templates" %}}) for template examples
- See [CLI Reference]({{%relref "reference/cli-reference" %}}) for command-line options
GPU Auto-Fit Mode
Note: By default, LocalAI sets gpu_layers to a very large value (99999999), which effectively disables llama-cpp's auto-fit functionality. This is intentional to work with LocalAI's VRAM-based model unloading mechanism.
To enable llama-cpp's auto-fit mode, set gpu_layers: -1 in your model configuration. However, be aware of the following:
-
Trade-off: Enabling auto-fit conflicts with LocalAI's built-in VRAM threshold-based unloading. Auto-fit attempts to fit all tensors into GPU memory automatically, while LocalAI's unloading mechanism removes models when VRAM usage exceeds thresholds.
-
Known Issues: Setting
gpu_layers: -1may triggertensor_buft_overridebuffer errors in some configurations, particularly when the model exceeds available GPU memory. -
Recommendation:
- Use the default settings for most use cases (LocalAI manages VRAM automatically)
- Only enable
gpu_layers: -1if you understand the implications and have tested on your specific hardware - Monitor VRAM usage carefully when using auto-fit mode
This is a known limitation being tracked in issue #8562. A future implementation may provide a runtime toggle or custom logic to reconcile auto-fit with threshold-based unloading.