[Feat] DiffusionEngine Support async batch infer (#2729)
Signed-off-by: Semmer <semmer@live.cn> Signed-off-by: jader <yjader@foxmail.com> Signed-off-by: asukaqaq-s <1311722138@qq.com> Co-authored-by: asukaqaq-s <1311722138@qq.com> Co-authored-by: jader <yjader@foxmail.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
This commit is contained in:
32 files changed
+1951
-190
No files matched your search
@@ -130,9 +130,21 @@ Warmup flags:
|
||||
|
||||
- `--warmup-requests`: Number of warmup requests.
|
||||
- `--warmup-num-inference-steps`: Steps used during warmup.
|
||||
- `--warmup-concurrency`: Maximum concurrent warmup requests. Use this to warm
|
||||
the same batch shape as the measured run instead of warming only batch=`1`.
|
||||
- For `--task t2v`: warmup requests are forced to use `num_frames=1` to make warmup faster and less noisy.
|
||||
|
||||
Traffic / concurrency flags:
|
||||
|
||||
- `--request-rate`: Target request rate (requests/second). If set to `inf`, the script sends all requests immediately.
|
||||
- `--max-concurrency`: Max number of in-flight requests (default: `1`). This can hard-cap the achieved QPS: if it is too small, requests will queue behind the semaphore, and both achieved throughput and observed SLO attainment can be skewed.
|
||||
|
||||
### Batched warmup note
|
||||
|
||||
For batched serving runs, warm the same in-flight shape you plan to measure.
|
||||
For example, a run with `--max-concurrency 8` should usually also use
|
||||
`--warmup-requests 8 --warmup-concurrency 8`; otherwise the first measured
|
||||
batch may still pay compile or CUDA-graph capture cost.
|
||||
|
||||
For a Qwen-Image continuous-batching replay example, see
|
||||
[`performance_dashboard/qwen_image_serving_performance.md`](./performance_dashboard/qwen_image_serving_performance.md).
|
||||
@@ -751,6 +751,52 @@ async def iter_requests(
|
||||
yield req
|
||||
|
||||
|
||||
def _make_warmup_request(
|
||||
requests_list: list[RequestFuncInput],
|
||||
index: int,
|
||||
args,
|
||||
) -> RequestFuncInput:
|
||||
warm_req = requests_list[index % len(requests_list)]
|
||||
if args.warmup_num_inference_steps is not None:
|
||||
warm_req = replace(
|
||||
warm_req,
|
||||
num_inference_steps=args.warmup_num_inference_steps,
|
||||
)
|
||||
if args.task == "t2v":
|
||||
warm_req = replace(warm_req, num_frames=1)
|
||||
return warm_req
|
||||
|
||||
|
||||
async def _run_warmups(
|
||||
requests_list: list[RequestFuncInput],
|
||||
args,
|
||||
session: aiohttp.ClientSession,
|
||||
request_func,
|
||||
) -> list[tuple[RequestFuncInput, RequestFuncOutput]]:
|
||||
if not args.warmup_requests or not requests_list:
|
||||
return []
|
||||
|
||||
warmup_requests = [_make_warmup_request(requests_list, i, args) for i in range(args.warmup_requests)]
|
||||
warmup_concurrency = min(int(args.warmup_concurrency), len(warmup_requests))
|
||||
warmup_semaphore = asyncio.Semaphore(warmup_concurrency)
|
||||
|
||||
print(
|
||||
f"Running {len(warmup_requests)} warmup request(s) "
|
||||
f"with num_inference_steps={args.warmup_num_inference_steps} "
|
||||
f"and warmup_concurrency={warmup_concurrency}..."
|
||||
)
|
||||
|
||||
async def limited_warmup_request_func(
|
||||
req: RequestFuncInput,
|
||||
) -> RequestFuncOutput:
|
||||
async with warmup_semaphore:
|
||||
return await request_func(req, session, None)
|
||||
|
||||
warmup_tasks = [asyncio.create_task(limited_warmup_request_func(req)) for req in warmup_requests]
|
||||
warmup_outputs = await asyncio.gather(*warmup_tasks)
|
||||
return list(zip(warmup_requests, warmup_outputs))
|
||||
|
||||
|
||||
def calculate_metrics(
|
||||
outputs: list[RequestFuncOutput],
|
||||
total_duration: float,
|
||||
@@ -900,23 +946,12 @@ async def benchmark(args):
|
||||
pbar = tqdm(total=len(requests_list), disable=args.disable_tqdm)
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
warmup_pairs: list[tuple[RequestFuncInput, RequestFuncOutput]] = []
|
||||
if args.warmup_requests and requests_list:
|
||||
print(
|
||||
f"Running {args.warmup_requests} warmup request(s) \
|
||||
with num_inference_steps={args.warmup_num_inference_steps}..."
|
||||
)
|
||||
for i in range(args.warmup_requests):
|
||||
warm_req = requests_list[i % len(requests_list)]
|
||||
if args.warmup_num_inference_steps is not None:
|
||||
warm_req = replace(
|
||||
warm_req,
|
||||
num_inference_steps=args.warmup_num_inference_steps,
|
||||
)
|
||||
if args.task == "t2v":
|
||||
warm_req = replace(warm_req, num_frames=1)
|
||||
warm_out = await limited_request_func(warm_req, session, None)
|
||||
warmup_pairs.append((warm_req, warm_out))
|
||||
warmup_pairs = await _run_warmups(
|
||||
requests_list=requests_list,
|
||||
args=args,
|
||||
session=session,
|
||||
request_func=request_func,
|
||||
)
|
||||
|
||||
if args.slo:
|
||||
# Prefer trace-provided per-request slo_ms. Only populate when missing.
|
||||
@@ -1072,6 +1107,13 @@ if __name__ == "__main__":
|
||||
default=1,
|
||||
help="num_inference_steps used for warmup requests.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--warmup-concurrency",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Maximum number of warmup requests to run concurrently. "
|
||||
"Set this to match the real batch shape when warming up torch.compile or CUDA graphs.",
|
||||
)
|
||||
parser.add_argument("--width", type=int, default=None, help="Image/Video width.")
|
||||
parser.add_argument("--height", type=int, default=None, help="Image/Video height.")
|
||||
parser.add_argument("--num-frames", type=int, default=None, help="Number of frames (for video).")
|
||||
@@ -1135,5 +1177,4 @@ if __name__ == "__main__":
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(benchmark(args))
|
||||
@@ -33,6 +33,12 @@ vllm serve Qwen/Qwen-Image --omni \
|
||||
--port 8091
|
||||
```
|
||||
|
||||
To replay step-wise continuous batching, compare
|
||||
`vllm serve Qwen/Qwen-Image --omni --port 8089 --step-execution --max-num-seqs 1`
|
||||
against the same command with `--max-num-seqs 8`. `--step-execution` is the
|
||||
feature gate, and increasing `--max-num-seqs` above `1` lets the scheduler
|
||||
keep more compatible requests active at once.
|
||||
|
||||
## 3.2 Key Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
@@ -70,6 +76,7 @@ python benchmarks/diffusion/diffusion_benchmark_serving.py \
|
||||
| `--task` | Task type (e.g., `t2i`) |
|
||||
| `--num-prompts` | Total number of requests |
|
||||
| `--max-concurrency` | Client-side concurrency |
|
||||
| `--warmup-concurrency` | Warmup concurrency for batch-shape prewarming |
|
||||
| `--random-request-config`| JSON string defining random request |
|
||||
|
||||
---
|
||||
@@ -133,6 +140,12 @@ python benchmarks/diffusion/diffusion_benchmark_serving.py \
|
||||
]'
|
||||
```
|
||||
|
||||
For a continuous-batching A/B replay, run the same benchmark command twice
|
||||
against the two serving configs above and keep traffic plus warmup settings
|
||||
identical across both runs. If the measured run uses `--max-concurrency 8`,
|
||||
warm with `--warmup-requests 8 --warmup-concurrency 8` so the first measured
|
||||
batch does not include compile or CUDA-graph capture overhead.
|
||||
|
||||
---
|
||||
|
||||
# 6. Performance Metrics
|
||||
@@ -163,6 +176,7 @@ To ensure consistent and comparable benchmark results:
|
||||
* Record parallel configuration
|
||||
* Record benchmark parameters (resolution, concurrency, number of prompts)
|
||||
* Ensure no background workload on GPUs during testing
|
||||
* When comparing continuous batching, keep warmup settings the same across runs
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user