Compare commits
3 Commits
d0c89dac48
...
8b4116fad0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b4116fad0 | |||
| 27d1c8fa92 | |||
| 984eb1f325 |
@@ -141,8 +141,8 @@ New comparable studies:
|
||||
|
||||
| Variant | Study ID | Status |
|
||||
| --- | --- | --- |
|
||||
| no-harness baseline | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` | running first |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` | queued to run after baseline |
|
||||
| no-harness baseline | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` | completed |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` | completed |
|
||||
|
||||
Both specs set:
|
||||
|
||||
@@ -152,4 +152,4 @@ Both specs set:
|
||||
- SLO: TTFT p95 <= 4000ms, TPOT p95 <= 25ms, target pass rate 0.95
|
||||
- search: full range, `inherit_incumbent_floor=false`
|
||||
|
||||
The no-harness baseline is running in tmux session `qwen27b-gpu8-noharness-20260513`. The harness run should only be started after the no-harness baseline finishes or reaches a sufficient early comparison point, because both need the full GPU host and should not run concurrently.
|
||||
Results: harness best `0.2696 req/s/GPU` (TP=4, MBT=7680) vs no-harness best `0.1233 req/s/GPU` (prefix-caching=false), a **+118.6%** improvement. Full analysis in `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md`.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# qwen235b Thinking Prefill Harness Ablation, 2026-05-10
|
||||
|
||||
**Superseded** by `qwen235b-thinking-prefill-ttft-3s6s9s-20260514.md` (updated SLO thresholds, 8-GPU setup). This document is retained for reference only.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# qwen235b Thinking Prefill Harness Ablation (TTFT 3s/6s/9s)
|
||||
|
||||
Date: 2026-05-14 / 2026-05-15
|
||||
|
||||
Supersedes: `qwen235b-thinking-prefill-ttft-20260510.md` (different SLO thresholds).
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
- Engine: internal vLLM at `/usr/local/bin/vllm`
|
||||
- Model: `/home/admin/resource/model/464482ce.qwen3-235b-a22b/256k-0717`
|
||||
- Trace window: `thinking_w20260327_1000`
|
||||
- Request mode: chat, with `completion_tokens_override=1` for prefill-only measurement
|
||||
- SLO: TTFT-only stepped p95 pass target, target pass rate `0.95`
|
||||
- input tokens `<=4096`: `3000 ms`
|
||||
- input tokens `<=32768`: `6000 ms`
|
||||
- otherwise: `9000 ms`
|
||||
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
|
||||
- Baseline topology: `TP=4`
|
||||
- LLM: `gpt-5.4`
|
||||
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
|
||||
|
||||
## Studies
|
||||
|
||||
| Variant | Study ID | search.high |
|
||||
| --- | --- | ---: |
|
||||
| no-harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-noharness-minprompt-gpt54-20260514` | 0.125 |
|
||||
| harness | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-12iter-harness-profileplanner-gpt54-20260514` | 0.125 |
|
||||
| harness (high=0.25) | `dash0-qwen235b-prefill-thinking-ttft-3s6s9s-high025-12iter-harness-profileplanner-gpt54-20260515` | 0.25 |
|
||||
|
||||
The `harness (high=0.25)` run was added to test whether raising `search.high` lets the harness find a better runtime config after reaching the search ceiling at `0.125`.
|
||||
|
||||
## Result
|
||||
|
||||
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure. `stop` means harness stopped before launching another trial.
|
||||
|
||||
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness raw `perf[i]` | 0.1804 | fail | 0.1892 | fail | 0.1892 | 0.1804 | 0.2217 | 0.2029 | 0.2029 | 0.2029 | 0.1892 | 0.1804 |
|
||||
| harness raw `perf[i]` | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
|
||||
| harness (high=0.25) raw `perf[i]` | 0.2029 | 0.3921 | 0.3442 | 0.3921 | 0.3821 | 0.3821 | 0.3821 | 0.3688 | 0.3821 | 0.3821 | 0.3821 | 0.3821 |
|
||||
|
||||
| Variant | GPU trials | Best iter | Best req/s | Best req/s/GPU | Best config summary |
|
||||
| --- | ---: | ---: | ---: | ---: | --- |
|
||||
| no-harness | 12 | 7 | 0.8867 | 0.2217 | TP=4, MNS=112, MBT=7168 |
|
||||
| harness | 2 (stop) | 2 | 3.0900 | 0.3863 | TP=8 |
|
||||
| harness (high=0.25) | 12 | 2 | 3.1367 | **0.3921** | TP=8 |
|
||||
|
||||
Harness reached **+74.2%** over no-harness at iter 2. With `search.high=0.25`, the harness found `0.3921 req/s/GPU` (+76.8%).
|
||||
|
||||
## Incumbent Curve
|
||||
|
||||
Best-so-far request rate per GPU after each iteration.
|
||||
|
||||
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness | 0.1804 | 0.1804 | 0.1892 | 0.1892 | 0.1892 | 0.1892 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 | 0.2217 |
|
||||
| harness | 0.2029 | 0.3863 | stop | stop | stop | stop | stop | stop | stop | stop | stop | stop |
|
||||
| harness (high=0.25) | 0.2029 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 | 0.3921 |
|
||||
|
||||
## Trial Details
|
||||
|
||||
No-harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.1804 | 0.1804 | completed | baseline (TP=4) |
|
||||
| 2 | - | 0.1804 | launch fail | TP=4, EP=4, MNS=128 |
|
||||
| 3 | 0.1892 | 0.1892 | completed | MNS=96 |
|
||||
| 4 | - | 0.1892 | launch fail | TP=4, DP=2, EP off, MNS=96 |
|
||||
| 5 | 0.1892 | 0.1892 | completed | MNS=112 |
|
||||
| 6 | 0.1804 | 0.1892 | completed | MNS=112, MBT=9216 |
|
||||
| 7 | 0.2217 | 0.2217 | completed | MNS=112, MBT=7168 |
|
||||
| 8 | 0.2029 | 0.2217 | completed | MNS=112, MBT=6144 |
|
||||
| 9 | 0.2029 | 0.2217 | completed | MNS=120, MBT=7168 |
|
||||
| 10 | 0.2029 | 0.2217 | completed | TP=4, DP=1, EP off, MNS=108, MBT=7168 |
|
||||
| 11 | 0.1892 | 0.2217 | completed | MNS=112, MBT=7680 |
|
||||
| 12 | 0.1804 | 0.2217 | completed | MNS=112, MBT=6912 |
|
||||
|
||||
Harness (`search.high=0.125`):
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
|
||||
| 2 | 0.3863 | 0.3863 | completed | TP=8 |
|
||||
| 3 | - | - | harness stop | search-high saturation (`sampling_u=0.123` vs `search.high=0.125`) |
|
||||
|
||||
Harness (`search.high=0.25`):
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.2029 | 0.2029 | completed | baseline (TP=4) |
|
||||
| 2 | 0.3921 | 0.3921 | completed | TP=8 |
|
||||
| 3 | 0.3442 | 0.3921 | completed | TP=8, chunked-prefill, MBT=32768 |
|
||||
| 4 | 0.3921 | 0.3921 | completed | TP=8, MBT=12288 |
|
||||
| 5 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=16384 |
|
||||
| 6 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=14336 |
|
||||
| 7 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=10240 |
|
||||
| 8 | 0.3688 | 0.3921 | completed | TP=8, EP off, MBT=11776 |
|
||||
| 9 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=13312 |
|
||||
| 10 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=7168 |
|
||||
| 11 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12032 |
|
||||
| 12 | 0.3821 | 0.3921 | completed | TP=8, EP off, MBT=12800 |
|
||||
|
||||
## Interpretation
|
||||
|
||||
No-harness never attempted TP=8. It stayed on the TP=4 baseline, encountered two launch failures (EP=4 and DP=2), and spent all remaining trials on runtime knob tuning within the TP=4 family. Its best finding was `MNS=112, MBT=7168` at iter 7 (`0.2217 req/s/GPU`).
|
||||
|
||||
Harness identified `ttft_prefill` as the dominant bottleneck from the baseline trial and immediately proposed TP=8 as the first topology move. This is the correct direction for a prefill-only workload with heavy-tail prompts (p95 ~19.7k tokens, p99 ~30k tokens).
|
||||
|
||||
With `search.high=0.125`, the harness stopped at iter 2 because the incumbent's best feasible `sampling_u=0.123` was within one search resolution of `search.high`. With `search.high=0.25`, the harness continued for 12 trials but the best remained iter 2 (`TP=8, default MBT`). The additional 10 trials explored MBT variations on TP=8 but none improved per-GPU throughput. This confirms the 2-trial harness result was already at or near the local optimum.
|
||||
|
||||
The gap between harness and no-harness (`+76.8%`) comes entirely from topology: TP=8 doubles the per-GPU prefill compute bandwidth compared to TP=4, which directly reduces TTFT and allows higher admitted request rates under the stepped TTFT SLO.
|
||||
|
||||
## Comparison with Previous Run (2026-05-10)
|
||||
|
||||
The 2026-05-10 run used different SLO thresholds and is documented in `qwen235b-thinking-prefill-ttft-20260510.md`. The core finding is consistent: harness finds TP=8 at iter 2-3 while no-harness gets stuck on TP=4 runtime tuning.
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Date: 2026-05-10
|
||||
|
||||
**Superseded** by `qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-20260513.md` (corrected 8-GPU setup). This document used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` (7 GPUs) and is retained for reference only.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0` (`172.27.114.84`)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Qwen27B Chat 0-8k Harness Ablation (8-GPU)
|
||||
|
||||
Date: 2026-05-13
|
||||
|
||||
Supersedes: `qwen27b-chat-0-8k-ttft4s-tpot25-20260510.md` (7-GPU / gpu3skip setup).
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`
|
||||
- Model: `/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`
|
||||
- Workload: `chat_w20260311_1000`, chat, 0-8k input window
|
||||
- SLO: TTFT <= 4000ms and TPOT <= 25ms, target pass rate = 0.95
|
||||
- Trial budget: 12 total tuning iterations per study
|
||||
- Search: `sampling_u` in `[0, 0.0625]`, tolerance `0.001`, max probes `6`
|
||||
- Execution: `python3 -m aituner.cli study tune ... --max-trials 12`
|
||||
- GPU env: `CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7` (8x H20)
|
||||
- Baseline topology: `TP=1`
|
||||
- LLM: `gpt-5.4`
|
||||
- Code: profile-driven harness planner, post GPU-visibility fix (`5c2958e`+)
|
||||
|
||||
## Studies
|
||||
|
||||
| Variant | Study ID |
|
||||
| --- | --- |
|
||||
| no-harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-noharness-minprompt-gpt54-20260513` |
|
||||
| harness | `dash0-qwen27b-chat-0-8k-ttft4s-tpot25-gpu8-12iter-harness-profileplanner-20260513` |
|
||||
|
||||
## Result
|
||||
|
||||
Raw per-iteration performance for Fig18-style plot. Metric: `best_request_rate_per_gpu` from that trial's own `result.json`. `NA` means the proposed config did not produce a feasible point. `fail` means engine launch failure.
|
||||
|
||||
| Variant | iter1 | iter2 | iter3 | iter4 | iter5 | iter6 | iter7 | iter8 | iter9 | iter10 | iter11 | iter12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness raw `perf[i]` | 0.0650 | fail | fail | 0.0617 | 0.0650 | 0.1233 | 0.1050 | 0.1233 | 0.0650 | 0.0650 | 0.0617 | 0.1233 |
|
||||
| harness raw `perf[i]` | 0.0650 | 0.1992 | 0.2621 | 0.2056 | 0.1544 | 0.2696 | 0.2621 | 0.2621 | 0.2696 | 0.2621 | 0.2621 | 0.2621 |
|
||||
|
||||
| Variant | Best iter | Best request rate | Best request rate / GPU | Best config summary |
|
||||
| --- | ---: | ---: | ---: | --- |
|
||||
| no-harness | 6 | 0.1233 | 0.1233 | `enable-prefix-caching=false` |
|
||||
| harness | 6 | 1.0783 | **0.2696** | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
|
||||
|
||||
Harness final best is **+118.6%** over no-harness.
|
||||
|
||||
## Incumbent Curve
|
||||
|
||||
Best-so-far request rate per GPU after each iteration.
|
||||
|
||||
| Variant | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| no-harness | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.0650 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 | 0.1233 |
|
||||
| harness | 0.0650 | 0.1992 | 0.2621 | 0.2621 | 0.2621 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 | 0.2696 |
|
||||
|
||||
## Trial Details
|
||||
|
||||
No-harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.0650 | 0.0650 | completed | baseline |
|
||||
| 2 | - | 0.0650 | launch fail | `gpu-memory-utilization=0.94`, `max-num-batched-tokens=16384` |
|
||||
| 3 | - | 0.0650 | launch fail | `enable-chunked-prefill=false` |
|
||||
| 4 | 0.0617 | 0.0650 | completed | `data-parallel-size=2` |
|
||||
| 5 | 0.0650 | 0.0650 | completed | `block-size=32` |
|
||||
| 6 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` |
|
||||
| 7 | 0.1050 | 0.1233 | completed | `enable-prefix-caching=false`, `block-size=32` |
|
||||
| 8 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=32` |
|
||||
| 9 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-batched-tokens=4096` |
|
||||
| 10 | 0.0650 | 0.1233 | completed | `enable-prefix-caching=false`, `max-num-seqs=16` |
|
||||
| 11 | 0.0617 | 0.1233 | completed | `data-parallel-size=2`, `enable-prefix-caching=false` |
|
||||
| 12 | 0.1233 | 0.1233 | completed | `enable-prefix-caching=false` (+ torch compile off) |
|
||||
|
||||
Harness:
|
||||
|
||||
| Iter | Result / GPU | Incumbent / GPU | Status | Config summary |
|
||||
| ---: | ---: | ---: | --- | --- |
|
||||
| 1 | 0.0650 | 0.0650 | completed | baseline |
|
||||
| 2 | 0.1992 | 0.1992 | completed | `tensor-parallel-size=2` |
|
||||
| 3 | 0.2621 | 0.2621 | completed | `tensor-parallel-size=4` |
|
||||
| 4 | 0.2056 | 0.2621 | completed | `tensor-parallel-size=8` |
|
||||
| 5 | 0.1544 | 0.2621 | completed | `tensor-parallel-size=4`, `data-parallel-size=2` |
|
||||
| 6 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680` |
|
||||
| 7 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `enable-chunked-prefill=true`, `max-num-batched-tokens=12288` |
|
||||
| 8 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7424` |
|
||||
| 9 | 0.2696 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=64` |
|
||||
| 10 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=56` |
|
||||
| 11 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=60` |
|
||||
| 12 | 0.2621 | 0.2696 | completed | `tensor-parallel-size=4`, `max-num-batched-tokens=7680`, `max-num-seqs=63` |
|
||||
|
||||
## Interpretation
|
||||
|
||||
No-harness never tested any TP change in 12 trials. It started from TP=1, encountered two early launch failures, then spent all remaining budget on runtime knobs (`enable-prefix-caching`, `block-size`, `max-num-seqs`, `max-num-batched-tokens`). Its best discovery was disabling prefix caching at iter 6, reaching only `0.1233 req/s/GPU`.
|
||||
|
||||
Harness systematically explored the TP frontier: iter 2 tested TP=2, iter 3 tested TP=4, iter 4 tested TP=8. The profile-driven planner identified `ttft_prefill` as the ranked bottleneck and proposed increasing TP as the primary relief action. After TP=4 proved best per-GPU, the harness tested TP=4/DP=2 (worse) then shifted to runtime refinement within the TP=4 family, settling on `max-num-batched-tokens=7680` as the marginal improvement.
|
||||
|
||||
The result demonstrates that topology exploration is critical for this workload: the no-harness LLM failed to discover TP>1 configurations entirely, while the harness reached the optimal TP=4 topology by iter 3 and refined it by iter 6.
|
||||
|
||||
## Comparison with Previous 7-GPU Run
|
||||
|
||||
The 7-GPU (`gpu3skip`) run from 2026-05-10 used `CUDA_VISIBLE_DEVICES=0,1,2,4,5,6,7` and is not directly comparable. The harness result on 7-GPU was `0.2742 req/s/GPU` (TP=4, chunked-prefill, MBT=16384). On 8-GPU, the harness found a similar TP=4 optimum at `0.2696 req/s/GPU` with slightly different runtime tuning. The core finding is consistent: harness accelerates topology discovery and significantly outperforms no-harness.
|
||||
106
docs/qwen27b-chat-0-8k-tpot25-16iter-20260506.md
Normal file
106
docs/qwen27b-chat-0-8k-tpot25-16iter-20260506.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# qwen27b-chat-0-8k TPOT25 16-Iter Harness Compare
|
||||
|
||||
## Goal
|
||||
|
||||
Rerun the internal vLLM Qwen3.5-27B chat 0-8k tuning comparison under a stricter
|
||||
TPOT SLO:
|
||||
|
||||
- no-harness: 16 tuning iterations;
|
||||
- harness: 16 tuning iterations, with permission to stop early if the harness
|
||||
convergence guard decides no further GPU trial is needed.
|
||||
|
||||
Both variants must be launched directly through AITuner. No state seeding,
|
||||
manual replay, or historical-result injection is allowed.
|
||||
|
||||
## Setup
|
||||
|
||||
- Host: `dash0`.
|
||||
- Hardware: 8 NVIDIA H20 GPUs.
|
||||
- Engine: internal vLLM at `/usr/local/bin/vllm`.
|
||||
- Model:
|
||||
`/home/admin/resource/model/464482ce/qwen3.5-27b/256k-0223-internal`.
|
||||
- Served model name: `qwen35-27b-aituner`.
|
||||
- Workload window: `chat_w20260311_1000`.
|
||||
- Trace path source: `/home/admin/cpfs/wjh/aituner/aituner/trace_windows/windows.json`.
|
||||
- Request mode: `chat`.
|
||||
- Input bucket: `0 <= input_length <= 8192`.
|
||||
- Replay scale: `1.0`.
|
||||
- Max concurrency: `32`.
|
||||
- Max requests per probe: unset, so each probe uses the full selected trace
|
||||
subset for its `sampling_u` threshold.
|
||||
- Restart engine after early stop: `true` for both variants. This is needed
|
||||
under TPOT25 because very slow infeasible probes can leave live HTTP requests
|
||||
in the engine after the SLO is already unrecoverable. Restarting keeps the
|
||||
next binary-search probe from being contaminated by previous in-flight work.
|
||||
- Search field: `sampling_u`.
|
||||
- Search range: `low=0.0`, `high=0.0625`.
|
||||
- Search probes: `max_probes=6`, `tolerance=0.001`.
|
||||
- Sampling seed: `20260325`.
|
||||
|
||||
## SLO
|
||||
|
||||
- Target pass rate: `0.95`.
|
||||
- TTFT rule:
|
||||
|
||||
| Input tokens | TTFT threshold |
|
||||
| ---: | ---: |
|
||||
| `<=4096` | `2000 ms` |
|
||||
| `<=32768` | `4000 ms` |
|
||||
| otherwise | `6000 ms` |
|
||||
|
||||
- TPOT rule: fixed `<=25 ms`.
|
||||
|
||||
## Specs
|
||||
|
||||
Remote generated specs:
|
||||
|
||||
- no-harness:
|
||||
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json`
|
||||
- harness:
|
||||
`.aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json`
|
||||
|
||||
The two specs were generated from
|
||||
`configs/examples/dash0_qwen27b_tight_slo_run4_0_8k.json`. After normalizing
|
||||
`study_id` and `llm.use_harness`, the JSON payloads compare equal. Therefore the
|
||||
only tuning-behavior difference between the formal comparison runs is whether
|
||||
the harness is enabled.
|
||||
|
||||
## Commands
|
||||
|
||||
No-harness:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m aituner.cli study tune \
|
||||
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-noharness.json \
|
||||
--store-root .aituner-tight \
|
||||
--max-trials 16
|
||||
```
|
||||
|
||||
Harness:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python3 -m aituner.cli study tune \
|
||||
--spec .aituner-tight/specs/dash0-qwen27b-chat-0-8k-tpot25-restart-16iter-harness.json \
|
||||
--store-root .aituner-tight \
|
||||
--max-trials 16
|
||||
```
|
||||
|
||||
## Run Log
|
||||
|
||||
- 2026-05-06 12:37 CST: generated both remote specs and verified that the only
|
||||
normalized difference is `llm.use_harness`.
|
||||
- 2026-05-06 12:37 CST: started no-harness in tmux session
|
||||
`qwen27b_tpot25_noharness_16iter_20260506`.
|
||||
- 2026-05-06 21:06 CST: stopped the initial no-harness pre-run before using it
|
||||
for comparison. It used `restart_engine_after_early_stop=false`; the first
|
||||
TP1 baseline probe already recorded `slo_pass_rate_unrecoverable`, but
|
||||
unfinished requests remained live in vLLM and would contaminate the next probe.
|
||||
- 2026-05-06 21:07 CST: generated the formal clean specs with
|
||||
`restart_engine_after_early_stop=true` for both variants and verified the
|
||||
normalized diff is still only `llm.use_harness`.
|
||||
- 2026-05-06 21:09 CST: started formal no-harness run in tmux session
|
||||
`qwen27b_tpot25_restart_noharness_16iter_20260506`.
|
||||
|
||||
## Results
|
||||
|
||||
Pending.
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from .compare import run_compare
|
||||
@@ -12,8 +13,20 @@ from .harness import (
|
||||
build_harness_stop_proposal,
|
||||
)
|
||||
from .job import append_job, build_trial_job
|
||||
from .lca import (
|
||||
build_workload_profile,
|
||||
resolve_length_mode,
|
||||
similarity_report,
|
||||
)
|
||||
from .llm import build_prompt, call_llm_for_proposal, load_capability_profile, parse_proposal_text
|
||||
from .spec import Proposal, SpecError, load_study_spec, to_jsonable
|
||||
from .spec import (
|
||||
Proposal,
|
||||
SpecError,
|
||||
StudySpec,
|
||||
load_structured_file,
|
||||
load_study_spec,
|
||||
to_jsonable,
|
||||
)
|
||||
from .store import StudyStore
|
||||
from .trace import load_trace_requests, summarize_window
|
||||
from .worker import run_trial
|
||||
@@ -422,6 +435,159 @@ def cmd_compare_run(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _resolve_profile_gpu_count(args: argparse.Namespace, study: StudySpec) -> int:
|
||||
gpu_count = args.gpu_count
|
||||
if gpu_count is None:
|
||||
gpu_count = study.hardware.gpu_count
|
||||
if gpu_count <= 0:
|
||||
raise SpecError("--gpu-count must be > 0.")
|
||||
return int(gpu_count)
|
||||
|
||||
|
||||
def _load_profile_study_spec(spec_path: Path) -> StudySpec:
|
||||
payload = dict(load_structured_file(spec_path))
|
||||
llm_payload = dict(payload.get("llm") or {})
|
||||
llm_payload.pop("endpoint", None)
|
||||
payload["llm"] = llm_payload
|
||||
return StudySpec.from_dict(payload)
|
||||
|
||||
|
||||
def _profile_current_study_window(args: argparse.Namespace) -> dict[str, object]:
|
||||
spec_path = Path(args.spec).resolve()
|
||||
study = _load_profile_study_spec(spec_path)
|
||||
mode = resolve_length_mode(
|
||||
request_mode=study.trace.request_mode,
|
||||
length_mode=args.length_mode,
|
||||
)
|
||||
window, requests = load_trace_requests(study, study_spec_path=spec_path)
|
||||
profile = build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=_resolve_profile_gpu_count(args, study),
|
||||
length_mode=mode,
|
||||
)
|
||||
return {
|
||||
"profile": profile.to_dict(),
|
||||
"source": {
|
||||
"study_spec_path": str(spec_path),
|
||||
"window_id": study.trace.window_id,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _resolve_windows_path_for_profile(study: StudySpec, *, study_spec_path: Path) -> Path:
|
||||
path = Path(study.trace.windows_path)
|
||||
if not path.is_absolute():
|
||||
path = (study_spec_path.parent / path).resolve()
|
||||
return path
|
||||
|
||||
|
||||
def _load_profile_windows(
|
||||
study: StudySpec,
|
||||
*,
|
||||
study_spec_path: Path,
|
||||
) -> list[dict[str, object]]:
|
||||
windows_path = _resolve_windows_path_for_profile(study, study_spec_path=study_spec_path)
|
||||
payload = json.loads(windows_path.read_text(encoding="utf-8"))
|
||||
raw_windows = payload.get("windows") if isinstance(payload, dict) else payload
|
||||
if not isinstance(raw_windows, list):
|
||||
raise SpecError(f"windows payload must contain a list: {windows_path}")
|
||||
return [
|
||||
{str(key): value for key, value in item.items()}
|
||||
for item in raw_windows
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
|
||||
|
||||
def _selected_profile_windows(
|
||||
args: argparse.Namespace,
|
||||
study: StudySpec,
|
||||
*,
|
||||
study_spec_path: Path,
|
||||
) -> list[dict[str, object]]:
|
||||
windows = _load_profile_windows(study, study_spec_path=study_spec_path)
|
||||
window_ids = set(args.window_id or [])
|
||||
selected: list[dict[str, object]] = []
|
||||
for item in windows:
|
||||
window_id = str(item.get("window_id") or "").strip()
|
||||
if not window_id:
|
||||
continue
|
||||
if window_ids and window_id not in window_ids:
|
||||
continue
|
||||
if not window_ids and not args.all:
|
||||
if window_id != study.trace.window_id:
|
||||
continue
|
||||
trace_type = str(item.get("trace_type") or "").strip()
|
||||
if args.trace_type and trace_type != args.trace_type:
|
||||
continue
|
||||
date_value = str(item.get("date") or "").strip()
|
||||
if args.date_from and date_value and date_value < args.date_from:
|
||||
continue
|
||||
if args.date_to and date_value and date_value > args.date_to:
|
||||
continue
|
||||
if args.slot_token and str(item.get("slot_token") or "").strip() != args.slot_token:
|
||||
continue
|
||||
selected.append(item)
|
||||
selected.sort(
|
||||
key=lambda item: (
|
||||
str(item.get("date") or ""),
|
||||
str(item.get("slot_token") or ""),
|
||||
str(item.get("window_id") or ""),
|
||||
)
|
||||
)
|
||||
if args.limit is not None:
|
||||
selected = selected[: args.limit]
|
||||
if not selected:
|
||||
raise SpecError("No trace windows selected for profile similarity.")
|
||||
return selected
|
||||
|
||||
|
||||
def cmd_profile_window(args: argparse.Namespace) -> int:
|
||||
print(json.dumps(_profile_current_study_window(args), ensure_ascii=False, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_profile_similarity(args: argparse.Namespace) -> int:
|
||||
spec_path = Path(args.spec).resolve()
|
||||
study = _load_profile_study_spec(spec_path)
|
||||
mode = resolve_length_mode(
|
||||
request_mode=study.trace.request_mode,
|
||||
length_mode=args.length_mode,
|
||||
)
|
||||
gpu_count = _resolve_profile_gpu_count(args, study)
|
||||
profiles = []
|
||||
selected = _selected_profile_windows(args, study, study_spec_path=spec_path)
|
||||
for item in selected:
|
||||
window_id = str(item["window_id"])
|
||||
window_study = replace(study, trace=replace(study.trace, window_id=window_id))
|
||||
window, requests = load_trace_requests(window_study, study_spec_path=spec_path)
|
||||
profiles.append(
|
||||
build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=gpu_count,
|
||||
length_mode=mode,
|
||||
)
|
||||
)
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"source": {
|
||||
"study_spec_path": str(spec_path),
|
||||
"selected_window_count": len(profiles),
|
||||
"length_mode": mode,
|
||||
"gpu_count": gpu_count,
|
||||
},
|
||||
"profiles": [profile.to_dict() for profile in profiles],
|
||||
"similarity": similarity_report(profiles),
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="AITuner CLI")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
@@ -490,6 +656,50 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
compare_run.add_argument("--output-root")
|
||||
compare_run.set_defaults(func=cmd_compare_run)
|
||||
|
||||
profile = subparsers.add_parser("profile")
|
||||
profile_sub = profile.add_subparsers(dest="profile_command", required=True)
|
||||
|
||||
profile_window = profile_sub.add_parser("window")
|
||||
profile_window.add_argument("--spec", required=True)
|
||||
profile_window.add_argument(
|
||||
"--length-mode",
|
||||
default="auto",
|
||||
choices=["auto", "total", "input", "output"],
|
||||
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
|
||||
)
|
||||
profile_window.add_argument(
|
||||
"--gpu-count",
|
||||
type=int,
|
||||
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
|
||||
)
|
||||
profile_window.set_defaults(func=cmd_profile_window)
|
||||
|
||||
profile_similarity = profile_sub.add_parser("similarity")
|
||||
profile_similarity.add_argument("--spec", required=True)
|
||||
profile_similarity.add_argument("--window-id", action="append")
|
||||
profile_similarity.add_argument("--trace-type")
|
||||
profile_similarity.add_argument("--date-from")
|
||||
profile_similarity.add_argument("--date-to")
|
||||
profile_similarity.add_argument("--slot-token")
|
||||
profile_similarity.add_argument("--limit", type=int)
|
||||
profile_similarity.add_argument(
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="Profile all windows selected by filters. Without this or --window-id, only the study window is used.",
|
||||
)
|
||||
profile_similarity.add_argument(
|
||||
"--length-mode",
|
||||
default="auto",
|
||||
choices=["auto", "total", "input", "output"],
|
||||
help="Token length basis for the L vector. auto uses output for decode_only and total otherwise.",
|
||||
)
|
||||
profile_similarity.add_argument(
|
||||
"--gpu-count",
|
||||
type=int,
|
||||
help="GPU denominator for per-GPU arrival rate. Defaults to hardware.gpu_count.",
|
||||
)
|
||||
profile_similarity.set_defaults(func=cmd_profile_similarity)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
406
src/aituner/lca.py
Normal file
406
src/aituner/lca.py
Normal file
@@ -0,0 +1,406 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import statistics
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Sequence
|
||||
|
||||
from .trace import TraceRequest, WindowRecord
|
||||
|
||||
|
||||
EPSILON = 1e-9
|
||||
|
||||
FEATURE_NAMES = [
|
||||
"L.log_mean_length",
|
||||
"L.log_p95_over_mean_length",
|
||||
"L.cv_length",
|
||||
"C.log_mean_hit_length",
|
||||
"C.log_p95_over_mean_hit_length",
|
||||
"C.cv_hit_length",
|
||||
"C.hit_rate",
|
||||
"A.log_request_rate_per_gpu",
|
||||
"A.cv_interarrival",
|
||||
"A.log_fano_1s",
|
||||
]
|
||||
|
||||
FAMILY_SLICES = {
|
||||
"L": slice(0, 3),
|
||||
"C": slice(3, 7),
|
||||
"A": slice(7, 10),
|
||||
}
|
||||
|
||||
LENGTH_MODES = {"total", "input", "output"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WorkloadProfile:
|
||||
window_id: str
|
||||
trace_type: str
|
||||
request_count: int
|
||||
duration_s: float
|
||||
gpu_count: int
|
||||
length_mode: str
|
||||
feature_names: list[str]
|
||||
vector: list[float]
|
||||
stats: dict[str, Any]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"window_id": self.window_id,
|
||||
"trace_type": self.trace_type,
|
||||
"request_count": self.request_count,
|
||||
"duration_s": self.duration_s,
|
||||
"gpu_count": self.gpu_count,
|
||||
"length_mode": self.length_mode,
|
||||
"feature_names": self.feature_names,
|
||||
"vector": self.vector,
|
||||
"stats": self.stats,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RobustScale:
|
||||
feature_names: list[str]
|
||||
center: list[float]
|
||||
scale: list[float]
|
||||
|
||||
def transform(self, vector: Sequence[float]) -> list[float]:
|
||||
return [
|
||||
(float(value) - self.center[idx]) / self.scale[idx]
|
||||
for idx, value in enumerate(vector)
|
||||
]
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"feature_names": self.feature_names,
|
||||
"center": self.center,
|
||||
"scale": self.scale,
|
||||
}
|
||||
|
||||
|
||||
def resolve_length_mode(*, request_mode: str | None = None, length_mode: str = "auto") -> str:
|
||||
normalized = str(length_mode or "auto").strip().lower()
|
||||
if normalized == "auto":
|
||||
return (
|
||||
"output"
|
||||
if str(request_mode or "").strip().lower() == "decode_only"
|
||||
else "total"
|
||||
)
|
||||
if normalized not in LENGTH_MODES:
|
||||
raise ValueError(
|
||||
"length_mode must be one of: auto, total, input, output."
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def build_workload_profile(
|
||||
requests: list[TraceRequest],
|
||||
window: WindowRecord,
|
||||
*,
|
||||
gpu_count: int,
|
||||
length_mode: str = "total",
|
||||
) -> WorkloadProfile:
|
||||
if gpu_count <= 0:
|
||||
raise ValueError("gpu_count must be > 0.")
|
||||
if length_mode not in LENGTH_MODES:
|
||||
raise ValueError(f"Unsupported length_mode: {length_mode}")
|
||||
|
||||
duration_s = _duration_s(requests, window)
|
||||
input_lengths = [float(item.prompt_tokens_hint or 0) for item in requests]
|
||||
output_lengths = [float(item.completion_tokens_hint or 0) for item in requests]
|
||||
profile_lengths = [
|
||||
_profile_length(input_len, output_len, length_mode=length_mode)
|
||||
for input_len, output_len in zip(input_lengths, output_lengths)
|
||||
]
|
||||
hit_lengths, cache_stats = _ideal_cache_hit_lengths(
|
||||
requests,
|
||||
input_lengths=input_lengths,
|
||||
block_size=_block_size(window),
|
||||
)
|
||||
arrival_stats = _arrival_stats(requests, duration_s=duration_s, gpu_count=gpu_count)
|
||||
|
||||
length_stats = _series_stats(profile_lengths)
|
||||
hit_stats = _series_stats(hit_lengths)
|
||||
total_profile_length = sum(profile_lengths)
|
||||
total_input_length = sum(input_lengths)
|
||||
total_hit_length = sum(hit_lengths)
|
||||
feature_hit_rate = (
|
||||
float(total_hit_length / max(total_profile_length, EPSILON))
|
||||
if total_profile_length > 0
|
||||
else 0.0
|
||||
)
|
||||
input_hit_rate = (
|
||||
float(total_hit_length / max(total_input_length, EPSILON))
|
||||
if total_input_length > 0
|
||||
else 0.0
|
||||
)
|
||||
|
||||
vector = [
|
||||
math.log1p(length_stats["mean"]),
|
||||
math.log1p(length_stats["p95"] / max(length_stats["mean"], EPSILON)),
|
||||
length_stats["cv"],
|
||||
math.log1p(hit_stats["mean"]),
|
||||
math.log1p(hit_stats["p95"] / max(hit_stats["mean"], EPSILON)),
|
||||
hit_stats["cv"],
|
||||
feature_hit_rate,
|
||||
math.log1p(arrival_stats["request_rate_per_gpu"]),
|
||||
arrival_stats["interarrival_cv"],
|
||||
math.log1p(arrival_stats["fano_1s"]),
|
||||
]
|
||||
|
||||
return WorkloadProfile(
|
||||
window_id=window.window_id,
|
||||
trace_type=window.trace_type,
|
||||
request_count=len(requests),
|
||||
duration_s=duration_s,
|
||||
gpu_count=int(gpu_count),
|
||||
length_mode=length_mode,
|
||||
feature_names=list(FEATURE_NAMES),
|
||||
vector=[float(item) for item in vector],
|
||||
stats={
|
||||
"length": {
|
||||
**length_stats,
|
||||
"mode": length_mode,
|
||||
"total": total_profile_length,
|
||||
"input_total": total_input_length,
|
||||
"output_total": sum(output_lengths),
|
||||
},
|
||||
"cache": {
|
||||
**hit_stats,
|
||||
**cache_stats,
|
||||
"total_hit_length": total_hit_length,
|
||||
"hit_rate": feature_hit_rate,
|
||||
"input_hit_rate": input_hit_rate,
|
||||
},
|
||||
"arrival": arrival_stats,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def fit_robust_scale(profiles: Sequence[WorkloadProfile]) -> RobustScale:
|
||||
if not profiles:
|
||||
raise ValueError("At least one profile is required to fit a robust scale.")
|
||||
centers: list[float] = []
|
||||
scales: list[float] = []
|
||||
for idx in range(len(FEATURE_NAMES)):
|
||||
values = [float(profile.vector[idx]) for profile in profiles]
|
||||
median = _percentile(values, 50.0)
|
||||
iqr = _percentile(values, 75.0) - _percentile(values, 25.0)
|
||||
centers.append(float(median))
|
||||
scales.append(float(iqr if abs(iqr) > EPSILON else 1.0))
|
||||
return RobustScale(feature_names=list(FEATURE_NAMES), center=centers, scale=scales)
|
||||
|
||||
|
||||
def profile_similarity(
|
||||
left: WorkloadProfile,
|
||||
right: WorkloadProfile,
|
||||
*,
|
||||
scale: RobustScale | None = None,
|
||||
) -> float:
|
||||
scaler = scale or fit_robust_scale([left, right])
|
||||
z_left = scaler.transform(left.vector)
|
||||
z_right = scaler.transform(right.vector)
|
||||
return _similarity_from_z(z_left, z_right)
|
||||
|
||||
|
||||
def similarity_report(profiles: Sequence[WorkloadProfile]) -> dict[str, Any]:
|
||||
if not profiles:
|
||||
raise ValueError("At least one profile is required.")
|
||||
scale = fit_robust_scale(profiles)
|
||||
transformed = [scale.transform(profile.vector) for profile in profiles]
|
||||
rows: list[dict[str, Any]] = []
|
||||
matrix: list[list[float]] = []
|
||||
for i, left in enumerate(profiles):
|
||||
row_values: list[float] = []
|
||||
for j, right in enumerate(profiles):
|
||||
sim = _similarity_from_z(transformed[i], transformed[j])
|
||||
row_values.append(sim)
|
||||
rows.append(
|
||||
{
|
||||
"left": left.window_id,
|
||||
"right": right.window_id,
|
||||
"similarity": sim,
|
||||
"family_similarity": _family_similarity(transformed[i], transformed[j]),
|
||||
}
|
||||
)
|
||||
matrix.append(row_values)
|
||||
return {
|
||||
"feature_names": list(FEATURE_NAMES),
|
||||
"scaler": scale.to_dict(),
|
||||
"windows": [profile.window_id for profile in profiles],
|
||||
"matrix": matrix,
|
||||
"pairs": rows,
|
||||
}
|
||||
|
||||
|
||||
def dumps_profile(profile: WorkloadProfile) -> str:
|
||||
return json.dumps(profile.to_dict(), ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
|
||||
def _duration_s(requests: list[TraceRequest], window: WindowRecord) -> float:
|
||||
duration = max(float(window.window_end) - float(window.window_start), 0.0)
|
||||
if duration > 0:
|
||||
return duration
|
||||
if len(requests) >= 2:
|
||||
return max(0.0, float(requests[-1].arrival_s) - float(requests[0].arrival_s))
|
||||
return 0.0
|
||||
|
||||
|
||||
def _profile_length(input_length: float, output_length: float, *, length_mode: str) -> float:
|
||||
if length_mode == "input":
|
||||
return max(input_length, 0.0)
|
||||
if length_mode == "output":
|
||||
return max(output_length, 0.0)
|
||||
return max(input_length, 0.0) + max(output_length, 0.0)
|
||||
|
||||
|
||||
def _block_size(window: WindowRecord) -> int:
|
||||
value = window.source_payload.get("block_size")
|
||||
if isinstance(value, bool):
|
||||
return 1
|
||||
if isinstance(value, (int, float)) and value > 0:
|
||||
return int(value)
|
||||
if isinstance(value, str) and value.strip():
|
||||
try:
|
||||
parsed = int(value)
|
||||
except ValueError:
|
||||
return 1
|
||||
return parsed if parsed > 0 else 1
|
||||
return 1
|
||||
|
||||
|
||||
def _ideal_cache_hit_lengths(
|
||||
requests: list[TraceRequest],
|
||||
*,
|
||||
input_lengths: list[float],
|
||||
block_size: int,
|
||||
) -> tuple[list[float], dict[str, Any]]:
|
||||
seen_hashes: set[Any] = set()
|
||||
hit_lengths: list[float] = []
|
||||
total_blocks = 0
|
||||
repeated_blocks = 0
|
||||
rows_with_hash_ids = 0
|
||||
for request, input_length in zip(requests, input_lengths):
|
||||
hash_ids = request.metadata.get("hash_ids")
|
||||
if not isinstance(hash_ids, list):
|
||||
hit_lengths.append(0.0)
|
||||
continue
|
||||
rows_with_hash_ids += 1
|
||||
repeated_for_request = 0
|
||||
for hash_id in hash_ids:
|
||||
total_blocks += 1
|
||||
if hash_id in seen_hashes:
|
||||
repeated_blocks += 1
|
||||
repeated_for_request += 1
|
||||
else:
|
||||
seen_hashes.add(hash_id)
|
||||
hit_lengths.append(float(min(max(input_length, 0.0), repeated_for_request * block_size)))
|
||||
return hit_lengths, {
|
||||
"block_size": block_size,
|
||||
"rows_with_hash_ids": rows_with_hash_ids,
|
||||
"total_blocks": total_blocks,
|
||||
"repeated_blocks": repeated_blocks,
|
||||
"repeated_block_ratio": (
|
||||
float(repeated_blocks / total_blocks) if total_blocks else 0.0
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _arrival_stats(
|
||||
requests: list[TraceRequest],
|
||||
*,
|
||||
duration_s: float,
|
||||
gpu_count: int,
|
||||
) -> dict[str, Any]:
|
||||
arrivals = [float(item.arrival_s) for item in requests]
|
||||
interarrivals = [
|
||||
max(0.0, arrivals[idx] - arrivals[idx - 1])
|
||||
for idx in range(1, len(arrivals))
|
||||
]
|
||||
per_second_counts = _per_second_counts(arrivals, duration_s=duration_s)
|
||||
qps = float(len(requests) / duration_s) if duration_s > 0 else 0.0
|
||||
return {
|
||||
"request_rate": qps,
|
||||
"request_rate_per_gpu": float(qps / gpu_count) if gpu_count > 0 else 0.0,
|
||||
"interarrival_cv": _cv(interarrivals),
|
||||
"fano_1s": _fano(per_second_counts),
|
||||
"one_second_count_mean": statistics.fmean(per_second_counts)
|
||||
if per_second_counts
|
||||
else 0.0,
|
||||
"one_second_count_variance": statistics.pvariance(per_second_counts)
|
||||
if len(per_second_counts) >= 2
|
||||
else 0.0,
|
||||
"one_second_bin_count": len(per_second_counts),
|
||||
}
|
||||
|
||||
|
||||
def _per_second_counts(arrivals: list[float], *, duration_s: float) -> list[float]:
|
||||
if duration_s <= 0:
|
||||
return [float(len(arrivals))] if arrivals else []
|
||||
bin_count = max(1, int(math.ceil(duration_s)))
|
||||
counts = [0.0 for _ in range(bin_count)]
|
||||
for arrival in arrivals:
|
||||
if arrival < 0:
|
||||
continue
|
||||
idx = int(math.floor(arrival))
|
||||
if 0 <= idx < bin_count:
|
||||
counts[idx] += 1.0
|
||||
return counts
|
||||
|
||||
|
||||
def _series_stats(values: list[float]) -> dict[str, float]:
|
||||
return {
|
||||
"count": float(len(values)),
|
||||
"mean": statistics.fmean(values) if values else 0.0,
|
||||
"p50": _percentile(values, 50.0),
|
||||
"p95": _percentile(values, 95.0),
|
||||
"cv": _cv(values),
|
||||
}
|
||||
|
||||
|
||||
def _cv(values: list[float]) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
mean = statistics.fmean(values)
|
||||
if abs(mean) <= EPSILON:
|
||||
return 0.0
|
||||
return float(statistics.pstdev(values) / mean) if len(values) >= 2 else 0.0
|
||||
|
||||
|
||||
def _fano(values: list[float]) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
mean = statistics.fmean(values)
|
||||
if abs(mean) <= EPSILON:
|
||||
return 0.0
|
||||
return float(statistics.pvariance(values) / mean) if len(values) >= 2 else 0.0
|
||||
|
||||
|
||||
def _percentile(values: Sequence[float], p: float) -> float:
|
||||
if not values:
|
||||
return 0.0
|
||||
ordered = sorted(float(item) for item in values)
|
||||
if len(ordered) == 1:
|
||||
return ordered[0]
|
||||
rank = (p / 100.0) * (len(ordered) - 1)
|
||||
lower = int(math.floor(rank))
|
||||
upper = int(math.ceil(rank))
|
||||
if lower == upper:
|
||||
return ordered[lower]
|
||||
weight = rank - lower
|
||||
return float(ordered[lower] * (1.0 - weight) + ordered[upper] * weight)
|
||||
|
||||
|
||||
def _similarity_from_z(left: Sequence[float], right: Sequence[float]) -> float:
|
||||
distance = math.sqrt(
|
||||
sum((float(lval) - float(rval)) ** 2 for lval, rval in zip(left, right))
|
||||
)
|
||||
return float(math.exp(-distance))
|
||||
|
||||
|
||||
def _family_similarity(left: Sequence[float], right: Sequence[float]) -> dict[str, float]:
|
||||
result: dict[str, float] = {}
|
||||
for family, family_slice in FAMILY_SLICES.items():
|
||||
result[family] = _similarity_from_z(left[family_slice], right[family_slice])
|
||||
return result
|
||||
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import io
|
||||
import math
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
@@ -25,6 +27,12 @@ from aituner.harness import (
|
||||
build_harness_guided_proposal,
|
||||
build_harness_stop_proposal,
|
||||
)
|
||||
from aituner.lca import (
|
||||
build_workload_profile,
|
||||
profile_similarity,
|
||||
resolve_length_mode,
|
||||
similarity_report,
|
||||
)
|
||||
from aituner.llm import _extract_response_text, build_prompt, parse_proposal_text, validate_proposal
|
||||
from aituner.search import ThresholdProbe, binary_search_max_feasible
|
||||
from aituner.slo import RequestOutcome, evaluate_request, summarize_evaluations
|
||||
@@ -48,7 +56,7 @@ from aituner.worker import (
|
||||
_wait_for_server_or_exit,
|
||||
run_trial,
|
||||
)
|
||||
from aituner.trace import TraceRequest
|
||||
from aituner.trace import TraceRequest, WindowRecord
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -241,6 +249,150 @@ class CoreFlowTests(unittest.TestCase):
|
||||
self.assertIn("knob_harnesses", prompt)
|
||||
self.assertTrue(study_root.exists())
|
||||
|
||||
def test_lca_workload_profile_uses_standard_10d_features(self) -> None:
|
||||
window = WindowRecord(
|
||||
window_id="w1",
|
||||
trace_path=Path("trace.jsonl"),
|
||||
trace_type="chat",
|
||||
window_start=0.0,
|
||||
window_end=4.0,
|
||||
source_payload={"block_size": 64},
|
||||
)
|
||||
requests = [
|
||||
TraceRequest(
|
||||
row_id="r1",
|
||||
arrival_s=0.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=100,
|
||||
completion_tokens_hint=10,
|
||||
metadata={"hash_ids": [1, 2]},
|
||||
),
|
||||
TraceRequest(
|
||||
row_id="r2",
|
||||
arrival_s=1.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=100,
|
||||
completion_tokens_hint=20,
|
||||
metadata={"hash_ids": [1, 3]},
|
||||
),
|
||||
]
|
||||
|
||||
profile = build_workload_profile(
|
||||
requests,
|
||||
window,
|
||||
gpu_count=2,
|
||||
length_mode="total",
|
||||
)
|
||||
|
||||
self.assertEqual(len(profile.feature_names), 10)
|
||||
self.assertEqual(len(profile.vector), 10)
|
||||
self.assertEqual(profile.feature_names[0], "L.log_mean_length")
|
||||
self.assertAlmostEqual(profile.stats["cache"]["total_hit_length"], 64.0)
|
||||
self.assertAlmostEqual(profile.stats["cache"]["hit_rate"], 64.0 / 230.0)
|
||||
self.assertAlmostEqual(profile.stats["cache"]["input_hit_rate"], 64.0 / 200.0)
|
||||
self.assertAlmostEqual(profile.vector[3], math.log1p(32.0))
|
||||
self.assertAlmostEqual(profile.vector[5], 1.0)
|
||||
self.assertAlmostEqual(profile.stats["arrival"]["request_rate_per_gpu"], 0.25)
|
||||
self.assertAlmostEqual(profile.stats["arrival"]["fano_1s"], 0.5)
|
||||
self.assertEqual(resolve_length_mode(request_mode="decode_only"), "output")
|
||||
|
||||
def test_lca_similarity_matrix_separates_different_profiles(self) -> None:
|
||||
window = WindowRecord(
|
||||
window_id="base",
|
||||
trace_path=Path("trace.jsonl"),
|
||||
trace_type="chat",
|
||||
window_start=0.0,
|
||||
window_end=4.0,
|
||||
source_payload={"block_size": 64},
|
||||
)
|
||||
|
||||
def make_profile(window_id: str, input_tokens: int, *, arrival_gap: float) -> object:
|
||||
reqs = [
|
||||
TraceRequest(
|
||||
row_id=f"{window_id}-1",
|
||||
arrival_s=0.0,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=input_tokens,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [window_id, 1]},
|
||||
),
|
||||
TraceRequest(
|
||||
row_id=f"{window_id}-2",
|
||||
arrival_s=arrival_gap,
|
||||
sampling_u=1.0,
|
||||
body={},
|
||||
prompt_tokens_hint=input_tokens,
|
||||
completion_tokens_hint=16,
|
||||
metadata={"hash_ids": [window_id, 1, 2]},
|
||||
),
|
||||
]
|
||||
return build_workload_profile(
|
||||
reqs,
|
||||
WindowRecord(
|
||||
window_id=window_id,
|
||||
trace_path=window.trace_path,
|
||||
trace_type=window.trace_type,
|
||||
window_start=window.window_start,
|
||||
window_end=window.window_end,
|
||||
source_payload=window.source_payload,
|
||||
),
|
||||
gpu_count=1,
|
||||
length_mode="total",
|
||||
)
|
||||
|
||||
p1 = make_profile("same-a", 100, arrival_gap=1.0)
|
||||
p2 = make_profile("same-b", 100, arrival_gap=1.0)
|
||||
p3 = make_profile("different", 10000, arrival_gap=0.1)
|
||||
|
||||
report = similarity_report([p1, p2, p3])
|
||||
|
||||
self.assertAlmostEqual(profile_similarity(p1, p2), 1.0)
|
||||
self.assertGreater(report["matrix"][0][1], report["matrix"][0][2])
|
||||
self.assertIn("L", report["pairs"][2]["family_similarity"])
|
||||
|
||||
def test_cli_profile_window_outputs_lca_profile(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
stdout = io.StringIO()
|
||||
with mock.patch("sys.stdout", stdout):
|
||||
rc = cli_main(
|
||||
[
|
||||
"profile",
|
||||
"window",
|
||||
"--spec",
|
||||
str(study_path),
|
||||
"--gpu-count",
|
||||
"8",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
payload = json.loads(stdout.getvalue())
|
||||
self.assertEqual(payload["profile"]["window_id"], "chat_w1")
|
||||
self.assertEqual(len(payload["profile"]["vector"]), 10)
|
||||
self.assertEqual(payload["profile"]["gpu_count"], 8)
|
||||
|
||||
def test_cli_profile_window_does_not_resolve_llm_endpoint(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
study_path = _write_study_assets(tmp_path)
|
||||
payload = json.loads(study_path.read_text(encoding="utf-8"))
|
||||
payload["llm"]["endpoint"] = {
|
||||
"provider": "codex",
|
||||
"model": "gpt-5.4",
|
||||
}
|
||||
study_path.write_text(json.dumps(payload), encoding="utf-8")
|
||||
stdout = io.StringIO()
|
||||
with mock.patch("sys.stdout", stdout):
|
||||
rc = cli_main(["profile", "window", "--spec", str(study_path)])
|
||||
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(json.loads(stdout.getvalue())["profile"]["window_id"], "chat_w1")
|
||||
|
||||
def test_harness_uses_latency_failures_before_generic_unrecoverable(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
|
||||
Reference in New Issue
Block a user