tuieval 0.2.0.dev11__tar.gz → 0.2.0.dev28__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.
Files changed (76) hide show
  1. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/CHANGELOG.md +12 -0
  2. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/PKG-INFO +20 -8
  3. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/README.md +19 -7
  4. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/models.md +130 -3
  5. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/writing-packs.md +6 -0
  6. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/_version.py +2 -2
  7. tuieval-0.2.0.dev28/src/tuieval/builtin.toml +83 -0
  8. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/cli.py +21 -13
  9. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/compare.py +17 -3
  10. tuieval-0.2.0.dev28/src/tuieval/doctor.py +131 -0
  11. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/engine.py +418 -68
  12. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/code.py +1 -0
  13. tuieval-0.2.0.dev28/src/tuieval/onboard.py +132 -0
  14. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/packs.py +17 -2
  15. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/run_evals.py +115 -13
  16. tuieval-0.2.0.dev28/src/tuieval/scaffold.py +243 -0
  17. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/selftest.py +11 -7
  18. tuieval-0.2.0.dev28/src/tuieval/templates/models.toml +23 -0
  19. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/tui.py +526 -55
  20. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/verdict.py +57 -0
  21. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/tests/mock_server.py +5 -4
  22. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/tests/test_tuieval.py +477 -4
  23. tuieval-0.2.0.dev11/src/tuieval/scaffold.py +0 -106
  24. tuieval-0.2.0.dev11/src/tuieval/templates/models.toml +0 -142
  25. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/.gitignore +0 -0
  26. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/LICENSE +0 -0
  27. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/RELEASING.md +0 -0
  28. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/README.md +0 -0
  29. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/favicon.ico +0 -0
  30. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/readme-header.png +0 -0
  31. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/social-preview.png +0 -0
  32. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-1024.png +0 -0
  33. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-128.png +0 -0
  34. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-16.png +0 -0
  35. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-16.svg +0 -0
  36. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-256.png +0 -0
  37. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-32.png +0 -0
  38. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-48.png +0 -0
  39. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-512.png +0 -0
  40. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-64.png +0 -0
  41. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon-animated.svg +0 -0
  42. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/brand/tuieval-icon.svg +0 -0
  43. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/docs/images/tui-setup.png +0 -0
  44. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/pyproject.toml +0 -0
  45. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/__init__.py +0 -0
  46. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/__main__.py +0 -0
  47. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/client.py +0 -0
  48. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/export.py +0 -0
  49. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/__init__.py +0 -0
  50. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/answer.py +0 -0
  51. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/rag.py +0 -0
  52. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/reply.py +0 -0
  53. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/graders/tool_call.py +0 -0
  54. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/machines.py +0 -0
  55. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/profiles.py +0 -0
  56. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/remove.py +0 -0
  57. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/answer/pack.toml +0 -0
  58. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/answer/system.txt +0 -0
  59. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/answer/tests.yaml +0 -0
  60. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/code/pack.toml +0 -0
  61. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/code/system.txt +0 -0
  62. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/code/tests.yaml +0 -0
  63. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/rag/pack.toml +0 -0
  64. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/rag/system.txt +0 -0
  65. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/rag/tests.yaml +0 -0
  66. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/reply/pack.toml +0 -0
  67. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/reply/system.txt +0 -0
  68. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/reply/tests.yaml +0 -0
  69. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/tool_call/pack.toml +0 -0
  70. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/tool_call/system.txt +0 -0
  71. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/tool_call/tests.yaml +0 -0
  72. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/templates/packs/tool_call/tools.yaml +0 -0
  73. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/tune.py +0 -0
  74. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/watch_proxy.py +0 -0
  75. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/workspace.py +0 -0
  76. {tuieval-0.2.0.dev11 → tuieval-0.2.0.dev28}/src/tuieval/yamlout.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Run several models at a time: `parallel_models` per machine, **Models at a time** in the TUI, or `tuieval run --parallel N` (default 1). Models wait for memory to fit, and answers note what ran alongside them.
6
+ - A new workspace's `models.toml` is 23 lines: the llama, local and OpenRouter servers and the defaults are built in (docs/models.md lists them in full; a server defined in `models.toml` replaces the built-in one with that name). Existing workspaces keep their settings; built-in servers they don't define become available, and adding a model still prefers the workspace's own servers.
7
+ - Less to take in at first: until something has run, the setup screen leaves out context sizes, tuning state and the Tune key; Presets and Hide are off the footer (`?` lists every key); `tuieval help` groups commands into Start here, Day to day and Advanced.
8
+ - Results lead with a plain summary per model ("ready for Support; not decided yet for Coding → run Certify to finish"), in the TUI, `tuieval verdict` (with the command to run next) and the report; the statistics follow.
9
+ - First packs without hand-written YAML: `tuieval new-pack <name> --from questions.csv` (question and answer columns) and `--about "<topic>"` (writes a prompt for a strong model to draft the tests). A test can take its prompt from a file (`input_file`). `tuieval selftest` errors say how to fix them.
10
+ - `tuieval init` in a terminal is a guided setup: it finds models on servers already running (LM Studio, Ollama, llama-server, vLLM), adds the ones you pick, creates a starter pack and runs a Smoke check (`--yes` for no questions).
11
+ - `tuieval add` without a model lists the models on running servers to pick from; `tuieval add <id>` finds the server that has it and adds that server to models.toml. The TUI's add dialog lists them too, with rarely needed options folded away.
12
+ - `?` on any TUI screen explains what it's for, its keys, and the words tuieval uses (tiers, verdicts, critical failures, gates).
13
+ - The setup screen is ready on first open: the only pack and model are ticked, the tier is Smoke until something has run, and ticked boxes are clearly marked (unticked ones are empty).
14
+ - First runs fail fast with the fix instead of hanging: a server that isn't running fails in seconds (it used to wait up to 20 minutes), a missing `llama-server` says how to install it, and `tuieval run` with no models says how to add one.
15
+ - `tuieval doctor` checks servers, models, packs and API keys, and says what to fix.
16
+ - Run only some tests of a pack: `e` in the TUI, or `tuieval run --tests`. Runs add up; `--force` reruns just the picked tests. Presets keep the pick.
5
17
  - `tuieval tune` scores options on projected full-length answers (`[tune] answer_tokens`, default 1024), so generation speed counts as in real runs.
6
18
  - `tuieval tune` reports an option's memory cost instead of rejecting it; it rejects only on critical memory pressure or over `[tune] swap_limit_mb`.
7
19
  - Warm-start tuning recognises fine-tunes whose GGUF describes the MTP draft layer differently (e.g. listing KV heads per layer) as the same model shape, so they start from an already-tuned sibling instead of tuning in full.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tuieval
3
- Version: 0.2.0.dev11
3
+ Version: 0.2.0.dev28
4
4
  Summary: Evaluate local and frontier LLMs on your own questions: accuracy, speed, tokens and PASS/FAIL verdicts, in the terminal.
5
5
  Project-URL: Homepage, https://github.com/ashe-wb/tuieval
6
6
  Project-URL: Issues, https://github.com/ashe-wb/tuieval/issues
@@ -43,12 +43,21 @@ Then you get an answer to the questions that matter:
43
43
 
44
44
  ```bash
45
45
  pipx install tuieval # or: uv tool install tuieval, or pip install tuieval (Python 3.11+)
46
- tuieval init my-evals && cd my-evals
47
- tuieval new-pack my-first-pack # a pack of example questions to edit
48
- tuieval add ~/models/Some-Model-Q4_K_M.gguf
49
- tuieval # open the TUI
46
+ tuieval init my-evals # guided: finds models already running (LM Studio, Ollama, vLLM…),
47
+ # adds a starter pack and runs a 3-question check
48
+ cd my-evals && tuieval # open the TUI (? explains any screen)
50
49
  ```
51
50
 
51
+ Whatever runs your models, the first steps are short:
52
+
53
+ | You have | Do this |
54
+ |---|---|
55
+ | **LM Studio, Ollama or vLLM** | Start its server and load a model, then `tuieval init my-evals`: it finds the model. Later: `tuieval add` lists new ones. |
56
+ | **GGUF files** | Install llama.cpp (`brew install llama.cpp`), then `tuieval add ~/models/Some-Model-Q4_K_M.gguf`. tuieval starts `llama-server` for it, sized to your machine. |
57
+ | **An OpenRouter key** | `export OPENROUTER_API_KEY=…`, then `tuieval run --tier smoke --only openrouter:<model id>`. |
58
+
59
+ Stuck? `tuieval doctor` checks servers, models, packs and keys, and says what to fix.
60
+
52
61
  `pip install tuieval` gets the latest release. Every change on `main` is also published as a dev build (`X.Y.0.devN`); get it with `pipx install --pip-args=--pre tuieval` or `pip install --pre tuieval`.
53
62
 
54
63
  Once you've added your own packs and models, the setup screen looks like this (packs on the left, models with a verdict code per use case on the right, the highlighted model's details below):
@@ -117,7 +126,8 @@ tuieval uses the current folder, or `--workspace DIR` / `TUIEVAL_HOME`. Keep it
117
126
  ```bash
118
127
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # llama.cpp (llama-server on PATH)
119
128
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision
120
- tuieval add qwen3:8b --server local # an already-running server (set its url)
129
+ tuieval add # pick from the models on running servers
130
+ tuieval add qwen3:8b # finds the running server that has it
121
131
  tuieval run --tier smoke --only openrouter:qwen/qwen3-32b # any OpenRouter model, no config needed
122
132
  ```
123
133
 
@@ -125,19 +135,21 @@ See **[docs/models.md](docs/models.md)** for servers, OpenRouter endpoint pinnin
125
135
 
126
136
  ## The TUI
127
137
 
128
- 1. **Pick packs and models** (space ticks; type to filter models by label or tag). Each model shows a short code per use case, e.g. `C✓ S?` (✓ pass, ✗ fail, ? inconclusive; grey = from earlier results).
138
+ 1. **Pick packs and models** (space ticks; type to filter models by label or tag). Each model shows a short code per use case, e.g. `C✓ S?` (✓ pass, ✗ fail, ? inconclusive; grey = from earlier results). To run only some tests of a pack, highlight it and press `e`. Runs of different tests add up, and the pack can PASS once all its tests have run.
129
139
  2. **Pick a tier** (Smoke to check setup, Screen, Certify) and **press `s`**. The line above the buttons shows how many answers that is and roughly how long it will take. For each model, tuieval starts its server (or uses a running one), checks the right model is loaded, runs every selected pack, then stops it.
130
140
  3. **Watch the run:** progress with ETA, live score, tok/s, TTFT and memory, the current test with reasoning and answer side by side, recent results with the grader's reason. `k` skips a model, `c` cancels (finished work is kept and resumes next time). Runs started while one is going are queued.
131
141
  4. **Press `r` for results:** production readiness, verdict history, scorecard, speed & tokens (★ marks models nothing beats on both accuracy and time), per question, is the difference real?, tests that separate models, by difficulty, failures, and test quality.
132
142
 
133
- Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model.
143
+ Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model. `?` explains any screen.
134
144
 
135
145
  ## Command line
136
146
 
137
147
  ```bash
138
148
  tuieval run # Screen every model on every pack
139
149
  tuieval run --tier certify --only a,b --packs support-bot,coding
150
+ tuieval run --packs coding --tests parse-dates,fix-bug # only these tests (or --tests coding:parse-dates)
140
151
  tuieval run --dry-run # the plan and server commands
152
+ tuieval doctor # check servers, models, packs and keys, and what to fix
141
153
  tuieval verdict # PASS / FAIL / INCONCLUSIVE per model and use case
142
154
  tuieval report # the same with evidence, as a markdown file
143
155
  tuieval history # every verdict change over time
@@ -17,12 +17,21 @@ Then you get an answer to the questions that matter:
17
17
 
18
18
  ```bash
19
19
  pipx install tuieval # or: uv tool install tuieval, or pip install tuieval (Python 3.11+)
20
- tuieval init my-evals && cd my-evals
21
- tuieval new-pack my-first-pack # a pack of example questions to edit
22
- tuieval add ~/models/Some-Model-Q4_K_M.gguf
23
- tuieval # open the TUI
20
+ tuieval init my-evals # guided: finds models already running (LM Studio, Ollama, vLLM…),
21
+ # adds a starter pack and runs a 3-question check
22
+ cd my-evals && tuieval # open the TUI (? explains any screen)
24
23
  ```
25
24
 
25
+ Whatever runs your models, the first steps are short:
26
+
27
+ | You have | Do this |
28
+ |---|---|
29
+ | **LM Studio, Ollama or vLLM** | Start its server and load a model, then `tuieval init my-evals`: it finds the model. Later: `tuieval add` lists new ones. |
30
+ | **GGUF files** | Install llama.cpp (`brew install llama.cpp`), then `tuieval add ~/models/Some-Model-Q4_K_M.gguf`. tuieval starts `llama-server` for it, sized to your machine. |
31
+ | **An OpenRouter key** | `export OPENROUTER_API_KEY=…`, then `tuieval run --tier smoke --only openrouter:<model id>`. |
32
+
33
+ Stuck? `tuieval doctor` checks servers, models, packs and keys, and says what to fix.
34
+
26
35
  `pip install tuieval` gets the latest release. Every change on `main` is also published as a dev build (`X.Y.0.devN`); get it with `pipx install --pip-args=--pre tuieval` or `pip install --pre tuieval`.
27
36
 
28
37
  Once you've added your own packs and models, the setup screen looks like this (packs on the left, models with a verdict code per use case on the right, the highlighted model's details below):
@@ -91,7 +100,8 @@ tuieval uses the current folder, or `--workspace DIR` / `TUIEVAL_HOME`. Keep it
91
100
  ```bash
92
101
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # llama.cpp (llama-server on PATH)
93
102
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision
94
- tuieval add qwen3:8b --server local # an already-running server (set its url)
103
+ tuieval add # pick from the models on running servers
104
+ tuieval add qwen3:8b # finds the running server that has it
95
105
  tuieval run --tier smoke --only openrouter:qwen/qwen3-32b # any OpenRouter model, no config needed
96
106
  ```
97
107
 
@@ -99,19 +109,21 @@ See **[docs/models.md](docs/models.md)** for servers, OpenRouter endpoint pinnin
99
109
 
100
110
  ## The TUI
101
111
 
102
- 1. **Pick packs and models** (space ticks; type to filter models by label or tag). Each model shows a short code per use case, e.g. `C✓ S?` (✓ pass, ✗ fail, ? inconclusive; grey = from earlier results).
112
+ 1. **Pick packs and models** (space ticks; type to filter models by label or tag). Each model shows a short code per use case, e.g. `C✓ S?` (✓ pass, ✗ fail, ? inconclusive; grey = from earlier results). To run only some tests of a pack, highlight it and press `e`. Runs of different tests add up, and the pack can PASS once all its tests have run.
103
113
  2. **Pick a tier** (Smoke to check setup, Screen, Certify) and **press `s`**. The line above the buttons shows how many answers that is and roughly how long it will take. For each model, tuieval starts its server (or uses a running one), checks the right model is loaded, runs every selected pack, then stops it.
104
114
  3. **Watch the run:** progress with ETA, live score, tok/s, TTFT and memory, the current test with reasoning and answer side by side, recent results with the grader's reason. `k` skips a model, `c` cancels (finished work is kept and resumes next time). Runs started while one is going are queued.
105
115
  4. **Press `r` for results:** production readiness, verdict history, scorecard, speed & tokens (★ marks models nothing beats on both accuracy and time), per question, is the difference real?, tests that separate models, by difficulty, failures, and test quality.
106
116
 
107
- Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model.
117
+ Other keys: `t` tunes the ticked models' speed flags, `a` adds a model, `m` scans for GGUFs, `p` saves or loads a preset, `x` hides a model. `?` explains any screen.
108
118
 
109
119
  ## Command line
110
120
 
111
121
  ```bash
112
122
  tuieval run # Screen every model on every pack
113
123
  tuieval run --tier certify --only a,b --packs support-bot,coding
124
+ tuieval run --packs coding --tests parse-dates,fix-bug # only these tests (or --tests coding:parse-dates)
114
125
  tuieval run --dry-run # the plan and server commands
126
+ tuieval doctor # check servers, models, packs and keys, and what to fix
115
127
  tuieval verdict # PASS / FAIL / INCONCLUSIVE per model and use case
116
128
  tuieval report # the same with evidence, as a markdown file
117
129
  tuieval history # every verdict change over time
@@ -1,6 +1,6 @@
1
1
  # Models, servers and machines
2
2
 
3
- Everything about models lives in your workspace's `models.toml`. `tuieval init` writes one with three servers ready to use:
3
+ Everything about models lives in your workspace's `models.toml`. `tuieval init` writes a short one; three servers are built in and ready to use (their full definitions are at the end of this page, under [Built-in servers and defaults](#built-in-servers-and-defaults)):
4
4
 
5
5
  | server | what it is |
6
6
  |---|---|
@@ -14,7 +14,8 @@ Everything about models lives in your workspace's `models.toml`. `tuieval init`
14
14
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --tags 9b,dense,q4 # GGUF -> llama
15
15
  tuieval add ~/models/Some-Model-Q4_K_M.gguf --no-think # same model, thinking off (label …-nothink)
16
16
  tuieval add ~/models/VL-Q4.gguf --mmproj ~/models/VL-mmproj.gguf # vision model
17
- tuieval add qwen3:8b --server local # a model your running server serves
17
+ tuieval add # pick from the models on servers already running
18
+ tuieval add qwen3:8b # finds the running server that has it
18
19
  tuieval scan --add # every new GGUF under model_dirs
19
20
  tuieval list # models; hidden ones listed separately
20
21
  tuieval remove --hidden # clean up every hidden model
@@ -70,7 +71,18 @@ The best server flags differ per model and per machine, so tuieval splits them b
70
71
 
71
72
  - **Machine id** is detected automatically (e.g. `m3max-64gb`, `m2-16gb`; set `EVALS_MACHINE` to rename it). Every result records the machine, the server version and the exact speed flags used. `tuieval machines` lists this machine and every other machine that has run the evals (they record themselves in `tuning/`).
72
73
  - **Fit check (Apple Silicon, llama):** before starting a GGUF, tuieval reads its header (layers, KV heads, hybrid attention layers) and picks the largest context that fits this machine's GPU memory, capped at `max_ctx`. A model that can't fit at 8k context is skipped with *doesn't fit on <machine>* instead of swapping. Packs that need more context than fits are skipped. It never quantizes the KV cache on its own, since that changes answers. Servers that size their own memory (their `cmd` doesn't take `{ctx}`) are left alone: their context is the model's `max_context`; `fit_check` on the server changes that.
73
- - **Per-machine settings** go under `[machines.<id>]`: `memory_headroom_gb` (GPU memory kept free for macOS, default 4) and `gpu_residency_gb` (see the stall guard).
74
+ - **Per-machine settings** go under `[machines.<id>]`: `memory_headroom_gb` (GPU memory kept free for macOS, default 4), `gpu_residency_gb` (see the stall guard) and `parallel_models` (see below).
75
+
76
+ ## Several models at a time
77
+
78
+ By default a run serves one model at a time. On a machine with room for more, set `parallel_models = 2` (or more) under `[machines.<id>]`, type a number in **Models at a time** on the TUI's setup screen (blank uses the machine's setting), or pass `tuieval run --parallel N`.
79
+
80
+ - Models start in queue order, each in its own lane. The next one waits while its fit-check memory estimate wouldn't fit next to the ones running (without one, e.g. a server that sizes its own memory, the setting decides). Contexts are never shrunk to make room.
81
+ - A second model on the same server gets a free port, so a server's `cmd` must take `{port}`.
82
+ - A server with `before_start` (which may stop other servers) always runs on its own. Tuning is always one model at a time.
83
+ - Answers are judged exactly as in a run of one model. Each answer records the models served while it ran (`ran_alongside`), and a load time measured next to others is marked (`loaded_alongside`), since models side by side share the machine's GPU and memory bandwidth. The speed table and the answer view show it.
84
+ - In the TUI, one model's answers stream at a time: `v` switches to the next, `k` skips the one streaming. `tuieval run` prints one line per answer instead of streaming.
85
+ - Only one run uses the machine at a time across tuieval windows; models at a time applies within a run.
74
86
 
75
87
  ## Tuning
76
88
 
@@ -142,3 +154,118 @@ On Apple Silicon Macs, once the system's GPU allocations pass about half of RAM,
142
154
  | `api_key_env` | environment variable holding the API key |
143
155
  | `thinking_param` | `reasoning` to send `[sampling] enable_thinking` as OpenRouter's `reasoning.enabled` |
144
156
  | `headers` | extra HTTP headers |
157
+
158
+ ## Built-in servers and defaults
159
+
160
+ A workspace's `models.toml` only needs what differs from these. `[defaults]` fill in key by key. `[sampling]` applies only when `models.toml` has no `[sampling]` section at all, because sampling is part of every result's fingerprint. A server you define replaces the built-in one with the same name; to change one setting, copy the whole block into `models.toml` and edit it. `tuieval add` adds a `[servers.<name>]` block with just a `url` when it finds a model on a server that's already running.
161
+
162
+ ```toml
163
+ [defaults] fill in key by key; [sampling] only when models.toml has no [sampling] at all (it's part of
164
+ # every result's fingerprint); a server here is replaced whole by one of the same name in models.toml.
165
+
166
+ [defaults]
167
+ repeat = 3
168
+ request_timeout_ms = 1800000 # per request; long thinking runs need it
169
+ ready_timeout_s = 1200 # how long a model may take to load before giving up
170
+ model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
171
+
172
+ # Sent with every request. A model may override it (see `thinking` / `sampling` below); results
173
+ # record the settings each model actually ran with, and the scorecard shows any difference.
174
+ [sampling]
175
+ temperature = 0.7
176
+ top_p = 0.95
177
+ top_k = 20
178
+ min_p = 0.0
179
+ max_tokens = 16384
180
+ enable_thinking = true # test every model in the SAME mode
181
+
182
+ # llama.cpp's server (https://github.com/ggml-org/llama.cpp), started for each GGUF model.
183
+ [servers.llama]
184
+ # Output-affecting flags only. Anything here must be identical wherever results are compared,
185
+ # so it's part of each result's fingerprint. Speed-only flags live in `perf` and `tune` below.
186
+ cmd = [
187
+ "llama-server",
188
+ "-m", "{model}",
189
+ "--alias", "{served_name}",
190
+ "--host", "127.0.0.1", "--port", "{port}",
191
+ "--jinja",
192
+ "--metrics",
193
+ "-np", "1",
194
+ "-ctk", "{kv_type}", "-ctv", "{kv_type}",
195
+ "-c", "{ctx}",
196
+ "--reasoning-format", "auto",
197
+ ]
198
+ port = 8080
199
+ model_is_path = true # check the GGUF exists and fits before starting
200
+ vision_args = ["--mmproj", "{mmproj}"] # appended only for models that set mmproj
201
+ kv_type = "f16" # a model's own `kv_type` (e.g. "q8_0") makes it a different model entry
202
+ max_ctx = 65536 # upper bound; the fit check lowers it per machine (see machines.py)
203
+ version_cmd = ["llama-server", "--version"] # recorded with every result
204
+ # Added to every request. cache_prompt = false: each answer is computed from scratch, so a repeat of
205
+ # the same question reuses nothing from the last time it was asked (and its TTFT is honest).
206
+ # Answers record any prompt tokens the server reused.
207
+ request = { cache_prompt = false }
208
+
209
+ # Speed-only flags, always applied (not tuned).
210
+ perf = ["-ngl", "99"]
211
+
212
+ # Speed-only knobs `tuieval tune` tries, one at a time from the best so far. The first option of
213
+ # each knob is the default for models not tuned on this machine yet. Placeholders: {p} P-cores,
214
+ # {p_minus_2}, {all} all cores. Options that stop the server from starting are skipped.
215
+ # Check the flags against `llama-server --help` for your build.
216
+ [servers.llama.tune]
217
+ threads = [["-t", "{p}", "-tb", "{p}"], ["-t", "{p_minus_2}", "-tb", "{p}"], ["-t", "{all}", "-tb", "{all}"]]
218
+ ubatch = [["-ub", "512"], ["-ub", "256"], ["-ub", "1024"]]
219
+ batch = [["-b", "2048"], ["-b", "4096"]]
220
+ flash_attn = [["-fa", "on"], ["-fa", "off"]]
221
+
222
+ # A server that's already running (LM Studio, Ollama, vLLM, a remote box): give its URL and no cmd.
223
+ # Its models are added with `tuieval add <model id> --server local`.
224
+ [servers.local]
225
+ url = "http://127.0.0.1:1234" # LM Studio's default; Ollama is http://127.0.0.1:11434, vLLM :8000
226
+
227
+ # OpenRouter: ANY model it serves, with no [[models]] entry. Name it when you run:
228
+ # tuieval run --tier smoke --only openrouter:qwen/qwen3-235b-a22b-2507
229
+ # TUI: tick "+ openrouter: any model" in the model list, or press "a" and type openrouter:<model id>
230
+ # Results go to results/or-<id>/ and the model stays listed once it has results. A plain
231
+ # `tuieval run` never includes OpenRouter models (they cost money); name them.
232
+ # Needs OPENROUTER_API_KEY in the environment of the shell that starts tuieval.
233
+ # pin_endpoint: every answer of a model comes from one provider endpoint (the closest to the released
234
+ # weights), with no fallback, since providers run different quantizations.
235
+ # To keep providers that store or train on prompts (and could learn your tests) out entirely, add
236
+ # request = { provider = { data_collection = "deny" } }
237
+ [servers.openrouter]
238
+ url = "https://openrouter.ai/api/v1"
239
+ any_model = true
240
+ label_prefix = "or"
241
+ pin_endpoint = true
242
+ api_key_env = "OPENROUTER_API_KEY"
243
+ thinking_param = "reasoning" # [sampling] enable_thinking -> OpenRouter's reasoning.enabled
244
+ headers = { "X-Title" = "tuieval" }
245
+ ```
246
+
247
+ `[defaults] detect_ports` (default `[1234, 11434, 8080, 8000]`, the usual LM Studio, Ollama, llama-server and vLLM ports) is where `tuieval add`, `tuieval init` and `tuieval doctor` look for servers that are already running. `TUIEVAL_DETECT_PORTS` (comma-separated; empty means none) overrides it.
248
+
249
+ Optional sections:
250
+
251
+ ```toml
252
+ # Optional tuner settings.
253
+ # [tune]
254
+ # guard_similarity = 0.6 # greedy answers under a candidate flag must stay this similar to the defaults'
255
+ # request_timeout_s = 600 # a tuning request that takes longer fails that candidate
256
+ # warm_start = true # start from a tuned model with the same architecture and shapes (a fine-tune)
257
+ # warm_retest = ["spec", "ubatch"] # the knobs a warm start re-tries; the rest are inherited
258
+ # answer_tokens = 1024 # answer length options are scored for (projected objective)
259
+ # swap_limit_mb = 500 # reject options that swap more than this beyond the defaults (default: report only)
260
+
261
+ # Optional: llama-bench for the tuner's fast first stage (found on PATH as llama-bench otherwise).
262
+ # [bench]
263
+ # cmd = ["~/llama.cpp/build/bin/llama-bench"]
264
+
265
+ # Optional per-machine settings, by machine id (tuieval machines shows the ids).
266
+ # [machines.m3max-64gb]
267
+ # memory_headroom_gb = 3 # GPU memory kept free for macOS and other apps (default 4)
268
+ # gpu_residency_gb = 12 # GPU memory the driver keeps resident before churning (default: half of RAM)
269
+ # parallel_models = 2 # models a run serves at a time (default 1)
270
+ ```
271
+
@@ -10,6 +10,11 @@ tuieval new-pack support-bot --grader reply # answer | rag | reply | tool_c
10
10
 
11
11
  Edit `packs/support-bot/tests.yaml`, replace the examples with your own questions, and run `tuieval selftest support-bot` after every change.
12
12
 
13
+ Two shortcuts to a first set of real questions:
14
+
15
+ - **From a spreadsheet:** `tuieval new-pack quiz --from questions.csv` makes one test per row from a `question` and an `answer` column (optional: `id`, `category`, `difficulty`, `tolerance`, and `wrong` with known-wrong answers separated by `|`). Numbers are checked with the tolerance, words case-insensitively, and `NOT_AVAILABLE` means the right answer is "the question doesn't say".
16
+ - **Drafted by a model:** `tuieval new-pack support-bot --grader reply --about "refund questions for a shoe store"` also writes `DRAFT-PROMPT.md`. Paste it into the strongest model you have, save the YAML it writes as `tests.yaml`, and run `tuieval selftest`: it catches drafted tests whose expected answer is wrong or whose grader can't tell a mistake from the right answer. Read the questions anyway; you know your domain better than the model.
17
+
13
18
  To try a different set of questions, copy a pack (`cp -r packs/support-bot packs/support-bot-v2`), edit the copy, and pick whichever you want in the TUI.
14
19
 
15
20
  ## Fingerprints
@@ -55,6 +60,7 @@ min_consistency = 0.95 # share of variant groups where every variant p
55
60
  input: | # the user message
56
61
  A customer bought shoes 20 days ago …
57
62
  image: images/receipt.png # optional: sent as an image (needs vision)
63
+ input_file: prompts/refund.txt # optional, instead of input: the user message from a file in the pack
58
64
  grader: answer # optional: override the pack's grader
59
65
  group: refund-window-2 # optional: variants of one case share a group (consistency gate)
60
66
  critical: true # optional: any failure of this test is critical (disqualifying)
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.0.dev11'
22
- __version_tuple__ = version_tuple = (0, 2, 0, 'dev11')
21
+ __version__ = version = '0.2.0.dev28'
22
+ __version_tuple__ = version_tuple = (0, 2, 0, 'dev28')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,83 @@
1
+ # Built-in defaults and servers, used when a workspace's models.toml doesn't define them.
2
+ # [defaults] fill in key by key; [sampling] only when models.toml has no [sampling] at all (it's part of
3
+ # every result's fingerprint); a server here is replaced whole by one of the same name in models.toml.
4
+
5
+ [defaults]
6
+ repeat = 3
7
+ request_timeout_ms = 1800000 # per request; long thinking runs need it
8
+ ready_timeout_s = 1200 # how long a model may take to load before giving up
9
+ model_dirs = ["~/models"] # where "Scan for models" looks for new GGUFs
10
+
11
+ # Sent with every request. A model may override it (see `thinking` / `sampling` below); results
12
+ # record the settings each model actually ran with, and the scorecard shows any difference.
13
+ [sampling]
14
+ temperature = 0.7
15
+ top_p = 0.95
16
+ top_k = 20
17
+ min_p = 0.0
18
+ max_tokens = 16384
19
+ enable_thinking = true # test every model in the SAME mode
20
+
21
+ # llama.cpp's server (https://github.com/ggml-org/llama.cpp), started for each GGUF model.
22
+ [servers.llama]
23
+ # Output-affecting flags only. Anything here must be identical wherever results are compared,
24
+ # so it's part of each result's fingerprint. Speed-only flags live in `perf` and `tune` below.
25
+ cmd = [
26
+ "llama-server",
27
+ "-m", "{model}",
28
+ "--alias", "{served_name}",
29
+ "--host", "127.0.0.1", "--port", "{port}",
30
+ "--jinja",
31
+ "--metrics",
32
+ "-np", "1",
33
+ "-ctk", "{kv_type}", "-ctv", "{kv_type}",
34
+ "-c", "{ctx}",
35
+ "--reasoning-format", "auto",
36
+ ]
37
+ port = 8080
38
+ model_is_path = true # check the GGUF exists and fits before starting
39
+ vision_args = ["--mmproj", "{mmproj}"] # appended only for models that set mmproj
40
+ kv_type = "f16" # a model's own `kv_type` (e.g. "q8_0") makes it a different model entry
41
+ max_ctx = 65536 # upper bound; the fit check lowers it per machine (see machines.py)
42
+ version_cmd = ["llama-server", "--version"] # recorded with every result
43
+ # Added to every request. cache_prompt = false: each answer is computed from scratch, so a repeat of
44
+ # the same question reuses nothing from the last time it was asked (and its TTFT is honest).
45
+ # Answers record any prompt tokens the server reused.
46
+ request = { cache_prompt = false }
47
+
48
+ # Speed-only flags, always applied (not tuned).
49
+ perf = ["-ngl", "99"]
50
+
51
+ # Speed-only knobs `tuieval tune` tries, one at a time from the best so far. The first option of
52
+ # each knob is the default for models not tuned on this machine yet. Placeholders: {p} P-cores,
53
+ # {p_minus_2}, {all} all cores. Options that stop the server from starting are skipped.
54
+ # Check the flags against `llama-server --help` for your build.
55
+ [servers.llama.tune]
56
+ threads = [["-t", "{p}", "-tb", "{p}"], ["-t", "{p_minus_2}", "-tb", "{p}"], ["-t", "{all}", "-tb", "{all}"]]
57
+ ubatch = [["-ub", "512"], ["-ub", "256"], ["-ub", "1024"]]
58
+ batch = [["-b", "2048"], ["-b", "4096"]]
59
+ flash_attn = [["-fa", "on"], ["-fa", "off"]]
60
+
61
+ # A server that's already running (LM Studio, Ollama, vLLM, a remote box): give its URL and no cmd.
62
+ # Its models are added with `tuieval add <model id> --server local`.
63
+ [servers.local]
64
+ url = "http://127.0.0.1:1234" # LM Studio's default; Ollama is http://127.0.0.1:11434, vLLM :8000
65
+
66
+ # OpenRouter: ANY model it serves, with no [[models]] entry. Name it when you run:
67
+ # tuieval run --tier smoke --only openrouter:qwen/qwen3-235b-a22b-2507
68
+ # TUI: tick "+ openrouter: any model" in the model list, or press "a" and type openrouter:<model id>
69
+ # Results go to results/or-<id>/ and the model stays listed once it has results. A plain
70
+ # `tuieval run` never includes OpenRouter models (they cost money); name them.
71
+ # Needs OPENROUTER_API_KEY in the environment of the shell that starts tuieval.
72
+ # pin_endpoint: every answer of a model comes from one provider endpoint (the closest to the released
73
+ # weights), with no fallback, since providers run different quantizations.
74
+ # To keep providers that store or train on prompts (and could learn your tests) out entirely, add
75
+ # request = { provider = { data_collection = "deny" } }
76
+ [servers.openrouter]
77
+ url = "https://openrouter.ai/api/v1"
78
+ any_model = true
79
+ label_prefix = "or"
80
+ pin_endpoint = true
81
+ api_key_env = "OPENROUTER_API_KEY"
82
+ thinking_param = "reasoning" # [sampling] enable_thinking -> OpenRouter's reasoning.enabled
83
+ headers = { "X-Title" = "tuieval" }
@@ -2,28 +2,33 @@
2
2
 
3
3
  Usage: tuieval [--workspace DIR] [command] [options]
4
4
 
5
- tuieval open the TUI
6
- tuieval init [DIR] create a workspace (models.toml, packs/, graders/)
7
- tuieval new-pack NAME a new pack from a starter template (--grader answer|rag|reply|tool_call|code)
8
- tuieval add PATH|ID register a model in models.toml (a GGUF, or a model id)
9
- tuieval scan list GGUFs in model_dirs that aren't in models.toml yet
10
- tuieval list the models tuieval knows, hidden ones separately (--packs: the packs)
11
- tuieval remove MODEL remove models, or every hidden one (--hidden); archived in removed/
5
+ Start here
6
+ tuieval init [DIR] create a workspace; in a terminal, a guided setup to a first result
7
+ tuieval open the TUI (? explains any screen)
8
+ tuieval doctor check servers, models, packs and API keys, and say what to fix
9
+ tuieval add [PATH|ID] add a model: a GGUF, a model id, or none to pick from running servers
10
+ tuieval new-pack NAME a new pack of example questions (--from CSV, --about TOPIC, --grader …)
11
+ tuieval selftest check every test's reference and wrong answers against its grader
12
12
  tuieval run [...] run evals from the command line (tuieval run --help)
13
+ tuieval verdict which model is ready for what, and what to run next
14
+
15
+ Day to day
13
16
  tuieval compare [...] scorecards (--speed, --pairwise, --failures)
14
- tuieval regrade re-score stored answers after changing a grader
15
- tuieval verdict production readiness (PASS/FAIL per model and use case)
16
- tuieval report the readiness verdicts with their evidence, as a markdown file
17
+ tuieval report the verdicts with their evidence, as a markdown file
17
18
  tuieval history every verdict change, newest first
18
- tuieval selftest check every test's reference and wrong answers against its grader
19
19
  tuieval items tests that don't separate models or look broken
20
20
  tuieval capture LOG --pack turn a real failure (logs/live/*.txt) into a new test
21
+ tuieval regrade re-score stored answers after changing a grader
22
+ tuieval list the models tuieval knows, hidden ones separately (--packs: the packs)
23
+ tuieval remove MODEL remove models, or every hidden one (--hidden); archived in removed/
24
+
25
+ Advanced
26
+ tuieval scan list GGUFs in model_dirs that aren't in models.toml yet
21
27
  tuieval machines this machine, other recorded machines, and fit and tuning per model
22
28
  tuieval tune MODEL find the fastest speed-only server flags for a model on this machine
23
29
  tuieval export pi MODEL serve a model in the pi coding agent with its tuned flags
24
30
  tuieval watch a proxy that shows the reasoning of any app using your server
25
- tuieval help this help
26
- tuieval --version
31
+ tuieval help · --version
27
32
 
28
33
  The workspace is --workspace DIR, else $TUIEVAL_HOME, else the current folder.
29
34
  """
@@ -67,6 +72,9 @@ def main(argv=None):
67
72
  if not workspace.is_workspace():
68
73
  sys.exit(f"{workspace.root()} isn't a tuieval workspace; run `tuieval init` first")
69
74
  return scaffold.cmd_new_pack(rest)
75
+ if cmd == "doctor":
76
+ from . import doctor
77
+ return doctor.cmd_doctor(rest)
70
78
  if cmd == "run":
71
79
  from . import run_evals
72
80
  return run_evals.main(rest)
@@ -37,7 +37,8 @@ def _load_native(data):
37
37
  "settings": data.get("settings", {}), "load_s": run.get("load_s"),
38
38
  "peak_rss_mb": run.get("peak_rss_mb"), "pack": pack["name"], "pack_fp": pack["fingerprint"],
39
39
  # hosted (OpenRouter) models: the provider endpoint and its declared quantization
40
- "endpoint": run.get("endpoint"), "quantization": run.get("quantization")}
40
+ "endpoint": run.get("endpoint"), "quantization": run.get("quantization"),
41
+ "loaded_alongside": run.get("loaded_alongside") or []}
41
42
  rows = []
42
43
  for r in data["results"]:
43
44
  rows.append({
@@ -54,6 +55,8 @@ def _load_native(data):
54
55
  "severity": r.get("severity"), "group": r.get("group"), "repeat": r.get("repeat", 0),
55
56
  "difficulty": r.get("difficulty", "unrated"),
56
57
  "machine": r.get("machine") or run.get("machine"), "prompt_tokens": r.get("prompt_tokens"),
58
+ # other models served at the same time (parallel_models): its speed was shared with them
59
+ "alongside": r.get("ran_alongside") or [],
57
60
  })
58
61
  return rows, info
59
62
 
@@ -210,6 +213,13 @@ def timing(r, machine=None, speeds=None):
210
213
  return None, None, None, None, None
211
214
 
212
215
 
216
+ def alongside_note(rows):
217
+ """' · n/N ran alongside others' when some answers ran while other models were served (their
218
+ speed was shared with them), else ''."""
219
+ shared = sum(bool(r.get("alongside")) for r in rows)
220
+ return f" · {shared}/{len(rows)} ran alongside others" if shared else ""
221
+
222
+
213
223
  def speed(rows, infos, machine=None, speeds=None):
214
224
  """(header, table, frontier): speed and token use per model.
215
225
 
@@ -227,7 +237,9 @@ def speed(rows, infos, machine=None, speeds=None):
227
237
  load = {}
228
238
  for i in infos:
229
239
  cur = load.setdefault(i["label"], {"load_s": None, "peak_rss_mb": None, "tags": i["tags"],
230
- "think": i["settings"].get("enable_thinking"), "served": set()})
240
+ "think": i["settings"].get("enable_thinking"), "served": set(),
241
+ "loaded_alongside": False})
242
+ cur["loaded_alongside"] |= bool(i.get("loaded_alongside"))
231
243
  if i.get("endpoint"):
232
244
  q = i.get("quantization")
233
245
  cur["served"].add(f"{'undeclared' if q in (None, 'unknown') else q} via {i['endpoint']}")
@@ -247,6 +259,7 @@ def speed(rows, infos, machine=None, speeds=None):
247
259
  here = machine is None or machine in where # load time and memory only where it ran
248
260
  if machine is None:
249
261
  kind = "measured on " + (", ".join(where) or "?")
262
+ kind += alongside_note(v)
250
263
  table.append([
251
264
  "★" if m in frontier else "", m, "; ".join(sorted(info.get("served") or [])) or "-",
252
265
  ",".join(info.get("tags") or []),
@@ -256,7 +269,8 @@ def speed(rows, infos, machine=None, speeds=None):
256
269
  _fmt(median(x[3] for x in t), ".0f"), _fmt(median(r["tokens"] for r in v), ".0f"),
257
270
  _fmt(median(r["reasoning_tokens"] for r in v), ".0f"), _fmt(median(r["answer_tokens"] for r in v), ".0f"),
258
271
  f"{sum(r['tokens'] for r in v) / passes:,.0f}" if passes else "-",
259
- _fmt(info.get("load_s") if here else None, ".0f"),
272
+ _fmt(info.get("load_s") if here else None, ".0f")
273
+ + (" (alongside others)" if here and info.get("load_s") and info.get("loaded_alongside") else ""),
260
274
  _fmt(info["peak_rss_mb"] / 1024 if info.get("peak_rss_mb") and here else None, ".1f"),
261
275
  ])
262
276
  return header, table, frontier