RFC: PD Ratio Throughput Optimization¶
Metadata¶
| Item | Content |
|---|---|
| Status | Approved |
| Author | jiayanan |
| Creation Date | 2026-03-19 |
| Related Links |
1. Overview¶
This proposal implements a PD ratio optimization feature: P and D are optimized independently, each finding their optimal batch size and parallel configuration. After obtaining their respective QPS, the system automatically derives the optimal PD ratio (the ratio of P instances to D instances) based on QPS balancing. The final output is Top N PD ratio results, where each result includes the corresponding P and D configurations.
QPS Formulas:
P QPS = p_concurrency / ttft * 1000 (req/s)
D QPS = d_concurrency / (tpot * output_length) * 1000 (req/s)
PD Ratio Calculation: PD ratio = D_QPS / P_QPS. For example, if P QPS = 10 req/s and D QPS = 15 req/s, then PD ratio = 1.5, meaning 1.5 P instances are needed per D instance to achieve supply-demand balance.
2. Detailed Design¶
2.1 CLI Entry Extension¶
Add the following parameters to cli/inference/throughput_optimizer.py:
| Parameter | Type | Description |
|---|---|---|
--prefill-devices-per-instance |
int | Number of devices per Prefill instance |
--decode-devices-per-instance |
int | Number of devices per Decode instance |
--enable-optimize-prefill-decode-ratio |
flag | Enable PD ratio optimization mode |
Validation Rules:
- When
--enable-optimize-prefill-decode-ratiois enabled,--prefill-devices-per-instanceand--decode-devices-per-instanceare required - P/D device counts must be positive integers
Usage Example:
python -m cli.inference.throughput_optimizer \
--input-length=2048 --output-length=512 \
model_id \
--device=DEVICE --num-devices=16 \
--prefill-devices-per-instance=4 --decode-devices-per-instance=2 \
--enable-optimize-prefill-decode-ratio \
--ttft-limits=500 --tpot-limits=50
2.2 ParallelRunner Extension¶
Add run_pd_ratio() method to ParallelRunner in serving_cast/parallel_runner.py.
Core Design: P and D are optimized independently, then combined to compute PD ratio.
Unlike run_agg()/run_disagg(), PD ratio mode requires different parallel configurations for P and D (e.g., P uses TP=4, D uses TP=2), thus separate UserInputConfig and ModelRunner instances must be created for each. P and D can run independently, each traversing the TP configuration space for optimization. After both complete, all P results and D results are combined to compute PD ratios.
run_pd_ratio():
1. Run P optimization independently
- num_devices = p_devices_per_instance
- Iterate feasible TP configs, find optimal batch size for each
- Collect all P results: [(p_parallel, p_batch, ttft, p_qps), ...]
2. Run D optimization independently
- num_devices = d_devices_per_instance
- Iterate feasible TP configs, find optimal batch size for each
- Collect all D results: [(d_parallel, d_batch, tpot, d_qps), ...]
3. Combine P and D results to compute PD ratio
- For each (P_result, D_result) pair, compute pd_ratio = D_QPS / P_QPS
- Sort by balanced QPS (min(P_QPS, D_QPS)) in descending order
- Output Top N ratio results
Configuration generation method (unified):
The configuration generation logic is identical for P and D, differing only in num_devices, so a single unified method is used with a parameter:
2.3 PDRatioThroughputOptimizer¶
Add serving_cast/service/pd_ratio_throughput_optimizer.py implementing the PDRatioThroughputOptimizer class.
Class Design:
PDRatioThroughputOptimizer is a combiner that does NOT inherit from BaseThroughputOptimizer. It takes existing P and D optimization results (as DataFrames) and combines them to compute PD ratios.
class PDRatioThroughputOptimizer:
"""Combiner for PD ratio throughput optimization.
Unlike Agg/Disagg optimizers that inherit from BaseThroughputOptimizer,
this class combines existing P and D optimization results to compute PD ratios.
It does not run optimization itself - it acts as a pure combiner.
"""
def __init__(self, output_length: int):
self.output_length = output_length
self._p_df: pd.DataFrame = None
self._d_df: pd.DataFrame = None
self._result_df: pd.DataFrame = None
def set_p_results(self, df: pd.DataFrame):
"""Set prefill optimization results."""
self._p_df = df
def set_d_results(self, df: pd.DataFrame):
"""Set decode optimization results."""
self._d_df = df
def optimize(self) -> pd.DataFrame:
"""Combine P and D results and compute PD ratios.
Returns DataFrame with all PD ratio combinations.
"""
Key Design Decisions:
- Uses DataFrame as input/output interface for consistency with
parallel_runner - Does NOT inherit from
BaseThroughputOptimizer- it's a combiner that processes existing results
Column Naming Convention:
-
Columns unique to one DataFrame remain unchanged (
ttftfrom prefill,tpotfrom decode) -
Overlapping columns get
_pand_dsuffixes (parallel_p,parallel_d, etc.) -
Final output uses
ttft_pandtpot_dfor consistency
2.4 Result Output¶
Overall Best Configuration is displayed first, followed by the Top N PD ratio table, consistent with the existing agg/disagg output style. Each row contains the corresponding P and D configuration information including concurrency:
P/D Devices Calculation Logic (only when user specifies --num-devices):
When the user provides --num-devices (total devices), the system calculates the total device allocation for P and D sides based on the PD ratio and per-instance device count. Constraints:
P_instances * prefill_devices_per_instance + D_instances * decode_devices_per_instance = num_devices
P_instances / D_instances ≈ pd_ratio
P_instances >= 1, D_instances >= 1 (positive integers)
Calculation: Enumerate all valid (P_instances, D_instances) positive integer pairs satisfying the total device constraint, and select the pair whose actual ratio P_instances / D_instances is closest to pd_ratio.
Example: num_devices=16, prefill_devices_per_instance=4, decode_devices_per_instance=2, pd_ratio=1.5
- P_instances=3, D_instances=2 → P_devices=12, D_devices=4 → actual ratio=1.5 → total=16 ✓
Example (user specified --num-devices):
********************************************************************************
--------------------------------------------------------------------------
Input Configuration:
Model: model_id
Devices: 16 DEVICE ← Only displayed when user provides --num-devices
Prefill Devices Per Instance: 4
Decode Devices Per Instance: 2
TTFT Limits: 500 ms
TPOT Limits: 50 ms
--------------------------------------------------------------------------
Overall Best Configuration:
PD Ratio: 1.50 (P Instance:D Instance)
Prefill QPS: 10.00 req/s (TTFT: 100.00 ms, Parallel: tp4pp1dp1, Batch: 4, Concurrency: 4)
Decode QPS: 15.00 req/s (TPOT: 13.33 ms, Parallel: tp2pp1dp1, Batch: 8, Concurrency: 8)
P Instances: 3 (12 devices) ← Only displayed when user provides --num-devices
D Instances: 2 (4 devices) ← Only displayed when user provides --num-devices
--------------------------------------------------------------------------
Top N PD Ratio Configurations:
+-----+----------+-----------+-----------+---------+---------+------------+------------+-----------+-----------+---------+---------+---------------+---------------+
| Top | PD Ratio | P QPS | D QPS | P TTFT | D TPOT | P Parallel | D Parallel | P Devices | D Devices | P Batch | D Batch | P Concurrency | D Concurrency |
| | (P:D) | (req/s) | (req/s) | (ms) | (ms) | | | /Instance | /Instance | Size | Size | | |
+-----+----------+-----------+-----------+---------+---------+------------+------------+-----------+-----------+---------+---------+---------------+---------------+
| 1 | 1.50 | 10.00 | 15.00 | 100.00 | 13.33 | tp4pp1dp1 | tp2pp1dp1 | 4 | 2 | 4 | 8 | 4 | 8 |
| 2 | 1.20 | 12.00 | 14.40 | 120.00 | 15.00 | tp2pp1dp2 | tp2pp1dp1 | 4 | 2 | 6 | 6 | 12 | 6 |
| 3 | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
+-----+----------+-----------+-----------+---------+---------+------------+------------+-----------+-----------+---------+---------+---------------+---------------+
********************************************************************************
Notes:
- The
Devicesline is only displayed when the user provides--num-devices - When user specifies
--num-devices, the Top N table includesP DevicesandD Devicescolumns showing the per-instance device count for P and D - When user specifies
--num-devices, Overall Best Configuration shows P/D instance count and total device allocation (calculated from PD ratio and num_devices constraint) - Table is sorted by balanced QPS in descending order
- Each row contains the complete P and D configuration for that ratio (including concurrency)
- Overall Best Configuration is output before the table, consistent with existing agg/disagg output style
2.5 Overall Architecture Flow¶
CLI Argument Parsing (throughput_optimizer.py)
│
├─ --enable-optimize-prefill-decode-ratio enabled?
│ ├─ Yes → ParallelRunner.run_pd_ratio()
│ │ │
│ │ ├─ Phase 1: P independent optimization
│ │ │ ├─ _get_pd_instance_user_configs(p_devices) → P TP config list
│ │ │ ├─ ProcessPoolExecutor parallel execution
│ │ │ │ └─ Each P TP config → ModelRunner → DisaggOptimizer → binary search best batch
│ │ │ └─ Collect all P results: [p_result_1, p_result_2, ...]
│ │ │
│ │ ├─ Phase 2: D independent optimization
│ │ │ ├─ _get_pd_instance_user_configs(d_devices) → D TP config list
│ │ │ ├─ ProcessPoolExecutor parallel execution
│ │ │ │ └─ Each D TP config → ModelRunner → DisaggOptimizer → binary search best batch
│ │ │ └─ Collect all D results: [d_result_1, d_result_2, ...]
│ │ │
│ │ └─ Phase 3: Combine and compute PD ratio
│ │ ├─ For each (P_result_i, D_result_j) compute QPS and PD ratio
│ │ ├─ Sort by balanced QPS in descending order
│ │ └─ Output Overall Best + Top N PD ratio results
│ │
│ └─ No → Existing run_agg() / run_disagg() flow
2.6 Module Interaction Diagram¶
┌──────────────────────────────────────────────────────────┐
│ throughput_optimizer.py (CLI) │
│ New: --prefill-devices-per-instance │
│ --decode-devices-per-instance │
│ --enable-optimize-prefill-decode-ratio │
└───────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ ParallelRunner │
│ New: run_pd_ratio() │
│ _get_pd_instance_user_configs(num_devices) │
│ _submit_pd_task() │
└───────────────────┬─────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
Phase 1: P Optimization Phase 2: D Optimization
(independent parallel) (independent parallel)
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ P ModelRunner │ │ D ModelRunner │
│ + DisaggOpt │ │ + DisaggOpt │
│ (prefill) │ │ (decode) │
└──────┬───────┘ └──────┬───────┘
│ │
└───────────┬───────────┘
▼
Phase 3: Combination
┌──────────────────────────┐
│ PDRatioThroughputOptimizer│
│ - Combine P/D results │
│ - Calculate QPS & PD Ratio│
│ - Sort and output Top N │
└──────────────────────────┘
3. Implementation Plan¶
Completed Features¶
- CLI parameter extension: Add
--prefill-devices-per-instance,--decode-devices-per-instance,--enable-optimize-prefill-decode-ratioparameters - ParallelRunner extension: Implement
run_pd_ratio()using_add_summary_resultpattern - PDRatioThroughputOptimizer: Implement combiner class with DataFrame interface, vectorized cross-join
- Result output: RFC-compliant Overall Best + Top N PD ratio table with deduplication
-
--dump-original-resultsapplies same filtering as normal output - Unit tests: Cover all PD ratio optimization scenarios
Test Plan¶
| Test Scenario | Description |
|---|---|
| Basic functionality | PD ratio optimization runs correctly and outputs correct PD ratio with Top N results |
| PD ratio calculation | Verify pd_ratio = D_QPS / P_QPS is computed correctly |
| Parameter validation | Error handling for missing required parameters and invalid values |
| Edge cases | Handling of zero P or D QPS |
| Output format | Verify Overall Best before Top N table, each row contains PD ratio, P/D QPS, P/D configs and concurrency |
| Backward compatibility | Ensure existing agg/disagg modes are unaffected |
4. Files Modified¶
| File | Description |
|---|---|
serving_cast/service/pd_ratio_throughput_optimizer.py |
Core combiner class with DataFrame interface |
serving_cast/service/optimizer_summary.py |
PD ratio output formatting and filtering |
serving_cast/parallel_runner.py |
run_pd_ratio() integration using _add_summary_result pattern |
cli/inference/throughput_optimizer.py |
CLI arguments |
tests/test_pd_ratio_throughput_optimizer.py |
Unit tests |