Skip to content

Commit 0fa9baf

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 32db8c2 + d2e330a commit 0fa9baf

36 files changed

Lines changed: 3807 additions & 4529 deletions

benchmarks/benchmark.py

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
Orchestrates guidellm burst/idle cycles alongside batch workloads to measure
66
whether gated dispatch protects interactive latency while batch makes SLO progress.
77
8-
Supports 6 scenarios:
8+
Supports 7 scenarios:
99
0 - Interactive only (baseline)
1010
1 - No batch-gateway (batch as regular requests)
1111
2 - Ungated batch (aggressive concurrency, no AIMD)
12-
3 - AIMD only (processor-side adaptive concurrency)
13-
4 - AIMD + llm-d Router flow control (two-layer protection)
12+
3 - Admission control + AIMD (saturation-based batch rejection + adaptive concurrency)
13+
4 - Flow control + AIMD (priority dispatch ordering + adaptive concurrency)
1414
5 - Async processor (blocked on integration)
15+
6 - Low batch concurrency (fixed perEndpoint cap, no AIMD)
1516
1617
Usage:
1718
python3 benchmarks/benchmark.py \
@@ -43,9 +44,10 @@
4344
0: "interactive-only",
4445
1: "no-batch-gateway",
4546
2: "ungated",
46-
3: "aimd",
47-
4: "aimd-flow-control",
47+
3: "admission-control-aimd",
48+
4: "flow-control-aimd",
4849
5: "async",
50+
6: "low-concurrency",
4951
}
5052

5153
SCRIPT_DIR = Path(__file__).parent.resolve()
@@ -1183,6 +1185,7 @@ def _generate_narrative(results, cfg):
11831185
ungated = next((r for r in results if r.scenario == 2), None)
11841186
aimd = next((r for r in results if r.scenario == 3), None)
11851187
fc = next((r for r in results if r.scenario == 4), None)
1188+
low_conc = next((r for r in results if r.scenario == 6), None)
11861189

11871190
lines = []
11881191
baseline_burst = _aggregate_phases(baseline.phases, "burst") if baseline else None
@@ -1204,17 +1207,25 @@ def _generate_narrative(results, cfg):
12041207
aimd_burst = _aggregate_phases(aimd.phases, "burst")
12051208
if aimd_burst and baseline_ttft:
12061209
overhead = ((aimd_burst["ttft_p99"] - baseline_ttft) / baseline_ttft) * 100
1207-
lines.append(f"AIMD-gated batch (S3) protected interactive TTFT p99 within "
1210+
lines.append(f"Admission control + AIMD (S3) protected interactive TTFT p99 within "
12081211
f"{overhead:.0f}% of baseline ({aimd_burst['ttft_p99']:.0f} ms) "
1209-
f"using lower static concurrency (perEndpoint: 30).")
1212+
f"using saturation-based batch rejection and adaptive concurrency.")
12101213

12111214
if fc:
12121215
fc_burst = _aggregate_phases(fc.phases, "burst")
12131216
if fc_burst and baseline_ttft:
12141217
overhead = ((fc_burst["ttft_p99"] - baseline_ttft) / baseline_ttft) * 100
1215-
lines.append(f"AIMD + flow control (S4) achieved the best protection at "
1218+
lines.append(f"Flow control + AIMD (S4) achieved the best protection at "
12161219
f"{overhead:.0f}% above baseline ({fc_burst['ttft_p99']:.0f} ms) "
1217-
f"with proactive Router-side batch shedding.")
1220+
f"with priority dispatch ordering (interactive before batch).")
1221+
1222+
if low_conc:
1223+
low_conc_burst = _aggregate_phases(low_conc.phases, "burst")
1224+
if low_conc_burst and baseline_ttft:
1225+
overhead = ((low_conc_burst["ttft_p99"] - baseline_ttft) / baseline_ttft) * 100
1226+
lines.append(f"Low concurrency (S6) limited interactive TTFT p99 impact to "
1227+
f"{overhead:.0f}% above baseline ({low_conc_burst['ttft_p99']:.0f} ms) "
1228+
f"using a fixed per-endpoint concurrency cap.")
12181229

12191230
# Batch completion summary with per-job SLO status
12201231
for r in results:
@@ -1318,7 +1329,7 @@ def generate_html_report(cfg, results):
13181329
if result.batch_timeline:
13191330
timelines_json[f"S{result.scenario} ({result.name})"] = result.batch_timeline
13201331

1321-
# AIMD dynamics data (scenarios 3-4)
1332+
# AIMD dynamics data (scenarios with AIMD enabled)
13221333
aimd_chart_data = []
13231334
for result in results:
13241335
if result.aimd_metrics and "aimd_concurrency_limit_series" in result.aimd_metrics:
@@ -1376,8 +1387,12 @@ def generate_html_report(cfg, results):
13761387
burst_agg = _aggregate_phases(result.phases, "burst")
13771388
idle_agg = _aggregate_phases(result.phases, "idle")
13781389

1379-
ttft_p99_burst = f"{burst_agg['ttft_p99']:.1f}" if burst_agg else "N/A"
1380-
itl_p99_burst = f"{burst_agg['itl_p99']:.1f}" if burst_agg else "N/A"
1390+
if burst_agg:
1391+
ttft_burst = f"{burst_agg['ttft_p50']:.0f} / {burst_agg['ttft_p95']:.0f} / {burst_agg['ttft_p99']:.0f}"
1392+
itl_burst = f"{burst_agg['itl_p50']:.0f} / {burst_agg['itl_p95']:.0f} / {burst_agg['itl_p99']:.0f}"
1393+
else:
1394+
ttft_burst = "N/A"
1395+
itl_burst = "N/A"
13811396
idle_rps = f"{idle_agg['completed'] / max(1, cfg.idle_seconds * (cfg.cycles - cfg.warmup_cycles)):.2f}" if idle_agg else "N/A"
13821397

13831398
if result.scenario <= 1:
@@ -1399,8 +1414,8 @@ def generate_html_report(cfg, results):
13991414

14001415
summary_rows.append(
14011416
f"<tr><td>S{result.scenario} ({result.name})</td>"
1402-
f"<td>{ttft_p99_burst}</td>"
1403-
f"<td>{itl_p99_burst}</td>"
1417+
f"<td>{ttft_burst}</td>"
1418+
f"<td>{itl_burst}</td>"
14041419
f"<td>{idle_rps}</td>"
14051420
f"<td>{batch_slo}</td>"
14061421
f"<td>{batch_ttft_p50}</td></tr>"
@@ -1501,8 +1516,8 @@ def generate_html_report(cfg, results):
15011516
<table>
15021517
<tr>
15031518
<th>Scenario</th>
1504-
<th>Interactive TTFT p99 (burst)</th>
1505-
<th>Interactive ITL p99 (burst)</th>
1519+
<th>Interactive TTFT at peak (p50 / p95 / p99)</th>
1520+
<th>Interactive ITL at peak (p50 / p95 / p99)</th>
15061521
<th>Batch idle throughput (req/s)</th>
15071522
<th>Batch SLO completion</th>
15081523
<th>Batch TTFT p50</th>
@@ -1563,7 +1578,7 @@ def generate_html_report(cfg, results):
15631578
</div>
15641579
</div>
15651580
1566-
<h2>AIMD Concurrency Dynamics (Scenarios 3-4)</h2>
1581+
<h2>AIMD Concurrency Dynamics</h2>
15671582
<div class="chart-container">
15681583
<canvas id="aimdChart"></canvas>
15691584
</div>
@@ -1965,7 +1980,7 @@ def run_scenario(cfg, scenario):
19651980
'{"claimName":"benchmark-results"}}]}}', "--"],
19661981
cfg.context, namespace, check=False)
19671982

1968-
# Submit batch (scenarios 2-4 only)
1983+
# Submit batch (scenarios 2-6)
19691984
if scenario >= 2:
19701985
submit_batches(cfg, namespace)
19711986
time.sleep(10)
@@ -2022,7 +2037,7 @@ def run_scenario(cfg, scenario):
20222037
if metrics.completed > 0:
20232038
phases.append(metrics)
20242039

2025-
# Collect AIMD metrics for scenarios 3-4
2040+
# Collect AIMD metrics (scenarios with AIMD enabled: S3, S4)
20262041
aimd_metrics = {}
20272042
if scenario >= 3 and os.environ.get("PROMETHEUS_URL"):
20282043
end_time = datetime.datetime.utcfromtimestamp(time.time())
@@ -2506,7 +2521,7 @@ def main():
25062521
# Validate scenarios
25072522
for s in cfg.scenarios:
25082523
if s not in SCENARIO_NAMES:
2509-
log(f"ERROR: Unknown scenario {s}. Valid: 0-5")
2524+
log(f"ERROR: Unknown scenario {s}. Valid: 0-6")
25102525
sys.exit(1)
25112526

25122527
# Rate sweep mode
@@ -2674,7 +2689,7 @@ def main():
26742689
"total_errors": sum(p.errors for p in burst_phases),
26752690
}
26762691

2677-
# AIMD and flow control metrics (scenarios 3-4)
2692+
# AIMD and flow control metrics
26782693
if result.aimd_metrics:
26792694
scenario_data["aimd"] = {
26802695
k: v for k, v in result.aimd_metrics.items()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Scenario 3: Admission control + AIMD
2+
# Processor sends x-gateway-inference-objective: "batch-sheddable" header.
3+
# The llm-d Router's admission controller (flow control feature gate OFF)
4+
# rejects sheddable requests (priority < 0) when saturation >= 1.0,
5+
# generating 429s that feed AIMD multiplicative decrease.
6+
#
7+
# InferenceObjective CRDs and Router overlay are deployed by setup.sh.
8+
processor:
9+
config:
10+
dispatchMode: sync
11+
concurrency:
12+
global: 60
13+
perEndpoint: 30
14+
recovery: 5
15+
aimd:
16+
enabled: true
17+
min: 5
18+
backoffFactor: 0.5
19+
additiveIncrease: 1
20+
globalInferenceGateway:
21+
url: "http://llm-d-inference-gateway-istio:80"
22+
inferenceObjective: "batch-sheddable"
23+
requestTimeout: "30s"
24+
maxRetries: 3
25+
initialBackoff: "1s"
26+
maxBackoff: "10s"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Admission control overlay for llm-d Router (router.epp.* format for v0.9.0+ charts).
2+
# Applied in scenario 3 to enable saturation-based admission control:
3+
# - Flow control feature gate is OFF (no priority dispatch ordering)
4+
# - Concurrency-detector provides the saturation signal
5+
# - LegacyAdmissionController rejects sheddable requests (priority < 0)
6+
# when saturation >= 1.0
7+
#
8+
# maxConcurrency should be tuned to the model's observed peak in-flight
9+
# requests. When total in-flight exceeds maxConcurrency, saturation
10+
# crosses 1.0 and batch requests are rejected with 429.
11+
router:
12+
epp:
13+
pluginsConfigFile: "admission-control-plugins.yaml"
14+
pluginsCustomConfig:
15+
admission-control-plugins.yaml: |
16+
apiVersion: inference.networking.x-k8s.io/v1alpha1
17+
kind: EndpointPickerConfig
18+
plugins:
19+
- type: concurrency-detector
20+
parameters:
21+
maxConcurrency: 50
22+
saturationDetector:
23+
pluginRef: concurrency-detector
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Admission control overlay for llm-d Router (InferencePool EPP chart).
2+
# Applied in scenario 3 to enable saturation-based admission control:
3+
# - Flow control feature gate is OFF (no priority dispatch ordering)
4+
# - Concurrency-detector provides the saturation signal
5+
# - LegacyAdmissionController rejects sheddable requests (priority < 0)
6+
# when saturation >= 1.0
7+
#
8+
# maxConcurrency should be tuned to the model's observed peak in-flight
9+
# requests. When total in-flight exceeds maxConcurrency, saturation
10+
# crosses 1.0 and batch requests are rejected with 429.
11+
inferenceExtension:
12+
pluginsConfigFile: "admission-control-plugins.yaml"
13+
pluginsCustomConfig:
14+
admission-control-plugins.yaml: |
15+
apiVersion: inference.networking.x-k8s.io/v1alpha1
16+
kind: EndpointPickerConfig
17+
plugins:
18+
- type: concurrency-detector
19+
parameters:
20+
maxConcurrency: 50
21+
saturationDetector:
22+
pluginRef: concurrency-detector

benchmarks/helm-values/scenario-3-aimd.yaml

Lines changed: 0 additions & 38 deletions
This file was deleted.

benchmarks/helm-values/scenario-4-aimd-flow-control.yaml renamed to benchmarks/helm-values/scenario-4-flow-control-aimd.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# Scenario 4: AIMD + llm-d Router flow control
2-
# Same processor AIMD config as scenario 3, plus two-layer protection:
1+
# Scenario 4: Flow control + AIMD
2+
# Same processor AIMD config as scenario 3, plus priority dispatch ordering:
33
# - llm-d Router flowControl feature gate enabled (EndpointPickerConfig)
44
# - Priority bands: interactive (100) + batch (-1, sheddable)
55
# - InferenceObjective CRDs: interactive-default (100), batch-sheddable (-1)
66
# - Processor sends x-gateway-inference-objective: "batch-sheddable" header
77
#
8-
# With flow control active, the Router returns 429 for batch requests during
9-
# saturation. This triggers AIMD multiplicative decrease, producing the
10-
# expected sawtooth pattern. Lower perEndpoint (30) same as S3 for fair
11-
# comparison; the differentiator here is the Router's proactive 429 shedding.
8+
# Flow control's dispatch cycle dispatches one request per tick, highest
9+
# priority first. Interactive (100) always dispatches before batch (-1).
10+
# This priority ordering passively protects interactive latency.
11+
# AIMD is enabled but stays flat without 429s (ready for future integration).
1212
#
13-
# CRDs and Router overlay are deployed by setup.sh (scenario 4 block).
13+
# CRDs and Router overlay are deployed by setup.sh.
1414
processor:
1515
config:
1616
dispatchMode: sync

benchmarks/helm-values/scenario-4-flow-control-overlay-router.yaml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
# Flow control overlay for llm-d Router (router.epp.* format for v0.9.0+ charts).
2-
# Applied in scenario 4 to enable two-layer protection:
3-
# Layer 1: Router EPP flow control (priority-based request scheduling)
4-
# Layer 2: Processor AIMD (adaptive concurrency per endpoint)
2+
# Applied in scenario 4 to enable priority dispatch ordering:
3+
# Flow control's dispatch cycle dispatches one request per tick, highest
4+
# priority first. Interactive (priority 100) always dispatches before
5+
# batch (priority -1). This priority ordering passively protects
6+
# interactive latency.
7+
#
8+
# Saturation detector choice: concurrency-detector with generous maxConcurrency
9+
# (1000) is a benchmarking workaround — it keeps saturation artificially low so
10+
# the dispatch cycle runs freely without rejection. This isolates priority
11+
# ordering as the sole protection mechanism. For production, consider:
12+
# - utilization-detector (reacts to real GPU pressure: queue depth, KV cache)
13+
# - concurrency-detector with a tuned maxConcurrency matching model capacity
14+
# Both are currently affected by llm-d-router bugs (#1474, #2060) that make the
15+
# saturation signal unreliable. Once fixed, combine with priority-holdback-policy
16+
# for graduated per-band shedding.
517
#
618
# Priority bands:
719
# Interactive (100): low-latency, round-robin fairness, FCFS ordering
@@ -19,10 +31,9 @@ router:
1931
- type: round-robin-fairness-policy
2032
- type: global-strict-fairness-policy
2133
- type: slo-deadline-ordering-policy
22-
- type: utilization-detector
34+
- type: concurrency-detector
2335
parameters:
24-
queueDepthThreshold: 2
25-
kvCacheUtilThreshold: 0.5
36+
maxConcurrency: 1000
2637
flowControl:
2738
maxBytes: 4294967296
2839
defaultRequestTTL: 30s
@@ -39,5 +50,5 @@ router:
3950
maxBytes: 536870912
4051
fairnessPolicyRef: global-strict-fairness-policy
4152
orderingPolicyRef: fcfs-ordering-policy
42-
saturationDetector:
43-
pluginRef: utilization-detector
53+
saturationDetector:
54+
pluginRef: concurrency-detector

benchmarks/helm-values/scenario-4-flow-control-overlay.yaml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
# Flow control overlay for llm-d Router (InferencePool EPP chart).
2-
# Applied in scenario 4 to enable two-layer protection:
3-
# Layer 1: Router EPP flow control (priority-based request scheduling)
4-
# Layer 2: Processor AIMD (adaptive concurrency per endpoint)
2+
# Applied in scenario 4 to enable priority dispatch ordering:
3+
# Flow control's dispatch cycle dispatches one request per tick, highest
4+
# priority first. Interactive (priority 100) always dispatches before
5+
# batch (priority -1). This priority ordering passively protects
6+
# interactive latency.
7+
#
8+
# Saturation detector choice: concurrency-detector with generous maxConcurrency
9+
# (1000) is a benchmarking workaround — it keeps saturation artificially low so
10+
# the dispatch cycle runs freely without rejection. This isolates priority
11+
# ordering as the sole protection mechanism. For production, consider:
12+
# - utilization-detector (reacts to real GPU pressure: queue depth, KV cache)
13+
# - concurrency-detector with a tuned maxConcurrency matching model capacity
14+
# Both are currently affected by llm-d-router bugs (#1474, #2060) that make the
15+
# saturation signal unreliable. Once fixed, combine with priority-holdback-policy
16+
# for graduated per-band shedding.
517
#
618
# Priority bands:
719
# Interactive (100): low-latency, round-robin fairness, FCFS ordering
@@ -18,10 +30,9 @@ inferenceExtension:
1830
- type: round-robin-fairness-policy
1931
- type: global-strict-fairness-policy
2032
- type: slo-deadline-ordering-policy
21-
- type: utilization-detector
33+
- type: concurrency-detector
2234
parameters:
23-
queueDepthThreshold: 2
24-
kvCacheUtilThreshold: 0.5
35+
maxConcurrency: 1000
2536
flowControl:
2637
maxBytes: 4294967296
2738
defaultRequestTTL: 30s
@@ -38,5 +49,5 @@ inferenceExtension:
3849
maxBytes: 536870912
3950
fairnessPolicyRef: global-strict-fairness-policy
4051
orderingPolicyRef: fcfs-ordering-policy
41-
saturationDetector:
42-
pluginRef: utilization-detector
52+
saturationDetector:
53+
pluginRef: concurrency-detector

0 commit comments

Comments
 (0)