55Orchestrates guidellm burst/idle cycles alongside batch workloads to measure
66whether 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
1617Usage:
1718 python3 benchmarks/benchmark.py \
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
5153SCRIPT_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 ()
0 commit comments