Planner Goodput-per-GPU Sweep

Experimental Spica search over Planner policies for MiniMax-M2.5 on B200 GPUs

View as Markdown

Experimental. These replay results characterize one software snapshot and workload. Do not treat them as production capacity guidance or a performance commitment. Spica’s search behavior and output may change without a standard deprecation period.

Enable the planner (SLA mode) on the same deployment as the router experiment, then use Spica’s smart sweep to find the planner configuration that maximizes goodput-per-GPU on the Mooncake toolagent trace, and compare against the static deployment and the default planner.

Setup

model / hardwareMiniMaxAI/MiniMax-M2.5, b200_sxm, TP4 = TEP (moe_ep=4), vLLM 0.19.0
deploymentagg, planner-managed replicas (1–8 workers; gpu_budget=32 cap)
routerfixed kv_router (overlap_score_credit=1.0, prefill_load_scale=4.0, router_temperature=0.0)
workloadMooncake toolagent trace (23,608 reqs), open-loop (arrival timestamps, native rate ~6.7 req/s)
SLAttft_ms=2000, itl_ms=50 (per-request goodput SLA)
goalmaximize goodput_per_gpu = goodput / avg_gpu (time-averaged provisioned GPUs)
Dynamo source revisionc7378f8e

Why open-loop (not closed-loop)

A planner experiment must be open-loop. In closed-loop (fixed N in flight) the throughput is concurrency-bound — fewer workers serve the same N concurrent requests slower, so scaling down lowers throughput (hence goodput) roughly as fast as it lowers GPU count, and goodput-per-GPU does not improve (the optimum degenerates to “stay at max workers”). In open-loop the throughput is arrival-bound (fixed total tokens over the trace’s ~59 min), so adding workers does not raise throughput — it only lowers latency. The planner can then scale down in lulls without losing goodput, and goodput-per-GPU improves. That is the regime where the planner has value.

Baselines (open-loop, full trace)

configgoodput (tok/s)avg_gpugoodput/gpumean TTFTmean TPOT
static 8 workers (32 GPU)120332.037.621712.5
planner — default (hybrid_180_5)118313.587.745836.0

The default planner already gives 2.3× the static goodput-per-GPU — same goodput, but it scales the fleet to an average of ~13.5 GPUs instead of pinning 32.

Sweep config

run_smart_search, goal.target = goodput_per_gpu. Deployment + router + workload + SLA fixed; only the planner knobs swept (full trace, open-loop):

1search_space:
2 model_name: MiniMaxAI/MiniMax-M2.5
3 hardware_sku: b200_sxm
4 backend: [vllm]
5 deployment_mode: [agg]
6 gpu_budget: 32
7 context_length: 131072
8 parallel_configs:
9 - {tp: 4, moe_ep: 4, replicas: 8} # fixed; planner scales replicas 1..8
10 agg_max_num_batched_tokens: [16384]
11 agg_max_num_seqs: [512]
12 router_mode: [kv_router] # fixed tuned router
13 overlap_score_credit: [1.0]
14 prefill_load_scale: [4.0]
15 router_temperature: [0.0]
16 # swept: the planner
17 planner_scaling_policy: [throughput_180_5, throughput_600_5, load_180_5,
18 load_180_10, hybrid_180_5, hybrid_600_5]
19 planner_load_sensitivity: [aggressive, default, conservative]
20 planner_fpm_sampling: [small, default, large, fine]
21workload:
22 trace_path: <toolagent_trace.jsonl> # open-loop: no replay_concurrency
23 trace_format: mooncake
24goal:
25 target: goodput_per_gpu
26 sla: {ttft_ms: 2000, itl_ms: 50}
27sweep:
28 max_rounds: 6
29 candidates_per_round: 8
30 parallel_evals: 8

Result — goodput-per-GPU by planner-policy family

The dominant factor is the scaling-policy family (48 trials → 19 distinct configs):

policy familygoodput/gpuavg_gpugoodputmean TTFTmean TPOT
load_* (reactive)~117–121~8~950–1000~1100–1250~73–84
hybrid_*~82–86~14~1180~440–460~34–37
throughput_* (predictive)~59~20~1190~320–370~23–28

Best: load_180_10goodput/gpu ≈ 121 (avg_gpu 8.3, goodput 999).

Static 37.6 → default planner 87.7 → optimized planner ≈ 121 — the tuned planner is 3.2× static and +38% over the default planner.

All searched configurations (full-trace sweep)

Every distinct planner config the Vizier sweep evaluated (48 trials → 19 distinct), best goodput-per-GPU first. load_scaling_down_sensitivity 70/80/90 = aggressive/default/conservative; max_num_fpm_samples is the FPM budget (small=32, default=64, large/fine=128) and only affects the predictive throughput-scaling policies — for the load_* policies it is inert (so its spread within the load_180_10 family is noise).

goodput/gpuavg_gpugoodputttft_mstpot_msplanner_scaling_policyload_scaling_down_sensitivitymax_num_fpm_samples
121.08.26999112177.8load_180_1090128
119.17.76924124983.8load_180_109032
117.08.09946120781.6load_180_107032
116.78.43984109777.1load_180_108064
116.68.03936124883.5load_180_109064
116.48.30966118180.1load_180_107064
115.58.771013103272.8load_180_1070128
115.48.831019104173.3load_180_1080128
115.38.22948119380.4load_180_59032
115.38.10934124583.1load_180_108032
113.78.57974116379.0load_180_590128
111.08.87984111776.8load_180_58064
109.68.89975111376.1load_180_58032
108.49.421021101471.3load_180_570128
85.913.75118145836.5hybrid_180_57064
82.014.10115551437.3hybrid_600_590128
81.814.51118843333.5hybrid_180_580128
59.520.17119931923.1throughput_180_59032
59.120.11118937027.5throughput_600_590128

Takeaways

  • Reactive load scaling ≫ hybrid ≫ predictive throughput scaling for goodput-per-GPU. Predictive throughput scaling over-provisions (it pre-scales to avoid SLA misses → avg_gpu ~20 → worst goodput/gpu); reactive load scaling tracks the actual load and scales down hard (avg_gpu ~8).
  • The optimizer drives latency to the SLA edge to minimize GPUs: the best load_* config runs TPOT ~78 ms (near the 50 ms ITL bound) and TTFT ~1100 ms (under the 2000 ms bound), letting goodput dip to ~1000 while avg_gpu drops to ~8 → goodput/gpu peaks. The conservative predictive policies keep latency low but waste GPUs.
  • Caveat: load_* policies don’t use planner_fpm_sampling (it only affects predictive throughput scaling), so the sens/fpm spread within the load_180_10 family (117–121) is mostly mocker noise (~5%). The robust conclusion is the policy family (load_180_10), not a precise sens/fpm setting.

Reproduce

$python -m aisimulate.spica --config path/to/planner-sweep.yaml

Notes: the planner path needs the aic-forward-pass binding; a per-throughput-interval load-predictor sub-sweep runs first (forecast-loss winner pinned per interval). Static and default-planner baselines are run via dynamo.replay.run_trace_replay (static: no planner_config; planner: planner_config + the SLA). Same deployment as Router End-to-End Latency Sweep.