Compare commits

91 Commits

Author SHA1 Message Date
c245774d76 Ignore generated run configs 2026-06-24 11:48:21 +08:00
d85572e7b5 Update AITuner roadmap framing 2026-06-24 11:45:42 +08:00
c0a9235b80 Document vLLM-first harness roadmap 2026-06-24 11:23:39 +08:00
c4173b2b3b Document remote proxy setup 2026-06-23 20:12:53 +08:00
6d874ecbff Update Qwen235B progress snapshot 2026-06-23 18:24:57 +08:00
403ae2e2b7 Document Qwen235B 2x2 progress 2026-06-23 18:23:56 +08:00
861d754f29 Localize Qwen27B harness ablation doc 2026-06-23 18:14:35 +08:00
76ec19224c Document Qwen27B 2x2 harness ablation 2026-06-23 10:08:46 +08:00
e67bc86240 Probe coupled prefill runtime knobs before stop 2026-06-22 19:30:23 +08:00
fd94ab9f3b Prevent prefill convergence stop before seq probe 2026-06-22 14:43:55 +08:00
4607711bb5 Add reusable clean pair runner 2026-06-22 00:05:31 +08:00
d23b69219b Add clean dash1 harness ablation runner 2026-06-21 00:51:08 +08:00
488fae7e63 Add tuning progress report for harness evaluation 2026-06-21 00:48:21 +08:00
426151bc9f Harness stop uses full state baseline 2026-06-20 22:48:27 +08:00
a9d237bbfd Show effective flags in ablation trajectory 2026-06-20 10:24:53 +08:00
5257fbc1a2 Improve harness incumbent follow-up search 2026-06-20 05:37:15 +08:00
b3156a382a Harness: gate gpu-mem-util/seqs-raise on 'no untested TP increase' (frontier-closed)
The first gpt-5.5 verification run exposed a bug in the prior gate: topology_settled =
cur_tp>base_tp let gpu-memory-utilization fire on a TP2 incumbent (TP2>baseline TP1)
and preempt the still-open TP4 frontier -- the harness proposed TP2+gpu-mem-util=0.92
at iter 2 instead of climbing to TP4. The candidate path runs before the topology-
frontier check, so a score>=0.35 runtime candidate wins.

Fix: gate runtime micro-tuning (gpu-mem-util, raising max-num-seqs) on the TP frontier
being closed -- topology_settled = no untested _next_allowed_tp remains (respects GPU
count, so TP4 is the real ceiling on 6 GPUs). New regression test: TP2 incumbent with
TP4 reachable must climb TP and must NOT propose gpu-mem-util. 116 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 13:33:29 +08:00
76cca89a43 Add harness-only dash1 driver to verify the gpu-mem-util fix recovers ~0.87 + stops
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:29:32 +08:00
83162e7a64 Ablation: pin gpt-5.5 @ ai.gahow.org (chat.completions); re-read token per arm
- Pin endpoint.model=gpt-5.5, base_url=https://ai.gahow.org/v1, wire_api=chat.completions
  in both ablation specs so both arms uniformly use the current ~/.codex model (the
  prior runs used the stale ai.prism.uno/gpt-5.4 that config.toml has since moved off).
- run_ablation_pair_d1.sh re-reads the codex token from auth.json right before each arm
  instead of capturing it once at launch (the stale-at-use capture 401'd naive 2/3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:27:47 +08:00
a3523f5601 Harness: explore gpu-memory-utilization (and raise max-num-seqs) before Stop-B
The harness defined a gpu-memory-utilization family but hard-coded active_now=False
and never generated a candidate for it, and only ever *lowered* max-num-seqs for
decode_tpot. So on the decode-bound 27B incumbent it stopped at TP4=0.648 while the
naive (use_harness=false) baseline freely found gpu-memory-utilization=0.94 -> 0.873
(+35%) and max-num-seqs=48. That made the harness look worse than naive -- a real
coverage gap, not bad luck.

Fix in _runtime_candidate_actions (topology-before-runtime gated: only once topology
has moved off the baseline, so a baseline latency bottleneck still gets a TP change):
- Add a gpu-memory-utilization hill-climb candidate (+0.02/step toward a 0.97 safe
  ceiling) for decode_tpot/admission incumbents, scored high enough (>=0.35) to block
  a premature Stop-B until it is tried; the incumbent guard keeps the step only if
  per-GPU rate improves and the engine launches, and the tested signature terminates
  the climb (so 0.96 OOM/regression backs off to 0.94 automatically).
- Let max-num-seqs *rise* for decode_tpot (not only fall) to exploit decode parallelism.
- Activate the gpu-memory-utilization harness family for decode_tpot/admission.

Verified: new unit test asserts a settled TP4 decode-bound incumbent gets a
gpu-memory-utilization raise (0.9->0.92) and no stop while untried. 115 tests pass.
Empirical reliability (harness recovers ~0.87 and stops) to be confirmed by re-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:25:47 +08:00
95c02d7dd9 Fig-18: chained driver for 2 extra naive runs (n=3 nondeterminism)
A single naive run can luck into the TP4 optimum at iter 1 (gpt-5.4 free-form
guess), which weakens the single-curve story. Run naive 2 more times on the same
real-output substrate to capture the fail/slow/lucky spread -- the actual finding.
Waits for ABLATION12_DONE so it never contends for GPUs with the main pair.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:06:05 +08:00
a1b804f879 Ablation: search.high 0.25 -> 0.15 (skip wildly-infeasible top probes)
Smoke on the real-output substrate measured feasible sampling_u = 0.0156 (TP2)
and 0.0742 (TP4, per-GPU 0.618 = 2.24x TP2). search.high=0.25 made the binary
search waste its two top probes (u=0.125/0.0625, always infeasible, admitting the
most long-output requests) on every trial. 0.15 keeps ~2x headroom over the TP4
boundary (0.0742) and trims ~15-20% of per-trial cost with identical feasibility
results; if a runtime-tuned config ever saturates 0.15 the harness search-high
saturation stop fires (informative, not silent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:11:52 +08:00
0c23285f39 Fig18 substrate: real output_length + criterion-A time_scale + Stop-A drain deadline
Replace the out=128 / scale=0.5 ablation substrate with a paper-faithful one:
- Use the trace's real output_length (drop completion_tokens_override=128). The
  0-8k chat window has p50=531 / p99=2436 / max=35168 output tokens, so decode
  (TPOT) becomes the dominant bottleneck instead of an artificial 128-token cap.
- replay_time_scale=0.8775, chosen by criterion-A: binary-search the smallest
  scale whose A-family L-C-A similarity to the real (scale=1.0) arrivals stays
  >= tau (0.90). The old scale=0.5 had sim_A=0.56, distorting the arrival axis
  far below the tau bar used everywhere else. New calibrator:
  scripts/calibrate_time_scale.py.
- Per-probe Stop-A-consistent drain deadline (worker._probe_drain_deadline): the
  wall-clock a *feasible* config needs to drain the LCA-admitted set
  (last_arrival + worst-case TTFT + p99_out * TPOT budget + margin). With real
  outputs decode dominates wall-clock, so the old fixed 320s cap would truncate
  the Stop-A offered window mid-decode. early_stop_max_elapsed_s (1000s) is now a
  hard ceiling; the per-probe deadline governs. The lag cap still cuts overload.

12-iter paired driver (both arms on dash1, removes the dash0/dash1 host confound):
scripts/run_ablation_pair_d1.sh. 115 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:24:00 +08:00
816765071f Complete harness-vs-naive ablation: harness 3x faster + stops; naive nondeterministic
Full naive run (dash1) reached the same TP4=0.34 optimum as the harness but took 6
iters (vs 2), never stopped (full budget), and spent trials 2-5 on worse TP2+runtime
detours. The other naive run (dash0) wandered runtime-only on TP1, found nothing, and
crashed the engine. Refined conclusion (matches paper §7.3): a strong model can
sometimes find the right knob unaided, so the harness's value is reliability + speed +
stop discipline, not that naive always fails. Harness: 2 iters-to-best, stopped at 4,
no regression. Naive: 3x slower at best, no stop, failed at worst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 13:03:26 +08:00
97d2ddabb1 Ablation driver: force direct LLM connection (codex proxy is dash0-local)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:05:44 +08:00
8e58b4033d Note dash1 lacks LLM gateway access (naive-completion deferred to dash0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:55:39 +08:00
b779f6e56a Add dash1 naive-completion driver for the ablation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:52:54 +08:00
e7d1b3ba01 Harness-vs-naive ablation result: harness steers to TP & converges; naive wanders
Controlled use_harness on/off on dense 27B (same workload/SLO/substrate, only the flag
differs). Harness ON: TP2 -> TP4 (0.34 req/s/GPU) in 2 iters, rejected two worse
refinements, premature LLM stop vetoed then honored -> converged, no regression.
Naive OFF: kept TP=1 and cranked runtime knobs (mbt 16k->65k, seqs, caching), all 5
trials infeasible (same TPOT/TTFT compute bottleneck), one engine OOM crash, no feasible
config found. The bottleneck is compute; the harness steered to the knob family that
adds compute (TP) while naive wandered in knobs that cannot. Reproduces the paper's
Fig-18 finding. Substrate is compressed (process comparison, not peak-rate); naive run
was infra-interrupted at trial-5 (already conclusive). Read from cpfs via dash1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:51:56 +08:00
579dd86698 Ablation: --skip-baseline so loops climb from first proposal
The low-capacity TP1 auto-baseline is infeasible under tight TTFT/TPOT + time
compression, which tripped baseline_all_infeasible and terminated the loop before any
climb. Skip the auto-baseline so both runs start from the first LLM/harness proposal
(harness steers to TP from the long-prompt profile) — the ablation is about the
proposal path, so an explicit TP1 row is not required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:59:46 +08:00
37342a5749 Add chained harness-vs-naive ablation driver (sequential runs + DONE marker)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:30:41 +08:00
5965f4fbbc Ablation substrate: scale=0.5 + out=128 + 6 probes (TP1 measurable, tractable)
scale=0.2 made TP1 uniformly infeasible (no baseline); bound decode to 128 tokens and
use mild 2x compression so TP1 registers a real, fast baseline, with 6 probes to span
TP1's low and TP4's high feasibility boundaries. Both configs identical except use_harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:29:30 +08:00
a1cbab0e69 Document harness-vs-naive ablation: setup, substrate calibration, blocker
Sets up the controlled use_harness ON-vs-OFF ablation on dense 27B:
- both configs committed and validated on dash0 (differ only in
  use_harness + study_id), LLM auth + clean engine launch confirmed;
- characterizes exactly what the harness toggles (Harnesses: prompt
  section with ranked bottleneck hypotheses + knob-family steering,
  deterministic guided/stop proposals, Stop-B validator/veto) vs naive;
- substrate calibration from a real harness-ON run: at scale=0.2 the
  180s elapsed cap fires correctly but TP1 is uniformly infeasible even
  at u=0.125 (pass=0, elapsed-capped) -> recommend scale 0.4-0.5 for a
  real baseline; comparability caveat documented.

Honest status: full two-run sweep NOT completed in-session (~5-6
GPU-hours, sequential); GPUs left clean (all 0 MiB, no orphans; SIGTERM
teardown re-validated). Includes a precise continuation recipe and the
scripts/ablation_trajectory.py helper (validated against a prior store).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:16:27 +08:00
0794efa249 Reduce ablation probe budget to 3 per trial for tractability
First TP1 baseline probe under scale=0.2 ran ~6min (severe overload, 260
preemptions on the lighter half of the trace; TP1 is decode-bound and the
arrival-lag early-stop does not cut a decode-drain-bound probe). Cut
search.max_probes 5->3 to bound binary-search steps per trial. Caps stay
at elapsed=180/lag=30. Both configs still differ only in use_harness +
study_id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:01:19 +08:00
d975e57bb5 Scale ablation early-stop caps to the compressed window (scale=0.2)
At replay_time_scale=0.2 the 600s arrival window compresses to 120s, so
the inherited 900s wall-clock elapsed cap let overloaded TP1 probes burn
~15min each (the tractability hazard the brief flagged). Scale the caps
proportionately to the time axis: early_stop_max_elapsed_s 900->180,
early_stop_max_lag_s 120->30. Feasible probes (~120s arrival + drain)
finish well inside 180s; overloaded probes die in ~3min. Both configs
still differ only in use_harness + study_id. Adds the ablation doc
skeleton and a read-only trajectory-extraction helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:49:57 +08:00
a16016a876 Add harness vs naive ablation configs (27b, scale=0.2 substrate)
Two configs identical except llm.use_harness and study_id, for the
controlled harness-ON vs naive-OFF tuning-trajectory ablation on dense
Qwen3.5-27B. Faster substrate (replay_time_scale=0.2, search.high=0.25,
max_probes=5) keeps the ablation tractable; Stop-A stays enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:31:23 +08:00
07f5d92e1d Add consolidated two-stop summary doc
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:16:28 +08:00
f2ff0faebd Document Stop-B end-to-end on dense 27B: the improving climb + no-regression
Real gpt-5.4 agentic loop raised per-GPU TP1 0.123 -> TP2 0.2925 -> TP4 1.0012 (8.1x),
each a correctly-diagnosed real gain; then a TP4 runtime tweak measured 0.942 < 1.00
and was correctly rejected (no regression). With the 30B run (validator stop + LLM-stop
veto), all Stop-B behaviors are now validated end-to-end. The SIGTERM-teardown fix was
validated in practice (clean engine teardown, no GPU leak on stop). Efficiency finding:
at scale=1.0, infeasible high-theta probes burn the 900s elapsed cap, so a practical
loop needs a lower cap; this is why the run was stopped after iter-4 rather than driven
to an explicit Stop-B firing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:07:00 +08:00
4a64196a99 Add 27B Stop-B agentic-loop config (harness-driven, GPUs 2-7)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 09:08:46 +08:00
b17b213575 Tear down the engine on SIGTERM instead of orphaning it
Killing `study tune` with a default SIGTERM skipped the finally blocks, leaving the
vLLM engine and its EngineCore workers (which inherit the AITUNER_* marker env) alive
on the GPUs — twice leaking GPU memory that needed a root reset. Install a SIGTERM
handler in run_trial that raises KeyboardInterrupt so _terminate_process_tree runs,
ignore SIGTERM during teardown so a second signal can't re-orphan it, and restore the
prior handler afterward. Main-thread-guarded; unit-tested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 09:08:06 +08:00
93ce339d61 Document 27B TP sweep: per-GPU rises sharply with TP (dense), opposite of MoE
Under the length-aware TTFT SLO (4s + L_in/8k), dense Qwen3.5-27B per-GPU throughput:
TP1=0.065, TP2=0.2925 (4.5x), TP4>=0.908 (>=14x, ceiling-saturated). TP1 is TPOT-bound
(one H20 can't decode a 27B under 50ms/token once batched); loosening TTFT didn't move
TP1, confirming TPOT is the binding constraint. Opposite of MoE 30B-A3B where TP1 was
best per-GPU. Validates the harness + length-aware SLO produce meaningful, non-saturated
measurements (TP1/TP2). TP4 saturated -> lower bound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 01:54:40 +08:00
b1b74318f6 Pin 27B A/B to GPUs 2-7 (route around leaked GPU0/1 memory)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:01:22 +08:00
2fcaf80450 Wrap socket/timeout errors in HTTP client as HttpClientError
stream_chat_completion (and the LLM stream/chat paths) only caught HTTPError, so a
request exceeding request_timeout_s raised a raw TimeoutError mid-stream that escaped
_run_one_request (which only catches HttpClientError), propagated through the probe,
and crashed the whole trial ("failed: timed out"). A timed-out request is a failed
request (SLO miss), not a trial crash. Catch OSError (covers TimeoutError, URLError,
ConnectionError) after HTTPError and wrap it. Exposed by lowering request_timeout_s
to 180s on the 27B run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:58:28 +08:00
3541065675 Speed up 27B TP A/B: request_timeout 180s, search.high 0.125
The wide 0.5 range made TP1 (low-capacity) waste many infeasible high-theta probes,
and the 900s request timeout made overloaded probes drain hung requests for 15min
each. Cap drain at 180s and bound the search to where the boundaries actually are.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:40:42 +08:00
7678c7d5e8 Switch 27B TP A/B to length-aware TTFT SLO (4s + L_in/8k), widen search
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:35:23 +08:00
ed2bbe0323 Add linear_ms SLO rule (length-aware TTFT budget)
threshold_ms = intercept_ms + per_token_ms * input_tokens. Lets the TTFT target
scale with prefill work, e.g. "4s + L_in/8k" => intercept_ms=4000, per_token_ms=0.125
(4s base, +1s per 8k input tokens). slo + spec + test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:35:23 +08:00
77af4ded2a Flag Stop-B e2e per-GPU trajectory as non-benchmark (saturation + smoke regime)
The reported trajectory validates the Stop-B mechanics only. TP2-DP2/TP4 saturated
the trace ceiling (best_sampling_u~0.98) so their per-GPU peak is underestimated, and
the run used the smoke regime (scale=0.1 + 512 cap). The TP1>TP2 ordering may be real
for the small-active MoE but this run cannot establish it; the 27B TP A/B is the valid
follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:40:38 +08:00
4f45b546a1 Add 27B TP A/B (deterministic ground-truth: does TP2 beat TP1 per-GPU)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:39:54 +08:00
90c3eb51c8 Document Stop-B end-to-end validation (Phase 5)
Real gpt-5.4 agentic loop on Qwen3-30B-A3B/H20 with Stop-A enabled. Validates both
Stop-B paths: search-high-saturation (validator-authorized immediate stop) and
multi-iteration convergence. The TP1 baseline stays the per-GPU incumbent (2.90
req/s/GPU); TP/DP scaling raises raw throughput but lowers per-GPU efficiency and is
correctly never adopted (no regression). The Phase-4 authority model is exercised
live: a premature LLM stop is vetoed (validator_did_not_authorize_stop), then a later
justified stop is honored after the veto budget. EP launch-failures handled as
hard-negative evidence. Auditable reason chains throughout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:58:44 +08:00
0b6beafeb8 Phase 5: widen search.high to 1.0 to force multi-iteration Stop-B convergence
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:12:32 +08:00
d4aff81691 Add Stop-B end-to-end config (agentic loop, Stop-A enabled)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:05:39 +08:00
f31e9ccfd5 Record Stop-A boundary-guard A/B: correct verdict, ~38% replay saved
With the guard enabled the binary search recovers best sampling_u=0.078125
(rate 2.30 req/s), identical to the full-replay baseline. The guard fired on
exactly the one feasibility-knee probe (0.08594, re-measured full -> infeasible);
the other three probes truncated to ~45-50%. Net ~38% replay saved on the trial
with no peak-rate overestimate. Stop-A + boundary guard is safe to enable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:57:53 +08:00
03e556f0ab Add Stop-A ON config (adaptive_stop enabled + boundary guard) for A/B
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:25:24 +08:00
dfc823f972 Add Stop-A SLO-boundary guard
When a truncated probe's measured pass-rate lands within trace.adaptive_stop.
boundary_delta of the SLO target, re-measure on the full window and use that
verdict. Offered-L-C-A convergence cannot see engine-state drift in the window
tail, so a near-knee truncated verdict is untrustworthy (validated: prefix 0.96
vs full 0.946 at threshold 0.08594). The guard fires only on feasibility-knee
probes, so non-boundary probes keep the Stop-A saving. Default delta=0.02.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:25:24 +08:00
9f52812753 Document Stop-A validation: calibration + GPU fidelity check
CPU calibration (chat vs coder) reproduces the paper's C-slowest ordering and
shows C-convergence difficulty is driven by signal noise (low-reuse chat) not
reuse magnitude. GPU fidelity check on Qwen3-30B-A3B: truncating at the L-C-A
convergence prefix saves ~52% replay (tau_c=0.90) with 3/4 probe verdicts
preserved; the one mismatch is a boundary false-positive at the feasibility knee
(prefix 0.96 vs full 0.946), caused by second-half engine-state drift the offered
L-C-A cannot see. Argues for revisiting the SLO-boundary guard before enabling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:03:16 +08:00
958739027a Fix Stop-A validation config: system vllm, cap max-model-len
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:22:48 +08:00
0f57ee96a9 Drop LLM endpoint from Stop-A full-data config (baseline-only run)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:19:46 +08:00
43125f48cf Address review of two-stop branch
- lca._prefix_profile: anchor the prefix window to the prefix's own first arrival
  so the A-rate is measured over the prefix span (matches the design intent;
  no-op for the 0-based canonical pipeline).
- cli study tune: label file-originated stops as file_proposal rather than
  llm_after_veto_budget (the veto never applies to file proposals).
- spec.AdaptiveStopSpec: reject stable_checks > max_checks (would make
  convergence undetectable and silently disable Stop-A).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:19:08 +08:00
3af1d84ac0 Add Stop-A full-data validation config (real-time replay, no cap)
A single-config baseline run with adaptive_stop disabled and replay_time_scale=1.0,
so per-request probe_details capture the full 600s window for offline analysis of
whether truncating at the L-C-A convergence prefix preserves the feasibility verdict.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:15:12 +08:00
08e53fd897 Add Stop-A calibration script (CPU-only convergence curve)
Prints the offered-L-C-A convergence curve and the stop fraction at candidate
tau_c values for a raw trace window, to calibrate Stop-A thresholds and compare
how late C converges across workloads. No serving required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:10:02 +08:00
a8f903498d Add Stop-B authority: deterministic validator overrides LLM stop
Phase 4 of the two-stop work. The harness already pre-empts the LLM with
deterministic stops and guided probes, but an LLM-originated should_stop could
still end the loop while the validator saw remaining opportunity.

Add harness._stop_authority, exposed as context["stop_authority"], whose
`authorized` mirrors the deterministic harness stop decision and whose
`opportunity_remains` flags an open topology frontier or a high-value planned
candidate. In study tune, an LLM-originated should_stop is now honored only when
the validator authorizes it; an unauthorized stop is vetoed (bounded budget) so
the loop cannot converge prematurely on the agent's say-so. File- and
harness-originated stops are unaffected, and the stop reason chain is recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:45:14 +08:00
51a9e4a007 Add Stop-A: offered-L-C-A convergence early-stop for replay
Phase 2 of the two-stop work. The L-C-A vector is a deterministic function of the
trace's offered metadata, so the convergence of prefix-vs-full L-C-A (the paper's
Fig. 9 curve) can be computed up front rather than monitored live, with identical
result and no per-request overhead.

- lca.find_convergence_prefix: earliest arrival-ordered prefix whose L and A family
  similarities reach tau and the slow C family reaches the stricter tau_c for
  stable_checks consecutive checkpoints. Self-similarity uses the raw log-feature
  vector (same window -> identical per-dim spread; RobustScaler is reserved for the
  cross-window Stop-C). If C never converges it reports the full set, which is the
  C-gate: no early stop on a cold/under-warmed cache. The checkpoint sims double as
  Phase 3 calibration data.
- spec.AdaptiveStopSpec (trace.adaptive_stop), disabled by default until the
  thresholds are calibrated, so existing studies are unaffected.
- worker._adaptive_replay_set truncates each probe's replay to the convergence
  prefix and records a certificate (converged, fraction, family similarity) into
  probe history and probe_details. Offered request_rate at the threshold is
  unchanged; only wall-clock replay shrinks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:23:49 +08:00
0f15bbc3f1 Make the offered-load axis session-coherent
Phase 1 of the two-stop work. Subsampling the trace by per-request uniform score
broke multi-turn sessions (a kept turn-2 could lose its turn-1), which lowered the
realized KV-cache hit rate as offered load dropped — so the feasibility boundary
was measured on a workload with a different C than production, contradicting the
paper's scale-stationary L-C-A premise.

prepare_trace_windows now resolves each row's session root via the parent_chat_id
chain in a single streaming pass and assigns sampling_u per session, so thresholding
keeps or drops whole sessions and preserves intra-session prefix reuse. Rows whose
parent fell outside the span fall back to grouping under the parent id.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:16:06 +08:00
6f8e3c95c1 Unify harness L-C-A on the canonical lca.WorkloadProfile
Phase 0 of the two-stop work. The prompt block labeled `workload_lca_profile`
previously re-derived L-C-A from summarize_window's ad-hoc percentiles, diverging
from the paper's 10-dim RobustScaler vector implemented in lca.py. Make that block
authoritative: build_harness_context now accepts an optional workload_profile and
renders the canonical 10-dim vector + per-family stats when present, falling back
to the legacy rendering only when no profile is supplied (direct unit-test calls).

Real call sites (study prompt/llm-propose/tune, run_baseline_then_llm) build the
profile via lca.build_study_workload_profile and pass it through build_prompt. The
heuristic regime classifiers keep reading window_summary; that is the heuristic
layer, distinct from the similarity metric.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:12:17 +08:00
8b4116fad0 Add reference paper and qwen27b tpot25 16-iter notes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:02:30 +08:00
27d1c8fa92 Add L-C-A workload profile metric and CLI profile commands
Implement the paper's 10-dimensional L-C-A workload feature vector
(RobustScaler-normalized, sim=exp(-||dz||)) in lca.py, and wire it into
`aituner profile window` / `aituner profile similarity`. Covered by tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:02:24 +08:00
984eb1f325 Document 8-GPU harness ablation results for qwen27b and qwen235b prefill
Add completed experiment results from dash0 runs after 2026-05-13:
- qwen27b chat 0-8k: harness +118.6% over no-harness (0.2696 vs 0.1233 req/s/GPU)
- qwen235b prefill TTFT 3s/6s/9s: harness +76.8% (0.3921 vs 0.2217 req/s/GPU)

Mark old 7-GPU and pre-5/13 docs as superseded. Update implementation
log with completed run status.
2026-05-16 21:23:16 +08:00
d0c89dac48 Clean marked trial engine processes 2026-05-16 15:51:04 +08:00
cf9b8b3f68 Clean vLLM process groups after parent exit 2026-05-16 14:52:05 +08:00
5a879a8592 Fix decode harness partial probe handling 2026-05-16 14:18:07 +08:00
f18765b235 Document eight-GPU harness rerun 2026-05-13 09:04:14 +08:00
5c2958e6c1 Constrain harness topology by visible GPUs 2026-05-13 01:25:31 +08:00
fb6d74a18c Document harness v2 rerun criteria 2026-05-12 22:23:12 +08:00
e3ed775afd Fix harness SLO early-stop diagnosis 2026-05-12 22:20:01 +08:00
ef359c8eea Document profile-driven harness run 2026-05-12 21:40:19 +08:00
17e9681ca0 Add profile-driven harness planner 2026-05-12 21:28:44 +08:00
63d6a111f4 Document profile-driven harness design 2026-05-12 21:09:29 +08:00
2d03b1cd4c Add SLO-driven topology frontier harness guard 2026-05-12 21:00:49 +08:00
e1125475ae Minimize no-harness ablation prompt 2026-05-12 09:42:53 +08:00
ae756600ce Support full-range and incumbent-floor search modes 2026-05-11 12:58:46 +08:00
8516cd88c0 Use full search range for every trial 2026-05-11 12:50:22 +08:00
14259fcec9 Measure lower-range performance for infeasible trials 2026-05-10 14:30:34 +08:00
bf7c02e721 Clarify qwen27b raw per-iteration performance 2026-05-10 14:24:10 +08:00
b0325ecfd9 Clarify qwen235b raw per-iteration performance 2026-05-10 14:21:49 +08:00
4cfd3757b6 Document qwen235b prefill harness ablation 2026-05-10 13:05:49 +08:00
bdb08f6edc Handle missing streamed token metrics 2026-05-10 02:40:00 +08:00
307e2eb0e8 Document qwen27b harness ablation 2026-05-10 01:12:21 +08:00
adc4351e5d Report latency stats for infeasible baseline 2026-05-08 11:10:34 +08:00
eb137a0b62 Document TPOT40 baseline infeasible run 2026-05-08 02:57:03 +08:00
f212673f44 Stop tuning when baseline is infeasible 2026-05-08 01:07:36 +08:00
a7a5e9ad80 Make tune trial budget resumable 2026-05-07 17:18:06 +08:00
7263587cb6 clean: ci 2026-05-06 22:56:53 +08:00
62 changed files with 10583 additions and 175 deletions

View File

@@ -1,25 +0,0 @@
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: python -m pip install -e .
- name: Test
run: python -m unittest discover -s tests -v

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
.aituner-tight/
.aituner-prefill/
.aituner-compare/
.aituner-run-configs/
.env
__pycache__/
*.pyc

View File

@@ -6,6 +6,10 @@
- Hardware expectation: 8 NVIDIA H20 GPUs.
- SSH check: use `ssh dash0` before scheduling or debugging remote runs.
- Remote project path: `/home/admin/cpfs/wjh/aituner/aituner`.
- If remote downloads are slow or fail, start the proxy from the remote `wjh`
home directory with `./auto_proxy.sh`, then run downloads in a shell where
`proxyOn` from `~/.bashrc` has been applied. If `autossh` is unavailable,
`ssh -Nf proxy` provides the same local `127.0.0.1:11235` tunnel.
## Local/remote sync workflow

View File

@@ -0,0 +1,180 @@
{
"study_id": "dash0-qwen27b-ablation-harness-on",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "qwen3.5-27b-256k-0223-internal",
"served_model_name": "qwen35-27b-aituner"
},
"engine": {
"engine_name": "vllm",
"engine_version": "latest-release-on-dash0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18082,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 180,
"launch_args": [
"serve",
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
],
"base_envs": {
"VLLM_DISABLE_COMPILE_CACHE": "1",
"DS_LLM_IGNORE_WARMUP": "1",
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
"PARAM_TOTAL_MAX": "262144",
"PARAM_IN_LENGTH_MAX": "262144",
"PARAM_MAX_LENGTH_MAX": "131072",
"DS_LLM_MAX_THINK_TOKENS": "81920",
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
"VLLM_FP8_USE_BLADNN": "1",
"VLLM_MOE_USE_BLADNN": "1",
"VLLM_GDN_USE_BLADNN": "0",
"VLLM_USE_V1": "1",
"VLLM_IS_HYBRID_MODEL": "1",
"VLLM_ENABLE_TORCH_COMPILE": "1",
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
"VLLM_USE_FLASHINFER_SAMPLER": "0",
"VLLM_DP_MASTER_PORT": "9528",
"VLLM_RESPONSE_TIMEOUT": "300",
"VLLM_LOG_REQ_KV_LENS": "1",
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18082,
"served-model-name": "qwen35-27b-aituner",
"trust-remote-code": true,
"dtype": "bfloat16",
"gpu-memory-utilization": 0.9,
"enable-prefix-caching": true,
"mamba-cache-mode": "light",
"distributed-executor-backend": "mp",
"block-size": 64,
"enable-chunked-prefill": true,
"max-num-batched-tokens": 8192,
"disable-cascade-attn": true,
"max-model-len": 262144,
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
"mm-processor-cache-gb": 0,
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
"mamba-cache-dtype": "float32",
"skip-mm-profiling": true,
"quantization": "fp8",
"tensor-parallel-size": 1,
"disable-log-requests": true
},
"tunable_envs": [
"VLLM_ENABLE_TORCH_COMPILE"
],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"expert-parallel-size",
"gpu-memory-utilization",
"block-size",
"max-num-batched-tokens",
"max-num-seqs",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1
]
},
"python_executable": "python3"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 32,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 0.8775,
"early_stop_max_lag_s": 45.0,
"early_stop_max_elapsed_s": 1000.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "linear_ms",
"intercept_ms": 4000,
"per_token_ms": 0.125
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.15,
"tolerance": 0.001,
"max_probes": 6,
"sample_seed": 20260325,
"inherit_incumbent_floor": true
},
"llm": {
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
"max_history_trials": 8,
"endpoint": {
"provider": "codex",
"model": "gpt-5.5",
"base_url": "https://ai.gahow.org/v1",
"wire_api": "chat.completions",
"stream": true,
"api_key_env": "OPENAI_API_KEY",
"timeout_s": 180
},
"use_harness": true
}
}

View File

@@ -0,0 +1,180 @@
{
"study_id": "dash0-qwen27b-ablation-naive-off",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "qwen3.5-27b-256k-0223-internal",
"served_model_name": "qwen35-27b-aituner"
},
"engine": {
"engine_name": "vllm",
"engine_version": "latest-release-on-dash0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18082,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 180,
"launch_args": [
"serve",
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
],
"base_envs": {
"VLLM_DISABLE_COMPILE_CACHE": "1",
"DS_LLM_IGNORE_WARMUP": "1",
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
"PARAM_TOTAL_MAX": "262144",
"PARAM_IN_LENGTH_MAX": "262144",
"PARAM_MAX_LENGTH_MAX": "131072",
"DS_LLM_MAX_THINK_TOKENS": "81920",
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
"VLLM_FP8_USE_BLADNN": "1",
"VLLM_MOE_USE_BLADNN": "1",
"VLLM_GDN_USE_BLADNN": "0",
"VLLM_USE_V1": "1",
"VLLM_IS_HYBRID_MODEL": "1",
"VLLM_ENABLE_TORCH_COMPILE": "1",
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
"VLLM_USE_FLASHINFER_SAMPLER": "0",
"VLLM_DP_MASTER_PORT": "9528",
"VLLM_RESPONSE_TIMEOUT": "300",
"VLLM_LOG_REQ_KV_LENS": "1",
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18082,
"served-model-name": "qwen35-27b-aituner",
"trust-remote-code": true,
"dtype": "bfloat16",
"gpu-memory-utilization": 0.9,
"enable-prefix-caching": true,
"mamba-cache-mode": "light",
"distributed-executor-backend": "mp",
"block-size": 64,
"enable-chunked-prefill": true,
"max-num-batched-tokens": 8192,
"disable-cascade-attn": true,
"max-model-len": 262144,
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
"mm-processor-cache-gb": 0,
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
"mamba-cache-dtype": "float32",
"skip-mm-profiling": true,
"quantization": "fp8",
"tensor-parallel-size": 1,
"disable-log-requests": true
},
"tunable_envs": [
"VLLM_ENABLE_TORCH_COMPILE"
],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"expert-parallel-size",
"gpu-memory-utilization",
"block-size",
"max-num-batched-tokens",
"max-num-seqs",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1
]
},
"python_executable": "python3"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 32,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 0.8775,
"early_stop_max_lag_s": 45.0,
"early_stop_max_elapsed_s": 1000.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "linear_ms",
"intercept_ms": 4000,
"per_token_ms": 0.125
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.15,
"tolerance": 0.001,
"max_probes": 6,
"sample_seed": 20260325,
"inherit_incumbent_floor": true
},
"llm": {
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
"max_history_trials": 8,
"endpoint": {
"provider": "codex",
"model": "gpt-5.5",
"base_url": "https://ai.gahow.org/v1",
"wire_api": "chat.completions",
"stream": true,
"api_key_env": "OPENAI_API_KEY",
"timeout_s": 180
},
"use_harness": false
}
}

View File

@@ -0,0 +1,177 @@
{
"study_id": "dash0-qwen27b-stopB-loop-chat-0-8k",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "qwen3.5-27b-256k-0223-internal",
"served_model_name": "qwen35-27b-aituner"
},
"engine": {
"engine_name": "vllm",
"engine_version": "latest-release-on-dash0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18082,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 180,
"launch_args": [
"serve",
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
],
"base_envs": {
"VLLM_DISABLE_COMPILE_CACHE": "1",
"DS_LLM_IGNORE_WARMUP": "1",
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
"PARAM_TOTAL_MAX": "262144",
"PARAM_IN_LENGTH_MAX": "262144",
"PARAM_MAX_LENGTH_MAX": "131072",
"DS_LLM_MAX_THINK_TOKENS": "81920",
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
"VLLM_FP8_USE_BLADNN": "1",
"VLLM_MOE_USE_BLADNN": "1",
"VLLM_GDN_USE_BLADNN": "0",
"VLLM_USE_V1": "1",
"VLLM_IS_HYBRID_MODEL": "1",
"VLLM_ENABLE_TORCH_COMPILE": "1",
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
"VLLM_USE_FLASHINFER_SAMPLER": "0",
"VLLM_DP_MASTER_PORT": "9528",
"VLLM_RESPONSE_TIMEOUT": "300",
"VLLM_LOG_REQ_KV_LENS": "1",
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18082,
"served-model-name": "qwen35-27b-aituner",
"trust-remote-code": true,
"dtype": "bfloat16",
"gpu-memory-utilization": 0.9,
"enable-prefix-caching": true,
"mamba-cache-mode": "light",
"distributed-executor-backend": "mp",
"block-size": 64,
"enable-chunked-prefill": true,
"max-num-batched-tokens": 8192,
"disable-cascade-attn": true,
"max-model-len": 262144,
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
"mm-processor-cache-gb": 0,
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
"mamba-cache-dtype": "float32",
"skip-mm-profiling": true,
"quantization": "fp8",
"tensor-parallel-size": 1,
"disable-log-requests": true
},
"tunable_envs": [
"VLLM_ENABLE_TORCH_COMPILE"
],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"expert-parallel-size",
"gpu-memory-utilization",
"block-size",
"max-num-batched-tokens",
"max-num-seqs",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1
]
},
"python_executable": "python3"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 32,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 1.0,
"early_stop_max_lag_s": 120.0,
"early_stop_max_elapsed_s": 900.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "linear_ms",
"intercept_ms": 4000,
"per_token_ms": 0.125
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.25,
"tolerance": 0.001,
"max_probes": 6,
"sample_seed": 20260325,
"inherit_incumbent_floor": true
},
"llm": {
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
"max_history_trials": 8,
"endpoint": {
"provider": "codex",
"model": "gpt-5.4",
"stream": true,
"api_key_env": "OPENAI_API_KEY",
"timeout_s": 180
}
}
}

View File

@@ -0,0 +1,176 @@
{
"study_id": "dash0-qwen27b-tp-ab-chat-0-8k",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "qwen3.5-27b-256k-0223-internal",
"served_model_name": "qwen35-27b-aituner"
},
"engine": {
"engine_name": "vllm",
"engine_version": "latest-release-on-dash0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18082,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 180,
"launch_args": [
"serve",
"/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal"
],
"base_envs": {
"VLLM_DISABLE_COMPILE_CACHE": "1",
"DS_LLM_IGNORE_WARMUP": "1",
"DS_LLM_IGNORE_CHECK_WARMUP": "1",
"VLLM_ENABLE_MODEL_RUNNER_WARMUP": "1",
"VLLM_GDN_USE_FUSED_QKVZBA_KERNEL": "0",
"PARAM_TOTAL_MAX": "262144",
"PARAM_IN_LENGTH_MAX": "262144",
"PARAM_MAX_LENGTH_MAX": "131072",
"DS_LLM_MAX_THINK_TOKENS": "81920",
"DS_LLM_GRACEFUL_SHUTDOWN_WAIT_SECONDS": "600",
"VLLM_FP8_USE_BLADNN": "1",
"VLLM_MOE_USE_BLADNN": "1",
"VLLM_GDN_USE_BLADNN": "0",
"VLLM_USE_V1": "1",
"VLLM_IS_HYBRID_MODEL": "1",
"VLLM_ENABLE_TORCH_COMPILE": "1",
"VLLM_ATTENTION_BACKEND": "FLASH_ATTN",
"VLLM_QUANTIZE_ROUTED_EXPERTS_ONLY": "1",
"VLLM_USE_FLASHINFER_SAMPLER": "0",
"VLLM_DP_MASTER_PORT": "9528",
"VLLM_RESPONSE_TIMEOUT": "300",
"VLLM_LOG_REQ_KV_LENS": "1",
"DS_LLM_GRACEFUL_SHUTDOWN_KEEP_SECONDS": "600",
"CUDA_VISIBLE_DEVICES": "2,3,4,5,6,7"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18082,
"served-model-name": "qwen35-27b-aituner",
"trust-remote-code": true,
"dtype": "bfloat16",
"gpu-memory-utilization": 0.9,
"enable-prefix-caching": true,
"mamba-cache-mode": "light",
"distributed-executor-backend": "mp",
"block-size": 64,
"enable-chunked-prefill": true,
"max-num-batched-tokens": 8192,
"disable-cascade-attn": true,
"max-model-len": 262144,
"speculative-config": "{\"method\":\"qwen3_next_vl_mtp\",\"num_speculative_tokens\":3}",
"mm-processor-cache-gb": 0,
"limit-mm-per-prompt": "{\"image\":256,\"video\":64}",
"compilation-config": "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"use_inductor\":false,\"pass_config\":{\"fuse_norm_quant\":false,\"fuse_act_quant\":false,\"fuse_attn_quant\":false}}",
"mamba-cache-dtype": "float32",
"skip-mm-profiling": true,
"quantization": "fp8",
"tensor-parallel-size": 1,
"disable-log-requests": true
},
"tunable_envs": [
"VLLM_ENABLE_TORCH_COMPILE"
],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"expert-parallel-size",
"gpu-memory-utilization",
"block-size",
"max-num-batched-tokens",
"max-num-seqs",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1
]
},
"python_executable": "python3"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 32,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 1.0,
"early_stop_max_lag_s": 120.0,
"early_stop_max_elapsed_s": 900.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "linear_ms",
"intercept_ms": 4000,
"per_token_ms": 0.125
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.125,
"tolerance": 0.001,
"max_probes": 6,
"sample_seed": 20260325
},
"llm": {
"system_prompt": "Propose a single engine config patch that increases the maximum feasible sampling_u under the SLO target. Favor launch-safe changes grounded in the incumbent result and only propose knobs that plausibly improve throughput above the incumbent request rate.",
"max_history_trials": 8,
"endpoint": {
"provider": "codex",
"model": "gpt-5.4",
"stream": true,
"api_key_env": "OPENAI_API_KEY",
"timeout_s": 180
}
}
}

View File

@@ -0,0 +1,138 @@
{
"study_id": "dash0-qwen30b-a3b-stopA-fulldata-chat-0-8k",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "Qwen/Qwen3-30B-A3B",
"served_model_name": "qwen3-30b-a3b-community"
},
"engine": {
"engine_name": "vllm",
"engine_version": "0.20.0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18230,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 900,
"launch_args": [
"serve",
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
],
"base_envs": {
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
"HOME": "/tmp/wjh",
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18230,
"served-model-name": "qwen3-30b-a3b-community",
"gpu-memory-utilization": 0.9,
"max-model-len": 16384,
"trust-remote-code": true,
"enable-prefix-caching": true
},
"tunable_envs": [],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"enable-expert-parallel",
"expert-parallel-size",
"gpu-memory-utilization",
"max-num-batched-tokens",
"max-num-seqs",
"block-size",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1,
2,
4,
8
]
},
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"completion_tokens_override": 128,
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 64,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 1.0,
"early_stop_max_lag_s": 120.0,
"early_stop_max_elapsed_s": 900.0
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "step_ms",
"buckets": [
{
"max_input_tokens": 4096,
"threshold_ms": 2000
},
{
"max_input_tokens": 32768,
"threshold_ms": 4000
},
{
"threshold_ms": 6000
}
]
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.125,
"tolerance": 0.001,
"max_probes": 4,
"sample_seed": 20260325
},
"llm": {
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
"max_history_trials": 8,
"use_harness": false
}
}

View File

@@ -0,0 +1,147 @@
{
"study_id": "dash0-qwen30b-a3b-stopA-on-chat-0-8k",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "Qwen/Qwen3-30B-A3B",
"served_model_name": "qwen3-30b-a3b-community"
},
"engine": {
"engine_name": "vllm",
"engine_version": "0.20.0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18230,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 900,
"launch_args": [
"serve",
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
],
"base_envs": {
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
"HOME": "/tmp/wjh",
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18230,
"served-model-name": "qwen3-30b-a3b-community",
"gpu-memory-utilization": 0.9,
"max-model-len": 16384,
"trust-remote-code": true,
"enable-prefix-caching": true
},
"tunable_envs": [],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"enable-expert-parallel",
"expert-parallel-size",
"gpu-memory-utilization",
"max-num-batched-tokens",
"max-num-seqs",
"block-size",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1,
2,
4,
8
]
},
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"completion_tokens_override": 128,
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 64,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"replay_time_scale": 1.0,
"early_stop_max_lag_s": 120.0,
"early_stop_max_elapsed_s": 900.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "step_ms",
"buckets": [
{
"max_input_tokens": 4096,
"threshold_ms": 2000
},
{
"max_input_tokens": 32768,
"threshold_ms": 4000
},
{
"threshold_ms": 6000
}
]
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 0.125,
"tolerance": 0.001,
"max_probes": 4,
"sample_seed": 20260325
},
"llm": {
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
"max_history_trials": 8,
"use_harness": false
}
}

View File

@@ -0,0 +1,155 @@
{
"study_id": "dash0-qwen30b-a3b-stopB-e2e-hi-chat-0-8k",
"hardware": {
"gpu_count": 8,
"gpu_model": "H20",
"host_candidates": [
"dash0"
]
},
"model": {
"model_id": "Qwen/Qwen3-30B-A3B",
"served_model_name": "qwen3-30b-a3b-community"
},
"engine": {
"engine_name": "vllm",
"engine_version": "0.20.0",
"exec_path": "/usr/local/bin/vllm",
"cwd": "/home/admin/cpfs/wjh/aituner/aituner",
"host": "127.0.0.1",
"port": 18230,
"healthcheck_path": "/v1/models",
"ready_timeout_s": 900,
"request_timeout_s": 900,
"launch_args": [
"serve",
"/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B"
],
"base_envs": {
"CUDA_VISIBLE_DEVICES": "0,1,2,3,4,5,6,7",
"HOME": "/tmp/wjh",
"XDG_CACHE_HOME": "/tmp/wjh/.cache"
},
"base_flags": {
"host": "127.0.0.1",
"port": 18230,
"served-model-name": "qwen3-30b-a3b-community",
"gpu-memory-utilization": 0.9,
"max-model-len": 16384,
"trust-remote-code": true,
"enable-prefix-caching": true
},
"tunable_envs": [],
"tunable_flags": [
"tensor-parallel-size",
"data-parallel-size",
"enable-expert-parallel",
"expert-parallel-size",
"gpu-memory-utilization",
"max-num-batched-tokens",
"max-num-seqs",
"block-size",
"enable-prefix-caching",
"enable-chunked-prefill"
],
"topology_constraints": {
"require_tp_dp_product_equals_gpu_count": false,
"require_ep_size_leq_tp_dp_product": true,
"require_ep_size_divides_tp_dp_product": true,
"require_enable_expert_parallel_when_ep_gt_one": true,
"validate_cuda_graph_sizes_divisible_by_tp_when_tp_ep_reduce_scatter": true,
"allowed_tp_dp_products": [
1,
2,
4,
8
],
"allowed_tensor_parallel_sizes": [
1,
2,
4,
8
],
"allowed_data_parallel_sizes": [
1,
2,
4,
8
],
"allowed_expert_parallel_sizes": [
1,
2,
4,
8
]
},
"python_executable": "/tmp/wjh/venvs/vllm-0.20.0-cu129/bin/python"
},
"trace": {
"windows_path": "/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json",
"window_id": "chat_w20260311_1000",
"completion_tokens_override": 128,
"u_field": "sampling_u",
"timestamp_field": "timestamp",
"max_concurrency": 64,
"input_length_filter": {
"min_input_tokens": 0,
"max_input_tokens": 8192
},
"max_requests_per_probe": 512,
"replay_time_scale": 0.1,
"early_stop_max_lag_s": 120.0,
"early_stop_max_elapsed_s": 900.0,
"adaptive_stop": {
"enabled": true,
"tau": 0.9,
"tau_c": 0.9,
"stable_checks": 3,
"max_checks": 20,
"min_fraction": 0.1,
"boundary_delta": 0.02
}
},
"slo": {
"target_pass_rate": 0.95,
"ttft_rule": {
"kind": "step_ms",
"buckets": [
{
"max_input_tokens": 4096,
"threshold_ms": 2000
},
{
"max_input_tokens": 32768,
"threshold_ms": 4000
},
{
"threshold_ms": 6000
}
]
},
"tpot_rule": {
"kind": "fixed_ms",
"threshold_ms": 50
}
},
"search": {
"low": 0.0,
"high": 1.0,
"tolerance": 0.001,
"max_probes": 6,
"sample_seed": 20260325
},
"llm": {
"system_prompt": "Tune community vLLM 0.20.0 serving for Qwen3-30B-A3B. Start from the default vLLM engine configuration, use only launch-safe patches, and optimize request_rate_per_gpu under the configured SLO.",
"max_history_trials": 8,
"use_harness": true,
"endpoint": {
"provider": "codex",
"model": "gpt-5.4",
"stream": true,
"api_key_env": "OPENAI_API_KEY",
"timeout_s": 240
}
}
}

View File

@@ -0,0 +1,13 @@
{
"observation": "baseline TP1 (deployed flags)",
"diagnosis": "deterministic TP A/B point",
"config_patch": {
"env_patch": {},
"flag_patch": {}
},
"expected_effects": [
"measure peak request_rate_per_gpu at this topology"
],
"why_not_previous_failures": "n/a",
"should_stop": false
}

View File

@@ -0,0 +1,15 @@
{
"observation": "TP2",
"diagnosis": "deterministic TP A/B point",
"config_patch": {
"env_patch": {},
"flag_patch": {
"tensor-parallel-size": 2
}
},
"expected_effects": [
"measure peak request_rate_per_gpu at this topology"
],
"why_not_previous_failures": "n/a",
"should_stop": false
}

View File

@@ -0,0 +1,15 @@
{
"observation": "TP4",
"diagnosis": "deterministic TP A/B point",
"config_patch": {
"env_patch": {},
"flag_patch": {
"tensor-parallel-size": 4
}
},
"expected_effects": [
"measure peak request_rate_per_gpu at this topology"
],
"why_not_previous_failures": "n/a",
"should_stop": false
}

View File

@@ -0,0 +1,26 @@
{
"report_id": "qwen27b-abl12-harness-vs-naive",
"output_root": "../../.aituner-reports/qwen27b-abl12-harness-vs-naive",
"target_fraction": 0.95,
"min_final_ratio": 0.98,
"cases": [
{
"case_id": "qwen27b-chat-0-8k-real-output",
"description": "12-trial harness-vs-naive ablation on the 0-8k chat window with real output lengths.",
"tags": ["qwen27b", "chat", "0-8k", "h20", "real-output"],
"budgets": [1, 2, 3, 4, 6, 8, 12],
"arms": [
{
"name": "harness",
"kind": "harness",
"study_root": "../../.aituner/abl12-harness/dash0-qwen27b-ablation-harness-on"
},
{
"name": "naive",
"kind": "naive",
"study_root": "../../.aituner/abl12-naive/dash0-qwen27b-ablation-naive-off"
}
]
}
]
}

215
docs/aituner-roadmap.md Normal file
View File

@@ -0,0 +1,215 @@
# AITuner roadmap
本文只维护最小 roadmappaper framing、claim 树、已有证据、最高优先级实验。
详细实验流水账放到对应专题文档里。
## Paper thesis
AITuner 的核心不是“用 LLM 调参”。更准确的 framing 是:
```text
black-box knob optimization
-> grey-box / mechanism-guided experimental optimization
```
也就是说AITuner 仍然通过真实实验测量目标函数,但它不再把 serving engine 当成
完全黑盒的 `config vector -> scalar score`。Harness 将 workload、SLO failure、
probe trace、topology constraints 和 failure memory 转换成结构化的 serving
mechanism state并把下一步搜索限制在可解释、可验证的 intervention 上。
因此 LLM 不是不可替代的核心。LLM 是 planner backend / copilot核心系统贡献是
planner-agnostic 的 tuning substrate
```text
Harness H = (O, R, G, V, M)
O: observation schema
workload L/C/A profile + probe trace + latency/SLO failure + launch status
R: regime attribution
SLO violation -> prefill-bound / decode-bound / admission-bound / memory-bound / launch-bound
G: serving intervention grammar
regime -> legal intervention families, not raw arbitrary knobs
V: validator
tunable schema + topology constraints + no-repeat + failure memory + stop authority
M: measurement/scoring protocol
SLO-constrained feasible frontier, req/s/GPU, latency quantiles, pass-rate guard
```
Planner 是可替换的:
```text
pi in {LLM, BO, bandit, deterministic heuristic, tree search}
```
AITuner 的强 claim 应该是:同一个 planner 放在 harness-shaped space 里,比放在
raw knob space 里更快、更稳、更接近最优;弱模型或非 LLM planner 也能从这个 substrate
中获益。
## Why not pure white-box
我们不应 claim 完整 white-box optimization。AITuner 没有解析 vLLM scheduler、
kernel、KV cache、通信和排队的闭式性能模型。更稳妥也更强的表述是 grey-box
- objective 仍然由真实测量决定;
- action space、constraints、failure attribution 和 intervention semantics 是系统知识驱动;
- 每个 trial 是一个 counterfactual experiment而不是盲目采样一个 knob vector。
## 关键设计点
| 设计点 | 更强表述 | 作用 | 需要证明 |
| --- | --- | --- | --- |
| Observation | mechanism state | 将 workload shape、probe trace、SLO failure、launch/memory failure 结构化 | agent 看到的是可计算状态,不是自然语言日志 |
| Bottleneck classifier | SLO violation attribution | 把失败归因到 serving regime而不是只看哪个指标超阈值 | attribution 和后续有效 intervention 有因果关联 |
| Candidate family | serving intervention grammar | 把 raw knobs 提升为 topology / batching / admission / memory interventions | 搜索空间被压缩,但不写死某个 case |
| Scoring | counterfactual verdict | 用 SLO frontier 和 req/s/GPU 判断 intervention 是否支持假设 | 最终 winner 由测量决定,不由 LLM 决定 |
| Validator / stop | fail-safe control | 禁止非法、重复、已知失败 family只有 validator 授权 stop | 错误 attribution 最多浪费 trial不污染 incumbent |
## Claim roadmap
| Claim | 当前状态 | 证据文档 | 关键缺口 |
| --- | --- | --- | --- |
| C1. Harness 将 raw knob search 转成 mechanism-guided intervention search提升固定预算优化效果 | 已有强信号 | [Qwen27B 2x2](harness-ablation/qwen27b-tight-2x2-model-ablation-20260623.md), [Qwen30B SLO robustness](harness-ablation/qwen30b-slo-robustness-20260624.md) | 补 Qwen235B decode 2x2 aggregate补 mechanism ablation |
| C2. 收益来自 harness-defined substrate不依赖某个强 LLM | 部分已有 | [Qwen27B 2x2](harness-ablation/qwen27b-tight-2x2-model-ablation-20260623.md) | 做 `BO/heuristic + harness` vs `BO/heuristic + raw knobs` |
| C3. Weak planner + harness 可以匹配或超过 strong LLM naive | Qwen27B 已支持Qwen235B 正在补 | [Qwen27B 2x2](harness-ablation/qwen27b-tight-2x2-model-ablation-20260623.md), [Qwen235B prefill progress](harness-ablation/qwen235b-prefill-2x2-progress-20260623.md) | 完成 Qwen235B decode 2x2更新 prefill final doc |
| C4. Attribution 和 intervention grammar 有机制贡献,不只是 prompt 信息更多 | 设计已有,严格证据不足 | [AITuner summary](aituner-harness-summary.md) | 做 shuffled attribution / no attribution / no grammar / no topology-first / no validator ablation |
| C5. AITuner 找到 near-optimal region而不是只找到一个可行 config | Qwen30B 有解释性信号 | [Qwen30B SLO robustness](harness-ablation/qwen30b-slo-robustness-20260624.md) | 选 1-2 个 case 做局部 grid 或专家配置对照 |
| C6. AITuner 能随 SLO tightness 移动到合适 frontier | Qwen30B 已完成 | [Qwen30B SLO robustness](harness-ablation/qwen30b-slo-robustness-20260624.md) | 再选一个非同质 case 做 SLO sweep同时画 SLO tightness -> frontier/regime transition |
| C7. Engine adapter 让 intervention grammar 可迁移到其他 serving engine | 设计上可行,暂不作为主实验 claim | `EngineLaunchSpec` / launch recipe / tunable schema | vLLM 主线完成后,再做 SGLang adapter 和一个低成本验证 case |
## 最高优先级实验
### P0. 完成 Qwen235B decode 2x2 并整理 aggregate
目的:补齐最核心的 `harness on/off x strong/weak planner` 证据,回答:
```text
weak LLM + harness >= strong LLM naive ?
```
预期产出:
- 2x2 表格:每个 arm 在相同 iter budget 下的 best-so-far req/s/GPU
- convergence curve / normalized AUC
- 每个 arm 的 trial path 和主要 config patches
- 解释 naive 为什么走错harness 如何通过 regime attribution 走到正确 intervention。
优先级原因:实验已经在跑,增量成本最低,而且直接支撑 C1/C3。
### P1. Planner-agnostic substrate 实验
目的:证明 AITuner 不是 LLM tuner而是 harness-defined optimization substrate。
最小实验矩阵:
| Planner | Raw knob space | Harness intervention space |
| --- | --- | --- |
| deterministic heuristic | raw heuristic | harness policy |
| BO 或 lightweight bandit | raw BO | harness-guided BO |
| weak LLM | naive weak LLM | weak LLM + harness |
| strong LLM | naive strong LLM | strong LLM + harness |
如果 BO 实现成本高,先用 deterministic harness policy 做 non-LLM planner baseline
它已经能证明“没有 LLM 也能 work”。随后再补 BO使论证更强。
预期图:
- x-axis: trial budget
- y-axis: best-so-far SLO-constrained req/s/GPU
- line groups: raw knob space vs harness intervention space
- 单独 barinvalid launch rate、repeated config rate、wasted trial rate。
优先级原因:这是新 framing 的关键实验。没有它paper 仍然容易被读成“LLM prompt
engineering”。
### P2. Mechanism ablation
目的:证明 harness 内部不是普通信息堆叠,而是 attribution、intervention grammar、
validator 分别贡献有效机制。
建议 ablation
| Variant | 删除/破坏什么 | 预期证明 |
| --- | --- | --- |
| full AITuner | 无 | 最好 |
| no attribution | 不提供 regime attribution只给 scalar score 和历史结果 | attribution 对方向选择有贡献 |
| shuffled attribution | 故意打乱 regime label但保留文本长度 | 收益来自语义正确性,不是更多 prompt tokens |
| no intervention grammar | 允许任意 tunable knobs移除 family guidance | action-space shaping 有贡献 |
| no topology-first | runtime knobs 可以优先于 topology intervention | topology 是 LLM serving 的一阶决策 |
| no validator/failure memory | 允许重复、已知 launch failure family | fail-safe control 减少 GPU burn |
预期图:
- mechanism ablation barfinal best、AUC、TTT
- waste breakdowninvalid launch、repeat config、wrong-family trial
- case study trace每个 variant 前 3-5 个 proposal 对比。
优先级原因:这是回应 novelty 质疑的核心证据。
### P3. Near-optimum / expert baseline 证据
目的:证明 AITuner 不是只找到“能收敛但性能差”的 config。
优先选择一个成本可控 case 做局部 grid
```text
topology: TP/DP frontier
runtime: max-num-seqs, max-num-batched-tokens, gpu-memory-utilization 的小邻域
objective: max feasible req/s/GPU under pass_rate >= 0.95
```
预期图:
- local grid heatmap
- AITuner trial path overlay
- AITuner best vs grid best vs expert config
- near-optimum gap例如 `AITuner >= 95% of local grid optimum`
优先级原因:这是 claim “tune 出最好的 config而不是差的收敛 config” 的必要证据。
### P4. 第二个 SLO robustness case
目的:证明 Qwen30B 的 SLO robustness 不是单 case 现象。
不要先大规模铺 sweep。先选一个和 Qwen30B 机制不同的 case
- 一个 decode-heavy case观察 TP/DP redistribution 和 concurrency/memory intervention
- 或一个 long-prefill / tight-TTFT case观察 TP 和 prefill batching intervention。
预期图:
- x-axis: SLO tightness
- y-axis: best feasible req/s/GPU
- marker/color: selected intervention regime
- annotation: final TP/DP/MNS/MBT
- 展示 SLO 放宽时 frontier/right shift 或 regime transition。
优先级原因:重要,但应排在 planner-agnostic 和 mechanism ablation 之后。
### P5. SGLang / multi-engine adapter validation
目的:证明 intervention grammar 可以通过 adapter lowering 到不同 serving engine。
当前暂缓,不作为 vLLM 主线之前的高优先级实验。等 C1-C5 稳定后再做一个低成本 case
```text
same workload profile
same SLO objective
same intervention grammar
different engine adapter
```
优先级原因:它能扩展 generality但不能替代 vLLM 主线的机制证明。
## 暂不做
- 暂不把主 claim 写成“LLM 比 BO 更聪明”。新 claim 是 harness substrate 对多种 planner
都有用。
- 暂不 claim full white-box 或全局最优。当前更稳妥的是 grey-box、near-optimum、
fixed-budget utility。
- 暂不横向铺大量 SLO sweep。先补机制 ablation、planner-agnostic 和 near-optimum。
- 暂不把 multi-engine support 放进主实验 claim。先写成 adapter-based design等 vLLM
证据链完整后再补一个 SGLang validation。

View File

@@ -0,0 +1,99 @@
# Harness vs naive (use_harness on/off) — convergence ablation — 2026-06-16/17
Controlled ablation of the paper's "harness" (domain-knowledge knob-family steering):
the same agentic loop with `llm.use_harness=true` vs `false` (= the paper's naive
agentic tuner: free-form LLM proposals, no `Harnesses:` prompt section, no
deterministic guided proposals, no Stop-B validator/veto). Same workload, model, SLO,
substrate — the only difference is `use_harness` (configs
`dash0_qwen27b_ablation_harness_on.json` / `..._naive_off.json`, verified to differ
only in that flag + study_id).
- Model: dense Qwen3.5-27B, vLLM 0.11.1, 8×H20 (dash0 and dash1 share the cpfs mount).
- Workload: chat 08k, length-aware TTFT SLO (4s + L_in/8k) + TPOT ≤ 50 ms, pass ≥ 95%.
- Substrate (process comparison, not absolute peak-rate): `replay_time_scale=0.5`,
`completion_tokens_override=128`, Stop-A on, `search.high=0.25`, 6 probes, max-trials 6,
**`--skip-baseline`** (the low-capacity TP1 auto-baseline is infeasible under this
SLO+compression and would trip `baseline_all_infeasible`; skipping it lets both loops
climb from their first proposal).
- This measures the tuning *process* (which knob family, convergence speed, stop
discipline), not a validated peak-rate.
## Harness ON — converged in 2 iterations, then stopped
| iter | proposer | config | per_gpu | outcome |
| --- | --- | --- | --- | --- |
| 1 | LLM (harness-guided) | TP2 | 0.247 | feasible |
| 2 | harness (deterministic) | **TP4** | **0.340** | feasible — incumbent |
| 3 | harness | TP4 + chunked-prefill + mbt=16384 | 0.333 | worse → rejected |
| (—) | LLM | `should_stop` | — | **VETOED** ("decode TPOT still the bottleneck; adjacent probes weak") |
| 4 | LLM | TP2 + DP2 | 0.194 | worse → rejected |
| (—) | LLM | `should_stop` | **STOP** | honored after veto budget |
Best **TP4 @ 0.340**; iters-to-best = **2**; ran **4 trials then stopped** (Stop-B +
one veto of a premature stop); no regression.
## Naive OFF — nondeterministic; reaches the optimum slowly at best, fails at worst
The naive (free-form) `gpt-5.4` loop behaved very differently across two runs — it has
no harness steering and no stop logic:
**Run A (dash0, interrupted by an outage at trial-5):** kept **TP=1** the whole time and
cycled runtime knobs (`max-num-batched-tokens` 16k→65k, `max-num-seqs`, caching). All
trials **infeasible** (same `tpot>50` + `ttft>budget`), trial-4 **crashed the engine**
(OOM at mbt=65536). Found **no feasible config** in 5 trials — never tried raising TP.
**Run B (dash1, full budget):**
| iter | config | per_gpu | note |
| --- | --- | --- | --- |
| 1 | TP2 | 0.247 | feasible |
| 2 | TP2 + max-num-seqs=32 | 0.218 | worse |
| 3 | TP2 + mbt=12288 | 0.218 | worse |
| 4 | TP2 (re-proposal) | 0.218 | no gain |
| 5 | TP2 + gpu-mem-util=0.85 | 0.218 | worse |
| 6 | **TP4** | **0.340** | reaches the optimum — at the last trial |
Best **TP4 @ 0.340** — the *same* optimum as the harness — but iters-to-best = **6**,
it used the **entire budget with no early stop**, and trials 25 were detours (TP2 +
runtime tweaks, all worse than trial-1) before it stumbled onto TP4.
## Comparison
| | Harness ON | Naive OFF (B, dash1) | Naive OFF (A, dash0) |
| --- | --- | --- | --- |
| best per-GPU | 0.340 (TP4) | 0.340 (TP4) | none (failed) |
| iters-to-best | **2** | 6 | — |
| trials used | **4 (stopped)** | 6 (full budget, no stop) | 5 (interrupted) |
| stopped early? | yes (Stop-B + veto) | no | — |
| wasted trials | 2 (post-best refinements) | 4 (TP2+runtime detours) | 5 (runtime-only, infeasible) |
| path to optimum | direct (TP2→TP4) | slow (TP2→runtime detour→TP4) | wrong family (runtime on TP1) |
## Interpretation (honest)
The bottleneck is **compute** (decode TPOT + prefill queueing), which only a
compute-adding knob (**tensor parallelism**) fixes. Findings:
1. **A strong frontier model can sometimes find the right knob unaided** — naive run B
eventually reached TP4 = 0.34, the same optimum as the harness. This matches the
paper's own caveat (§7.3): stronger models reduce, but do not remove, the need for
structured guidance. So the harness's value is **not** "naive always fails."
2. **The harness's value is reliability, speed, and stop discipline.** With the harness:
converged in **2 iters** and **stopped at 4** (recognized convergence; vetoed a
premature stop). Naive: **3× slower** to the same answer (6 iters), **never stopped**
(burned the full budget on detours), and in run A **failed outright** — never tried
TP, found nothing, crashed the engine. Naive is **nondeterministic and unreliable**;
the harness is fast, monotone (no regression), and self-terminating.
3. This reproduces the paper's Figure-18 story: the harness converges in a few
iterations and stops, while the naive agentic tuner wastes the budget (and can fail
to converge entirely).
## Caveats
- Compressed substrate (scale=0.5, out=128) → per-GPU numbers are *process* comparators,
not validated peak-rates; the convergence behavior is the result. (The TP4 optimum
reproduced at 0.340 across the harness run and naive run B, a useful consistency check.)
- One run per arm per host; naive is nondeterministic (runs A and B differ markedly),
which is itself part of the finding. The harness arm's deterministic guided proposal
(TP4 at iter 2) and validator veto are reproducible.
- Infra notes: dash0 (LLM-gateway reachable) went down mid-experiment; dash1 shares the
cpfs and ran the completion. The codex `config.toml` points at a dash0-local proxy
(`127.0.0.1:11235`); on dash1 the LLM endpoint must be reached directly (set empty
`*_proxy` env) — see `scripts/run_naive_d1.sh`.

View File

@@ -0,0 +1,250 @@
# Profile-Driven Harness Design
## Problem
The current harness is too rule-like. It can help in a narrow setup, but it can also overfit to a previous observation such as "TP=2 is enough" and then fail when the SLO or workload changes. Adding one more rule for each failure mode is not acceptable.
The harness should make AITuner behave more like a performance engineer:
1. Profile the workload and engine behavior.
2. Diagnose the active bottleneck from measurements.
3. Form hypotheses about which knob families can relieve that bottleneck.
4. Choose the next experiment that best disambiguates or improves the system.
5. Interpret the result and update the diagnosis.
6. Stop only when the measured search space is exhausted or the remaining hypotheses have low value.
The harness should not encode a fixed answer for qwen27b, TP=4, TPOT25, or any testcase-specific threshold.
## Design Principles
### 1. Harness is an evidence system, not a prompt trick
The harness should provide structured evidence and a decision protocol. It should not simply add more natural-language hints to the prompt.
The output should include:
- `profile`: measured workload and engine traits.
- `bottleneck_diagnosis`: ranked bottleneck hypotheses with evidence.
- `candidate_actions`: candidate config changes with predicted effect and risk.
- `experiment_plan`: the next config to test, why it is informative, and what outcome would confirm/refute the hypothesis.
- `stop_decision`: only if no useful experiment remains under the current measurement budget.
### 2. Separate profiling from tuning
AITuner currently learns mostly from full trial results. That is too coarse. The harness needs a profile layer that extracts bottleneck signals from every probe:
- workload profile: input/output token distributions, cache reuse, burstiness, selected-request count per sampling threshold;
- latency profile: TTFT and TPOT mean/p50/p95/p99 by threshold and optionally by input/output token bucket;
- engine profile: prefill/decode throughput, waiting/running request counts, KV cache usage, preemptions, GPU utilization if available;
- topology profile: TP/DP/EP product, per-GPU request rate, launch stability, memory pressure;
- measurement profile: search high/low/tolerance, which thresholds were measured, whether failures were early-stop artifacts or true low-load infeasibility.
This profile should be persisted per trial so later decisions do not depend on prompt memory.
### 3. Diagnose bottlenecks from counters and slopes
A professional tuning loop should not infer bottlenecks from a single failed reason. It should compare how metrics change across thresholds and configs.
Useful diagnostics:
- TTFT-bound: TTFT p95/p99 rises sharply with request rate, long prompts dominate failures, prefill throughput is saturated, decode TPOT is still healthy.
- TPOT-bound: TPOT p95/p99 fails while TTFT is acceptable, decode throughput or per-token latency dominates, high sequence concurrency or batching creates token latency tails.
- Admission/queueing-bound: waiting requests and arrival lag grow, both TTFT and TPOT may degrade, utilization may be high but service time per request is not the only issue.
- Memory/KV-bound: KV cache usage, preemptions, OOM/launch failures, or max model length/cache pressure limit throughput.
- Topology/communication-bound: increasing TP lowers per-request latency but may reduce per-GPU throughput; DP improves admission but may not reduce per-request latency.
The diagnosis should be a ranked list with confidence, not a single label:
```json
{
"bottlenecks": [
{
"name": "decode_tpot",
"confidence": 0.62,
"evidence": ["tpot_p95 fails at last infeasible probe", "ttft_p95 remains below SLO"]
},
{
"name": "prefill_ttft",
"confidence": 0.31,
"evidence": ["long prompt tail", "ttft_p99 grows near knee"]
}
]
}
```
### 4. Candidate actions come from a knob-effect model
The harness should maintain a generic model of knob effects, not case-specific rules.
Examples:
| Knob family | Expected effect | Main risks |
|---|---|---|
| Increase TP | lower per-request compute latency, may help TTFT/TPOT tails | communication overhead, lower per-GPU efficiency, fewer independent replicas |
| Increase DP | more replicas, better admission/queueing | does not reduce per-request compute latency, may waste memory |
| Increase EP | MoE expert distribution, possible decode/prefill balance | launch complexity, communication, only relevant with model/engine evidence |
| Lower max-num-seqs | reduce decode contention and tail TPOT | underutilization, lower throughput |
| Raise max-num-seqs | improve admission/concurrency | TPOT/TTFT tails, memory pressure |
| Raise max-num-batched-tokens | improve prefill batching and GPU occupancy | long-prefill HoL blocking, memory pressure |
| Lower max-num-batched-tokens | reduce long-prefill interference | underfilled prefill batches |
| Change block-size | cache fragmentation/allocator effects | launch/runtime instability, workload-specific |
The harness should score candidates with:
```text
score = expected_bottleneck_relief
+ information_gain
+ launch_safety
- regression_risk
- measurement_cost
```
This lets it choose between "exploit current incumbent" and "explore unmeasured topology" from evidence, not hardcoded order.
### 5. Use hypothesis-driven experiments
Each trial should answer a specific question.
Bad:
- "Try TP=4 because previous run needed TP=4."
Good:
- "At the current best TP=2, the last infeasible probe is TPOT-bound. Increasing TP to 4 should reduce per-token compute latency but may reduce per-GPU efficiency. This trial tests whether the SLO is compute-latency limited or topology-overhead limited."
Every candidate should have:
- hypothesis;
- expected metric movement;
- risk;
- confirm condition;
- reject condition;
- fallback next family.
### 6. Stop is a decision under measured search limits
Early stop should not mean "we found something good." It should mean:
- the configured `search.high` is saturated by a feasible incumbent; or
- the remaining high-value hypotheses are already measured or invalidated; or
- all remaining candidates have low expected information gain or high launch risk relative to the current best; or
- the baseline is infeasible even at the lowest load and the SLO is too tight.
For Fig18 raw mode, stop can be disabled or recorded as `stop` rather than filling remaining columns with best-so-far.
## Proposed Architecture
### Components
1. `Profiler`
- Input: trial result, probe history, engine logs/metrics if available, study spec.
- Output: `TrialProfile`.
2. `BottleneckAnalyzer`
- Input: recent `TrialProfile`s, workload profile, SLO.
- Output: ranked bottleneck hypotheses.
3. `KnobEffectModel`
- Generic mapping from bottleneck hypotheses to possible knob families.
- Contains no model-specific or SLO-specific constants.
4. `ExperimentPlanner`
- Generates candidate config patches.
- Scores candidates by expected relief, information gain, risk, and cost.
- Emits the next experiment and rationale.
5. `StopPolicy`
- Uses measured search coverage and remaining candidate scores.
- Does not stop just because a strong incumbent appears.
6. `PromptRenderer`
- Renders the structured plan for the LLM when LLM involvement is needed.
- The LLM can choose among candidates or refine rationale, but should not invent arbitrary directions without evidence.
### Data Flow
```text
study spec + workload trace
|
v
workload profile
|
trial result/probes/logs --> trial profile
|
v
bottleneck analyzer --> ranked hypotheses
|
v
knob effect model --> candidate action set
|
v
experiment planner --> next config / stop
|
v
AITuner evaluates config over configured search range
```
## How This Handles the Current Failure
Current fixed TTFT4s + TPOT25 data:
- TP=2 reached `0.1992 req/s/GPU`.
- Later TP=2 local runtime probes did not improve.
- Min-prompt no-harness found TP=4 with `0.2696-0.2758 req/s/GPU`.
A profile-driven harness should have reasoned:
1. The current incumbent is good but not proof of optimum.
2. The failed probes still show SLO pressure, especially token-latency/tail behavior.
3. Higher TP is a candidate action because it may reduce per-request compute latency.
4. TP=4 is an unmeasured topology hypothesis within allowed constraints.
5. Before spending many local TP=2 runtime trials or stopping, test TP=4 as a high-information experiment.
This is not because TP=4 is hardcoded. If TP=4 had already been measured and underperformed, the planner would prefer local runtime refinement or stop. If the bottleneck were admission/queueing with TTFT/TPOT healthy, the planner might prefer DP or max-num-seqs instead.
## Implementation Plan
### Phase 1: Make the current harness evidence-first
- Persist a compact `TrialProfile` for every trial.
- Build ranked bottleneck hypotheses from probe sequences.
- Replace single `active_bottleneck` with ranked hypotheses and evidence.
- Replace deterministic local refinement with candidate scoring.
- Keep existing rules only as entries in the generic `KnobEffectModel`.
### Phase 2: Candidate scoring
- Generate candidates from:
- adjacent topology moves;
- same-topology runtime moves;
- rollback/avoidance after failures;
- no-op stop.
- Score candidates by:
- whether they directly target top bottleneck;
- whether they test an unmeasured high-information direction;
- launch risk from prior failures;
- expected impact on request_rate_per_gpu;
- measurement cost.
### Phase 3: Controlled experiments
- Add ablations:
- old strong-prompt no-harness;
- min-prompt no-harness;
- current rule harness;
- profile-driven harness.
- Run each under the same SLO/workload/search.
- Report raw `perf[i]`, best-so-far, failed configs, and stop reason.
## Acceptance Criteria
The profile-driven harness is acceptable only if:
1. It does not encode model names, fixed SLO values, or known winning configs.
2. It can explain every proposal as a hypothesis tied to measured bottleneck evidence.
3. It does not early-stop while an unmeasured high-score candidate remains.
4. It improves or matches min-prompt no-harness convergence on at least the qwen27b TTFT4s/TPOT25 setup.
5. It does not regress the previous stepped TTFT/TPOT50 setup.
6. It records enough evidence for a human engineer to audit why each trial was chosen.

View File

@@ -0,0 +1,155 @@
# Profile-Driven Harness Implementation Log
Date: 2026-05-12
## Goal
The harness should accelerate AITuner as a general tuning system, not as a collection of case-specific rules. The current implementation moves the harness toward a performance-engineering loop:
1. extract a compact profile from each measured trial;
2. rank bottleneck hypotheses from workload and probe evidence;
3. generate generic candidate actions from a knob-effect model;
4. score candidates by expected bottleneck relief, information gain, launch safety, and regression risk;
5. block early stop while a high-value untested candidate remains.
This is intended to apply across qwen3.5-27b chat, qwen3-235b prefill-only, qwen3-235b decode-only, and different SLOs without encoding model names, SLO constants, or known winning configs.
## Code Changes
- `src/aituner/harness.py`
- Added `trial_profiles` to normalize trial topology, performance, probe failures, latency quantiles, and launch failure evidence.
- Added `bottleneck_hypotheses`, a ranked list instead of a single active bottleneck label.
- Added `candidate_actions`, generated from topology and runtime knob families.
- Added `experiment_plan`, which selects the next high-score candidate or declares stop readiness.
- Updated harness proposal generation to prefer the profile-driven next action before falling back to legacy deterministic proposal code.
- Updated harness stop logic so convergence/validation stop is blocked when the planner still has a high-value untested candidate.
- `tests/test_core_flow.py`
- Added coverage that a strong TP=2 incumbent with TTFT pressure still selects an unmeasured TP=4 topology candidate.
- Added coverage that decode-only TPOT pressure at max TP can prefer lowering `max-num-seqs` instead of blindly lowering TP.
## Current Scoring Model
The candidate score is intentionally generic:
```text
score = expected_bottleneck_relief * bottleneck_confidence
+ information_gain
+ launch_safety
- regression_risk
```
Examples:
- TTFT/prefill bottleneck: increasing TP and prefill batching candidates receive relief score.
- Decode TPOT bottleneck: increasing TP is useful if a higher legal TP exists; if already at high TP, lowering decode concurrency can become the higher-value candidate.
- Admission/queueing bottleneck: more DP or higher safe concurrency receives relief score.
The scores are not tied to qwen27b/qwen235b or a fixed TPOT/TTFT threshold. They are tied to the measured bottleneck class and legal tunable space.
## Verification
Local:
```bash
python3 -m compileall -q src tests
PYTHONPATH=src python3 -m unittest tests.test_core_flow
```
Result: `93` tests passed.
## Next Experiment
Run the same qwen3.5-27b chat 0-8k setup as the current ablation baseline:
- workload: chat, input length 0-8k
- SLO: TTFT p95 <= 4000ms, TPOT p95 <= 25ms, target pass rate 0.95
- search: full range, `inherit_incumbent_floor=false`
- budget: 12 total tuning iterations
- LLM model: `gpt-5.4`
- variant: harness enabled with profile-driven planner
The no-harness min-prompt baseline is already available and only needs to be reused for comparison unless the setup changes.
## Experiment Started
Started on `dash0` (`11.73.2.172`) at commit `17e9681`.
- tmux session: `qwen27b-profileplanner-harness-20260512`
- spec: `.aituner-tight/specs/dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-harness-profileplanner-20260512.json`
- study id: `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-harness-profileplanner-20260512`
- log: `.aituner-tight/logs/qwen27b-profileplanner-harness-20260512.log`
- status at launch check: `trial-0001` baseline is running under AITuner; no manual intervention in the tuning loop.
## V1 Early Stop
The first profile-planner run was stopped before accepting it as evidence. A read-only replay of its completed baseline probe history showed that the planner would choose `max-num-seqs=64` for iter2. That was a diagnosis bug:
- `slo_pass_rate_unrecoverable` is a binary-search early-stop summary, not a bottleneck class.
- The harness was counting that summary as an admission/queueing failure.
- Because this count dominated the real TTFT/TPOT failure counts, the planner selected a concurrency action instead of testing TP.
Fix commit: `e3ed775`.
The fix excludes `slo_pass_rate_unrecoverable` from the admission/queueing failure bucket. With the same baseline probe history, the planner now ranks `ttft_prefill` first and proposes `tensor-parallel-size=2` for iter2.
## V2 Experiment Started
Started on `dash0` (`11.73.2.172`) at commit `e3ed775`.
- tmux session: `qwen27b-profileplanner-v2-harness-20260512`
- spec: `.aituner-tight/specs/dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-harness-profileplanner-v2-20260512.json`
- study id: `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-harness-profileplanner-v2-20260512`
- log: `.aituner-tight/logs/qwen27b-profileplanner-v2-harness-20260512.log`
- monitor: read-only subagent `Wegener`
Acceptance for this run is based on end-to-end trial results, not unit tests. If the first four trials lag the min-prompt no-harness baseline (`0.0650`, `0.1992`, `0.2696`, then failed/NA), the run should be treated as a failed harness iteration and the harness should be optimized again.
## V2 Result And Failure
V2 was stopped early after four trials because it did not improve the no-harness baseline and made a preventable launch-risk proposal.
Raw `request_rate/GPU`:
| Variant | iter1 | iter2 | iter3 | iter4 |
| --- | ---: | ---: | ---: | --- |
| no-harness min-prompt | 0.0650 | 0.1992 | 0.2696 | 0.2696 |
| harness v2 | 0.0650 | 0.1992 | 0.2696 | failed |
Harness v2 did correctly diagnose the first bottleneck and proposed:
- iter2: `tensor-parallel-size=2`, raw `0.1992 req/s/GPU`;
- iter3: `tensor-parallel-size=4`, raw `0.2696 req/s/GPU`.
However, iter4 proposed `tensor-parallel-size=8` and failed at engine launch. The study's `hardware.gpu_count` is 8, but the launch environment sets `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7`, which exposes only 7 GPUs. Therefore TP=8 should not have been considered launch-safe.
This is a general harness bug: topology planning must use the effective visible GPU count from the execution profile, not just the nominal hardware count.
Fix:
- parse `engine.base_envs.CUDA_VISIBLE_DEVICES`;
- compute effective GPU count as `min(hardware.gpu_count, visible_device_count)`;
- filter topology candidates and adjacent TP frontier candidates by the effective GPU count.
## GPU Visibility Correction
On 2026-05-13 we corrected the intended experiment setup: `CUDA_VISIBLE_DEVICES` should be `0,1,2,3,4,5,6,7`, not the previous `0,1,2,4,5,6,7`.
This invalidates direct comparison between the old `gpu3skip` runs and new 8-GPU runs. The old v2 failure was real under the old visible-device profile, but it was not the intended 8-card H20 setup.
New comparable studies:
| Variant | Study ID | Status |
| --- | --- | --- |
| no-harness baseline | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` | completed |
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` | completed |
Both specs set:
- `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7`
- model endpoint: `gpt-5.4`
- workload: qwen3.5-27b chat 0-8k
- SLO: TTFT p95 <= 4000ms, TPOT p95 <= 25ms, target pass rate 0.95
- search: full range, `inherit_incumbent_floor=false`
Results: harness best `0.2696 req/s/GPU` (TP=4, MBT=7680) vs no-harness best `0.1233 req/s/GPU` (prefix-caching=false), a **+118.6%** improvement. Full analysis in `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md`.

View File

@@ -0,0 +1,138 @@
# Qwen235B prefill 2x2 progress - 2026-06-23
Snapshot: 2026-06-23 18:24 CST / 10:24 UTC.
本文整理当前 dash1/dash2/dash3 上的 Qwen235B prefill 2x2 实验进度。这个
case 仍在跑 strong-model arm因此本文是 progress report不是最终 aggregate
结论。
## 当前远端状态
| Host | 当前状态 | 说明 |
| --- | --- | --- |
| dash1 | running | `aituner-q235b-2x2-gpt55-20260623T010038Z` 仍在跑,当前是 `gpt-5.5 + naive` 的 trial-00048 张 H20 被 vLLM 占用。 |
| dash2 | idle | 没有 tmux/GPU 任务;最近完成的是 `qwen235b-prefill-jointprobe-harness-dash2-20260622T132010Z` harness-only 验证。 |
| dash3 | idle | 没有 tmux/GPU 任务;`gpt-5.4-mini` 2x2 arm 已完成并生成 report。 |
注意:三台机器共享 `/home/admin/cpfs/wjh/aituner/aituner`,所以 `.aituner`
`.aituner-reports` 在不同 dash 节点上看到的是同一批产物。
## 已完成gpt-5.4-mini 2x2 arm
Report:
```text
.aituner-reports/qwen235b-prefill-2x2-gpt54mini-dash3-20260623T010038Z/report.md
```
Aggregate:
| Arm | Kind | Trials | Final req/s/GPU | Final/ref | TTT | AUC | Failed | No feasible |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `harness` | harness | 8 | 0.3217 | 1.0000 | 3 | 0.9483 | 0 | 1 |
| `naive` | naive | 8 | - | - | - | 0.0000 | 2 | 8 |
Interpretation:
- `gpt-5.4-mini + harness` 找到了 `0.3217 req/s/GPU`,达到该 report 的
reference best。
- `gpt-5.4-mini + naive` 8 个 trials 都没有找到 feasible config其中 2 个是
engine launch failure。
- Report 中 `Harness-vs-naive pass/checks: 0/1` 是 aggregator 对
`best_naive_final_per_gpu = null` 的保守处理:因为 naive 没有 feasible best
final ratio 无法计算,所以 pass 记为 false。就实际 tuning 结果而言,这个 arm
是 harness dominates naive。
Harness trajectory:
| Trial | Patch | req/s/GPU | Pass rate | 说明 |
| ---: | --- | ---: | ---: | --- |
| 1 | `TP=8, DP=1` | 0.2879 | 0.9522 | 初始 topology 满足 SLO但未达到最终 best。 |
| 2 | `TP=8, max-num-seqs=96` | 0.2879 | 0.9537 | 单独调 `max-num-seqs` 无明显提升。 |
| 3 | `TP=8, max-num-batched-tokens=16384, max-num-seqs=96` | 0.3085 | 0.9568 | joint runtime probe 提升。 |
| 4 | `TP=8, max-num-seqs=144, max-num-batched-tokens=32768` | 0.2879 | 0.9530 | 过大的 batching/seq 组合回退。 |
| 5 | `TP=4, DP=2` | - | - | 无 feasible best说明 DP-heavy/mixed topology 不解决该 prefill path。 |
| 6 | `TP=8, max-num-seqs=96, max-num-batched-tokens=24576` | 0.2708 | 0.9523 | batching 进一步增大后回退。 |
| 7 | `TP=4, DP=1, max-num-seqs=96, max-num-batched-tokens=16384` | 0.2338 | 0.9590 | 少用 GPU 的 TP4/DP1 per-GPU 不占优。 |
| 8 | `TP=8, DP=1, max-num-seqs=128, max-num-batched-tokens=16384` | 0.3217 | 0.9508 | 当前 best。 |
这个结果说明:在 Qwen235B prefill case 上harness 的价值不只是 topology
选择,还包括在 TTFT/prefill 方向下做受约束的 runtime joint probe。最终 best 是
`TP=8, DP=1, max-num-seqs=128, max-num-batched-tokens=16384`
## 正在运行gpt-5.5 2x2 arm
Session:
```text
tmux: aituner-q235b-2x2-gpt55-20260623T010038Z
driver log: .aituner/qwen235b-prefill-2x2-gpt55-dash1-20260623T010038Z.driver.log
```
Driver timeline:
```text
harness clean pair start 2026-06-23T01:00:40+00:00
harness clean pair done 2026-06-23T08:21:13+00:00
naive clean pair start 2026-06-23T08:21:13+00:00
```
Harness side has completed all 8 trials:
| Trial | Patch | req/s/GPU | Pass rate |
| ---: | --- | ---: | ---: |
| 1 | `TP=8, DP=1` | 0.2879 | 0.9522 |
| 2 | `TP=8, max-num-seqs=96` | 0.2879 | 0.9530 |
| 3 | `TP=8, max-num-batched-tokens=16384, max-num-seqs=96` | 0.3085 | 0.9561 |
| 4 | `TP=8, max-num-batched-tokens=32768, max-num-seqs=144` | 0.2783 | 0.9543 |
| 5 | `TP=8, DP=1, max-num-batched-tokens=24576, max-num-seqs=96` | 0.2654 | 0.9513 |
| 6 | `TP=4, DP=2, max-num-batched-tokens=16384, max-num-seqs=96` | - | - |
| 7 | `TP=8, DP=1, max-num-batched-tokens=16384, max-num-seqs=80` | 0.3156 | 0.9505 |
| 8 | `TP=8, max-num-batched-tokens=32768, max-num-seqs=120` | 0.2879 | 0.9508 |
Current harness best: `trial-0007`, `0.3156 req/s/GPU`.
Naive side is still running. Current state:
- Completed/recorded through trial-0003, with current best `0.2879 req/s/GPU`.
- trial-0004 is active with `TP=8, DP=1, max-num-batched-tokens=8192,
max-num-seqs=128`.
- trial-0004 probe history so far:
| threshold | request rate | req/s/GPU | pass rate | feasible | main failures |
| ---: | ---: | ---: | ---: | --- | --- |
| 0.0625 | 1.5750 | 0.1969 | 0.9651 | true | TTFT misses and TTFT threshold violations |
| 0.09375 | 2.3650 | 0.2956 | 0.7308 | false | `slo_pass_rate_unrecoverable`, TTFT violations |
| 0.078125 | 1.9567 | 0.2446 | 0.9591 | true | TTFT misses and TTFT threshold violations |
| 0.0859375 | 2.1667 | 0.2708 | 0.9546 | true | TTFT misses and TTFT threshold violations |
As of the snapshot, vLLM is still processing requests for trial-0004, so the naive
side has not produced its final result or report yet.
## Prior Qwen235B context
These earlier runs explain why the current 2x2 matters:
| Run | Result | What it showed |
| --- | --- | --- |
| `qwen235b-prefill-clean-gpt55-dash1-20260621T160712Z` | harness 0.2879, naive 0.3217 | Earlier harness stopped/refined too weakly; naive found better final config. |
| `qwen235b-prefill-seqguard-gpt55-dash1-20260622T064445Z` | harness 0.2879, naive 0.2577 | Seq guard prevented the worst early-stop failure but still did not reach the old naive best. |
| `qwen235b-prefill-jointprobe-harness-dash2-20260622T132010Z` | harness-only 0.3085 | Joint `max-num-batched-tokens + max-num-seqs` probe improved over seqguard. |
| `qwen235b-prefill-2x2-gpt54mini-dash3-20260623T010038Z` | harness 0.3217, naive no feasible | Weak model plus harness now reaches the old best and dominates weak naive. |
The current evidence points to the harness needing both:
1. topology discipline: stay on `TP=8, DP=1` for this prefill-heavy 235B setup;
2. runtime joint probing: tune `max-num-batched-tokens` and `max-num-seqs` together
instead of stopping after the first feasible TP8 result.
## Open item
The final Qwen235B 2x2 conclusion is blocked on the still-running
`gpt-5.5 + naive` arm on dash1. Once it completes, generate an aggregate report
combining:
- `qwen235b-prefill-2x2-gpt55-dash1-20260623T010038Z`
- `qwen235b-prefill-2x2-gpt54mini-dash3-20260623T010038Z`
and then update this progress report into a final ablation report.

View File

@@ -0,0 +1,89 @@
# qwen235b Thinking Prefill Harness Ablation, 2026-05-10
**Superseded** by `qwen235b-thinking-prefill-ttft-3s6s9s-20260514.md` (updated SLO thresholds, 8-GPU setup). This document is retained for reference only.
## Setup
- Host: `dash0`
- Engine: internal vLLM at `/usr/local/bin/vllm`
- Model: `/home/admin/resource/model/464482ce.qwen3-235b-a22b/256k-0717`
- Trace window: `thinking_w20260327_1000`
- Request mode: chat, with `completion_tokens_override=1` for prefill-only measurement
- SLO: TTFT-only stepped p95 pass target, target pass rate `0.95`
- input tokens `<=4096`: `3000 ms`
- input tokens `<=32768`: `6000 ms`
- otherwise: `9000 ms`
- Search: `sampling_u` in `[0, 0.125]`, tolerance `0.001`, max probes `6`
- Trial budget: no-harness allowed 12 GPU trials; harness allowed 12 but could stop early
- Store root: `.aituner-prefill`
The two fresh specs were identical except `study_id` and `llm.use_harness`:
- no-harness: `.aituner-prefill/specs/dash0-qwen235b-prefill-thinking-run1-ttft-harness-ablation-12iter-noharness-rerun2-20260510.json`
- harness: `.aituner-prefill/specs/dash0-qwen235b-prefill-thinking-run1-ttft-harness-ablation-12iter-harness-rerun2-20260510.json`
Both runs were launched through `python3 -m aituner.cli study tune`; no proposal or study state was edited manually during tuning.
## Result
The table below is the raw per-iteration performance for a Fig18-style plot. Use this table as `perf[i]`; do not replace missing points with `max(perf[:i+1])`.
Metric: `best_request_rate_per_gpu` from that trial's own `result.json`. `NA` means the proposed config did not produce a feasible point in the measured search range, either because the engine/probe failed or because every sampled probe was infeasible.
Important caveat: these runs were produced before the lower-range fallback fix. For same-parallel-size runtime patches, AITuner inherited the incumbent `sampling_u` as the new search floor. If the config was infeasible above that floor, the old worker wrote `NA` without searching below the floor. Therefore the `NA` entries below are not complete Fig18-quality raw performance points; they are "no feasible point above inherited floor." A rerun with the fixed worker is required to fill their true lower-load performance.
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness raw `perf[i]` | 0.2029 | NA | NA | 0.3863 | NA | NA | NA | 0.3879 | 0.3892 | 0.3896 | 0.3900 | 0.3900 |
| harness raw `perf[i]` | 0.2029 | NA | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop |
The raw no-harness curve is therefore not monotonic. The apparent monotonic 12-iter sequence comes only from plotting best-so-far rather than the measured performance of each proposal.
Per-trial details:
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness, per-trial | 0.2029 | - | - | 0.3863 | - | - | - | 0.3879 | 0.3892 | 0.3896 | 0.3900 | 0.3900 |
| harness, per-trial | 0.2029 | - | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop |
Best-so-far curve, shown only to explain final incumbent selection:
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness | 0.2029 | 0.2029 | 0.2029 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3879 | 0.3892 | 0.3896 | 0.3900 | 0.3900 |
| harness | 0.2029 | 0.2029 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 | 0.3863 |
For plotting raw `perf[i]`, the failed/infeasible points should stay missing or be rendered as invalid trials. If a plotting script requires numeric values, use `0` only with an explicit label that this means "no feasible configuration under the configured SLO"; do not forward-fill from the incumbent.
Final best:
| Variant | GPU trials spent | Best trial | Best config summary | Best req/s | Best req/s/GPU | Final vs no-harness |
| --- | ---: | --- | --- | ---: | ---: | ---: |
| no-harness | 12 | `trial-0011`/`trial-0012` | TP8, DP1, EP off, `max-num-batched-tokens` 7936/8064 | 3.1200 | 0.3900 | baseline |
| harness | 3 | `trial-0003` | TP8, DP1, EP off | 3.0900 | 0.3863 | -0.96% |
Harness reached `0.38625 req/s/GPU` at iter3. No-harness first reached the same TP8 family at iter4, then spent eight more GPU trials to move from `0.38625` to `0.39000 req/s/GPU`, an absolute gain of `0.00375 req/s/GPU` or `0.97%`.
## What the Harness Did
The harness did not use a testcase-specific throughput threshold. The stop decision came from the generic search-high saturation rule:
- incumbent highest feasible probe: `sampling_u=0.123046875`
- configured `search.high`: `0.125`
- binary-search resolution: `(0.125 - 0.0) / 2^6 = 0.001953125`
- gap to search high: `0.001953125`
Because the incumbent was feasible and within one configured search resolution of `search.high`, the harness emitted `harness-stop-0004` before launching another GPU trial. This means the current study could no longer measure a materially higher workload without increasing `search.high`; it is not a claim of global engine optimality.
The harness context also made the LLM response more directed after failure:
- After baseline, it exposed the TTFT-only prefill bottleneck and the sharp queueing knee around `sampling_u=0.03515625`.
- The LLM first chose TP4/DP2 to use the idle 4 GPUs while preserving the validated TP4 shard shape. This failed with `connection refused`, matching the no-harness failure family.
- The next harness prompt included that failure, and the LLM switched to TP8/DP1 with EP off, explicitly avoiding the failed DP2 family.
- No-harness inserted an extra EP4 launch-failure trial before reaching TP8/DP1.
## Conclusion
Harness accelerated convergence mainly through early stopping, not by finding a much better final config on this setup. It reduced GPU trials from 12 to 3 while preserving 99.0% of the no-harness final throughput. It also reached the first strong TP8 point one trial earlier than no-harness.
The limitation is that the generic search-high stop guard stopped before local runtime tuning of `max-num-batched-tokens`, which no-harness used to recover a small additional `0.97%`. For this setup, that tradeoff is acceptable if the goal is fast convergence under a fixed measurement ceiling; if the goal is exact final throughput, the next study should raise `search.high` or disable search-high early stop for a local-polish phase.

View File

@@ -0,0 +1,117 @@
# qwen235b Thinking Prefill Harness Ablation (TTFT 3s/6s/9s)
Date: 2026-05-14 / 2026-05-15
Supersedes: `qwen235b-thinking-prefill-ttft-20260510.md` (different SLO thresholds).
## Setup
- Host: `dash0`
- Engine: internal vLLM at `/usr/local/bin/vllm`
- Model: `/home/admin/resource/model/464482ce.qwen3-235b-a22b/256k-0717`
- Trace window: `thinking_w20260327_1000`
- Request mode: chat, with `completion_tokens_override=1` for prefill-only measurement
- SLO: TTFT-only stepped p95 pass target, target pass rate `0.95`
- input tokens `<=4096`: `3000 ms`
- input tokens `<=32768`: `6000 ms`
- otherwise: `9000 ms`
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
- Baseline topology: `TP=4`
- LLM: `gpt-5.4`
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
## Studies
| Variant | Study ID | search.high |
| --- | --- | ---: |
| no-harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-noharness-minprompt-gpt54-20260514` | 0.125 |
| harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-harness-profileplanner-gpt54-20260514` | 0.125 |
| harness (high=0.25) | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-high025-12iter-harness-profileplanner-gpt54-20260515` | 0.25 |
The `harness (high=0.25)` run was added to test whether raising `search.high` lets the harness find a better runtime config after reaching the search ceiling at `0.125`.
## Result
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure. `stop` means harness stopped before launching another trial.
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness raw `perf[i]` | 0.1804 | fail | 0.1892 | fail | 0.1892 | 0.1804 | 0.2217 | 0.2029 | 0.2029 | 0.2029 | 0.1892 | 0.1804 |
| harness raw `perf[i]` | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
| harness (high=0.25) raw `perf[i]` | 0.2029 | 0.3921 | 0.3442 | 0.3921 | 0.3821 | 0.3821 | 0.3821 | 0.3688 | 0.3821 | 0.3821 | 0.3821 | 0.3821 |
| Variant | GPU trials | Best iter | Best req/s | Best req/s/GPU | Best config summary |
| --- | ---: | ---: | ---: | ---: | --- |
| no-harness | 12 | 7 | 0.8867 | 0.2217 | TP=4, MNS=112, MBT=7168 |
| harness | 2 (stop) | 2 | 3.0900 | 0.3863 | TP=8 |
| harness (high=0.25) | 12 | 2 | 3.1367 | **0.3921** | TP=8 |
Harness reached **+74.2%** over no-harness at iter 2. With `search.high=0.25`, the harness found `0.3921 req/s/GPU` (+76.8%).
## Incumbent Curve
Best-so-far request rate per GPU after each iteration.
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness | 0.1804 | 0.1804 | 0.1892 | 0.1892 | 0.1892 | 0.1892 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 |
| harness | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
| harness (high=0.25) | 0.2029 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 |
## Trial Details
No-harness:
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.1804 | 0.1804 | completed | baseline (TP=4) |
| 2 | - | 0.1804 | launch fail | TP=4, EP=4, MNS=128 |
| 3 | 0.1892 | 0.1892 | completed | MNS=96 |
| 4 | - | 0.1892 | launch fail | TP=4, DP=2, EP off, MNS=96 |
| 5 | 0.1892 | 0.1892 | completed | MNS=112 |
| 6 | 0.1804 | 0.1892 | completed | MNS=112, MBT=9216 |
| 7 | 0.2217 | 0.2217 | completed | MNS=112, MBT=7168 |
| 8 | 0.2029 | 0.2217 | completed | MNS=112, MBT=6144 |
| 9 | 0.2029 | 0.2217 | completed | MNS=120, MBT=7168 |
| 10 | 0.2029 | 0.2217 | completed | TP=4, DP=1, EP off, MNS=108, MBT=7168 |
| 11 | 0.1892 | 0.2217 | completed | MNS=112, MBT=7680 |
| 12 | 0.1804 | 0.2217 | completed | MNS=112, MBT=6912 |
Harness (`search.high=0.125`):
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
| 2 | 0.3863 | 0.3863 | completed | TP=8 |
| 3 | - | - | harness stop | search-high saturation (`sampling_u=0.123` vs `search.high=0.125`) |
Harness (`search.high=0.25`):
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
| 2 | 0.3921 | 0.3921 | completed | TP=8 |
| 3 | 0.3442 | 0.3921 | completed | TP=8, chunked-prefill, MBT=32768 |
| 4 | 0.3921 | 0.3921 | completed | TP=8, MBT=12288 |
| 5 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=16384 |
| 6 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=14336 |
| 7 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=10240 |
| 8 | 0.3688 | 0.3921 | completed | TP=8, EP off, MBT=11776 |
| 9 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=13312 |
| 10 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=7168 |
| 11 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12032 |
| 12 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12800 |
## Interpretation
No-harness never attempted TP=8. It stayed on the TP=4 baseline, encountered two launch failures (EP=4 and DP=2), and spent all remaining trials on runtime knob tuning within the TP=4 family. Its best finding was `MNS=112, MBT=7168` at iter 7 (`0.2217 req/s/GPU`).
Harness identified `ttft_prefill` as the dominant bottleneck from the baseline trial and immediately proposed TP=8 as the first topology move. This is the correct direction for a prefill-only workload with heavy-tail prompts (p95 ~19.7k tokens, p99 ~30k tokens).
With `search.high=0.125`, the harness stopped at iter 2 because the incumbent's best feasible `sampling_u=0.123` was within one search resolution of `search.high`. With `search.high=0.25`, the harness continued for 12 trials but the best remained iter 2 (`TP=8, default MBT`). The additional 10 trials explored MBT variations on TP=8 but none improved per-GPU throughput. This confirms the 2-trial harness result was already at or near the local optimum.
The gap between harness and no-harness (`+76.8%`) comes entirely from topology: TP=8 doubles the per-GPU prefill compute bandwidth compared to TP=4, which directly reduces TTFT and allows higher admitted request rates under the stepped TTFT SLO.
## Comparison with Previous Run (2026-05-10)
The 2026-05-10 run used different SLO thresholds and is documented in `qwen235b-thinking-prefill-ttft-20260510.md`. The core finding is consistent: harness finds TP=8 at iter 2-3 while no-harness gets stuck on TP=4 runtime tuning.

View File

@@ -0,0 +1,103 @@
# Qwen27B Chat 0-8k Harness Ablation
Date: 2026-05-10
**Superseded** by `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md` (corrected 8-GPU setup). This document used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` (7 GPUs) and is retained for reference only.
## Setup
- Host: `dash0` (`172.27.114.84`)
- Model: `/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`
- Workload: chat, 0-8k input window
- SLO: TTFT <= 4000ms and TPOT <= 25ms, target pass rate = 0.95
- Trial budget: 12 total tuning iterations per study
- Execution: direct `python3 -m aituner.cli study tune ... --max-trials 12`
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7`
- Code commit: `adc4351`
The previous no-harness run was affected by the `dash0` migration and had many engine launch failures. This document uses the clean no-harness rerun from 2026-05-09.
## Studies
| Variant | Study ID |
| --- | --- |
| no-harness rerun | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-noharness-rerun-20260509` |
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu3skip-12iter-harness-20260508` |
## Result
The table below is the raw per-iteration performance for a Fig18-style plot. Use this table as `perf[i]`; do not replace missing points with `max(perf[:i+1])`.
Metric: `best_request_rate_per_gpu` from that trial's own `result.json`. `NA` means the proposed config did not produce a feasible point in the measured search range. `stop` means the harness stopped before launching another GPU trial.
Important caveat: these runs were produced before the lower-range fallback fix. For same-parallel-size runtime patches, AITuner inherited the incumbent `sampling_u` as the new search floor. If the config was infeasible above that floor, the old worker wrote `NA` without searching below the floor. Therefore the `NA` entries below are not complete Fig18-quality raw performance points; they are "no feasible point above inherited floor." A rerun with the fixed worker is required to fill their true lower-load performance.
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness raw `perf[i]` | 0.0650 | 0.0617 | 0.0308 | NA | NA | NA | NA | NA | NA | 0.2025 | NA | NA |
| harness raw `perf[i]` | 0.0650 | 0.0617 | 0.2025 | NA | 0.1283 | NA | 0.2696 | 0.2742 | NA | NA | NA | stop |
The raw no-harness curve is not monotonic: iter2 and iter3 are worse than the baseline, and iter4-9 do not produce feasible configs. The monotonic curve below is best-so-far/incumbent tracking, not the measured performance of each proposal.
| Variant | Best iter | Best request rate | Best request rate / GPU | Best config summary |
| --- | ---: | ---: | ---: | --- |
| no-harness rerun | 10 | 0.4050 | 0.2025 | `tensor-parallel-size=2`, `data-parallel-size=1`, `max-num-batched-tokens=12288` |
| harness | 8 | 1.0967 | 0.2742 | `tensor-parallel-size=4`, `enable-chunked-prefill=true`, `max-num-batched-tokens=16384` |
Harness reached a higher incumbent and did so earlier. Final best request rate per GPU improved by about `35.4%` over the clean no-harness rerun.
## Incumbent Curve
Values are incumbent best request rate per GPU after each tuning iteration. This table is useful for explaining final best selection, but it should not be used as Fig18 raw `perf[i]`.
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness rerun | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.2025 | 0.2025 | 0.2025 |
| harness | 0.0650 | 0.0650 | 0.2025 | 0.2025 | 0.2025 | 0.2025 | 0.2696 | 0.2742 | 0.2742 | 0.2742 | 0.2742 | stop |
For plotting raw `perf[i]`, keep `NA` points missing or render them as invalid trials. If a plotting script requires numeric values, use `0` only with an explicit label that this means "no feasible configuration under the configured SLO"; do not forward-fill from the incumbent.
## Trial Details
No-harness rerun:
| Iter | Trial result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.0650 | 0.0650 | completed | baseline |
| 2 | 0.0617 | 0.0650 | completed | `tp=1`, `dp=2`, `max-num-batched-tokens=12288` |
| 3 | 0.0308 | 0.0650 | completed | `tp=1`, `dp=4` |
| 4 | - | 0.0650 | completed, infeasible | `max-num-batched-tokens=12288` |
| 5 | - | 0.0650 | completed, infeasible | `tp=1`, `dp=2`, `max-num-batched-tokens=16384` |
| 6 | - | 0.0650 | completed, infeasible | `tp=1`, `dp=2`, `max-num-batched-tokens=12288`, `block-size=32` |
| 7 | - | 0.0650 | completed, infeasible | `max-num-batched-tokens=10240` |
| 8 | - | 0.0650 | completed, infeasible | `max-num-batched-tokens=7168` |
| 9 | - | 0.0650 | completed, infeasible | `tp=1`, `dp=2` |
| 10 | 0.2025 | 0.2025 | completed | `tp=2`, `dp=1`, `max-num-batched-tokens=12288` |
| 11 | - | 0.2025 | completed, infeasible | `tp=2`, `dp=1`, `max-num-batched-tokens=10240` |
| 12 | - | 0.2025 | completed, infeasible | `tp=2`, `dp=1`, `max-num-batched-tokens=13312` |
Harness:
| Iter | Trial result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.0650 | 0.0650 | completed | baseline |
| 2 | 0.0617 | 0.0650 | completed | `tp=1`, `dp=2` |
| 3 | 0.2025 | 0.2025 | completed | `tp=2`, `dp=1` |
| 4 | - | 0.2025 | completed, infeasible | `tp=2`, chunked prefill, `max-num-batched-tokens=16384` |
| 5 | 0.1283 | 0.2025 | completed | `tp=2`, `dp=2` |
| 6 | - | 0.2025 | completed, infeasible | `tp=2`, `dp=1`, `max-num-seqs=4` |
| 7 | 0.2696 | 0.2696 | completed | `tp=4`, `dp=1` |
| 8 | 0.2742 | 0.2742 | completed | `tp=4`, chunked prefill, `max-num-batched-tokens=16384` |
| 9 | - | 0.2742 | completed, infeasible | `tp=4`, chunked prefill, `max-num-batched-tokens=24576` |
| 10 | - | 0.2742 | completed, infeasible | `tp=4`, chunked prefill, `max-num-batched-tokens=16384`, `max-num-seqs=8` |
| 11 | - | 0.2742 | completed, infeasible | `tp=4`, chunked prefill, `max-num-batched-tokens=16384`, `max-num-seqs=16` |
| 12 | - | 0.2742 | harness stop | validation exhausted after strong incumbent |
## Interpretation
The clean no-harness rerun eventually found the `tp=2` topology at iter 10, so the old migration-tainted no-harness result was indeed too pessimistic. Harness still improves the process in two ways:
- It reaches the `tp=2` topology by iter 3 instead of iter 10.
- It then escalates to `tp=4` and a nearby batching refinement, reaching `0.2742 req/s/GPU`.
The harness effect is not "one iter to best"; it is directional search. It turns bottleneck evidence into topology validation probes, then validates runtime refinements around the stronger incumbent and stops when further nearby probes do not improve.

View File

@@ -0,0 +1,99 @@
# Qwen27B Chat 0-8k Harness Ablation (8-GPU)
Date: 2026-05-13
Supersedes: `qwen27b-chat-0-8k-ttft4s-tpot25-20260510.md` (7-GPU / gpu3skip setup).
## Setup
- Host: `dash0`
- Model: `/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`
- Workload: `chat_w20260311_1000`, chat, 0-8k input window
- SLO: TTFT <= 4000ms and TPOT <= 25ms, target pass rate = 0.95
- Trial budget: 12 total tuning iterations per study
- Search: `sampling_u` in `[0, 0.0625]`, tolerance `0.001`, max probes `6`
- Execution: `python3 -m aituner.cli study tune ... --max-trials 12`
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
- Baseline topology: `TP=1`
- LLM: `gpt-5.4`
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
## Studies
| Variant | Study ID |
| --- | --- |
| no-harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` |
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` |
## Result
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu` from that trial's own `result.json`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure.
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness raw `perf[i]` | 0.0650 | fail | fail | 0.0617 | 0.0650 | 0.1233 | 0.1050 | 0.1233 | 0.0650 | 0.0650 | 0.0617 | 0.1233 |
| harness raw `perf[i]` | 0.0650 | 0.1992 | 0.2621 | 0.2056 | 0.1544 | 0.2696 | 0.2621 | 0.2621 | 0.2696 | 0.2621 | 0.2621 | 0.2621 |
| Variant | Best iter | Best request rate | Best request rate / GPU | Best config summary |
| --- | ---: | ---: | ---: | --- |
| no-harness | 6 | 0.1233 | 0.1233 | `enable-prefix-caching=false` |
| harness | 6 | 1.0783 | **0.2696** | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
Harness final best is **+118.6%** over no-harness.
## Incumbent Curve
Best-so-far request rate per GPU after each iteration.
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 |
| harness | 0.0650 | 0.1992 | 0.2621 | 0.2621 | 0.2621 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 |
## Trial Details
No-harness:
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.0650 | 0.0650 | completed | baseline |
| 2 | - | 0.0650 | launch fail | `gpu-memory-utilization=0.94`, `max-num-batched-tokens=16384` |
| 3 | - | 0.0650 | launch fail | `enable-chunked-prefill=false` |
| 4 | 0.0617 | 0.0650 | completed | `data-parallel-size=2` |
| 5 | 0.0650 | 0.0650 | completed | `block-size=32` |
| 6 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` |
| 7 | 0.1050 | 0.1233 | completed | `enable-prefix-caching=false`, `block-size=32` |
| 8 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=32` |
| 9 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-batched-tokens=4096` |
| 10 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=16` |
| 11 | 0.0617 | 0.1233 | completed | `data-parallel-size=2`, `enable-prefix-caching=false` |
| 12 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` (+ torch compile off) |
Harness:
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
| ---: | ---: | ---: | --- | --- |
| 1 | 0.0650 | 0.0650 | completed | baseline |
| 2 | 0.1992 | 0.1992 | completed | `tensor-parallel-size=2` |
| 3 | 0.2621 | 0.2621 | completed | `tensor-parallel-size=4` |
| 4 | 0.2056 | 0.2621 | completed | `tensor-parallel-size=8` |
| 5 | 0.1544 | 0.2621 | completed | `tensor-parallel-size=4`, `data-parallel-size=2` |
| 6 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
| 7 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `enable-chunked-prefill=true`, `max-num-batched-tokens=12288` |
| 8 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7424` |
| 9 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=64` |
| 10 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=56` |
| 11 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=60` |
| 12 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=63` |
## Interpretation
No-harness never tested any TP change in 12 trials. It started from TP=1, encountered two early launch failures, then spent all remaining budget on runtime knobs (`enable-prefix-caching`, `block-size`, `max-num-seqs`, `max-num-batched-tokens`). Its best discovery was disabling prefix caching at iter 6, reaching only `0.1233 req/s/GPU`.
Harness systematically explored the TP frontier: iter 2 tested TP=2, iter 3 tested TP=4, iter 4 tested TP=8. The profile-driven planner identified `ttft_prefill` as the ranked bottleneck and proposed increasing TP as the primary relief action. After TP=4 proved best per-GPU, the harness tested TP=4/DP=2 (worse) then shifted to runtime refinement within the TP=4 family, settling on `max-num-batched-tokens=7680` as the marginal improvement.
The result demonstrates that topology exploration is critical for this workload: the no-harness LLM failed to discover TP>1 configurations entirely, while the harness reached the optimal TP=4 topology by iter 3 and refined it by iter 6.
## Comparison with Previous 7-GPU Run
The 7-GPU (`gpu3skip`) run from 2026-05-10 used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` and is not directly comparable. The harness result on 7-GPU was `0.2742 req/s/GPU` (TP=4, chunked-prefill, MBT=16384). On 8-GPU, the harness found a similar TP=4 optimum at `0.2696 req/s/GPU` with slightly different runtime tuning. The core finding is consistent: harness accelerates topology discovery and significantly outperforms no-harness.

View File

@@ -0,0 +1,366 @@
# Qwen27B tight-SLO 2x2 harness ablation - 2026-06-23
本文整理以下 aggregate report并解释 harness 为什么能够让 tuning 更快、更有效:
```text
.aituner-reports/qwen27b-tight-2x2-aggregate-20260623T005838Z/report.md
```
这个实验是一个 2x2 ablation模型强弱和是否启用 `use_harness` 交叉。
核心问题是harness 是否提供了可复用的搜索结构,而不仅仅是更强 LLM
或者更长 prompt 带来的偶然收益。
## 实验设计
Case: `qwen27b-tight-slo-2x2-aggregate`
实验基座:
- Served model: `qwen3.5-27b-256k-0223-internal`
- Hardware: H20最多 8 GPUs。
- Trace: `chat_w20260311_1000`,输入长度过滤到 0-8192 tokens
`replay_time_scale=1.0``max_concurrency=32`
- SLO: pass rate >= 0.95TTFT step rule 为 <=4096 input tokens 时 2s
<=32768 input tokens 时 4s更长输入时 6sTPOT <= 50 ms。
- Search: 在 `sampling_u in [0, 0.0625]` 上二分探测tolerance 0.001
max 6 probes。
- Tunable envs: `VLLM_ENABLE_TORCH_COMPILE`
- Tunable flags: `tensor-parallel-size`, `data-parallel-size`,
`expert-parallel-size`, `gpu-memory-utilization`, `block-size`,
`max-num-batched-tokens`, `max-num-seqs`, `enable-prefix-caching`,
`enable-chunked-prefill`
- Topology constraints: TP 和 DP 均在 `{1,2,4,8}` 中,允许的 TP*DP product 为
`{1,2,4,8}`,本 case 中 EP 固定为 1。
2x2 arms:
| Arm | Tuner model | Harness | Trial budget used |
| --- | --- | --- | ---: |
| `gpt55_harness` | `gpt-5.5` | on | 2 |
| `gpt55_naive` | `gpt-5.5` | off | 10 |
| `gpt54mini_harness` | `gpt-5.4-mini` | on | 2 |
| `gpt54mini_naive` | `gpt-5.4-mini` | off | 10 |
同一个 tuner model 内,主要差异是 `use_harness`。跨模型比较则用来判断:
更弱模型加 harness 是否能匹配或超过更强模型的 naive tuning。
## Aggregate result
Reference best: `0.4429 req/s/GPU`
Convergence target: reference 的 95%,即 `0.4208 req/s/GPU`
| Arm | Kind | Trials | Final req/s/GPU | Final/ref | Trials to target | Normalized AUC | Failed | No feasible |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `gpt55_harness` | harness | 2 | 0.4429 | 1.0000 | 2 | 0.9484 | 0 | 0 |
| `gpt55_naive` | naive | 10 | 0.0273 | 0.0616 | - | 0.0588 | 2 | 2 |
| `gpt54mini_harness` | harness | 2 | 0.4429 | 1.0000 | 2 | 0.9450 | 0 | 0 |
| `gpt54mini_naive` | naive | 10 | 0.0231 | 0.0522 | - | 0.0498 | 1 | 1 |
Harness-vs-naive 检查全部通过:
| Harness arm | Final vs best naive | AUC vs best naive | Pass |
| --- | ---: | ---: | --- |
| `gpt55_harness` | 16.2290x | 16.1296x | true |
| `gpt54mini_harness` | 16.2290x | 16.0720x | true |
最关键的 ablation 信号是:`gpt-5.4-mini + harness`
`gpt-5.5 + harness` 达到同一个 final throughput也都是 2 trials 达到 target
而两个 naive arms 用满 10 trials 后仍低于 harness arms 16x 以上。
## Agent loop 流程图
下面是当前 harness 化 agent loop 的抽象流程。LLM 仍然可以参与 proposal
但它拿到的不是裸文本历史,而是结构化 observation、bottleneck diagnosis、
candidate actions 和 validator 约束;同时 validator 可以授权 stop也可以阻止
重复失败或不合法配置。
```mermaid
flowchart TD
A[Study spec: trace, SLO, search range, tunable knobs] --> B[Run one engine config]
B --> C[Binary-search probes over sampling_u]
C --> D[Build observation o_t]
D --> E[Bottleneck classifier]
E --> F[Candidate family generator]
F --> G[Score candidate actions]
G --> H[Prompt renderer / planner]
H --> I[LLM or deterministic harness proposal]
I --> J{Config validator}
J -- invalid, repeated, unsafe --> F
J -- valid config_patch --> B
G --> K{Stop validator}
K -- search_high_saturated_by_incumbent --> L[Stop and keep incumbent]
K -- useful candidates remain --> H
```
这个 loop 中harness 的作用不是把 prompt 写得更漂亮,而是把 tuning 变成
一个受测量约束的决策过程:
```text
measurement -> diagnosis -> candidate family -> scored action -> validated proposal/stop
```
## 形式化设计observation
每个 trial 结束后AITuner 不只记录一段自然语言总结,而是形成结构化 observation
```text
o_t = (
config_t,
probe_history_t,
pass_rate_t,
latency/SLO_failure_profile_t,
request_rate_t,
parallel_size_t,
launch_status_t,
prior_failures_t,
incumbent_t
)
```
本实验里 observation 中最重要的字段是:
- `config_t`: 当前 trial 的 `flag_patch``env_patch`,例如 `TP=2, DP=1`
- `probe_history_t`: 在不同 `sampling_u` 下二分探测得到的 feasible/infeasible
结果。
- `pass_rate_t`: 是否满足 target pass rate 0.95。
- `latency/SLO_failure_profile_t`: TTFT 和 TPOT 哪个先触发 SLO pressure。
- `request_rate_t`: 当前配置在 SLO 下能承载的 request rate。
- `parallel_size_t`: 该配置实际使用的并行规模,用于归一化 per-GPU objective。
- `prior_failures_t`: 之前哪些配置 launch failed 或 no feasible避免重复试错。
- `incumbent_t`: 当前最优配置及其 `request_rate_per_gpu`
目标函数是:
```text
J(config_t) = request_rate_t / parallel_size_t
subject to pass_rate_t >= 0.95
```
也就是说harness 优化的是满足 SLO 后的 `req/s/GPU`,不是 raw throughput
也不是 LLM 主观认为“更强”的配置。
## 形式化设计bottleneck classifier
`bottleneck classifier` 把 observation 映射成 ranked bottleneck hypotheses
```text
b_t = ranked_bottleneck(o_t)
```
它判断的不是“哪个 knob 看起来常用”,而是“当前 SLO failure 和 latency profile
说明哪个系统环节在限制 objective”。
常见分类包括:
| Bottleneck | 典型证据 | 倾向 knob family |
| --- | --- | --- |
| `ttft_prefill` | 长 prompt 下 TTFT 接近或超过 SLOprefill service time 是瓶颈 | 提高 TP调整 prefill batching |
| `decode_tpot` | TPOT p95/p99 超 SLOdecode token latency 是瓶颈 | 调整 `max-num-seqs`,提高 TP降低 decode contention |
| `admission_queueing` | waiting/arrival lag 增长,服务时间未必单独变差 | 提高 DP调整 admission/concurrency knobs |
| `memory_kv` | KV cache pressure、preemption、OOM、launch failure | 调整 `gpu-memory-utilization``block-size`、sequence/token caps |
| `topology_comm` | TP 增加降低 latency 但 per-GPU efficiency 下降 | 回退 TP比较 DP/TP tradeoff |
本实验里,两个 harness arms 都把 ranked bottleneck 识别为
`ttft_prefill`。原因是 workload 有 heavy-tailed long prompts并且 TTFT SLO 很紧;
这意味着单个请求的 prefill service time 是主要限制。DP-only 只能增加 replica
不能缩短一个长 prompt 的 prefill 路径,因此不是第一优先级。
## 形式化设计candidate family
`candidate family generator` 根据 bottleneck 和 topology constraints 生成可比较的
action family
```text
A_t = candidate_knob_families(
b_t,
topology_constraints,
prior_failures_t,
incumbent_t
)
```
在这个 case 中:
- `b_t = ttft_prefill`
- 允许的 TP frontier 是 `TP=1 -> TP=2 -> TP=4 -> TP=8`
- 允许的 DP frontier 是 `DP=1,2,4,8`,但 DP-only 不直接缓解单请求 prefill
latency。
- EP 固定为 1因此不探索 expert parallel。
- 之前没有 failed topology因此相邻 TP probe launch risk 低。
所以 harness 选择了:
```text
trial-0001: TP=2, DP=1
trial-0002: TP=4, DP=1
```
这不是写死“Qwen27B 应该 TP4”。如果 classifier 输出的是
`admission_queueing`candidate family 会更偏向 DP 或 `max-num-seqs`;如果输出是
`memory_kv`,则会更偏向 memory/cache/sequence knobs。
## 形式化设计scoring
每个 candidate action 都按同一个抽象打分:
```text
score(a) = expected_bottleneck_relief(a)
+ information_gain(a)
+ launch_safety(a)
- regression_risk(a)
- measurement_cost(a)
```
这些项在本实验里的含义是:
- `expected_bottleneck_relief`: TP2/TP4 预计能降低 long-prefill compute latency
直接作用于 `ttft_prefill`
- `information_gain`: TP frontier probe 可以区分“需要 compute-latency relief”
还是“只是 admission/replica 不够”。
- `launch_safety`: TP2/TP4 均满足 topology constraints没有重复 failed signature。
- `regression_risk`: TP 增加会带来通信开销,可能损害 per-GPU efficiency所以必须用
`request_rate_per_gpu` 验证。
- `measurement_cost`: 每个 GPU trial 成本高;因此高信息量的 topology probe 优先于
多个局部 runtime tweak。
实际结果验证了这个 scoring
| Arm | Trial | Patch | req/s/GPU | Pass rate | 解释 |
| --- | ---: | --- | ---: | ---: | --- |
| `gpt55_harness` | 1 | `TP=2, DP=1` | 0.2142 | 0.9572 | 相邻 TP probe 已满足 SLO但仍未饱和 search high。 |
| `gpt55_harness` | 2 | `TP=4, DP=1` | 0.4429 | 0.9718 | TP frontier 继续缓解 prefill bottleneck达到 reference best。 |
| `gpt54mini_harness` | 1 | `TP=2, DP=1` | 0.1992 | 0.9707 | 弱模型也选择同一机制路径。 |
| `gpt54mini_harness` | 2 | `TP=4, DP=1` | 0.4429 | 0.9727 | 弱模型加 harness 匹配强模型加 harness。 |
## 形式化设计validator stop
Stop 不是 LLM 自己说“我觉得差不多了”。Stop 必须通过 `stop validator`
```text
stop(o_t, incumbent_t, search_state_t, candidate_set_t) -> true/false
```
本实验里 stop 的记录是:
```text
tuning_stop_reason: harness_stop
validator_reason: search_high_saturated_by_incumbent
diagnosis: The incumbent's highest measured probe is feasible and is within the
configured binary-search resolution of search.high.
```
含义是:
1. 当前 incumbent 的最高测量 probe 已经 feasible。
2. 该 feasible probe 距离 `search.high` 已经在 binary-search tolerance 内。
3. 在当前搜索区间和 SLO 约束下,继续花 GPU trial 很难提高 measured objective。
4. 因此 validator 授权 stop并保留当前 incumbent。
这给 harness 带来了 stop discipline它既不会因为 LLM 过早自信而随便停,也不会在
已经 saturate search high 后继续 burn budget。
## 实际 tune 了哪些 knobs
Harness winning path 只改了 topology
```text
base config + tensor-parallel-size=4, data-parallel-size=1
```
它没有在 winning path 中调 scheduler/cache/memory knobs因为 `ttft_prefill`
bottleneck 下,首要动作是缩短单请求 prefill service time。
Naive arms 则走了另一个方向:
| Arm | 所有 trials 使用的 topology | 变化过的 runtime knobs | Best req/s/GPU |
| --- | --- | --- | ---: |
| `gpt55_naive` | `TP=1, DP=8` | `max-num-batched-tokens`, `max-num-seqs`, `block-size`, `gpu-memory-utilization`, prefix caching, chunked prefill | 0.0273 |
| `gpt54mini_naive` | `TP=1, DP=8` | `max-num-batched-tokens`, `max-num-seqs`, `block-size`, `gpu-memory-utilization` | 0.0231 |
`gpt55_naive` 的第一个 proposal 明确选择 `TP=1, DP=8`,理由是模型能单卡放下,
因此 horizontal data parallelism 应该最大化 request rate而 TP 会带来通信开销。
之后 naive proposals 一直保留 DP-heavy topology只围绕 runtime knobs 搜索。
两个 naive arms 合计 20 个 trial slots 都没有进入 TP2/TP4 topology frontier。
## 为什么比 baseline 更好
Baseline 失败的原因是优化了错误的因果路径。
`ttft_prefill`-bound workload关键服务时间是单个请求的 prefill latency。
DP-heavy topology 可以增加 replica 数,但每个 replica 仍用 TP1 处理长 prompt
它不能显著缩短单请求 prefill path。在 tight TTFT SLO 下,这会导致 feasible
`sampling_u` 很低;再除以 GPU 数得到 `req/s/GPU` 后,结果只有
`0.02-0.027 req/s/GPU`
Harness 的优化路径是:
```text
observed SLO pressure
-> classify as ttft_prefill
-> choose legal TP frontier probe
-> measure feasible req/s/GPU under the same SLO
-> stop only when search.high is saturated by incumbent
```
这条路径是可测量、可反驳的。如果 TP4 降低了 latency 但
`request_rate_per_gpu` 明显下降harness 会 reject 这个 hypothesis。如果
bottleneck 是 admission/queueing 而不是 TTFT/prefill同一个 knob-effect model
会偏向 DP 或 `max-num-seqs`,而不是 TP frontier。
因此这个结果不是“Qwen27B case 里我们 prompt 诱导模型说 TP4”。更准确的结论是
harness 用 SLO-derived bottleneck evidence 把搜索导向了正确的 knob family
再用 per-GPU objective 和 validator stop 验证这个方向。
## 证据边界
这份报告强支撑 Qwen27B tight-SLO case 上的 harness 机制,但不能单独当作通用性证明。
当前可成立的结论是:
- 在这个 case 中harness 同时提升了 final quality、convergence speed、AUC 和
stop discipline。
- `gpt-5.4-mini + harness` 匹配 `gpt-5.5 + harness`,并显著超过
`gpt-5.5 + naive`,说明收益主要来自 harness 的结构化状态和 validator而不是
单纯来自更强模型。
- 成功路径用的是通用机制SLO-derived bottleneck classification、topology
constraints、knob-effect scoring、per-GPU objective、validator-authorized stop。
- 还需要在其他 bottleneck/case 上继续验证,例如 prefill scheduler pressure、
decode TPOT pressure、memory/KV pressure、admission/queueing pressure。
## 原始 aggregate report 摘录
```text
# qwen27b-tight-2x2-aggregate-20260623T005838Z
## Aggregate
- Cases: `1`
- Harness-vs-naive pass/checks: `2`/`2`
- Winner counts: `{"final_best": {"gpt55_harness": 1}, "fastest_to_target": {"gpt55_harness": 1}, "normalized_auc": {"gpt55_harness": 1}}`
## By Kind
| Kind | Arms | Mean final/ref | Mean AUC | Target reached |
| --- | ---: | ---: | ---: | ---: |
| `harness` | 2 | 1.0000 | 0.9467 | 2 |
| `naive` | 2 | 0.0569 | 0.0543 | 0 |
## Cases
### qwen27b-tight-slo-2x2-aggregate
- Reference best req/s/GPU: `0.4429`
- Target fraction: `0.95`
- Winners: `{"final_best": "gpt55_harness", "fastest_to_target": "gpt55_harness", "normalized_auc": "gpt55_harness"}`
| Arm | Kind | Trials | Final/GPU | Final/ref | TTT | AUC | Failed | No feasible |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| `gpt55_harness` | `harness` | 2 | 0.4429 | 1.0000 | 2 | 0.9484 | 0 | 0 |
| `gpt55_naive` | `naive` | 10 | 0.0273 | 0.0616 | - | 0.0588 | 2 | 2 |
| `gpt54mini_harness` | `harness` | 2 | 0.4429 | 1.0000 | 2 | 0.9450 | 0 | 0 |
| `gpt54mini_naive` | `naive` | 10 | 0.0231 | 0.0522 | - | 0.0498 | 1 | 1 |
| Harness | Final vs best naive | Target speedup | AUC vs best naive | Pass |
| --- | ---: | ---: | ---: | --- |
| `gpt55_harness` | 16.2290 | - | 16.1296 | `True` |
| `gpt54mini_harness` | 16.2290 | - | 16.0720 | `True` |
```

View File

@@ -0,0 +1,51 @@
# Qwen3.5-27B TP sweep under length-aware TTFT SLO — 2026-06-16
Branch `feat/two-stop`. Deterministic ground-truth A/B (proposal files, no LLM):
TP1 vs TP2 vs TP4 on the dense Qwen3.5-27B (internal 256k, fp8, spec-decode) at
08k chat, vLLM 0.11.1, H20, `replay_time_scale=1.0` (no smoke), Stop-A enabled,
pinned to GPUs 27.
**SLO**: TTFT ≤ `4000 + 0.125·L_in` ms (= 4s + L_in/8k), TPOT ≤ 50 ms, pass ≥ 95%.
## Result
| config | best_u | raw req/s | req/s/GPU | pass | saturated |
| --- | --- | --- | --- | --- | --- |
| TP1 | 0.00195 | 0.065 | **0.065** | 1.00 | no |
| TP2 | 0.0195 | 0.585 | **0.2925** | 0.96 | no |
| TP4 | 0.123 | 3.63 | **≥0.908** | 0.98 | **yes (best_u≈high=0.125)** |
- **Per-GPU throughput rises sharply with TP for the dense 27B**: TP2 = 4.5× TP1,
TP4 ≥ 14× TP1. Opposite of the MoE Qwen3-30B-A3B (TP1 best per-GPU) — confirms the
dense-vs-MoE distinction.
- **Mechanism**: TP1 is TPOT-bound — one H20 cannot decode a 27B under 50 ms/token
once the batch grows, so it saturates at ~0.065 req/s/GPU. Loosening TTFT (2s→4-5s)
did *not* change TP1 (still 0.065), confirming TPOT — not TTFT — is TP1's binding
constraint. Each TP doubling speeds decode+prefill enough to more than recover the
added GPUs.
- **TP4 saturated** the offered-load ceiling (`best_u=0.123 ≈ 0.125`): still feasible
after ~the whole trace, so 0.908 is a lower bound. True peak (and TP8) need a
raised `search.high` to measure.
## Process findings (fed back into the harness)
- **Bug fixed**: a request exceeding `request_timeout_s` raised a raw `TimeoutError`
mid-stream that escaped `_run_one_request` and crashed the whole trial; now wrapped
as `HttpClientError` (failed request, not failed trial). Commit `2fcaf80`.
- **Open gap**: killing a `study tune` run orphans the `VLLM::EngineCore` workers
(SIGTERM/SIGKILL of the loop doesn't tear down the engine), which twice left leaked
GPU memory on GPUs 0/1 (dead PIDs still pinning KV, only clearable via root
`nvidia-smi --gpu-reset`). Fix: SIGTERM handler in the CLI loop + make
`_terminate_process_tree` match `EngineCore` workers, not just `vllm serve`.
- Experiment hygiene: scale=1.0 makes each probe take real arrival time; `search.high`
must bracket the config's boundary (too wide wastes probes on a low-capacity config;
too low saturates a high-capacity one), and `request_timeout_s` must be modest so
overloaded probes drain fast.
## Next
- Re-measure TP4 (and TP8) with `search.high` raised (e.g. 0.5) to find the true peak
per-GPU and the TP knee.
- Run the Stop-B agentic loop on this 27B stack: unlike the 30B (baseline already
optimal), here the loop should climb TP1→TP2→TP4 and stop — a real improving
trajectory (the original Phase-5 "A" goal).

View File

@@ -0,0 +1,164 @@
# Qwen30B SLO robustness - 2026-06-24
本文整理 Qwen30B-A3B community vLLM 0.20 case 在三档 SLO 下的 harness/naive
对比,并解释不同 SLO 为什么没有导致完全不同的最终 topology却改变了可承载负载边界
和 bottleneck 判断。
原始报告位于远端共享 checkout
```text
.aituner-reports/qwen30b-slo-robust-gpt55-dash1-20260623T163521Z-strict/report.md
.aituner-reports/qwen30b-slo-robust-gpt55-dash1-20260623T163521Z-medium/report.md
.aituner-reports/qwen30b-slo-robust-gpt55-dash1-20260623T163521Z-loose/report.md
```
## 实验设计
Case: `qwen30b-a3b-slo-{strict,medium,loose}-gpt55`
共同设置:
- Served model: Qwen30B-A3B community vLLM 0.20。
- Hardware: H20允许 1/2/4/8 GPU topology。
- Trace: chat 0-8k输出长度 128。
- Search: `sampling_u in [0, 1.0]`tolerance 0.001max 6 probes。
- Objective: 在 pass rate >= 0.95 下最大化 `request_rate / used_gpu_count`
- Tuner model: `gpt-5.5`
三档 SLO
| SLO | TTFT step rule | TPOT |
| --- | --- | ---: |
| strict | <=4k: 1s, <=32k: 2s, else: 3s | 40 ms |
| medium | <=4k: 2s, <=32k: 4s, else: 6s | 50 ms |
| loose | <=4k: 4s, <=32k: 8s, else: 12s | 70 ms |
## 结果摘要
| SLO | Harness final req/s/GPU | Naive final req/s/GPU | Final speedup | AUC speedup | Harness TTT |
| --- | ---: | ---: | ---: | ---: | ---: |
| strict | 2.2083 | 0.8000 | 2.7604x | 2.7886x | 1 |
| medium | 3.2583 | 0.8000 | 4.0729x | 4.0729x | 1 |
| loose | 3.2583 | 1.0458 | 3.1155x | 4.4622x | 1 |
三个 SLO 下 harness 都在第一个 trial 到达该 SLO 下的 reference best。naive 在 8 个
trials 内没有达到 95% reference target。
## 最终 tune 出来的配置
三档 SLO 的最终 best topology 都是:
```text
tensor-parallel-size = 2
data-parallel-size = 1
enable-expert-parallel = false
```
但这不表示 SLO 没有影响。SLO 改变的是同一个 topology 的可行负载上限:
| SLO | Best config | Best sampling_u | Total req/s | req/s/GPU | Pass rate |
| --- | --- | ---: | ---: | ---: | ---: |
| strict | `TP=2, DP=1` | 0.484375 | 4.4167 | 2.2083 | 1.0000 |
| medium | `TP=2, DP=1` | 0.750000 | 6.5167 | 3.2583 | 1.0000 |
| loose | `TP=2, DP=1` | 0.750000 | 6.5167 | 3.2583 | 1.0000 |
strict 到 medium/loose 的主要变化是 feasible frontier 右移:同一个 `TP=2, DP=1`
配置在 strict 下只能稳定承载 `sampling_u=0.484375`,在 medium/loose 下可以承载
`sampling_u=0.75`
## 为什么 `TP=2, DP=1` 稳定胜出
AITuner 的 scoring 不是 raw throughput而是 SLO-constrained per-GPU throughput
```text
J(c, SLO) = max_u request_rate(c, u) / used_gpu_count(c)
subject to pass_rate(c, u, SLO) >= 0.95
```
这解释了为什么 `TP=4` 没有赢。`TP=4` 的单请求 latency 更低、总吞吐可以更高,
但它使用两倍 GPUper-GPU objective 反而下降:
| SLO | Config | Total req/s | Used GPUs | req/s/GPU | 解释 |
| --- | --- | ---: | ---: | ---: | --- |
| strict | `TP=2, DP=1` | 4.4167 | 2 | 2.2083 | strict best |
| strict | `TP=4, DP=1` | 4.4167 | 4 | 1.1042 | latency 更低,但 GPU efficiency 更差 |
| medium/loose | `TP=2, DP=1` | 6.5167 | 2 | 3.2583 | medium/loose best |
| medium/loose | `TP=4, DP=1` | 8.3667 | 4 | 2.0917 | raw throughput 更高,但 per-GPU 不划算 |
因此 harness 学到的不是“越多 GPU 越好”,而是更具体的机制:
```text
TP=1: 单请求 prefill/decode latency 偏高SLO-constrained load frontier 低。
TP=2: 足够缓解 latency同时 GPU 数量仍低per-GPU objective 最优。
TP=4: 继续降低 latency但通信和 GPU 数量成本超过收益。
```
## SLO 改变 bottleneck 的方式
strict 下,`TP=2, DP=1``sampling_u=0.484375` 可行,但下一档
`sampling_u=0.5` 直接进入 queueing collapse
| Point | Pass rate | 主要失败原因 |
| --- | ---: | --- |
| strict, `u=0.484375` | 1.0000 | 无 |
| strict, `u=0.5` | 0.0290 | `tpot_ms>40`, `ttft_ms>1000/2000`, `slo_pass_rate_unrecoverable` |
medium/loose 下TTFT 阈值放宽后,同一 topology 能承载更高 arrival intensity。
但是在 `u=0.765625` 仍会进入不可恢复的排队区:
| SLO | Feasible point | Next infeasible point | 主要失败原因 |
| --- | --- | --- | --- |
| medium | `u=0.75`, pass 1.0000 | `u=0.765625`, pass 0.6900 | `tpot_ms>50`, `slo_pass_rate_unrecoverable` |
| loose | `u=0.75`, pass 1.0000 | `u=0.765625`, pass 0.2900 | `tpot_ms>70`, `slo_pass_rate_unrecoverable` |
这说明 SLO 放宽不是无限提高吞吐。服务系统还有 queueing stability frontier
超过 frontier 后,即使单个请求的 steady-state latency 看起来可控,排队也会让 pass rate
迅速崩掉。
## 其他候选配置的信号
`TP=1, DP=1` 对 SLO 更敏感:
| SLO | `TP=1, DP=1` req/s/GPU | 解释 |
| --- | ---: | --- |
| strict | 2.2000 | 接近 strict best但略低于 `TP=2` |
| medium | 2.2000 | 仍低于 `TP=2` |
| loose | 2.8500 | 宽松 SLO 下受益明显,但仍低于 `TP=2` |
`gpu-memory-utilization=0.92` 在 medium/loose 中与 `TP=2` 打平:
| SLO | Config | req/s/GPU |
| --- | --- | ---: |
| medium | `TP=2, gpu-memory-utilization=0.92` | 3.2583 |
| loose | `TP=2, gpu-memory-utilization=0.92` | 3.2583 |
这说明该 workload 的主瓶颈不是 KV memory headroom而是 topology 和 queueing
frontier。
EP family 在该环境下不稳定:
```text
TP=4, EP=2/4, enable-expert-parallel=true -> engine_launch exit_code=2
```
这些失败 trial 没有进入 best candidate但它们说明当前 failure memory 还可以继续加强:
同一类 EP launch failure 出现后,后续 proposal 应更积极地屏蔽该 family。
## 对 paper claim 的含义
这组实验支持的 claim 是:
1. Harness 对 SLO 变化有稳定收益strict/medium/loose 三档均显著优于 naive。
2. Harness 不是固定写死某个 knob。它通过 SLO-constrained probing 找到 feasible
frontier在本 case 中最终 topology 相同,但可承载负载边界随 SLO 改变。
3. Harness 的 value 来自 topology-first candidate family、per-GPU scoring 和
validator 对 failed family 的处理,而不是自然语言 prompt 的偶然表达。
这组实验尚不能单独 claim
- 所有模型和 workload 上都 robust。
- `TP=2, DP=1` 是全局最优。
- EP family 已经被最优处理。
对应的后续证据应放在 roadmap 中跟踪:局部 grid/near-optimum、跨模型 2x2、跨 workload
SLO robustness以及 failure-memory ablation。

View File

@@ -0,0 +1,121 @@
# Stop-A validation (Phase 3) — 2026-06-15
Branch `feat/two-stop`. Stop-A = truncate each probe's replay once the offered
L-C-A of the replayed prefix converges to the full set (pure L-C-A criterion +
C-gate). This note records the CPU calibration and the GPU fidelity check.
## 1. Calibration (CPU, no serving)
`scripts/stop_a_calibration.py` on the dash0 0321 10:0010:10 windows:
| dim | chat (19239 req, hit≈7%) | coder (2451 req, structured reuse) |
| --- | --- | --- |
| A | ≥0.95 by frac 0.10 | fast |
| L | ≥0.96 from frac 0.05 | 0.05=0.75 (heavy tail) → ≥0.94 by 0.20 |
| **C (slowest)** | noisy, dips (0.50→0.885, 0.55→0.835), stable ≥0.92 only ~0.85 | smooth, stable ≥0.92 by ~0.70 |
Stop fraction (τ_L=τ_A=0.90, W=3):
| τ_c | chat | coder |
| --- | --- | --- |
| 0.85 | 0.45 (273s) | 0.45 (255s) |
| 0.90 | 0.70 (423s) | 0.55 (318s) |
| 0.92 | 0.85 (513s) | 0.70 (411s) |
Findings:
- **C is the slowest dimension in both workloads** — reproduces paper §5.2 / Fig 9.
- **What makes C hard to call converged is signal *noise*, not reuse magnitude.**
Low-reuse chat has a sparse/spiky ideal-hit-length series, so its C similarity
oscillates and is *harder* to stabilize than the structured, higher-reuse coder.
Consequence: a strict τ_c (0.92) gives chat only ~15% saving. A more robust C
feature for the low-reuse regime is future work.
## 2. GPU fidelity check (Qwen3-30B-A3B, vLLM 0.11.1, H20)
One full-window run (`adaptive_stop` disabled, `replay_time_scale=1.0`, window
`chat_w20260311_1000`, 08k, out=128), then `scripts/stop_a_validate.py`
recomputes each probe's convergence prefix and compares the truncated verdict to
the full verdict — so a single GPU run validates truncation fidelity (no second run).
Trial result: best feasible `sampling_u=0.078125`, request_rate **2.30 req/s**,
pass_rate 0.973.
Per-probe verdict (τ=0.9):
| τ_c | verdict matches | mean replay saved |
| --- | --- | --- |
| 0.85 | 3/4 | 54% |
| 0.90 | 3/4 | 52% |
| 0.92 | 3/4 | 38% |
The mismatch is the same probe at every τ_c — the feasibility knee `0.08594`:
```
thresh full_pass prefix_pass full_feas prefix_feas
0.08594 0.946 0.9560.961 False True <- mismatch
0.07812 0.973 0.9870.990 True True
0.06250 0.986 1.000 True True
0.09375 0.268 0.490.54 False False
```
## 3. Interpretation
- **Stop-A works and saves ~50% of replay** (vs the full 600 s window) while
preserving 3/4 probe verdicts. (The paper's ~70% is vs a 30-min fixed baseline;
our baseline is the 600 s window, so the percentages are not directly comparable.)
- **The one failure is a boundary false-positive at the feasibility knee.** At
`0.08594` the full window is 0.946 (just below the 0.95 SLO) but the prefix is
0.9560.961 (just above): the *second half* of the window degraded — engine-state
drift (KV fill / fragmentation / later-arriving harder requests) that the
*offered* L-C-A cannot see. The C-gate did not help because offered-C had
converged; the divergence is in the measured pass-rate, not in C.
- If Stop-A were enabled, the binary search would accept `0.08594`, overestimating
the peak sustainable rate by one binary step (~10%).
**This is the boundary jitter we accepted when choosing the pure-L-C-A criterion.**
The data now argues for revisiting the previously-declined **SLO-boundary guard**:
keep replaying while the measured pass-rate is within ±δ of the target, even after
L-C-A converges. It targets exactly this knee case at low extra cost (it only
extends replay on probes sitting on the feasibility boundary). Recommend adding it
as a small Stop-A enhancement before enabling Stop-A in production studies.
## 4. SLO-boundary guard (implemented + validated)
Added `trace.adaptive_stop.boundary_delta` (default 0.02): when a truncated probe's
measured pass-rate lands within ±δ of the SLO target, re-measure on the full window
and use that verdict. Re-ran the same config with `adaptive_stop` enabled
(τ=0.9, τ_c=0.90, δ=0.02):
| threshold | feasible | pass | selected | replayed | boundary_extended |
| --- | --- | --- | --- | --- | --- |
| 0.06250 | True | 1.000 | 1086 | 487 (45%) | — |
| 0.09375 | False | 0.444 | 1656 | 822 (50%) | — |
| 0.07812 | True | 0.994 | 1378 | 682 (49%) | — |
| 0.08594 | **False** | 0.947 | 1523 | **1523 (100%)** | **True** |
Result: best feasible `sampling_u=0.078125` (rate 2.30 req/s) — **identical to the
full-replay baseline**. The guard fired on exactly the one knee probe and
re-measured it to the correct infeasible verdict; the other three probes truncated
to ~4550%. Net replayed 3514/5643 requests ≈ **38% replay saved on this trial
while recovering the correct peak rate** (no one-step overestimate).
**Conclusion: Stop-A with the boundary guard is correct (verdict matches full
replay) and still saves replay time. Safe to enable.** Configs:
`dash0_qwen30b_a3b_stopA_fulldata.json` (OFF baseline) and
`dash0_qwen30b_a3b_stopA_on.json` (ON).
## Repro
```
# calibration
PYTHONPATH=src python3 scripts/stop_a_calibration.py \
--jsonl <DIR>/qwen_chat_blksz_64_032109-032111.jsonl --block-size 64 \
--window-start 3600 --window-end 4200 --gpu-count 8 --label chat
# GPU run + fidelity
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
--store-root .aituner/stopA-fulldata --max-trials 1
PYTHONPATH=src python3 scripts/stop_a_validate.py \
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
--store-root .aituner/stopA-fulldata --tau 0.9 --tau-c 0.90
```

View File

@@ -0,0 +1,86 @@
# Stop-B end-to-end validation (Phase 5) — 2026-06-15
Branch `feat/two-stop`. Real agentic loop on dash0: Qwen3-30B-A3B / vLLM 0.11.1 /
8×H20, `gpt-5.4` (via codex/prism) proposing configs, Stop-A enabled to accelerate
each evaluation, `use_harness=True` so the Stop-B deterministic validator + LLM-stop
veto are active. Config `dash0_qwen30b_a3b_stopB_e2e.json`, `search.high=1.0`,
`max_probes=6`, `--max-trials 8`.
## Two stop paths exercised
**Run A (`search.high=0.125`)** — the default config already saturates the offered-load
search range, so Stop-B fired immediately via the **search-high-saturation** path:
`stop_authorized_by: validator`, reason *"the incumbent's highest measured probe is
feasible and within the binary-search resolution of search.high."* Correct
measurement-ceiling stop (no point proposing configs when the load range, not the
config, is the bound).
**Run B (`search.high=1.0`)** — forces a real multi-iteration search:
| trial | TP | DP | EP | feasible | raw req/s | **req/s/GPU** | source |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 0001 | 1 | 1 | 1 | ✓ | 2.90 | **2.900** | baseline |
| 0002 | 2 | 1 | 1 | ✓ | 4.42 | 2.208 | harness TP-seed |
| 0003 | 2 | 1 | 2 | ✗ launch-fail | — | — | harness (EP) |
| 0004 | 1 | 2 | 1 | ✓ | 4.42 | 2.208 | LLM (after veto) |
| 0005 | 2 | 2 | 1 | ✓ | 8.37 | 2.092 | harness |
| 0006 | 2 | 2 | 2 | ✗ launch-fail | — | — | harness (EP) |
| 0007 | 4 | 1 | 1 | ✓ | 8.37 | 2.092 | LLM |
| (0008) | — | — | — | **STOP** | — | — | LLM stop, honored after veto budget |
Incumbent: **trial-0001 (TP1), 2.90 req/s/GPU — never beaten.**
> **⚠️ The per-GPU trajectory above is NOT a valid benchmark — it validates only
> the Stop-B *mechanics*.** Two confounds:
> 1. **Trace-ceiling saturation.** TP2·DP2 and TP4 reached `best_sampling_u≈0.98`
> (still feasible after consuming ~the whole window), so their *true* peak
> per-GPU is higher than the 2.09 shown — we ran out of offered load to push
> them to their boundary. Only TP1 (u=0.31), TP2 (u=0.48) and DP2 (u=0.48)
> found real boundaries. The `sampling_u` axis maxes at the full trace, so any
> config that sustains more than the window's offered rate cannot be measured.
> 2. **Smoke regime.** This run inherited `replay_time_scale=0.1` +
> `max_requests_per_probe=512` (README: convergence test, *not* a benchmark) —
> compressed arrivals distort A and the 512 cap imposes a ~8.4 req/s ceiling.
>
> The below-ceiling TP1 (2.90) > TP2 (2.21) ordering *may* be real for this model
> (Qwen3-30B-A3B is an MoE with ~3B active params → little compute per token → TP
> adds all-reduce overhead with little benefit), which differs from the dense
> Qwen3.5-27B where TP2 wins. But this run cannot establish it. A valid benchmark
> needs `scale=1.0`, no cap, and enough offered-load headroom that strong configs
> are not trace-saturated — see the 27B TP A/B follow-up.
## Phase-5 acceptance
- **No regression.** The primary metric `request_rate_per_gpu` stayed 2.90 the whole
run. Scaling TP/DP raised *raw* throughput (4.42, 8.37) but lowered per-GPU
efficiency (2.21, 2.09); the loop correctly kept the TP1 baseline as incumbent and
never adopted a worse-per-GPU config. (Matches the paper: long-prompt, low-reuse
chat prefers small TP for per-GPU efficiency.)
- **Stop-B authority validated live.** At trial 4 the LLM tried to stop
(`should_stop=true`); the deterministic validator **vetoed** it
(`validator_did_not_authorize_stop`, `continue_harness_guided_search`), forcing one
more confirmation (DP2, which also failed to beat baseline). After the budget, the
LLM's later, well-justified stop was honored (`stop_authorized_by:
llm_after_veto_budget`). The bounded veto behaved exactly as designed.
- **Auditable reason chain.** Every stop/veto carries a diagnosis grounded in the
measured evidence (e.g. *"increasing TP 1→2 lowers per-GPU efficiency even though
token latency improves … EP is explicitly blocked by launch-failure evidence"*).
- **Launch-failure robustness.** Two EP configs (trial-0003, 0006) failed to launch
under vLLM 0.11.1; the harness recorded them as hard-negative evidence and the LLM
explicitly stopped proposing EP.
## Notes / limitations
- For this workload the baseline (TP1) is already per-GPU optimal, so iterations-to-
*best* = 1; the remaining trials are the loop *confirming* no config beats baseline
before stopping. A workload with an under-tuned default would show an improving
trajectory; this run validates the stop/no-regression machinery, not a tuning win.
- The final stop came via `llm_after_veto_budget` (validator vetoed once, then
deferred), not a pure deterministic validator stop — because the deterministic
conditions (3-within-2%, saturation, validation-exhausted) did not cleanly fire
when every trial was a distinct config with a distinct per-GPU rate. The validator
acted as the *guard* (preventing premature stop), which is its designed role.
- 7 trials > the paper's 36 average, inflated by the wider search range, 2 EP
launch-failures, and the veto. Acceptable for a validation run.
- LLM token: the non-interactive shell lacks `OPENAI_API_KEY`; export it from the
codex `auth.json` (`~/.codex/auth.json`) before the run.

View File

@@ -0,0 +1,64 @@
# Stop-B end-to-end on dense Qwen3.5-27B (the improving trajectory) — 2026-06-16
Branch `feat/two-stop`. Real `gpt-5.4` agentic loop (codex/prism), Stop-A enabled,
length-aware TTFT SLO (4s + L_in/8k, TPOT ≤ 50 ms), vLLM 0.11.1, H20, GPUs 27,
`replay_time_scale=1.0`, `search.high=0.25`, `inherit_incumbent_floor=true`.
Config `dash0_qwen27b_stopB_loop.json`. Companion to the 30B run
(`stop-b-e2e-20260616.md`); together they cover all Stop-B behaviors.
## Trajectory (incumbent = TP4 @ 1.00 req/s/GPU)
| iter | proposed by | config | per_gpu | adopted? |
| --- | --- | --- | --- | --- |
| 1 | baseline | TP1 | 0.123 | incumbent |
| 2 | gpt-5.4 | TP2 | 0.2925 (2.4×) | ✅ new incumbent |
| 3 | gpt-5.4 | TP4 | **1.0012 (8.1×)** | ✅ new incumbent |
| 4 | gpt-5.4 | TP4 + chunked-prefill + mbt=16384 | 0.942 | ❌ **worse → rejected** |
| 5 | gpt-5.4 | TP2 + DP2 | (loop stopped before completing) | — |
(Run stopped manually after iter-4 — see "Why stopped" below. Incumbent preserved
at TP4.)
## What this demonstrates (the piece the 30B run could not)
- **A genuine improving climb.** `gpt-5.4` + the harness raised per-GPU throughput
TP1 → TP2 → TP4 (0.123 → 0.29 → 1.00, 8.1×), each step a correctly-diagnosed real
gain: TP1 is TPOT-bound, so the agent scaled tensor-parallelism, then — once
topology was won — pivoted to **runtime tuning on the winning family** (chunked
prefill + larger batched tokens).
- **No regression.** The runtime tweak (iter-4) measured *below* plain TP4
(0.942 < 1.00), and the harness correctly **kept TP4 as the incumbent** rather than
adopting the worse config the core Stop-B guarantee, shown live.
- Combined with the 30B run (search-high-saturation `validator`-authorized stop +
premature-LLM-stop veto), every Stop-B behavior is now validated end-to-end:
improving climb, correct bottleneck-driven proposals, no regression, deterministic
stop authority, and the LLM-stop veto.
## Process wins / findings
- **SIGTERM teardown fix validated in practice.** This loop was stopped with a plain
SIGTERM and the engine + EngineCore workers torn down cleanly GPUs 27 freed, no
orphan, no leaked memory (contrast: the pre-fix runs twice leaked GPU0/1). Commit
`b17b213`.
- **Timeout-as-failed-request fix** (`2fcaf80`) held no trial crashed on
request timeouts this run.
## Why stopped (efficiency finding — feeds next round)
The loop was stopped after iter-4 rather than run to an explicit Stop-B firing,
because each TP4-family trial took ~3 h: at `scale=1.0`, infeasible high-θ probes
each run to the **`early_stop_max_elapsed_s=900` cap** (`probe_elapsed_s>900`), and
the primary+fallback binary search doubles the probe count. Stop-A truncates a
*converged* replay but does not shortcut an *overloaded* probe that simply runs out
the clock. **For a practical agentic loop at scale=1.0, lower `early_stop_max_elapsed_s`
(≈300 s)** so overloaded probes die fast; consider also having an infeasible-and-
overloaded probe early-stop on a fast lag/throughput signal rather than the elapsed
cap. The convergence itself was already evident (iter-4's runtime tweak and the
queued TP2+DP2 were not beating TP4).
## Next
- Lower the elapsed cap and (optionally) re-run to capture the explicit Stop-B stop
on this 27B stack.
- Land the deferred items: more robust C feature for the low-reuse regime; Stop-C
cross-day retune trigger; §7 baselines (SCOOT/naive/community).

View File

@@ -0,0 +1,106 @@
# qwen27b-chat-0-8k TPOT25 16-Iter Harness Compare
## Goal
Rerun the internal vLLM Qwen3.5-27B chat 0-8k tuning comparison under a stricter
TPOT SLO:
- no-harness: 16 tuning iterations;
- harness: 16 tuning iterations, with permission to stop early if the harness
convergence guard decides no further GPU trial is needed.
Both variants must be launched directly through AITuner. No state seeding,
manual replay, or historical-result injection is allowed.
## Setup
- Host: `dash0`.
- Hardware: 8 NVIDIA H20 GPUs.
- Engine: internal vLLM at `/usr/local/bin/vllm`.
- Model:
`/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`.
- Served model name: `qwen35-27b-aituner`.
- Workload window: `chat_w20260311_1000`.
- Trace path source: `/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json`.
- Request mode: `chat`.
- Input bucket: `0 <= input_length <= 8192`.
- Replay scale: `1.0`.
- Max concurrency: `32`.
- Max requests per probe: unset, so each probe uses the full selected trace
subset for its `sampling_u` threshold.
- Restart engine after early stop: `true` for both variants. This is needed
under TPOT25 because very slow infeasible probes can leave live HTTP requests
in the engine after the SLO is already unrecoverable. Restarting keeps the
next binary-search probe from being contaminated by previous in-flight work.
- Search field: `sampling_u`.
- Search range: `low=0.0`, `high=0.0625`.
- Search probes: `max_probes=6`, `tolerance=0.001`.
- Sampling seed: `20260325`.
## SLO
- Target pass rate: `0.95`.
- TTFT rule:
| Input tokens | TTFT threshold |
| ---: | ---: |
| `<=4096` | `2000 ms` |
| `<=32768` | `4000 ms` |
| otherwise | `6000 ms` |
- TPOT rule: fixed `<=25 ms`.
## Specs
Remote generated specs:
- no-harness:
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json`
- harness:
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json`
The two specs were generated from
`configs/examples/dash0_qwen27b_tight_slo_run4_0_8k.json`. After normalizing
`study_id` and `llm.use_harness`, the JSON payloads compare equal. Therefore the
only tuning-behavior difference between the formal comparison runs is whether
the harness is enabled.
## Commands
No-harness:
```bash
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json \
--store-root .aituner-tight \
--max-trials 16
```
Harness:
```bash
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json \
--store-root .aituner-tight \
--max-trials 16
```
## Run Log
- 2026-05-06 12:37 CST: generated both remote specs and verified that the only
normalized difference is `llm.use_harness`.
- 2026-05-06 12:37 CST: started no-harness in tmux session
`qwen27b_tpot25_noharness_16iter_20260506`.
- 2026-05-06 21:06 CST: stopped the initial no-harness pre-run before using it
for comparison. It used `restart_engine_after_early_stop=false`; the first
TP1 baseline probe already recorded `slo_pass_rate_unrecoverable`, but
unfinished requests remained live in vLLM and would contaminate the next probe.
- 2026-05-06 21:07 CST: generated the formal clean specs with
`restart_engine_after_early_stop=true` for both variants and verified the
normalized diff is still only `llm.use_harness`.
- 2026-05-06 21:09 CST: started formal no-harness run in tmux session
`qwen27b_tpot25_restart_noharness_16iter_20260506`.
## Results
Pending.

View File

@@ -0,0 +1,131 @@
# Qwen27B Chat 0-8k TPOT 40ms Baseline Infeasible Run
Date: 2026-05-07
## Goal
Re-run the internal vLLM + Qwen3.5-27B chat 0-8k tuning comparison after adding a study-level guard:
- if the automatic baseline trial has no feasible probe;
- and the lowest sampled request rate still fails the SLO target pass rate;
- then AITuner stops the whole study and reports that the SLO is too tight for the current setup.
This prevents spending the remaining tuning budget on LLM or harness proposals when the baseline itself demonstrates that the workload/SLO is infeasible at the search floor.
## Implementation
Commit: `f212673 Stop tuning when baseline is infeasible`
Changed behavior:
- `study tune` now persists `tuning_stop_reason` and `tuning_stop_diagnosis` in `state.json`.
- `study tune` also persists `tuning_stop_details`, including the lowest sampled probe's TTFT/TPOT mean, p50, p95, and p99.
- After the automatic baseline trial is ingested, AITuner checks the worker result:
- `status == completed`
- `best_request_rate is None`
- at least one probe exists
- all probes are infeasible
- If true, AITuner stops before asking the LLM or harness for any proposal.
- Re-running the same study respects the persisted stop state and does not resume tuning.
Validation:
```bash
python3 -m compileall -q src tests
PYTHONPATH=src python3 -m unittest tests.test_core_flow
```
Local and `dash0` both passed.
## Setup
Host: `dash0`
Remote repo: `/home/admin/cpfs/wjh/aituner/aituner`
Base spec: `configs/examples/dash0_qwen27b_tight_slo_run4_0_8k.json`
Model: `/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`
Workload: chat, 0-8k input window
SLO:
- TTFT: existing step rule from the base spec
- TPOT: fixed `40ms`
- target pass rate: `0.95`
Search:
- Direct AITuner command: `python3 -m aituner.cli study tune ... --max-trials 12`
- No manual proposal/state edits during either run.
- Both variants used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7`; this was identical for both specs.
- The two specs were verified equal after normalizing only `study_id` and `llm.use_harness`.
Specs:
- no-harness: `.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot40-gpu3skip-12iter-noharness-20260507.json`
- harness: `.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot40-gpu3skip-12iter-harness-20260507.json`
## Commands
No harness:
```bash
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot40-gpu3skip-12iter-noharness-20260507.json \
--store-root .aituner-tight \
--max-trials 12
```
Harness:
```bash
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot40-gpu3skip-12iter-harness-20260507.json \
--store-root .aituner-tight \
--max-trials 12
```
## Results
Both runs stopped after the baseline trial. No LLM/harness proposal was evaluated because baseline had no feasible probe.
| Variant | Trials executed | Best request rate | Best request rate / GPU | Stop reason |
| --- | ---: | ---: | ---: | --- |
| no-harness | 1 | - | - | `baseline_all_infeasible` |
| harness | 1 | - | - | `baseline_all_infeasible` |
Baseline probe curve:
| sampling_u | request rate | pass rate | feasible | early stop reason |
| ---: | ---: | ---: | --- | --- |
| 0.03125 | 0.895 | 0.000000 | false | `slo_pass_rate_unrecoverable` |
| 0.015625 | 0.483333 | 0.137931 | false | `slo_pass_rate_unrecoverable` |
| 0.0078125 | 0.246667 | 0.236486 | false | `slo_pass_rate_unrecoverable` |
| 0.00390625 | 0.123333 | 0.189189 | false | `slo_pass_rate_unrecoverable` |
| 0.001953125 | 0.065000 | 0.205128 | false | `slo_pass_rate_unrecoverable` |
| 0.0009765625 | 0.035000 | 0.142857 | false | `slo_pass_rate_unrecoverable` |
Lowest request rate latency summary:
| Variant | request rate | pass rate | TTFT mean | TTFT p50 | TTFT p95 | TTFT p99 | TPOT mean | TPOT p50 | TPOT p95 | TPOT p99 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| no-harness | 0.035000 | 0.142857 | 1288.953ms | 446.586ms | 3011.814ms | 3011.814ms | 12.661ms | 13.141ms | 15.097ms | 15.097ms |
| harness | 0.035000 | 0.142857 | 1268.090ms | 445.274ms | 2889.080ms | 2889.080ms | 12.658ms | 13.170ms | 15.102ms | 15.102ms |
This shows that the TPOT threshold of `40ms` is not the binding constraint at the lowest sampled rate. The observed TPOT p99 is about `15.1ms`; failures are driven by TTFT and by the unrecoverable-pass-rate early stop after too many requests have already failed or been skipped.
Final diagnosis written by AITuner:
```text
Baseline configuration has no feasible probe under the current SLO. Stopping tuning because even the lowest sampled request rate did not meet the target pass rate. lowest_sampled_request_rate=0.035 lowest_sampling_u=0.000976562 lowest_probe_pass_rate=0.142857 early_stop_reason=slo_pass_rate_unrecoverable
```
## Interpretation
This run does not measure harness acceleration. It proves that the TPOT 40ms setup is infeasible for the current baseline and search floor: even at `0.035` aggregate request rate, only `14.29%` of requests pass the SLO, far below the `95%` target.
The correct behavior is to stop the study early and report SLO infeasibility instead of spending the remaining 11 trial slots. Harness cannot accelerate convergence when there is no feasible baseline point and no incumbent for guided tuning.
For a Fig. 18-style convergence comparison, the next setup must first have at least one feasible baseline or feasible low-rate point under the same metric definitions.

63
docs/two-stop-summary.md Normal file
View File

@@ -0,0 +1,63 @@
# Two-stop work — summary (feat/two-stop)
Aligns the tuning harness with paper.pdf by implementing and validating the two
distinct stopping mechanisms the paper conflates, plus a length-aware SLO and two
harness-robustness fixes. 117 unit tests pass.
## The two stops (they are different things)
- **Stop-A — evaluation sufficiency** (per replay / probe): how much trace to replay
before a measurement is trustworthy. Criterion: the replayed prefix's offered
L-C-A converges to the full set's. Saves per-evaluation GPU time.
- **Stop-B — tuning convergence** (across iterations): whether any config-improvement
opportunity remains; stop if not. Saves iterations.
## What was built
| Area | Summary | Commit |
| --- | --- | --- |
| Unify L-C-A | The prompt's `workload_lca_profile` is now the canonical 10-dim `lca.py` vector, not an ad-hoc re-derivation | `6f8e3c9` |
| Session-coherent load axis | `sampling_u` assigned per session (parent_chat_id chain) so thresholding keeps/drops whole multi-turn sessions, preserving intra-session KV reuse (C) across load levels | `0f15bbc` |
| Stop-A | `lca.find_convergence_prefix` (deterministic offered-L-C-A convergence prefix + C-gate: never declare infeasible on a cold cache); `spec.AdaptiveStopSpec` (default off); `worker._adaptive_replay_set` truncates replay + writes a certificate | `51a9e4a` |
| Stop-A boundary guard | Re-measure the full window when a truncated probe's pass-rate is within `boundary_delta` of the SLO target (fixes feasibility-knee false positives) | `dfc823f` |
| Stop-B authority | `harness._stop_authority` (mirrors the deterministic validator); `study tune` honors an LLM `should_stop` only if the validator agrees, else a bounded veto | `a8f9034` |
| Length-aware SLO | `linear_ms` rule: `threshold = intercept_ms + per_token_ms·L_in` (e.g. 4s + L_in/8k) | `ed2bbe0` |
| Robustness: timeout | HTTP stream now wraps `OSError`/`TimeoutError` as `HttpClientError` — a request exceeding `request_timeout_s` is a failed request, not a crashed trial | `2fcaf80` |
| Robustness: SIGTERM | `run_trial` installs a SIGTERM handler so killing `study tune` tears down the engine (and EngineCore workers) instead of orphaning it / leaking GPU memory | `b17b213` |
| Tooling | `stop_a_calibration.py` (CPU convergence curve), `stop_a_validate.py` (offline truncation-fidelity) | `08e53fd`, `3af1d84` |
A subagent code review found no blocking bugs (and independently validated session
coherence against a real trace); three minor fixes applied in `43125f4`.
## Validation results (real GPU runs, dash0 H20)
- **Stop-A** (`stop-a-validation-20260615.md`): CPU calibration reproduces the paper's
C-slowest ordering; GPU fidelity check on Qwen3-30B-A3B saves ~52% replay at τ_c=0.90
with 3/4 probe verdicts preserved; the one mismatch is a feasibility-knee false
positive that the **boundary guard fixes** — with the guard, best threshold matches
full replay exactly while still saving ~38% replay.
- **27B TP sweep** (`qwen27b-tp-sweep-20260616.md`): under the length-aware SLO, dense
Qwen3.5-27B per-GPU rises sharply with TP — TP1 0.065 → TP2 0.29 → TP4 ≥0.91 — the
**opposite** of the MoE 30B-A3B (TP1 best per-GPU). TP1 is TPOT-bound.
- **Stop-B** (`stop-b-e2e-20260615.md`, `stop-b-e2e-27b-20260616.md`): the 30B run
shows the deterministic `validator` stop + a premature-LLM-stop **veto**; the 27B run
(real gpt-5.4 loop) shows a genuine **improving climb** TP1 0.123 → TP2 0.29 → TP4
1.00 req/s/GPU (8.1×), each a correctly-diagnosed gain, then correctly **rejecting** a
TP4 runtime tweak that measured worse (no regression). The SIGTERM fix was validated
in practice (clean teardown, no leak).
## Open items (next round)
- **Harness convergence ablation (NOT yet done on this branch).** The paper's harness
result — domain-knowledge knob-family rules steering the LLM and cutting iterations —
has only *qualitative* evidence here (the 27B climb shows correct steering) plus older
smoke-regime ablations (`qwen27b-chat-0-8k-harness-fig18.md`: iters-to-best 4→2). A
controlled `use_harness=true` vs `false` (naive tuner) comparison on the 27B is the
missing quantified result.
- **Loop efficiency**: at scale=1.0 infeasible high-θ probes burn the
`early_stop_max_elapsed_s=900` cap (a TP4 trial took ~3h). Lower it to ~300s for
practical agentic loops.
- **dash0 GPUs 0/1** still hold leaked memory (pre-fix orphans) — needs a root
`nvidia-smi --gpu-reset`.
- Deferred: more robust C feature for the low-reuse regime; Stop-C cross-day retune
trigger (paper Q1); §7 baselines (SCOOT / naive / community).

View File

@@ -51,6 +51,13 @@ enabled = true
sync_remote_path = "~/aituner"
fleet_root = "~/.aituner_gpu_fleet"
[[hosts]]
name = "dash4"
ssh_alias = "dash4"
enabled = true
sync_remote_path = "~/workspace/aituner"
fleet_root = "~/.aituner_gpu_fleet"
[[hosts]]
name = "dash5"
ssh_alias = "dash5"

View File

@@ -4,5 +4,5 @@ dash0
dash1
dash2
dash3
dash4
dash5

BIN
paper.pdf Normal file

Binary file not shown.

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Extract a per-iteration trajectory table from an ablation study store.
Usage: python3 ablation_trajectory.py <study_store_dir>
Prints iter, proposal source/name, config_patch summary, per_gpu, status,
and the running incumbent per_gpu. Read-only.
"""
import json
import sys
from pathlib import Path
TOPOLOGY_KEYS = (
("tensor-parallel-size", "TP"),
("data-parallel-size", "DP"),
("expert-parallel-size", "EP"),
)
RUNTIME_KEYS = (
"gpu-memory-utilization",
"enable-chunked-prefill",
"max-num-batched-tokens",
"max-num-seqs",
)
def topo(patch, base_flags=None):
fp = (patch or {}).get("flag_patch", {}) or {}
ep = (patch or {}).get("env_patch", {}) or {}
effective = dict(base_flags or {})
effective.update(fp)
parts = []
for k, label in TOPOLOGY_KEYS:
if k in effective:
parts.append(f"{label}{effective[k]}")
runtime = {k: effective[k] for k in RUNTIME_KEYS if k in effective}
runtime.update(
{
k: v
for k, v in fp.items()
if k not in {key for key, _ in TOPOLOGY_KEYS} and k not in runtime
}
)
runtime.update({f"env:{k}": v for k, v in ep.items()})
base = "+".join(parts) if parts else "baseline-topo"
if runtime:
base += " | " + ", ".join(f"{k}={v}" for k, v in runtime.items())
return base
def main():
store = Path(sys.argv[1])
state = json.load(open(store / "state.json"))
snapshot_path = store / "study_spec.snapshot.json"
base_flags = {}
if snapshot_path.exists():
snapshot = json.load(open(snapshot_path))
base_flags = ((snapshot.get("engine") or {}).get("base_flags") or {})
print(f"study_id: {state.get('study_id')}")
print(f"best_trial: {state.get('best_trial_id')} best_per_gpu: {state.get('best_request_rate_per_gpu')}")
print(f"stop_reason: {state.get('tuning_stop_reason')!r}")
print(f"stop_diagnosis: {state.get('tuning_stop_diagnosis')!r}")
print(f"stop_details: {json.dumps(state.get('tuning_stop_details'), ensure_ascii=False)}")
print()
incumbent = None
hdr = f"{'iter':<5}{'trial':<11}{'status':<14}{'per_gpu':<10}{'incumbent':<11}config"
print(hdr)
print("-" * len(hdr))
for i, t in enumerate(state.get("trials", []), 1):
pg = t.get("best_request_rate_per_gpu")
if pg is not None and (incumbent is None or pg > incumbent):
incumbent = pg
pgs = f"{pg:.4f}" if isinstance(pg, (int, float)) else str(pg)
incs = f"{incumbent:.4f}" if isinstance(incumbent, (int, float)) else str(incumbent)
print(
f"{i:<5}{t.get('trial_id',''):<11}{str(t.get('status','')):<14}{pgs:<10}{incs:<11}{topo(t.get('config_patch'), base_flags)}"
)
# also dump proposals dir to see what was *proposed* (incl. vetoed/failed)
pdir = store / "proposals"
if pdir.exists():
print("\n-- proposal files (chronological) --")
for p in sorted(pdir.glob("*.json")):
try:
pr = json.load(open(p))
except Exception:
continue
print(f" {p.stem}: should_stop={pr.get('should_stop')} | {topo(pr.get('config_patch'), base_flags)}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Criterion-A time_scale calibration.
Binary-search the smallest replay_time_scale whose A-family L-C-A similarity to the
real (scale=1.0) arrival process stays >= tau. Uniform time scaling distorts only
the A axis (rate + fano; interarrival CV is scale-invariant), so this bounds the
arrival-axis distortion introduced by compression using the same similarity metric
Stop-A uses. Pure trace metadata -> deterministic, no GPU needed.
Usage:
PYTHONPATH=src python3 scripts/calibrate_time_scale.py \
--trace trace_windows/traces/chat_w20260311_1000.jsonl \
--gpu-count 8 --min-input 0 --max-input 8192 --tau 0.9
"""
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
from aituner.lca import _family_similarity, build_workload_profile
from aituner.trace import TraceRequest, WindowRecord
def load_rows(path: Path, lo: int, hi: int) -> list[dict]:
with path.open(encoding="utf-8") as fh:
rows = [json.loads(l) for l in fh if l.strip()]
return [r for r in rows if lo <= int(r["input_length"]) <= hi]
def build_requests(rows: list[dict]) -> tuple[list[TraceRequest], float, float]:
reqs = []
for i, r in enumerate(rows):
reqs.append(
TraceRequest(
row_id=str(r.get("chat_id", i)),
arrival_s=float(r["timestamp"]),
sampling_u=float(r.get("sampling_u", 0.0)),
body={},
prompt_tokens_hint=int(r["input_length"]),
completion_tokens_hint=int(r["output_length"]),
metadata={"hash_ids": r.get("hash_ids") if isinstance(r.get("hash_ids"), list) else None},
)
)
amin = min(x.arrival_s for x in reqs)
amax = max(x.arrival_s for x in reqs)
return reqs, amin, amax
def profile_at(reqs, amin, amax, gpu_count, scale):
rs = [
TraceRequest(
x.row_id, (x.arrival_s - amin) * scale, x.sampling_u, x.body,
x.prompt_tokens_hint, x.completion_tokens_hint, x.metadata,
)
for x in reqs
]
span = (amax - amin) * scale
w = WindowRecord(
window_id="w", trace_path="", trace_type="chat",
window_start=0.0, window_end=span, source_payload={"block_size": 64},
)
return build_workload_profile(rs, w, gpu_count=gpu_count, length_mode="total")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--trace", type=Path, required=True)
ap.add_argument("--gpu-count", type=int, default=8)
ap.add_argument("--min-input", type=int, default=0)
ap.add_argument("--max-input", type=int, default=8192)
ap.add_argument("--tau", type=float, default=0.9)
args = ap.parse_args()
rows = load_rows(args.trace, args.min_input, args.max_input)
reqs, amin, amax = build_requests(rows)
print(f"n={len(reqs)} raw arrival span={amax - amin:.1f}s")
base = profile_at(reqs, amin, amax, args.gpu_count, 1.0)
print(f"{'scale':>6} {'simA':>7} {'rate/gpu':>9} {'fano':>8} {'span_s':>8}")
for s in (1.0, 0.95, 0.9, 0.85, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2):
p = profile_at(reqs, amin, amax, args.gpu_count, s)
a = _family_similarity(base.vector, p.vector)["A"]
print(f"{s:6.2f} {a:7.3f} {math.expm1(p.vector[7]):9.3f} {math.expm1(p.vector[9]):8.2f} {(amax-amin)*s:8.1f}")
lo, hi = 0.05, 1.0
for _ in range(40):
mid = (lo + hi) / 2
a = _family_similarity(base.vector, profile_at(reqs, amin, amax, args.gpu_count, mid).vector)["A"]
if a >= args.tau:
hi = mid
else:
lo = mid
print(f"\nsmallest scale with simA>={args.tau}: {hi:.4f} (arrival span {(amax-amin)*hi:.0f}s)")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -92,17 +92,39 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args()
def stable_uniform(*, seed: int, window_id: str, index: int, row: dict[str, Any]) -> float:
def resolve_session_root(row: dict[str, Any], root_of: dict[Any, Any]) -> Any:
"""Resolve the session root chat_id for a trace row.
Sessions are multi-turn chains linked via parent_chat_id (turn>1 points to the
parent turn's chat_id, the root turn has parent_chat_id=-1). Because parent
turns precede their children in time, a single streaming pass that records
chat_id -> root resolves the full chain. Rows whose parent is not yet known
(e.g. it fell outside the materialized span) fall back to the parent id so
siblings still group together.
"""
chat_id = row.get("chat_id")
parent = row.get("parent_chat_id")
parent_is_root = (
parent is None
or (isinstance(parent, (int, float)) and not isinstance(parent, bool) and int(parent) < 0)
)
root = chat_id if parent_is_root else root_of.get(parent, parent)
if chat_id is not None:
root_of[chat_id] = root
return root
def session_uniform(*, seed: int, window_id: str, session_root: Any) -> float:
"""Deterministic per-session uniform score in [0, 1).
All turns of a session share one score, so thresholding sampling_u keeps or
drops whole sessions and preserves intra-session prefix (KV-cache) reuse.
"""
payload = json.dumps(
{
"seed": seed,
"window_id": window_id,
"index": index,
"timestamp": row.get("timestamp"),
"input_length": row.get("input_length"),
"output_length": row.get("output_length"),
"chat_id": row.get("chat_id"),
"turn": row.get("turn"),
"session_root": session_root,
},
sort_keys=True,
separators=(",", ":"),
@@ -241,12 +263,16 @@ def materialize_windows(
bucket = grouped[(trace_path, prompt_path)]
bucket.sort(key=lambda item: (float(item["window_start"]), str(item["window_id"])))
matched_rows = 0
root_of: dict[Any, Any] = {}
with trace_path.open() as trace_handle, prompt_path.open() as prompt_handle:
for trace_raw, prompt_raw in zip(trace_handle, prompt_handle):
trace_raw = trace_raw.strip()
if not trace_raw:
continue
trace_row = json.loads(trace_raw)
# Resolve session linkage for every row (even unmatched ones)
# so multi-turn chains crossing the window edge still group.
session_root = resolve_session_root(trace_row, root_of)
timestamp = float(trace_row.get("timestamp") or 0.0)
matched_window: dict[str, Any] | None = None
for window in bucket:
@@ -267,11 +293,11 @@ def materialize_windows(
start = float(matched_window["window_start"])
out["source_timestamp"] = timestamp
out["timestamp"] = timestamp - start
out["sampling_u"] = stable_uniform(
out["session_root"] = session_root
out["sampling_u"] = session_uniform(
seed=sample_seed,
window_id=window_id,
index=stats_by_window[window_id].num_requests,
row=merged,
session_root=session_root,
)
handles[window_id].write(json.dumps(out, ensure_ascii=False) + "\n")
stats_by_window[window_id].record(out)
@@ -311,7 +337,7 @@ def build_output_window(
output["num_excluded_too_long"] = 0
output["sampling_u_field"] = "sampling_u"
output["sampling_seed"] = int(sample_seed)
output["sampling_strategy"] = "fixed_uniform_score"
output["sampling_strategy"] = "session_coherent_uniform_score"
output["first_request_ts"] = stats.first_request_ts
output["last_request_ts"] = stats.last_request_ts
output["first_request_index"] = stats.first_request_index

21
scripts/run_ablation_pair.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Run the harness-ON then naive-OFF tuning loops sequentially (use_harness ablation),
# then drop a DONE marker. Run from the repo root on the GPU host.
set -u
export OPENAI_API_KEY=$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])')
mkdir -p .aituner
rm -rf .aituner/abl-harness .aituner/abl-naive .aituner/ABLATION_DONE
echo "=== harness ON start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_harness_on.json \
--store-root .aituner/abl-harness --max-trials 6 --skip-baseline > .aituner/abl-harness.log 2>&1
echo "=== harness ON done $(date -Is) ==="
echo "=== naive OFF start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_naive_off.json \
--store-root .aituner/abl-naive --max-trials 6 --skip-baseline > .aituner/abl-naive.log 2>&1
echo "=== naive OFF done $(date -Is) ==="
touch .aituner/ABLATION_DONE

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# 12-iteration harness-vs-naive ablation, both arms on dash1 (clean paired run,
# no host confound). Substrate: real output_length (no completion override),
# replay_time_scale=0.8775 (criterion-A, sim_A>=0.90), Stop-A on (LCA offered
# window), per-probe Stop-A-consistent drain deadline. Harness stops early; naive
# runs the full budget. Run from the repo root on dash1.
set -u
# Re-read the codex token from auth.json right before each arm (capturing it once at
# launch goes stale during a long run -- that is what 401'd naive runs 2/3).
read_key() { export OPENAI_API_KEY=$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])'); }
# codex config.toml points at a dash0-local proxy (127.0.0.1:11235); on dash1 the
# LLM endpoint is reachable directly, so force a direct connection.
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
mkdir -p .aituner
rm -rf .aituner/abl12-harness .aituner/abl12-naive .aituner/ABLATION12_DONE
read_key
echo "=== harness ON (12-iter) start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_harness_on.json \
--store-root .aituner/abl12-harness --max-trials 12 --skip-baseline > .aituner/abl12-harness.log 2>&1
echo "=== harness ON (12-iter) done $(date -Is) ==="
read_key
echo "=== naive OFF (12-iter) start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_naive_off.json \
--store-root .aituner/abl12-naive --max-trials 12 --skip-baseline > .aituner/abl12-naive.log 2>&1
echo "=== naive OFF (12-iter) done $(date -Is) ==="
touch .aituner/ABLATION12_DONE

View File

@@ -10,6 +10,7 @@ from aituner.llm import (
load_capability_profile,
parse_proposal_text,
)
from aituner.lca import build_study_workload_profile
from aituner.spec import load_study_spec
from aituner.store import StudyStore
from aituner.trace import load_trace_requests, summarize_window
@@ -89,6 +90,7 @@ def main() -> int:
window_summary=summarize_window(requests, window),
state=state,
capability_profile=capability_profile,
workload_profile=build_study_workload_profile(study, requests, window),
)
prompt_name = f"prompt-{state.next_trial_index:04d}"
store.write_prompt(study.study_id, prompt_name, prompt)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Clean same-policy harness-vs-naive ablation on dash1.
#
# This is intended as the first robustness gate for harness evaluation:
# both arms use the same study substrate and the same configured LLM endpoint;
# the only intended difference is llm.use_harness.
set -euo pipefail
RUN_LABEL="${AITUNER_RUN_ID:-qwen27b-clean-pair-$(date -u +%Y%m%dT%H%M%SZ)}"
MAX_TRIALS="${MAX_TRIALS:-12}"
ROOT="$(pwd)"
HARNESS_STORE=".aituner/${RUN_LABEL}-harness"
NAIVE_STORE=".aituner/${RUN_LABEL}-naive"
REPORT_ROOT=".aituner-reports/${RUN_LABEL}"
SPEC_PATH=".aituner-reports/${RUN_LABEL}.spec.json"
read_key() {
if [ -z "${OPENAI_API_KEY:-}" ]; then
export OPENAI_API_KEY
OPENAI_API_KEY="$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])')"
fi
}
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
mkdir -p .aituner .aituner-reports
rm -rf "${HARNESS_STORE}" "${NAIVE_STORE}" "${REPORT_ROOT}" "${SPEC_PATH}"
read_key
echo "=== harness ON clean pair start $(date -Is) label=${RUN_LABEL} ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_harness_on.json \
--store-root "${HARNESS_STORE}" --max-trials "${MAX_TRIALS}" --skip-baseline \
> ".aituner/${RUN_LABEL}-harness.log" 2>&1
echo "=== harness ON clean pair done $(date -Is) ==="
read_key
echo "=== naive OFF clean pair start $(date -Is) label=${RUN_LABEL} ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_naive_off.json \
--store-root "${NAIVE_STORE}" --max-trials "${MAX_TRIALS}" --skip-baseline \
> ".aituner/${RUN_LABEL}-naive.log" 2>&1
echo "=== naive OFF clean pair done $(date -Is) ==="
python3 - <<PY
import json
from pathlib import Path
root = Path("${ROOT}")
run_label = "${RUN_LABEL}"
spec = {
"report_id": run_label,
"output_root": str(root / "${REPORT_ROOT}"),
"target_fraction": 0.95,
"min_final_ratio": 0.98,
"cases": [
{
"case_id": "qwen27b-chat-0-8k-clean-gpt55",
"description": "Clean same-policy gpt-5.5 harness-vs-naive pair on dash1.",
"tags": ["qwen27b", "chat", "0-8k", "h20", "clean-pair", "gpt-5.5"],
"budgets": [1, 2, 3, 4, 6, 8, 12],
"arms": [
{
"name": "harness",
"kind": "harness",
"study_root": str(root / "${HARNESS_STORE}" / "dash0-qwen27b-ablation-harness-on"),
},
{
"name": "naive",
"kind": "naive",
"study_root": str(root / "${NAIVE_STORE}" / "dash0-qwen27b-ablation-naive-off"),
},
],
}
],
}
Path("${SPEC_PATH}").write_text(json.dumps(spec, indent=2) + "\\n", encoding="utf-8")
PY
PYTHONPATH=src python3 scripts/tuning_report.py --spec "${SPEC_PATH}"
touch ".aituner/${RUN_LABEL}.DONE"
echo "=== clean pair report ready ${REPORT_ROOT} $(date -Is) ==="

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env bash
# Run a clean same-policy harness-vs-naive pair from one or two base specs.
#
# Required env:
# RUN_LABEL
# CASE_ID
# HARNESS_BASE_SPEC
#
# Optional env:
# NAIVE_BASE_SPEC defaults to HARNESS_BASE_SPEC
# MAX_TRIALS defaults to 12
# CASE_DESCRIPTION
# CASE_TAGS_JSON JSON list, defaults to []
# BUDGETS_JSON JSON list, defaults to [1,2,3,4,6,8,MAX_TRIALS]
# COMMON_SPEC_PATCH_FILE JSON deep-merged into both generated specs
# HARNESS_SPEC_PATCH_FILE JSON deep-merged into harness generated spec
# NAIVE_SPEC_PATCH_FILE JSON deep-merged into naive generated spec
set -euo pipefail
RUN_LABEL="${RUN_LABEL:?RUN_LABEL is required}"
CASE_ID="${CASE_ID:?CASE_ID is required}"
HARNESS_BASE_SPEC="${HARNESS_BASE_SPEC:?HARNESS_BASE_SPEC is required}"
NAIVE_BASE_SPEC="${NAIVE_BASE_SPEC:-${HARNESS_BASE_SPEC}}"
MAX_TRIALS="${MAX_TRIALS:-12}"
CASE_DESCRIPTION="${CASE_DESCRIPTION:-Clean same-policy harness-vs-naive pair.}"
CASE_TAGS_JSON="${CASE_TAGS_JSON:-[]}"
BUDGETS_JSON="${BUDGETS_JSON:-}"
ROOT="$(pwd)"
RUN_CONFIG_ROOT=".aituner-run-configs/${RUN_LABEL}"
HARNESS_SPEC="${RUN_CONFIG_ROOT}/harness.json"
NAIVE_SPEC="${RUN_CONFIG_ROOT}/naive.json"
HARNESS_STORE=".aituner/${RUN_LABEL}-harness"
NAIVE_STORE=".aituner/${RUN_LABEL}-naive"
REPORT_ROOT=".aituner-reports/${RUN_LABEL}"
REPORT_SPEC=".aituner-reports/${RUN_LABEL}.spec.json"
export RUN_LABEL CASE_ID HARNESS_BASE_SPEC NAIVE_BASE_SPEC MAX_TRIALS CASE_DESCRIPTION
export CASE_TAGS_JSON BUDGETS_JSON ROOT RUN_CONFIG_ROOT HARNESS_SPEC NAIVE_SPEC
export HARNESS_STORE NAIVE_STORE REPORT_ROOT REPORT_SPEC
read_key() {
if [ -z "${OPENAI_API_KEY:-}" ]; then
export OPENAI_API_KEY
OPENAI_API_KEY="$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])')"
fi
}
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
mkdir -p "${RUN_CONFIG_ROOT}" .aituner .aituner-reports
rm -rf "${HARNESS_STORE}" "${NAIVE_STORE}" "${REPORT_ROOT}" "${REPORT_SPEC}"
python3 - <<'PY'
import json
import os
from pathlib import Path
from typing import Any
def deep_merge(base: dict[str, Any], patch: dict[str, Any]) -> dict[str, Any]:
merged = dict(base)
for key, value in patch.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = deep_merge(merged[key], value)
else:
merged[key] = value
return merged
def load_patch(env_name: str) -> dict[str, Any]:
path = os.environ.get(env_name)
if not path:
return {}
payload = json.loads(Path(path).read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise SystemExit(f"{env_name} must point to a JSON object")
return payload
def generated_spec(base_path: str, *, use_harness: bool, suffix: str, arm_patch: dict[str, Any]) -> dict[str, Any]:
base = json.loads(Path(base_path).read_text(encoding="utf-8"))
if not isinstance(base, dict):
raise SystemExit(f"{base_path} must contain a JSON object")
common = load_patch("COMMON_SPEC_PATCH_FILE")
spec = deep_merge(base, common)
spec = deep_merge(spec, arm_patch)
spec["study_id"] = str(spec.get("study_id") or os.environ["CASE_ID"]) + f"-{suffix}"
llm = dict(spec.get("llm") or {})
llm["use_harness"] = use_harness
spec["llm"] = llm
return spec
run_config_root = Path(os.environ["RUN_CONFIG_ROOT"])
harness = generated_spec(
os.environ["HARNESS_BASE_SPEC"],
use_harness=True,
suffix="harness",
arm_patch=load_patch("HARNESS_SPEC_PATCH_FILE"),
)
naive = generated_spec(
os.environ["NAIVE_BASE_SPEC"],
use_harness=False,
suffix="naive",
arm_patch=load_patch("NAIVE_SPEC_PATCH_FILE"),
)
(run_config_root / "harness.json").write_text(json.dumps(harness, indent=2) + "\n", encoding="utf-8")
(run_config_root / "naive.json").write_text(json.dumps(naive, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"harness_study_id": harness["study_id"], "naive_study_id": naive["study_id"]}, ensure_ascii=False))
PY
read_key
echo "=== harness clean pair start $(date -Is) label=${RUN_LABEL} ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec "${HARNESS_SPEC}" \
--store-root "${HARNESS_STORE}" --max-trials "${MAX_TRIALS}" --skip-baseline \
> ".aituner/${RUN_LABEL}-harness.log" 2>&1
echo "=== harness clean pair done $(date -Is) ==="
read_key
echo "=== naive clean pair start $(date -Is) label=${RUN_LABEL} ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec "${NAIVE_SPEC}" \
--store-root "${NAIVE_STORE}" --max-trials "${MAX_TRIALS}" --skip-baseline \
> ".aituner/${RUN_LABEL}-naive.log" 2>&1
echo "=== naive clean pair done $(date -Is) ==="
python3 - <<'PY'
import json
import os
from pathlib import Path
root = Path(os.environ["ROOT"])
run_label = os.environ["RUN_LABEL"]
harness = json.loads(Path(os.environ["HARNESS_SPEC"]).read_text(encoding="utf-8"))
naive = json.loads(Path(os.environ["NAIVE_SPEC"]).read_text(encoding="utf-8"))
max_trials = int(os.environ["MAX_TRIALS"])
budgets_text = os.environ.get("BUDGETS_JSON") or ""
if budgets_text:
budgets = json.loads(budgets_text)
else:
budgets = [1, 2, 3, 4, 6, 8, max_trials]
budgets = sorted({int(item) for item in budgets if int(item) > 0})
tags = json.loads(os.environ.get("CASE_TAGS_JSON") or "[]")
spec = {
"report_id": run_label,
"output_root": str(root / os.environ["REPORT_ROOT"]),
"target_fraction": 0.95,
"min_final_ratio": 0.98,
"cases": [
{
"case_id": os.environ["CASE_ID"],
"description": os.environ["CASE_DESCRIPTION"],
"tags": tags,
"budgets": budgets,
"arms": [
{
"name": "harness",
"kind": "harness",
"study_root": str(
root / os.environ["HARNESS_STORE"] / harness["study_id"]
),
},
{
"name": "naive",
"kind": "naive",
"study_root": str(root / os.environ["NAIVE_STORE"] / naive["study_id"]),
},
],
}
],
}
Path(os.environ["REPORT_SPEC"]).write_text(json.dumps(spec, indent=2) + "\n", encoding="utf-8")
PY
PYTHONPATH=src python3 scripts/tuning_report.py --spec "${REPORT_SPEC}"
touch ".aituner/${RUN_LABEL}.DONE"
echo "=== clean pair report ready ${REPORT_ROOT} $(date -Is) ==="

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Harness-only re-run on gpt-5.5 to EMPIRICALLY verify the gpu-memory-utilization fix:
# success = the harness recovers ~0.87/GPU (climbs gpu-mem-util to ~0.94) and then stops,
# matching the naive-discovered ground truth. Run from the repo root on dash1.
set -u
read_key() { export OPENAI_API_KEY=$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])'); }
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
mkdir -p .aituner
rm -rf .aituner/abl12-harness .aituner/abl12-harness.log .aituner/HARNESS_ONLY_DONE
read_key
echo "=== harness ON (gpt-5.5, gpu-mem-util fix) start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_harness_on.json \
--store-root .aituner/abl12-harness --max-trials 12 --skip-baseline > .aituner/abl12-harness.log 2>&1
echo "=== harness ON done $(date -Is) ==="
touch .aituner/HARNESS_ONLY_DONE

15
scripts/run_naive_d1.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Complete the naive-OFF ablation arm to full budget on a fresh store (dash1).
set -u
export OPENAI_API_KEY=$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])')
# codex config.toml points at a local proxy (127.0.0.1:11235) that exists on dash0 but
# not dash1; the LLM endpoint is reachable directly, so force a direct connection.
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
mkdir -p .aituner
rm -rf .aituner/abl-naive-d1 .aituner/ABL_NAIVE_D1_DONE
echo "=== naive OFF (dash1) start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_naive_off.json \
--store-root .aituner/abl-naive-d1 --max-trials 6 --skip-baseline > .aituner/abl-naive-d1.log 2>&1
echo "=== naive OFF (dash1) done $(date -Is) ==="
touch .aituner/ABL_NAIVE_D1_DONE

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Fig-18 naive nondeterminism: after the main pair (ABLATION12_DONE) finishes, run
# 2 more naive arms (runs 2 and 3) on the SAME substrate. The naive LLM (gpt-5.4,
# use_harness=false) is nondeterministic, so the run-to-run spread (fail / slow /
# lucky) is the result. Harness arm stays a single deterministic curve. Run from
# the repo root on dash1; survives disconnect via setsid/nohup at launch.
set -u
export OPENAI_API_KEY=$(python3 -c 'import json,pathlib;print(json.load(open(pathlib.Path.home()/".codex/auth.json"))["OPENAI_API_KEY"])')
export http_proxy= https_proxy= all_proxy= HTTP_PROXY= HTTPS_PROXY= ALL_PROXY= no_proxy='*'
# Wait for the main harness+naive(run1) pair to complete so we never contend for GPUs.
echo "=== waiting for ABLATION12_DONE $(date -Is) ==="
while [ ! -f .aituner/ABLATION12_DONE ]; do sleep 120; done
echo "=== main pair done, starting naive repeats $(date -Is) ==="
for r in 2 3; do
rm -rf ".aituner/abl12-naive${r}" ".aituner/abl12-naive${r}.log"
echo "=== naive run ${r} start $(date -Is) ==="
PYTHONPATH=src python3 -m aituner.cli study tune \
--spec configs/examples/dash0_qwen27b_ablation_naive_off.json \
--store-root ".aituner/abl12-naive${r}" --max-trials 12 --skip-baseline > ".aituner/abl12-naive${r}.log" 2>&1
echo "=== naive run ${r} done $(date -Is) ==="
done
touch .aituner/NAIVE_REPEATS_DONE
echo "=== all naive repeats done $(date -Is) ==="

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Stop-A calibration: print the offered-L-C-A convergence curve for a raw trace window.
The convergence of prefix-vs-full L-C-A is a deterministic property of the trace
metadata (lengths, hash_ids, arrivals), so this runs on CPU without serving the
model. Use it to pick tau / tau_c / stable_checks and to compare how late the C
dimension converges across workloads (e.g. low-reuse chat vs high-reuse coder).
Example:
PYTHONPATH=src python3 scripts/stop_a_calibration.py \
--jsonl /dashscope/.../qwen_chat_blksz_64_032109-032111.jsonl \
--block-size 64 --window-start 3600 --window-end 4200 --gpu-count 8 --label chat
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from aituner.lca import find_convergence_prefix, resolve_length_mode
from aituner.trace import TraceRequest, WindowRecord
def _session_root(row: dict, root_of: dict) -> object:
chat_id = row.get("chat_id")
parent = row.get("parent_chat_id")
parent_is_root = parent is None or (
isinstance(parent, (int, float)) and not isinstance(parent, bool) and int(parent) < 0
)
root = chat_id if parent_is_root else root_of.get(parent, parent)
if chat_id is not None:
root_of[chat_id] = root
return root
def load_window(jsonl: Path, *, window_start: float, window_end: float) -> list[TraceRequest]:
root_of: dict = {}
requests: list[TraceRequest] = []
with jsonl.open(encoding="utf-8") as handle:
for line in handle:
line = line.strip()
if not line:
continue
row = json.loads(line)
_session_root(row, root_of) # keep chain complete even outside the window
ts = float(row.get("timestamp") or 0.0)
if not (window_start <= ts < window_end):
continue
hash_ids = row.get("hash_ids")
requests.append(
TraceRequest(
row_id=str(row.get("chat_id")),
arrival_s=ts - window_start,
sampling_u=1.0,
body={},
prompt_tokens_hint=int(row.get("input_length") or 0),
completion_tokens_hint=int(row.get("output_length") or 0),
metadata={"hash_ids": hash_ids if isinstance(hash_ids, list) else None},
)
)
requests.sort(key=lambda item: item.arrival_s)
return requests
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--jsonl", type=Path, required=True)
ap.add_argument("--block-size", type=int, required=True)
ap.add_argument("--window-start", type=float, default=3600.0)
ap.add_argument("--window-end", type=float, default=4200.0)
ap.add_argument("--gpu-count", type=int, default=8)
ap.add_argument("--length-mode", default="total")
ap.add_argument("--label", default="")
ap.add_argument("--tau", type=float, default=0.9)
ap.add_argument("--max-checks", type=int, default=20)
args = ap.parse_args()
requests = load_window(
args.jsonl, window_start=args.window_start, window_end=args.window_end
)
window = WindowRecord(
window_id=args.label or args.jsonl.stem,
trace_path=args.jsonl,
trace_type=args.label or "chat",
window_start=0.0,
window_end=float(args.window_end - args.window_start),
source_payload={"block_size": args.block_size},
)
mode = resolve_length_mode(length_mode=args.length_mode)
rows_with_hash = sum(1 for r in requests if r.metadata.get("hash_ids"))
print(
f"[{args.label}] requests={len(requests)} rows_with_hash_ids={rows_with_hash} "
f"window={args.window_start:.0f}-{args.window_end:.0f}s block_size={args.block_size}"
)
# Full curve (tau_c high so it never short-circuits; we read the curve directly).
point = find_convergence_prefix(
requests, window, gpu_count=args.gpu_count, length_mode=mode,
tau=args.tau, tau_c=1.01, stable_checks=10_000, max_checks=args.max_checks,
min_fraction=0.05,
)
print(" frac time_s L C A")
for c in point.checks:
s = c["family_similarity"]
print(
f" {c['fraction']:.2f} {c['time_s']:7.1f} "
f"{s['L']:.3f} {s['C']:.3f} {s['A']:.3f}"
)
# Stop fraction at candidate tau_c values (L,A >= tau, C >= tau_c, stable for W=3).
print(" -- stop fraction (tau_L=tau_A=%.2f, W=3) --" % args.tau)
for tau_c in (0.85, 0.90, 0.92, 0.95):
p = find_convergence_prefix(
requests, window, gpu_count=args.gpu_count, length_mode=mode,
tau=args.tau, tau_c=tau_c, stable_checks=3, max_checks=args.max_checks,
min_fraction=0.05,
)
verdict = (
f"stop@frac={p.fraction:.2f} t={p.stop_time_s:.0f}s"
if p.converged
else "NEVER (replays full window)"
)
print(f" tau_c={tau_c:.2f}: {verdict}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

101
scripts/stop_a_validate.py Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Validate Stop-A truncation fidelity from a full-replay trial's probe_details.
Given a completed trial that replayed the full window (adaptive_stop disabled), for
each probe recompute the L-C-A convergence prefix and compare the feasibility
verdict / pass-rate of the truncated prefix against the full probe. This answers:
"would Stop-A have changed the measured peak-sustainable-rate?" using only the one
full run (no second GPU run needed).
Example:
PYTHONPATH=src python3 scripts/stop_a_validate.py \
--spec configs/examples/dash0_qwen30b_a3b_stopA_fulldata.json \
--store-root .aituner/stopA-fulldata --tau 0.9 --tau-c 0.90
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from aituner.lca import find_convergence_prefix, resolve_length_mode
from aituner.spec import load_study_spec
from aituner.trace import load_trace_requests, select_requests_for_threshold
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--spec", type=Path, required=True)
ap.add_argument("--store-root", type=Path, required=True)
ap.add_argument("--tau", type=float, default=0.9)
ap.add_argument("--tau-c", type=float, default=0.90)
ap.add_argument("--stable-checks", type=int, default=3)
ap.add_argument("--target-pass-rate", type=float, default=0.95)
args = ap.parse_args()
study = load_study_spec(args.spec)
window, requests = load_trace_requests(study, study_spec_path=args.spec)
mode = resolve_length_mode(request_mode=study.trace.request_mode)
gpu_count = study.hardware.gpu_count
detail_files = sorted(args.store_root.glob("*/trials/*/probe_details.jsonl"))
if not detail_files:
print(f"no probe_details.jsonl under {args.store_root}")
return 1
print(f"target_pass_rate={args.target_pass_rate} tau={args.tau} tau_c={args.tau_c}")
print(
"thresh n_full stop_idx frac full_pass prefix_pass "
"full_feas prefix_feas verdict_match"
)
mismatches = 0
total = 0
saved_fractions = []
for detail_file in detail_files:
with detail_file.open(encoding="utf-8") as handle:
for line in handle:
line = line.strip()
if not line:
continue
probe = json.loads(line)
threshold = float(probe["threshold"])
outcomes = probe.get("outcomes") or []
# arrival-ordered outcomes that carry an arrival_s and verdict
ordered = sorted(
(o for o in outcomes if o.get("arrival_s") is not None),
key=lambda o: float(o["arrival_s"]),
)
n = len(ordered)
if n == 0:
continue
selected = select_requests_for_threshold(requests, threshold=threshold)
cp = find_convergence_prefix(
selected, window, gpu_count=gpu_count, length_mode=mode,
tau=args.tau, tau_c=args.tau_c, stable_checks=args.stable_checks,
)
# Map the convergence prefix fraction onto the replayed outcomes.
stop_n = max(1, min(n, round(cp.fraction * n)))
full_pass = sum(1 for o in ordered if o.get("evaluation")) / n
prefix_pass = sum(1 for o in ordered[:stop_n] if o.get("evaluation")) / stop_n
full_feas = full_pass >= args.target_pass_rate
prefix_feas = prefix_pass >= args.target_pass_rate
match = full_feas == prefix_feas
total += 1
mismatches += 0 if match else 1
saved_fractions.append(1.0 - cp.fraction)
print(
f"{threshold:.5f} {n:6d} {stop_n:7d} {cp.fraction:.2f} "
f"{full_pass:.3f} {prefix_pass:.3f} "
f"{str(full_feas):5s} {str(prefix_feas):5s} {match}"
)
if total:
avg_saved = sum(saved_fractions) / len(saved_fractions)
print(
f"\nverdict matches: {total - mismatches}/{total} "
f"mean replay saved: {avg_saved*100:.0f}%"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

36
scripts/tuning_report.py Normal file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from pathlib import Path
from aituner.tuning_report import run_tuning_report
def main() -> int:
parser = argparse.ArgumentParser(
description="Summarize anytime tuning progress across harness/naive study stores."
)
parser.add_argument("--spec", required=True, help="Path to a tuning report JSON spec.")
args = parser.parse_args()
summary = run_tuning_report(Path(args.spec))
print(
json.dumps(
{
"report_id": summary["report_id"],
"report_root": summary["report_root"],
"case_count": summary["aggregate"]["case_count"],
"harness_vs_naive_pass_count": summary["aggregate"]["harness_vs_naive_pass_count"],
"harness_vs_naive_check_count": summary["aggregate"]["harness_vs_naive_check_count"],
"winner_counts": summary["aggregate"]["winner_counts"],
},
ensure_ascii=False,
indent=2,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import argparse
import json
import sys
from dataclasses import replace
from pathlib import Path
from .compare import run_compare
@@ -12,13 +13,108 @@ from .harness import (
build_harness_stop_proposal,
)
from .job import append_job, build_trial_job
from .lca import (
build_study_workload_profile,
build_workload_profile,
resolve_length_mode,
similarity_report,
)
from .llm import build_prompt, call_llm_for_proposal, load_capability_profile, parse_proposal_text
from .spec import Proposal, SpecError, load_study_spec, to_jsonable
from .spec import (
Proposal,
SpecError,
StudySpec,
load_structured_file,
load_study_spec,
to_jsonable,
)
from .store import StudyStore
from .trace import load_trace_requests, summarize_window
from .worker import run_trial
def _is_empty_config_patch(proposal: Proposal) -> bool:
return not proposal.config_patch.env_patch and not proposal.config_patch.flag_patch
def _latency_percentiles(summary: object, metric: str) -> dict[str, float]:
if not isinstance(summary, dict):
return {}
payload = summary.get(metric)
if not isinstance(payload, dict):
return {}
selected: dict[str, float] = {}
for key in ("mean", "p50", "p95", "p99"):
value = payload.get(key)
if isinstance(value, (int, float)):
selected[key] = float(value)
return selected
def _format_latency_percentiles(metric: str, values: dict[str, float]) -> str:
if not values:
return ""
ordered = ", ".join(
f"{key}={values[key]:.3f}"
for key in ("mean", "p50", "p95", "p99")
if key in values
)
return f"{metric}({ordered})"
def _baseline_all_infeasible_stop(result: dict[str, object]) -> tuple[str, dict[str, object]] | None:
if result.get("status") != "completed":
return None
if isinstance(result.get("best_request_rate"), (int, float)):
return None
probes = result.get("probes")
if not isinstance(probes, list) or not probes:
return None
if any(isinstance(probe, dict) and probe.get("feasible") for probe in probes):
return None
diagnostics = result.get("all_infeasible_diagnostics")
if not isinstance(diagnostics, dict):
diagnostics = {}
lowest_rate = diagnostics.get("request_rate")
lowest_threshold = diagnostics.get("threshold")
pass_rate = diagnostics.get("pass_rate")
early_stop_reason = str(diagnostics.get("early_stop_reason") or "").strip()
latency_summary = diagnostics.get("latency_summary")
ttft = _latency_percentiles(latency_summary, "ttft_ms")
tpot = _latency_percentiles(latency_summary, "tpot_ms")
details: dict[str, object] = {
"lowest_sampled_request_rate": lowest_rate,
"lowest_sampling_u": lowest_threshold,
"lowest_probe_pass_rate": pass_rate,
"early_stop_reason": early_stop_reason,
"lowest_probe_latency_ms": {
"ttft": ttft,
"tpot": tpot,
},
"lowest_probe_latency_summary": latency_summary if isinstance(latency_summary, dict) else {},
}
pieces = [
"Baseline configuration has no feasible probe under the current SLO.",
"Stopping tuning because even the lowest sampled request rate did not meet the target pass rate.",
]
if isinstance(lowest_rate, (int, float)):
pieces.append(f"lowest_sampled_request_rate={float(lowest_rate):.6g}")
if isinstance(lowest_threshold, (int, float)):
pieces.append(f"lowest_sampling_u={float(lowest_threshold):.6g}")
if isinstance(pass_rate, (int, float)):
pieces.append(f"lowest_probe_pass_rate={float(pass_rate):.6g}")
if early_stop_reason:
pieces.append(f"early_stop_reason={early_stop_reason}")
for item in (
_format_latency_percentiles("lowest_probe_ttft_ms", ttft),
_format_latency_percentiles("lowest_probe_tpot_ms", tpot),
):
if item:
pieces.append(item)
return " ".join(pieces), details
def _study_source_path(study_root: Path) -> Path:
return Path((study_root / "study_spec.source").read_text(encoding="utf-8").strip())
@@ -45,6 +141,7 @@ def cmd_study_prompt(args: argparse.Namespace) -> int:
window_summary=summarize_window(requests, window),
state=state,
capability_profile=capability_profile,
workload_profile=build_study_workload_profile(study, requests, window),
)
prompt_name = args.prompt_name or f"prompt-{state.next_trial_index:04d}"
path = store.write_prompt(study.study_id, prompt_name, prompt)
@@ -65,8 +162,13 @@ def cmd_study_llm_propose(args: argparse.Namespace) -> int:
window_summary=summarize_window(requests, window),
state=state,
capability_profile=capability_profile,
workload_profile=build_study_workload_profile(study, requests, window),
)
proposal_text = call_llm_for_proposal(
policy=study.llm,
prompt=prompt,
use_harness=study.llm.use_harness,
)
proposal_text = call_llm_for_proposal(policy=study.llm, prompt=prompt)
proposal = parse_proposal_text(proposal_text, study)
name = args.proposal_name or f"proposal-{state.next_trial_index:04d}"
path = store.write_proposal(study.study_id, name, proposal)
@@ -124,15 +226,34 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
if proposal_files and max_trials > len(proposal_files):
max_trials = len(proposal_files)
executed: list[dict[str, object]] = []
stop_vetoes = 0
max_llm_stop_vetoes = 1
for idx in range(max_trials):
state = store.load_state(study.study_id)
if state.tuning_stop_reason:
executed.append(
{
"trial_id": None,
"stopped": True,
"reason": state.tuning_stop_reason,
"diagnosis": state.tuning_stop_diagnosis,
"details": state.tuning_stop_details,
"state_best_trial_id": state.best_trial_id,
"state_best_request_rate": state.best_request_rate,
}
)
break
if state.next_trial_index > max_trials:
break
window, requests = load_trace_requests(study, study_spec_path=spec_path)
window_summary = summarize_window(requests, window)
workload_profile = build_study_workload_profile(study, requests, window)
harness_context = (
build_harness_context(
study=study,
window_summary=window_summary,
state=state,
workload_profile=workload_profile,
)
if study.llm.use_harness
else None
@@ -142,6 +263,7 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
window_summary=window_summary,
state=state,
capability_profile=capability_profile,
workload_profile=workload_profile,
)
prompt_name = f"prompt-{state.next_trial_index:04d}"
store.write_prompt(study.study_id, prompt_name, prompt)
@@ -169,7 +291,10 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
ensure_ascii=False,
)
elif proposal_files:
proposal_source = proposal_files[idx]
proposal_index = state.next_trial_index - 1
if proposal_index >= len(proposal_files):
break
proposal_source = proposal_files[proposal_index]
proposal_text = proposal_source.read_text(encoding="utf-8")
proposal_name = proposal_source.stem
else:
@@ -200,29 +325,95 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
"No proposal files provided, study.llm.endpoint is not configured, "
"and the harness stop guard did not fire."
)
proposal_text = call_llm_for_proposal(policy=study.llm, prompt=prompt)
proposal_text = call_llm_for_proposal(
policy=study.llm,
prompt=prompt,
use_harness=study.llm.use_harness,
)
proposal_name = f"proposal-{state.next_trial_index:04d}"
raw_proposal_path = store.study_root(study.study_id) / "proposals" / f"{proposal_name}.raw.txt"
raw_proposal_path.write_text(proposal_text, encoding="utf-8")
proposal = parse_proposal_text(proposal_text, study)
store.write_proposal(study.study_id, proposal_name, proposal)
if proposal.should_stop:
if proposal_name.startswith("harness-stop-"):
is_harness_stop = proposal_name.startswith("harness-stop-")
is_llm_stop = not is_harness_stop and proposal_source is None
stop_authority = (
harness_context.get("stop_authority")
if isinstance(harness_context, dict)
else None
)
authorized = stop_authority is None or bool(stop_authority.get("authorized"))
# Stop-B authority: the deterministic validator overrides an
# LLM-originated stop. Veto an unauthorized stop (bounded) so the
# loop does not converge prematurely on the agent's say-so alone.
if is_llm_stop and not authorized and stop_vetoes < max_llm_stop_vetoes:
stop_vetoes += 1
executed.append(
{
"trial_id": None,
"proposal_name": proposal_name,
"proposal_source": "llm",
"stop_vetoed": True,
"reason": "validator_did_not_authorize_stop",
"validator_reason": (
stop_authority.get("reason") if stop_authority else None
),
"diagnosis": proposal.diagnosis,
}
)
continue
if is_harness_stop:
proposal_source_label = "harness"
else:
proposal_source_label = str(proposal_source) if proposal_source else "llm"
stop_authorized_by = (
"validator"
if (is_harness_stop or authorized)
else "file_proposal"
if proposal_source is not None
else "llm_after_veto_budget"
)
stop_reason = (
"harness_stop"
if is_harness_stop
else "proposal_file_stop"
if proposal_source is not None
else "llm_stop"
)
stop_details = {
"proposal_name": proposal_name,
"proposal_source": proposal_source_label,
"stop_authorized_by": stop_authorized_by,
}
if stop_authority:
stop_details["validator_reason"] = stop_authority.get("reason")
state.tuning_stop_reason = stop_reason
state.tuning_stop_diagnosis = proposal.diagnosis
state.tuning_stop_details = stop_details
store.save_state(state)
executed.append(
{
"trial_id": None,
"proposal_name": proposal_name,
"proposal_source": proposal_source_label,
"stopped": True,
"reason": state.tuning_stop_reason,
"stop_authorized_by": stop_authorized_by,
"diagnosis": proposal.diagnosis,
"details": stop_details,
"state_best_trial_id": state.best_trial_id,
"state_best_request_rate": state.best_request_rate,
}
)
break
is_auto_baseline = (
not proposal_files
and not args.skip_baseline
and state.next_trial_index == 1
and not state.trials
and _is_empty_config_patch(proposal)
)
trial, _ = store.materialize_trial(study=study, state=state, proposal=proposal)
trial_spec_path = Path(trial.artifact_dir) / "trial_spec.json"
result = run_trial(trial_spec_path)
@@ -243,6 +434,26 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
"state_best_request_rate": state.best_request_rate,
}
)
if is_auto_baseline:
stop = _baseline_all_infeasible_stop(result)
if stop is not None:
diagnosis, details = stop
state.tuning_stop_reason = "baseline_all_infeasible"
state.tuning_stop_diagnosis = diagnosis
state.tuning_stop_details = details
store.save_state(state)
executed.append(
{
"trial_id": None,
"stopped": True,
"reason": state.tuning_stop_reason,
"diagnosis": diagnosis,
"details": details,
"state_best_trial_id": state.best_trial_id,
"state_best_request_rate": state.best_request_rate,
}
)
break
final_state = store.load_state(study.study_id)
print(
@@ -252,6 +463,9 @@ def cmd_study_tune(args: argparse.Namespace) -> int:
"executed_trials": executed,
"best_trial_id": final_state.best_trial_id,
"best_request_rate": final_state.best_request_rate,
"tuning_stop_reason": final_state.tuning_stop_reason,
"tuning_stop_diagnosis": final_state.tuning_stop_diagnosis,
"tuning_stop_details": final_state.tuning_stop_details,
},
ensure_ascii=False,
)
@@ -284,6 +498,159 @@ def cmd_compare_run(args: argparse.Namespace) -> int:
return 0
def _resolve_profile_gpu_count(args: argparse.Namespace, study: StudySpec) -> int:
gpu_count = args.gpu_count
if gpu_count is None:
gpu_count = study.hardware.gpu_count
if gpu_count <= 0:
raise SpecError("--gpu-count must be > 0.")
return int(gpu_count)
def _load_profile_study_spec(spec_path: Path) -> StudySpec:
payload = dict(load_structured_file(spec_path))
llm_payload = dict(payload.get("llm") or {})
llm_payload.pop("endpoint", None)
payload["llm"] = llm_payload
return StudySpec.from_dict(payload)
def _profile_current_study_window(args: argparse.Namespace) -> dict[str, object]:
spec_path = Path(args.spec).resolve()
study = _load_profile_study_spec(spec_path)
mode = resolve_length_mode(
request_mode=study.trace.request_mode,
length_mode=args.length_mode,
)
window, requests = load_trace_requests(study, study_spec_path=spec_path)
profile = build_workload_profile(
requests,
window,
gpu_count=_resolve_profile_gpu_count(args, study),
length_mode=mode,
)
return {
"profile": profile.to_dict(),
"source": {
"study_spec_path": str(spec_path),
"window_id": study.trace.window_id,
},
}
def _resolve_windows_path_for_profile(study: StudySpec, *, study_spec_path: Path) -> Path:
path = Path(study.trace.windows_path)
if not path.is_absolute():
path = (study_spec_path.parent / path).resolve()
return path
def _load_profile_windows(
study: StudySpec,
*,
study_spec_path: Path,
) -> list[dict[str, object]]:
windows_path = _resolve_windows_path_for_profile(study, study_spec_path=study_spec_path)
payload = json.loads(windows_path.read_text(encoding="utf-8"))
raw_windows = payload.get("windows") if isinstance(payload, dict) else payload
if not isinstance(raw_windows, list):
raise SpecError(f"windows payload must contain a list: {windows_path}")
return [
{str(key): value for key, value in item.items()}
for item in raw_windows
if isinstance(item, dict)
]
def _selected_profile_windows(
args: argparse.Namespace,
study: StudySpec,
*,
study_spec_path: Path,
) -> list[dict[str, object]]:
windows = _load_profile_windows(study, study_spec_path=study_spec_path)
window_ids = set(args.window_id or [])
selected: list[dict[str, object]] = []
for item in windows:
window_id = str(item.get("window_id") or "").strip()
if not window_id:
continue
if window_ids and window_id not in window_ids:
continue
if not window_ids and not args.all:
if window_id != study.trace.window_id:
continue
trace_type = str(item.get("trace_type") or "").strip()
if args.trace_type and trace_type != args.trace_type:
continue
date_value = str(item.get("date") or "").strip()
if args.date_from and date_value and date_value < args.date_from:
continue
if args.date_to and date_value and date_value > args.date_to:
continue
if args.slot_token and str(item.get("slot_token") or "").strip() != args.slot_token:
continue
selected.append(item)
selected.sort(
key=lambda item: (
str(item.get("date") or ""),
str(item.get("slot_token") or ""),
str(item.get("window_id") or ""),
)
)
if args.limit is not None:
selected = selected[: args.limit]
if not selected:
raise SpecError("No trace windows selected for profile similarity.")
return selected
def cmd_profile_window(args: argparse.Namespace) -> int:
print(json.dumps(_profile_current_study_window(args), ensure_ascii=False, indent=2))
return 0
def cmd_profile_similarity(args: argparse.Namespace) -> int:
spec_path = Path(args.spec).resolve()
study = _load_profile_study_spec(spec_path)
mode = resolve_length_mode(
request_mode=study.trace.request_mode,
length_mode=args.length_mode,
)
gpu_count = _resolve_profile_gpu_count(args, study)
profiles = []
selected = _selected_profile_windows(args, study, study_spec_path=spec_path)
for item in selected:
window_id = str(item["window_id"])
window_study = replace(study, trace=replace(study.trace, window_id=window_id))
window, requests = load_trace_requests(window_study, study_spec_path=spec_path)
profiles.append(
build_workload_profile(
requests,
window,
gpu_count=gpu_count,
length_mode=mode,
)
)
print(
json.dumps(
{
"source": {
"study_spec_path": str(spec_path),
"selected_window_count": len(profiles),
"length_mode": mode,
"gpu_count": gpu_count,
},
"profiles": [profile.to_dict() for profile in profiles],
"similarity": similarity_report(profiles),
},
ensure_ascii=False,
indent=2,
)
)
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="AITuner CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
@@ -352,6 +719,50 @@ def build_parser() -> argparse.ArgumentParser:
compare_run.add_argument("--output-root")
compare_run.set_defaults(func=cmd_compare_run)
profile = subparsers.add_parser("profile")
profile_sub = profile.add_subparsers(dest="profile_command", required=True)
profile_window = profile_sub.add_parser("window")
profile_window.add_argument("--spec", required=True)
profile_window.add_argument(
"--length-mode",
default="auto",
choices=["auto", "total", "input", "output"],
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
)
profile_window.add_argument(
"--gpu-count",
type=int,
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
)
profile_window.set_defaults(func=cmd_profile_window)
profile_similarity = profile_sub.add_parser("similarity")
profile_similarity.add_argument("--spec", required=True)
profile_similarity.add_argument("--window-id", action="append")
profile_similarity.add_argument("--trace-type")
profile_similarity.add_argument("--date-from")
profile_similarity.add_argument("--date-to")
profile_similarity.add_argument("--slot-token")
profile_similarity.add_argument("--limit", type=int)
profile_similarity.add_argument(
"--all",
action="store_true",
help="Profile all windows selected by filters. Without this or --window-id, only the study window is used.",
)
profile_similarity.add_argument(
"--length-mode",
default="auto",
choices=["auto", "total", "input", "output"],
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
)
profile_similarity.add_argument(
"--gpu-count",
type=int,
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
)
profile_similarity.set_defaults(func=cmd_profile_similarity)
return parser

File diff suppressed because it is too large Load Diff

View File

@@ -179,6 +179,9 @@ def chat_completion(
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise HttpClientError(f"llm_completion failed: {exc.code} {detail}") from exc
except OSError as exc:
# TimeoutError (socket.timeout), URLError, ConnectionError all subclass OSError.
raise HttpClientError(f"llm_completion failed: {exc}") from exc
def stream_text_completion(
@@ -232,6 +235,8 @@ def stream_text_completion(
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise HttpClientError(f"stream_text_completion failed: {exc.code} {detail}") from exc
except OSError as exc:
raise HttpClientError(f"stream_text_completion failed: {exc}") from exc
return "".join(parts)
@@ -293,6 +298,10 @@ def stream_chat_completion(
except urllib.error.HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise HttpClientError(f"stream_chat_completion failed: {exc.code} {detail}") from exc
except OSError as exc:
# A request that exceeds request_timeout_s raises TimeoutError mid-stream;
# treat it as a failed request (SLO miss), not a crashed trial.
raise HttpClientError(f"stream_chat_completion failed: {exc}") from exc
ttft_ms = None if first_token_at is None else (first_token_at - start) * 1000.0
if completion_tokens is None and chunk_token_count > 0:
completion_tokens = chunk_token_count
@@ -310,7 +319,7 @@ def stream_chat_completion(
return StreamMetrics(
ttft_ms=ttft_ms,
tpot_ms=tpot_ms,
completion_tokens=used_tokens if used_tokens > 0 else None,
completion_tokens=used_tokens if used_tokens is not None and used_tokens > 0 else None,
completion_tokens_source=completion_tokens_source,
streamed_chunk_count=chunk_token_count,
)

577
src/aituner/lca.py Normal file
View File

@@ -0,0 +1,577 @@
from __future__ import annotations
import json
import math
import statistics
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Sequence
from .trace import TraceRequest, WindowRecord
if TYPE_CHECKING:
from .spec import StudySpec
EPSILON = 1e-9
FEATURE_NAMES = [
"L.log_mean_length",
"L.log_p95_over_mean_length",
"L.cv_length",
"C.log_mean_hit_length",
"C.log_p95_over_mean_hit_length",
"C.cv_hit_length",
"C.hit_rate",
"A.log_request_rate_per_gpu",
"A.cv_interarrival",
"A.log_fano_1s",
]
FAMILY_SLICES = {
"L": slice(0, 3),
"C": slice(3, 7),
"A": slice(7, 10),
}
LENGTH_MODES = {"total", "input", "output"}
@dataclass(frozen=True)
class WorkloadProfile:
window_id: str
trace_type: str
request_count: int
duration_s: float
gpu_count: int
length_mode: str
feature_names: list[str]
vector: list[float]
stats: dict[str, Any]
def to_dict(self) -> dict[str, Any]:
return {
"window_id": self.window_id,
"trace_type": self.trace_type,
"request_count": self.request_count,
"duration_s": self.duration_s,
"gpu_count": self.gpu_count,
"length_mode": self.length_mode,
"feature_names": self.feature_names,
"vector": self.vector,
"stats": self.stats,
}
@dataclass(frozen=True)
class RobustScale:
feature_names: list[str]
center: list[float]
scale: list[float]
def transform(self, vector: Sequence[float]) -> list[float]:
return [
(float(value) - self.center[idx]) / self.scale[idx]
for idx, value in enumerate(vector)
]
def to_dict(self) -> dict[str, Any]:
return {
"feature_names": self.feature_names,
"center": self.center,
"scale": self.scale,
}
def resolve_length_mode(*, request_mode: str | None = None, length_mode: str = "auto") -> str:
normalized = str(length_mode or "auto").strip().lower()
if normalized == "auto":
return (
"output"
if str(request_mode or "").strip().lower() == "decode_only"
else "total"
)
if normalized not in LENGTH_MODES:
raise ValueError(
"length_mode must be one of: auto, total, input, output."
)
return normalized
def build_workload_profile(
requests: list[TraceRequest],
window: WindowRecord,
*,
gpu_count: int,
length_mode: str = "total",
) -> WorkloadProfile:
if gpu_count <= 0:
raise ValueError("gpu_count must be > 0.")
if length_mode not in LENGTH_MODES:
raise ValueError(f"Unsupported length_mode: {length_mode}")
duration_s = _duration_s(requests, window)
input_lengths = [float(item.prompt_tokens_hint or 0) for item in requests]
output_lengths = [float(item.completion_tokens_hint or 0) for item in requests]
profile_lengths = [
_profile_length(input_len, output_len, length_mode=length_mode)
for input_len, output_len in zip(input_lengths, output_lengths)
]
hit_lengths, cache_stats = _ideal_cache_hit_lengths(
requests,
input_lengths=input_lengths,
block_size=_block_size(window),
)
arrival_stats = _arrival_stats(requests, duration_s=duration_s, gpu_count=gpu_count)
length_stats = _series_stats(profile_lengths)
hit_stats = _series_stats(hit_lengths)
total_profile_length = sum(profile_lengths)
total_input_length = sum(input_lengths)
total_hit_length = sum(hit_lengths)
feature_hit_rate = (
float(total_hit_length / max(total_profile_length, EPSILON))
if total_profile_length > 0
else 0.0
)
input_hit_rate = (
float(total_hit_length / max(total_input_length, EPSILON))
if total_input_length > 0
else 0.0
)
vector = [
math.log1p(length_stats["mean"]),
math.log1p(length_stats["p95"] / max(length_stats["mean"], EPSILON)),
length_stats["cv"],
math.log1p(hit_stats["mean"]),
math.log1p(hit_stats["p95"] / max(hit_stats["mean"], EPSILON)),
hit_stats["cv"],
feature_hit_rate,
math.log1p(arrival_stats["request_rate_per_gpu"]),
arrival_stats["interarrival_cv"],
math.log1p(arrival_stats["fano_1s"]),
]
return WorkloadProfile(
window_id=window.window_id,
trace_type=window.trace_type,
request_count=len(requests),
duration_s=duration_s,
gpu_count=int(gpu_count),
length_mode=length_mode,
feature_names=list(FEATURE_NAMES),
vector=[float(item) for item in vector],
stats={
"length": {
**length_stats,
"mode": length_mode,
"total": total_profile_length,
"input_total": total_input_length,
"output_total": sum(output_lengths),
},
"cache": {
**hit_stats,
**cache_stats,
"total_hit_length": total_hit_length,
"hit_rate": feature_hit_rate,
"input_hit_rate": input_hit_rate,
},
"arrival": arrival_stats,
},
)
def build_study_workload_profile(
study: "StudySpec",
requests: list[TraceRequest],
window: WindowRecord,
) -> WorkloadProfile:
"""Canonical L-C-A profile for a study's loaded window.
This is the single source of truth for the paper's 10-dimensional L-C-A
feature vector used by the harness prompt and (later) by Stop-A.
"""
mode = resolve_length_mode(
request_mode=study.trace.request_mode,
length_mode="auto",
)
return build_workload_profile(
requests,
window,
gpu_count=study.hardware.gpu_count,
length_mode=mode,
)
def fit_robust_scale(profiles: Sequence[WorkloadProfile]) -> RobustScale:
if not profiles:
raise ValueError("At least one profile is required to fit a robust scale.")
centers: list[float] = []
scales: list[float] = []
for idx in range(len(FEATURE_NAMES)):
values = [float(profile.vector[idx]) for profile in profiles]
median = _percentile(values, 50.0)
iqr = _percentile(values, 75.0) - _percentile(values, 25.0)
centers.append(float(median))
scales.append(float(iqr if abs(iqr) > EPSILON else 1.0))
return RobustScale(feature_names=list(FEATURE_NAMES), center=centers, scale=scales)
def profile_similarity(
left: WorkloadProfile,
right: WorkloadProfile,
*,
scale: RobustScale | None = None,
) -> float:
scaler = scale or fit_robust_scale([left, right])
z_left = scaler.transform(left.vector)
z_right = scaler.transform(right.vector)
return _similarity_from_z(z_left, z_right)
def similarity_report(profiles: Sequence[WorkloadProfile]) -> dict[str, Any]:
if not profiles:
raise ValueError("At least one profile is required.")
scale = fit_robust_scale(profiles)
transformed = [scale.transform(profile.vector) for profile in profiles]
rows: list[dict[str, Any]] = []
matrix: list[list[float]] = []
for i, left in enumerate(profiles):
row_values: list[float] = []
for j, right in enumerate(profiles):
sim = _similarity_from_z(transformed[i], transformed[j])
row_values.append(sim)
rows.append(
{
"left": left.window_id,
"right": right.window_id,
"similarity": sim,
"family_similarity": _family_similarity(transformed[i], transformed[j]),
}
)
matrix.append(row_values)
return {
"feature_names": list(FEATURE_NAMES),
"scaler": scale.to_dict(),
"windows": [profile.window_id for profile in profiles],
"matrix": matrix,
"pairs": rows,
}
@dataclass(frozen=True)
class ConvergencePoint:
converged: bool
stop_index: int
stop_time_s: float
fraction: float
family_similarity: dict[str, float]
checks: list[dict[str, Any]]
def to_dict(self) -> dict[str, Any]:
return {
"converged": self.converged,
"stop_index": self.stop_index,
"stop_time_s": self.stop_time_s,
"fraction": self.fraction,
"family_similarity": self.family_similarity,
"checks": self.checks,
}
def find_convergence_prefix(
requests: list[TraceRequest],
window: WindowRecord,
*,
gpu_count: int,
length_mode: str = "total",
tau: float = 0.9,
tau_c: float = 0.92,
stable_checks: int = 3,
max_checks: int = 20,
min_fraction: float = 0.1,
) -> ConvergencePoint:
"""Earliest arrival-ordered prefix whose offered L-C-A converges to the full set.
The L-C-A vector is a deterministic function of the trace metadata, so the
convergence of prefix-vs-full is itself deterministic (the paper's Fig. 9
curve). Stop-A replays only up to this prefix. A prefix counts as converged
when the L and A family similarities reach ``tau`` and the (slowest) C family
similarity reaches the stricter ``tau_c`` for ``stable_checks`` consecutive
checkpoints. If that never happens within the window the point reports the
full set (converged=False), which keeps the C-gate honest: an unconverged C
means the probe must replay the whole window rather than stop early.
"""
total = len(requests)
if total == 0:
return ConvergencePoint(
converged=False,
stop_index=0,
stop_time_s=0.0,
fraction=1.0,
family_similarity={"L": 1.0, "C": 1.0, "A": 1.0},
checks=[],
)
# Compare each arrival-ordered prefix to the whole set, both measured over
# their own elapsed span so the A (rate) dimension is comparable rather than
# diluted by the fixed window length.
target = _prefix_profile(
requests, total, window, gpu_count=gpu_count, length_mode=length_mode
)
indices = _checkpoint_indices(
total, max_checks=max_checks, min_fraction=min_fraction
)
checks: list[dict[str, Any]] = []
consecutive = 0
converged_index: int | None = None
converged_sims: dict[str, float] | None = None
for index in indices:
prefix = _prefix_profile(
requests, index, window, gpu_count=gpu_count, length_mode=length_mode
)
sims = _family_similarity(target.vector, prefix.vector)
checks.append(
{
"index": index,
"fraction": float(index / total),
"time_s": float(requests[index - 1].arrival_s),
"family_similarity": sims,
}
)
passed = sims["L"] >= tau and sims["A"] >= tau and sims["C"] >= tau_c
consecutive = consecutive + 1 if passed else 0
if consecutive >= stable_checks and converged_index is None:
converged_index = index
converged_sims = sims
break
if converged_index is None:
last_sims = checks[-1]["family_similarity"] if checks else {"L": 1.0, "C": 1.0, "A": 1.0}
return ConvergencePoint(
converged=False,
stop_index=total,
stop_time_s=float(requests[-1].arrival_s),
fraction=1.0,
family_similarity=last_sims,
checks=checks,
)
return ConvergencePoint(
converged=True,
stop_index=converged_index,
stop_time_s=float(requests[converged_index - 1].arrival_s),
fraction=float(converged_index / total),
family_similarity=converged_sims or {},
checks=checks,
)
def _prefix_profile(
requests: list[TraceRequest],
index: int,
window: WindowRecord,
*,
gpu_count: int,
length_mode: str,
) -> WorkloadProfile:
prefix = requests[:index]
start = float(prefix[0].arrival_s) if prefix else float(window.window_start)
end = float(prefix[-1].arrival_s) if prefix else float(window.window_start)
prefix_window = WindowRecord(
window_id=window.window_id,
trace_path=window.trace_path,
trace_type=window.trace_type,
window_start=start,
window_end=end,
source_payload=window.source_payload,
)
return build_workload_profile(
prefix, prefix_window, gpu_count=gpu_count, length_mode=length_mode
)
def _checkpoint_indices(total: int, *, max_checks: int, min_fraction: float) -> list[int]:
start = max(1, int(math.ceil(min_fraction * total)))
if total <= max_checks:
candidates = range(start, total + 1)
else:
step = max(1, total // max_checks)
candidates = list(range(start, total + 1, step))
if candidates and candidates[-1] != total:
candidates.append(total)
seen: list[int] = []
for value in candidates:
clamped = min(total, max(1, int(value)))
if not seen or seen[-1] != clamped:
seen.append(clamped)
return seen
def dumps_profile(profile: WorkloadProfile) -> str:
return json.dumps(profile.to_dict(), ensure_ascii=False, indent=2) + "\n"
def _duration_s(requests: list[TraceRequest], window: WindowRecord) -> float:
duration = max(float(window.window_end) - float(window.window_start), 0.0)
if duration > 0:
return duration
if len(requests) >= 2:
return max(0.0, float(requests[-1].arrival_s) - float(requests[0].arrival_s))
return 0.0
def _profile_length(input_length: float, output_length: float, *, length_mode: str) -> float:
if length_mode == "input":
return max(input_length, 0.0)
if length_mode == "output":
return max(output_length, 0.0)
return max(input_length, 0.0) + max(output_length, 0.0)
def _block_size(window: WindowRecord) -> int:
value = window.source_payload.get("block_size")
if isinstance(value, bool):
return 1
if isinstance(value, (int, float)) and value > 0:
return int(value)
if isinstance(value, str) and value.strip():
try:
parsed = int(value)
except ValueError:
return 1
return parsed if parsed > 0 else 1
return 1
def _ideal_cache_hit_lengths(
requests: list[TraceRequest],
*,
input_lengths: list[float],
block_size: int,
) -> tuple[list[float], dict[str, Any]]:
seen_hashes: set[Any] = set()
hit_lengths: list[float] = []
total_blocks = 0
repeated_blocks = 0
rows_with_hash_ids = 0
for request, input_length in zip(requests, input_lengths):
hash_ids = request.metadata.get("hash_ids")
if not isinstance(hash_ids, list):
hit_lengths.append(0.0)
continue
rows_with_hash_ids += 1
repeated_for_request = 0
for hash_id in hash_ids:
total_blocks += 1
if hash_id in seen_hashes:
repeated_blocks += 1
repeated_for_request += 1
else:
seen_hashes.add(hash_id)
hit_lengths.append(float(min(max(input_length, 0.0), repeated_for_request * block_size)))
return hit_lengths, {
"block_size": block_size,
"rows_with_hash_ids": rows_with_hash_ids,
"total_blocks": total_blocks,
"repeated_blocks": repeated_blocks,
"repeated_block_ratio": (
float(repeated_blocks / total_blocks) if total_blocks else 0.0
),
}
def _arrival_stats(
requests: list[TraceRequest],
*,
duration_s: float,
gpu_count: int,
) -> dict[str, Any]:
arrivals = [float(item.arrival_s) for item in requests]
interarrivals = [
max(0.0, arrivals[idx] - arrivals[idx - 1])
for idx in range(1, len(arrivals))
]
per_second_counts = _per_second_counts(arrivals, duration_s=duration_s)
qps = float(len(requests) / duration_s) if duration_s > 0 else 0.0
return {
"request_rate": qps,
"request_rate_per_gpu": float(qps / gpu_count) if gpu_count > 0 else 0.0,
"interarrival_cv": _cv(interarrivals),
"fano_1s": _fano(per_second_counts),
"one_second_count_mean": statistics.fmean(per_second_counts)
if per_second_counts
else 0.0,
"one_second_count_variance": statistics.pvariance(per_second_counts)
if len(per_second_counts) >= 2
else 0.0,
"one_second_bin_count": len(per_second_counts),
}
def _per_second_counts(arrivals: list[float], *, duration_s: float) -> list[float]:
if duration_s <= 0:
return [float(len(arrivals))] if arrivals else []
bin_count = max(1, int(math.ceil(duration_s)))
counts = [0.0 for _ in range(bin_count)]
for arrival in arrivals:
if arrival < 0:
continue
idx = int(math.floor(arrival))
if 0 <= idx < bin_count:
counts[idx] += 1.0
return counts
def _series_stats(values: list[float]) -> dict[str, float]:
return {
"count": float(len(values)),
"mean": statistics.fmean(values) if values else 0.0,
"p50": _percentile(values, 50.0),
"p95": _percentile(values, 95.0),
"cv": _cv(values),
}
def _cv(values: list[float]) -> float:
if not values:
return 0.0
mean = statistics.fmean(values)
if abs(mean) <= EPSILON:
return 0.0
return float(statistics.pstdev(values) / mean) if len(values) >= 2 else 0.0
def _fano(values: list[float]) -> float:
if not values:
return 0.0
mean = statistics.fmean(values)
if abs(mean) <= EPSILON:
return 0.0
return float(statistics.pvariance(values) / mean) if len(values) >= 2 else 0.0
def _percentile(values: Sequence[float], p: float) -> float:
if not values:
return 0.0
ordered = sorted(float(item) for item in values)
if len(ordered) == 1:
return ordered[0]
rank = (p / 100.0) * (len(ordered) - 1)
lower = int(math.floor(rank))
upper = int(math.ceil(rank))
if lower == upper:
return ordered[lower]
weight = rank - lower
return float(ordered[lower] * (1.0 - weight) + ordered[upper] * weight)
def _similarity_from_z(left: Sequence[float], right: Sequence[float]) -> float:
distance = math.sqrt(
sum((float(lval) - float(rval)) ** 2 for lval, rval in zip(left, right))
)
return float(math.exp(-distance))
def _family_similarity(left: Sequence[float], right: Sequence[float]) -> dict[str, float]:
result: dict[str, float] = {}
for family, family_slice in FAMILY_SLICES.items():
result[family] = _similarity_from_z(left[family_slice], right[family_slice])
return result

View File

@@ -1,13 +1,17 @@
from __future__ import annotations
import json
import time
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING, Any
from .harness import build_harness_context, render_harness_context
from .http_client import chat_completion, stream_text_completion
from .spec import LLMPolicySpec, Proposal, SpecError, StudySpec, StudyState
if TYPE_CHECKING:
from .lca import WorkloadProfile
def _parse_bool_like(value: Any, *, context: str) -> bool:
if isinstance(value, bool):
@@ -177,6 +181,7 @@ def build_prompt(
window_summary: dict[str, Any],
state: StudyState,
capability_profile: dict[str, Any] | None,
workload_profile: "WorkloadProfile | None" = None,
) -> str:
objective_notes: list[str] = []
if study.trace.request_mode == "decode_only":
@@ -211,16 +216,102 @@ def build_prompt(
)
launch_failures = _launch_failure_history(state)
parallel_candidates = _enumerate_parallel_candidates(study)
sections = [
common_preamble = [
"You are tuning an OpenAI-compatible serving engine.",
"Return exactly one JSON object with keys: observation, diagnosis, config_patch, expected_effects, why_not_previous_failures, should_stop.",
"config_patch must contain env_patch and flag_patch.",
"expected_effects must be a JSON array of short strings, not an object.",
"should_stop must be a boolean. Use true only when the harness convergence guard says another adjacent probe is not justified.",
(
"should_stop must be a boolean. Use true only when the harness convergence guard says another adjacent probe is not justified."
if study.llm.use_harness
else "should_stop must be a boolean. Use false unless no valid config can be proposed."
),
"Only use allowed tunable env keys and allowed tunable flag keys.",
"Do not wrap the JSON in markdown fences or any extra text.",
"Do not repeat a config that previously failed to launch unless the new patch explicitly removes the failing knob.",
"Treat previous engine launch failures as hard negative evidence. If you touch TP/DP/EP, keep the proposal inside the topology constraints exactly.",
]
if not study.llm.use_harness:
sections = [
*common_preamble,
"",
"Study context:",
json.dumps(
{
"study_id": study.study_id,
"objective": "maximize feasible request_rate_per_gpu at the SLO target",
"current_best": {
"trial_id": state.best_trial_id,
"best_parallel_size": state.best_parallel_size,
"best_sampling_u": state.best_sampling_u,
"best_request_rate": state.best_request_rate,
"best_request_rate_per_gpu": state.best_request_rate_per_gpu,
},
"hardware": {
"gpu_count": study.hardware.gpu_count,
"gpu_model": study.hardware.gpu_model,
},
"model": {
"model_id": study.model.model_id,
"served_model_name": study.model.served_model_name,
},
"trace": {
"window_id": study.trace.window_id,
"request_mode": study.trace.request_mode,
"completion_tokens_override": study.trace.completion_tokens_override,
"input_length_filter": (
{
"min_input_tokens": study.trace.input_length_filter.min_input_tokens,
"max_input_tokens": study.trace.input_length_filter.max_input_tokens,
}
if study.trace.input_length_filter is not None
else None
),
},
"engine": {
"engine_name": study.engine.engine_name,
"engine_version": study.engine.engine_version,
"base_flags": study.engine.base_flags,
"base_envs": study.engine.base_envs,
"allowed_flag_keys": study.engine.tunable_flags,
"allowed_env_keys": study.engine.tunable_envs,
"topology_constraints": (
study.engine.topology_constraints.__dict__
if study.engine.topology_constraints is not None
else None
),
},
},
ensure_ascii=False,
indent=2,
),
"",
"SLO:",
json.dumps(
{
"target_pass_rate": study.slo.target_pass_rate,
"ttft_rule": study.slo.ttft_rule,
"tpot_rule": study.slo.tpot_rule,
"objective_notes": objective_notes,
},
default=lambda value: value.__dict__,
ensure_ascii=False,
indent=2,
),
"",
"Trial history:",
json.dumps(history, ensure_ascii=False, indent=2),
"",
"Known launch failures:",
json.dumps(launch_failures, ensure_ascii=False, indent=2),
"",
"Tested config signatures:",
json.dumps(_tested_config_signatures(state), ensure_ascii=False, indent=2),
]
return "\n".join(sections)
sections = [
*common_preamble,
(
"TP/DP/EP are part of the tunable space for this study. Prioritize exploring legal topology changes in parallel space before runtime-only knobs unless recent history already proves a topology variant is worse or fails to launch."
if parallel_candidates
@@ -313,42 +404,32 @@ def build_prompt(
"Tested config signatures:",
json.dumps(_tested_config_signatures(state), ensure_ascii=False, indent=2),
]
if study.llm.use_harness:
sections.extend(
[
"",
"Harnesses:",
render_harness_context(
build_harness_context(
study=study,
window_summary=window_summary,
state=state,
)
),
"",
]
)
else:
sections.extend(
[
"",
"Harnesses:",
"Disabled by llm.use_harness=false for ablation.",
"",
]
)
sections.extend(
[
"",
"Harnesses:",
render_harness_context(
build_harness_context(
study=study,
window_summary=window_summary,
state=state,
workload_profile=workload_profile,
)
),
"",
]
)
sections.extend(
[
"The primary cross-topology comparison metric is request_rate_per_gpu, not raw request_rate.",
"The proposal should beat the incumbent on request_rate_per_gpu under the 95%+ SLO target.",
"The evaluator uses the best feasible sampling_u from the same tp_dp_product group when it exists.",
"If a tp_dp_product group has no history yet, the evaluator starts from the study's original search.low and runs a full binary search for that group.",
"Do not assume a configuration with fewer GPUs should inherit the global incumbent sampling_u.",
(
"Follow the active harness. Prefer stop over a weak exploratory proposal once a good incumbent has converged."
if study.llm.use_harness
else "For this ablation, reason from the raw study stack, trial history, launch failures, and tested config signatures without harness hints."
"The evaluator may use the same tp_dp_product incumbent as the search floor when search.inherit_incumbent_floor=true."
if study.search.inherit_incumbent_floor
else "The evaluator runs each proposal over the full configured search range so raw per-iteration performance is measured directly."
),
"Do not assume a configuration with fewer GPUs should inherit the global incumbent sampling_u.",
"Follow the active harness. Prefer stop over a weak exploratory proposal once a good incumbent has converged.",
]
)
return "\n".join(sections)
@@ -600,12 +681,15 @@ def call_llm_for_proposal(
*,
policy: LLMPolicySpec,
prompt: str,
use_harness: bool = True,
) -> str:
if policy.endpoint is None:
raise RuntimeError("study.llm.endpoint is not configured")
last_error: Exception | None = None
for attempt in range(2):
max_attempts = 4
for attempt in range(max_attempts):
try:
system_prompt = policy.system_prompt if use_harness else ""
if policy.endpoint.stream:
text = stream_text_completion(
base_url=policy.endpoint.base_url,
@@ -615,7 +699,7 @@ def call_llm_for_proposal(
model=policy.endpoint.model,
messages=[{"role": "user", "content": prompt}],
timeout_s=policy.endpoint.timeout_s,
system_prompt=policy.system_prompt,
system_prompt=system_prompt,
reasoning_effort=policy.endpoint.reasoning_effort,
)
else:
@@ -627,7 +711,7 @@ def call_llm_for_proposal(
model=policy.endpoint.model,
messages=[{"role": "user", "content": prompt}],
timeout_s=policy.endpoint.timeout_s,
system_prompt=policy.system_prompt,
system_prompt=system_prompt,
reasoning_effort=policy.endpoint.reasoning_effort,
)
text = _extract_response_text(response)
@@ -636,6 +720,7 @@ def call_llm_for_proposal(
last_error = RuntimeError("LLM response content is empty")
except Exception as exc: # noqa: BLE001
last_error = exc
if attempt == 0:
if attempt < max_attempts - 1:
time.sleep(min(30.0, 2.0 * (2**attempt)))
continue
raise RuntimeError(f"LLM proposal failed after retry: {last_error}") from last_error

View File

@@ -29,6 +29,9 @@ def _rule_threshold_ms(rule: ThresholdRule, prompt_tokens: int | None) -> float:
if rule.kind == "fixed_ms":
assert rule.threshold_ms is not None
return rule.threshold_ms
if rule.kind == "linear_ms":
assert rule.intercept_ms is not None and rule.per_token_ms is not None
return float(rule.intercept_ms) + float(rule.per_token_ms) * float(prompt_tokens or 0)
if rule.kind != "step_ms":
raise ValueError(f"Unsupported threshold rule: {rule.kind}")
prompt = float(prompt_tokens or 0)

View File

@@ -321,6 +321,71 @@ class InputLengthFilterSpec:
return spec
@dataclass(frozen=True)
class AdaptiveStopSpec:
"""Stop-A: truncate per-probe replay once the offered L-C-A converges.
Disabled by default; the thresholds are calibrated per workload (Phase 3)
before being switched on, so existing studies are unaffected.
"""
enabled: bool = False
tau: float = 0.9
tau_c: float = 0.92
stable_checks: int = 3
max_checks: int = 20
min_fraction: float = 0.1
boundary_delta: float = 0.02
@classmethod
def from_dict(cls, data: Any) -> "AdaptiveStopSpec":
if data is None:
return cls()
m = _require_mapping(data, context="trace.adaptive_stop")
enabled = (
_require_bool(m.get("enabled"), context="trace.adaptive_stop.enabled")
if m.get("enabled") is not None
else False
)
tau = _require_float(m.get("tau", 0.9), context="trace.adaptive_stop.tau")
tau_c = _require_float(m.get("tau_c", 0.92), context="trace.adaptive_stop.tau_c")
stable_checks = _require_int(
m.get("stable_checks", 3), context="trace.adaptive_stop.stable_checks"
)
max_checks = _require_int(
m.get("max_checks", 20), context="trace.adaptive_stop.max_checks"
)
min_fraction = _require_float(
m.get("min_fraction", 0.1), context="trace.adaptive_stop.min_fraction"
)
boundary_delta = _require_float(
m.get("boundary_delta", 0.02), context="trace.adaptive_stop.boundary_delta"
)
for name, value in (("tau", tau), ("tau_c", tau_c), ("min_fraction", min_fraction)):
if not 0.0 < value <= 1.0:
raise SpecError(f"trace.adaptive_stop.{name} must be in (0, 1].")
if not 0.0 <= boundary_delta < 1.0:
raise SpecError("trace.adaptive_stop.boundary_delta must be in [0, 1).")
if stable_checks <= 0 or max_checks <= 0:
raise SpecError(
"trace.adaptive_stop.stable_checks and max_checks must be > 0."
)
if stable_checks > max_checks:
raise SpecError(
"trace.adaptive_stop.stable_checks must be <= max_checks, "
"otherwise convergence can never be detected."
)
return cls(
enabled=enabled,
tau=tau,
tau_c=tau_c,
stable_checks=stable_checks,
max_checks=max_checks,
min_fraction=min_fraction,
boundary_delta=boundary_delta,
)
@dataclass(frozen=True)
class TraceSpec:
windows_path: str
@@ -338,6 +403,7 @@ class TraceSpec:
early_stop_max_lag_s: float | None = None
early_stop_max_elapsed_s: float | None = None
restart_engine_after_early_stop: bool = False
adaptive_stop: AdaptiveStopSpec = AdaptiveStopSpec()
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> "TraceSpec":
@@ -429,6 +495,7 @@ class TraceSpec:
if data.get("restart_engine_after_early_stop") is not None
else request_mode == "decode_only"
),
adaptive_stop=AdaptiveStopSpec.from_dict(data.get("adaptive_stop")),
)
@@ -437,6 +504,8 @@ class ThresholdRule:
kind: str
threshold_ms: float | None = None
buckets: list[dict[str, float]] = field(default_factory=list)
intercept_ms: float | None = None
per_token_ms: float | None = None
@classmethod
def from_dict(cls, data: Mapping[str, Any], *, context: str) -> "ThresholdRule":
@@ -448,6 +517,18 @@ class ThresholdRule:
data.get("threshold_ms"), context=f"{context}.threshold_ms"
),
)
if kind == "linear_ms":
# threshold = intercept_ms + per_token_ms * input_tokens
# e.g. "4s + L_in/8k" -> intercept_ms=4000, per_token_ms=0.125
intercept_ms = _require_float(
data.get("intercept_ms"), context=f"{context}.intercept_ms"
)
per_token_ms = _require_float(
data.get("per_token_ms"), context=f"{context}.per_token_ms"
)
if intercept_ms < 0 or per_token_ms < 0:
raise SpecError(f"{context}.intercept_ms/per_token_ms must be >= 0.")
return cls(kind=kind, intercept_ms=intercept_ms, per_token_ms=per_token_ms)
if kind == "step_ms":
raw = data.get("buckets")
if not isinstance(raw, list) or not raw:
@@ -511,6 +592,7 @@ class SamplingSearchSpec:
tolerance: float
max_probes: int
sample_seed: int
inherit_incumbent_floor: bool = False
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> "SamplingSearchSpec":
@@ -524,6 +606,10 @@ class SamplingSearchSpec:
sample_seed=_require_int(
data.get("sample_seed", 20260325), context="search.sample_seed"
),
inherit_incumbent_floor=_require_bool(
data.get("inherit_incumbent_floor", False),
context="search.inherit_incumbent_floor",
),
)
@@ -764,6 +850,9 @@ class StudyState:
best_request_rate: float | None = None
best_request_rate_per_gpu: float | None = None
next_trial_index: int = 1
tuning_stop_reason: str = ""
tuning_stop_diagnosis: str = ""
tuning_stop_details: dict[str, Any] = field(default_factory=dict)
best_by_parallel_size: dict[str, dict[str, Any]] = field(default_factory=dict)
trials: list[TrialSummary] = field(default_factory=list)

View File

@@ -45,6 +45,9 @@ class StudyStore:
best_request_rate=payload.get("best_request_rate"),
best_request_rate_per_gpu=payload.get("best_request_rate_per_gpu"),
next_trial_index=int(payload.get("next_trial_index", 1)),
tuning_stop_reason=str(payload.get("tuning_stop_reason") or ""),
tuning_stop_diagnosis=str(payload.get("tuning_stop_diagnosis") or ""),
tuning_stop_details=dict(payload.get("tuning_stop_details") or {}),
best_by_parallel_size={
str(key): value
for key, value in (payload.get("best_by_parallel_size") or {}).items()
@@ -82,15 +85,21 @@ class StudyStore:
trial_root = self.study_root(study.study_id) / "trials" / trial_id
trial_root.mkdir(parents=True, exist_ok=True)
parallel_size = _parallel_size_for_proposal(study=study, proposal=proposal)
search_low = _derive_search_floor(study=study, state=state, parallel_size=parallel_size)
search = study.search
if study.search.inherit_incumbent_floor:
search = replace(
study.search,
low=_derive_search_floor(
study=study,
state=state,
parallel_size=parallel_size,
),
)
spec = TrialSpec(
study_id=study.study_id,
trial_id=trial_id,
config_patch=proposal.config_patch,
search=replace(
study.search,
low=search_low,
),
search=search,
study_spec_path=str((self.study_root(study.study_id) / "study_spec.source").resolve()),
artifact_dir=str(trial_root),
probe_log_path=str(trial_root / "probe_history.json"),

View File

@@ -0,0 +1,581 @@
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .spec import SpecError, load_structured_file
from .store import StudyStore
DEFAULT_BUDGETS = [1, 2, 3, 4, 6, 8, 12]
DEFAULT_TARGET_FRACTION = 0.95
DEFAULT_MIN_FINAL_RATIO = 0.98
def run_tuning_report(spec_path: Path) -> dict[str, Any]:
spec_path = spec_path.resolve()
spec = _load_report_spec(spec_path)
report_root = _resolve_output_root(spec, spec_path=spec_path)
report_root.mkdir(parents=True, exist_ok=True)
cases = [
_summarize_case(case, spec_path=spec_path)
for case in spec["cases"]
]
summary = {
"report_id": spec["report_id"],
"report_root": str(report_root),
"target_fraction": spec["target_fraction"],
"min_final_ratio": spec["min_final_ratio"],
"cases": cases,
"aggregate": _aggregate_cases(cases),
}
StudyStore.write_json(report_root / "summary.json", summary)
(report_root / "report.md").write_text(_render_report(summary), encoding="utf-8")
return summary
def _load_report_spec(path: Path) -> dict[str, Any]:
payload = dict(load_structured_file(path))
report_id = str(payload.get("report_id") or "").strip()
if not report_id:
raise SpecError("report_id must be a non-empty string.")
raw_cases = payload.get("cases")
if not isinstance(raw_cases, list) or not raw_cases:
raise SpecError("cases must be a non-empty list.")
target_fraction = _as_float(payload.get("target_fraction"), default=DEFAULT_TARGET_FRACTION)
if target_fraction <= 0:
raise SpecError("target_fraction must be positive.")
min_final_ratio = _as_float(payload.get("min_final_ratio"), default=DEFAULT_MIN_FINAL_RATIO)
if min_final_ratio <= 0:
raise SpecError("min_final_ratio must be positive.")
cases = [
_load_case(
item,
idx=idx,
default_target_fraction=target_fraction,
default_min_final_ratio=min_final_ratio,
)
for idx, item in enumerate(raw_cases)
]
return {
"report_id": report_id,
"output_root": str(payload.get("output_root") or "").strip() or None,
"target_fraction": target_fraction,
"min_final_ratio": min_final_ratio,
"cases": cases,
}
def _load_case(
raw: Any,
*,
idx: int,
default_target_fraction: float,
default_min_final_ratio: float,
) -> dict[str, Any]:
if not isinstance(raw, dict):
raise SpecError(f"cases[{idx}] must be an object.")
case_id = str(raw.get("case_id") or "").strip()
if not case_id:
raise SpecError(f"cases[{idx}].case_id must be a non-empty string.")
raw_arms = raw.get("arms")
if not isinstance(raw_arms, list) or not raw_arms:
raise SpecError(f"cases[{idx}].arms must be a non-empty list.")
arms = [_load_arm(item, context=f"cases[{idx}].arms[{arm_idx}]") for arm_idx, item in enumerate(raw_arms)]
names = [item["name"] for item in arms]
if len(names) != len(set(names)):
raise SpecError(f"cases[{idx}].arms names must be unique.")
raw_budgets = raw.get("budgets", DEFAULT_BUDGETS)
if not isinstance(raw_budgets, list) or not raw_budgets:
raise SpecError(f"cases[{idx}].budgets must be a non-empty list.")
budgets = sorted({_positive_int(item, context=f"cases[{idx}].budgets") for item in raw_budgets})
return {
"case_id": case_id,
"description": str(raw.get("description") or "").strip(),
"tags": [str(item).strip() for item in raw.get("tags", []) if str(item).strip()]
if isinstance(raw.get("tags", []), list)
else [],
"budgets": budgets,
"target_fraction": _as_float(raw.get("target_fraction"), default=default_target_fraction),
"min_final_ratio": _as_float(raw.get("min_final_ratio"), default=default_min_final_ratio),
"arms": arms,
}
def _load_arm(raw: Any, *, context: str) -> dict[str, Any]:
if not isinstance(raw, dict):
raise SpecError(f"{context} must be an object.")
name = str(raw.get("name") or "").strip()
if not name:
raise SpecError(f"{context}.name must be a non-empty string.")
kind = str(raw.get("kind") or name).strip()
study_root = str(raw.get("study_root") or "").strip()
if not study_root:
raise SpecError(f"{context}.study_root must be a non-empty string.")
return {
"name": name,
"kind": kind,
"study_root": study_root,
"label": str(raw.get("label") or "").strip() or name,
}
def _resolve_output_root(spec: dict[str, Any], *, spec_path: Path) -> Path:
raw = spec.get("output_root")
if raw:
return _resolve_path(str(raw), base_dir=spec_path.parent)
return (Path(".aituner-reports") / str(spec["report_id"])).resolve()
def _summarize_case(case: dict[str, Any], *, spec_path: Path) -> dict[str, Any]:
arms = [
_summarize_arm(arm, budgets=case["budgets"], spec_path=spec_path)
for arm in case["arms"]
]
reference = _reference_best(arms)
max_budget = max(case["budgets"] + [arm["trial_count"] for arm in arms])
for arm in arms:
_add_reference_metrics(
arm,
reference=reference,
max_budget=max_budget,
target_fraction=case["target_fraction"],
)
winners = _case_winners(arms)
comparison = _harness_vs_naive(
arms,
min_final_ratio=case["min_final_ratio"],
)
return {
"case_id": case["case_id"],
"description": case["description"],
"tags": case["tags"],
"budgets": case["budgets"],
"target_fraction": case["target_fraction"],
"min_final_ratio": case["min_final_ratio"],
"reference_best_per_gpu": reference,
"max_budget": max_budget,
"arms": arms,
"winners": winners,
"harness_vs_naive": comparison,
"warnings": _case_warnings(case, arms, comparison),
}
def _summarize_arm(arm: dict[str, Any], *, budgets: list[int], spec_path: Path) -> dict[str, Any]:
study_root = _resolve_study_root(arm["study_root"], base_dir=spec_path.parent)
state = json.loads((study_root / "state.json").read_text(encoding="utf-8"))
trials = state.get("trials") if isinstance(state.get("trials"), list) else []
curve = _running_best_curve(trials)
final_best = curve[-1] if curve else None
best_trial_index = _first_index_at_value(curve, final_best)
return {
"name": arm["name"],
"kind": arm["kind"],
"label": arm["label"],
"study_root": str(study_root),
"study_id": state.get("study_id"),
"trial_count": len(trials),
"completed_count": sum(1 for item in trials if item.get("status") == "completed"),
"failed_count": sum(1 for item in trials if item.get("status") == "failed"),
"no_feasible_count": sum(
1 for item in trials if not isinstance(item.get("best_request_rate_per_gpu"), (int, float))
),
"best_trial_id": state.get("best_trial_id"),
"best_trial_index": best_trial_index,
"final_best_per_gpu": final_best,
"state_best_per_gpu": state.get("best_request_rate_per_gpu"),
"best_at_budget": {str(budget): _value_at_budget(curve, budget) for budget in budgets},
"running_best_per_gpu": curve,
"stop_reason": str(state.get("tuning_stop_reason") or ""),
"stop_diagnosis": str(state.get("tuning_stop_diagnosis") or ""),
}
def _add_reference_metrics(
arm: dict[str, Any],
*,
reference: float | None,
max_budget: int,
target_fraction: float,
) -> None:
final_best = arm.get("final_best_per_gpu")
arm["final_ratio_to_reference"] = (
float(final_best) / reference
if reference and isinstance(final_best, (int, float))
else None
)
target = reference * target_fraction if reference else None
arm["target_per_gpu"] = target
arm["trials_to_target"] = _trials_to_target(arm["running_best_per_gpu"], target)
arm["normalized_auc"] = _normalized_auc(
arm["running_best_per_gpu"],
reference=reference,
max_budget=max_budget,
)
def _harness_vs_naive(arms: list[dict[str, Any]], *, min_final_ratio: float) -> list[dict[str, Any]]:
naive = [arm for arm in arms if arm["kind"] == "naive"]
harnesses = [arm for arm in arms if arm["kind"] == "harness"]
if not naive or not harnesses:
return []
best_naive_final = _max_optional(arm.get("final_best_per_gpu") for arm in naive)
best_naive_ttt = _min_optional(arm.get("trials_to_target") for arm in naive)
best_naive_auc = _max_optional(arm.get("normalized_auc") for arm in naive)
rows = []
for harness in harnesses:
final = harness.get("final_best_per_gpu")
ttt = harness.get("trials_to_target")
auc = harness.get("normalized_auc")
final_ratio = (
float(final) / best_naive_final
if best_naive_final and isinstance(final, (int, float))
else None
)
auc_ratio = (
float(auc) / best_naive_auc
if best_naive_auc and isinstance(auc, (int, float))
else None
)
speedup = _speedup(best_naive_ttt, ttt)
pass_final = final_ratio is not None and final_ratio >= min_final_ratio
pass_speed = speedup is None or speedup >= 1.0
rows.append(
{
"harness": harness["name"],
"best_naive_final_per_gpu": best_naive_final,
"best_naive_trials_to_target": best_naive_ttt,
"best_naive_normalized_auc": best_naive_auc,
"final_ratio_vs_best_naive": final_ratio,
"target_trial_speedup_vs_best_naive": speedup,
"auc_ratio_vs_best_naive": auc_ratio,
"passes_min_final_ratio": pass_final,
"passes_speed": pass_speed,
"passes": pass_final and pass_speed,
}
)
return rows
def _case_winners(arms: list[dict[str, Any]]) -> dict[str, str | None]:
return {
"final_best": _argmax(arms, "final_best_per_gpu"),
"fastest_to_target": _argmin(arms, "trials_to_target"),
"normalized_auc": _argmax(arms, "normalized_auc"),
}
def _aggregate_cases(cases: list[dict[str, Any]]) -> dict[str, Any]:
by_kind: dict[str, dict[str, Any]] = {}
final_wins: dict[str, int] = {}
speed_wins: dict[str, int] = {}
auc_wins: dict[str, int] = {}
harness_passes = 0
harness_checks = 0
for case in cases:
for winner_key, target in (
("final_best", final_wins),
("fastest_to_target", speed_wins),
("normalized_auc", auc_wins),
):
winner = case["winners"].get(winner_key)
if winner:
target[winner] = target.get(winner, 0) + 1
for row in case["harness_vs_naive"]:
harness_checks += 1
if row["passes"]:
harness_passes += 1
for arm in case["arms"]:
bucket = by_kind.setdefault(
arm["kind"],
{
"arm_count": 0,
"mean_final_ratio_to_reference": None,
"mean_normalized_auc": None,
"target_reached_count": 0,
"_final_ratios": [],
"_aucs": [],
},
)
bucket["arm_count"] += 1
if isinstance(arm.get("final_ratio_to_reference"), (int, float)):
bucket["_final_ratios"].append(float(arm["final_ratio_to_reference"]))
if isinstance(arm.get("normalized_auc"), (int, float)):
bucket["_aucs"].append(float(arm["normalized_auc"]))
if isinstance(arm.get("trials_to_target"), int):
bucket["target_reached_count"] += 1
for bucket in by_kind.values():
ratios = bucket.pop("_final_ratios")
aucs = bucket.pop("_aucs")
bucket["mean_final_ratio_to_reference"] = _mean(ratios)
bucket["mean_normalized_auc"] = _mean(aucs)
return {
"case_count": len(cases),
"by_kind": by_kind,
"winner_counts": {
"final_best": final_wins,
"fastest_to_target": speed_wins,
"normalized_auc": auc_wins,
},
"harness_vs_naive_pass_count": harness_passes,
"harness_vs_naive_check_count": harness_checks,
}
def _case_warnings(
case: dict[str, Any],
arms: list[dict[str, Any]],
comparison: list[dict[str, Any]],
) -> list[str]:
warnings = []
kinds = {arm["kind"] for arm in arms}
if "harness" not in kinds or "naive" not in kinds:
warnings.append("case does not include both harness and naive arms")
if len(case["tags"]) < 2:
warnings.append("case has few tags; add workload/model/SLO tags to support generalization claims")
if not comparison:
return warnings
for row in comparison:
if not row["passes_min_final_ratio"]:
warnings.append(
f"{row['harness']} final best is below min_final_ratio versus best naive"
)
if not row["passes_speed"]:
warnings.append(
f"{row['harness']} reaches target later than best naive"
)
return warnings
def _running_best_curve(trials: list[Any]) -> list[float | None]:
curve: list[float | None] = []
incumbent: float | None = None
for trial in trials:
rate = trial.get("best_request_rate_per_gpu") if isinstance(trial, dict) else None
if isinstance(rate, (int, float)) and (incumbent is None or float(rate) > incumbent):
incumbent = float(rate)
curve.append(incumbent)
return curve
def _value_at_budget(curve: list[float | None], budget: int) -> float | None:
if not curve:
return None
index = min(max(budget, 1), len(curve)) - 1
return curve[index]
def _trials_to_target(curve: list[float | None], target: float | None) -> int | None:
if target is None:
return None
for idx, value in enumerate(curve, start=1):
if isinstance(value, (int, float)) and value >= target:
return idx
return None
def _normalized_auc(
curve: list[float | None],
*,
reference: float | None,
max_budget: int,
) -> float | None:
if not reference or max_budget <= 0:
return None
total = 0.0
for budget in range(1, max_budget + 1):
value = _value_at_budget(curve, budget)
total += float(value) if isinstance(value, (int, float)) else 0.0
return total / (reference * max_budget)
def _reference_best(arms: list[dict[str, Any]]) -> float | None:
return _max_optional(arm.get("final_best_per_gpu") for arm in arms)
def _resolve_study_root(raw_path: str, *, base_dir: Path) -> Path:
path = _resolve_path(raw_path, base_dir=base_dir)
if (path / "state.json").exists():
return path
matches = sorted(path.glob("*/state.json"))
if len(matches) == 1:
return matches[0].parent
if not matches:
raise SpecError(f"study_root does not contain state.json: {path}")
raise SpecError(f"study_root is ambiguous; point to a specific study directory: {path}")
def _resolve_path(raw_path: str, *, base_dir: Path) -> Path:
path = Path(raw_path)
if not path.is_absolute():
path = (base_dir / path).resolve()
return path
def _as_float(value: Any, *, default: float) -> float:
if value is None:
return default
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise SpecError(f"Expected numeric value, got {value!r}.")
return float(value)
def _positive_int(value: Any, *, context: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
raise SpecError(f"{context} must contain positive integers.")
return value
def _first_index_at_value(curve: list[float | None], value: float | None) -> int | None:
if value is None:
return None
for idx, item in enumerate(curve, start=1):
if item == value:
return idx
return None
def _argmax(rows: list[dict[str, Any]], key: str) -> str | None:
scored = [
(str(row["name"]), float(row[key]))
for row in rows
if isinstance(row.get(key), (int, float))
]
if not scored:
return None
scored.sort(key=lambda item: item[1], reverse=True)
return scored[0][0]
def _argmin(rows: list[dict[str, Any]], key: str) -> str | None:
scored = [
(str(row["name"]), int(row[key]))
for row in rows
if isinstance(row.get(key), int)
]
if not scored:
return None
scored.sort(key=lambda item: item[1])
return scored[0][0]
def _max_optional(values: Any) -> float | None:
scored = [float(item) for item in values if isinstance(item, (int, float))]
return max(scored) if scored else None
def _min_optional(values: Any) -> int | None:
scored = [int(item) for item in values if isinstance(item, int)]
return min(scored) if scored else None
def _mean(values: list[float]) -> float | None:
return sum(values) / len(values) if values else None
def _speedup(naive_trials: int | None, harness_trials: int | None) -> float | None:
if harness_trials is None:
return 0.0 if naive_trials is not None else None
if naive_trials is None:
return None
if harness_trials <= 0:
return None
return float(naive_trials) / float(harness_trials)
def _fmt(value: Any) -> str:
if isinstance(value, float):
return f"{value:.4f}"
if value is None:
return "-"
return str(value)
def _render_report(summary: dict[str, Any]) -> str:
lines = [
f"# {summary['report_id']}",
"",
"## Aggregate",
"",
f"- Cases: `{summary['aggregate']['case_count']}`",
f"- Harness-vs-naive pass/checks: `{summary['aggregate']['harness_vs_naive_pass_count']}`/`{summary['aggregate']['harness_vs_naive_check_count']}`",
f"- Winner counts: `{json.dumps(summary['aggregate']['winner_counts'], ensure_ascii=False)}`",
"",
"## By Kind",
"",
"| Kind | Arms | Mean final/ref | Mean AUC | Target reached |",
"| --- | ---: | ---: | ---: | ---: |",
]
for kind, payload in sorted(summary["aggregate"]["by_kind"].items()):
lines.append(
"| "
+ " | ".join(
[
f"`{kind}`",
str(payload["arm_count"]),
_fmt(payload["mean_final_ratio_to_reference"]),
_fmt(payload["mean_normalized_auc"]),
str(payload["target_reached_count"]),
]
)
+ " |"
)
lines.extend(["", "## Cases", ""])
for case in summary["cases"]:
lines.extend(
[
f"### {case['case_id']}",
"",
f"- Reference best req/s/GPU: `{_fmt(case['reference_best_per_gpu'])}`",
f"- Target fraction: `{case['target_fraction']}`",
f"- Winners: `{json.dumps(case['winners'], ensure_ascii=False)}`",
]
)
if case["warnings"]:
lines.append(f"- Warnings: `{json.dumps(case['warnings'], ensure_ascii=False)}`")
lines.extend(
[
"",
"| Arm | Kind | Trials | Final/GPU | Final/ref | TTT | AUC | Failed | No feasible |",
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
]
)
for arm in case["arms"]:
lines.append(
"| "
+ " | ".join(
[
f"`{arm['name']}`",
f"`{arm['kind']}`",
str(arm["trial_count"]),
_fmt(arm["final_best_per_gpu"]),
_fmt(arm["final_ratio_to_reference"]),
_fmt(arm["trials_to_target"]),
_fmt(arm["normalized_auc"]),
str(arm["failed_count"]),
str(arm["no_feasible_count"]),
]
)
+ " |"
)
if case["harness_vs_naive"]:
lines.extend(["", "| Harness | Final vs best naive | Target speedup | AUC vs best naive | Pass |", "| --- | ---: | ---: | ---: | --- |"])
for row in case["harness_vs_naive"]:
lines.append(
"| "
+ " | ".join(
[
f"`{row['harness']}`",
_fmt(row["final_ratio_vs_best_naive"]),
_fmt(row["target_trial_speedup_vs_best_naive"]),
_fmt(row["auc_ratio_vs_best_naive"]),
f"`{row['passes']}`",
]
)
+ " |"
)
lines.append("")
return "\n".join(lines)

View File

@@ -8,6 +8,7 @@ import statistics
import subprocess
import threading
import time
import traceback
from concurrent.futures import FIRST_COMPLETED, Future, ThreadPoolExecutor, wait
from dataclasses import dataclass
from pathlib import Path
@@ -15,8 +16,9 @@ from typing import Any, Callable
from .engine import build_launch_recipe
from .http_client import HttpClientError, stream_chat_completion, wait_for_server
from .lca import find_convergence_prefix, resolve_length_mode
from .search import ThresholdProbe, binary_search_max_feasible
from .slo import RequestOutcome, evaluate_request, summarize_evaluations
from .slo import RequestOutcome, _rule_threshold_ms, evaluate_request, summarize_evaluations
from .spec import ConfigPatch, SamplingSearchSpec, TrialSpec, load_study_spec, to_jsonable
from .trace import TraceRequest, load_trace_requests, select_requests_for_threshold
@@ -208,6 +210,151 @@ def _probe_outcome_details(
}
_SIGTERM_NOT_INSTALLED = object()
def _install_sigterm_as_keyboardinterrupt() -> Any:
"""Make SIGTERM raise KeyboardInterrupt so the engine-teardown finally runs.
When `study tune` is killed, a default SIGTERM skips the finally blocks and
orphans the vLLM engine (and its EngineCore workers) on the GPUs. Converting
SIGTERM to KeyboardInterrupt lets _terminate_process_tree run. Only installable
from the main thread; returns the previous handler (or a sentinel).
"""
if threading.current_thread() is not threading.main_thread():
return _SIGTERM_NOT_INSTALLED
def _handler(signum: int, frame: Any) -> None:
raise KeyboardInterrupt()
try:
return signal.signal(signal.SIGTERM, _handler)
except (ValueError, OSError):
return _SIGTERM_NOT_INSTALLED
def _restore_sigterm(previous: Any) -> None:
if previous is _SIGTERM_NOT_INSTALLED:
return
if threading.current_thread() is not threading.main_thread():
return
try:
signal.signal(signal.SIGTERM, previous)
except (ValueError, OSError):
pass
def _ignore_sigterm_if_main() -> None:
"""Ignore SIGTERM during teardown so a second signal cannot orphan the engine."""
if threading.current_thread() is not threading.main_thread():
return
try:
signal.signal(signal.SIGTERM, signal.SIG_IGN)
except (ValueError, OSError):
pass
def _probe_drain_deadline(
reqs: list[TraceRequest], slo: Any, *, ceiling: float | None
) -> float | None:
"""Stop-A-consistent per-probe drain deadline (wall-clock seconds).
The deadline is the time a *feasible* config needs to drain the admitted set:
the last admitted arrival plus the worst-case TTFT budget plus the p99 output
length times the TPOT budget. A config that cannot finish by this deadline is
genuinely SLO-infeasible, so the clock never pre-empts the LCA-matched offered
window (Stop-A) -- it only fails the unfit. ``ceiling`` is a hard safety cap.
"""
if not reqs or slo.tpot_rule is None:
return ceiling
last_arrival = max(float(r.arrival_s or 0.0) for r in reqs)
inputs = sorted(int(r.prompt_tokens_hint or 0) for r in reqs)
outputs = sorted(int(r.completion_tokens_hint or 0) for r in reqs)
def _p99(xs: list[int]) -> int:
return xs[min(len(xs) - 1, int(0.99 * len(xs)))] if xs else 0
p99_in, p99_out = _p99(inputs), _p99(outputs)
tpot_ms = _rule_threshold_ms(slo.tpot_rule, p99_in)
ttft_ms = _rule_threshold_ms(slo.ttft_rule, p99_in) if slo.ttft_rule is not None else 0.0
margin_s = 30.0
deadline = last_arrival + (ttft_ms + p99_out * tpot_ms) / 1000.0 + margin_s
return min(float(ceiling), deadline) if ceiling else deadline
def _adaptive_replay_set(
selected: list[TraceRequest],
*,
study: Any,
window: Any,
) -> tuple[list[TraceRequest], dict[str, Any] | None]:
"""Stop-A: truncate the replay to the offered-L-C-A convergence prefix.
Returns the (possibly shortened) request list to replay and a certificate of
the convergence decision. When Stop-A is disabled, or C never converges, the
full selected set is replayed (the C-gate: no early stop on a cold cache).
"""
spec = study.trace.adaptive_stop
if not getattr(spec, "enabled", False) or not selected:
return selected, None
point = find_convergence_prefix(
selected,
window,
gpu_count=study.hardware.gpu_count,
length_mode=resolve_length_mode(request_mode=study.trace.request_mode),
tau=spec.tau,
tau_c=spec.tau_c,
stable_checks=spec.stable_checks,
max_checks=spec.max_checks,
min_fraction=spec.min_fraction,
)
replay = selected[: point.stop_index] if point.stop_index > 0 else selected
certificate = {
"enabled": True,
"converged": point.converged,
"stop_index": point.stop_index,
"total_selected": len(selected),
"fraction": point.fraction,
"stop_time_s": point.stop_time_s,
"family_similarity": point.family_similarity,
}
return replay, certificate
def _should_extend_on_boundary(
*,
pass_rate: float,
target_pass_rate: float,
certificate: dict[str, Any] | None,
truncated: bool,
boundary_delta: float,
) -> bool:
"""SLO-boundary guard: re-measure on the full window when a truncated probe
lands within +/- boundary_delta of the SLO target.
Offered-L-C-A convergence cannot see engine-state drift in the window's tail,
so a near-boundary truncated verdict is untrustworthy. This fires only on
probes sitting on the feasibility knee, so non-boundary probes keep the Stop-A
time saving.
"""
if certificate is None or not certificate.get("converged"):
return False
if not truncated or boundary_delta <= 0:
return False
return abs(float(pass_rate) - float(target_pass_rate)) <= float(boundary_delta)
def _best_feasible_probe_record(probe_history: list[dict[str, Any]]) -> dict[str, Any] | None:
feasible = [
item
for item in probe_history
if item.get("feasible") and isinstance(item.get("request_rate"), (int, float))
]
if not feasible:
return None
return max(feasible, key=lambda item: float(item["request_rate"]))
def _replay_requests(
requests: list[TraceRequest],
*,
@@ -389,27 +536,76 @@ def _wait_for_server_or_exit(
raise HttpClientError(f"Timed out waiting for {base_url}{healthcheck_path}: {last_error}")
def _terminate_process_tree(process: subprocess.Popen[str], *, timeout_s: float = 30.0) -> None:
if process.poll() is not None:
return
def _process_group_exists(pgid: int) -> bool:
try:
os.killpg(pgid, 0)
return True
except ProcessLookupError:
return False
def _pids_matching_env(marker_env: dict[str, str] | None) -> list[int]:
if not marker_env:
return []
expected = {
f"{key}={value}".encode()
for key, value in marker_env.items()
}
pids: list[int] = []
proc_root = Path("/proc")
for entry in proc_root.iterdir():
if not entry.name.isdigit():
continue
pid = int(entry.name)
if pid == os.getpid():
continue
try:
environ = (entry / "environ").read_bytes()
except (FileNotFoundError, PermissionError, ProcessLookupError):
continue
if expected.issubset(set(environ.split(b"\0"))):
pids.append(pid)
return sorted(pids)
def _signal_pids(pids: list[int], sig: signal.Signals) -> None:
for pid in pids:
try:
os.kill(pid, sig)
except (ProcessLookupError, PermissionError):
continue
def _terminate_process_tree(
process: subprocess.Popen[str],
*,
timeout_s: float = 30.0,
marker_env: dict[str, str] | None = None,
) -> None:
try:
pgid = os.getpgid(process.pid)
except ProcessLookupError:
return
# Children can keep the session/process group alive after the vLLM API
# server exits. In that case the group id is still the original pid
# because the process was launched with start_new_session=True.
pgid = process.pid
try:
os.killpg(pgid, signal.SIGTERM)
except ProcessLookupError:
return
pass
_signal_pids(_pids_matching_env(marker_env), signal.SIGTERM)
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
if process.poll() is not None:
if not _process_group_exists(pgid) and not _pids_matching_env(marker_env):
return
time.sleep(0.1)
try:
os.killpg(pgid, signal.SIGKILL)
except ProcessLookupError:
return
process.wait(timeout=timeout_s)
pass
_signal_pids(_pids_matching_env(marker_env), signal.SIGKILL)
if process.poll() is None:
process.wait(timeout=timeout_s)
def run_trial(trial_spec_path: Path) -> dict[str, Any]:
@@ -426,12 +622,17 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
probe_details_path = artifact_dir / "probe_details.jsonl"
if probe_details_path.exists():
probe_details_path.unlink()
trial_marker_env = {
"AITUNER_STUDY_ID": trial.study_id,
"AITUNER_TRIAL_ID": trial.trial_id,
}
with engine_log_path.open("w", encoding="utf-8") as engine_log:
def launch_process() -> subprocess.Popen[str]:
launch_env = {**recipe.env, **trial_marker_env}
return subprocess.Popen( # noqa: S603
recipe.argv,
cwd=recipe.cwd,
env=recipe.env,
env=launch_env,
stdout=engine_log,
stderr=subprocess.STDOUT,
text=True,
@@ -439,6 +640,7 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
)
process = launch_process()
previous_sigterm = _install_sigterm_as_keyboardinterrupt()
probe_history: list[dict[str, Any]] = []
failure_stage = "engine_launch"
try:
@@ -453,27 +655,51 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
def evaluator(threshold: float) -> ThresholdProbe[ProbePayload]:
nonlocal process
selected = select_requests_for_threshold(requests, threshold=threshold)
restart_after_early_stop = study.trace.restart_engine_after_early_stop
outcomes, early_stopped, early_stop_reason = _replay_requests(
selected,
base_url=recipe.base_url,
timeout_s=recipe.request_timeout_s,
max_concurrency=study.trace.max_concurrency,
target_pass_rate=study.slo.target_pass_rate,
max_lag_s=study.trace.early_stop_max_lag_s,
max_elapsed_s=study.trace.early_stop_max_elapsed_s,
evaluate_outcome=lambda outcome: evaluate_request(outcome, study.slo),
drain_inflight_on_early_stop=not restart_after_early_stop,
replay_set, adaptive_stop_certificate = _adaptive_replay_set(
selected, study=study, window=window
)
restart_after_early_stop = study.trace.restart_engine_after_early_stop
def _run(reqs: list[TraceRequest]) -> tuple[list[RequestOutcome], bool, str]:
return _replay_requests(
reqs,
base_url=recipe.base_url,
timeout_s=recipe.request_timeout_s,
max_concurrency=study.trace.max_concurrency,
target_pass_rate=study.slo.target_pass_rate,
max_lag_s=study.trace.early_stop_max_lag_s,
max_elapsed_s=_probe_drain_deadline(
reqs, study.slo, ceiling=study.trace.early_stop_max_elapsed_s
),
evaluate_outcome=lambda outcome: evaluate_request(outcome, study.slo),
drain_inflight_on_early_stop=not restart_after_early_stop,
)
outcomes, early_stopped, early_stop_reason = _run(replay_set)
evaluations, summary = summarize_evaluations(outcomes, study.slo)
if _should_extend_on_boundary(
pass_rate=summary["slo_pass_rate"],
target_pass_rate=study.slo.target_pass_rate,
certificate=adaptive_stop_certificate,
truncated=len(replay_set) < len(selected),
boundary_delta=study.trace.adaptive_stop.boundary_delta,
):
# On the feasibility knee the truncated verdict is untrustworthy;
# re-measure the full window and use that result.
replay_set = selected
outcomes, early_stopped, early_stop_reason = _run(selected)
evaluations, summary = summarize_evaluations(outcomes, study.slo)
if adaptive_stop_certificate is not None:
adaptive_stop_certificate["boundary_extended"] = True
probe_details = _probe_outcome_details(
threshold=threshold,
selected=selected,
selected=replay_set,
outcomes=outcomes,
evaluations=evaluations,
early_stopped=early_stopped,
early_stop_reason=early_stop_reason,
)
probe_details["adaptive_stop"] = adaptive_stop_certificate
with probe_details_path.open("a", encoding="utf-8") as details_handle:
details_handle.write(
json.dumps(probe_details, ensure_ascii=False) + "\n"
@@ -514,17 +740,23 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
probe_record = {
"threshold": threshold,
"request_count": payload.request_count,
"replayed_request_count": len(replay_set),
"pass_rate": payload.pass_rate,
"request_rate": payload.request_rate,
"feasible": payload.feasible,
"early_stopped": payload.early_stopped,
"early_stop_reason": payload.early_stop_reason,
"latency_summary": payload.latency_summary,
"adaptive_stop": adaptive_stop_certificate,
}
probe_history.append(probe_record)
StudyStore.write_json(Path(trial.probe_log_path), probe_history)
if early_stopped and restart_after_early_stop:
_terminate_process_tree(process, timeout_s=30.0)
_terminate_process_tree(
process,
timeout_s=30.0,
marker_env=trial_marker_env,
)
process = launch_process()
_wait_for_server_or_exit(
process,
@@ -538,41 +770,106 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
payload=payload,
)
search = binary_search_max_feasible(
primary_search = binary_search_max_feasible(
low=trial.search.low,
high=trial.search.high,
tolerance=trial.search.tolerance,
max_probes=trial.search.max_probes,
evaluator=evaluator,
)
best = search.best_feasible_payload
search_for_best = primary_search
best = primary_search.best_feasible_payload
best_source = "primary_search"
fallback_search = None
skipped_lower_range_fallback = False
lower_range_fallback_skip_reason = ""
original_search_low = float(study.search.low)
inherited_search_floor = float(trial.search.low)
if best is None and inherited_search_floor > original_search_low:
if trial.search.inherit_incumbent_floor:
skipped_lower_range_fallback = True
lower_range_fallback_skip_reason = (
"primary_search_above_incumbent_floor_all_infeasible"
)
else:
fallback_search = binary_search_max_feasible(
low=original_search_low,
high=inherited_search_floor,
tolerance=trial.search.tolerance,
max_probes=trial.search.max_probes,
evaluator=evaluator,
)
if fallback_search.best_feasible_payload is not None:
search_for_best = fallback_search
best = fallback_search.best_feasible_payload
best_source = "lower_range_fallback"
def serialize_probe(probe: ThresholdProbe[ProbePayload]) -> dict[str, Any]:
return {
"threshold": probe.threshold,
"feasible": probe.feasible,
"payload": {
"request_count": probe.payload.request_count,
"pass_rate": probe.payload.pass_rate,
"request_rate": probe.payload.request_rate,
"early_stopped": probe.payload.early_stopped,
"early_stop_reason": probe.payload.early_stop_reason,
"latency_summary": probe.payload.latency_summary,
},
}
all_probes = [
*primary_search.probes,
*((fallback_search.probes if fallback_search is not None else [])),
]
result = {
"study_id": trial.study_id,
"trial_id": trial.trial_id,
"status": "completed",
"config_patch": to_jsonable(trial.config_patch),
"best_sampling_u": search.best_threshold if best is not None else None,
"best_source": best_source,
"best_sampling_u": search_for_best.best_threshold if best is not None else None,
"best_request_rate": best.request_rate if best is not None else None,
"best_pass_rate": best.pass_rate if best is not None else None,
"best_request_count": best.request_count if best is not None else None,
"probes": [
{
"threshold": probe.threshold,
"feasible": probe.feasible,
"payload": {
"request_count": probe.payload.request_count,
"pass_rate": probe.payload.pass_rate,
"request_rate": probe.payload.request_rate,
"early_stopped": probe.payload.early_stopped,
"early_stop_reason": probe.payload.early_stop_reason,
"latency_summary": probe.payload.latency_summary,
},
}
for probe in search.probes
],
"probes": [serialize_probe(probe) for probe in all_probes],
}
if best is None and search.probes:
last_probe = search.probes[-1]
if fallback_search is not None or skipped_lower_range_fallback:
result["primary_search"] = {
"low": inherited_search_floor,
"high": trial.search.high,
"best_sampling_u": primary_search.best_threshold
if primary_search.best_feasible_payload is not None
else None,
"best_request_rate": primary_search.best_feasible_payload.request_rate
if primary_search.best_feasible_payload is not None
else None,
"probes": [serialize_probe(probe) for probe in primary_search.probes],
}
if skipped_lower_range_fallback:
result["lower_range_fallback"] = {
"triggered": False,
"skipped": True,
"reason": lower_range_fallback_skip_reason,
"low": original_search_low,
"high": inherited_search_floor,
"probes": [],
}
if fallback_search is not None:
result["lower_range_fallback"] = {
"triggered": True,
"low": original_search_low,
"high": inherited_search_floor,
"best_sampling_u": fallback_search.best_threshold
if fallback_search.best_feasible_payload is not None
else None,
"best_request_rate": fallback_search.best_feasible_payload.request_rate
if fallback_search.best_feasible_payload is not None
else None,
"probes": [serialize_probe(probe) for probe in fallback_search.probes],
}
if best is None and all_probes:
last_probe = all_probes[-1]
result["all_infeasible_diagnostics"] = {
"threshold": last_probe.threshold,
"request_count": last_probe.payload.request_count,
@@ -585,6 +882,26 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
StudyStore.write_json(Path(trial.result_path), result)
return result
except Exception as exc: # noqa: BLE001
partial_best = _best_feasible_probe_record(probe_history)
if partial_best is not None:
result = {
"study_id": trial.study_id,
"trial_id": trial.trial_id,
"status": "completed",
"config_patch": to_jsonable(trial.config_patch),
"best_source": "partial_probe_before_failure",
"best_sampling_u": partial_best.get("threshold"),
"best_request_rate": partial_best.get("request_rate"),
"best_pass_rate": partial_best.get("pass_rate"),
"best_request_count": partial_best.get("request_count"),
"completed_with_probe_failure": True,
"failure_stage": failure_stage,
"failure_reason": str(exc),
"failure_traceback": traceback.format_exc(),
"probes": probe_history,
}
StudyStore.write_json(Path(trial.result_path), result)
return result
result = {
"study_id": trial.study_id,
"trial_id": trial.trial_id,
@@ -596,9 +913,12 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
"best_request_count": None,
"failure_stage": failure_stage,
"failure_reason": str(exc),
"failure_traceback": traceback.format_exc(),
"probes": probe_history,
}
StudyStore.write_json(Path(trial.result_path), result)
return result
finally:
_terminate_process_tree(process, timeout_s=30.0)
_ignore_sigterm_if_main()
_terminate_process_tree(process, timeout_s=30.0, marker_env=trial_marker_env)
_restore_sigterm(previous_sigterm)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,100 @@
from __future__ import annotations
import importlib.util
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
_SPEC = importlib.util.spec_from_file_location(
"prepare_trace_windows",
REPO_ROOT / "scripts" / "prepare_trace_windows.py",
)
assert _SPEC and _SPEC.loader
ptw = importlib.util.module_from_spec(_SPEC)
# Register before exec so dataclasses can resolve the module's annotations.
sys.modules[_SPEC.name] = ptw
_SPEC.loader.exec_module(ptw)
class SessionCoherentSamplingTests(unittest.TestCase):
def test_multi_hop_chain_resolves_to_root(self) -> None:
root_of: dict[object, object] = {}
# turn1 root, turn2 -> turn1, turn3 -> turn2 (multi-hop), streamed in order.
self.assertEqual(
ptw.resolve_session_root({"chat_id": 1, "parent_chat_id": -1, "turn": 1}, root_of),
1,
)
self.assertEqual(
ptw.resolve_session_root({"chat_id": 2, "parent_chat_id": 1, "turn": 2}, root_of),
1,
)
self.assertEqual(
ptw.resolve_session_root({"chat_id": 3, "parent_chat_id": 2, "turn": 3}, root_of),
1,
)
def test_unknown_parent_falls_back_to_parent_id(self) -> None:
root_of: dict[object, object] = {}
# parent never seen (fell outside the span): group siblings under the parent.
self.assertEqual(
ptw.resolve_session_root({"chat_id": 50, "parent_chat_id": 9, "turn": 2}, root_of),
9,
)
self.assertEqual(
ptw.resolve_session_root({"chat_id": 51, "parent_chat_id": 9, "turn": 2}, root_of),
9,
)
def test_all_turns_of_a_session_share_one_u(self) -> None:
root_of: dict[object, object] = {}
rows = [
{"chat_id": 1, "parent_chat_id": -1, "turn": 1},
{"chat_id": 2, "parent_chat_id": 1, "turn": 2},
{"chat_id": 3, "parent_chat_id": 2, "turn": 3},
]
us = {
ptw.session_uniform(
seed=7,
window_id="w",
session_root=ptw.resolve_session_root(row, root_of),
)
for row in rows
}
self.assertEqual(len(us), 1)
only = next(iter(us))
self.assertGreaterEqual(only, 0.0)
self.assertLess(only, 1.0)
def test_thresholding_keeps_or_drops_whole_sessions(self) -> None:
# Two distinct sessions get distinct scores; a threshold either keeps a
# session's every turn or none of them.
seed, window_id = 20260325, "chat_w_x"
sessions = {
"A": [
{"chat_id": 10, "parent_chat_id": -1},
{"chat_id": 11, "parent_chat_id": 10},
],
"B": [
{"chat_id": 20, "parent_chat_id": -1},
{"chat_id": 21, "parent_chat_id": 20},
],
}
root_of: dict[object, object] = {}
scored: list[tuple[str, float]] = []
for name, rows in sessions.items():
for row in rows:
root = ptw.resolve_session_root(row, root_of)
u = ptw.session_uniform(seed=seed, window_id=window_id, session_root=root)
scored.append((name, u))
for name in sessions:
us = {u for n, u in scored if n == name}
self.assertEqual(len(us), 1, f"session {name} turns must share one u")
for threshold in (0.0, 0.25, 0.5, 0.75, 1.0):
for name in sessions:
kept = {u <= threshold for n, u in scored if n == name}
self.assertEqual(len(kept), 1, "a session must be kept/dropped as a whole")
if __name__ == "__main__":
unittest.main()

109
tests/test_tuning_report.py Normal file
View File

@@ -0,0 +1,109 @@
from __future__ import annotations
import json
import tempfile
import unittest
from pathlib import Path
from aituner.tuning_report import run_tuning_report
def _write_state(root: Path, *, study_id: str, rates: list[float | None]) -> None:
root.mkdir(parents=True)
trials = []
best_rate = None
best_trial_id = None
for idx, rate in enumerate(rates, start=1):
trial_id = f"trial-{idx:04d}"
trials.append(
{
"trial_id": trial_id,
"status": "completed" if rate is not None else "failed",
"parallel_size": 1,
"best_request_rate": rate,
"best_request_rate_per_gpu": rate,
"config_patch": {"env_patch": {}, "flag_patch": {}},
}
)
if rate is not None and (best_rate is None or rate > best_rate):
best_rate = rate
best_trial_id = trial_id
payload = {
"study_id": study_id,
"best_trial_id": best_trial_id,
"best_request_rate": best_rate,
"best_request_rate_per_gpu": best_rate,
"next_trial_index": len(rates) + 1,
"trials": trials,
}
(root / "state.json").write_text(json.dumps(payload), encoding="utf-8")
class TuningReportTests(unittest.TestCase):
def test_tuning_report_scores_harness_vs_naive_anytime_progress(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
_write_state(
tmp_path / "studies" / "harness-study",
study_id="harness-study",
rates=[0.4, 0.9],
)
_write_state(
tmp_path / "naive-study",
study_id="naive-study",
rates=[0.4, None, 0.7, 0.9],
)
spec_path = tmp_path / "report.json"
spec_path.write_text(
json.dumps(
{
"report_id": "report-1",
"output_root": str(tmp_path / "out"),
"target_fraction": 0.8,
"cases": [
{
"case_id": "case-1",
"tags": ["model-a", "chat"],
"budgets": [1, 2, 4],
"arms": [
{
"name": "harness",
"kind": "harness",
"study_root": str(tmp_path / "studies"),
},
{
"name": "naive",
"kind": "naive",
"study_root": str(tmp_path / "naive-study"),
},
],
}
],
}
),
encoding="utf-8",
)
summary = run_tuning_report(spec_path)
case = summary["cases"][0]
self.assertEqual(case["reference_best_per_gpu"], 0.9)
self.assertEqual(case["winners"]["final_best"], "harness")
self.assertEqual(case["winners"]["fastest_to_target"], "harness")
harness = case["arms"][0]
naive = case["arms"][1]
self.assertEqual(harness["best_at_budget"]["2"], 0.9)
self.assertEqual(naive["best_at_budget"]["2"], 0.4)
self.assertEqual(case["target_fraction"], 0.8)
self.assertEqual(harness["trials_to_target"], 2)
self.assertEqual(naive["trials_to_target"], 4)
self.assertEqual(naive["failed_count"], 1)
comparison = case["harness_vs_naive"][0]
self.assertTrue(comparison["passes"])
self.assertEqual(comparison["target_trial_speedup_vs_best_naive"], 2.0)
self.assertTrue((tmp_path / "out" / "summary.json").exists())
self.assertTrue((tmp_path / "out" / "report.md").exists())
if __name__ == "__main__":
unittest.main()