Skip to content

varlen backward hangs on SM90/H100 at head_dim=512 with torch 2.13/triton 3.7.1 (returns correctly on torch 2.11/triton 3.6) #298

Description

@dengoswei

Summary

On H100 (SM90), ffpa_attn_varlen_func(...).sum().backward() never returns for head_dim=512. The forward is fine and fast; only the backward hangs. It reproduces at every shape I tried, including one as small as total_q=128, num_heads=2, so it is not a size or tiling boundary.

The dense entry point ffpa_attn_func with an explicit TritonBackend(backward=True, enable_tma=True, persist_dkdv=True) works perfectly on the same machine, in the same process, at the same geometry — 29.2 ms fwd+bwd for 3x2048, 32q/4kv, D512, bf16, causal, which is ~8x faster than what we run today. So this is specifically the varlen path.

Environment

ffpa-attn 0.2.2 (latest on PyPI)
torch 2.13.0+cu130
triton 3.7.1
GPU NVIDIA H100 80GB HBM3, sm90
python 3.12

Your own test suite passes here unmodified: pytest tests/test_ffpa_bwd.py -k sm908 passed in 4.87 s. So this is not a broken install or an environment/version skew.

Minimal repro

import torch
from ffpa_attn import ffpa_attn_varlen_func

nseq, S, H, D = 1, 128, 2, 512          # smallest shape that reproduces
dt = torch.bfloat16
cu = torch.arange(0, (nseq + 1) * S, S, device="cuda", dtype=torch.int32)
q = torch.randn(nseq * S, H, D, device="cuda", dtype=dt, requires_grad=True)
k = torch.randn(nseq * S, H, D, device="cuda", dtype=dt, requires_grad=True)
v = torch.randn(nseq * S, H, D, device="cuda", dtype=dt, requires_grad=True)

o = ffpa_attn_varlen_func(q, k, v, cu, cu, S, S, causal=True)
torch.cuda.synchronize()                # returns; 6.79 s cold (compile), 0.07 s warm
o.sum().backward()
torch.cuda.synchronize()                # never returns

Shape sweep

Each shape in its own child process, killed at 120 s. head_dim=512, bf16, causal, H100.

shape forward backward
nseq=1 S=128 HQ=2 HKV=2 ok 6.79 s (cold compile) hang
nseq=1 S=128 HQ=32 HKV=4 ok 0.07 s hang
nseq=1 S=512 HQ=32 HKV=4 ok 0.07 s hang
nseq=1 S=2048 HQ=32 HKV=4 ok 0.08 s hang
nseq=2 S=2048 HQ=32 HKV=4 ok 0.09 s hang
nseq=3 S=2048 HQ=32 HKV=4 ok 0.08 s hang

Where it stops

Python stack, captured by a watchdog thread after 120 s (identical at every shape):

ffpa_attn/cute/__init__.py:919        _varlen_fwd_backward
  dq, dk, dv = torch.ops.ffpa_attn._varlen_bwd_cute(
ffpa_attn/cute/__init__.py:828        _varlen_bwd_custom
ffpa_attn/cute/_ffpa_bwd_sm90.py:447  _ffpa_attn_backward_sm90
  _call_with_tvm_ffi_current_stream(         # <- the dQ launch
ffpa_attn/cute/_utils.py:165          _call_with_tvm_ffi_current_stream
  return fn(*args)
nvidia_cutlass_dsl/python_packages/cutlass/cutlass_dsl/tvm_ffi_provider.py:629

Line 447 is the dQ launch, i.e. the dKdV launch at ~431 already returned. But since _call_with_tvm_ffi_current_stream does not synchronise (_utils.py:159-165), the launches are async and I cannot tell from this which of the two kernels is actually spinning — I have not yet run with CUDA_LAUNCH_BLOCKING=1 to disambiguate. Happy to, if useful.

GPU stays at 100% utilisation throughout.

What is ruled out

  • Not compilation. The first forward compiles in 6.79 s and every later one takes 0.07 s, so compilation is cached and costs seconds. The backward got 120 s at every shape, including that tiny one.
  • Not the compile cache. Reproduced with FLASH_ATTENTION_CUTE_DSL_CACHE_DIR on fast local disk (an earlier attempt on a network mount had its own unrelated problem; this run did not).
  • Not shape. See the sweep — the smallest shape hangs too.
  • Not a version skew. Your sm90 backward tests pass here, 8/8.
  • Not the call site, as far as I can tell: ffpa_attn_varlen_func exposes no backward_backend, and functional.py:278-284 requires forward_backend == backward_backend. Since the varlen entry dispatches straight to _ffpa_attn_varlen_cute, its backward is pinned to CuTeDSL with no way to select the Triton backward that the dense tests exercise. If there is a supported way to steer this, I would be glad to be corrected.

Possibly relevant: test coverage of this path

I went looking for an existing test to run before filing, and could not find one for this combination. Across all 9 files in tests/, exactly one test exercises varlen together with a gradient: tests/test_ffpa_cute_sm80.py::test_sm80_cutedsl_varlen_autograd_matches_sdpa. It differs from the failing case on four axes:

that test this report
path SM80 SM90
head_dim 320 512
causal no yes
heads 2q/2kv (MHA) 2q/2kv and 32q/4kv (GQA) — both hang

tests/test_ffpa_bwd.py has 29 backward tests and 0 mentions of varlen; all 10 of its head_dim=512 backward tests pass an explicit backward_backend=TritonBackend(...).

So the SM90 varlen backward looks uncovered rather than regressed — which would explain why this survived a release. Mentioning it only because it might help you decide where to look; apologies if I have missed a test elsewhere.

Context

This is relevant to #276, where it is stated that "Hopper works great currently for packed/padded training because its backward is cu_seqlens-aware and compiles once". That does not match what I measure on H100 with head_dim=512 — the forward is indeed fast and compiles once, but the backward does not return. It is possible the difference is head_dim (320 vs 512) or causal; I have not yet isolated which, and would run whichever variant is most useful to you.

We were evaluating FFPA for packed-sequence RL training with Gemma-4's 512-dim global-attention layers, where it benchmarks ~8x faster than our current backend. Very happy to run further diagnostics on H100 — this is a machine we have access to.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions