llama-mdl 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llama_mdl-0.2.0/LICENSE +21 -0
- llama_mdl-0.2.0/PKG-INFO +364 -0
- llama_mdl-0.2.0/README.md +317 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/PKG-INFO +364 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/SOURCES.txt +18 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/dependency_links.txt +1 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/entry_points.txt +2 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/requires.txt +3 -0
- llama_mdl-0.2.0/llama_mdl.egg-info/top_level.txt +2 -0
- llama_mdl-0.2.0/mdl.py +642 -0
- llama_mdl-0.2.0/mdl_ui.py +1326 -0
- llama_mdl-0.2.0/pyproject.toml +57 -0
- llama_mdl-0.2.0/setup.cfg +4 -0
- llama_mdl-0.2.0/tests/test_atomic.py +122 -0
- llama_mdl-0.2.0/tests/test_chat.py +180 -0
- llama_mdl-0.2.0/tests/test_cli.py +204 -0
- llama_mdl-0.2.0/tests/test_commands.py +130 -0
- llama_mdl-0.2.0/tests/test_fx.py +95 -0
- llama_mdl-0.2.0/tests/test_live.py +117 -0
- llama_mdl-0.2.0/tests/test_ui.py +99 -0
llama_mdl-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 diverse
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
llama_mdl-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llama-mdl
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Run one local llama.cpp server from a config file, with an optional terminal dashboard
|
|
5
|
+
Author: diverse
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 diverse
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/diverseau/llama-mdl
|
|
29
|
+
Project-URL: Repository, https://github.com/diverseau/llama-mdl
|
|
30
|
+
Project-URL: Issues, https://github.com/diverseau/llama-mdl/issues
|
|
31
|
+
Project-URL: Changelog, https://github.com/diverseau/llama-mdl/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: llama.cpp,llm,gguf,cli,tui,local-ai
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: Utilities
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Provides-Extra: ui
|
|
45
|
+
Requires-Dist: textual<9,>=3; extra == "ui"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# mdl
|
|
49
|
+
|
|
50
|
+
A small CLI for running one local [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
|
51
|
+
server at a time from a config file, instead of pasting flag soup into your shell.
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
llama-server -m /srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf -ngl 99 \
|
|
55
|
+
--n-cpu-moe 24 -c 65536 -fa on --cache-type-k q8_0 \
|
|
56
|
+
--cache-type-v q8_0 -np 1 --port 8080
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
mdl run ornith
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+
|
|
65
|
+
`mdl.py` is a single file, Python 3.11+ (needs `tomllib`), standard library
|
|
66
|
+
only. Runs on Linux, macOS and Windows.
|
|
67
|
+
|
|
68
|
+
`mdl_ui.py` adds an optional terminal dashboard (`mdl ui`). It is the only
|
|
69
|
+
part that needs a dependency — [Textual](https://textual.textualize.io/) —
|
|
70
|
+
and the CLI never imports it, so every command but `ui` stays
|
|
71
|
+
dependency-free.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
pipx install llama-mdl # or: pip install llama-mdl
|
|
77
|
+
pipx install "llama-mdl[ui]" # with the terminal dashboard
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The package is `llama-mdl`; the command it installs is `mdl`. (Plain `mdl`
|
|
81
|
+
on PyPI is an unrelated project.) Nothing but the dashboard has a
|
|
82
|
+
dependency, and that is [Textual](https://textual.textualize.io/).
|
|
83
|
+
|
|
84
|
+
Or run it straight from a clone - it is two files and a standard library:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
git clone https://github.com/diverseau/llama-mdl ~/src/mdl
|
|
88
|
+
python ~/src/mdl/mdl.py --help
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Then create a starter config:
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
mdl init
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
That writes `~/.config/mdl/models.toml`, finds `llama-server` on your PATH if
|
|
98
|
+
it is there, and tells you what to edit.
|
|
99
|
+
|
|
100
|
+
## Config
|
|
101
|
+
|
|
102
|
+
`~/.config/mdl/models.toml`. One table per model; the table name is what you
|
|
103
|
+
pass to `mdl run`.
|
|
104
|
+
|
|
105
|
+
```toml
|
|
106
|
+
# Optional. Defaults to "llama-server" on $PATH.
|
|
107
|
+
# $MDL_LLAMA_SERVER overrides this.
|
|
108
|
+
llama_server = "/opt/llama.cpp/build/bin/llama-server"
|
|
109
|
+
|
|
110
|
+
[ornith]
|
|
111
|
+
model = "/srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf"
|
|
112
|
+
ngl = 99
|
|
113
|
+
n_cpu_moe = 24
|
|
114
|
+
ctx = 65536
|
|
115
|
+
flash_attn = true
|
|
116
|
+
kv_type = "q8_0"
|
|
117
|
+
parallel = 1
|
|
118
|
+
port = 8080
|
|
119
|
+
|
|
120
|
+
[qwen-small]
|
|
121
|
+
model = "/srv/models/Qwen3-8B-Q5_K_M.gguf"
|
|
122
|
+
ngl = 99
|
|
123
|
+
ctx = 16384
|
|
124
|
+
port = 8080
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
On Windows, write paths with forward slashes (`C:/models/foo.gguf`) or double
|
|
128
|
+
the backslashes, since TOML treats `\` as an escape character.
|
|
129
|
+
|
|
130
|
+
### Keys
|
|
131
|
+
|
|
132
|
+
| Key | llama-server flag | Notes |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `model` | `-m` | Required. |
|
|
135
|
+
| `ngl` | `-ngl` | |
|
|
136
|
+
| `n_cpu_moe` | `--n-cpu-moe` | |
|
|
137
|
+
| `ctx` | `-c` | |
|
|
138
|
+
| `flash_attn` | `-fa on` | Only emitted when `true`. |
|
|
139
|
+
| `kv_type` | `--cache-type-k` and `--cache-type-v` | Both get the same value. |
|
|
140
|
+
| `parallel` | `-np` | |
|
|
141
|
+
| `port` | `--port` | Defaults to 8080. |
|
|
142
|
+
| `args` | passed through verbatim | Array of strings, appended last. |
|
|
143
|
+
|
|
144
|
+
Two top-level keys sit outside the model tables: `llama_server` (above) and
|
|
145
|
+
`ready_timeout`, the seconds `run` waits for `/health` before giving up.
|
|
146
|
+
It defaults to 300, which a 70B on a slow disk can exceed.
|
|
147
|
+
|
|
148
|
+
Anything else in a model table is an error, so a typo like `flash_atn` tells you
|
|
149
|
+
instead of silently doing nothing.
|
|
150
|
+
|
|
151
|
+
## Commands
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
mdl run <name> Start <name> in the background, tail its log until the
|
|
155
|
+
server answers /health, and exit. The server keeps running
|
|
156
|
+
after mdl exits.
|
|
157
|
+
mdl stop SIGTERM the running server, SIGKILL after 10s, clean up.
|
|
158
|
+
mdl ps [--json] name, pid, port and uptime, or "nothing running".
|
|
159
|
+
--json prints the state as JSON (null when idle) for
|
|
160
|
+
scripts and status bars.
|
|
161
|
+
mdl list The models defined in the config.
|
|
162
|
+
mdl add <gguf> Append an entry for a .gguf to the config, with sane
|
|
163
|
+
defaults. Takes an optional name and port.
|
|
164
|
+
mdl check Validate every model in the config without launching
|
|
165
|
+
anything. Exits non-zero if it finds a problem.
|
|
166
|
+
mdl init Write a starter config, if you do not have one.
|
|
167
|
+
mdl --version The version, for bug reports.
|
|
168
|
+
mdl ui The dashboard. Bare `mdl` opens it too.
|
|
169
|
+
mdl logs [-f] Print the running server's log; -f follows it.
|
|
170
|
+
Takes a model name to read a stopped one's log.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Without textual installed, `mdl ui` fails with one line and bare `mdl` prints
|
|
174
|
+
the usage string, exactly as it always did.
|
|
175
|
+
|
|
176
|
+
```console
|
|
177
|
+
$ mdl list
|
|
178
|
+
ornith /srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf
|
|
179
|
+
qwen-small /srv/models/Qwen3-8B-Q5_K_M.gguf
|
|
180
|
+
|
|
181
|
+
$ mdl run ornith
|
|
182
|
+
starting ornith (pid 48812), log /home/leon/.local/state/mdl/ornith.log
|
|
183
|
+
load_tensors: offloaded 43/43 layers to GPU
|
|
184
|
+
llama_context: n_ctx = 65536
|
|
185
|
+
main: server is listening on http://127.0.0.1:8080
|
|
186
|
+
ready: ornith on http://127.0.0.1:8080 (pid 48812)
|
|
187
|
+
|
|
188
|
+
$ mdl ps
|
|
189
|
+
ornith pid 48812 port 8080 up 1h04m
|
|
190
|
+
|
|
191
|
+
$ mdl stop
|
|
192
|
+
stopped ornith (pid 48812)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`add` and `check` are the two that save the most time:
|
|
196
|
+
|
|
197
|
+
```console
|
|
198
|
+
$ mdl add ~/models/Qwen3-8B-Q5_K_M.gguf
|
|
199
|
+
added [qwen3-8b-q5-k-m] to /home/leon/.config/mdl/models.toml
|
|
200
|
+
Qwen3-8B-Q5_K_M.gguf (5.4G, 37 layers)
|
|
201
|
+
run it with: mdl run qwen3-8b-q5-k-m
|
|
202
|
+
|
|
203
|
+
$ mdl check
|
|
204
|
+
ornith ok
|
|
205
|
+
qwen-small model file not found
|
|
206
|
+
mdl: 1 problem(s) found
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`add` only appends, and `check` never launches anything, so both are safe
|
|
210
|
+
to run against a config you care about.
|
|
211
|
+
|
|
212
|
+
## The UI
|
|
213
|
+
|
|
214
|
+
`mdl ui` (or just `mdl`) opens a dashboard over the same config and the same
|
|
215
|
+
state file. Anything you do in it is visible to the CLI and vice versa.
|
|
216
|
+
|
|
217
|
+
Idle, it lists your models with a status dot, shows the selected model's
|
|
218
|
+
parameters, and previews the exact `llama-server` command it would run.
|
|
219
|
+
`e` edits those parameters and saves them back to `models.toml`, leaving
|
|
220
|
+
your comments and layout alone.
|
|
221
|
+
Running, it swaps in live telemetry: VRAM, KV-cache use, a tokens/sec
|
|
222
|
+
sparkline, busy slots, and a colour-coded log tail.
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
key does
|
|
226
|
+
up/down, j k select a model
|
|
227
|
+
enter, r run the selected model
|
|
228
|
+
s stop the running server
|
|
229
|
+
R restart
|
|
230
|
+
e edit ngl / ctx / kv_type / port, saved to models.toml
|
|
231
|
+
c copy the llama-server command
|
|
232
|
+
p prompt the running model without leaving the UI
|
|
233
|
+
l focus the log, / filters it
|
|
234
|
+
g reload the config
|
|
235
|
+
? help
|
|
236
|
+
q quit the UI - the server keeps running
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Quitting never stops a server; `s` is the only thing that does.
|
|
240
|
+
|
|
241
|
+
The telemetry panels need llama.cpp's metrics endpoint, so add `--metrics`
|
|
242
|
+
to a model's `args` to light them up:
|
|
243
|
+
|
|
244
|
+
```toml
|
|
245
|
+
args = ["--metrics"]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Without it the dashboard still works, and those panels say `metrics off`
|
|
249
|
+
rather than failing. While a model is loading they say `loading` instead,
|
|
250
|
+
since nothing is listening yet.
|
|
251
|
+
|
|
252
|
+
### Talking to the model
|
|
253
|
+
|
|
254
|
+
`p` opens a chat with whatever is running, without leaving the UI.
|
|
255
|
+
|
|
256
|
+

|
|
257
|
+
|
|
258
|
+
It keeps the conversation, so follow-up questions have context; `ctrl+l`
|
|
259
|
+
starts a fresh one. Reasoning is shown dimmed and timed
|
|
260
|
+
separately, whether the server hands it back in its own field or inline
|
|
261
|
+
as `<think>` tags. `esc` interrupts a running reply - it closes the
|
|
262
|
+
socket rather than waiting for the next token - and closes the pane once
|
|
263
|
+
nothing is streaming.
|
|
264
|
+
|
|
265
|
+
The rate is the server's own `tok/s` when it reports timings, and ours
|
|
266
|
+
otherwise. `ttft` is time to first token, which is the number that tells
|
|
267
|
+
you whether a long context is hurting.
|
|
268
|
+
|
|
269
|
+
### Animation
|
|
270
|
+
|
|
271
|
+
The wordmark drifts its gradient by default. Set `ui_fx = "off"` at the
|
|
272
|
+
top level of the config to paint it flat, or pass `mdl ui --no-fx` for a
|
|
273
|
+
one-off.
|
|
274
|
+
|
|
275
|
+
## Files
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
~/.config/mdl/models.toml your config
|
|
279
|
+
~/.local/state/mdl/state.json name, pid, port and start time of the server
|
|
280
|
+
~/.local/state/mdl/<name>.log server stdout+stderr, rotated on each run
|
|
281
|
+
~/.local/state/mdl/<name>.log.1 the previous run, and .2 before that
|
|
282
|
+
~/.local/state/mdl/ui-marks.json which models the UI has seen start or fail
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
`$XDG_CONFIG_HOME` and `$XDG_STATE_HOME` are honoured if set. On Windows the
|
|
286
|
+
same layout lives under `%USERPROFILE%`.
|
|
287
|
+
|
|
288
|
+
## Behaviour notes
|
|
289
|
+
|
|
290
|
+
- **One server at a time.** `run` while something is up is an error telling you
|
|
291
|
+
to `stop` first.
|
|
292
|
+
- **Readiness is an HTTP probe, not log scraping.** `run` polls `/health` on
|
|
293
|
+
the configured port. llama.cpp has reworded its startup line between builds;
|
|
294
|
+
this contract has not.
|
|
295
|
+
- **Obvious mistakes fail before launch.** A missing model file, a missing
|
|
296
|
+
binary or a busy port is one line in milliseconds, not a failed model load.
|
|
297
|
+
- **Stale state self-heals.** If the pid in `state.json` is gone (crash, reboot,
|
|
298
|
+
`kill -9`) the file is removed and `ps` reports nothing running.
|
|
299
|
+
- **If the server exits during startup,** `run` reports its exit status, removes
|
|
300
|
+
the state file, and exits 1. The log has the reason.
|
|
301
|
+
- **If it does not report ready in time,** `run` exits 1 but leaves the server
|
|
302
|
+
running, since it may still be loading. Check the log, or `mdl stop`. Raise
|
|
303
|
+
`ready_timeout` if 300s is genuinely not enough.
|
|
304
|
+
- **Pid reuse is guarded against.** The state file records the OS process
|
|
305
|
+
creation time, so a recycled pid is not mistaken for your server. macOS has
|
|
306
|
+
no cheap way to read that, so it falls back to the pid alone.
|
|
307
|
+
- **`stop` signals the process tree, not just the pid.** If your `llama_server`
|
|
308
|
+
is a wrapper script, killing the wrapper alone would orphan the real server
|
|
309
|
+
and leave the port held.
|
|
310
|
+
- **The last few logs are kept.** `<name>.log` shuffles along to `.1` and `.2`
|
|
311
|
+
on each run, so the crash you were not watching is still there.
|
|
312
|
+
- Errors are one line on stderr and a non-zero exit. No tracebacks.
|
|
313
|
+
|
|
314
|
+
## Tests
|
|
315
|
+
|
|
316
|
+
```sh
|
|
317
|
+
python tests/run.py # fast: no real model needed
|
|
318
|
+
python tests/run.py --live # also drives a real model through the UI
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The fast suites run against a temp config and a fake `llama-server`, so they
|
|
322
|
+
never touch `~/.config/mdl`. The POSIX process semantics (detaching, orphan
|
|
323
|
+
self-heal, SIGTERM escalating to SIGKILL) need Linux:
|
|
324
|
+
|
|
325
|
+
```sh
|
|
326
|
+
docker run --rm -v "$PWD:/repo:ro" python:3.12-slim \
|
|
327
|
+
sh -c 'cp -r /repo /w && cd /w && python3 tests/integration_posix.py'
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Non-goals
|
|
331
|
+
|
|
332
|
+
These are deliberate, and issues asking for them will be closed with a link
|
|
333
|
+
here. `mdl` starts one server, stops it, and tells you what is running.
|
|
334
|
+
|
|
335
|
+
- **No daemon.** Nothing runs in the background except the server itself.
|
|
336
|
+
- **No multiple concurrent servers.** These models are measured in gigabytes
|
|
337
|
+
of VRAM; running two is usually a mistake, and refusing is a feature.
|
|
338
|
+
- **No model downloading.** Use `huggingface-cli`, or your browser.
|
|
339
|
+
- **No hot-swap or auto-unload.** `mdl stop && mdl run other` is two words.
|
|
340
|
+
- **No web UI.** llama-server already ships one.
|
|
341
|
+
|
|
342
|
+
If you want these, [llama-swap](https://github.com/mostlygeek/llama-swap)
|
|
343
|
+
and [Ollama](https://ollama.com) are good and solve different problems.
|
|
344
|
+
|
|
345
|
+
## Contributing
|
|
346
|
+
|
|
347
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Short version: open an issue first,
|
|
348
|
+
keep `mdl.py` free of dependencies, and run the tests.
|
|
349
|
+
|
|
350
|
+
```sh
|
|
351
|
+
python tests/run.py
|
|
352
|
+
ruff check .
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
CI runs the suites on Linux, macOS and Windows across Python 3.11-3.13, the
|
|
356
|
+
pinned Textual floor and the current release, Ruff, the POSIX process suite,
|
|
357
|
+
and a packaging check on both the wheel and the sdist.
|
|
358
|
+
|
|
359
|
+
Security issues go through [SECURITY.md](SECURITY.md), privately, rather
|
|
360
|
+
than the public tracker.
|
|
361
|
+
|
|
362
|
+
## License
|
|
363
|
+
|
|
364
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# mdl
|
|
2
|
+
|
|
3
|
+
A small CLI for running one local [llama.cpp](https://github.com/ggml-org/llama.cpp)
|
|
4
|
+
server at a time from a config file, instead of pasting flag soup into your shell.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
llama-server -m /srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf -ngl 99 \
|
|
8
|
+
--n-cpu-moe 24 -c 65536 -fa on --cache-type-k q8_0 \
|
|
9
|
+
--cache-type-v q8_0 -np 1 --port 8080
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
mdl run ornith
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
`mdl.py` is a single file, Python 3.11+ (needs `tomllib`), standard library
|
|
19
|
+
only. Runs on Linux, macOS and Windows.
|
|
20
|
+
|
|
21
|
+
`mdl_ui.py` adds an optional terminal dashboard (`mdl ui`). It is the only
|
|
22
|
+
part that needs a dependency — [Textual](https://textual.textualize.io/) —
|
|
23
|
+
and the CLI never imports it, so every command but `ui` stays
|
|
24
|
+
dependency-free.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
pipx install llama-mdl # or: pip install llama-mdl
|
|
30
|
+
pipx install "llama-mdl[ui]" # with the terminal dashboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The package is `llama-mdl`; the command it installs is `mdl`. (Plain `mdl`
|
|
34
|
+
on PyPI is an unrelated project.) Nothing but the dashboard has a
|
|
35
|
+
dependency, and that is [Textual](https://textual.textualize.io/).
|
|
36
|
+
|
|
37
|
+
Or run it straight from a clone - it is two files and a standard library:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
git clone https://github.com/diverseau/llama-mdl ~/src/mdl
|
|
41
|
+
python ~/src/mdl/mdl.py --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then create a starter config:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
mdl init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That writes `~/.config/mdl/models.toml`, finds `llama-server` on your PATH if
|
|
51
|
+
it is there, and tells you what to edit.
|
|
52
|
+
|
|
53
|
+
## Config
|
|
54
|
+
|
|
55
|
+
`~/.config/mdl/models.toml`. One table per model; the table name is what you
|
|
56
|
+
pass to `mdl run`.
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
# Optional. Defaults to "llama-server" on $PATH.
|
|
60
|
+
# $MDL_LLAMA_SERVER overrides this.
|
|
61
|
+
llama_server = "/opt/llama.cpp/build/bin/llama-server"
|
|
62
|
+
|
|
63
|
+
[ornith]
|
|
64
|
+
model = "/srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf"
|
|
65
|
+
ngl = 99
|
|
66
|
+
n_cpu_moe = 24
|
|
67
|
+
ctx = 65536
|
|
68
|
+
flash_attn = true
|
|
69
|
+
kv_type = "q8_0"
|
|
70
|
+
parallel = 1
|
|
71
|
+
port = 8080
|
|
72
|
+
|
|
73
|
+
[qwen-small]
|
|
74
|
+
model = "/srv/models/Qwen3-8B-Q5_K_M.gguf"
|
|
75
|
+
ngl = 99
|
|
76
|
+
ctx = 16384
|
|
77
|
+
port = 8080
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
On Windows, write paths with forward slashes (`C:/models/foo.gguf`) or double
|
|
81
|
+
the backslashes, since TOML treats `\` as an escape character.
|
|
82
|
+
|
|
83
|
+
### Keys
|
|
84
|
+
|
|
85
|
+
| Key | llama-server flag | Notes |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `model` | `-m` | Required. |
|
|
88
|
+
| `ngl` | `-ngl` | |
|
|
89
|
+
| `n_cpu_moe` | `--n-cpu-moe` | |
|
|
90
|
+
| `ctx` | `-c` | |
|
|
91
|
+
| `flash_attn` | `-fa on` | Only emitted when `true`. |
|
|
92
|
+
| `kv_type` | `--cache-type-k` and `--cache-type-v` | Both get the same value. |
|
|
93
|
+
| `parallel` | `-np` | |
|
|
94
|
+
| `port` | `--port` | Defaults to 8080. |
|
|
95
|
+
| `args` | passed through verbatim | Array of strings, appended last. |
|
|
96
|
+
|
|
97
|
+
Two top-level keys sit outside the model tables: `llama_server` (above) and
|
|
98
|
+
`ready_timeout`, the seconds `run` waits for `/health` before giving up.
|
|
99
|
+
It defaults to 300, which a 70B on a slow disk can exceed.
|
|
100
|
+
|
|
101
|
+
Anything else in a model table is an error, so a typo like `flash_atn` tells you
|
|
102
|
+
instead of silently doing nothing.
|
|
103
|
+
|
|
104
|
+
## Commands
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
mdl run <name> Start <name> in the background, tail its log until the
|
|
108
|
+
server answers /health, and exit. The server keeps running
|
|
109
|
+
after mdl exits.
|
|
110
|
+
mdl stop SIGTERM the running server, SIGKILL after 10s, clean up.
|
|
111
|
+
mdl ps [--json] name, pid, port and uptime, or "nothing running".
|
|
112
|
+
--json prints the state as JSON (null when idle) for
|
|
113
|
+
scripts and status bars.
|
|
114
|
+
mdl list The models defined in the config.
|
|
115
|
+
mdl add <gguf> Append an entry for a .gguf to the config, with sane
|
|
116
|
+
defaults. Takes an optional name and port.
|
|
117
|
+
mdl check Validate every model in the config without launching
|
|
118
|
+
anything. Exits non-zero if it finds a problem.
|
|
119
|
+
mdl init Write a starter config, if you do not have one.
|
|
120
|
+
mdl --version The version, for bug reports.
|
|
121
|
+
mdl ui The dashboard. Bare `mdl` opens it too.
|
|
122
|
+
mdl logs [-f] Print the running server's log; -f follows it.
|
|
123
|
+
Takes a model name to read a stopped one's log.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Without textual installed, `mdl ui` fails with one line and bare `mdl` prints
|
|
127
|
+
the usage string, exactly as it always did.
|
|
128
|
+
|
|
129
|
+
```console
|
|
130
|
+
$ mdl list
|
|
131
|
+
ornith /srv/models/Ornith-1.5-35B-A3B-Q4_K_M.gguf
|
|
132
|
+
qwen-small /srv/models/Qwen3-8B-Q5_K_M.gguf
|
|
133
|
+
|
|
134
|
+
$ mdl run ornith
|
|
135
|
+
starting ornith (pid 48812), log /home/leon/.local/state/mdl/ornith.log
|
|
136
|
+
load_tensors: offloaded 43/43 layers to GPU
|
|
137
|
+
llama_context: n_ctx = 65536
|
|
138
|
+
main: server is listening on http://127.0.0.1:8080
|
|
139
|
+
ready: ornith on http://127.0.0.1:8080 (pid 48812)
|
|
140
|
+
|
|
141
|
+
$ mdl ps
|
|
142
|
+
ornith pid 48812 port 8080 up 1h04m
|
|
143
|
+
|
|
144
|
+
$ mdl stop
|
|
145
|
+
stopped ornith (pid 48812)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`add` and `check` are the two that save the most time:
|
|
149
|
+
|
|
150
|
+
```console
|
|
151
|
+
$ mdl add ~/models/Qwen3-8B-Q5_K_M.gguf
|
|
152
|
+
added [qwen3-8b-q5-k-m] to /home/leon/.config/mdl/models.toml
|
|
153
|
+
Qwen3-8B-Q5_K_M.gguf (5.4G, 37 layers)
|
|
154
|
+
run it with: mdl run qwen3-8b-q5-k-m
|
|
155
|
+
|
|
156
|
+
$ mdl check
|
|
157
|
+
ornith ok
|
|
158
|
+
qwen-small model file not found
|
|
159
|
+
mdl: 1 problem(s) found
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`add` only appends, and `check` never launches anything, so both are safe
|
|
163
|
+
to run against a config you care about.
|
|
164
|
+
|
|
165
|
+
## The UI
|
|
166
|
+
|
|
167
|
+
`mdl ui` (or just `mdl`) opens a dashboard over the same config and the same
|
|
168
|
+
state file. Anything you do in it is visible to the CLI and vice versa.
|
|
169
|
+
|
|
170
|
+
Idle, it lists your models with a status dot, shows the selected model's
|
|
171
|
+
parameters, and previews the exact `llama-server` command it would run.
|
|
172
|
+
`e` edits those parameters and saves them back to `models.toml`, leaving
|
|
173
|
+
your comments and layout alone.
|
|
174
|
+
Running, it swaps in live telemetry: VRAM, KV-cache use, a tokens/sec
|
|
175
|
+
sparkline, busy slots, and a colour-coded log tail.
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
key does
|
|
179
|
+
up/down, j k select a model
|
|
180
|
+
enter, r run the selected model
|
|
181
|
+
s stop the running server
|
|
182
|
+
R restart
|
|
183
|
+
e edit ngl / ctx / kv_type / port, saved to models.toml
|
|
184
|
+
c copy the llama-server command
|
|
185
|
+
p prompt the running model without leaving the UI
|
|
186
|
+
l focus the log, / filters it
|
|
187
|
+
g reload the config
|
|
188
|
+
? help
|
|
189
|
+
q quit the UI - the server keeps running
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Quitting never stops a server; `s` is the only thing that does.
|
|
193
|
+
|
|
194
|
+
The telemetry panels need llama.cpp's metrics endpoint, so add `--metrics`
|
|
195
|
+
to a model's `args` to light them up:
|
|
196
|
+
|
|
197
|
+
```toml
|
|
198
|
+
args = ["--metrics"]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Without it the dashboard still works, and those panels say `metrics off`
|
|
202
|
+
rather than failing. While a model is loading they say `loading` instead,
|
|
203
|
+
since nothing is listening yet.
|
|
204
|
+
|
|
205
|
+
### Talking to the model
|
|
206
|
+
|
|
207
|
+
`p` opens a chat with whatever is running, without leaving the UI.
|
|
208
|
+
|
|
209
|
+

|
|
210
|
+
|
|
211
|
+
It keeps the conversation, so follow-up questions have context; `ctrl+l`
|
|
212
|
+
starts a fresh one. Reasoning is shown dimmed and timed
|
|
213
|
+
separately, whether the server hands it back in its own field or inline
|
|
214
|
+
as `<think>` tags. `esc` interrupts a running reply - it closes the
|
|
215
|
+
socket rather than waiting for the next token - and closes the pane once
|
|
216
|
+
nothing is streaming.
|
|
217
|
+
|
|
218
|
+
The rate is the server's own `tok/s` when it reports timings, and ours
|
|
219
|
+
otherwise. `ttft` is time to first token, which is the number that tells
|
|
220
|
+
you whether a long context is hurting.
|
|
221
|
+
|
|
222
|
+
### Animation
|
|
223
|
+
|
|
224
|
+
The wordmark drifts its gradient by default. Set `ui_fx = "off"` at the
|
|
225
|
+
top level of the config to paint it flat, or pass `mdl ui --no-fx` for a
|
|
226
|
+
one-off.
|
|
227
|
+
|
|
228
|
+
## Files
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
~/.config/mdl/models.toml your config
|
|
232
|
+
~/.local/state/mdl/state.json name, pid, port and start time of the server
|
|
233
|
+
~/.local/state/mdl/<name>.log server stdout+stderr, rotated on each run
|
|
234
|
+
~/.local/state/mdl/<name>.log.1 the previous run, and .2 before that
|
|
235
|
+
~/.local/state/mdl/ui-marks.json which models the UI has seen start or fail
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`$XDG_CONFIG_HOME` and `$XDG_STATE_HOME` are honoured if set. On Windows the
|
|
239
|
+
same layout lives under `%USERPROFILE%`.
|
|
240
|
+
|
|
241
|
+
## Behaviour notes
|
|
242
|
+
|
|
243
|
+
- **One server at a time.** `run` while something is up is an error telling you
|
|
244
|
+
to `stop` first.
|
|
245
|
+
- **Readiness is an HTTP probe, not log scraping.** `run` polls `/health` on
|
|
246
|
+
the configured port. llama.cpp has reworded its startup line between builds;
|
|
247
|
+
this contract has not.
|
|
248
|
+
- **Obvious mistakes fail before launch.** A missing model file, a missing
|
|
249
|
+
binary or a busy port is one line in milliseconds, not a failed model load.
|
|
250
|
+
- **Stale state self-heals.** If the pid in `state.json` is gone (crash, reboot,
|
|
251
|
+
`kill -9`) the file is removed and `ps` reports nothing running.
|
|
252
|
+
- **If the server exits during startup,** `run` reports its exit status, removes
|
|
253
|
+
the state file, and exits 1. The log has the reason.
|
|
254
|
+
- **If it does not report ready in time,** `run` exits 1 but leaves the server
|
|
255
|
+
running, since it may still be loading. Check the log, or `mdl stop`. Raise
|
|
256
|
+
`ready_timeout` if 300s is genuinely not enough.
|
|
257
|
+
- **Pid reuse is guarded against.** The state file records the OS process
|
|
258
|
+
creation time, so a recycled pid is not mistaken for your server. macOS has
|
|
259
|
+
no cheap way to read that, so it falls back to the pid alone.
|
|
260
|
+
- **`stop` signals the process tree, not just the pid.** If your `llama_server`
|
|
261
|
+
is a wrapper script, killing the wrapper alone would orphan the real server
|
|
262
|
+
and leave the port held.
|
|
263
|
+
- **The last few logs are kept.** `<name>.log` shuffles along to `.1` and `.2`
|
|
264
|
+
on each run, so the crash you were not watching is still there.
|
|
265
|
+
- Errors are one line on stderr and a non-zero exit. No tracebacks.
|
|
266
|
+
|
|
267
|
+
## Tests
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
python tests/run.py # fast: no real model needed
|
|
271
|
+
python tests/run.py --live # also drives a real model through the UI
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The fast suites run against a temp config and a fake `llama-server`, so they
|
|
275
|
+
never touch `~/.config/mdl`. The POSIX process semantics (detaching, orphan
|
|
276
|
+
self-heal, SIGTERM escalating to SIGKILL) need Linux:
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
docker run --rm -v "$PWD:/repo:ro" python:3.12-slim \
|
|
280
|
+
sh -c 'cp -r /repo /w && cd /w && python3 tests/integration_posix.py'
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Non-goals
|
|
284
|
+
|
|
285
|
+
These are deliberate, and issues asking for them will be closed with a link
|
|
286
|
+
here. `mdl` starts one server, stops it, and tells you what is running.
|
|
287
|
+
|
|
288
|
+
- **No daemon.** Nothing runs in the background except the server itself.
|
|
289
|
+
- **No multiple concurrent servers.** These models are measured in gigabytes
|
|
290
|
+
of VRAM; running two is usually a mistake, and refusing is a feature.
|
|
291
|
+
- **No model downloading.** Use `huggingface-cli`, or your browser.
|
|
292
|
+
- **No hot-swap or auto-unload.** `mdl stop && mdl run other` is two words.
|
|
293
|
+
- **No web UI.** llama-server already ships one.
|
|
294
|
+
|
|
295
|
+
If you want these, [llama-swap](https://github.com/mostlygeek/llama-swap)
|
|
296
|
+
and [Ollama](https://ollama.com) are good and solve different problems.
|
|
297
|
+
|
|
298
|
+
## Contributing
|
|
299
|
+
|
|
300
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Short version: open an issue first,
|
|
301
|
+
keep `mdl.py` free of dependencies, and run the tests.
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
python tests/run.py
|
|
305
|
+
ruff check .
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
CI runs the suites on Linux, macOS and Windows across Python 3.11-3.13, the
|
|
309
|
+
pinned Textual floor and the current release, Ruff, the POSIX process suite,
|
|
310
|
+
and a packaging check on both the wheel and the sdist.
|
|
311
|
+
|
|
312
|
+
Security issues go through [SECURITY.md](SECURITY.md), privately, rather
|
|
313
|
+
than the public tracker.
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT. See [LICENSE](LICENSE).
|