Configuration¶
gmlx.yaml names the models gmlx can run, the settings each one runs with,
and the server's own settings. Every key has a default, so a file holds only
the keys you want to change. A model name from the file works in a request,
in gmlx run and in gmlx chat.
- Create the file
- Where gmlx looks
- What the file contains
- Models, Aliases, Profiles, Rules
- Sampling, Model loading, Prompt cache
- Model discovery
- Server
- Voice, Assistant, Launch, Chat themes
- Changing the file
- Flags and environment variables
- Complete example
Create the file¶
gmlx init scans a folder of GGUF files and writes a config with one entry
for each model it finds:
The file goes to ~/.config/gmlx/gmlx.yaml. With no flags on a terminal,
init asks its questions as a wizard instead. The result holds the server
settings you chose and the models it found:
# gmlx configuration, written by `gmlx init`.
# Every key, with its default: https://asher.github.io/gmlx/config.html
# The settings a server runs with: gmlx serve --print-config
# The sampling each model starts from: gmlx profiles
server:
host: 127.0.0.1
port: 8080
model_dirs:
- ~/models
defaults:
ttl_s: 900
cache:
enabled: true
models:
# sampling (gemma): t=1 top_p=0.95 top_k=64
gemma-4-12b-it-q4:
path: gemma-4-12b-it-Q4_K_M.gguf
# sampling (qwen3.6): t=1 top_p=0.95 top_k=20
qwen3.8-27b-ud-q6:
path: Qwen3.8-27B-UD-Q6_K.gguf
speculative: true
The comment above each model shows the sampling values it starts from,
taken from its family defaults and the GGUF. Start the
server with gmlx serve, which finds the file on its own.
Where gmlx looks¶
A command that needs the file reads the first one it finds:
~/.config/gmlx/gmlx.yaml, wheregmlx initwrites.~/.gmlx.yaml.
Pass --config FILE to read another file. gmlx never reads a gmlx.yaml
in the current folder, because a config can name commands that the server
runs.
gmlx reads only the first file it finds. If you already have a
~/.gmlx.yaml, a new ~/.config/gmlx/gmlx.yaml hides it completely, so
add new blocks to the file you already have.
To see every setting a server would run with, including the defaults you
did not set, run gmlx serve --print-config. It prints YAML and exits
without loading a model.
What the file contains¶
Every block is optional. A server needs models, from models or from a
discover scan.
| Block | What it sets |
|---|---|
models |
The models gmlx can run, each with its GGUF file and settings |
aliases |
Extra model names, such as coder for qwen3.8-27b-ud-q6@coding |
image_models |
Image generation models, each with its DiT, text encoder and VAE files |
video_models |
Video generation models, each with its DiT, text encoder, VAEs and LoRA |
profiles |
Named sets of sampling, loading and prompt settings that any model can use |
rules |
A profile for every model whose id matches a pattern |
discover |
Folders scanned at each start for GGUFs that have no entry |
server |
Address, API key, model folders, memory budget and extra services |
talk |
The voice client's model, voice, wake phrase and listening thresholds |
assistant |
Tool servers and long-term memory of the built-in assistant |
launch |
How gmlx launch runs clients in containers |
theme, themes |
Colors of the terminal chat |
A key is named by its full path, such as server.port. models.*.pin
means the pin key of any entry under models.
Models¶
Each entry under models is one model. The entry's key is the model's id,
which a request puts in its model field and which gmlx run and
gmlx chat take on the command line. You can rename an entry freely, but
an id cannot contain @, which separates an id from a profile name.
models:
qwen3.8-27b-ud-q6:
path: Qwen3.8-27B-UD-Q6_K.gguf
speculative: true
pin: true
gemma-12b:
path: gemma-4-12b-it-Q4_K_M.gguf
mmproj: mmproj-gemma-4-12b-it-bf16.gguf
profile: coding
An entry whose file is missing is skipped with a warning, and the server
keeps running. gmlx sync-models removes such entries.
| Key | Default | Meaning |
|---|---|---|
path |
required | The GGUF file: absolute, relative to a server.model_dirs folder, or hf:<org>/<repo>/<file.gguf>[@rev] |
profile |
none | Profile for requests that name none: one under profiles or an intent such as coding |
profiles |
none | Changes to a profile for this model only, keyed by profile or intent name. Each value takes the keys of overrides. |
overrides |
none | Settings that win over every profile, for this model only. Takes every profile key except extends. |
family |
detected | The family defaults to start from. Set it only when detection picks the wrong family. |
mmproj |
none | Vision or audio projector GGUF that makes the model multimodal |
asr |
found beside the model | Speech companion GGUF for a model that reads audio through one, such as Granite Switch. false pairs none. |
speculative |
false |
Speculative decoding with the GGUF's MTP head, or with draft_gguf. On a MoE, auto loads the drafter only when the experts fit in memory beside it. |
draft_gguf |
none | A separate drafter GGUF. Setting it turns on speculative. |
native_mtp |
false |
Draft with the GGUF's own MTP head even when draft_gguf is set |
speculative_width_cap |
null |
Speculate on at most this many requests at a time. null takes the drafter's default, 0 removes the cap. |
context_copy |
false |
Also draft from the prompt, as Context copy describes. |
adapters |
none | LoRA GGUFs, each a path or {path, strength}, strength 1 by default. A path listed twice is an error. |
adapter |
none | Shorthand for one adapters entry at strength 1. Set adapter or adapters, not both. |
pin |
false |
Load at start and stay loaded. Only POST /unload unloads a pinned model. |
ttl_s |
server.defaults.ttl_s |
Seconds without a request before the model unloads. null or 0 turns off the timeout. |
stream |
none | Run a model larger than memory. experts streams the routed experts from disk, cpu runs the whole model on the CPU. |
Each drafter has its own default speculative_width_cap, which
Several requests at once
lists.
Ids with the same path and different adapters share one loaded copy.
The strength in adapters is the default of a request, and a request can
set another without a reload, as
Mixing adapters per request shows.
profiles changes one profile for one model. Here @coding also sets
min_p on this model, and other models keep the plain intent:
models:
qwen3.8-27b-ud-q6:
path: Qwen3.8-27B-UD-Q6_K.gguf
profiles:
coding: {sampling: {min_p: 0.05}}
Streaming keys¶
These keys apply only to a model with stream set.
Models larger than memory explains how to choose a
placement and how to size the moe_* keys, which all change the output.
| Key | Default | Meaning |
|---|---|---|
moe_experts |
the model's count | Fixed number of experts per token, below the number the model was trained with |
moe_expert_mass |
off | Keep the smallest set of experts whose gate weights reach this share, above 0 and at most 1 |
moe_miss_shed |
off | Drop experts outside the decode arena while the kept ones still cover this share of the gate weight |
moe_layer_shed |
off | Probability, between 0 and 1, of skipping a streamed layer's routed experts |
moe_prestage |
ranked |
Experts read ahead: ranked reads the predicted ones, keepers only those that moe_miss_shed keeps, so it needs moe_miss_shed |
prefill_feeder |
true |
Prefill reads expert weights directly from the GGUF |
decode_feeder |
true with stream: experts |
Decoding keeps the most used experts in a wired arena |
stream_fast_disk |
auto |
Read-ahead for streamed decoding. auto tests the drive, on and off force the choice. |
Rope scaling¶
rope_scaling runs a model past its trained context. Its keys are the
rope scaling flags of gmlx run, and each one
wins over its key in the GGUF header:
models:
qwen3.8-flash-next-512k:
path: Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf
rope_scaling:
type: yarn
factor: 2.0
original_context: 262144
Two ids on one GGUF with different rope_scaling load as separate models,
and the prompt cache on disk keeps their KV apart. A YaRN factor extends
the context that /v1/models reports to original_context times the
factor. A linear factor leaves it at the trained context. A constant factor can cost quality on short prompts, so pick the
smallest factor that fits.
Aliases¶
An alias gives a model a second name, and can fix a profile to it. The
server lists each alias in /v1/models as its own entry, so a client that
picks models from a menu can choose a model and a profile together.
An alias cannot contain @ or match a model id. gmlx pull <name> adds
the name as an alias with the comment set by gmlx pull, and a later pull
of the same name moves it to the new file.
Image models¶
Each entry under image_models is one image generation model: a DiT GGUF
with its text encoder and VAE. gmlx pull writes
one for a model it downloads, and model discovery adds
one for a DiT it finds with its parts. The entry's key is its id, which a request
to the image routes puts in its model field. Image ids, model ids,
aliases and served assistants share one set of names, so an image id
cannot repeat any of them, and it cannot contain @.
models:
qwen3-4b:
path: Qwen3-4B-Q4_K_M.gguf
image_models:
z-image-turbo:
path: z-image-turbo-Q4_K_M.gguf
encoder: qwen3-4b
vae: z-image-vae-BF16.gguf
qwen-image:
path: qwen-image-2.1-Q4_K_M.gguf
encoder: Qwen3-VL-8B-Instruct-Q4_K_M.gguf
encoder_mmproj: mmproj-Qwen3-VL-8B-Instruct-BF16.gguf
vae: qwen-image-2.1-vae-F32.gguf
defaults: {size: 1024x1024, steps: 30}
server:
defaults:
image_model: z-image-turbo
Image models load into the same memory budget as chat models, and unload
the same way: by ttl_s, by POST /unload, or to
make room for another model. The memory an entry counts is its weights
and what one image at its default size needs while it runs, so two ids
with the same files and another default size or guidance load as two
entries.
| Key | Default | Meaning |
|---|---|---|
path |
required | The DiT GGUF, found as a model path is |
encoder |
required | The text encoder: the id of an entry under models, whose loaded copy it shares, or a GGUF path |
encoder_mmproj |
none | The encoder's vision tower GGUF, which Qwen-Image 2.1 edits from reference images need. A shared encoder brings its model's mmproj instead. |
vae |
required | The VAE, a GGUF or a safetensors file |
family |
detected | The image family, such as z-image, qwen-image-2.1 or flux-3-klein. A file of another family is refused at load. |
variant |
detected | The DiT's variant, such as turbo, for a file whose weights match no known variant. A Krea 2 file of unknown weights needs variant: turbo to load. |
adapters |
none | LoRA files applied to every request, each a path or {path, strength} with strength 1 by default |
upsampler |
none | The models id of the chat model that rewrites each prompt, as Prompt upsampling describes |
upsample_prompt |
the family's | A text file with the system prompt for the upsampler, found in model_dirs when relative. Qwen-Image 2.1 needs one, which gmlx pull qwen-image fetches |
upsample |
true |
Rewrite prompts with the upsampler. false runs them as typed, unless a request sends prompt_upsampling: true. |
defaults |
the family's | Request defaults: size as WxH, steps, guidance, sampler (euler or uni_pc_bh2) and negative_prompt |
encoder_release |
auto |
Release the text encoder after each encode: always, never, or auto, which keeps it while the whole model fits in 80 percent of the working budget |
pin |
false |
Load at start and stay loaded, as a model's pin |
ttl_s |
server.defaults.ttl_s |
Seconds without a request before the model unloads. null or 0 turns off the timeout. |
The auto choice of encoder_release counts no request LoRAs. A shared
encoder must be a model without adapters. A quantized chat GGUF used as
the encoder gives slightly different images than a full precision encoder
file.
Video models¶
Each entry under video_models is one video generation model for
gmlx video generate: a DiT GGUF with its text
encoder, its video and audio VAEs and its LoRA. gmlx video pull writes
one for each recipe it pulls. Video ids share one set of names with model
ids, image ids, aliases and served assistants.
video_models:
h3:
path: unsloth__MiniMax-H3-GGUF/minimax_h3_fl2va_pruned-Q4_K.gguf
encoder: unsloth__Qwen3-VL-32B-Instruct-GGUF/Qwen3-VL-32B-Instruct-Q4_K_M.gguf
vae: Comfy-Org__MiniMax-H3/vae/minimax_h3_video_vae_fp16.safetensors
audio_vae: Comfy-Org__MiniMax-H3/vae/minimax_h3_audio_vae_fp32.safetensors
adapters: [lightx2v__Minimax-h3-Turbo/minimax_h3_fl2v_turbo_8step_v1.1_768p_comfyui_bf16.safetensors]
defaults: {steps: 8, shift: 6, size: 1344x768, seconds: 5, int8: off}
| Key | Default | Meaning |
|---|---|---|
path |
required | The DiT GGUF, found as a model path is |
encoder |
required | The text encoder GGUF |
encoder_mmproj |
none | The encoder's vision tower GGUF, which reads a first or last frame |
vae |
required | The video VAE safetensors file |
audio_vae |
required | The audio VAE safetensors file |
tiny_vae |
none | The TAEH3 file that --preview decodes with |
adapters |
none | LoRA files applied to every clip, each a path or {path, strength} with strength 1 by default |
variant |
detected | The DiT's variant, such as fl2va-turbo4 for a merged Turbo file gmlx does not know |
defaults |
none | Clip defaults: steps, shift, size as WxH, seconds and int8. Unset, steps and shift come from the variant, and a clip is 1344x768 for 5 s |
upsampler |
none | The models id of the chat model that rewrites each prompt, as Prompt upsampling describes |
upsample_prompt |
none | MiniMax's prompt guide, which the upsampler reads, found in model_dirs when relative. gmlx video pull fetches it |
upsample |
true |
Rewrite prompts with the upsampler. false runs them as typed, unless a request sends prompt_upsampling: true. |
defaults.int8 is off, the default, attention, linears or all,
the parts of the DiT that run in int8, as
Faster clips with int8 describes.
linears and all take more memory.
Profiles¶
A profile is a named set of settings that any model can use. Every model starts from its family defaults, the sampling values its publisher recommends, so a new file needs no profiles at all.
Some publishers also recommend values for a task, such as a lower
temperature for code. gmlx offers these as built-in profiles called
intents: @coding, @instruct, @creative, @reasoning-low,
@reasoning-medium, @reasoning-high, @reasoning-max and
@reasoning-xhigh. Add one to the model id to use it:
Your own profiles go under profiles. A profile can start from an intent
with extends and change only what it sets. In the file, you write an
intent without the @, as in extends: coding. A profile with an
intent's name replaces that intent.
profiles:
review:
extends: coding
system: "You are a terse code reviewer."
sampling:
temperature: 0.3
max_tokens: 2048
agent:
chat_template_kwargs: {preserve_thinking: true}
How a request gets its settings¶
Settings come in layers, and a later layer wins over an earlier one:
| Layer | Set by |
|---|---|
| Family defaults | Built in, for each detected family |
| Server default profile | server.defaults.profile |
| Rule profile | The first matching entry in rules |
| Model profile | models.*.profile, or @name on the request |
| Per-model profile change | models.*.profiles |
| Model overrides | models.*.overrides |
| Request fields | The request body |
A request's @name replaces the model's own profile rather than adding to
it. gmlx profiles <id> prints the sampling values a model resolves to
under each of its profiles, with every layer applied.
Profile keys¶
| Key | Default | Meaning |
|---|---|---|
extends |
none | Profile or intent to start from. Each key this profile sets replaces the inherited value. |
sampling |
none | Defaults for the sampling request fields. A field the request sends wins. |
load |
none | Model loading keys |
cache |
none | Prompt cache keys that change server.cache for this profile's models |
system |
none | System prompt, used only when the request has no system message |
chat_template |
the GGUF's | Inline Jinja, or a path to a .jinja or .txt file, that replaces the GGUF's chat template |
chat_template_kwargs |
none | Variables passed to the chat template on each request. Keys the request sends win. |
thinking |
the template's | Reasoning on, off or adaptive, on any family |
reasoning_effort |
the template's | A reasoning level the template accepts, such as low, medium or high |
preserve_thinking: true in chat_template_kwargs keeps earlier <think>
blocks in the prompt on the Qwen3.6 and Gemma 4 templates, so an agent sees
its earlier reasoning. A template applies at load, so two ids with different
chat_template values are two loaded copies.
thinking and reasoning_effort map to the variable that each model's
template reads, so one profile works across families:
| Family | Template variable | Values |
|---|---|---|
| Qwen3.x, GLM | enable_thinking |
true or false |
| MiniMax-M3 | thinking_mode |
Three states, so adaptive works |
| MiniMax-M3.1 | None | Thinking is adaptive. In gmlx chat, on ends the prompt inside the thinking block |
| Kimi K2.x | thinking |
true or false |
| Hy3 | reasoning_effort |
Levels that include no_think |
| gpt-oss, Beam | reasoning_effort |
low, medium or high |
Some templates have no thinking switch, such as gpt-oss, Beam, Muse, K2
Horizon and MiniMax-M3.1. On these, off closes each thinking block as soon
as it opens. A thinking_budget you set wins
over it.
A request's enable_thinking, thinking or reasoning_effort field wins
over the profile. gmlx run and gmlx chat take --thinking and
--reasoning-effort.
A request's thinking also takes the z.ai form, {"type": "enabled"} or
{"type": "disabled"}. In that form, clear_thinking: false keeps earlier
reasoning in the prompt.
Rules¶
Rules give a profile to every model whose id matches a pattern, so a group
of models can share settings. The first rule that matches wins. A rule
ranks below a model's own profile and above
server.defaults.profile.
| Key | Default | Meaning |
|---|---|---|
match |
required | Model ids the rule applies to, as a shell wildcard pattern such as qwen3.8-*, not a regular expression |
profile |
required | The profile the matching models get |
Sampling¶
A sampling block sets defaults for the request fields that control
generation. It goes in a profile or in a model's overrides. Each key is
also a request field of the same name, and the request's value wins. A
default of "family" comes from the family defaults,
and gmlx profiles <id> prints it.
| Key | Default | Meaning |
|---|---|---|
temperature |
family | How random each token choice is. 0 always takes the most likely token. Also accepted as temp. |
top_p |
family | Sample only from the most likely tokens whose probabilities add up to this value. 0 turns it off. |
top_k |
family | Sample only from this many of the most likely tokens. 0 turns it off. |
min_p |
family | Drop tokens below this share of the most likely token's probability. 0 turns it off. |
max_tokens |
no limit | Stop after this many tokens. gmlx serve --max-tokens caps requests that set none. |
seed |
none | Repeatable sampling for this request, when its batch and the speculation setting also match |
stop |
none | A string, or a list of strings, that ends generation. The Anthropic endpoint uses its own stop_sequences. |
repetition_penalty |
off | Lowers the chance of tokens seen in the last repetition_context_size tokens. 1 has no effect. |
repetition_context_size |
20 |
Recent tokens that repetition_penalty looks at |
presence_penalty |
off | Lowers the chance of every token already generated, by the same amount |
frequency_penalty |
off | Lowers the chance of each token in proportion to how often it was generated |
xtc_probability |
off | Chance that XTC removes the most likely candidates, to vary the text |
xtc_threshold |
0.0 |
XTC removes candidates whose probability is above this value |
enable_thinking |
the template's | Whether the chat template opens a thinking block. A profile's thinking sets it on any family. |
thinking_budget |
no limit | Close the thinking block after this many reasoning tokens. -1 sets no budget. |
thinking_budget_message |
none | Serve only. Text written in the thinking block ahead of the end marker when the budget runs out. |
thinking_start_token |
detected | Text that opens the model's reasoning. Family defaults set it for some families. Else gmlx reads it from the chat template or the vocabulary. |
thinking_end_token |
detected | Text that closes the model's reasoning. gmlx detects it with the start text. |
When gmlx detects no markers, a response_format schema applies from the
first token, and the thinking budget looks for <think>. Set both markers
for a model whose reasoning text gmlx does not detect.
On a model with a built-in drafter, thinking_budget can run up to one
draft round over, and a request that runs in a batch with other requests
drops it. gmlx run and gmlx chat ignore thinking_budget_message.
Thinking budget describes the request fields and
the drafters.
Model loading¶
A load block changes how a model is built, for example to quantize its KV
cache so that a long context uses less memory. It goes in a profile or in a
model's overrides. Two ids that differ in a load key are two loaded
copies. When you serve a single GGUF with no config, each key is also a
gmlx serve flag, such as --kv-bits.
| Key | Default | Meaning |
|---|---|---|
kv_bits |
none, 6 with kvarn |
Bits per KV cache value. uniform takes 2, 3, 4, 6 or 8, and kvarn takes 2, 3, 4, 5, 6 or 8. |
kv_quant_scheme |
auto |
auto picks for the model, uniform is affine, and kvarn is kvarn |
kv_group_size |
64 |
Group size of uniform quantization |
kv_tail_tokens |
1024 |
Newest tokens that stay fp16 under kvarn, as a multiple of 128 |
quantized_kv_start |
0 |
Tokens the cache holds in fp16 before it quantizes. Batched requests and kvarn quantize from the first token. |
max_kv_size |
no cap | Most context tokens a request may use. gmlx run and gmlx chat also make the cache a rotating window of this size. |
prefill_step_size and dtype apply to the whole server, under
Scheduling.
Prompt cache¶
The prompt cache keeps the computed start of recent prompts, so a request
that begins the same way skips that part of prefill. Agents and long chats
gain the most, because they resend the same history every turn.
server.cache sets it for every model, and a cache block
in a profile or a model's overrides changes it.
Prompt cache says what it restores for each architecture.
Model discovery¶
The discover block lists folders that the server scans at each start and
reload. Every GGUF without an entry under models becomes a model, named
after its file, so a new file appears after a restart or a reload.
gmlx run, gmlx list and gmlx rm scan the same folders. To write the
entries into the file once instead, run gmlx init or gmlx sync-models.
The scan names each model after its file. It drops the shard suffix,
markers such as mmproj, assistant, draft and mtp, and imatrix tags,
and adds the short quantization: Qwen3-8B-Q4_K_M.gguf becomes
qwen3-8b-q4. When two files would get the same name, both get the full
quantization, such as qwen3-8b-q4-k-m and qwen3-8b-q4-k-s, and a clash
that remains gets a number, as does a name that a model or an image model
already has. gmlx list shows the names.
| Key | Default | Meaning |
|---|---|---|
dir |
null |
The folder to scan. null scans every folder in server.model_dirs. |
recursive |
false |
Also scan subfolders |
pair_mmproj |
true |
Pair each mmproj*.gguf with the quants of the model in the same folder that it matches, or with the quant folders below it |
speculative |
auto |
auto and true turn on speculative decoding for MTP models and pair drafters, as the model's speculative does. false never does. |
A companion pairs with each quant of its model in the same folder. With
no model beside it, it pairs with every model in the folders one level
down when they are all quants of one model, the layout of repositories
that keep each quant in its own folder. A named companion, such as
mmproj-Qwen3.8-27B-BF16.gguf, must match the model's name. A generic
one, such as mmproj-F16.gguf, stays unpaired beside models that are not
all quants of one model. Files are quants of one model when their names
match past the quant and their headers give one arch and hidden size. A
companion whose header gives another hidden size than the model's also
stays unpaired. When a folder holds several companions, the first by file
name wins.
A drafter GGUF becomes a model's draft_gguf when all of these hold:
- It is in the same folder as the model, or in an
MTPfolder beside the model's folder or inside it. - Its architecture can draft for the model, and their hidden sizes match.
- Its file name matches the model's, or the model is the only one in those folders that qualifies.
A drafter whose header names its base model pairs with that model in any
scanned folder. A model with stream set gets no drafter.
An image DiT becomes an image model when the scan also
finds its family's text encoder and VAE. Files are matched by their
weights, not their names, and the nearest match to the DiT wins. A text
encoder that is also a chat model, scanned or configured without
adapters, is shared by its id; for the Qwen-Image models the chat model
must also have its mmproj. A DiT without its encoder or VAE is
reported with the gmlx pull command that fetches them.
Server¶
The server block sets where the server listens, who may call it, where
models are found, how much memory they may use, and which extra services
run. With no server block, the server listens on 127.0.0.1:8080 with
the prompt cache off. The keys below are under server, as in the
complete example.
Address and authentication¶
The client commands chat, talk, ps, systemone, launch and
launch menubar take --api-key to send the key. A change to api_key,
media_urls or cors_origins applies after gmlx restart.
With media_urls on, any client that reaches the port can make the Mac
fetch from any public host. The server still refuses hosts on the Mac and
the local network.
Pages on localhost, 127.0.0.1 and [::1], and desktop apps built on
Electron, Tauri or VS Code webviews, may always call the server. curl sends
no origin and is not affected. For a browser extension, add the origin that
the server log names, such as chrome-extension://<id>. These wildcards
allow every extension of one browser:
chrome-extension://*for Chrome, Edge and other Chromium browsersmoz-extension://*for Firefoxsafari-web-extension://*for Safari, which changes an extension's ID at every start
A listed origin can use the tools of served assistants, which run on the Mac, so prefer one extension's own origin to a wildcard.
Model folders¶
Services¶
Each service adds an endpoint. true picks the default model.
Speech, embeddings and rerank lists the models each one
accepts.
| Key | Default | Meaning |
|---|---|---|
stt |
A served model that hears audio, if any | Speech-to-text model: an alias such as whisper-turbo, a Hugging Face repo id, a folder, or the id of a served model that hears audio. false turns it off. |
tts |
none | Text-to-speech model: an alias such as kokoro, a Hugging Face repo id or a folder |
embeddings |
none | Embeddings model: a GGUF path, an hf: reference, an alias, or the repo id or folder of an MLX encoder checkpoint |
rerank |
none | Reranker: a Qwen3-Reranker GGUF path, an hf: reference or an alias |
Memory and residency¶
The server keeps several models loaded up to budget_gb, and unloads a
model after ttl_s idle seconds or when the budget needs room.
Memory explains which models stay loaded and when two ids
share one loaded copy.
The MLX buffer cache says when to change
cache_limit_gb.
Scheduling¶
These keys apply to every model. Each one except token_queue_timeout_s
also has a gmlx serve flag.
decode_prefill_ratio also takes a number, such as 1.0, which makes each
prefill chunk wait until the generating requests have had that multiple of
its GPU time. 0 runs one chunk per generation step.
Concurrent requests shows the effect.
Features¶
| Key | Default | Meaning |
|---|---|---|
family_defaults |
true |
Start each request from its family defaults and offer the intents. false turns off both. |
stochastic_mtp |
true |
Accept more speculative tokens by rejection sampling. Sampled output is not token-identical. A reload does not apply it. |
gpu_keepwarm |
on | Keep the GPU clock up between tokens of a streamed model with a decode feeder. false turns it off. |
menubar |
true |
Let a server started in the background open the menu bar app |
cache |
off | Prompt cache settings for every model |
Stochastic acceptance
describes stochastic_mtp.
Image requests¶
These keys set how the server answers the image routes for the models
under image_models.
| Key | Default | Meaning |
|---|---|---|
image.url_ttl_s |
3600 |
Seconds a file behind a url response stays available |
image.url_max_bytes |
1G |
Most bytes of such files kept at once, as bytes or a size such as 2G. The oldest go first. |
image.max_queue |
4 |
Image requests that may wait behind the one running. One more gets 429 with Retry-After. |
image.embed_metadata |
false |
Write the prompt and settings into each PNG the routes return |
image.tensor_cache_mb |
64 |
MB each image model keeps of encoded prompts, and again of reference image latents. The step routes keep as much of uploaded tensors. |
image.adapter_cache_mb |
none | Optional cap in MB on the request LoRAs of each image or video model, again on their merged patches and on step route uploads. Unset, LoRAs use free memory. |
image.steps_api |
true |
Serve the image step routes that the ComfyUI node pack and gmlx image bench --via-steps call. |
image.steps_remote |
false |
Take step route requests from clients beyond loopback. The routes take uploads of any bytes, so they answer loopback clients alone by default. |
The memory gmlx counts for an image model includes its tensor caches at
their full size: two of image.tensor_cache_mb, and a third while the step
routes are on. A video model with an upsampler keeps room past
image.tensor_cache_mb for one encoded caption as long as the upsampler
writes, about 120 MiB for MiniMax-H3, so the ComfyUI nodes' encode of a long
caption is not refused. Request LoRAs are counted when their jobs run, as the
LoRA refusal describes,
and a LoRA over a set image.adapter_cache_mb is refused with a message
that names the key. At the defaults, one model of any family still fits on
a 16 GB Mac at its default quant and size.
Video jobs¶
These keys set the job store of POST /v1/videos for the
models under video_models.
Structured decisions¶
These keys set up POST /v1/systemone, which
Structured decisions describes. canvas, constrained and
the three think keys apply only to DiffusionGemma. A reload applies new
values to the next request.
| Key | Default | Meaning |
|---|---|---|
systemone.model |
none | Model id or alias, with an optional @profile, for a request that names no configured model |
systemone.canvas |
64 |
Most tokens one structured read fills, as a positive multiple of 16 |
systemone.constrained |
true |
Compute probabilities over the answer tokens only. false uses the full vocabulary. |
systemone.max_questions |
64 |
Most questions in one request |
systemone.max_samples |
32 |
Most samples and auto_max a request may ask for. A larger value is lowered to this one. |
systemone.think |
0 |
Thought budget in tokens, from 0 to 4096, for a request that sets none. "auto" thinks only when an answer is uncertain. |
systemone.think_threshold |
0.8 |
Under "auto", think when the chosen label's probability is below this value |
systemone.think_budget |
64 |
Under "auto", the thought budget in tokens |
Served assistants¶
The server can run the assistant's tool loop itself and offer each assistant as a model. A client that names the assistant as its model gets tools without a loop of its own, as Assistant describes.
| Key | Default | Meaning |
|---|---|---|
assistants.*.model |
required | The configured model that answers for the assistant |
assistants.*.memory |
false |
Long-term memory, in one store that all the assistant's clients share |
assistants.*.mcp |
null |
Tool servers, with the keys of assistant.mcp. null uses assistant.mcp, [] gives no tools. |
assistant_allow_remote |
false |
Allow served assistants on an address other than loopback. Their tools run on the server's Mac. |
Voice¶
The talk block sets up the voice client gmlx talk and the
menu bar app's voice sessions. Most keys also have a
gmlx talk flag, which wins over the file.
| Key | Default | Meaning |
|---|---|---|
model |
the server's default | Model id or alias to talk to, with an optional @profile |
voice |
the server's default | A Kokoro preset or a Qwen3-TTS speaker name |
speed |
1.0 |
Speaking speed, from 0.25 to 4 |
system |
a prompt for speakable text | System prompt for the spoken persona. null or "" sends none. |
language |
detected | Language hint for speech recognition, such as en |
max_tokens |
no limit | Most tokens in a spoken reply |
mode |
wake |
How listening starts: wake on the wake phrase, vad on any speech, ptt on Space, text from typed prompts |
wake_word |
hey assistant |
The wake phrase, any English text. It needs no training. |
wake_threshold |
0.3 |
Confidence, from 0 to 1, that the wake phrase needs. A higher value gives fewer false wakes. |
push_to_talk_modifier |
globe |
Modifier held with Space for the menu bar hotkey: globe, right-command, right-option or control |
input_device |
the system input | Microphone, by part of its name or by index. /devices in gmlx talk lists them. |
output_device |
the system output | Speaker, by part of its name or by index |
chime |
true |
Play a sound on wake and at the end of a turn |
brain |
chat |
What answers: chat is the plain model, assistant adds the assistant block's tools and memory |
vad.threshold |
0.6 |
Speech probability, from 0 to 1, above which audio counts as speech |
vad.silence_ms |
550 |
Pause in ms that ends an utterance. Lower answers sooner and cuts off more sentences. |
vad.min_speech_ms |
300 |
Utterances shorter than this many ms are dropped as noise |
vad.pre_roll_ms |
400 |
Audio in ms kept from before speech starts |
Assistant¶
The assistant block gives the built-in assistant its tools and memory.
gmlx chat --assistant, gmlx talk with brain: assistant, and
served assistants use it. The coding agents that
gmlx launch connects do not. Assistant explains the tool
loop and memory.
assistant:
max_tool_rounds: 8
mcp:
- name: files
command: [npx, -y, "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
- name: search
url: http://127.0.0.1:8931/mcp
memory:
top_k: 6
| Key | Default | Meaning |
|---|---|---|
max_tool_rounds |
8 |
Most rounds of tool calls in one turn before the model must answer |
tool_timeout_s |
60 |
Most seconds one tool call may take |
mcp |
none | MCP servers that provide the tools. Each entry has a name and one of command and url. |
mcp[].name |
required | A name for the server, unique in the list |
mcp[].command |
none | Command that starts a server over stdio, as a list of arguments or a shell-style string |
mcp[].url |
none | Address of a server over streamable HTTP |
mcp[].env |
none | Environment variables for a stdio server, such as the token it needs |
memory.enabled |
true |
Remember facts across conversations. Needs server.embeddings. |
memory.path |
~/.local/share/gmlx/assistant-memory.db |
Database file of the memory, under $XDG_DATA_HOME when that is set |
memory.top_k |
4 |
Remembered facts added to each turn |
memory.extract |
true |
Store short facts that the model extracts. false stores each exchange as it is. |
memory.ttl_days |
forever | Facts older than this many days are forgotten at start |
memory.max_items |
20000 |
Most facts the store holds |
A stdio server gets only HOME, PATH, SHELL, TERM, USER and
LOGNAME from your environment, so put anything else it needs in env.
Its log goes to ~/.cache/gmlx/mcp-<name>.log.
Launch¶
The launch block configures container mode and
defines custom agents. launch reads it only from the
config file in your home folder, ~/.config/gmlx/gmlx.yaml or
~/.gmlx.yaml, never from a file that --config names. The server ignores
the block.
Keys under launch.container apply to every client. Each one also works
under launch.container.clients.<client> for a single client, where the
client's value wins and lists from both levels add up. Six keys exist only
under a client. Agents take most of these keys, plus five of their own.
This block turns on container mode with more memory, gives Claude Code a
volume and a seed, runs Open WebUI from its official image with the served
assistant home, and defines the agent research-bot:
launch:
container:
enabled: true
memory: 6G
clients:
claude-code:
volumes: [claude-pg:/var/lib/postgresql:8G]
seed: [~/.claude/CLAUDE.md]
open-webui:
image: ghcr.io/open-webui/open-webui:v0.11.4
command: image
assistants: [home]
agents:
research-bot:
runtime: python
command: [research-bot]
launch.container¶
| Key | Default | Meaning |
|---|---|---|
enabled |
false |
Run clients in a container. --container and --no-container override it. |
mount_cwd |
true, false for open-webui and elia |
Share the current folder read-write at the same path, and start the client there. --mount-cwd and --no-mount-cwd override it. |
mounts |
none | More folders to share, each PATH[:DST][:ro]. PATH is a full path or starts with ~. --mount adds entries. |
volumes |
none | Named volumes, each NAME:/path[:SIZE], created with SIZE when missing. The default size is 32G. |
forward |
none | Ports the container reaches on its own 127.0.0.1, which lead to the same ports on the Mac's 127.0.0.1 |
network |
default |
default reaches the internet and your local network. none reaches only the gmlx server and forwarded ports. --network overrides it. |
cpus |
4 |
CPUs the container gets |
memory |
4G |
Memory the container gets, such as 6144M. It counts against the memory the model server can use. |
ssh_agent |
false |
true lets the client use the SSH agent in SSH_AUTH_SOCK. A socket path gives it that agent instead. |
env |
none | Variables for the container: NAME passes yours in, NAME=VALUE sets one. An entry can replace a client setting such as OPENAI_API_KEY. |
open_browser |
true |
Open a browser app in the Mac's browser once it answers. false prints the address. |
paste_copy_max |
1G |
Largest pasted file that launch copies from another disk into the private home. A file on the same disk is cloned, with no limit. |
For a variable that is part of the client's settings, such as
OPENAI_API_KEY or ANTHROPIC_BASE_URL, an env entry NAME keeps
launch's value. Only NAME=VALUE replaces it.
A volume under a client is separate for each project, while a volume at
the top level is shared by every project and client. With ssh_agent, the
client can sign with every key in the agent. To use 1Password's agent, give
its socket path,
~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock.
More on each key:
- Volumes, Forwarded ports and Pasting files and images
- Limits for
memory - Access you turn on for
ssh_agent,forward,envandnetwork
launch.container.clients¶
Settings for single clients, keyed by claude-code, opencode, pi,
omp, hermes, goose, aichat, elia, open-webui or dsh. Each
client takes the keys above and these six:
| Key | Default | Meaning |
|---|---|---|
image |
the image gmlx builds | Run this image, a local tag or a registry reference. Not with build or packages. |
build |
the image gmlx builds | Build the image from this Containerfile, or from a folder with a Containerfile or Dockerfile. A full path or one that starts with ~. |
command |
the client's own | A list replaces the client's command. image runs the image's own ENTRYPOINT and CMD. |
packages |
none | Debian packages added to the image gmlx builds. With build, only when the Containerfile starts from the client's :base. |
seed |
none | Files and folders copied from your home folder into the private home, at the same path |
assistants |
none | Served assistants the client can use. The others stay hidden from it. |
Container images covers image, build and
command. Here a client gets extra packages and two seeds:
launch:
container:
clients:
claude-code:
packages: [make, python3, postgresql-client]
seed: [~/.claude/CLAUDE.md, ~/.claude/commands]
Seeds says when a seed is copied.
--seed-instructions also seeds the client's instruction and skill files,
which Instructions and skills
lists. A served assistant's tools run on the Mac, so list assistants only
for chat apps where you write the messages, as
What the client reaches on the server
explains.
launch.agents¶
Each entry under launch.agents defines a custom agent,
such as research-bot in the example above. A name starts with a
lowercase letter and holds lowercase letters and digits, with single - or
_ characters between them, as in research-bot or ci_fixer2. It has at
most 32 characters, does not end in - or _, and is not a client's name
or menubar.
An agent needs command and one of runtime, image or build. It also
takes every key of launch.container except enabled and clients, and
the client keys image, build, seed and assistants. The table lists
command, which an agent always needs, and the five keys only agents take:
Custom agents covers dependencies at run time, the source folder, what the agent gets and a browser interface.
Chat themes¶
The theme and themes keys set the colors of gmlx chat.
Each theme sets styles for kinds of text, such as heading and thinking,
and takes extends for the ones it leaves out. Themes
lists them.
Changing the file¶
gmlx checks the whole file each time it reads it. An unknown key fails the
load and names the key, so a typo such as pinned: for pin: is caught
before the server starts. Under sampling, load and cache, an unknown
key prints only a warning and the load goes on, so read the server log
after you change those blocks.
A running server reads its file again on POST /v1/reload or SIGHUP, and
the commands that edit the file send the reload for you:
curl -X POST http://127.0.0.1:8080/v1/reload # with api_key set, add -H "Authorization: Bearer <key>"
gmlx restart # applies everything, such as a new api_key
Loaded models stay loaded through a reload. A change to a model's load
settings, such as mmproj or speculative, applies the next time it loads.
A reload also turns each service on or off, or moves it to
another model.
| Command | What it changes |
|---|---|
gmlx sync-models |
Adds new GGUFs from model_dirs and removes entries whose file is gone |
gmlx pull |
Adds an entry for each GGUF it downloads |
gmlx rm |
Deletes a model's files and its entry |
These commands keep your comments and formatting. Pass --no-reload to
init, sync-models or rm to leave a running server alone, and
--no-register to gmlx pull to leave the file alone.
Flags and environment variables¶
Some settings also have a gmlx serve flag or an environment variable.
The flag wins, then the key in the file, then the variable. Two variables
win over the file: GMLX_CACHE_LIMIT_GB over server.cache_limit_gb, and
GMLX_MTP_WIDTH_CAP over each model's speculative_width_cap. The flags
are in the CLI reference and the variables in
Environment variables.
Complete example¶
This file uses the blocks most setups need:
server:
model_dirs: [~/models]
budget_gb: 96
cache:
enabled: true
disk: {path: ~/.cache/gmlx/apc, max_gb: 200}
defaults:
model: qwen3.8-27b-ud-q6
ttl_s: 900
profiles:
review:
extends: coding
system: "You are a terse senior engineer."
sampling: {temperature: 0.2, max_tokens: 4096}
long-context:
load: {kv_bits: 8}
rules:
- {match: "*coder*", profile: review}
models:
qwen3.8-27b-ud-q6:
path: Qwen3.8-27B-UD-Q6_K.gguf
speculative: true
pin: true
profiles:
coding: {sampling: {min_p: 0.05}} # changes @coding for this model
overrides: {sampling: {max_tokens: 8192}} # wins over every profile
gemma-31b:
path: google_gemma-4-31B-it-Q6_K_L.gguf
draft_gguf: gemma-4-31B-it-assistant.Q8_0.gguf
gemma-e4b:
path: gemma-4-E4B-it-Q6_K.gguf
mmproj: mmproj-gemma-4-E4B-it-bf16.gguf
profile: long-context
aliases:
coder: qwen3.8-27b-ud-q6@review
assistant:
mcp:
- {name: clock, command: [uvx, mcp-server-time]}
discover:
- {dir: null, recursive: true} # serves GGUFs in model_dirs that have no entry
The smallest useful file has one model with a path: