Skip to content

Commit 2d86b81

Browse files
authored
chore: flux benchmarking script + code clean (ai-dynamo#8083)
Signed-off-by: ayushag <ayushag@nvidia.com>
1 parent 2e7a1e6 commit 2d86b81

7 files changed

Lines changed: 437 additions & 149 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/bin/bash
2+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Generic aiperf benchmark for vLLM-Omni text-to-image generation.
6+
# Assumes the server (Dynamo or native vllm-omni) is already running.
7+
#
8+
# Usage:
9+
# bash aiperf_image_gen.sh [OPTIONS]
10+
#
11+
# Options:
12+
# --model <model> Model to benchmark (default: black-forest-labs/FLUX.2-klein-4B)
13+
# --url <url> Server URL (default: http://localhost:8000)
14+
# --concurrency <n> Number of concurrent requests (default: 1)
15+
# --request-count <n> Total requests to send (default: 10)
16+
# --warmup-count <n> Warmup requests before measurement (default: 2)
17+
# --image-size <WxH> Generated image size (default: 1024x1024)
18+
# --response-format <fmt> Response format: url or b64_json (default: url)
19+
# --prompt-tokens-mean <n> Mean synthetic prompt length in tokens (default: 50)
20+
# --prompt-tokens-stddev <n> Stddev of synthetic prompt length (default: 10)
21+
# -h, --help Show this help message
22+
#
23+
# Examples:
24+
# bash aiperf_image_gen.sh
25+
# bash aiperf_image_gen.sh --model zai-org/GLM-Image --concurrency 4
26+
# bash aiperf_image_gen.sh --model Qwen/Qwen-Image --image-size 512x512 --request-count 20
27+
28+
MODEL="black-forest-labs/FLUX.2-klein-4B"
29+
URL="http://localhost:8000"
30+
CONCURRENCY=1
31+
REQUEST_COUNT=10
32+
WARMUP_COUNT=2
33+
IMAGE_SIZE="1024x1024"
34+
RESPONSE_FORMAT="url"
35+
PROMPT_TOKENS_MEAN=50
36+
PROMPT_TOKENS_STDDEV=10
37+
ARTIFACT_DIR=""
38+
39+
while [[ $# -gt 0 ]]; do
40+
case $1 in
41+
--model) MODEL=$2; shift 2 ;;
42+
--url) URL=$2; shift 2 ;;
43+
--concurrency) CONCURRENCY=$2; shift 2 ;;
44+
--request-count) REQUEST_COUNT=$2; shift 2 ;;
45+
--warmup-count) WARMUP_COUNT=$2; shift 2 ;;
46+
--image-size) IMAGE_SIZE=$2; shift 2 ;;
47+
--response-format) RESPONSE_FORMAT=$2; shift 2 ;;
48+
--prompt-tokens-mean) PROMPT_TOKENS_MEAN=$2; shift 2 ;;
49+
--prompt-tokens-stddev) PROMPT_TOKENS_STDDEV=$2; shift 2 ;;
50+
--artifact-dir) ARTIFACT_DIR=$2; shift 2 ;;
51+
-h|--help)
52+
sed -n '/^# Usage/,/^[^#]/p' "$0" | grep '^#' | sed 's/^# \?//'
53+
exit 0 ;;
54+
*) echo "Unknown option: $1"; exit 1 ;;
55+
esac
56+
done
57+
58+
AIPERF_ARGS=(
59+
aiperf profile
60+
--model "$MODEL"
61+
--tokenizer gpt2
62+
--url "$URL"
63+
--endpoint-type image-generation
64+
--synthetic-input-tokens-mean "$PROMPT_TOKENS_MEAN"
65+
--synthetic-input-tokens-stddev "$PROMPT_TOKENS_STDDEV"
66+
--extra-inputs "size:${IMAGE_SIZE}"
67+
--extra-inputs "response_format:${RESPONSE_FORMAT}"
68+
--concurrency "$CONCURRENCY"
69+
--request-count "$REQUEST_COUNT"
70+
--warmup-request-count "$WARMUP_COUNT"
71+
--ui none
72+
--no-server-metrics
73+
)
74+
75+
if [[ -n "$ARTIFACT_DIR" ]]; then
76+
AIPERF_ARGS+=(--artifact-dir "$ARTIFACT_DIR")
77+
fi
78+
79+
"${AIPERF_ARGS[@]}"

components/src/dynamo/vllm/omni/args.py

Lines changed: 109 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""Omni-specific argument parsing for python -m dynamo.vllm.omni."""
55

66
import argparse
7+
import dataclasses
78
import logging
89
from typing import Optional
910

@@ -24,15 +25,50 @@
2425
logger = logging.getLogger(__name__)
2526

2627

27-
class OmniArgGroup(ArgGroup):
28-
"""Diffusion pipeline kwargs passed through to AsyncOmni() constructor.
28+
@dataclasses.dataclass
29+
class OmniDiffusionKwargs:
30+
"""AsyncOmni constructor kwargs for diffusion engine configuration.
31+
32+
Every field here is passed directly to AsyncOmni(**kwargs) and consumed by
33+
_create_default_diffusion_stage_cfg() in vllm-omni. Adding a new vllm-omni
34+
diffusion flag only requires adding it here and to OmniArgGroup — the
35+
passthrough in base_handler is automatic.
36+
"""
37+
38+
enable_layerwise_offload: bool = False
39+
layerwise_num_gpu_layers: int = 1
40+
vae_use_slicing: bool = False
41+
vae_use_tiling: bool = False
42+
boundary_ratio: float = 0.875
43+
flow_shift: Optional[float] = None
44+
cache_backend: Optional[str] = None
45+
cache_config: Optional[str] = None
46+
enable_cache_dit_summary: bool = False
47+
enable_cpu_offload: bool = False
48+
enforce_eager: bool = False
49+
2950

30-
These are NOT part of OmniEngineArgs (which handles vLLM engine-level
31-
args like model, tp, max_model_len). Instead they are direct constructor
32-
kwargs for AsyncOmni and need Dynamo-side env-var (DYN_OMNI_*) support,
33-
so we define them here rather than relying on the upstream arg parser.
51+
@dataclasses.dataclass
52+
class OmniParallelKwargs:
53+
"""Diffusion parallelism configuration passed to DiffusionParallelConfig.
54+
55+
Every field here maps 1:1 to a DiffusionParallelConfig field (excluding
56+
tensor_parallel_size which comes from engine_args, and fixed/derived fields).
57+
Adding a new parallelism field only requires adding it here and to OmniArgGroup.
3458
"""
3559

60+
ulysses_degree: int = 1
61+
ring_degree: int = 1
62+
cfg_parallel_size: int = 1
63+
vae_patch_parallel_size: int = 1
64+
use_hsdp: bool = False
65+
hsdp_shard_size: int = -1
66+
hsdp_replicate_size: int = 1
67+
68+
69+
class OmniArgGroup(ArgGroup):
70+
"""CLI argument definitions for Dynamo vLLM-Omni."""
71+
3672
name = "dynamo-omni"
3773

3874
def add_arguments(self, parser) -> None:
@@ -49,7 +85,6 @@ def add_arguments(self, parser) -> None:
4985
help="Path to vLLM-Omni stage configuration YAML file (optional).",
5086
)
5187

52-
# Video encoding
5388
add_argument(
5489
g,
5590
flag_name="--default-video-fps",
@@ -59,7 +94,7 @@ def add_arguments(self, parser) -> None:
5994
help="Default frames per second for generated videos.",
6095
)
6196

62-
# Layerwise offloading
97+
# OmniDiffusionKwargs fields
6398
add_negatable_bool_argument(
6499
g,
65100
flag_name="--enable-layerwise-offload",
@@ -75,8 +110,6 @@ def add_arguments(self, parser) -> None:
75110
arg_type=int,
76111
help="Number of ready layers (blocks) to keep on GPU during generation.",
77112
)
78-
79-
# VAE optimization
80113
add_negatable_bool_argument(
81114
g,
82115
flag_name="--vae-use-slicing",
@@ -91,8 +124,6 @@ def add_arguments(self, parser) -> None:
91124
default=False,
92125
help="Enable VAE tiling for memory optimization in diffusion models.",
93126
)
94-
95-
# Diffusion scheduling
96127
add_argument(
97128
g,
98129
flag_name="--boundary-ratio",
@@ -113,8 +144,6 @@ def add_arguments(self, parser) -> None:
113144
arg_type=float,
114145
help="Scheduler flow_shift parameter (5.0 for 720p, 12.0 for 480p).",
115146
)
116-
117-
# Cache acceleration
118147
add_argument(
119148
g,
120149
flag_name="--cache-backend",
@@ -141,8 +170,6 @@ def add_arguments(self, parser) -> None:
141170
default=False,
142171
help="Enable cache-dit summary logging after diffusion forward passes.",
143172
)
144-
145-
# Execution mode
146173
add_negatable_bool_argument(
147174
g,
148175
flag_name="--enable-cpu-offload",
@@ -204,7 +231,7 @@ def add_arguments(self, parser) -> None:
204231
help="Maximum size in bytes for reference audio files (default: 50MB).",
205232
)
206233

