18 Commits

Author SHA1 Message Date
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
13 changed files with 1032 additions and 5 deletions

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,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`.

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

@@ -0,0 +1,71 @@
#!/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
def topo(patch):
fp = (patch or {}).get("flag_patch", {}) or {}
ep = (patch or {}).get("env_patch", {}) or {}
parts = []
for k, label in (
("tensor-parallel-size", "TP"),
("data-parallel-size", "DP"),
("expert-parallel-size", "EP"),
):
if k in fp:
parts.append(f"{label}{fp[k]}")
runtime = {
k: v
for k, v in fp.items()
if k not in ("tensor-parallel-size", "data-parallel-size", "expert-parallel-size")
}
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"))
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'))}"
)
# 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'))}")
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())

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

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

@@ -24,6 +24,13 @@ _RUNTIME_KEYS = {
_STRONG_INCUMBENT_MIN_GAIN = 1.8
_MIN_POST_INCUMBENT_VALIDATION_TRIALS = 2
_VALIDATION_TRIALS_WITHOUT_FAMILY_COVERAGE = 3
# Decode-bound throughput is frequently KV-cache limited, so more gpu-memory-utilization
# yields more KV blocks and more concurrent decode. Hill-climb in small steps toward a
# safe ceiling and let measurement find the real peak: a too-high target regresses or
# fails to launch and is rejected by the incumbent guard, and its tested signature then
# blocks re-proposal so the climb terminates.
_GMU_STEP = 0.02
_GMU_SAFE_CEILING = 0.97
def build_harness_context(
@@ -383,14 +390,17 @@ def _knob_harnesses(
"knob_family": "gpu-memory-utilization",
"use_when": [
"The engine launches cleanly but memory headroom limits batching.",
"A decode-bound incumbent (decode_tpot) is KV-cache limited and could sustain more concurrent decode with more KV blocks.",
],
"procedure": [
"Make small adjustments only after topology and batching knobs are stable.",
"Raise gpu-memory-utilization one small step at a time and keep the step only if request_rate_per_gpu improves and the engine still launches.",
],
"guards": [
"Treat launch OOM as hard negative evidence and back off immediately.",
"Do not exceed a safe utilization ceiling; stop climbing once a higher target regresses or fails to launch.",
],
"active_now": False,
"active_now": active_bottleneck in {"decode_tpot", "admission_or_queueing"},
}
)
return harnesses
@@ -1184,6 +1194,15 @@ def _runtime_candidate_actions(
topology_patch = _preserve_topology_patch(study, anchor_flags)
actions: list[dict[str, Any]] = []
base_tp = _parse_int_like(study.engine.base_flags.get("tensor-parallel-size"), default=1)
base_dp = _parse_int_like(study.engine.base_flags.get("data-parallel-size"), default=1)
cur_tp = _parse_int_like(anchor_flags.get("tensor-parallel-size"), default=base_tp)
cur_dp = _parse_int_like(anchor_flags.get("data-parallel-size"), default=base_dp)
# Topology-before-runtime: gpu-mem-util / raising max-num-seqs are micro-tuning that is
# only justified once topology has moved off the baseline. At the baseline a latency
# bottleneck must still be answered with a topology change, not a runtime tweak.
topology_settled = cur_tp > base_tp or cur_dp > base_dp
if "max-num-batched-tokens" in tunable:
current_mbt = _parse_int_like(anchor_flags.get("max-num-batched-tokens"), default=0)
mbt_targets: list[tuple[str, int]] = []
@@ -1226,8 +1245,17 @@ def _runtime_candidate_actions(
if top_bottleneck == "admission_or_queueing":
target = max(8, int(current_mns * 1.5)) if current_mns > 0 else 64
mns_targets.append(("raise_max_num_seqs", _round_up_to_multiple(target, 8)))
elif top_bottleneck == "decode_tpot" and current_mns > 8:
mns_targets.append(("lower_max_num_seqs", max(8, current_mns // 2)))
elif top_bottleneck == "decode_tpot":
if current_mns > 8:
mns_targets.append(("lower_max_num_seqs", max(8, current_mns // 2)))
# Decode concurrency can also be too low: once topology is settled, raising
# max-num-seqs exploits decode parallelism when the incumbent has SLO headroom.
# The incumbent guard keeps it only if per-GPU rate improves.
if topology_settled:
raise_target = _round_up_to_multiple(
max(16, int(current_mns * 1.5)) if current_mns > 0 else 48, 8
)
mns_targets.append(("raise_max_num_seqs", raise_target))
for action_id, target in mns_targets:
patch = {**topology_patch, "max-num-seqs": target}
signature = _config_signature({"env_patch": {}, "flag_patch": patch})
@@ -1273,6 +1301,37 @@ def _runtime_candidate_actions(
],
)
)
if (
"gpu-memory-utilization" in tunable
and topology_settled
and top_bottleneck in {"decode_tpot", "admission_or_queueing"}
):
current_gmu = _parse_float_like(
anchor_flags.get("gpu-memory-utilization"), default=0.9
)
if 0.0 < current_gmu < _GMU_SAFE_CEILING:
target = round(min(_GMU_SAFE_CEILING, current_gmu + _GMU_STEP), 4)
if target > current_gmu:
patch = {**topology_patch, "gpu-memory-utilization": target}
signature = _config_signature({"env_patch": {}, "flag_patch": patch})
if signature not in tested_signatures:
actions.append(
_runtime_action(
action_id="raise_gpu_memory_utilization",
knob_family="gpu-memory-utilization",
score=0.4 + _information_gain(bottleneck_hypotheses, "runtime"),
patch=patch,
hypothesis=(
"Raise gpu-memory-utilization to add KV-cache headroom so the "
"decode-bound incumbent can sustain more concurrent decode."
),
expected_effects=[
"add KV-cache blocks for higher decode concurrency on the incumbent topology",
"reject if the higher memory target regresses request_rate_per_gpu or fails to launch",
],
)
)
return actions
@@ -2252,6 +2311,19 @@ def _parse_int_like(value: Any, *, default: int) -> int:
return default
def _parse_float_like(value: Any, *, default: float) -> float:
if value is None or isinstance(value, bool):
return default
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str) and value.strip():
try:
return float(value.strip())
except ValueError:
return default
return default
def _config_signature(config_patch: Any) -> str:
if not isinstance(config_patch, dict):
config_patch = {}

View File

@@ -18,7 +18,7 @@ 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
@@ -254,6 +254,34 @@ def _ignore_sigterm_if_main() -> None:
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],
*,
@@ -640,7 +668,9 @@ def run_trial(trial_spec_path: Path) -> dict[str, Any]:
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,
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,
)

View File

@@ -55,6 +55,7 @@ from aituner.store import StudyStore
from aituner.trace import load_trace_requests, summarize_window
from aituner.worker import (
_adaptive_replay_set,
_probe_drain_deadline,
_install_sigterm_as_keyboardinterrupt,
_restore_sigterm,
_should_extend_on_boundary,
@@ -535,6 +536,38 @@ class CoreFlowTests(unittest.TestCase):
)
)
def test_probe_drain_deadline_tracks_admitted_set_and_caps_at_ceiling(self) -> None:
slo = SloSpec.from_dict(
{
"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},
}
)
def req(arrival_s: float, in_tok: int, out_tok: int) -> TraceRequest:
return TraceRequest(
row_id="r",
arrival_s=arrival_s,
sampling_u=0.1,
body={},
prompt_tokens_hint=in_tok,
completion_tokens_hint=out_tok,
metadata={},
)
# 100 requests, last arrival 500s, p99 in=8000 / out=2000.
reqs = [req(float(i * 5), 8000, 2000) for i in range(100)]
# deadline = last_arrival + (ttft_ms + p99_out*tpot_ms)/1000 + margin
# = 495 + (5000 + 2000*50)/1000 + 30 = 495 + 105 + 30 = 630
self.assertAlmostEqual(
_probe_drain_deadline(reqs, slo, ceiling=1000.0), 630.0, places=3
)
# Ceiling caps a deadline that would otherwise exceed it.
self.assertEqual(_probe_drain_deadline(reqs, slo, ceiling=400.0), 400.0)
# No requests or no TPOT rule -> fall back to the ceiling.
self.assertEqual(_probe_drain_deadline([], slo, ceiling=400.0), 400.0)
def test_linear_ms_ttft_rule_scales_with_input_length(self) -> None:
slo = SloSpec.from_dict(
{
@@ -1285,6 +1318,113 @@ class CoreFlowTests(unittest.TestCase):
},
)
def test_harness_raises_gpu_mem_util_on_settled_decode_bound_incumbent(self) -> None:
"""Regression for the coverage gap that let the naive baseline beat the harness:
a settled TP incumbent that is decode_tpot-bound must get a gpu-memory-utilization
raise (KV-cache headroom) before the harness is allowed to stop."""
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
study_path = _write_study_assets(
tmp_path,
slo_overrides={
"ttft_rule": {"kind": "fixed_ms", "threshold_ms": 4000},
"tpot_rule": {"kind": "fixed_ms", "threshold_ms": 50},
},
engine_overrides={
"tunable_flags": [
"tensor-parallel-size",
"gpu-memory-utilization",
],
"topology_constraints": {
"allowed_tensor_parallel_sizes": [1, 2, 4],
"allowed_data_parallel_sizes": [1],
"allowed_tp_dp_products": [1, 2, 4],
},
},
)
study = load_study_spec(study_path)
result_path = tmp_path / "trial-0002.json"
result_path.write_text(
json.dumps(
{
"status": "completed",
"best_sampling_u": 0.074,
"best_request_rate": 2.6,
"best_pass_rate": 0.97,
"probes": [
{
"threshold": 0.074,
"feasible": True,
"payload": {
"request_count": 300,
"pass_rate": 0.97,
"request_rate": 2.6,
"latency_summary": {"failed_reason_counts": {}},
},
},
{
"threshold": 0.09,
"feasible": False,
"payload": {
"request_count": 300,
"pass_rate": 0.6,
"request_rate": 3.2,
"early_stop_reason": "slo_pass_rate_unrecoverable",
"latency_summary": {
"failed_reason_counts": {"tpot_ms>50.0": 90}
},
},
},
],
}
),
encoding="utf-8",
)
state = StudyState(
study_id=study.study_id,
best_trial_id="trial-0002",
best_request_rate=2.6,
best_request_rate_per_gpu=0.65,
trials=[
TrialSummary(
trial_id="trial-0001",
status="completed",
best_request_rate=1.1,
best_request_rate_per_gpu=0.275,
config_patch={"env_patch": {}, "flag_patch": {"tensor-parallel-size": 2}},
),
TrialSummary(
trial_id="trial-0002",
status="completed",
best_request_rate=2.6,
best_request_rate_per_gpu=0.65,
result_path=str(result_path),
config_patch={
"env_patch": {},
"flag_patch": {
"tensor-parallel-size": 4,
"gpu-memory-utilization": 0.9,
},
},
),
],
)
context = build_harness_context(
study=study, window_summary={"prompt_tokens_p95": 1500}, state=state
)
proposal = build_harness_guided_proposal(context)
self.assertIsNotNone(proposal)
self.assertFalse(proposal.should_stop)
# TP4 preserved; gpu-memory-utilization hill-climbed one step (0.9 -> 0.92).
self.assertEqual(
proposal.config_patch.flag_patch.get("tensor-parallel-size"), 4
)
self.assertEqual(
proposal.config_patch.flag_patch.get("gpu-memory-utilization"), 0.92
)
# And the harness must NOT authorize a stop while that knob is untried.
self.assertIsNone(build_harness_stop_proposal(context))
def test_harness_validates_unmeasured_tp_frontier_before_runtime_refinement(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)