Compare commits
18 Commits
feat/two-s
...
83162e7a64
| Author | SHA1 | Date | |
|---|---|---|---|
| 83162e7a64 | |||
| a3523f5601 | |||
| 95c02d7dd9 | |||
| a1b804f879 | |||
| 0c23285f39 | |||
| 816765071f | |||
| 97d2ddabb1 | |||
| 8e58b4033d | |||
| b779f6e56a | |||
| e7d1b3ba01 | |||
| 579dd86698 | |||
| 37342a5749 | |||
| 5965f4fbbc | |||
| a1cbab0e69 | |||
| 0794efa249 | |||
| d975e57bb5 | |||
| a16016a876 | |||
| 07f5d92e1d |
180
configs/examples/dash0_qwen27b_ablation_harness_on.json
Normal file
180
configs/examples/dash0_qwen27b_ablation_harness_on.json
Normal 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
|
||||
}
|
||||
}
|
||||
180
configs/examples/dash0_qwen27b_ablation_naive_off.json
Normal file
180
configs/examples/dash0_qwen27b_ablation_naive_off.json
Normal 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
|
||||
}
|
||||
}
|
||||
99
docs/harness-ablation/harness-vs-naive-20260616.md
Normal file
99
docs/harness-ablation/harness-vs-naive-20260616.md
Normal 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 0–8k, 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 2–5 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
63
docs/two-stop-summary.md
Normal 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).
|
||||
71
scripts/ablation_trajectory.py
Normal file
71
scripts/ablation_trajectory.py
Normal 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()
|
||||
99
scripts/calibrate_time_scale.py
Normal file
99
scripts/calibrate_time_scale.py
Normal 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
21
scripts/run_ablation_pair.sh
Executable 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
|
||||
31
scripts/run_ablation_pair_d1.sh
Normal file
31
scripts/run_ablation_pair_d1.sh
Normal 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
15
scripts/run_naive_d1.sh
Executable 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
|
||||
26
scripts/run_naive_repeats_d1.sh
Normal file
26
scripts/run_naive_repeats_d1.sh
Normal 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) ==="
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user