| 1 | # Runtime Tuning |
| 2 | |
| 3 | The Recipes JSON remains the baseline. vLLM Recipes already provide validated |
| 4 | model, hardware, strategy, environment variables, and serving arguments. |
| 5 | Runtime tuning is optional and is intended for parameters whose best value can |
| 6 | depend on the actual deployment resources or expected request workload. |
| 7 | |
| 8 | ## Information Sources |
| 9 | |
| 10 | Runtime tuning combines up to three inputs: |
| 11 | |
| 12 | - **vLLM Recipe** — the required baseline from the Recipes JSON API or a direct |
| 13 | recipe JSON file. |
| 14 | - **Hardware information (optional)** — detected with `--detect-hardware`, |
| 15 | including effective CPU/NUMA topology and memory availability. |
| 16 | - **Workload information (optional)** — supplied through CLI hints such as input |
| 17 | and output token lengths, concurrency, TTFT/TPOT objectives, and target QPS. |
| 18 | |
| 19 | If no optional hardware or workload information is supplied, the converter keeps |
| 20 | the normal Recipes conversion behavior. |
| 21 | |
| 22 | See [Deployment-Time Parameters](#deployment-time-parameters) for the mapping |
| 23 | between these inputs and individual runtime parameters. |
| 24 | |
| 25 | ## Hardware Information |
| 26 | |
| 27 | Hardware detection is enabled only with `--detect-hardware`. It reports the |
| 28 | effective resources available to the process or container, including CPU/NUMA |
| 29 | topology and memory information. The current policy can use those values to |
| 30 | refine `tensor-parallel-size` and `gpu-memory-utilization`. |
| 31 | |
| 32 | ```bash |
| 33 | python3 tools/recipes/recipe_json_to_vllm_config.py \ |
| 34 | --model meta-llama/Llama-3.1-8B-Instruct \ |
| 35 | --hardware xeon6 \ |
| 36 | --detect-hardware |
| 37 | ``` |
| 38 | |
| 39 | Hardware detection is optional. Recipe hardware selects the tuning policy; host |
| 40 | hardware detection provides resource information to that policy. |
| 41 | |
| 42 | ## Workload Information |
| 43 | |
| 44 | Workload information is supplied explicitly because the converter runs before a |
| 45 | vLLM server exists. Supported hints include: |
| 46 | |
| 47 | - `--input-tokens` |
| 48 | - `--output-tokens` |
| 49 | - `--concurrency` |
| 50 | - `--ttft-sla-ms` |
| 51 | - `--tpot-sla-ms` |
| 52 | - `--target-qps` |
| 53 | |
| 54 | Example: |
| 55 | |
| 56 | ```bash |
| 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 128 \ |
| 61 | --output-tokens 128 \ |
| 62 | --concurrency 32 \ |
| 63 | --ttft-sla-ms 3000 \ |
| 64 | --tpot-sla-ms 100 |
| 65 | ``` |
| 66 | |
| 67 | `--output-tokens` is used to estimate steady-state request turnover for |
| 68 | `max-num-batched-tokens`. When both `--target-qps` and `--tpot-sla-ms` are |
| 69 | available, they are also used to estimate prompt arrival pressure per scheduler |
| 70 | step. `--ttft-sla-ms` is collected but is not yet converted directly into a |
| 71 | batch-size formula because the relationship between TTFT and scheduler budget is |
| 72 | model- and hardware-dependent. |
| 73 | |
| 74 | ## Deployment-Time Parameters |
| 75 | |
| 76 | The parameters below may already exist in a recipe. They are candidates for |
| 77 | deployment-time refinement when the recipe value is missing, generic, or based |
| 78 | on a validation environment that differs from the user's target deployment. |
| 79 | |
| 80 | | Runtime parameter | Why it may need deployment-time refinement | Main decision input | Current policy | |
| 81 | | --- | --- | --- | --- | |
| 82 | | `tensor-parallel-size` | The effective CPU/NUMA topology available to a container or pod can differ from the system used to validate the recipe. | Hardware topology | Use the largest power-of-two TP value that does not exceed the effective NUMA-node count. | |
| 83 | | `gpu-memory-utilization` | Available memory can differ by machine size, container limits, and other memory use. The vLLM option name is also used by the CPU backend. | Hardware memory + recipe baseline | Calculate a conservative fraction from the most constrained NUMA node. | |
| 84 | | `max-num-seqs` | The useful scheduler concurrency depends on the number of requests expected to be active at the same time. | Workload concurrency | Set `max-num-seqs` to `--concurrency` when supplied. | |
| 85 | | `max-num-batched-tokens` | Each scheduler iteration must share its token budget between active decodes and incoming prefills. | Input/output token shape, concurrency, and optional QPS/TPOT | Calculate decode budget + expected prefill demand, with vLLM scheduler constraints as floors. | |
| 86 | | `data-parallel-size` | The required replica count depends on the requested throughput and the capacity of one replica. | Capacity target | Keep the recipe value today because per-replica SLA capacity is not known. | |
| 87 | |
| 88 | ## How Each Runtime Parameter Is Calculated |
| 89 | |
| 90 | ### `tensor-parallel-size` |
| 91 | |
| 92 | TP is derived from the effective NUMA topology reported by hardware detection: |
| 93 | |
| 94 | ```text |
| 95 | TP = largest power of two <= effective NUMA-node count |
| 96 | ``` |
| 97 | |
| 98 | For example, 2 effective NUMA nodes produce TP=2, while 6 nodes currently |
| 99 | produce TP=4. This is a topology-based starting point and avoids automatically |
| 100 | selecting unusual non-power-of-two TP sizes before they are validated. |
| 101 | |
| 102 | ### `gpu-memory-utilization` |
| 103 | |
| 104 | For every effective NUMA memory node, the policy calculates: |
| 105 | |
| 106 | ```text |
| 107 | node_safe_fraction = available_memory / total_memory - 0.10 |
| 108 | safe_fraction = min(0.90, minimum node_safe_fraction) |
| 109 | ``` |
| 110 | |
| 111 | The 10% reserve leaves memory for model/runtime overhead outside the vLLM cache |
| 112 | budget. If the recipe already contains a smaller value, the policy preserves the |
| 113 | smaller value. If it is absent, `0.80` is used as the initial ceiling: |
| 114 | |
| 115 | ```text |
| 116 | candidate = min(recipe value or 0.80, safe_fraction) |
| 117 | ``` |
| 118 | |
| 119 | ### `max-num-seqs` |
| 120 | |
| 121 | When `--concurrency` is supplied, it directly represents the requested maximum |
| 122 | number of simultaneously active requests: |
| 123 | |
| 124 | ```text |
| 125 | max-num-seqs = concurrency |
| 126 | ``` |
| 127 | |
| 128 | If concurrency is not supplied, the converter does not override this parameter. |
| 129 | |
| 130 | ### `max-num-batched-tokens` |
| 131 | |
| 132 | The token budget is workload-derived and no longer assumes a fixed number of |
| 133 | parallel prefills. |
| 134 | |
| 135 | First, the policy determines the active sequence count: |
| 136 | |
| 137 | ```text |
| 138 | active_sequences = |
| 139 | --concurrency |
| 140 | or recipe max-num-seqs |
| 141 | or vLLM default max_num_seqs (128) |
| 142 | ``` |
| 143 | |
| 144 | Decode requests consume approximately one token per active sequence in a |
| 145 | scheduler iteration: |
| 146 | |
| 147 | ```text |
| 148 | decode_budget = active_sequences |
| 149 | ``` |
| 150 | |
| 151 | The policy then estimates how many new prompts need prefill work per scheduler |
| 152 | step: |
| 153 | |
| 154 | ```text |
| 155 | prefills_per_step = max( |
| 156 | 1, |
| 157 | active_sequences / output_tokens, |
| 158 | target_qps * tpot_sla_ms / 1000 |
| 159 | ) |
| 160 | |
| 161 | prefills_per_step = min(active_sequences, prefills_per_step) |
| 162 | prefill_budget = ceil(input_tokens * prefills_per_step) |
| 163 | ``` |
| 164 | |
| 165 | The final scheduler budget is: |
| 166 | |
| 167 | ```text |
| 168 | max-num-batched-tokens = max( |
| 169 | vLLM default max_num_batched_tokens (2048), |
| 170 | active_sequences, |
| 171 | decode_budget + prefill_budget |
| 172 | ) |
| 173 | ``` |
| 174 | |
| 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 = 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 |
| 185 | policy also ensures: |
| 186 | |
| 187 | ```text |
| 188 | max-num-batched-tokens >= max-model-len |
| 189 | ``` |
| 190 | |
| 191 | ### `data-parallel-size` |
| 192 | |
| 193 | `--target-qps` alone is not enough to choose DP safely. A correct capacity rule |
| 194 | also needs measured per-replica throughput that still satisfies TTFT/TPOT: |
| 195 | |
| 196 | ```text |
| 197 | DP = ceil(target_qps / qps_per_replica_at_SLO) |
| 198 | ``` |
| 199 | |
| 200 | Because the converter does not have that measured capacity yet, it intentionally |
| 201 | keeps the recipe DP value rather than guessing. |
| 202 | |
| 203 | ## Precedence |
| 204 | |
| 205 | ```text |
| 206 | vLLM defaults |
| 207 | -> vLLM Recipes baseline |
| 208 | -> hardware refinement (optional) |
| 209 | -> workload / SLO refinement (optional) |
| 210 | -> config.yml + env.sh |
| 211 | ``` |
| 212 | |
| 213 | ## Runtime-Tuning Hardware Scope |
| 214 | |
| 215 | Runtime tuning is selected from the resolved recipe JSON's `hardware` field, |
| 216 | rather than by inspecting which physical devices happen to be present on the |
| 217 | host. This matters because a GPU server also exposes its host CPU topology. |
| 218 | |
| 219 | The current runtime-tuning policy registry contains `xeon6`. A tuning request |
| 220 | for unregistered recipe hardware, such as `b200`, fails before host hardware |
| 221 | detection: |
| 222 | |
| 223 | ```text |
| 224 | ERROR: Runtime tuning is not supported for recipe hardware 'b200'. |
| 225 | Currently supported: xeon6. |
| 226 | ``` |
| 227 | |
| 228 | Plain recipe conversion remains available for all recipe hardware. The gate only |
| 229 | applies when optional runtime-tuning inputs are requested. |
| 230 | |
| 231 | ## vLLM CPU Docker Shell |
| 232 | |
| 233 | Run hardware detection and runtime tuning inside the target vLLM CPU container |
| 234 | so detected CPU, NUMA, memory, and cgroup limits match the deployment |
| 235 | environment. |
| 236 | |
| 237 | From the vLLM source tree: |
| 238 | |
| 239 | ```bash |
| 240 | mkdir -p recipe-output |
| 241 | |
| 242 | docker run --rm -it \ |
| 243 | --entrypoint bash \ |
| 244 | --security-opt seccomp=unconfined \ |
| 245 | --cap-add SYS_NICE \ |
| 246 | --shm-size=4g \ |
| 247 | -p 8000:8000 \ |
| 248 | -v "$PWD/tools/recipes:/recipes:ro" \ |
| 249 | -v "$PWD/recipe-output:/output" \ |
| 250 | -v "$HOME/.cache/huggingface:/root/.cache/huggingface" \ |
| 251 | -w /output \ |
| 252 | vllm/vllm-openai-cpu:latest-x86_64 |
| 253 | ``` |
| 254 | |
| 255 | `/output` is writable; `/recipes` remains read-only. |
| 256 | |
| 257 | Inside the container, generate one runtime-tuned initial configuration: |
| 258 | |
| 259 | ```bash |
| 260 | 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 128 \ |
| 265 | --output-tokens 128 \ |
| 266 | --concurrency 32 \ |
| 267 | --ttft-sla-ms 3000 \ |
| 268 | --tpot-sla-ms 100 \ |
| 269 | --config-out /output/config.yml \ |
| 270 | --env-out /output/env.sh |
| 271 | ``` |
| 272 | |
| 273 | Deploy the generated initial configuration: |
| 274 | |
| 275 | ```bash |
| 276 | source /output/env.sh |
| 277 | vllm serve --config /output/config.yml |
| 278 | ``` |
| 279 | |
| 280 | For optional benchmark-backed validation of the scheduler settings, continue |
| 281 | with [SWEEP_TUNING.md](SWEEP_TUNING.md). |
| 282 | |
| 283 | ## Implementation |
| 284 | |
| 285 | - `hardware_detection.py` collects effective CPU/NUMA/memory information. |
| 286 | - `runtime_tuning.py` owns hardware-policy selection and independent parameter |
| 287 | tuning functions. |
| 288 | - `recipe_json_to_vllm_config.py` resolves the recipe, collects optional inputs, |
| 289 | applies the selected policy, and generates `config.yml` and `env.sh`. |
| 290 | |
| 291 | For benchmark-backed validation of the initial scheduler suggestion, see |
| 292 | [SWEEP_TUNING.md](SWEEP_TUNING.md). |