Skip to content

[perf] FA3 custom_op + functional RoPE: compile-traceable DistributedAttention - #1384

Open
rich7420 wants to merge 3 commits into
hao-ai-lab:mainfrom
rich7420:perf/fa3-custom-op-compile-trace
Open

[perf] FA3 custom_op + functional RoPE: compile-traceable DistributedAttention#1384
rich7420 wants to merge 3 commits into
hao-ai-lab:mainfrom
rich7420:perf/fa3-custom-op-compile-trace

Conversation

@rich7420

@rich7420 rich7420 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Purpose

Make DistributedAttention.forward compile-traceable so inductor can fuse
across the Wan T2V block forward. −9.28 % e2e on H100 sp=1 + FA3.

Update (rebased onto latest main). This PR originally removed
@torch.compiler.disable outright. Upstream has since landed
_maybe_compiler_disable (an env-gated wrapper — torch.compiler.disable by
default, opt-out via FASTVIDEO_DISABLE_ATTENTION_COMPILE=0). I've rebased
onto that mechanism rather than fight the new default: the PR no longer
touches the decorator. What remains are the two changes that make upstream's
opt-in path actually work and pay off — without them, setting
FASTVIDEO_DISABLE_ATTENTION_COMPILE=0 either fails to trace (bare FA3 is not
graphable) or silently skips fusion (in-place RoPE breaks the trace).

Reproduce the numbers below with FASTVIDEO_DISABLE_ATTENTION_COMPILE=0.

Changes

  • New attention/utils/flash_attn_3_compile.py — wrap flash_attn_3 as
    torch.library.custom_op + register_fake, mirroring the FA4 wrapper in
    flash_attn_cute.py. Inductor can now graph through FA3. The custom op has
    no autograd, so grad-requiring inputs (training) route to the raw FA3 func
    (its own backward); inference (no-grad) takes the compile-traceable op.
  • attention/backends/flash_attn.py — import flash_attn_func from the new
    wrapper instead of the bare flash_attn_interface symbol.
  • attention/layer.py — rewrite the in-place
    qkv[:bs*2] = _apply_rotary_emb(qkv[:bs*2], …) to a functional cat-based
    form in both DistributedAttention.forward and DistributedAttention_VSA.forward
    (matches LocalAttention.forward), so torch.compile can trace RoPE. The
    @_maybe_compiler_disable decorator from upstream is left in place.

sequence_model_parallel_all_to_all_4D already short-circuits on
world_size == 1, so the sp=1 path is fully compileable. sp > 1 paths were
not re-verified — recommend /test full on multi-GPU before merge.

Test Results

Measured with FASTVIDEO_DISABLE_ATTENTION_COMPILE=0 (attention traced into
the compile graph).

Perf — Wan T2V 1.3B, H100 sp=1 + FA3, 30 steps, 720×1280×77:

before after Δ
avg_generation_time_s 127.31 115.50 −11.82 s (−9.28 %)
individual times 127.30 / 127.33 115.40 / 115.60 within-run var 198 ms
denoising_stage 117.72 104.80 −12.92 s
GPU compute (HW-norm.) 372.7 s 341.5 s −8.39 %

nsys-ai diff confirms expected fusion:

  • self-attn FA3 fused as
    triton_red_fused__flash_attn_3_forward__to_copy_add_addmm_mean_mul_pow_rsqrt × 5 400
  • RoPE collapses into
    triton_poi_fused__to_copy_add_cat_mul_neg_slice_stack_unbind_… × 5 482
  • CFG concat (CatArrayBatchedCopy_vectorized) 5 754 → 354 launches
  • elementwise_kernel 19 455 → 8 655; unrolled_elementwise_kernel 11 486 → 686

Quality — VBench A/B on 5 fixed-seed prompts vs pre-PR baseline:

metric baseline this PR Δ
vbench.subject_consistency 0.9526 0.9524 −0.0002
vbench.background_consistency 0.9558 0.9572 +0.0013
vbench.aesthetic_quality 0.6050 0.6042 −0.0009
vbench.imaging_quality 0.6035 0.6024 −0.0010
vbench.temporal_flickering 0.9787 0.9786 −0.0000

All 5 dimensions within ±0.0013. Visually identical on side-by-side
inspection.

Outputs are algorithm-identical; small per-pixel deltas (avg-frame SSIM
0.946) come from inductor fusion changing fp32 accumulator ordering inside
the new triton kernels, amplified by 30-step iterative denoising. Perceptual
quality (VBench, eye test) preserved.

Checklist

  • pre-commit run --all-files
  • Perf benchmark on H100 sp=1 + FA3 (FASTVIDEO_DISABLE_ATTENTION_COMPILE=0)
  • VBench A/B (5 prompts × 5 metrics)
  • Rebased onto upstream _maybe_compiler_disable env-gate
  • sp > 1 / VSA regression — recommend /test full before merge

@mergify mergify Bot added type: perf Performance improvement scope: attention Attention backends (VSA, STA, Flash, etc.) labels May 22, 2026
@mergify

mergify Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews and 🤖 CI

Protection Waiting on
🔴 PR merge requirements 👀 reviews and 🤖 CI

🔴 PR merge requirements

Waiting for

  • #approved-reviews-by>=1
  • check-success=full-suite-passed
This rule is failing.
  • #approved-reviews-by>=1
  • check-success=full-suite-passed
  • check-success=fastcheck-passed
  • check-success~=pre-commit
  • title~=(?i)^\[(feat|feature|bugfix|fix|refactor|perf|ci|doc|docs|misc|chore|kernel|new.?model|skill|skills|infra)\]

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request enables torch.compile support for attention layers by refactoring in-place RoPE assignments into functional operations and removing @torch.compiler.disable decorators. It also introduces a new utility module that wraps Flash Attention 3 using torch.library.custom_op to ensure traceability. Feedback was provided regarding the Flash Attention 3 wrapper, noting that it currently lacks autograd registration and discards the softmax LSE tensor, which limits its functionality to inference-only when used with torch.compile.

Comment on lines +23 to +26
out = _raw_flash_attn_3_func(q, k, v, softmax_scale=softmax_scale, causal=causal)
if isinstance(out, tuple):
out = out[0]
return out

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.

medium

The current implementation of _flash_attn_3_forward discards the softmax_lse tensor returned by flash_attn_func and does not register an autograd implementation for the custom op. This effectively makes this wrapper inference-only when used with torch.compile. If training or gradient-based optimization is attempted with this op under torch.compile, it will fail or produce incorrect results because the backward pass is not defined for the custom op.

Consider adding a comment documenting this limitation or implementing register_autograd if training support is intended.

@rich7420
rich7420 force-pushed the perf/fa3-custom-op-compile-trace branch from 892081e to 2cf6935 Compare May 22, 2026 16:32
@SolitaryThinker

Copy link
Copy Markdown
Collaborator

Hi @rich7420 — automated review from Gob, one of @SolitaryThinker's AI reviewers. Findings aren't all human-verified; ping @SolitaryThinker if anything looks off.

TL;DR

The FA3 custom op has a fake implementation and a non-mutating forward schema, but it is wired into the general FlashAttention backend without an autograd formula, so FA3 training/gradient use will fail. The PR also removes the distributed-attention compile guard while the SP all-to-all path still reaches unwrapped distributed collectives, and no compile/custom-op regression test was added.

Verdict: Changes requested

  • S0 (blockers): 0
  • S1 (must-fix): 2
  • S2 (should-fix; surfaced if persistent or important): 1
  • S3 (discussion): not shown here; see review.md

Findings (formatted for upload)

[S1] FA3 custom op is selected for training but has no autograd formula

What: flash_attn.py selects the new FA3 wrapper whenever flash_attn_interface imports, and FlashAttentionImpl.forward then calls that wrapper for the normal no-mask FlashAttention path. The wrapper registers only @torch.library.custom_op plus register_fake; its own module docstring says it does not register autograd and discards the FA3 softmax LSE needed for backward.

Why it matters: FastVideo uses this backend from both inference and training stacks on CUDA. With requires-grad Q/K/V, a torch.library.custom_op without register_autograd will fail backward instead of preserving the old FA3 autograd behavior.

Suggested fix: Either implement FA3 register_autograd like flash_attn_cute.py does, saving Q/K/V/out/LSE and calling the FA3 backward kernel, or gate this wrapper to inference/no-grad only and keep a training-safe fallback. Add an explicit backward/opcheck test for whichever behavior is intended.

Evidence: fastvideo/attention/backends/flash_attn.py:19, fastvideo/attention/backends/flash_attn.py:227, fastvideo/attention/utils/flash_attn_3_compile.py:4


[S1] DistributedAttention compile guard was removed before SP collectives became compile-safe

What: DistributedAttention.forward and DistributedAttention_VSA.forward are no longer compiler-disabled, but both still call sequence_model_parallel_all_to_all_4D. For SP world sizes above 1, that path reaches DistributedAutograd.AllToAll4D.apply(..., self.device_group, ...) and then calls dist.all_to_all_single inside the autograd Function; there is no torch.library custom op, fake/meta implementation, or compile smoke proving this boundary traces.

Why it matters: The previous disable made the distributed wrapper an explicit eager boundary. Removing it can turn that into hidden graph breaks or compile failures around NCCL collectives, so users may think distributed attention is compiled when it is not, or see runtime failures only under SP/VSA configurations.

Suggested fix: Keep the compile disable around the distributed wrapper, or at least around the SP communication helper, until the collectives are wrapped in a compile-safe custom op with fake/autograd support. If the path is actually supported today, add a rank-2 torch.compile(..., fullgraph=True) smoke covering DistributedAttention and DistributedAttention_VSA through both all-to-all calls.

Evidence: fastvideo/attention/layer.py:99, fastvideo/attention/layer.py:195, fastvideo/distributed/device_communicators/base_device_communicator.py:155


[S2] No compile/custom-op regression test was added

What: The PR changes only flash_attn.py, layer.py, and the new FA3 wrapper. Recursive test search found only the existing FA4 custom-op test; there is no FA3 parity/opcheck/autograd test and no distributed-attention compile smoke.

Why it matters: This PR is specifically changing torch.compile boundaries. Missing fake/autograd metadata and hidden graph breaks are exactly the failures that will slip through without a focused compile test.

Suggested fix: Add CUDA tests for FA3 wrapper forward parity against raw FA3 for fp16/bf16 and causal/non-causal, run torch.library.opcheck, assert the intended backward behavior, and add at least an SP=1 torch.compile(fullgraph=True) attention smoke. Add an SP>1 distributed compile smoke if CI has multi-GPU coverage.

Evidence: fastvideo/attention/utils/flash_attn_3_compile.py:20


— Gob (@SolitaryThinker's AI reviewer). Full review (including S3 items) is archived locally.

…9.3% e2e on H100 sp=1)

PR hao-ai-lab#684 ("[Feature] Optionally enable torch compile") added
@torch.compiler.disable on DistributedAttention.forward / _VSA.forward so
torch.compile would not crash on the bare flash_attn_3 call + in-place RoPE
slice assignment. Side effect: every attention call became a graph break,
fragmenting the per-block compiled region and leaving 84% of denoising
direct_copy_kernel_cuda launches in eager mode.

Three changes that together unlock the compile region for sp=1:

1. fastvideo/attention/utils/flash_attn_3_compile.py (new):
   Register flash_attn_3 as torch.library.custom_op + register_fake — mirrors
   the pattern flash_attn_cute.py already uses for FA4. Inductor now treats
   FA3 as an opaque-but-known boundary and can fuse adjacent ops into a
   single triton kernel.

2. fastvideo/attention/backends/flash_attn.py:
   Import flash_attn_func from the new wrapper instead of bare
   flash_attn_interface.flash_attn_func.

3. fastvideo/attention/layer.py:
   - Drop @torch.compiler.disable from DistributedAttention.forward and
     DistributedAttention_VSA.forward. With FA3 now a custom_op, and
     sequence_model_parallel_all_to_all_4D already short-circuiting on
     world_size==1, the disable is no longer required.
   - Replace
       qkv[:batch_size * 2] = _apply_rotary_emb(qkv[:batch_size * 2], ...)
     with the functional form
       qk_rope = _apply_rotary_emb(qkv[:batch_size * 2], ...)
       qkv = torch.cat([qk_rope, qkv[batch_size * 2:]], dim=0)
     so torch.compile can trace it (same pattern LocalAttention.forward
     already uses).

Wan T2V 1.3B inference, H100 sp=1+FA3, 30 steps, 720x1280x77:
  avg_generation_time_s: 127.314s -> 115.499s  (-11.815s, -9.28%)
  denoising_stage:       117.72s  -> 104.80s   (-12.92s, -11.0%)
  individual_times:      [127.30, 127.33] -> [115.40, 115.60]
                                            (198ms variance, well below the
                                             ~2% Modal H100 jitter floor)
  GPU compute_only_ms:   372,726ms -> 335,984ms (-36,743ms)
  Hardware-normalized:   -8.76% (calibrated against unchanged FA3 device_kernel
                                 timing on different Modal H100 cards)

nsys-ai diff confirms the expected fusion landed:
  - New: triton_red_fused__flash_attn_3_forward__to_copy_add_addmm_mean_mul_
         pow_rsqr * 5,400 instances (self-attn FA3 fused with QK RMSNorm).
  - New: triton_poi_fused__to_copy_add_cat_mul_neg_slice_stack_unbind_unsqueeze_
         view * 5,482 (RoPE as a single kernel).
  - CatArrayBatchedCopy_vectorized: 5,400 -> 0 (CFG concat folded into fusion).
  - elementwise_kernel: 19,455 -> 8,655; unrolled_elementwise_kernel:
    11,486 -> 686. Many small eager kernels collapsed.
  - Eliminated: triton_red_fused__to_copy_add_addmm_mul_native_layer_norm_view_0
                / _3 (10k+ instances combined), refused into wider kernels.

Quality verified: VBench 5-dimension A/B on 5 fixed-seed prompts vs the
pre-PR baseline, all metrics within +/-0.0013:
  subject_consistency:    0.9526 -> 0.9524  (-0.0002)
  background_consistency: 0.9558 -> 0.9572  (+0.0013)
  aesthetic_quality:      0.6050 -> 0.6042  (-0.0009)
  imaging_quality:        0.6035 -> 0.6024  (-0.0010)
  temporal_flickering:    0.9787 -> 0.9786  (-0.0000)
Outputs are algorithm-identical; small per-pixel deltas (avg-frame SSIM
0.946) come from inductor fusion changing fp32 accumulator ordering inside
the new triton kernels, amplified by 30-step iterative denoising.

sp>1 path unchanged in behavior — sequence_model_parallel_all_to_all_4D
short-circuits on world_size==1 only; for >1 the dist collective runs as
before, just inside a (possibly broken) compile graph. Validated only at
sp=1 on this branch; sp>1 should be regression-tested before relying on
fusion in distributed runs.
@rich7420
rich7420 force-pushed the perf/fa3-custom-op-compile-trace branch 2 times, most recently from d482434 to f4e5b8d Compare June 5, 2026 16:41
The custom_op wrapper registers no autograd, so a backward through it fails.
Since flash_attn.py wires this wrapper in for both inference and training,
training with the FA3 backend would break (raw FA3 supports backward). Route
grad-requiring inputs to the raw FA3 function and keep the no-grad inference
path on the compile-traceable custom op.
@rich7420
rich7420 force-pushed the perf/fa3-custom-op-compile-trace branch from f4e5b8d to 2360b99 Compare June 5, 2026 17:42

@SolitaryThinker SolitaryThinker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @rich7420 — automated review from Gob, one of @SolitaryThinker's AI reviewers. Findings aren't all human-verified; ping @SolitaryThinker if anything looks off.

Summary

Reviewing at head 2360b997 (open, mergeable, fastcheck green; kernel/attention scope). The real perf win (−9.28% e2e, H100 sp=1+FA3) comes from layer.py: dropping @torch.compiler.disable and rewriting the in-place RoPE slice-assign to a functional cat. That part is sound and output-equivalent. No blockers — outputs are correct and the speedup is real. Two things to resolve before merge.

Verdict: COMMENT

No in-scope blockers. The perf win is legitimate; the concerns are the redundant new module and the unverified sp>1 path.

Major concerns

MAJORfastvideo/attention/utils/flash_attn_3_compile.py duplicates the on-main FA2/FA3 custom-op wrapper from #1373; the FA3 inference path now double-wraps.
flash_attn_func_compilable + torch.ops.fastvideo._flash_attn_default_forward already live on main (backends/flash_attn.py:49-89, landed in #1373 2f3ca8aa, which is an ancestor of this head). That wrapper already (a) wraps the FA2/FA3 default flash_attn_func in a torch.library.custom_op, (b) registers a fake, and (c) has the same autograd carve-out routing grad inputs to the raw func. The only live hot-path call is FlashAttentionImpl.forward:307 → flash_attn_func_compilablenot the module-level flash_attn_func this PR redirects, which flash_attn.py:37 captures as _fa_default and re-wraps. So at runtime FA3 inference becomes flash_attn_func_compilable → _flash_attn_default_forward (custom op) → _fa_default → _flash_attn_3_forward (new custom op) → raw FA3: a custom op nested inside a custom op. Dynamo treats the outer op as opaque and never traces into the inner one, so the new op's traceability/fake are dead under every current call path (grep confirms no other live importer of the backend flash_attn_func). The body's "Inductor can now graph through FA3" already landed in #1373.
Suggested fix: drop flash_attn_3_compile.py + the flash_attn.py:14-19 import change, keep only the layer.py disable removal (the actual perf source). If #1373's wrapper is somehow insufficient for FA3, please document why in the PR body — the diff doesn't show a reason.

MAJOR — sp>1 / VSA compile path is unverified after removing @torch.compiler.disable (layer.py:59,151).
With the guard gone, at sp>1 the compiled region now spans sequence_model_parallel_all_to_all_4D → DistributedAutograd.AllToAll4D.apply → dist.all_to_all_single (NCCL inside an autograd.Function). At sp=1 the collective short-circuits (base_device_communicator.py:142 returns input unchanged), so the claimed path is clean — but sp>1 isn't tested. Mitigating: compile is applied without fullgraph=True by default (composed_pipeline_base.py:115,153), so dynamo graph-breaks gracefully at the collective — a perf non-improvement, not a correctness regression. Real risk only if a user sets fullgraph=True (then sp>1 hard-errors at compile) or assumes sp>1 is fused when it silently isn't. You already flag this ("sp > 1 paths were not re-verified").
Suggested fix: run /test full on multi-GPU before merge; if sp>1 + fullgraph is meant to work, add a rank-2 torch.compile(..., fullgraph=True) smoke over DistributedAttention/_VSA. Otherwise note in the body that sp>1 falls back to a graph break.

Minor

MINOR — if the new module is kept despite the above, the fastvideo::_flash_attn_3_forward op + its register_fake have no test (#1373's test_flash_attn_default_custom_op.py covers _flash_attn_default_forward only). Removing the module (preferred) moots this; otherwise mirror that test (forward parity vs raw FA3 fp16/bf16 × causal + torch.library.opcheck). The fake itself is correct.

Test plan

Solid evidence for the claimed path: H100 sp=1+FA3 before/after (127.31→115.50 s), nsys-ai diff showing the expected fusion, and a 5-prompt VBench A/B (all dims within ±0.0013). The reported avg-frame SSIM 0.946 would pass the repo's Wan T2V gate (tests/ssim/test_wan_t2v_similarity.py:94, threshold 0.93) — worth a /test ssim to confirm in CI. Gaps: no sp>1/VSA regression and no committed test/benchmark for the new op. Recommend /test full + /test ssim before merge.

(Note: the autograd-fallback you added in 2360b997 resolves the training-breakage flagged in the earlier review on this PR — that item is fixed at this head.)

— Gob (@SolitaryThinker's AI reviewer).

@rich7420

rich7420 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @SolitaryThinker 's AI Gob— verified on a real H100, quick notes:

  • FA3 autograd (S1): false positive. flash_attn_func (flash_attn_3_compile.py:55-59) routes any requires_grad input to raw FA3; the custom op only handles the no-grad inference path. H100 check: backward runs and Q/K/V grads are bit-exact vs raw FA3.
  • Distributed compile guard (S1): at SP=1 all_to_all_4D early-returns (world_size==1), so no collective is traced and torch.compile(fullgraph=True) on the wrapper passes. SP>1 + compile is out of scope for this PR.
  • Test (S2): fair — will add an FA3 opcheck/parity + SP=1 compile smoke mirroring the FA4 test.

@mergify

mergify Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase:

git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts label Jun 23, 2026
…compile-trace

# Conflicts:
#	fastvideo/attention/layer.py
@rich7420 rich7420 changed the title [perf] FA3 custom_op + remove DistributedAttention compile disable [perf] FA3 custom_op + functional RoPE: compile-traceable DistributedAttention Jul 10, 2026
@mergify mergify Bot removed the needs-rebase PR has merge conflicts label Jul 10, 2026
@mergify

mergify Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

This PR has merge conflicts with the base branch. Please rebase:

git fetch origin main
git rebase origin/main
# Resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts label Jul 12, 2026
@SolitaryThinker

Copy link
Copy Markdown
Collaborator

Rebase triage: a pure rebase onto main is no longer possible — main has since absorbed this PR's core contribution. #1373 registered the FA2/FA3 flash_attn_func as a torch.library custom op (now living in fastvideo/attention/utils/flash_attn_default.py, including register_fake and the same requires-grad carve-out this PR's flash_attn_3_compile.py implements), and #1388 added the real backward. After a rebase, this PR's flash_attn.py hunk has no target region left and flash_attn_3_compile.py would be a duplicate FA3 custom-op registration that nothing imports.

What's still novel here: the two functional-RoPE hunks in fastvideo/attention/layer.py — replacing the in-place qkv[:batch_size*2] = _apply_rotary_emb(...) with the functional torch.cat form in DistributedAttention.forward and DistributedAttention_VSA.forward (compile/trace-friendly).

Two ways forward:

  1. Rebase and reduce this PR to just the functional-RoPE change (retitle accordingly) — happy to do that push for you if you'd like, just say the word;
  2. Close as superseded by [perf]: register FA2/FA3 default flash_attn_func as a torch.library custom op #1373/[perf]: register a real backward for FA2 default + masked/varlen custom ops (training-under-compile) #1388 and submit the RoPE change separately.

Leaving the branch untouched until you weigh in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase PR has merge conflicts scope: attention Attention backends (VSA, STA, Flash, etc.) type: perf Performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants