Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ the fact.
`--tokenizer-mode deepseek_v4` and the `deepseek_v4` tool-call and reasoning
parsers. The repos ship no chat template, so a profile without the tokenizer
mode could not serve chat requests.
- Thinking models: `inspect` says when a model's chat template has a thinking
switch and whether it is on by default, `new` records it as `THINKING` in the
profile, and `list` and `status` show it. `new --thinking on|off` and the
panel's Thinking control set the default through vLLM's
`--default-chat-template-kwargs`. Covers Qwen3, Qwen3.5, GLM-4.5 and later,
DeepSeek V3.1 and V4, and SmolLM3; the switch lives in each model type's
catalogue file.
- `glm-5.3-flash-tf-ablit-2spark` recipe: the GLM-5.3 Flash TensorFold launch
across two DGX Sparks with the abliterated checkpoint, thinking kept on.

Expand Down
1 change: 1 addition & 0 deletions catalogue/engines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Each file here is one inference server llm-helper can launch, named after the en

To add an engine, copy the closest file to `<id>.json` and change it.
No Go code is needed: shared code acts on the engine's `capabilities`, never on its name.
`chat_template_kwargs_flag` names the flag that sets default chat template kwargs, which is how a profile turns a model's thinking on or off; leave it out if the engine has none.
The file is checked when it loads, and the tests in `internal/engine` render every file here for every rank.

The fields are described in "Contributing an engine" in [docs/providers.md](../../docs/providers.md).
Expand Down
3 changes: 2 additions & 1 deletion catalogue/engines/vllm.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
},
"capabilities": {
"vllm_args": true,
"gguf_quant": true
"gguf_quant": true,
"chat_template_kwargs_flag": "--default-chat-template-kwargs"
}
}
1 change: 1 addition & 0 deletions catalogue/model-types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ It says what every repo of that type needs on each engine, so the finetunes and
To add one, copy the closest file to `<model_type>.json`.
Under `engines`, keyed by an engine id from [`catalogue/engines/`](../engines), it can list files to fetch (`files`), flags to add when the card did not name them (`args`), and chat template fixes (`chat_template_fixes`).
Give each a `why`.
A type whose chat template has a switch for reasoning before the answer, such as Qwen3's `enable_thinking`, also gets a top-level `thinking` block; a file can have only that.
A configuration for one specific repo that was run and measured belongs in [`catalogue/recipes/`](../recipes) instead.

The fields are described in [docs/model-types.md](../../docs/model-types.md).
Expand Down
11 changes: 11 additions & 0 deletions catalogue/model-types/deepseek_v3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "deepseek_v3",
"display_name": "DeepSeek V3",
"why": "DeepSeek V3.1 and later V3 releases can think before they answer. V3-0324 and R1 have no switch in their chat template and are not flagged.",
"thinking": {
"kwarg": "thinking",
"default": false,
"why": "The chat template answers directly unless the thinking kwarg is true, which makes it reason first."
}
}
6 changes: 6 additions & 0 deletions catalogue/model-types/deepseek_v4.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"id": "deepseek_v4",
"display_name": "DeepSeek V4",
"why": "DeepSeek V4 (Flash, Pro and the Flash Vision preview) ships no chat template. It encodes chats in Python, which vLLM carries as its deepseek_v4 tokenizer mode, and writes tool calls and thinking in its own format.",
"thinking": {
"kwarg": "thinking",
"default": true,
"detect": "always",
"why": "vLLM's deepseek_v4 tokenizer mode starts a thinking block before each reply unless the thinking kwarg is false."
},
"engines": {
"vllm": {
"args": [
Expand Down
11 changes: 11 additions & 0 deletions catalogue/model-types/glm4_moe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "glm4_moe",
"display_name": "GLM-4.5 / 4.6 / 4.7",
"why": "GLM-4.5, 4.5-Air, 4.6 and 4.7 think before they answer by default.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons before answering unless enable_thinking is false."
}
}
11 changes: 11 additions & 0 deletions catalogue/model-types/glm4_moe_lite.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "glm4_moe_lite",
"display_name": "GLM-4.7-Flash",
"why": "GLM-4.7-Flash thinks before it answers by default.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons before answering unless enable_thinking is false."
}
}
11 changes: 11 additions & 0 deletions catalogue/model-types/glm4v_moe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "glm4v_moe",
"display_name": "GLM-4.5V",
"why": "GLM-4.5V thinks before it answers by default.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons before answering unless enable_thinking is false."
}
}
11 changes: 11 additions & 0 deletions catalogue/model-types/glm_moe_dsa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "glm_moe_dsa",
"display_name": "GLM-5",
"why": "GLM-5 and models built on it think before they answer by default.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons before answering unless enable_thinking is false."
}
}
11 changes: 11 additions & 0 deletions catalogue/model-types/qwen3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "qwen3",
"display_name": "Qwen3",
"why": "Qwen3 dense models whose chat template has a thinking switch. The 2507 Instruct and Thinking releases have no switch and are not flagged.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons inside <think> before answering unless enable_thinking is false, which on short prompts can be most of the reply time."
}
}
5 changes: 5 additions & 0 deletions catalogue/model-types/qwen3_5.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"id": "qwen3_5",
"display_name": "Qwen3.5",
"why": "Qwen3.5 and models built on it (Ornith among them) ship a chat template that raises an error on a system message after the first turn.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons inside <think> before answering unless enable_thinking is false, which on short prompts can be most of the reply time."
},
"engines": {
"vllm": {
"chat_template_fixes": [
Expand Down
5 changes: 5 additions & 0 deletions catalogue/model-types/qwen3_5_moe.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"id": "qwen3_5_moe",
"display_name": "Qwen3.5 MoE",
"why": "Qwen3.5 and models built on it (Ornith among them) ship a chat template that raises an error on a system message after the first turn.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons inside <think> before answering unless enable_thinking is false, which on short prompts can be most of the reply time."
},
"engines": {
"vllm": {
"chat_template_fixes": [
Expand Down
11 changes: 11 additions & 0 deletions catalogue/model-types/qwen3_moe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "qwen3_moe",
"display_name": "Qwen3 MoE",
"why": "Qwen3 MoE models (30B-A3B, 235B-A22B) whose chat template has a thinking switch. The 2507 Instruct and Thinking releases have no switch and are not flagged.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons inside <think> before answering unless enable_thinking is false, which on short prompts can be most of the reply time."
}
}
11 changes: 11 additions & 0 deletions catalogue/model-types/smollm3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"schema_version": 1,
"id": "smollm3",
"display_name": "SmolLM3",
"why": "SmolLM3 thinks before it answers by default.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "The chat template reasons inside <think> before answering unless enable_thinking is false (or the system prompt says /no_think)."
}
}
17 changes: 16 additions & 1 deletion docs/model-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ A launch configuration for one specific repo, run and measured on named hardware
"id": "gpt_oss",
"display_name": "gpt-oss",
"why": "Why this architecture needs anything at all.",
"thinking": {
"kwarg": "enable_thinking",
"default": true,
"why": "What the switch does to replies."
},
"engines": {
"vllm": {
"files": [
Expand Down Expand Up @@ -55,12 +60,22 @@ A launch configuration for one specific repo, run and measured on named hardware
```

- `id` is the `model_type` value, and the file is named after it.
- `thinking` describes a chat template switch for reasoning before the answer. `kwarg` is the template variable, and `default` is whether the model thinks when nothing sets it. With `detect` left out or set to `template`, it applies only to repos whose chat template names `kwarg`, so a release of the same architecture without the switch is not flagged. `detect: always` applies it to every repo of the type, for types whose chat formatting lives in the engine rather than a template.
- `engines` is keyed by engine id. An engine with no entry gets nothing added. `files` apply on any engine. `args` and `chat_template_fixes` are in vLLM's flag syntax, so they are only accepted for engines with the `vllm_args` capability.
- `files` are fetched into `dir` under the data directory when a profile is created, unless already there. With `env` set, the profile gets that variable pointing at the directory.
- `args` are added to the profile's `EXTRA_ARGS` unless the card already named the flag. `values` are tried in order and the first the installed engine registers is used; if none is registered, the flag is left out. `with` flags are added alongside.
- `chat_template_fixes` apply when the repo's `chat_template.jinja` contains `contains`. The patched copy is saved under the data directory and passed with `--chat-template`. If the text is there but `match` does not match, nothing is patched and `new` says so.

Each file is checked when it loads: an unknown field, an unknown engine, `args` or `chat_template_fixes` for an engine without `vllm_args`, a bad regular expression, a `dir` outside the data directory or a `base_url` that is not https is refused.
A file needs `engines`, `thinking`, or both.
Each file is checked when it loads: an unknown field, an unknown engine, `args` or `chat_template_fixes` for an engine without `vllm_args`, a bad regular expression, a `dir` outside the data directory, a `base_url` that is not https, a `kwarg` that is not a template variable name or an unknown `detect` is refused.

## Thinking models

When a repo matches a `thinking` entry, `inspect` says whether the model thinks by default, `new` records it as `THINKING` in the profile, and `list` and `status` show it.
`new --thinking on|off` and the panel's Thinking control set it for the new profile.
They pass the kwarg through the engine's `chat_template_kwargs_flag` (`--default-chat-template-kwargs` on vLLM), so a client that sends its own `chat_template_kwargs` still overrides it per request.
An engine without that flag cannot switch thinking, and `new` says so.
Models that always think and have no switch are not flagged.

## Changing one locally

Expand Down
13 changes: 13 additions & 0 deletions docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ Qwen3's cards are the case this covers: their `vllm serve` line names `deepseek_
`search` marks what is too big for this machine and what cannot load, and preselects the biggest quant that fits.
`new` refuses a quant that cannot load rather than serving it — activating a profile restarts the service, so a model that cannot load would take the running one down with it.

## Thinking models

Some models reason before they answer, and whether they do is a switch in their chat template.
`inspect` says when a repo has that switch and whether it is on by default, and `list` and `status` mark the profile.
A thinking pass can run to thousands of tokens before the first word of the answer, so turn it off for a model you want fast replies from:

```sh
llm-helper inspect Qwen/Qwen3-0.6B # thinking: on by default
llm-helper new Qwen/Qwen3-0.6B --thinking off
```

Clients can still turn it back on per request; see [Model types](./model-types.md).

## Choosing a quantization

Reach for a quantized release when a model is large: roughly 1 byte per parameter for FP8/INT8 and 0.5 for INT4/AWQ/GPTQ, against the budget `doctor` reports.
Expand Down
1 change: 1 addition & 0 deletions docs/panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Picking a result checks its weight size against this machine's budget and its mo
The weights themselves are downloaded by vLLM as it starts, so a big model spends a while in the panel's loading state before it serves.
Saved models can be removed from the same list they are switched from — the active one can't, and the weights stay in your HuggingFace cache either way.

When the model is a thinking model, the page shows a **Thinking** control set to the model's default; turning it off saves a profile that answers without the reasoning pass.
When the installed vLLM cannot load GGUF, the page offers to install the plugin; see [Models and quantizations](./models.md).

## Docs
Expand Down
7 changes: 7 additions & 0 deletions docs/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ EXTRA_ARGS="--enable-auto-tool-choice --tool-call-parser qwen3_coder"
| `KV_CACHE_MEMORY_BYTES` | Size of the KV cache in bytes, computed when the profile is written. |
| `EXTRA_ARGS` | Extra flags passed to the profile's server verbatim. |
| `PROVIDER` | The server that runs this profile, `vllm` when absent. See [Providers](./providers.md). |
| `THINKING` | `on` or `off` for a [thinking model](./model-types.md), written by `new`. Shown by `list` and `status`. |

Any other `KEY=VALUE` in the file is exported into the server's environment as-is.

Expand All @@ -33,6 +34,12 @@ Any other `KEY=VALUE` in the file is exported into the server's environment as-i
`EXTRA_ARGS` is parsed like a shell line, so keep it quoted when it carries more than one flag.
A flag value with spaces in it — a JSON payload, for example — can be quoted inside.

### THINKING

`THINKING` records what `new` set; the server is switched by the `--default-chat-template-kwargs` flag in `EXTRA_ARGS`.
To turn thinking off in an existing profile, add `--default-chat-template-kwargs '{"enable_thinking":false}'` to its `EXTRA_ARGS` (with the model's own kwarg, which `model-types show <model_type>` prints) and set `THINKING=off`.
Profiles written before this key existed have no `THINKING` line, and `list` shows nothing for them until they are re-created.

### KV cache sizing

`KV_CACHE_MEMORY_BYTES` is sized when the profile is written, from the model's own attention shape and `KV_CONTEXT_TARGET` in `config.env` — four full contexts by default.
Expand Down
1 change: 1 addition & 0 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ A definition holds `schema_version` and the engine's fields, and says:
- `serve` and `optional_args` for a process, or `container.roles` for a container: the command, with placeholders such as `{model}`, `{port}` and the engine's own params. An optional group is left out when any of its placeholders is empty. A container's `launch_args` take only what every rank shares: `{model}`, `{port}`, `{nodes}`, the params and the pinned models' paths.
- `health`: the path that answers 200, and whether it does so only once the model is loaded (`ready`) or as soon as the process listens (`live`).
- `capabilities`: the few differences shared code acts on, such as `vllm_args` for vLLM's sizing and flag checks. Launching and health checks act on a capability, never on an engine's name.
`chat_template_kwargs_flag` names the flag that sets default chat template kwargs, which is how a profile turns a model's thinking on or off; see [Model types](./model-types.md).

The definition is checked when it loads.
An unknown field, an unknown placeholder, a key that would land on the command line, or a container with no role that serves HTTP is refused with the reason.
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/cli/new.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ return as soon as the server is started instead of watching the boot

which quantization to serve, for repos that ship several (see 'llm-helper inspect')

### --thinking

| | |
| ---- | -------- |
| Type | `string` |

on or off: whether a thinking model reasons before it answers (default: the model's own)

### --timeout

| | |
Expand Down
1 change: 1 addition & 0 deletions internal/cli/editset.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var standardProfileKeys = map[string]bool{
"KV_CACHE_MEMORY_BYTES": true,
"EXTRA_ARGS": true,
profile.RecipeKey: true,
profile.ThinkingKey: true,
engine.Key: true,
}

Expand Down
19 changes: 18 additions & 1 deletion internal/cli/modeltypescmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ func runModelTypesList(out, errOut io.Writer, jsonOut bool) error {
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "ID\tENGINES\tNAME\tSOURCE")
for _, r := range rows {
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", r.ID, strings.Join(engineIDs(r.Entry), ","), r.DisplayName, r.Source)
engines := strings.Join(engineIDs(r.Entry), ",")
if engines == "" {
engines = "-"
}
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", r.ID, engines, r.DisplayName, r.Source)
}
if err := tw.Flush(); err != nil {
return err
Expand All @@ -122,6 +126,19 @@ func runModelTypesShow(out io.Writer, id string, jsonOut bool) error {
fmt.Fprintf(out, "%s (%s)\n\n", e.DisplayName, e.ID)
fmt.Fprintf(out, " %-14s %s\n\n", "source", modelTypeSource(e))
fmt.Fprintln(out, e.Why)
if t := e.Thinking; t != nil {
detect := "when the chat template names " + t.Kwarg
if t.Detect == modeltype.DetectAlways {
detect = "on every repo of this type"
}
def := "off"
if t.Default {
def = "on"
}
fmt.Fprintf(out, "\nThinking (%s)\n", detect)
fmt.Fprintf(out, " chat template kwarg %s, %s unless a profile sets it\n", t.Kwarg, def)
fmt.Fprintf(out, " %s\n", t.Why)
}
for _, id := range engineIDs(e) {
r := e.Engines[id]
fmt.Fprintf(out, "\nOn %s\n", id)
Expand Down
Loading
Loading