Repository navigation
[Perf][Diffusion][Qwen-Image] Replace per-bucket varlen loop with flat varlen attention for mixfusion - #6711
renyongxing wants to merge 55 commits into
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
The MFU of the attention operator in this PR has not reached the ideal range, and the cause is still under investigation. |
|
This PR appears to be related to model: qwen-image. Model owners: @NumberWan @renyongxing, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
|
@vllm-omni-review-bot please review |
Omni ReviewBot: no human activity for 10 days@renyongxing this pull request has had no human commit, comment or review since 2026-08-29. Per repository policy it may be closed if it stays inactive. To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline. |
|
The Rope encoding in mixfusion is optimized by using the fusion operator. It is expected to be submitted and the comparison experiment result will be uploaded this week. |
…Image-3.0 and Qwen-Image (vllm-project#3638) Signed-off-by: renyongxing <renyongxing2@huawei.com>
…en-Image mixfusion Signed-off-by: renyongxing <renyongxing2@huawei.com>
…ache pop key - qwen_image_mixfusion_benefit: OmniDiffusionRequest now takes prompt/request_id (single), not prompts/request_ids lists; construct per-request and gather. - hunyuan_image3_real_mixfusion_benefit: same API migration (one request per prompt, asyncio.gather for the batched path). - diffusion_model_runner: state_cache pop key uses state.request_id instead of the non-existent state.req_id in the interrupted/denoise_completed path. Signed-off-by: renyongxing <renyongxing2@huawei.com>
MixFusion scheduling and pipeline code (base_scheduler, qwen/hunyuan pipelines) still iterates request.prompts after the single-request API migration. Add a backwards-compatible prompts property returning [self.prompt] so the mixfusion batch path works on the current single-prompt request API. Signed-off-by: renyongxing <renyongxing2@huawei.com>
Replace the native apply_rotary_emb_torch path in QwenImageCrossAttention with the fused Triton rotary_kernel path. MixFusion lays out per-token (chunk-position, resolution) rotary frequencies in chunk order; flatten the 3D image_freq_chunks to [B*chunk_size, D/2] so every image token carries its own frequencies and feed them alongside text freqs through the fused kernel. Also adapt the mixfusion benchmark scripts to the single-request API and enable torch profiler output for the batched path. Signed-off-by: renyongxing <renyongxing2@huawei.com>
5a97412 to
b071d3e
Compare
…ry native forward hooks has_active_block_hooks() only inspected Omni's HookRegistry, so the MixFusion packed forward (which calls block.forward_mixfusion directly) could bypass PyTorch-native forward hooks on the blocks. Under HSDP (use_hsdp=True, hsdp_shard_size>1) fully_shard registers native forward pre-hooks for the all-gather of sharded weights; bypassing them runs the blocks on sharded weights and breaks inference. Also treat PyTorch-native hooks (block._forward_pre_hooks / _forward_hooks) as active so has_active_block_hooks() covers both hook mechanisms; the pipeline then falls back to serial denoising, whose per-request loops route every block through the hooked module call. Covered by new CPU unit tests simulating fully_shard's pre-hook. Signed-off-by: renyongxing <renyongxing2@huawei.com>
Omni ReviewBot triage noteAutomated triage of commit
These are automated triage suggestions only — the final decision belongs to the maintainers. |
|
Hi @hsliuustc0106, thanks for the re-review and for confirming the earlier fixes. Both remaining items are addressed on the current head (0ccca59):
has_active_block_hooks() now treats PyTorch-native hooks (block._forward_pre_hooks / block._forward_hooks) as active, in addition to Omni's HookRegistry. The pipeline re-checks this on every denoise step, and when any block carries an active hook it falls back to _denoise_step_serial_ragged, whose per-request loops route every block through the hooked module call — so fully_shard's all-gather pre-hooks run as usual. We went with the serial fallback rather than register_fsdp_forward_method because the check is mechanism-agnostic: it protects against any native forward hook on the blocks, not just FSDP's, and needs no coupling to the HSDP implementation. The fallback also logs, so a silent packed-path loss is visible in server logs. Covered by new CPU unit tests that simulate fully_shard's pre-hook behavior (tests/diffusion/models/qwen_image/test_qwen_image_mixfusion_hooks.py).
MixFusion previously required every request to opt in via sampling.extra_args["enable_mixfusion"]. There is now a server-level default in OmniDiffusionConfig (enable_mixfusion, mixfusion_min_chunk_tokens, mixfusion_max_chunks), propagated from the orchestrator CLI through StageDeployConfig. The per-request switch is still there and takes precedence: both the pipeline gate and the scheduler key read extra_args.get("enable_mixfusion", od_config.enable_mixfusion), so an explicit per-request True/False always overrides the server default. |
|
@hsliuustc0106 To quantify the advantages of MixFusion in tensor parallelism and be closer to the actual scenario, we use Two modes of arrival are reported:
Result - s0 (simultaneous burst, same size pairing):
**Results - s1 (staggered arrivals, mixed size order): * *
**Observations: * *
**Summary: **The advantage of MixFusion comes from the packaging capability of heterogeneous requests, which cannot be achieved by the |
|
Re-reviewed at [P2] Register the server-level CLI flags — arg_utils.py:627 The new fields exist in The previous HSDP P1 is fixed: native PyTorch hooks now trigger serial fallback. No new correctness blocker found in the reviewed delta. The remote head remained unchanged. All five reported checks and diff hygiene pass; no Buildkite result is listed. Validation was static—tests and GPU benchmarks were not independently rerun on the fork head. |
The server-level MixFusion knobs added in ee69278 (enable_mixfusion, mixfusion_min_chunk_tokens, mixfusion_max_chunks) were modeled on OrchestratorArgs and StageDeployConfig but never registered in OmniServeCommand.subparser_init(), so "vllm serve ... --omni --enable-mixfusion" failed with an unrecognized-arguments error and the server-level defaults could only be set through the deploy YAML. Register --enable-mixfusion (BooleanOptionalAction, so --no-enable-mixfusion can override a YAML-enabled stage), --mixfusion-min-chunk-tokens and --mixfusion-max-chunks, with defaults (256/128) documented in the help text. Unset flags stay out of the explicit kwargs dict, so the deploy YAML's per-stage values remain in force; explicit flags flow through build_stage_runtime_overrides (the fields are deploy-schema keys, exempt from the OrchestratorArgs blacklist) and the diffusion stage CLI field gate into the stage runtime overrides. Covered by new parser tests including the propagation check. Signed-off-by: renyongxing <renyongxing2@huawei.com>
…bility The MixFusion shape relaxation in the schedulers was gated only on the enable_mixfusion flag, so on any pipeline without ragged-latent support the scheduler dropped height/width/resolution from the batch key, packed mixed-resolution requests into one step batch, and the runner crashed per step in _gather_tensor_rows on the resulting shape mismatch. Introduce an opt-in pipeline capability and gate every layer on it: - Pipelines declare `supports_mixfusion: ClassVar[bool] = True`; QwenImagePipeline opts in. The attribute is read via getattr and documented on SupportsStepExecution only, mirroring supports_chunk_step_grouping, so the runtime_checkable protocol keeps matching all pipelines. - registry gains resolve_diffusion_pipeline_cls()/supports_mixfusion() to resolve the selected pipeline class (custom pipeline_class, Diffusers adapter, or registry lookup) without instantiating it. - The engine fails fast at startup when server-level enable_mixfusion is set on a pipeline without the capability. - The schedulers resolve the capability in initialize() and pass it into _apply_mixfusion_shape_relaxation; on an unsupported pipeline the request-level flag degrades to a no-op (resolution keeps grouping the batch, per-prompt requests keep their key), which is safe because MixFusion is performance-only. Tests cover capability resolution, both scheduler directions, and the engine startup check. Signed-off-by: renyongxing <renyongxing2@huawei.com>
…annotation Remove the local .bak_* backup ignore rule that leaked into the PR and correct the denoise_step return annotation: the list branch returns _denoise_step_mixfusion's list[torch.Tensor]. Signed-off-by: renyongxing <renyongxing2@huawei.com>
…text wake_events are created in _init_executor while the global start method is only switched to spawn later in _launch_workers, so mp.Event() would come from the fork context and then be pickled into spawn-Process args. Sync primitives must come from the same context as the processes that consume them; record this at the pin site. Signed-off-by: renyongxing <renyongxing2@huawei.com>
feature_compatibility.md gains a MixFusion serving section covering the three flags, the supports_mixfusion capability requirement (server-level flag fails at startup on unsupported pipelines, request-level flag is a no-op), and the packed-path constraints (edit fallback, zero_cond_t and SP>1 errors, hooks/true-CFG serial fallback, NPU dense attention). diffusion_continuous_batching.md describes the scheduler-side shape relaxation, the packed dispatch and its fallback chain, threshold aggregation, and the attention_kwargs limitation. Signed-off-by: renyongxing <renyongxing2@huawei.com>
Resolve conflict in docs/user_guide/feature_compatibility.md: keep the MixFusion section alongside the upstream allgather_degree GPU-count formula and markdown fixes. Signed-off-by: renyongxing <renyongxing2@huawei.com>
Omni ReviewBot: supersededThe CI failure noted on |
Signed-off-by: renyongxing <renyongxing2@huawei.com>
|
@hsliuustc0106 Thanks for the reviews. A few updates since the last round: Serve CLI flag registration (P2). Fixed in 5096155: --enable-mixfusion (BooleanOptionalAction, so --no-enable-mixfusion also works), --mixfusion-min-chunk-tokens (default 256), and --mixfusion-max-chunks (default 128) are now registered on the serve subparser and propagate through the stage-CLI override chain. Three parser tests cover registration and propagation. Pipeline capability gating for MixFusion (new, f4d92ff). In self-review we found that the shape relaxation in _apply_mixfusion_shape_relaxation was gated only on the enable_mixfusion flag, while only QwenImagePipeline sets use_ragged_latents. On any other step-execution pipeline, server-level --enable-mixfusion or a request-level flag would relax height/width/resolution out of the batch key, co-batch mixed resolutions, and crash per step in the dense _prepare_latents path. This is now gated end-to-end on an opt-in pipeline capability:
|
…n config reads The backwards-compatible `prompts` property on OmniDiffusionRequest shadowed the ad-hoc attribute pattern used by single-request code paths: joy_image's preprocess assigns `request.prompts`, which an unsettable property rejects, and its regression test asserts `not hasattr(request, "prompts")`, which the property makes impossible. Drop the accessor and express the per-prompt check in the MixFusion shape relaxation against the single `request.prompt` API instead. Also read `od_config` defensively in QwenImage's prepare_encode MixFusion block: unit tests build the pipeline without __init__, so the attribute may be absent. Signed-off-by: renyongxing <renyongxing2@huawei.com>
Omni ReviewBot: supersededThe CI failure noted on |
Signed-off-by: renyongxing <renyongxing2@huawei.com> # Conflicts: # vllm_omni/diffusion/attention/backends/flash_attn.py # vllm_omni/diffusion/models/qwen_image/qwen_image_transformer.py
Omni ReviewBot: supersededThe CI failure noted on |
Signed-off-by: renyongxing <renyongxing2@huawei.com>
Resolve conflicts: keep mixfusion fields in arg_utils; adopt upstream StageConfigFactory shim in omni_engine_base (fields flow via OmniDiffusionConfig). Signed-off-by: renyongxing <renyongxing2@huawei.com>
Signed-off-by: renyongxing <renyongxing2@huawei.com>
|
@hsliuustc0106 I conducted a Buildkite verification locally, and the results are as follows. Local reproduction of the Buildkite "Simple Test" (cuda/test-ready.yml) — failure analysis and re-trigger request Setup
Raw numbers
The two runs are not directly comparable: the PR run used Failure analysis — all failures are environmental, none traced to the PR
Conclusion No PR-introduced regression was found in the local reproduction; every failure cluster is attributable to the offline environment, a torch teardown bug in the container, or run-to-run interference. |
|
@hsliuustc0106 The last GPU job of build #17585 has been stuck in waiting for 2+ hours with no log output. The GPU agent appears to be online but shows as Unassigned (not attached to any cluster queue), so it never receives the job. Could someone with admin access re-attach the agent to the GPU queue, then retry the job? |
Purpose
Continue the work of #3638 (Efficient Mixed-Resolution Batching Strategy for HunyuanImage-3.0 and Qwen-Image). While fully preserving the original author's changes (standalone commit
43ffa56a9, authored bydesenSunUBW), replace Qwen-Image mixfusion's per-bucket equal-length varlen loop with a single flat varlen attention pass to eliminate inter-bucket bubbles and reduce end-to-end latency for mixed-resolution batches.Scope
Following review feedback that this PR was over the size budget, the non-Qwen-Image work has been split out:
[Perf][Diffusion][Hunyuan] Bucket-based MixFusion for Hunyuan-Image-3.0[Benchmark][Diffusion] MixFusion benchmarksWhat remains here is the Qwen-Image flat-varlen change plus the shared scheduling/batching infrastructure it requires — 667 non-test added lines across 11 files. No Hunyuan-specific code and no benchmark tooling is left in this PR. The one shared piece both family PRs build on,
_apply_mixfusion_shape_relaxationinvllm_omni/diffusion/sched/base_scheduler.py, intentionally stays here rather than being duplicated.Key changes:
vllm_omni/diffusion/attention/backends/abstract.py+flash_attn.py:AttentionMetadatagains varlen fields (is_varlen,q_cu_seqlens,kv_cu_seqlens,max_q_len,max_kv_len,padded_tokens); add_forward_varlen_flat(expects[total_tokens, H, D], callsflash_attn_varlen_funcwith the same interface as Enable Efficient Mixed-Resolution Batching Strategy for HunyuanImage-3.0 and Qwen-Image #3638).vllm_omni/diffusion/models/qwen_image/qwen_image_transformer.py:forward_mixfusionswitches from the per-bucket loop to a single flat varlen pass (cu_seqlensbuilt from real text lengths viamask.sum, text packed with 17-way padding, output unpacked bycu_seqlens).4cde003a0,28d3c272c): mixfusion benchmarks build one request per prompt +asyncio.gather;state_cachepop key usesrequest_id;OmniDiffusionRequest.promptscompatibility accessor for scheduler/pipelinereq.promptsiteration.Reference / Acknowledgement
The flat-varlen FlashAttention method used in this PR (varlen
AttentionMetadatafieldsq_cu_seqlens/kv_cu_seqlens/max_q_len/max_kv_len/padded_tokens; flat packed[total_tokens, heads, head_dim]Q/K/V tensors; and theflash_attn_varlen_func-based branch that rejects attention masks and requirespadded_tokens=0) is adapted from the approach proposed in PR #4024 — [Diffusion] Enable Qwen-Image dynamic step batching by @asukaqaq-s .How the application differs:
loop with one flat varlen pass over the mixfusion batch.
Note: PR #4024 is not yet merged into
main. If it lands first, the overlappingAttentionMetadatavarlen fields and the flat varlen branch inflash_attn.pyshould be reconciled to avoid duplication.Test Plan
Automated tests
tests/diffusion/models/qwen_image/test_qwen_image_mixfusion_packing.pyruns under
pytest -sv tests/diffusion tests/model_executor -m 'core_model and cpu'(theSimple · Diffusion & Model Executor Testselector).tests/e2e/online_serving/test_qwen_image.py -m core_model --run-level core_model(the
Diffusion · Qwen Image Testselector) guards the Qwen-Image path this PR touches.enable_mixfusion=true; mixfusion itselfis exercised by the packing unit tests above plus the manual benchmark runs below. The benchmark
harness (
mixfusion_benefit.py) now lives in [Benchmark][Diffusion] MixFusion benchmarks #8117.(a == b).all() == True).--family qwen:vLLM Version: 0.28.0
**vLLM-Omni Commit:**0.28.0
Test Result
chunk_size=1024,chunk_count=11FLASH_ATTNThe numbers above were produced with the pre-split per-family benchmark script; only the entry point moved, the measured code path is the one in this PR.
How to enable MixFusion
enable_mixfusionis opt-in per request, passed through the diffusionextra_argsfield:{"extra_args": {"enable_mixfusion": true}}It is accepted both at the top level of the request body and nested under
extra_body.extra_args. Two optional tuning knobs live in the same dict:mixfusion_min_chunk_tokens256gcd(token counts) < thismixfusion_max_chunks128Online serving:
Default is
False, so nothing changes unless you ask for itThe key does not exist on
main— it is introduced by this PR, and every read site defaults it toFalse.A deployment that never passesenable_mixfusiontherefore behaves as before.