207-
# Diffusion parallel configuration
234+
# OmniParallelKwargs fields
208235
add_argument(
209236
g,
210237
flag_name="--ulysses-degree",
@@ -227,9 +254,43 @@ def add_arguments(self, parser) -> None:
227254
env_var="DYN_OMNI_CFG_PARALLEL_SIZE",
228255
default=1,
229256
arg_type=int,
230-
choices=[1, 2],
257+
choices=[1, 2, 3],
231258
help="Number of GPUs used for classifier free guidance parallelism.",
232259
)
260+
add_argument(
261+
g,
262+
flag_name="--vae-patch-parallel-size",
263+
env_var="DYN_OMNI_VAE_PATCH_PARALLEL_SIZE",
264+
default=1,
265+
arg_type=int,
266+
help="Number of ranks used for VAE patch/tile parallelism during decode/encode.",
267+
)
268+
add_negatable_bool_argument(
269+
g,
270+
flag_name="--use-hsdp",
271+
env_var="DYN_OMNI_USE_HSDP",
272+
default=False,
273+
help=(
274+
"Enable Hybrid Sharded Data Parallel (HSDP) for diffusion models. "
275+
"Shards model weights across GPUs to reduce per-GPU memory usage."
276+
),
277+
)
278+
add_argument(
279+
g,
280+
flag_name="--hsdp-shard-size",
281+
env_var="DYN_OMNI_HSDP_SHARD_SIZE",
282+
default=-1,
283+
arg_type=int,
284+
help="Number of GPUs to shard model weights across when using HSDP (-1 = auto).",
285+
)
286+
add_argument(
287+
g,
288+
flag_name="--hsdp-replicate-size",
289+
env_var="DYN_OMNI_HSDP_REPLICATE_SIZE",
290+
default=1,
291+
arg_type=int,
292+
help="Number of HSDP replica groups (default: 1).",
293+
)
233294

234295
# Disaggregated stage worker flags
235296
add_argument(
@@ -244,7 +305,6 @@ def add_arguments(self, parser) -> None:
244305
"Requires --stage-configs-path."
245306
),
246307
)
247-
248308
add_negatable_bool_argument(
249309
g,
250310
flag_name="--omni-router",
@@ -263,30 +323,18 @@ class OmniConfig(DynamoRuntimeConfig):
263323
component: str = "backend"
264324
endpoint: Optional[str] = None
265325

266-
# mirror vLLM
267326
model: str
268327
served_model_name: Optional[str] = None
269-
270-
# vLLM-Omni engine args
271328
engine_args: OmniEngineArgs
272329

273-
# OmniArgGroup fields (populated by from_cli_args)
274330
stage_configs_path: Optional[str] = None
275331
default_video_fps: int = 16
276-
enable_layerwise_offload: bool = False
277-
layerwise_num_gpu_layers: int = 1
278-
vae_use_slicing: bool = False
279-
vae_use_tiling: bool = False
280-
boundary_ratio: float = 0.875
281-
flow_shift: Optional[float] = None
282-
cache_backend: Optional[str] = None
283-
cache_config: Optional[str] = None
284-
enable_cache_dit_summary: bool = False
285-
enable_cpu_offload: bool = False
286-
enforce_eager: bool = False
287-
ulysses_degree: int = 1
288-
ring_degree: int = 1
289-
cfg_parallel_size: int = 1
332+
333+
# Nested structs — each group of fields has a clear destination
334+
diffusion: OmniDiffusionKwargs = dataclasses.field(
335+
default_factory=OmniDiffusionKwargs
336+
)
337+
parallel: OmniParallelKwargs = dataclasses.field(default_factory=OmniParallelKwargs)
290338

291339
# TTS parameters
292340
tts_max_instructions_length: int = 500
@@ -299,15 +347,36 @@ class OmniConfig(DynamoRuntimeConfig):
299347
stage_id: Optional[int] = None
300348
omni_router: bool = False
301349

350+
@classmethod
351+
def from_cli_args(cls, args: argparse.Namespace) -> "OmniConfig":
352+
config = super().from_cli_args(args)
353+
config.diffusion = dataclasses.replace(
354+
OmniDiffusionKwargs(),
355+
**{
356+
f.name: getattr(args, f.name)
357+
for f in dataclasses.fields(OmniDiffusionKwargs)
358+
if hasattr(args, f.name)
359+
},
360+
)
361+
config.parallel = dataclasses.replace(
362+
OmniParallelKwargs(),
363+
**{
364+
f.name: getattr(args, f.name)
365+
for f in dataclasses.fields(OmniParallelKwargs)
366+
if hasattr(args, f.name)
367+
},
368+
)
369+
return config
370+
302371
def validate(self) -> None:
303372
DynamoRuntimeConfig.validate(self)
304373
if self.default_video_fps <= 0:
305374
raise ValueError("--default-video-fps must be > 0")
306-
if self.ulysses_degree <= 0:
375+
if self.parallel.ulysses_degree <= 0:
307376
raise ValueError("--ulysses-degree must be > 0")
308-
if self.ring_degree <= 0:
377+
if self.parallel.ring_degree <= 0:
309378
raise ValueError("--ring-degree must be > 0")
310-
if not (0 < self.boundary_ratio <= 1):
379+
if not (0 < self.diffusion.boundary_ratio <= 1):
311380
raise ValueError("--boundary-ratio must be in (0, 1]")
312381
if self.stage_configs_path is None:
313382
if self.stage_id is not None:
@@ -334,7 +403,6 @@ def parse_omni_args() -> OmniConfig:
334403
dynamo_runtime_argspec.add_arguments(parser)
335404
omni_argspec.add_arguments(parser)
336405

337-
# Add vLLM-Omni engine args
338406
vg = parser.add_argument_group(
339407
"vLLM-Omni Engine Options. Please refer to vLLM-Omni documentation for more details."
340408
)
@@ -349,7 +417,6 @@ def parse_omni_args() -> OmniConfig:
349417
args, unknown = parser.parse_known_args()
350418
config = OmniConfig.from_cli_args(args)
351419

352-
# Default endpoint to "generate" if not explicitly provided by user
353420
if config.endpoint is None:
354421
config.endpoint = "generate"
355422

components/src/dynamo/vllm/omni/base_handler.py

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""Base handler for vLLM-Omni multi-stage pipelines."""
55

66
import asyncio
7+
import dataclasses
78
import logging
89
import time
910
from typing import Any, AsyncGenerator, Dict
@@ -74,31 +75,17 @@ def _build_omni_kwargs(self, config) -> Dict[str, Any]:
7475
if config.stage_configs_path:
7576
omni_kwargs["stage_configs_path"] = config.stage_configs_path
7677

77-
# Diffusion engine-level params — read directly from config namespace
78-
diffusion_fields = [
79-
"enable_layerwise_offload",
80-
"layerwise_num_gpu_layers",
81-
"vae_use_slicing",
82-
"vae_use_tiling",
83-
"boundary_ratio",
84-
"flow_shift",
85-
"cache_backend",
86-
"cache_config",
87-
"enable_cache_dit_summary",
88-
"enable_cpu_offload",
89-
"enforce_eager",
90-
]
91-
for field in diffusion_fields:
92-
value = getattr(config, field, None)
78+
for field, value in dataclasses.asdict(config.diffusion).items():
9379
if value is not None:
9480
omni_kwargs[field] = value
9581

96-
# Build DiffusionParallelConfig if available
82+
# tensor_parallel_size comes from engine_args (vLLM's --tensor-parallel-size)
9783
if DiffusionParallelConfig is not None:
9884
parallel_config = DiffusionParallelConfig(
99-
ulysses_degree=getattr(config, "ulysses_degree", 1),
100-
ring_degree=getattr(config, "ring_degree", 1),
101-
cfg_parallel_size=getattr(config, "cfg_parallel_size", 1),
85+
tensor_parallel_size=getattr(
86+
config.engine_args, "tensor_parallel_size", 1
87+
),
88+
**dataclasses.asdict(config.parallel),
10289
)
10390
omni_kwargs["parallel_config"] = parallel_config
10491
else:

0 commit comments

Comments
 (0)