[Tools][Recipes] Improve sweep recommendations and short-alias parsing (#53946)
Signed-off-by: louie-tsai <louie.tsai@intel.com>
Louie Tsai committed
Aug 26, 2026 at 23:58 UTC
79651d6085885cf2842baf85526ece7afa87c87d
7 files changed
+351
-73
docs/models/hardware_supported_models/cpu.md
+4
-4
@@ -30,7 +30,7 @@ vllm serve --config config.yaml
30
31
| Model | Architecture | Supported | Recipe |
32
| ------------------------------------ | ---------------------------------------- | --------- | ------ |
33
-| openai/gpt-oss-20b | GptOssForCausalLM | ✅ | — |
33
+| openai/gpt-oss-20b | GptOssForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/openai/gpt-oss-20b?hardware=xeon6) |
34
| meta-llama/Llama-3.1-8B | LlamaForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/meta-llama/Llama-3.1-8B?hardware=xeon6) |
35
| meta-llama/Llama-3.1-8B-Instruct | LlamaForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/meta-llama/Llama-3.1-8B-Instruct?hardware=xeon6) |
36
| meta-llama/Llama-3.2-1B | LlamaForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/meta-llama/Llama-3.2-1B?hardware=xeon6) |
@@ -48,13 +48,13 @@ vllm serve --config config.yaml
48
| TheBloke/TinyLlama-1.1B-Chat-v1.0-AWQ | LlamaForCausalLM | ✅ | — |
49
| TheBloke/TinyLlama-1.1B-Chat-v1.0-GPTQ | LlamaForCausalLM | ✅ | — |
50
| ibm-granite/granite-3.2-2b-instruct | GraniteForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/ibm-granite/granite-3.2-2b-instruct?hardware=xeon6) |
51
-| Qwen/Qwen3-1.7B | Qwen3ForCausalLM | ✅ | — |
51
+| Qwen/Qwen3-1.7B | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-1.7B?hardware=xeon6) |
52
| Qwen/Qwen3-4B | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-4B?hardware=xeon6) |
53
-| Qwen/Qwen3-8B | Qwen3ForCausalLM | ✅ | — |
53
+| Qwen/Qwen3-8B | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-8B?hardware=xeon6) |
54
| Qwen/Qwen3-14B | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-14B?hardware=xeon6) |
55
| Qwen/Qwen3-14B-FP8 | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-14B?hardware=xeon6&variant=fp8) |
56
| Qwen/Qwen3-14B-AWQ | Qwen3ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-14B?hardware=xeon6&variant=awq) |
57
-| Qwen/Qwen3-30B-A3B | Qwen3MoeForCausalLM | ✅ | — |
57
+| Qwen/Qwen3-30B-A3B | Qwen3MoeForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/Qwen3-30B-A3B?hardware=xeon6) |
58
| Qwen/Qwen3-30B-A3B-Instruct-2507-FP8 | Qwen3MoeForCausalLM | ✅ | — |
59
| Qwen/QwQ-32B | Qwen2ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/QwQ-32B?hardware=xeon6) |
60
| Qwen/QwQ-32B-AWQ | Qwen2ForCausalLM | ✅ | [Xeon 6](https://recipes.vllm.ai/Qwen/QwQ-32B?hardware=xeon6&variant=awq) |
tools/recipes/README.md
+2
-2
@@ -87,7 +87,7 @@ capacity objectives.
87
python3 tools/recipes/recipe_json_to_vllm_config.py \
88
--model meta-llama/Llama-3.1-8B-Instruct \
89
--hardware xeon6 \
90
- --input-tokens 2048 \
90
+ --input-tokens 128 \
91
--output-tokens 128 \
92
--concurrency 32 \
93
--ttft-sla-ms 3000 \
@@ -111,7 +111,7 @@ python3 tools/recipes/recipe_json_to_vllm_config.py \
111
--model meta-llama/Llama-3.1-8B-Instruct \
112
--hardware xeon6 \
113
--detect-hardware \
114
- --input-tokens 2048 \
114
+ --input-tokens 128 \
115
--output-tokens 128 \
116
--concurrency 32 \
117
--generate-sweep
tools/recipes/RUNTIME_TUNING.md
+5
-5
@@ -57,7 +57,7 @@ Example:
57
python3 tools/recipes/recipe_json_to_vllm_config.py \
58
--model meta-llama/Llama-3.1-8B-Instruct \
59
--hardware xeon6 \
60
- --input-tokens 2048 \
60
+ --input-tokens 128 \
61
--output-tokens 128 \
62
--concurrency 32 \
63
--ttft-sla-ms 3000 \
@@ -172,13 +172,13 @@ max-num-batched-tokens = max(
172
)
173
```
174
175
-For example, with 2048 input tokens, 128 output tokens, and concurrency 32:
175
+For example, with 128 input tokens, 128 output tokens, and concurrency 32:
176
177
```text
178
decode_budget = 32
179
prefills_per_step = max(1, 32 / 128) = 1
180
-prefill_budget = 2048
181
-candidate = max(2048, 32, 32 + 2048) = 2080
180
+prefill_budget = 128
181
+candidate = max(2048, 32, 32 + 128) = 2048
182
```
183
184
If chunked prefill is explicitly disabled and `max-model-len` is available, the
@@ -261,7 +261,7 @@ python3 /recipes/recipe_json_to_vllm_config.py \
261
--model meta-llama/Llama-3.1-8B-Instruct \
262
--hardware xeon6 \
263
--detect-hardware \
264
- --input-tokens 2048 \
264
+ --input-tokens 128 \
265
--output-tokens 128 \
266
--concurrency 32 \
267
--ttft-sla-ms 3000 \
tools/recipes/SWEEP_TUNING.md
+23
-10
@@ -11,7 +11,7 @@ python3 tools/recipes/recipe_json_to_vllm_config.py \
11
--model meta-llama/Llama-3.1-8B-Instruct \
12
--hardware xeon6 \
13
--detect-hardware \
14
- --input-tokens 2048 \
14
+ --input-tokens 128 \
15
--output-tokens 128 \
16
--concurrency 32 \
17
--ttft-sla-ms 3000 \
@@ -30,8 +30,11 @@ sweep/
30
└── SWEEP.md
31
```
32
33
-The sweep currently varies only `max-num-seqs` and
34
-`max-num-batched-tokens`.
33
+The sweep varies only `max-num-seqs` and `max-num-batched-tokens`. It uses an
34
+eight-point directed design: a batch-budget curve at the initial sequence count
35
+plus lower/higher batch interactions at three-quarters and one-half of that
36
+count. This gives broader coverage than a one-parameter-at-a-time sweep without
37
+the cost of a full Cartesian grid.
38
39
## Run and Recommend
40
@@ -54,13 +57,23 @@ sweep/recommended-config.yml
57
sweep/recommendation.json
58
```
59
57
-With TTFT/TPOT objectives, the benchmark uses vLLM `--goodput`, and the
58
-recommender selects highest mean request goodput across repeated runs. Without
59
-latency objectives it selects highest mean output-token throughput. Failed
60
-benchmark configurations are excluded.
60
+With TTFT/TPOT objectives, the benchmark uses vLLM `--goodput`. The recommender
61
+calculates duration-weighted combined compliance across repeated runs and
62
+requires both median P99 latency objectives and the minimum compliance ratio
63
+(default `0.99`). Among eligible configurations it selects the highest mean
64
+output-token throughput. Use `recommend.py --minimum-compliance VALUE` to
65
+change the compliance requirement.
66
62
-`recommended-config.yml` changes only the two swept scheduler parameters.
63
-`recommendation.json` records the measured evidence.
67
+If no configuration is eligible, `recommend.py` records the highest-goodput
68
+configuration as `best_effort` in `recommendation.json`, does not write a
69
+deployable `recommended-config.yml`, and exits with status 2. Without latency
70
+objectives it selects highest mean output-token throughput. Failed benchmark
71
+configurations are excluded in either mode.
72
+
73
+When an eligible configuration exists, `recommended-config.yml` changes only
74
+the two swept scheduler parameters. `recommendation.json` records mean, median,
75
+and worst-run P99 values, combined compliance, and the measured evidence for
76
+every candidate.
77
78
Deploy:
79
@@ -83,7 +96,7 @@ python3 /recipes/recipe_json_to_vllm_config.py \
96
--model meta-llama/Llama-3.1-8B-Instruct \
97
--hardware xeon6 \
98
--detect-hardware \
86
- --input-tokens 2048 \
99
+ --input-tokens 128 \
100
--output-tokens 128 \
101
--concurrency 32 \
102
--ttft-sla-ms 3000 \
tools/recipes/recipe_json_to_vllm_config.py
+115
-10
@@ -59,10 +59,36 @@ except ImportError as exc:
59
60
DEFAULT_API_BASE = "https://recipes.vllm.ai"
61
62
-SHORT_ALIASES = {
63
- "-tp": "tensor-parallel-size",
62
+VALUE_SHORT_ALIASES = {
63
+ "-q": "quantization",
64
"-pp": "pipeline-parallel-size",
65
+ "-n": "nnodes",
66
+ "-r": "node-rank",
67
+ "-tp": "tensor-parallel-size",
68
+ "-dcp": "decode-context-parallel-size",
69
+ "-pcp": "prefill-context-parallel-size",
70
"-dp": "data-parallel-size",
71
+ "-dpn": "data-parallel-rank",
72
+ "-dpr": "data-parallel-start-rank",
73
+ "-dpl": "data-parallel-size-local",
74
+ "-dpa": "data-parallel-address",
75
+ "-dpp": "data-parallel-rpc-port",
76
+ "-dpb": "data-parallel-backend",
77
+ "-asc": "api-server-count",
78
+}
79
+
80
+FLAG_SHORT_ALIASES = {
81
+ "-dph": "data-parallel-hybrid-lb",
82
+ "-dpe": "data-parallel-external-lb",
83
+ "-dpm": "data-parallel-multi-port-external-lb",
84
+ "-ep": "enable-expert-parallel",
85
+}
86
+
87
+DICT_SHORT_ALIASES = {
88
+ "-sc": "speculative-config",
89
+ "-dc": "diffusion-config",
90
+ "-cc": "compilation-config",
91
+ "-ac": "attention-config",
92
}
93
94
@@ -464,6 +490,11 @@ def discover_recipe_source(
490
491
def coerce(value: str) -> Any:
492
"""Convert CLI string values to useful YAML scalar/object types."""
493
+ # Recipes may preserve Python-style booleans in dotted config aliases,
494
+ # for example: -cc.pass_config.fuse_rope_kvcache=True.
495
+ if value in ("True", "False"):
496
+ return value == "True"
497
+
498
try:
499
return json.loads(value)
500
except (json.JSONDecodeError, TypeError):
@@ -473,8 +504,19 @@ def coerce(value: str) -> Any:
504
def is_option_token(token: str) -> bool:
505
if token.startswith("--"):
506
return True
476
- if token in SHORT_ALIASES or token == "-O":
507
+
508
+ short_name = token.split("=", 1)[0]
509
+ if (
510
+ short_name in VALUE_SHORT_ALIASES
511
+ or short_name in FLAG_SHORT_ALIASES
512
+ or short_name in DICT_SHORT_ALIASES
513
+ or token == "-O"
514
+ ):
515
return True
516
+
517
+ if any(token.startswith(f"{alias}.") for alias in DICT_SHORT_ALIASES):
518
+ return True
519
+
520
return token.startswith("-O") and len(token) > 2
521
522
@@ -554,22 +596,85 @@ def argv_to_config(argv: list[Any]) -> dict[str, Any]:
596
i += 2
597
continue
598
557
- # Selected common short aliases.
558
- if token in SHORT_ALIASES:
559
- if i + 1 >= len(argv):
560
- raise ValueError(f"{token} is missing its value")
599
+ # Dotted dictionary aliases accepted by FlexibleArgumentParser:
600
+ # -cc.mode=3
601
+ # -cc.mode 3
602
+ # -cc.pass_config.foo=True
603
+ # -cc.custom_ops+ -quant_fp8
604
+ # The same syntax applies to -sc, -dc, and -ac.
605
+ dotted_alias = next(
606
+ (alias for alias in DICT_SHORT_ALIASES if token.startswith(f"{alias}.")),
607
+ None,
608
+ )
609
+ if dotted_alias is not None:
610
+ dotted = token[len(dotted_alias) + 1 :]
611
+ raw_key, separator, raw_value = dotted.partition("=")
612
+
613
+ append = raw_key.endswith("+")
614
+ if append:
615
+ raw_key = raw_key[:-1]
616
+
617
+ if not raw_key:
618
+ raise ValueError(f"{token} must contain a key after {dotted_alias}.")
619
+
620
+ if not separator:
621
+ if i + 1 >= len(argv):
622
+ raise ValueError(f"{token} is missing its value")
623
+ # Consume the next token unconditionally. Dotted list values may
624
+ # themselves begin with "-", for example "-quant_fp8".
625
+ raw_value = argv[i + 1]
626
+ i += 2
627
+ else:
628
+ i += 1
629
+
630
+ value: Any = raw_value.split(",") if append else coerce(raw_value)
631
+
632
merge_value(
633
config,
563
- [SHORT_ALIASES[token]],
564
- coerce(argv[i + 1]),
634
+ [DICT_SHORT_ALIASES[dotted_alias], *raw_key.split(".")],
635
+ value,
636
)
637
+ continue
638
+
639
+ # Support -alias=value for value-bearing and whole-dict aliases.
640
+ if "=" in token:
641
+ short_name, raw_value = token.split("=", 1)
642
+ if short_name in VALUE_SHORT_ALIASES:
643
+ merge_value(
644
+ config,
645
+ [VALUE_SHORT_ALIASES[short_name]],
646
+ coerce(raw_value),
647
+ )
648
+ i += 1
649
+ continue
650
+ if short_name in DICT_SHORT_ALIASES:
651
+ merge_value(
652
+ config,
653
+ [DICT_SHORT_ALIASES[short_name]],
654
+ coerce(raw_value),
655
+ )
656
+ i += 1
657
+ continue
658
+
659
+ # Boolean short aliases are flags and do not consume a value.
660
+ if token in FLAG_SHORT_ALIASES:
661
+ merge_value(config, [FLAG_SHORT_ALIASES[token]], True)
662
+ i += 1
663
+ continue
664
+
665
+ # Scalar and whole-dictionary short aliases consume one value.
666
+ if token in VALUE_SHORT_ALIASES or token in DICT_SHORT_ALIASES:
667
+ if i + 1 >= len(argv):
668
+ raise ValueError(f"{token} is missing its value")
669
+ key = VALUE_SHORT_ALIASES.get(token) or DICT_SHORT_ALIASES[token]
670
+ merge_value(config, [key], coerce(argv[i + 1]))
671
i += 2
672
continue
673
674
if not token.startswith("--"):
675
raise ValueError(
676
f"Unexpected positional/short argument {token!r}. "
572
- "The converter expects Recipes to emit long-form vLLM serve options."
677
+ "The converter accepts vLLM serve long options and known short aliases."
678
)
679
680
# --key=value
tools/recipes/sweep_generation.py
+23
-6
@@ -43,7 +43,13 @@ def validate_sweep_workload(workload: WorkloadHints) -> None:
43
44
45
def build_serve_params(config: dict[str, Any]) -> list[dict[str, Any]]:
46
- """Build a small directed sweep around the single initial suggestion."""
46
+ """Build a diverse, bounded sweep around the initial suggestion.
47
+
48
+ The initial five-point sweep changed only one parameter at a time and
49
+ skipped the useful middle sequence count. Eight directed points cover the
50
+ batch-budget curve at full concurrency plus interactions at 3/4 and 1/2 of
51
+ the initial scheduler concurrency without paying for a full Cartesian grid.
52
+ """
53
initial_seqs = _positive_int(config, "max-num-seqs")
54
initial_batch = _positive_int(config, "max-num-batched-tokens")
55
@@ -58,10 +64,15 @@ def build_serve_params(config: dict[str, Any]) -> list[dict[str, Any]]:
64
minimum_batch = max(minimum_batch, max_model_len)
65
66
lower_seqs = max(1, (initial_seqs + 1) // 2)
67
+ middle_seqs = max(lower_seqs, (3 * initial_seqs + 3) // 4)
68
lower_batch = max(
69
minimum_batch,
70
_strict_lower_power_of_two(initial_batch),
71
)
72
+ smaller_batch = max(
73
+ minimum_batch,
74
+ _strict_lower_power_of_two(lower_batch),
75
+ )
76
higher_batch = max(
77
minimum_batch,
78
_strict_upper_power_of_two(initial_batch),
@@ -90,10 +101,13 @@ def build_serve_params(config: dict[str, Any]) -> list[dict[str, Any]]:
101
# Keep the exact initial suggestion as the measured baseline. Additional
102
# values exist only in the optional sweep package.
103
add("initial", initial_seqs, initial_batch)
93
- add("lower_scheduler_concurrency", lower_seqs, initial_batch)
104
+ add("smaller_batch_budget", initial_seqs, smaller_batch)
105
add("lower_batch_budget", initial_seqs, lower_batch)
95
- add("latency_focused", lower_seqs, lower_batch)
106
add("higher_batch_budget", initial_seqs, higher_batch)
107
+ add("middle_seqs_lower_batch", middle_seqs, lower_batch)
108
+ add("middle_seqs_higher_batch", middle_seqs, higher_batch)
109
+ add("lower_seqs_lower_batch", lower_seqs, lower_batch)
110
+ add("lower_seqs_higher_batch", lower_seqs, higher_batch)
111
112
return candidates
113
@@ -286,9 +300,12 @@ recommended-config.yml
300
recommendation.json
301
```
302
289
-With TTFT/TPOT objectives it selects highest mean request goodput across
290
-repeated runs. Without latency objectives it selects highest mean output-token
291
-throughput. Configurations with failed requests are excluded.
303
+With TTFT/TPOT objectives it requires duration-weighted combined compliance of
304
+at least 99% plus median P99 TTFT/TPOT compliance, then selects highest mean
305
+output-token throughput. If no candidate qualifies, it records a best-effort
306
+candidate but does not write `recommended-config.yml`. Without latency
307
+objectives it selects highest mean output-token throughput. Configurations with
308
+failed requests are excluded.
309
310
`recommended-config.yml` copies the initial configuration and changes only
311
`max-num-seqs` and `max-num-batched-tokens`.
tools/recipes/sweep_recommendation.py
+179
-36
@@ -10,7 +10,7 @@ import argparse
10
import json
11
from collections import defaultdict
12
from pathlib import Path
13
-from statistics import mean
13
+from statistics import mean, median
14
from typing import Any
15
16
try:
@@ -23,6 +23,7 @@ DEFAULT_CONFIG_PATH: str | None = None
23
DEFAULT_ENV_PATH: str | None = None
24
DEFAULT_TTFT_SLA_MS: float | None = None
25
DEFAULT_TPOT_SLA_MS: float | None = None
26
+DEFAULT_MINIMUM_COMPLIANCE: float = 0.99
27
28
29
def _number(value: object) -> float | None:
@@ -37,6 +38,16 @@ def _mean(rows: list[dict[str, Any]], key: str) -> float | None:
38
return mean(numbers) if numbers else None
39
40
41
+def _percentile_summary(
42
+ rows: list[dict[str, Any]], key: str
43
+) -> tuple[float | None, float | None, float | None]:
44
+ values = [_number(row.get(key)) for row in rows]
45
+ numbers = [value for value in values if value is not None]
46
+ if not numbers:
47
+ return None, None, None
48
+ return mean(numbers), median(numbers), max(numbers)
49
+
50
+
51
def _positive_int(row: dict[str, Any], key: str) -> int | None:
52
value = row.get(key)
53
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
@@ -69,6 +80,9 @@ def _aggregate_candidates(
80
rows: list[dict[str, Any]],
81
*,
82
use_goodput: bool,
83
+ ttft_sla_ms: float | None = None,
84
+ tpot_sla_ms: float | None = None,
85
+ minimum_compliance: float = DEFAULT_MINIMUM_COMPLIANCE,
86
) -> list[dict[str, Any]]:
87
grouped: dict[tuple[int, int], list[dict[str, Any]]] = defaultdict(list)
88
@@ -83,9 +97,28 @@ def _aggregate_candidates(
97
for (seqs, batch), runs in sorted(grouped.items()):
98
failed_requests = sum(int(_number(run.get("failed")) or 0) for run in runs)
99
output_throughput = _mean(runs, "output_throughput")
100
+ request_throughput = _mean(runs, "request_throughput")
101
request_goodput = _mean(runs, "request_goodput")
87
- p99_ttft_ms = _mean(runs, "p99_ttft_ms")
88
- p99_tpot_ms = _mean(runs, "p99_tpot_ms")
102
+ mean_p99_ttft_ms, median_p99_ttft_ms, worst_p99_ttft_ms = _percentile_summary(
103
+ runs, "p99_ttft_ms"
104
+ )
105
+ mean_p99_tpot_ms, median_p99_tpot_ms, worst_p99_tpot_ms = _percentile_summary(
106
+ runs, "p99_tpot_ms"
107
+ )
108
+
109
+ completed_requests = sum(
110
+ int(_number(run.get("completed")) or 0) for run in runs
111
+ )
112
+ estimated_compliant_requests = sum(
113
+ (_number(run.get("request_goodput")) or 0.0)
114
+ * (_number(run.get("duration")) or 0.0)
115
+ for run in runs
116
+ )
117
+ combined_compliance_ratio = (
118
+ estimated_compliant_requests / completed_requests
119
+ if completed_requests > 0
120
+ else None
121
+ )
122
123
valid = failed_requests == 0 and output_throughput is not None
124
reason = None
@@ -96,6 +129,25 @@ def _aggregate_candidates(
129
valid = False
130
reason = "request_goodput is missing; rerun sweep with SLO goodput enabled"
131
132
+ p99_sla_eligible = valid
133
+ if ttft_sla_ms is not None:
134
+ p99_sla_eligible = bool(
135
+ p99_sla_eligible
136
+ and median_p99_ttft_ms is not None
137
+ and median_p99_ttft_ms <= ttft_sla_ms
138
+ )
139
+ if tpot_sla_ms is not None:
140
+ p99_sla_eligible = bool(
141
+ p99_sla_eligible
142
+ and median_p99_tpot_ms is not None
143
+ and median_p99_tpot_ms <= tpot_sla_ms
144
+ )
145
+ compliance_eligible = bool(
146
+ valid
147
+ and combined_compliance_ratio is not None
148
+ and combined_compliance_ratio >= minimum_compliance
149
+ )
150
+
151
candidates.append(
152
{
153
"max_num_seqs": seqs,
@@ -103,9 +155,26 @@ def _aggregate_candidates(
155
"run_count": len(runs),
156
"failed_requests": failed_requests,
157
"mean_request_goodput": request_goodput,
158
+ "mean_request_throughput": request_throughput,
159
"mean_output_throughput": output_throughput,
107
- "mean_p99_ttft_ms": p99_ttft_ms,
108
- "mean_p99_tpot_ms": p99_tpot_ms,
160
+ "mean_p99_ttft_ms": mean_p99_ttft_ms,
161
+ "median_p99_ttft_ms": median_p99_ttft_ms,
162
+ "worst_p99_ttft_ms": worst_p99_ttft_ms,
163
+ "mean_p99_tpot_ms": mean_p99_tpot_ms,
164
+ "median_p99_tpot_ms": median_p99_tpot_ms,
165
+ "worst_p99_tpot_ms": worst_p99_tpot_ms,
166
+ "estimated_compliant_requests": estimated_compliant_requests,
167
+ "completed_requests": completed_requests,
168
+ "combined_compliance_ratio": combined_compliance_ratio,
169
+ "combined_compliance_percent": (
170
+ combined_compliance_ratio * 100
171
+ if combined_compliance_ratio is not None
172
+ else None
173
+ ),
174
+ "benchmark_valid": valid,
175
+ "p99_sla_eligible": p99_sla_eligible,
176
+ "compliance_eligible": compliance_eligible,
177
+ "sla_eligible": p99_sla_eligible and compliance_eligible,
178
"valid": valid,
179
"invalid_reason": reason,
180
"summary_files": sorted({str(run["_summary_path"]) for run in runs}),
@@ -119,7 +188,7 @@ def _select_candidate(
188
candidates: list[dict[str, Any]],
189
*,
190
use_goodput: bool,
122
-) -> tuple[dict[str, Any], str]:
191
+) -> tuple[dict[str, Any] | None, dict[str, Any], str]:
192
valid = [candidate for candidate in candidates if candidate["valid"]]
193
if not valid:
194
raise ValueError(
@@ -128,21 +197,37 @@ def _select_candidate(
197
)
198
199
if use_goodput:
131
- objective = "highest_mean_request_goodput"
132
- winner = max(
200
+ objective = "p99_constrained_throughput"
201
+ best_effort = max(
202
valid,
203
key=lambda candidate: (
204
candidate["mean_request_goodput"],
205
+ candidate["combined_compliance_ratio"] or 0.0,
206
candidate["mean_output_throughput"],
207
-candidate["max_num_batched_tokens"],
208
-candidate["max_num_seqs"],
209
),
210
)
141
- if winner["mean_request_goodput"] <= 0:
211
+ if best_effort["mean_request_goodput"] <= 0:
212
raise ValueError(
213
"No sweep configuration produced non-zero request goodput for "
214
"the supplied TTFT/TPOT objectives."
215
)
216
+ eligible = [candidate for candidate in valid if candidate["sla_eligible"]]
217
+ winner = (
218
+ max(
219
+ eligible,
220
+ key=lambda candidate: (
221
+ candidate["mean_output_throughput"],
222
+ candidate["combined_compliance_ratio"],
223
+ candidate["mean_request_goodput"],
224
+ -candidate["max_num_batched_tokens"],
225
+ -candidate["max_num_seqs"],
226
+ ),
227
+ )
228
+ if eligible
229
+ else None
230
+ )
231
else:
232
objective = "highest_mean_output_throughput"
233
winner = max(
@@ -153,8 +238,9 @@ def _select_candidate(
238
-candidate["max_num_seqs"],
239
),
240
)
241
+ best_effort = winner
242
157
- return winner, objective
243
+ return winner, best_effort, objective
244
245
246
def _load_config(path: Path) -> dict[str, Any]:
@@ -219,6 +305,15 @@ def parse_args() -> argparse.Namespace:
305
default=DEFAULT_TPOT_SLA_MS,
306
help="TPOT objective used to generate request goodput.",
307
)
308
+ parser.add_argument(
309
+ "--minimum-compliance",
310
+ type=float,
311
+ default=DEFAULT_MINIMUM_COMPLIANCE,
312
+ help=(
313
+ "Minimum duration-weighted fraction of completed requests that must "
314
+ "meet all supplied objectives (default: 0.99)."
315
+ ),
316
+ )
317
parser.add_argument(
318
"--output-config",
319
default="recommended-config.yml",
@@ -234,6 +329,8 @@ def parse_args() -> argparse.Namespace:
329
330
def main() -> int:
331
args = parse_args()
332
+ if not 0.0 < args.minimum_compliance <= 1.0:
333
+ raise ValueError("--minimum-compliance must be greater than 0 and at most 1.")
334
script_dir = Path(__file__).resolve().parent
335
336
config_path = _resolve(script_dir, args.config)
@@ -258,23 +355,64 @@ def main() -> int:
355
raise ValueError(f"No summary.json sweep results found under {results_dir}.")
356
357
use_goodput = args.ttft_sla_ms is not None or args.tpot_sla_ms is not None
261
- candidates = _aggregate_candidates(rows, use_goodput=use_goodput)
262
- winner, objective = _select_candidate(candidates, use_goodput=use_goodput)
358
+ candidates = _aggregate_candidates(
359
+ rows,
360
+ use_goodput=use_goodput,
361
+ ttft_sla_ms=args.ttft_sla_ms,
362
+ tpot_sla_ms=args.tpot_sla_ms,
363
+ minimum_compliance=args.minimum_compliance,
364
+ )
365
+ winner, best_effort, objective = _select_candidate(
366
+ candidates, use_goodput=use_goodput
367
+ )
368
369
initial_config = _load_config(config_path)
265
- recommended_config = dict(initial_config)
266
- recommended_config["max-num-seqs"] = winner["max_num_seqs"]
267
- recommended_config["max-num-batched-tokens"] = winner["max_num_batched_tokens"]
268
-
269
- _write_config(
270
- output_config,
271
- source_path=config_path,
272
- config=recommended_config,
273
- objective=objective,
274
- )
370
+ if winner is not None:
371
+ recommended_config = dict(initial_config)
372
+ recommended_config["max-num-seqs"] = winner["max_num_seqs"]
373
+ recommended_config["max-num-batched-tokens"] = winner["max_num_batched_tokens"]
374
+ _write_config(
375
+ output_config,
376
+ source_path=config_path,
377
+ config=recommended_config,
378
+ objective=objective,
379
+ )
380
+ elif output_config.exists():
381
+ output_config.unlink()
382
+
383
+ measured = None
384
+ recommended = None
385
+ if winner is not None:
386
+ recommended = {
387
+ "max_num_seqs": winner["max_num_seqs"],
388
+ "max_num_batched_tokens": winner["max_num_batched_tokens"],
389
+ }
390
+ measured = {
391
+ key: winner[key]
392
+ for key in (
393
+ "run_count",
394
+ "mean_request_throughput",
395
+ "mean_request_goodput",
396
+ "mean_output_throughput",
397
+ "combined_compliance_ratio",
398
+ "combined_compliance_percent",
399
+ "estimated_compliant_requests",
400
+ "completed_requests",
401
+ "mean_p99_ttft_ms",
402
+ "median_p99_ttft_ms",
403
+ "worst_p99_ttft_ms",
404
+ "mean_p99_tpot_ms",
405
+ "median_p99_tpot_ms",
406
+ "worst_p99_tpot_ms",
407
+ )
408
+ }
409
410
recommendation = {
411
+ "status": (
412
+ "sla_feasible" if winner is not None else "no_sla_feasible_configuration"
413
+ ),
414
"selection_objective": objective,
415
+ "minimum_compliance_ratio": args.minimum_compliance,
416
"slo": {
417
"ttft_ms": args.ttft_sla_ms,
418
"tpot_ms": args.tpot_sla_ms,
@@ -283,16 +421,14 @@ def main() -> int:
421
"max_num_seqs": initial_config.get("max-num-seqs"),
422
"max_num_batched_tokens": initial_config.get("max-num-batched-tokens"),
423
},
286
- "recommended": {
287
- "max_num_seqs": winner["max_num_seqs"],
288
- "max_num_batched_tokens": winner["max_num_batched_tokens"],
289
- },
290
- "measured": {
291
- "run_count": winner["run_count"],
292
- "mean_request_goodput": winner["mean_request_goodput"],
293
- "mean_output_throughput": winner["mean_output_throughput"],
294
- "mean_p99_ttft_ms": winner["mean_p99_ttft_ms"],
295
- "mean_p99_tpot_ms": winner["mean_p99_tpot_ms"],
424
+ "recommended": recommended,
425
+ "measured": measured,
426
+ "best_effort": {
427
+ "max_num_seqs": best_effort["max_num_seqs"],
428
+ "max_num_batched_tokens": best_effort["max_num_batched_tokens"],
429
+ "mean_request_goodput": best_effort["mean_request_goodput"],
430
+ "combined_compliance_ratio": best_effort["combined_compliance_ratio"],
431
+ "p99_sla_eligible": best_effort["p99_sla_eligible"],
432
},
433
"candidates": candidates,
434
}
@@ -301,6 +437,12 @@ def main() -> int:
437
encoding="utf-8",
438
)
439
440
+ if winner is None:
441
+ print("No SLA-feasible runtime configuration was found.")
442
+ print(f"Wrote diagnostic evidence to {output_json}")
443
+ print("No recommended config was written.")
444
+ return 2
445
+
446
print("Recommended runtime configuration")
447
print()
448
print(f" max-num-seqs: {winner['max_num_seqs']}")
@@ -309,11 +451,12 @@ def main() -> int:
451
print(f"Selection objective: {objective}")
452
if use_goodput:
453
print(f" mean request goodput: {winner['mean_request_goodput']:.2f} req/s")
454
+ print(f" combined compliance: {winner['combined_compliance_percent']:.2f}%")
455
print(f" mean output throughput: {winner['mean_output_throughput']:.2f} tok/s")
313
- if winner["mean_p99_ttft_ms"] is not None:
314
- print(f" mean P99 TTFT: {winner['mean_p99_ttft_ms']:.2f} ms")
315
- if winner["mean_p99_tpot_ms"] is not None:
316
- print(f" mean P99 TPOT: {winner['mean_p99_tpot_ms']:.2f} ms")
456
+ if winner["median_p99_ttft_ms"] is not None:
457
+ print(f" median P99 TTFT: {winner['median_p99_ttft_ms']:.2f} ms")
458
+ if winner["median_p99_tpot_ms"] is not None:
459
+ print(f" median P99 TPOT: {winner['median_p99_tpot_ms']:.2f} ms")
460
print()
461
print(f"Wrote {output_config}")
462
print(f"Wrote {output_json}")