models.ini Format

models.ini is an INI file passed straight to llama-server via --models-preset <path> (see backend/router_ctl.py start()). LlamaForge never translates it into a different format for the router — the dashboard reads and rewrites it directly (backend/config.py read_sections() / set_keys()), and llama-server parses it itself when the router starts. config.json's models_ini key points at the file (default: <repo root>/models.ini).

The file is created for you if it's absent: config.ensure_models_ini() runs on startup (and both launch scripts do the same before starting the router) and writes a minimal file with a [*] section, because llama-server refuses to start without one. A relative models_ini (the shipped default is ./models.ini) is anchored to the repo root before it's handed to the detached router, so launching from another directory can't point the router at an empty registry. When the active engine is ik_llama, a separate -ikllama sibling registry is used (e.g. models-ikllama.ini), since the two binaries accept different flags — see Build & Update.

Format

Real example

From the project's own models.ini:

[*]
ctx-size = 150000

[qwen3.6-35b-a3b-ud-q4-k-xl]
model = D:/models/LlamaForge-downloads/unsloth--Qwen3.6-35B-A3B-MTP-GGUF/Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf
mmproj = D:/models/LlamaForge-downloads/unsloth--Qwen3.6-35B-A3B-MTP-GGUF/mmproj-F16.gguf
n-cpu-moe = 37

[qwen3-coder-next-q4-k-m]
model = D:/models/lmstudio-community/Qwen3-Coder-Next-GGUF/Qwen3-Coder-Next-Q4_K_M.gguf

[gpt-oss-20b-mxfp4]
model = D:/models/lmstudio-community/gpt-oss-20b-GGUF/gpt-oss-20b-MXFP4.gguf
ctx-size = 100000

qwen3-coder-next-q4-k-m has no ctx-size override, so it inherits 150000 from [*]. gpt-oss-20b-mxfp4 sets its own ctx-size = 100000, overriding the global. qwen3.6-35b-a3b-ud-q4-k-xl adds mmproj (a multimodal projector file) and n-cpu-moe (MoE experts offloaded to CPU) on top of model.

Common keys

KeyMaps toPurpose
model--modelPath to the GGUF model file. Required per section.
mmproj--mmprojPath to a multimodal projector GGUF, for vision-capable models.
ctx-size--ctx-sizeContext window size, in tokens.
n-cpu-moe--n-cpu-moeNumber of MoE expert layers offloaded to CPU (mixed CPU/GPU inference for MoE models).
embeddings--embeddingsSet to true to mark/run the model in embeddings mode.
spec-draft-model--spec-draft-modelPath to a speculative draft model (e.g. an auto-attached mtp-* sidecar).
spec-type--spec-typeSpeculative-decoding type, e.g. draft-mtp or an ngram-* mode.

Beyond these, any llama-server flag can be set as a key — the dashboard's Advanced UI mode (ui_mode: "advanced" in config.json) exposes the full set (roughly 220 flags), discovered at runtime by parsing llama-server --help (backend/argspec.py build_schema()).

Automatic ctx-size defaults

config.apply_ctx_defaults() keeps ctx-size values sane across the file: it sets [*] ctx-size = 150000 (the gguf.CTX_FULL baseline), and for any model whose GGUF-reported trained context length is below that, writes an explicit per-model ctx-size override capped to what the model actually supports (never over-extending it). Models whose trained length can't be read are left untouched, and a model that already supports the full 150000 has any smaller per-model override removed so it falls back to the global. This runs on server startup and after scan/download operations that add new models.

Auto-wired MTP draft models

When a scan finds an mtp-* GGUF next to a model, it attaches it the way mmproj is attached: the sibling's path is written as spec-draft-model on the parent section. It additionally sets spec-type = draft-mtp only when the sidecar declares NextN layers (gguf.has_nextn() — the property llama.cpp gates MTP on), so a sidecar the current build can't use is attached but left inert rather than breaking the load. This wiring is additive: because spec-type is also how you select ngram-* speculation, a re-scan never overwrites a spec-type (or spec-draft-model) you already set by hand.

Editing

Prefer the dashboard over hand-editing: POST /api/save (per-model knobs) and the scan/download flows call config.set_keys(), which updates or removes individual keys in place while preserving comments and the position of every other line. Hand edits are safe too — read_sections() is tolerant of blank lines, comments, and section order.

See also config.json Reference for the models_ini path setting, and HTTP API for the endpoints that read and mutate this file.