Skip to content

[PyTorch] Restore FlashAttention 2 head dim support on sm103 - #3360

Closed
kalectory wants to merge 1 commit into
NVIDIA:mainfrom
kalectory:fix-sm103-fa2-head-dim
Closed

kalectory wants to merge 1 commit into
NVIDIA:mainfrom
kalectory:fix-sm103-fa2-head-dim

Conversation

@kalectory

@kalectory kalectory commented Aug 12, 2026 •

Copy link
Copy Markdown

Description

Restore the FlashAttention 2 head-dimension behavior from #2836. #2629 preserved the new padded Q/V head-dimension handling but accidentally reintroduced the exact compute-capability allowlist that #2836 removed.

On B300 (sm103), that allowlist rejects a padded head dimension of 256 and falls back to quadratic unfused attention.

image

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Testing

Status quo

  • Layout: one 8xB300 node with four B300s assigned to the Megatron trainer (TP=4) and four to rollout inference. Qwen3.6-27B used BF16 THD attention, head dim 256, Transformer Engine 2.11.0, and FlashAttention 2.8.3.
  • Our production workload requested FlashAttention, but TE's exact compute-capability allowlist omitted B300 (sm103) and silently selected UnfusedDotProductAttention instead.
  • XID 1043331 replayed an unsliced 65,332-token datum with a 65,536-token microbatch budget. The quadratic unfused softmax tried to allocate 202.75 GiB and raised torch.OutOfMemoryError during forward.

After the fix

  • Used the same image, package versions, TP4 trainer layout, model, BF16 THD configuration, head dim, 65,536-token budget, and exact 65,332-token datum. Only this selector gate was patched.
  • The full 64k training update completed without OOM: forward_backward took 413.2s, the Adam optimizer step took 9.6s, and grad norm was finite at 1776.48. This validates FlashAttention 2 and 64k context on the four-B300 trainer configuration.
  • Python compilation and diff checks pass.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Signed-off-by: Neil Kale <263453039+kalectory@users.noreply.github.com>
@kalectory
kalectory requested a review from cyanguwa as a code owner August 12, 2026 22:20
@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Aug 12, 2026
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR restores FlashAttention 2 eligibility for padded Q/V head dimensions up to 256 on sm103 by removing a stale architecture allowlist.

  • Retains validation that the padded head dimension is divisible by 8 and no greater than 256.
  • Updates the backend-selection diagnostic to reflect the revised eligibility condition.

Confidence Score: 5/5

The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issues identified.

The change narrowly removes an obsolete architecture filter while retaining FlashAttention 2’s padded head-dimension bounds, and the reviewed selection and dispatch paths reveal no established regression.

Important Files Changed

Filename Overview
transformer_engine/pytorch/attention/dot_product_attention/utils.py Removes the stale architecture-specific FA2 head-dimension restriction while preserving the general padded-dimension limits and aligning the diagnostic message.

Reviews (1): Last reviewed commit: "Restore FlashAttention 2 head dim suppor..." | Re-trigger Greptile

@cyanguwa

Copy link
Copy Markdown
Collaborator

/te-ci L0

erictang000 added a commit to NovaSky-AI/SkyRL that referenced this pull request Aug 17, 2026
applying NVIDIA/TransformerEngine#3360 to TE
2.16

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes attention backend selection at Megatron init via `exec` on TE
internals; mitigated by SM guards, literal 2.16.0 source matching, and
idempotent no-ops on unaffected GPUs, but wrong TE versions or
arch-specific FA2 issues could still affect training memory or numerics.
> 
> **Overview**
> Backports **NVIDIA/TransformerEngine#3360** for the pinned
**transformer-engine 2.16.0** so Megatron training can select
**FlashAttention 2** for **head_dim 256** (e.g. Gemma 2/3) on GPUs
outside TE’s SM allowlist (notably **sm103** B300/GB300, also
sm86/sm89).
> 
> A runtime patch **recompiles and rebinds** TE’s
`get_attention_backend` to drop the erroneous `head_dim > 192` +
compute-capability gate, keeping only FA2’s real limits (`<= 256`, `% 8
== 0`). It **no-ops** on sm80/90/100/120, when TE isn’t importable, or
when TE source no longer matches 2.16.0, and clears TE’s memoized
`_attention_backends` after apply.
> 
> **Megatron only:** `patch_fa2_head_dim_allowlist()` runs in
`make_megatron_module()` before `provide_distributed_model()` (policy
and ref workers). A **verify script** and README document repro and
numerics vs unfused attention.
> 
> Remove this patch when the TE pin includes upstream #3360.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1d9d8c9. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
erictang000 added a commit to erictang000/SkyRL that referenced this pull request Oct 1, 2026
Moves the TE pin from 2.16.0 to 2.19.0 across the `megatron` extra and
`override-dependencies`.

Packaging:

* `transformer-engine` and `transformer-engine-cu13` move off the `astral-cu130`
  index, which stops at 2.16.0, onto PyPI -- which publishes both prebuilt at
  2.19.0 (the meta as `py3-none-any`, carrying the whole pure-Python
  `transformer_engine/pytorch/` tree; the core as manylinux_2_28 wheels for
  x86_64 and aarch64). The core pin loses its `+cu.13.0` local segment, which
  was an Astral-index artifact; TE-torch's own metadata asks for a bare
  `transformer_engine_cu13==2.19.0`, so the two still agree without an override.
* `transformer-engine-torch` has no prebuilt wheel for torch 2.13 anywhere
  (PyPI is sdist-only), so it keeps pointing at self-built wheels, now under the
  `cu13torch2.13-te2.19` tag on NovaSky-AI/skyrl-wheels: 3 interpreters
  (cp312/313/314) x 2 arches, GLIBC_2.34 floor, same as the existing release.

The FA2 head_dim backport is RETARGETED, not deleted:

TE's `head_dim > 192` compute-capability allowlist is still present in 2.19.0.
NVIDIA/TransformerEngine#3360, which removes it, is open and unmerged. What
changed is the spelling: 2.16.x expresses the gate against `head_dim_qk`, and
2.17.0 onward against `fa2_padded_head_dim = max(head_dim_qk, head_dim_v)`. The
clause is otherwise character-identical.

`patch_fa2_head_dim.py` matched only the 2.16.0 text and was written to no-op on
anything else, so under a naive bump it would have silently stopped applying and
quietly dropped Gemma-style head_dim=256 to unfused attention on every arch
outside sm80/90/100/120. It now matches both spellings, and when it matches
neither it reports whether an allowlist is still present in the source rather
than implying it was fixed upstream.

Confirmed against the published meta wheels: the gate is present in 2.16.0,
2.16.1, 2.17.0, 2.17.1, 2.18.0 and 2.19.0 -- as `head_dim_qk` in the 2.16 line
and `fa2_padded_head_dim` from 2.17.0 on. Verified on an installed 2.19.0 that
the retargeted patch fires: gate present before, absent after, idempotent.

Also refreshes the stale TE-gate comments in `pyproject.toml`, all re-read from
2.19.0's source: the flash-attn bound went `max_version = 2.8.3` to `2.8.4`
exclusive (so the `flash-attn==2.8.3` pin is held by the combined FA2+FA4 wheel
requirement, not by TE), FA4 is now allowed under context parallelism for
cp_comm_type p2p/all_gather/a2a, and TE's own FA4 arch gate now excludes
`< sm90`, so sm86/sm87/sm89 no longer need SKYRL_DISABLE_FA4.

Verified on torch 2.13.0+cu130 / sm89: `te.Linear` and `te.TransformerLayer`
fwd+bwd pass, megatron-core at the pinned rev imports
`megatron.core.extensions.transformer_engine` cleanly, and both fake-INT4 QAT
tripwires still hold (`TEGroupedLinear._get_weight_tensors` exists and
`GroupedLinear.forward` still calls it).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
erictang000 added a commit to NovaSky-AI/SkyRL that referenced this pull request Oct 2, 2026
Bumps the `transformer-engine` pin from 2.16.0 to 2.19.0.

## Packaging

- `transformer-engine` and `transformer-engine-cu13` move off the
`astral-cu130` index (it stops at 2.16.0) onto PyPI, which publishes
both prebuilt at 2.19.0: the meta as `py3-none-any` (it carries the
whole pure-Python `transformer_engine/pytorch/` tree), the core as
`manylinux_2_28` wheels for x86_64 and aarch64.
- The core pin loses its `+cu.13.0` local segment, which was an
Astral-index artifact. `transformer-engine-torch`'s own metadata asks
for a bare `transformer_engine_cu13==2.19.0`, so the two still agree
without an override.
- `transformer-engine-torch` has no prebuilt wheel for torch 2.13
anywhere (PyPI only has an sdist). The new wheels are published at
**[`cu13torch2.13-te2.19`](https://github.com/NovaSky-AI/skyrl-wheels/releases/tag/cu13torch2.13-te2.19)**
on `NovaSky-AI/skyrl-wheels`: cp312/313/314 × x86_64/aarch64, GLIBC_2.34
floor (same as `cu13torch2.13`). Both arches were built by one script
using pip-wheel CUDA 13.0.88, with no system CUDA. aarch64 was built on
an Anyscale `m7g.12xlarge` job.
- `uv.lock` is regenerated. Exactly three packages changed:
`transformer-engine` 2.16.0 → 2.19.0, `transformer-engine-cu13`
2.16.0+cu.13.0 → 2.19.0, `transformer-engine-torch` 2.16.0 → 2.19.0. No
packages were added or removed. TE 2.19's new
`nvidia-cudnn-frontend>=1.28.0` requirement is already locked at 1.30.0.

## FA2 `head_dim` backport: retargeted, not deleted

TE's `head_dim > 192` compute-capability allowlist is **still present in
2.19.0**.
[NVIDIA/TransformerEngine#3360](NVIDIA/TransformerEngine#3360),
which removes it, is still open. What changed is the spelling:

| release | gate |
|---|---|
| 2.16.0, 2.16.1 | `head_dim_qk > 192 and device_compute_capability not
in (...)` |
| 2.17.0 – 2.19.0 | `fa2_padded_head_dim > 192 and
device_compute_capability not in (...)` |

(#2836 removed the `head_dim_qk` form, and it came back as
`fa2_padded_head_dim` before v2.17 shipped.)

`patch_fa2_head_dim.py` only matched the 2.16.0 text and was written to
quietly do nothing on anything else. Under a plain version bump it would
have stopped applying without any error, sending `head_dim=256` (Gemma
2/3) to unfused attention on every arch outside sm80/90/100/120. It now
matches both spellings. If it matches neither, it logs a warning that
says whether an allowlist is still in the source, instead of implying
upstream fixed it. Verified on an installed 2.19.0: gate present before,
absent after, and calling the patch again is a no-op.
`patches/te/README.md` now records the history and how to check for
removal at the source level.

## Other comment refreshes (re-read from 2.19.0 source)

- The flash-attn bound is now `version < max_version = 2.8.4` (it was
`2.8.3`). The `flash-attn==2.8.3` pin is now held by the combined
FA2+FA4 wheel requirement, not by TE.
- FA4 is now allowed under context parallelism for `cp_comm_type`
p2p/all_gather/a2a.
- TE's own FA4 arch gate now excludes `< sm90`, so sm86/sm87/sm89 no
longer need `SKYRL_DISABLE_FA4=1`.

## NVRTC regression: TE now pinned to the pip NVRTC

TE 2.19 fails MXFP8 RMSNorm on any machine whose `/usr/local/cuda` is
CUDA 12.x: `NVRTC_ERROR_COMPILATION` in `rmsnorm_fwd_kernel.cu`, with
syntax errors in `cuda_fp8.hpp`. TE's loader prefers the system NVRTC
(`NVRTC_HOME` / `CUDA_HOME` / `/usr/local/cuda`) over the pip
`nvidia-cuda-nvrtc` wheel. In 2.19 it also sets `NVTE_CUDA_INCLUDE_DIR`
to the pip `nvidia/cu13` headers, so NVRTC 12.9 ends up compiling
against CUDA 13 headers. **This is a regression from 2.16.** 2.16 uses
the same loader order but leaves the include dir unset, so system NVRTC
and system headers still match. The same standalone repro (a
`te.LayerNormLinear` RMSNorm forward under MXFP8) passes on 2.16.0 and
fails on 2.19.0.

`patches/te/pin_nvrtc.py` sets `NVRTC_HOME` to the pip
`nvidia/cu{major}` from `skyrl/backends/skyrl_train/__init__.py`, which
runs before every SkyRL TE/Megatron import. It takes 0.3 ms and doesn't
import torch. It does nothing when the user sets
`NVTE_CUDA_INCLUDE_DIR`, when `NVRTC_HOME` already holds a `libnvrtc`,
or when TE is already imported. A tripwire test fails once TE starts
preferring its pip NVRTC, which is when the patch can be deleted.

## Also in this PR

- **Merged upstream `main`.** The old base was missing
`patches/vllm/patch_model_runner_registry.py` (restored by #2363) while
`weight_receivers.py` imported it. Every MXFP8 weight sync failed with
`ModuleNotFoundError`.
- **vLLM 0.30 FP8 KV scale hooks** (independent of TE; fixes
`qwen3.5-0.8b-dense_mxfp8_fp8kv`, which generated NaN on `main` too).
Both post-boot safety nets for the serialized-FP8 KV/attention scales
were silently disabled:
- **Runner registry:** it only wrapped the V1
`GPUModelRunner.load_model`. vLLM 0.30 defaults to Model Runner V2, so
`current_model_runner()` returned `None` and the post-sync normalization
returned early. It now wraps both runners.
- **Post-wake patch:** it targeted
`GPUModelRunner.post_kv_cache_wake_up`, which 0.30 removed. It now wraps
`Worker.wake_up`. `skyrl_wake_for_weight_sync` wakes the allocator
directly, so it calls the same helper.
- vLLM-marked tests pin both hooks to the installed vLLM, so a future
rename fails loudly.
- **GPU test conftest:** sets `RAY_worker_register_timeout_seconds=600`
for raylets the tests start; it does nothing when attaching to an
existing cluster. Each worker boots through `uv run --isolated`, and the
35B row's 8 workers starting at once took longer than Ray's 60 s
default, so the actor became unschedulable.
- **`.agents/docs/dependency_bumps.md`:** the required test matrix for
TE bumps, which is all Megatron GPU CI labels plus the manual B200 MXFP8
rows.

## Verification

On torch 2.13.0+cu130 / L4 (sm89), x86_64 cp312:
- `te.Linear` and `te.TransformerLayer` fwd+bwd pass with finite
outputs.
- megatron-core at the pinned rev `d476c21b` imports
`megatron.core.extensions.transformer_engine`,
`transformer_engine_spec_provider`, `transformer.attention` and
`gpt_layer_specs` cleanly.
- Both fake-INT4 QAT tripwires still hold:
`TEGroupedLinear._get_weight_tensors` exists and `GroupedLinear.forward`
still calls it.
- The MXFP8 surfaces used by `weight_sync/fp8/quantize.py` are unchanged
(`MXFP8Quantizer(fp8_dtype=, rowwise=, columnwise=)`, `tex.DType`).

For all six wheels, a script checked the tag, the `.so` architecture,
the layout, the glibc floor and the `cu13==2.19.0` pin. `pre-commit run
--all-files` passes.

On 8x B200 (sm100), `pytest -m b200 -k mxfp8
tests/backends/skyrl_train/gpu/gpu_ci/megatron/test_megatron_models.py`:

| row | before | after |
|---|---|---|
| `qwen3.5-0.8b-dense_mxfp8` | ❌ NVRTC compile error | ✅ |
| `qwen3.5-0.8b-dense_mxfp8_fp8kv` | ❌ NaN logprobs (also on `main`) | ✅
|
| `qwen3.5-35b-a3b_b200_tp1_ep8_mxfp8` | ❌ Ray workers fail to register
(also on `main`) | ✅ |

- All 3 rows pass with an explicit `NVRTC_HOME`, before the pin patch
and the wake-hook fix landed.
- Both 0.8B rows pass on the pushed code with `NVRTC_HOME` unset, so
they rely on the pin patch alone. The log contains no `Run-time NVRTC
version: 12` line.
- The 35B row wasn't rerun after those last two changes. The 0.8B rows
exercise both.

New CPU tests (`tests/backends/skyrl_train/patches/{te,vllm}/`,
`test_vllm_compat_fp8_kv_scales.py`) pass under both the `skyrl-train`
and `fsdp`/`megatron` extras.

**Not yet run:** the full CPU pytest suites, the labeled GPU CI
(`run_megatron_gpu_ci`, `run_megatron_gpu_ci_models`,
`run_h100_gpu_ci`), and any forward pass on aarch64 (the build host was
CPU-only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Eric Tang <erictang000@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kalectory kalectory closed this Oct 5, 2026
@0z5a

0z5a commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@kalectory @cyanguwa This regression is still on main (eb3c860a). utils.py:1045 still allows FA2 for a padded head_dim > 192 only on ((8, 0), (9, 0), (10, 0), (12, 0)), so sm103 (B300 / GB300) still falls back to fused or unfused attention for head dims in (192, 256]. This PR was approved but closed without merging. Was that intentional, or could it be reopened?

One thing to consider before dropping the clause entirely. flash-attn's hdim256 backward picks its kernel by opt-in shared memory per block (run_mha_bwd_hdim256 in csrc/flash_attn/src/flash_bwd_launch_template.h). Below 144 KB, in the branch commented "sm86 and sm89, max smem is 99 KB. Only works without dropout", it launches no kernel at all when dropout is enabled. The mha_bwd check that used to reject "head dim > 192 with dropout" on those GPUs was removed in Dao-AILab/flash-attention@bbb21d6, and neither v2.8.3 nor main has a replacement. Removing TE's allowlist would therefore send sm86/sm89 training with dropout down that path. This comes from reading the source; I have not run it on an sm86/sm89 GPU.

A narrower fix is to add (10, 3) to the tuple. sm103 has the same 227 KB of opt-in shared memory per block as sm90 and sm100, so it takes the same backward kernel as sm100. I have that change plus a regression test in tests/pytorch/attention/test_attention_backend_selection.py. The test mocks the compute capability, so it needs no GPU:

  • sm103 with padded head dims 224 and 256, and MLA 192/256, select FA2;
  • the 192, 264 and 252 boundaries;
  • an sm100 control.

The three sm103 cases above 192 fail on current main and pass with the change. I'm happy to open it as a PR, or to leave it to you if you'd rather reopen this one.

@cyanguwa

Copy link
Copy Markdown
Collaborator

@0z5a, thanks for the comment. Looks like the author closed the PR himself, cc @kalectory.

Overall, I still think this change is necessary, at least for sm103. @0z5a, could you please submit a PR for the narrower fix you mentioned? I think it's safer to just enable sm103 for now and leave sm86/89 out. Thanks!

@kalectory

Copy link
Copy Markdown
Author

Thanks @cyanguwa. I agree with the solution @0z5a suggested, will keep this PR closed and defer the changes to their fix for sm103.

@0z5a

0z5a commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

@cyanguwa Thanks! The narrower fix is up as #3714. It adds only (10, 3) to the allowlist and leaves sm86/sm89 unchanged. It also adds a backend-selection regression test: the three sm103 cases above 192 fail on current main and pass with the change. The PR is a draft for now; I'll mark it ready after a final review on my side.

The (12, 0) dropout case mentioned above is tracked separately in #3711, with the fix in #3715.

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

Labels

attention community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants