Save inference parity implementation and evaluation harness

This commit is contained in:
Georg Bauer
2026-09-10 22:45:59 +02:00
parent b99ce2aa10
commit 02db0968ae
198 changed files with 111205 additions and 586 deletions
+84
View File
@@ -94,6 +94,90 @@ pie-natural`, `--case composed-basics`, `--attempts 3`, `--model MODEL_ID`, or
`--endpoint URL` to narrow or repeat runs. This slow, model-dependent suite is
intentionally separate from `cargo test`.
### Headless model evaluation
Run a bounded, instrumented generation through the production runtime without
opening the UI:
```sh
cargo run --release -- model-eval \
--model qwen3.8-flash-next \
--reasoning low \
--acceleration off \
--power-percent 100
```
The evaluator first performs a short unmeasured warmup through the same system-prompt
builder and production runtime as the UI, then resets its GPU counters and starts
the measured request. `warmup_result.elapsed_ms` includes initial model loading;
its `timing` object separates `model_load_ms` and `non_loading_elapsed_ms` (the
latter still includes setup, inference, and checkpoint work). Do not add the
reported load time to the warmup total. A `model_loaded` event reports the same
load duration as the shared runtime metrics. Canary samples retain their start
`phase` and record `completion_phase`, actual completion `elapsed_ms`, and host
observation `observed_elapsed_ms`; per-phase summaries group by probe start.
`result.timing.decode_ms` measures the shared engine decode loop, including
sampling and streamed output callbacks but excluding subsequent pending-token
finalization and checkpoint work. Stats shows the same measurement as "Decode
loop (without checkpoint)". The older host-observed interval through request
completion is retained as `decode_to_request_end_ms`; it is not pure decode time.
`decode_loop_tokens_per_second` uses completion tokens divided by the loop duration;
`decode_tokens_per_second` remains the UI's last progress rate. `checkpoint_io`
reports request operation counts and the last read/write duration, not summed I/O
time. A missing decode duration is `null`, not zero.
The default test
prompt identifies a prose-generation test and asks the model to choose
its own genre, setting, and characters, then output only a complete short story
without questions, choices, limitations, or coding discussion. JSON Lines report
runtime phases, prefill and decode speed, Metal busy time,
command-buffer duration, CPU, physical memory, disk I/O, and the complete reasoning
and answer. `--input-file PATH` appends a UTF-8 file to the supplied prompt for
realistic long-prefill tests. Repeat `--prompt` to run multiple turns in one
ongoing chat: every later turn includes all preceding user prompts and complete
assistant responses and continues from the same session checkpoint. An independent
Metal canary runs inside the same
process as the production runtime through
loading, prefill, decode, and finishing, reporting per-phase scheduling and
completion latency plus stalls that could make a UI unresponsive. Use
`--acceleration on|off` to compare DSpark on DeepSeek or integrated MTP on
GLM/Qwen. The canary itself submits GPU work, so use `--canary off` for clean
throughput measurements and `--canary on` for responsiveness diagnostics. The UI
canary is off by default and can be enabled in Stats. A supervisor terminates
the worker only when startup or token progress stalls, or when the memory limit
is exceeded; there is no whole-run timeout. See `model-eval --help` for all
overrides. The evaluator only opens already installed model artifacts and never
downloads missing models.
Evaluation defaults to `--plain-chat on`: the shared UI prompt builder omits
agent/tool schemas, skill instructions and the built-in coding-agent system
prompt; an explicitly customized system prompt is preserved. The corresponding
UI configuration is `plain_chat: true` in its YAML configuration. In that mode
the UI also skips workspace/date injections, extension hooks, A2UI handling,
tool execution, automatic response retries and automatic first-turn titles.
Use a **new chat** in both frontends: existing history is never silently removed.
The normal UI default remains agent mode (`plain_chat: false`). `--plain-chat off`
restores agent prompt construction for diagnostics, but does not make the harness
an agent/tool executor. The effective mode and complete system prompt are reported
in the start receipt; do not compare runs with different modes.
For a summary, story, and Python one-shot in a single chat, pass three prompts:
```sh
target/release/ds4-server model-eval \
--model qwen3.8-flash-next --reasoning low \
--power-percent 100 --acceleration off --canary on \
--input-file README.md \
--prompt 'Give a concise summary of the following project README. Return the summary directly without asking questions.' \
--prompt 'This is a prose-generation test. Respond only with a complete short fictional story in ordinary prose. Choose the genre, setting, and characters yourself and begin the story immediately. Do not ask questions, offer choices, explain limitations, or discuss coding.' \
--prompt 'This is an offline code-generation benchmark. Do not invoke tools or emit tool-call markup. Return exactly one Python code block defining has_close_elements(numbers: list[float], threshold: float) -> bool. It must return True if any two distinct numbers are closer than threshold. Include no explanation outside the code block.'
```
The input file is appended only to the first prompt. Each turn starts after the
previous response finishes; `measurement_start` records the turn number and the
number of history messages (1, 3, 5), and each `result` reports its own timings,
cached tokens, reasoning, and answer. The warmup uses a separate session and is
not included in this history.
## Build and run
DS4Server targets Apple silicon with macOS 15 or newer and requires Rust 1.97 plus