Skip to content

[Kernel][HunyuanVideo-1.5] Fuse video/text QK RMSNorm + RoPE + cat into one Triton launch - #7596

Open
yuweih205 wants to merge 7 commits into
vllm-project:mainfrom
yuweih205:perf/hunyuanvideo15-fused-qk-norm-rope
Open

yuweih205 wants to merge 7 commits into
vllm-project:mainfrom
yuweih205:perf/hunyuanvideo15-fused-qk-norm-rope

Conversation

@yuweih205

@yuweih205 yuweih205 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Fuse video/text Q/K preparation and concatenation. Video tokens rotate; trailing text tokens use identity rotations. Keep video-first ordering and the existing sequence-parallel path.

The fusion paths are selected automatically; there is no fusion environment-variable override. Unsupported devices, dtypes and geometry retain their existing paths. All six PRs contain identical shared operator and test files and only their own model integration; any PR can merge first.

In eager mode the fused path reproduces the vLLM CUDA RMSNorm reduction order and keeps the rotation uncontracted (cuda_rms_norm=True), so video/text Q/K/V (identity rows for text) are bit-identical to the unfused chain. Compiled graphs (regional torch.compile, dynamic=True) keep using the fused custom op. There the unfused chain still runs vLLM's CUDA RMSNorm kernel as an opaque op while Inductor generates the RoPE/concat arithmetic; on the measured shallow shapes the fused output was byte-identical to that compiled chain as well (see Test Result). cuda_rms_norm=False at the fused calls restores the previous revision's Triton-order numerics (kernel-level cost of the CUDA-order reduction measured below).

Test Plan

  • Current commit: 9d96e8566.
  • infra-debug NVIDIA H200; Python 3.12.13, vLLM 0.31.0, Torch 2.13.0+cu130, CUDA 13.0, Triton 3.7.1.
  • Repository pytest configuration and CI markers enabled.
python -m pytest -s -v tests/diffusion/models/hunyuan_video/test_hunyuan_video_15_fused_qk_norm_rope.py \
  -m core_model --run-level core_model

Test Result

On 2026-10-17 (infra-debug NVIDIA H200; Python 3.12, vLLM 0.31.0, Torch 2.13.0+cu130, CUDA 13.0, Triton 3.7.1; eager, world/TP/SP size 1.)

  • tests/diffusion/models/hunyuan_video/test_hunyuan_video_15_fused_qk_norm_rope.py: 4 passed, 0 failed/skipped.
  • Shared operator suite (identical file in all six PRs): 34 passed.
  • Changed-file pre-commit checks (ruff, typos, SPDX, test markers, import checks, mypy) passed.

Eager parity and timing (original arm = table packing disabled, i.e. the unfused chain; bitwise arm = this head's default, cuda_rms_norm=True; fast arm = the same launch with cuda_rms_norm=False, i.e. the previous revision's Triton-order numerics; CUDA-event medians over three ABBA rounds, each fused arm interleaved with the original arm; exact byte comparison of the full output before and after timing):

Case Original Bitwise (default) Fast (cuda_rms_norm=False) Bitwise vs fast Bytes differing vs original: bitwise / fast
HunyuanVideo-1.5 shallow forward, B=1 5.75 ms 5.46 ms (−5.13%) 5.45 ms (−5.37%) +0.21% 0 / 52476 of 262144 (max abs 0.0156)
HunyuanVideo-1.5 shallow forward, B=2 9.72 ms 9.18 ms (−5.55%) 9.16 ms (−5.77%) +0.22% 0 / 106371 of 524288 (max abs 0.0156)

Kernel alone (joint op, 16 heads, 512 text + 4096 image tokens per sample; CUDA-graph replay medians from the heads-per-program campaign on this operator (26 geometries × fast/bitwise × 4/8/16 heads per program, infra-debug H200, job hyw-vomni-hp-validation-1017-r1; eight heads were bitwise-identical to four in all 52 path/mode/geometry cells and faster in 51)): four → eight heads per program moves B=1: bitwise 42.6 → 37.6 µs, fast 38.3 → 34.8 µs; the bitwise default is now +8.0% of the fast kernel and −1.8% of the previous revision's fast kernel.

Model level, the bitwise arm's latency is +0.21% (B=1) / +0.22% (B=2) relative to the fast arm — inside the ±0.5% run-to-run spread of this harness. To take the previous numerics instead, pass cuda_rms_norm=False at the fused calls in vllm_omni/diffusion/models/hunyuan_video/hunyuan_video_15_transformer.py (joint launch, table packer); this is a code-level switch, no environment variable.

Production compile (same harness, regionally_compile(transformer, dynamic=True) as the diffusion model runner applies it, production IR op priority ['vllm_c', 'native']; the previous public head of this PR was run the same way in the same job):

Case Original (compiled) Fused (compiled) Fused vs original output
HunyuanVideo-1.5 shallow forward, compiled, B=1 7.52 ms 7.49 ms (−0.36%) 0 bytes differing
HunyuanVideo-1.5 shallow forward, compiled, B=2 9.33 ms 9.15 ms (−1.93%) 0 bytes differing

Eager in the same run: −4.68% / −4.59%. Both heads compile with the same 11 graph breaks per forward (all from _vllm_fa2_C.varlen_fwd having no fake implementation, which the non-fullgraph production path tolerates), no recompilation across repeated calls, 2 fused launches per forward, and vLLM's rms_norm_kernel (8 launches per forward) present in the unfused chain in both eager and compiled runs.

The model-level and request-level rows above were measured at the previous head (a3cd4526c, four heads per program); the eight-heads commit changes only the kernel launch shape with bitwise-identical output, so the parity results carry over and the fused arms gain the kernel delta on top of the figures shown.

These are operator, component and forward-level regressions on the stated shapes; pretrained end-to-end accuracy/performance on other models was not rerun for this update.

AI assistance: Claude Code contributed the initial implementation and this revision; Codex simplified automatic selection, implemented and checked CUDA-reference numerics, and ran earlier validation.

@vllm-omni-review-bot

Copy link
Copy Markdown

This PR appears to belong to: docs/design/module/diffusion/diffusion_model_integration.md, docs/design/module/diffusion/index.md.

Module owners: @david6666666 @wtomin @Isotr0py

Routing: @david6666666 via module of the changed files, module named in the PR description, CODEOWNERS; @wtomin via module of the changed files, module named in the PR description, CODEOWNERS; @Isotr0py via module of the changed files, module named in the PR description

@yuweih205, 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.

@hsliuustc0106 hsliuustc0106 added the high priority high priority issue, needs to be done asap label Sep 16, 2026
@yuweih205
yuweih205 force-pushed the perf/hunyuanvideo15-fused-qk-norm-rope branch from 64ba7eb to 4461158 Compare September 16, 2026 06:13
@yuweih205

Copy link
Copy Markdown
Contributor Author

Self-review: the double-stream attention takes an explicit qk_norm_rope_table and uses fused_joint_qkv_norm_rope (video first, as the eager cat); text rows carry the identity rotation, which the kernel applies exactly. The table is packed once per forward, only after fused_qk_norm_rope_available() and the token gate pass (last commit moves that check before the identity-row cat, per the review on #7600). SP keeps the eager chain. Tests: table layout on CUDA, CPU/non-bf16 skip, identity rows exact, attention fused vs eager. End-to-end on HunyuanVideo-1.5-Diffusers-480p_t2v in the description (−4.3% per clip; control arm shows RMSNorm rounding order only).

@Dong1017 Dong1017 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (static + H200 unit run at 4461158f)

What this solves: the per-block eager chain (video RMSNorm → RoPE, text RMSNorm, three cats) across HunyuanVideo 1.5's 54 dual-stream blocks becomes one Triton launch producing the joint [B, S_v+S_t, H, D] QKV attention consumes. Consumer-side increment over #7560: per-forward packed rope table with identity rows for the un-rotated text tokens, a no-allocation gate (SP / non-bf16 / CPU / token floor), and per-call dispatch with the eager chain preserved line-for-line as fallback.

Verification: I ran the 4 tests on H200 (vllm 0.29.0, torch 2.13.0+cu130, triton 3.7.1, clean head): 4 passed. Note the head tree requires vLLM 0.29 (compute_layout_strides, diffusion_kv/layout.py:23) — the hybrid pre-0.29 tree in the test plan is not what the ready pipeline will execute. Quality/perf A/B is same-tree, same-workload, A B B A with a rounding-order control arm — baseline consistent; e2e numbers remain author-reported.

Findings: no blockers. Two inline items (test wording vs "bitwise" claim; second private copy of the table helper). Verified correct: [video|text] order end-to-end, identity-row exactness (fp32 x·1 − pair·0), weight slots/eps/coeff dtype, interleaved convention (is_neox_style=False ⇔ kernel interleaved=True, rotary 128 = head_dim), SP and fallback paths.

RFC #7382: fits the §4.2 QK/RoPE consumer-adoption track (H3 → Boogu → Flux.2 → this) with per-consumer validation and retained references as required; this is not the §4.3 gated-residual delivery. Two alignment items: (1) the RFC's public entry is diffusion/layers/ops/ plus a public eligibility query — this PR adds the second consumer of private _fused_cuda_supported, so I'd land that migration before a third consumer appears; (2) HunyuanVideo 1.5's gated-residual adoption is named in §4.3 but stays out of this PR — worth an explicit pointer in the RFC's PR mapping.

Coordination: merge order behind #7560; ops-package/eligibility-query ownership; CUDA CI wiring for the tests/diffusion/layers/ op suite (only the NPU variant runs today); remaining dual-stream candidates (flux1/sd3/qwen_image/cosmos3/longcat/ovis) each need text-rows (identity vs real angles), rope style, head-count/eps contract, and SP routing checked before adoption.

Comment thread vllm_omni/diffusion/models/hunyuan_video/hunyuan_video_15_transformer.py Outdated
@yuweih205
yuweih205 force-pushed the perf/hunyuanvideo15-fused-qk-norm-rope branch from 4461158 to 9833942 Compare September 17, 2026 06:17
@yuweih205

Copy link
Copy Markdown
Contributor Author

@Dong1017 Thanks for the H200 run. Done: private packer removed (pack_qk_norm_rope_table(identity_rows=...) from #7560), SPDX header restored, description reworded (exact check is rotation-only; attention-level comparison at the ≤1-ulp RMSNorm tolerance). Happy to switch to a public eligibility query once it exists in layers/ops; not blocking here.

@vllm-omni-review-bot

vllm-omni-review-bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Omni ReviewBot triage note

Resolved as of da5e924d0499: the high-priority or low-quality signal noted on an earlier commit no longer applies.

@hsliuustc0106 hsliuustc0106 added the Kernel optimization Codes related to optimize kernel execution to improve hardware utilization label Sep 22, 2026
@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: no human activity for 9 days

@yuweih205 this pull request has had no human commit, comment or review since 2026-09-19. Please confirm the current plan and next step. The author or a maintainer decides whether to change the PR state.

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.

@vllm-omni-review-bot

Copy link
Copy Markdown
Omni ReviewBot routing record

Assigned Strict on cursor (cursor-grok-4.6-high) under experiment fleet-strict-cursor-grok46-zcode-glm53flash-5050-c5-z10-20261002.

Signed-off-by: HuangYuwei <yuweih205@gmail.com>
@yuweih205
yuweih205 force-pushed the perf/hunyuanvideo15-fused-qk-norm-rope branch from f84ebbd to 01752b9 Compare October 8, 2026 04:15
Signed-off-by: HuangYuwei <yuweih205@gmail.com>
Signed-off-by: HuangYuwei <yuweih205@gmail.com>
@yuweih205
yuweih205 force-pushed the perf/hunyuanvideo15-fused-qk-norm-rope branch from 01752b9 to f7e730a Compare October 8, 2026 06:13
Signed-off-by: HuangYuwei <yuweih205@gmail.com>
@yuweih205
yuweih205 force-pushed the perf/hunyuanvideo15-fused-qk-norm-rope branch from 4b25e59 to 3020de9 Compare October 8, 2026 07:42
@yuweih205

yuweih205 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Supplemental eager bitwise measurements and queued production check

Existing PR performance results remain as recorded. The retained operator scripts and raw results stay pinned to their original artifact revision.

Measured operator case: H200; B1, 2048 image/video + 256 text tokens, 16 heads, head dimension 128; BF16 activations/weights/RoPE table, full interleaved RoPE on image/video rows and identity text rows, epsilon 1e-6, seed 20261008, world/TP/SP size 1. The candidate has zero integer-view Q/K/V bit mismatches against the original eager CUDA RMSNorm/RoPE chain. Original and candidate repeat checks pass.

Path Original chain (ms) Current fusion (ms) Reference candidate (ms) Candidate saving vs original Candidate extra latency vs current
Joint 0.246770 0.109039 0.110161 55.36% 1.03%

Eager CUDA-event medians: six balanced-order samples, 20 warmup calls, 100 calls per sample. These durations cover Q/K/V preparation with the packed RoPE table reused; table packing is excluded. They establish operator parity and latency for this case.

Prepared production setting: change VLLM_OMNI_FUSED_QK_NORM_ROPE_NUMERICS=fast to vllm_cuda_128; this resolves rms_norm_reduction="triton" → "vllm_cuda_128" and enable_fp_fusion=True → False. The common patch and validation records are prepared locally and have not been pushed to the six PRs. The retained table measured the isolated candidate.

Actual r3 target: prepared source cce0fdfd9a3d810be67cda07eff537ef11cb99c5, using model_production_bitwise.py, SHA256 1d4eaff2c5de50b0c09a098adfab736bd936dba1c01653f870bd7dbb4b3efed9. The model's imported production functions are wrapped only for counting and receive unchanged arguments; no experimental operator is substituted.

The three eager arms use the same initialized model/state/inputs:

Arm MIN_TOKENS NUMERICS
Original chain 1000000000000 fast
Current fusion 0 fast
CUDA reference preset 0 vllm_cuda_128

H200, vLLM 0.29.0, Torch 2.13.0/CUDA 13.0, Triton 3.7.1; BF16, head dimension 128, B1/B2, seed 20261008, world/TP/SP size 1; two Hunyuan blocks plus one text-refiner layer, 64 configured text tokens and a 16×16 image/video grid. Hunyuan also uses 16 secondary-text and 16 image-condition tokens. Original RMSNorm is pinned to vllm_c; the harness records source/operator hashes and actual RMSNorm/RoPE forwards/providers. Complete output storage bits must match between the CUDA reference and original; every arm must repeat exactly and meet its fused-call counts before eager ABBA timing (20 warmup calls, 20 calls/sample, three rounds), including model RoPE generation and per-forward table packing.

Source manifests and reproduction instructions and the eight-worker runner pin the settings. The eight-GPU r3 job is queued in MOVA2.0纯交付分区; full-model parity/performance is not yet established.

@yuweih205

Copy link
Copy Markdown
Contributor Author

Fresh eager measurements: bitwise reference parity with retained speedup

The H200 performance follow-up is now complete. The measured CUDA-reference numerical preset has zero output storage-byte mismatches versus the original eager chain, with a measurable transformer-forward saving.

Tested implementation: prepared source cce0fdfd9a3d810be67cda07eff537ef11cb99c5. The numerical preset patch is still outside the public PR head; the exact shared patch is retained for review.

Settings: MIN_TOKENS=1000000000000, NUMERICS=fast for original; MIN_TOKENS=0, NUMERICS=vllm_cuda_128 for exact fusion, using the VLLM_OMNI_FUSED_QK_NORM_ROPE_ prefix. The preset selects rms_norm_reduction="vllm_cuda_128" and enable_fp_fusion=False. Model calls use the prepared operator directly.

Scope: eager, random-weight, full-width shallow HunyuanVideo1.5 transformer, 16 heads × 128 dimensions, BF16, 4096 image/video tokens and 512 configured text tokens, B1/B2, seed 20261008, world/TP/SP size 1. Two joint/dual blocks, with text-refiner depth 1 and the additional condition streams recorded in the JSON.
These timings include the complete shallow transformer forward, model RoPE generation and per-forward table packing. Text encoding, sampling and VAE are outside this measurement.

Batch Original chain (ms) Bitwise preset (ms) Forward latency reduction Output storage bits
1 5.832320 5.540894 4.997% 0 byte mismatches
2 9.858630 9.311595 5.549% 0 byte mismatches

The original RMSNorm provider is pinned and checked as vllm_c. Each arm repeats exactly; exact versus original is checked before and after timing. Source/operator hashes, state/input hashes, real norm/RoPE dispatch and fused-call counts are recorded. Provider hooks and counter wrappers are removed during timing.

Timing: H200 141 GB, vLLM 0.29.0, Torch 2.13.0+cu130, CUDA 13.0, Triton 3.7.1; CUDA-event medians, 10 warmup forwards per arm, three ABBA rounds, 10 calls/sample, six samples/arm. These are the stated random-weight eager cases; they do not establish pretrained full image/video generation parity.

Raw records: B1, B2. See the reproduction procedure and all raw results. The fast arm and its separate timing/parity records are retained in those JSON files.

AI assistance: Codex ran and checked the GPU measurements and prepared this additive evidence note.

@vllm-omni-review-bot

Copy link
Copy Markdown
Omni ReviewBot attempt record

Review attempt ended as failed (failed; retrying strict/cursor/cursor-grok-4.6-high in 120s (try 2 of 3)).

@vllm-omni-review-bot

Copy link
Copy Markdown
Omni ReviewBot attempt record

Review attempt ended as failed (failed; retrying strict/cursor/cursor-grok-4.6-high in 600s (try 3 of 3)).

@vllm-omni-review-bot

Copy link
Copy Markdown
Omni ReviewBot attempt record

Review attempt ended as failed (failed; falling back to direct/cursor/auto).

Signed-off-by: HuangYuwei <yuweih205@gmail.com>
…e fused Q/K path

HunyuanVideo-1.5 passes `cuda_rms_norm=True` to the joint op: the fused path is
now bit-identical to vLLM RMSNorm + RoPE (identity rows for text) in eager mode
instead of differing by the Triton reduction order.

- `fused_joint_qkv_norm_rope` takes `cuda_rms_norm` like the single-stream op
  (BF16, head_dim = rotary_dim = 128, FMA contraction off) and drops
  `interleaved`: its half-split mode was never reachable in production.
- `pack_qk_norm_rope_table(cuda_rms_norm=True)` returns None outside that
  geometry, so consumers never fuse outside the bitwise contract.
- Shared tests: joint op bitwise against vLLM's CUDA RMSNorm, the Flux.2
  eager-chain test exact under `cuda_rms_norm`, compile and CUDA-graph
  coverage for both modes.

Signed-off-by: HuangYuwei <yuweih205@gmail.com>
…nels

One-warp programs of eight heads instead of four are 8-15% faster under
CUDA-graph replay on H200 across 26 joint and single-stream geometries
(320 to 16K tokens, 8 to 64 heads, fast and CUDA-matching modes) with
bitwise-identical output. The only slower case is 20 heads in the fast
mode (+2.6%), where eight heads pad the head count to 24 slots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

high priority high priority issue, needs to be done asap Kernel optimization Codes related to optimize kernel execution to improve hardware utilization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants