Configuration
Every setting is one field in a group — server, model, vram,
reuse, switch, spec, vision, media, image, speech, download, ngram, kv_disk —
and has the same name on every surface
(ADR 0046):
| Surface | Spelling | Example |
|---|---|---|
| flag | --<group>-<field> |
--reuse-kv-host-pool-bytes 8G |
| env var | IGNIS_<GROUP>_<FIELD> |
IGNIS_REUSE_KV_HOST_POOL_BYTES=8G |
| config file | <group>: { <field>: … } |
reuse: { kv_host_pool_bytes: 8G } |
ignis-server help --fields prints every field — flag, env var, file key,
default, rule, the models that take it — from the table the server itself is
built from; it is the reference, and the tables below add the why. (--format json gives the same list to a tool.) A bool’s flag may stand bare
(--server-ui is --server-ui true) or take its value (--server-ui false).
Precedence, highest first: flag, env var, config file, profile, built-in default. An empty value is an unset one.
Per model. A field both models take but want sized differently has a
family-scoped variant beside it — qwen38 for Qwen3.8-27B, qwen38flashnext
for Qwen3.8-Flash-Next: --qwen38flashnext-reuse-kv-host-pool-bytes 1G,
IGNIS_QWEN38FLASHNEXT_REUSE_KV_HOST_POOL_BYTES, or in the file
reuse: { qwen38flashnext: { kv_host_pool_bytes: 1G } }. Within each source
the family’s value wins over the general one, and a model switch takes the
target model’s own. A field only one model has (--vision-enabled, the
Flash-Next knobs) is refused at start when set for the other model, and
dropped with a log line when a switch moves to the other model.
The config file. JSON or YAML, named by --config <path> /
IGNIS_CONFIG (it must exist). With neither, a start looks for
./ignis.config.yaml, ./ignis.config.json, then %APPDATA%\ignis\config.yaml
(Windows) or $XDG_CONFIG_HOME/ignis/config.yaml / ~/.config/ignis/config.yaml
(Linux); the first found is used. The startup line ignis.config.source says
which (explicit, discovered or none). An unknown key or a bad value in it
refuses the start naming the key.
ignis-server config generate --out ignis.config.yaml # every default, plus any flags givenignis-server config print # what would run: file + env + flagsignis-server config patch --model-max-context 131072 # change the file in placeProfiles. --profile <name> / IGNIS_PROFILE / the file’s profile:
names a bundle of hardware-shaped defaults, below the file and above the
built-in defaults. Three are built in, and the default, --profile auto,
picks one by the card’s total memory as NVML reports it: under 20 GiB
(a 16 GB card) rtx50-16gb, 28 to 40 GiB (a 5090) rtx5090, 88 to 104 GiB
(an RTX PRO 6000) rtxpro6000. A card of any other size, or a total NVML
cannot read, is refused, naming --profile — at a start, and at config generate|print|patch and model list|download too, which resolve exactly as a
start would, except that the model commands need no card: to them a total
NVML cannot read (no NVIDIA driver) is no card, as on a build without CUDA; a
profile you name (--profile, IGNIS_PROFILE, the
file’s profile:, none included) always wins and the card is not consulted
for the choice. The start’s log line ignis.config.source names the profile
chosen and the total it was chosen by; GET /v1/config and the Playground show
the profile it resolved to. A build without CUDA has no card, so auto there is
none.
rtx5090holds numbers measured on that card. Under it Flash-Next pins its calibration-hottest 2 GiB of experts in VRAM (--vram-pinned-expert-bytes 2G, the pinned hot set): 2.1 GB less RAM for at most ~1% of three-lane decode.--vram-pinned-expert-bytes 0turns it off. The 27B prefills 2,048 tokens a chunk (--model-prefill-chunk): with vision on that costs no VRAM, and long prompts prefill ~2% faster (finding).rtxpro6000is the RTX PRO 6000 Blackwell series (Workstation, Max-Q, Server Edition, 96 GB each), measured on a Server Edition under Linux (finding). Both models get 1,048,576 tokens of context (both with YaRN at 4, whichautorope scaling picks for them). Flash-Next gets 8 decode lanes, MTP speculation with 3 draft tokens (Flash-Next speculation (MTP)) and--ngram-hot-bytes auto: the whole n-gram table in RAM when the host plan leaves room for it (Flash-Next n-gram startup cache). Everything else is as on the 5090, except that the 27B’s VRAM headroom stays at its default.--profile autopicks it on this card;--profile rtxpro6000names it.rtx50-16gbis the 16 GB class (RTX 5060 Ti 16 GB, 5070 Ti, 5080): derived, not measured, since no 16 GB card exists in the project. The 27B at 262,144 tokens of context on two decode lanes, with DFlash2 at 7 draft tokens and the drafter’s headauto, vision on at 4,096 tokens an image (the tower and pool in host RAM, so no context lost while no image is in flight), the VRAM headroom of the 5090’s 27B (1200M) and a 1,024-token prefill chunk. It names no model: a start with no artifact takes the catalog’s 16 GB entry of the default id.
Adaptation. A profile’s values, and the built-in defaults, are what the 27B asks for, not what it insists on: when the VRAM plan cannot hold them in the memory the start finds free, the load lowers them, in this order, and plans again after each step:
- the drafter’s head: a
shortlistfrom a profile falls tofull(the shortlist is bound beside the full head and costs ~357 MB); --model-max-context, down--model-adapt-context-steptokens at a time (65,536) to one step, keeping the largest context at which the KV pool holds one whole sequence and a spare reservation of--vram-adapt-spare-tokens(65,536) for a second request (at one lane, the sequence alone);--spec-decode-lanesto 1, after which the context steps down again from the profile’s value.
A start that fits as configured changes nothing, even when its pool has no
room for a second request: a budget one byte smaller can therefore run a
smaller context that has that room. A value you name (a flag, an env var, the
config file, a live patch – even one equal to the default) never adapts. With
the lanes named, a context with room for the spare reservation still wins
first; only when none has it does the load keep the largest context the plan
holds, and the log says one sequence only. The ignis.runtime.vram_plan log
event lists every step taken in adapted, empty when none was – on the 5090
under rtx5090, a 23.3 GB budget gave decode_lanes 8 -> 1, max_context 917504 -> 327680 (adapt_context_step 65536, one lane). GET /v1/models
reports the same steps in adapted, beside the context the load runs
(max_model_len) and its decode lanes (decode_lanes), while GET /v1/config
keeps the values configured. When not even one step’s context on one lane
fits, the start is refused with that plan’s deficit and every step it tried.
DFlash2 is never switched off; a budget named with
--vram-allow-oversubscription never adapts, nor does a named
--vram-kv-pool-bytes below one sequence (refused by name, as before).
Flash-Next does not adapt: its expert cache takes what the plan leaves. make
leaves the 27B’s context to the profile, so it adapts; MAX_CONTEXT=n names
one, which does not.
A file’s profiles: { <name>: { <group>: { … } } } defines one for any other
card. auto picks the class’s profile by name, the lookup a named profile
goes through, so a file whose profiles: redefines rtx50-16gb, rtx5090 or
rtxpro6000 replaces what auto picks for that class. A profile called none
or auto is refused when the file is read: both words mean something to
--profile, so such a profile could never be reached.
config generate writes every resolved value as a value of the FILE, the
profile’s among them: a file generated under auto carries the numbers of the
card class it was generated on, and a value in a file never adapts to the card
that reads it. Generate with --profile none for a file that does not depend on
the card.
At runtime. GET /v1/config shows the running configuration (fields
declared visible only: never the API key); GET /v1/config?format=yaml shows
it as the config file it amounts to, the text config print writes.
Both are in the file’s shape: a group key holds the shared value, and a model
family’s section (the loaded model’s too) sits beside it wherever that family’s
value differs, so what the loaded model runs with is its section’s, else the
group’s.
PATCH /v1/config takes a partial document in the file’s shape: a change to
the deadlines, the message policies, the thinking defaults or the switch
knobs applies at once, and so does a value the loaded model does not run with
— one in the other model’s family section, or a group value the loaded model
overrides in its own — which that model uses when it next loads; a change to
anything the loaded model’s load is built from reloads it with the whole
patch, as a model switch does. A null removes a key: in a family’s section,
so the group’s value applies to that model again; at a group key, so the
profile’s or the built-in default applies — a flag or an env var that sets the
field still outranks the file, and the answer names it. A field only one
model takes (spec.draft_rows) is set and removed in that model’s section,
whichever key names it. Turning speculation off takes one step: a patch
with spec.backend: off that names no spec.draft_tokens or
spec.draft_head beside it removes the ones the file or an earlier patch holds
at that same key, as if it said null for each, so
{"spec":{"qwen38flashnext":{"backend":"off"}}} is enough; naming a count
beside off in the same patch is still refused, and so is a count a flag or an
env var sets (a patch cannot remove those). Either way it is
written to the config file in use once it has taken, a removed key and the
section it empties taken out. GET /v1/config/fields says what each of those fields
is: its key, kind, default, rule, the model families that take it, and whether
a change is live, needs a reload, or can only be made at start — the same
declarations as ignis-server help --fields --format json, minus the fields
GET /v1/config hides. GET /v1/config/origins says where each value in force
comes from, for the shared value and for each model family: the default, the
profile, the file, an env var, a flag or a live change, whether it was set for
that family itself, and the flag, env var, file key or profile that set it.
A family’s own value in the file or from a live change is the one a group
change does not reach and a null removes.
