Skip to main content
Version: Latest

Capacity-planning workflows

Fleet Sim is most useful as a sequence of questions. Start with the workload, calibrate one baseline, and introduce new pools or policies only when the baseline shows why they are needed.

The commands below demonstrate the workflow. They intentionally do not include example savings or recommended GPU counts: those values depend on the workload and profile assumptions you provide.

1. Describe the workload

Before sizing a fleet, collect:

  • prompt and output token counts;
  • request timestamps or arrival-rate ranges;
  • the latency objective and how it is measured;
  • the selected model or route, if traffic already uses semantic routing; and
  • bursts, daily cycles, and failure periods that an average rate would hide.

The CLI accepts a cumulative distribution of total tokens:

{
"cdf": [
[512, 0.25],
[2048, 0.70],
[8192, 0.95],
[32768, 1.0]
]
}

Thresholds must increase and cumulative fractions should end at 1.0. CLI workloads sampled from a CDF assume 80% input tokens and 20% output tokens and Poisson arrivals. If that split or arrival process does not resemble your traffic, use the result only as a sensitivity study or use the Python library's TraceWorkload with the Fleet API.

The dashboard accepts JSONL and CSV trace uploads and summarizes prompt, output, arrival, and routing distributions. For simulation jobs, the service currently converts the uploaded lengths to a CDF and generates Poisson arrivals; it does not replay original timestamps or route labels. Remove prompt text and user identifiers before upload because only numeric planning fields are needed.

2. Establish a fixed-fleet baseline

Use simulate when you already know the current short- and long-pool counts:

vllm-sr-sim simulate \
--cdf data/azure_cdf.json \
--lam 200 \
--slo 500 \
--b-short 6144 \
--n-s 24 \
--n-l 8 \
--n-req 30000 \
--out baseline.json

Look at each pool separately. A fleet-wide percentile can hide a small long pool with a deep queue. Check at least:

  • P99 TTFT and P99 queue wait by pool;
  • completion and SLO-compliance fractions;
  • mean utilization;
  • preemptions or requests that did not complete; and
  • sensitivity to the random seed and request count.

The first run is not a calibration. Adjust the profile so modeled TTFT, throughput, concurrency, and KV capacity match a controlled load test of the same model and serving configuration.

3. Find a short/long threshold

A two-pool design helps only when the pools have meaningfully different service characteristics and the workload has enough traffic on both sides of the split.

Use pareto to evaluate CDF breakpoints as candidate thresholds:

vllm-sr-sim pareto \
--cdf data/lmsys_cdf.json \
--lam 200 \
--slo 500 \
--gpu-short a100 \
--gpu-long h100 \
--out threshold-sweep.json

Choose a threshold for operational reasons, not just the lowest modeled cost:

  • it should leave useful headroom in both pools;
  • the short pool must be able to serve every request routed to it;
  • route classification and token estimation must be stable near the boundary;
  • answer quality must remain acceptable if the pools serve different models; and
  • small workload shifts should not cause a large fleet-count jump.

After choosing a candidate, use simulate with the resulting counts instead of relying only on the analytical row.

4. Search a two-pool fleet

optimize sizes pools analytically and can DES-check the lowest-cost candidates:

vllm-sr-sim optimize \
--cdf data/azure_cdf.json \
--lam 200 \
--slo 500 \
--b-short 6144 \
--gpu-short a100 \
--gpu-long h100 \
--verify-top 3 \
--n-sim-req 30000 \
--out candidates.json

The search also sweeps a compress-and-route gamma band. That model assumes a fraction of borderline traffic can be safely shortened. The CLI does not know your real category mix or compression quality. Treat any benefit from gamma > 1 as conditional until you measure compression eligibility, latency, and task quality on representative requests.

5. Plan for traffic growth

Use whatif to find rates where a pool needs another capacity unit or loses its latency margin:

vllm-sr-sim whatif \
--cdf data/azure_cdf.json \
--lam-range 100 150 200 300 400 \
--slo 500 \
--b-short 6144 \
--gpu-short a100 \
--gpu-long h100 \
--out arrival-sweep.json

Use several workload shapes, not only several rates. A change in long-context share can overload the long pool even when total requests per second stays constant. Add your own operational reserve for failures, deployments, and burst absorption; CLI optimization does not infer that reserve from fleet telemetry.

6. Compare routing policies

For a fixed two-pool fleet, compare-routers runs three CLI policies over the same generated arrivals: length routing, compress-and-route with gamma=1.5, and uniform random routing.

vllm-sr-sim compare-routers \
--cdf data/agent_heavy_cdf.json \
--lam 200 \
--slo 500 \
--b-short 6144 \
--n-s 24 \
--n-l 8 \
--n-req 30000

This command is a controlled simulator comparison, not a benchmark of all router implementations in the repository. In particular, it does not run a live semantic classifier or include its latency and errors.

For routed production data, derive one CDF and arrival fraction per selected model or pool, then use a model topology with per-pool workloads. This preserves the observed aggregate routing mix without pretending to re-run the classifier or reproduce request order.

7. Model more than two pools

Use simulate-fleet for model-specific pools or arbitrary heterogeneous topologies. A minimal JSON file looks like this:

{
"pools": [
{
"id": "general",
"gpu": "a100",
"n_gpus": 12,
"max_ctx": 8192
},
{
"id": "long-context",
"gpu": "h100",
"n_gpus": 8,
"max_ctx": 65536
}
],
"router": "length"
}
vllm-sr-sim simulate-fleet fleet.json \
--cdf data/azure_cdf.json \
--lam 200 \
--slo 500 \
--n-req 30000 \
--out fleet-result.json

Supported CLI JSON router values are length, model, semantic, random, and least_loaded. For a model-routed CLI study, omit --cdf and provide a workloads entry for each pool; passing --cdf overrides those per-pool streams. Verify the fallback pool so missing model names do not silently distort a programmatic study.

8. Evaluate disaggregated prefill and decode

Disaggregation is relevant when prefill and decode need different capacity or hardware. It also introduces KV transfer, networking, and coordination costs.

vllm-sr-sim disagg \
--cdf data/azure_cdf.json \
--lam 200 \
--slo-ttft 500 \
--slo-tpot 100 \
--gpu-prefill h100 \
--gpu-decode a100 \
--mean-isl 2048 \
--mean-osl 256 \
--out disagg.json

The optimizer uses built-in degradation and transfer correction factors. Those are assumptions, not measurements of your network or disaggregated runtime. Replace the conclusion with a deployment test before selecting a prefill/decode ratio.

9. Add power only after performance calibration

tok-per-watt and grid-flex build on the same performance profile plus a power curve. They are useful for comparing scenarios after both have been measured for the target model.

vllm-sr-sim tok-per-watt \
--cdf data/azure_cdf.json \
--lam 200 \
--slo 500 \
--gpus h100 a100
vllm-sr-sim grid-flex \
--cdf data/azure_cdf.json \
--lam 200 \
--n-gpus 32 \
--gpu h100 \
--slo 500 \
--verify-des 20000 \
--out flex-curve.json

Do not use the built-in A10G versus A100/H100 tok-per-watt output as a hardware-only comparison: the bundled profiles represent different model sizes and parallel layouts. Even for profiles labeled with the same model, calibrate wall power, throughput, and output length under the same test conditions.

grid-flex estimates what happens when a concurrency cap reduces modeled power. It does not apply a cap to vLLM or participate in a demand-response system.

Before using a result

Record the following with every decision:

  • workload source and observation window;
  • model, precision, tensor parallelism, GPU SKU, and vLLM settings;
  • profile constants and how they were measured;
  • simulator version, command, seed, and request count;
  • analytical and DES results, including any disagreement; and
  • the load-test result that accepted or rejected the design.

This turns a simulator run into a reviewable capacity assumption instead of an unsupported performance claim.