Thread tanh logit softcapping through FlashAttention (FA2 and FA3) - #3391
Thread tanh logit softcapping through FlashAttention (FA2 and FA3)#3391nvegesna-netizen wants to merge 37 commits into
Conversation
Greptile SummaryAdds tanh attention-logit softcapping throughout TransformerLayer, MultiheadAttention, and DotProductAttention, with backend capability filtering and reference coverage.
Confidence Score: 5/5The PR appears safe to merge; no actionable regressions remain in the reviewed changes. No new correctness, security, or repository-rule violations were identified. All previous findings were manually resolved, and the latest change consistently rejects negative softcap values before backend dispatch. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[DotProductAttention with softcap] --> B{softcap is zero?}
B -->|Yes| C[Existing backend selection]
B -->|No| D{Eligible FA3 build and shape and non-CP?}
D -->|Yes| E[FlashAttention 3]
D -->|No| F{FA2 >= 2.6 and supported dropout mode?}
F -->|Yes| G[FlashAttention 2]
F -->|No| H{Context parallel?}
H -->|No| I[Unfused attention]
H -->|Yes| J[No eligible backend: raise]
Reviews (36): Last reviewed commit: "fix(pytorch): reject a negative softcap ..." | Re-trigger Greptile |
a6a793b to
5917f0d
Compare
19a21eb to
5ae46ce
Compare
|
Please follow this link to fix the DCO of this PR as well. Thanks! https://github.com/NVIDIA/TransformerEngine/pull/3391/checks?check_run_id=97902991433 |
…in FA3) Add a user `softcap` value (tanh logit softcapping, `softcap*tanh(x/softcap)`) to DotProductAttention so models like Gemma2 can run on the fused flash path instead of an unfused/FlexAttention kernel. - Add `softcap` to DotProductAttention (init+forward) and AttentionParams; thread it into the FA2 non-CP kwargs and all three context-parallel autograd functions (forward + ctx-saved backward). softcap=0.0 reproduces prior behavior. - get_attention_backend: when softcap != 0, disable FusedAttention/unfused and steer to FA2 -- disable FA3/FA4, and disable FA2 < 2.6.0 -- so the cap is never silently dropped (FA2 < 2.6.0) or hit at runtime as NotImplementedError (FA3/FA4). Also disable FA3 under context parallelism (its CP path hard-rejects nonzero softcap) so CP+softcap steers to FA2, which supports it, instead of crashing. - FA3 softcap opt-in: NVTE_FA3_SOFTCAP=1, Hopper (sm90) hd<=256, non-CP only, gated on a fail-closed signature probe (fa3_supports_softcap). Forward threads softcap into fa_3_optional_forward_kwargs; the existing Hopper autograd function carries it into backward automatically. Default off; unchanged behavior steers to FA2. - ONNX export: fail loudly (assert) rather than silently drop softcap -- export unconditionally force-selects UnfusedDotProductAttention, which has no softcap support, so this previously exported models with softcapping silently omitted. - Tests: test_softcap.py (FA2 fwd/bwd parity vs pure-PyTorch reference), wired into qa/L0_pytorch_unittest/test.sh. FA4 softcap opt-in is deliberately NOT included here -- see follow-up PR. On Blackwell (SM100), FA4's dedicated head_dim=256 forward kernel has no score_mod support at all (kernel constructor asserts `score_mod is None`), so there is currently no FA4 kernel path this could opt into; adding the scaffolding now would just be inert code with nothing to exercise. Addresses review findings: CP+FA3 softcap selection crash, ONNX silent drop, and the missing CI wiring for test_softcap.py. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
python -O / PYTHONOPTIMIZE strips assert statements, which would silently reopen the ONNX export softcap-drop bug the previous commit fixed (ONNX mode would again force-select UnfusedDotProductAttention with softcap silently omitted, with no error). Switch to an explicit if/raise ValueError, which survives optimized execution. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
for more information, see https://pre-commit.ci (reapplied after a force-push rebase clobbered pre-commit.ci's original 19a21eb commit; same content, restored by hand) Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
…fold test into test_attention.py UnfusedDotProductAttention now applies softcap * tanh(scores / softcap) to the already-scaled logits, matching how FlashAttention folds softmax_scale into its tanh argument, so it can serve as the softcap reference backend. Backend selection therefore no longer disqualifies unfused attention for softcap, and the ONNX-export guard is dropped since the export path force-selects unfused and torch.tanh is exportable. test_softcap.py is replaced by a model_configs_softcap dict and test_dpa_softcap in test_attention.py, which reuses test_dot_product_attention for backend sweeping. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
Drop the redundant NVTE_FA3_SOFTCAP opt-in. `use_flash_attention_3` already derives from NVTE_FLASH_ATTN_V3, so the existing flag governs the FA3 softcap path and NVTE_FLASH_ATTN_V3=0 disables it. Correctness stays established by the build-capability probe, head_dim <= 256, and the non-CP requirement. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
FA4 exposes no softcap kwarg and its head_dim=256 kernel asserts score_mod is None, so there is no kernel to route the cap through. The FA4 call path in backends.py passes no softcap, so an FA4 selection with a nonzero softcap silently dropped the cap instead of failing closed. NVTE_FLASH_ATTN_V4 defaults to enabled, so this was reachable on SM100+ with flash-attn v4 installed and no context parallelism. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
flash-attn rejects a nonzero softcap combined with nonzero dropout at dispatch: "Softcapping does not support dropout for now" in csrc/flash_attn/flash_api.cpp, present in mha_fwd and mha_varlen_fwd from v2.6.0 (the earliest version TE allows softcap on) onwards. Backend selection did not model this, so a softcap + attention-dropout config passed selection, routed to FA2, and crashed inside flash-attn. Dropout only reaches the kernel while training, since backends.py passes `self.attention_dropout if self.training else 0.0`, so the gate is on `attention_dropout != 0.0 and is_training` to avoid blocking valid inference configs. UnfusedDotProductAttention supports both softcap and dropout and stays available, so this steers rather than hard-fails. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
82a2bf4 to
5ecabac
Compare
test_dot_product_attention forced is_training=False whenever FusedAttention could not train a config, so that backends only available for inference could still be compared. softcap always disables FusedAttention, so test_dpa_softcap silently degraded to a forward-only comparison and the PR's backward-parity claim -- the FA2 softcap backward kernel included -- went untested. Add fwd_only_without_fused_attn (default True, so every other caller is byte-for-byte unchanged) and opt test_dpa_softcap out, which pairs FlashAttention against UnfusedDotProductAttention with is_training=True and restores the dgrad comparison. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
Two gaps remained after folding test_softcap.py into test_attention.py. softcap=0.0 no-op: every model_configs_softcap entry uses a nonzero cap, so nothing asserted the backward-compatibility claim. The half that the PR actually changed is backend selection, and a filter that fired at 0.0 would silently remove FusedAttention and FA4 from other tests rather than fail one. test_dpa_softcap_zero_backend_selection asserts FusedAttention survives softcap=0.0 and is disabled by a nonzero cap. Unfused coverage and tanh's nonlinear region: test_dpa_softcap needs two TE backends, so it skips entirely without flash-attn even though UnfusedDotProductAttention now implements softcap and is the reference for everything else. It also cannot detect a dropped cap at all: 0.1 * randn inputs put the logits at O(1e-2), where the reference output moves by 9e-9 at cap=50 and 2e-4 at cap=0.01. test_dpa_softcap_vs_reference compares forward and dQ/dK/dV against a pure-PyTorch oracle one backend at a time, so it runs with unfused alone, and uses randn inputs so the cap moves the output by O(1). An assertion on that displacement keeps the test from going vacuous if the config drifts. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
Resolve conflict in context_parallel.py: keep the softcap threading into FA2 backward kwargs alongside the new no-load-balance THD zero_tensors guard from NVIDIA#3438. The two changes are independent. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
…rd input Threading `softcap` through the three context-parallel autograd functions added a forward input to each without adding the matching gradient slot to the corresponding backward return tuple, leaving every CP backward one gradient short of its forward inputs. Because `softcap` sits mid-signature, the omission also shifted every later slot in AttnFuncWithCPAndQKVOA2A: `d_softmax_offset` was being returned in `softmax_type`'s position. Insert the missing slot at the `softcap` position in all three tuples. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
No test exercised CP together with softcap, which is why a backward that returned one gradient fewer than its forward inputs went unnoticed in all three CP autograd functions. Thread softcap through the CP runner so it reaches DotProductAttention, and add one case per CP autograd function -- p2p, all_gather and a2a -- checking the softcapped forward and dgrad against the non-CP reference. The cap sits in tanh's nonlinear region so a path that dropped it diverges rather than matching a numerically linear reference. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
…ttention UnfusedDotProductAttention applied the tanh cap after adding post_scale_bias / ALiBi, computing cap(scale*QK + bias). FlashAttention-2 computes cap(scale*QK) + bias: its kernel softcaps immediately after the QK^T gemm and only then adds ALiBi, and it pre-divides alibi_slope by scale_softmax -- which softcapping sets to `softcap` -- so the bias deliberately lands outside the tanh (csrc/flash_attn/src/flash_fwd_kernel.h, mask.h, flash_api.cpp). ALiBi is the one bias type flash supports, so with softcap + ALiBi the unfused and flash paths returned different numerics depending only on whether a suitable flash-attn was installed. Defer the additive bias until after the cap so the two agree. pre_scale_bias is folded in before the scaling by construction and stays inside the cap; flash does not support it. softcap = 0.0 remains a bit-exact no-op for every bias type. Add two tests, both forcing UnfusedDotProductAttention: - test_dpa_softcap_bias_ordering pins cap(scores) + bias against cap(scores + bias), using post_scale_bias to drive the same branch ALiBi uses without needing slope machinery in the reference. - test_dpa_softcap_qk_layer_scaling covers softcap under NVTE_APPLY_QK_LAYER_SCALING, where the cap must be divided by layer_number; omitting that leaves an effective cap of softcap * layer_number. Both carry anti-vacuity asserts, and both were verified by mutation on an H100: reintroducing either bug makes the corresponding test fail, and the existing softcap suite still passes. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
/te-ci pytorch L0 L1 L3 |
…lable test_dpa_softcap_zero_backend_selection asserted that FusedAttention is selectable at softcap=0.0. Whether it is available at all depends on the arch and on NVTE_ALLOW_NONDETERMINISTIC_ALGO, not on softcap: the A100 and L40 CI jobs fail the assert in their deterministic pass, where cuDNN offers no fused backend for this config, while the same test passes on H100 and B200. The test is about the softcap filter, so make the availability half a skip. The meaningful direction -- a nonzero softcap must disable FusedAttention -- still runs wherever a fused backend exists, and skipping keeps the comparison from being silently vacuous where one does not. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Triaged the te-ci failures. One was ours; the rest reproduce on unrelated PRs. Fix pushed in Ours:
|
| job | failure | evidence |
|---|---|---|
L0_pytorch_unittest--L40_1GPU |
test_numerics.py::test_layernorm_linear_accuracy[...] numeric drift (e.g. -2.5 vs -2.765625) |
also fails on #3466, #3465, #3457 |
L1_pytorch_distributed_unittest--{H100_4GPU,B200_8GPU} |
test_fusible_ops.py::test_distributed_fuser_ops[2] |
also fails on #3466, #3457 |
L1_pytorch_mcore_fsdp_integration--B200_8GPU |
No route to host on the c10d TCPStore rendezvous |
infrastructure |
L0_pytorch_lint |
<EMPTY LOG> |
no output produced |
None of those touch attention.
…ap-core # Conflicts: # tests/pytorch/attention/test_attention.py # transformer_engine/pytorch/attention/dot_product_attention/dot_product_attention.py
|
/te-ci pytorch L0 L1 L3 |
…radient The Lint job fails on this branch with dot_product_attention.py:243:4: C0116: Missing function or method docstring dot_product_attention.py:248:4: C0116: Missing function or method docstring _IdentityWithMaskedGradient came from NVIDIA#3274 and is byte-identical to main; the same two errors fail Lint on that PR's own branch (desh/mixed-thd-pr-minimal), which merged before the check went green, so this branch inherited a red Lint by merging main. Not introduced here, but it blocks this PR. Use the same `# pylint: disable=missing-function-docstring` the other autograd Functions in this package use (e.g. context_parallel.py) rather than inventing docstrings. Drop this commit if NVIDIA#3274 is fixed upstream first. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
softcap is only exposed through DotProductAttention right now. Could you please also thread it through MultiheadAttention and TransformerLayer? Thanks.
There was a problem hiding this comment.
Added to both. Each takes a softcap constructor argument stored on the module plus a forward override that falls back to it, matching how window_size is threaded. TransformerLayer passes it to both self_attention and inter_attention, so decoder cross attention is capped too. Resolution sits outside the thd_attention_policies branch at both levels, since softcap is not mask specific. Defaults are 0.0 and None, so existing callers are unaffected.
Two things worth flagging.
I appended softcap to the end of both forward signatures rather than grouping it with the other attention arguments. Inserting it mid signature shifted encoder_output and is_first_microbatch, so a downstream caller passing those positionally would have silently bound the wrong value. Nothing in the repo does that, but both classes are public API. Constructors keep it grouped with window_size, since it sits past every positional tuple used to build these modules.
I also added test_transformer_layer_softcap_plumbing, which hooks both core_attention modules in a decoder layer and asserts each receives the cap, from the constructor and from a forward override. Writing it showed that passing softcap through common_attention_kwargs was partly masking rather than redundant: it pre-set each MultiheadAttention's self.softcap, so deleting the cross attention pass through still satisfied the constructor assertion, since the fallback picked the value back up. The forward override assertion still failed, so the test caught it either way, but only on one of its two halves. Removing that entry makes both halves load bearing, and leaves softcap matching window_size exactly.
The test is more than you asked for and there is no precedent for it here, so easy to drop. My reason for adding it is that a merge from main during this branch's life already silently dropped softcap from attention_params_kwargs, and only a manual check caught it.
Validated on an H100 since writing the above. With the softcap suite green as the baseline, 26 passed, deleting the cross attention pass through fails test_transformer_layer_softcap_plumbing, and restoring it returns the suite to green. The other two arms behave the same way: capping the bias fails the bias_outside_cap variant, and dropping the cap / layer_number division fails the qk_layer_scaling variant. So this is measured rather than inferred.
There was a problem hiding this comment.
Thanks for thoroughly designing the tests, but I wonder if we could consolidate them a little bit, without losing meaningful test signals. How about this:
- keep only 2 configs in
model_configs_softcap:softcap_1_0for the high softcap value and to testflash_attn_funcAPI;softcap_3_1(with "padding_causal" mask though) for a low softcap value and to testflash_attn_varlen_funcAPI - fold
model_configs_softcap_referencefrom three configs to two, by merging GQA into the causal config; they are orthogonal to softcapping - merge
test_dpa_softcap_vs_reference,test_dpa_softcap_bias_orderingandtest_dpa_softcap_qk_layer_scalinginto one test, parametrized over variants{"plain", "bias_outside_cap", "qk_layer_scaling"}; we can extract a helper function to do the repeated work: force a backend via env vars, build randn q/k/v, construct the module with the cap, compute a right and a wrong variant, assert the two differ by more than 10 * atol, assert TE matches the right one, return the DPA kwargs, forward kwargs and the two closures - drop the causal variant from the bias-ordering and qk-layer-scaling cases; the other tests should be sufficient for testing whether the additive bias sits outside the tanh and whether the cap is divided by layer_number
Thanks!
There was a problem hiding this comment.
Done in a7b5160, following all four bullets.
model_configs_softcap is down to softcap_1_0 and softcap_3_1, the latter switched to padding_causal as you suggested so it goes through flash_attn_varlen_func. model_configs_softcap_reference is down to two, with GQA merged into the causal config. The three tests are now one test_dpa_softcap_vs_reference parametrized over {plain, bias_outside_cap, qk_layer_scaling}, with a _softcap_variant_spec helper returning the DPA kwargs, the forward kwargs and a right and a wrong reference closure. The shared work of forcing the backend, building inputs, and asserting the two references differ by more than 10 * atol now happens once. The bias variant reuses _softcap_reference_attention via new bias and cap_includes_bias arguments instead of duplicating its GQA and causal handling, which is what made the merged version shorter than the three it replaces. Causal is dropped from both special variants, as you asked.
The matrix is 48 cases on a bf16 capable GPU, since param_types picks up bf16 when available, with 19 running and 29 skipping and each special variant running once on the non causal reference config. qk_layer_scaling is fp16 only. On an fp16 only machine it is 24, 10 and 14.
Two things I checked before making the change, since consolidating tests can quietly remove the signal they exist for. Both variants still fail if their bug is reintroduced: capping the bias separates the two references by about 0.98, and dropping the cap / layer_number division by about 0.44, against the 10 * atol threshold, which is 0.20 in fp16 and 0.40 in bf16. Those margins hold because the surviving softcap_ref_1_0 is the same shape the originals ran on. I also hoisted layer_number to _SOFTCAP_QK_LAYER_NUMBER = 8 with a note that 3 is too small to separate, because that value is load bearing and would otherwise look arbitrary to the next person.
On head dimensions, the two surviving configs cover 64 and 128: dbb36c33 moves softcap_3_1 to head_dim 128, since FA2 and FA3 compile a separate softcap kernel per head_dim and the input scale is head_dim invariant.
I kept backward on the plain variant only, matching what runs today. Extending it to all three would be more coverage, but it is behaviour that has never run on hardware and I would rather not fold that in under a consolidation.
…ap-core # Conflicts: # transformer_engine/pytorch/attention/dot_product_attention/dot_product_attention.py
… comments Review feedback from NVIDIA#3391. Thread softcap through MultiheadAttention and TransformerLayer, following the window_size pattern: a constructor argument stored on the module and a forward argument that falls back to it. TransformerLayer passes softcap via common_attention_kwargs, so it reaches cross-attention as well as self-attention, matching how softmax_type (also not mask-specific) is handled; scoping it to self-attention would silently leave decoder cross-attention uncapped. Resolution sits outside the thd_attention_policies branch at both levels, since softcap is not mask-specific. Drop "only supported by the FlashAttention and UnfusedDotProductAttention backends" from the softcap docstrings. It is inaccurate, since support depends on the FlashAttention version, and get_attention_backend() is the single place that should describe backend eligibility. The same wording appeared in the DotProductAttention class and forward docstrings, so all three now describe only what softcap does. Shorten the softcap comment in UnfusedDotProductAttention, keeping why the cap precedes the additive bias and why it is divided by layer_number, as each of those was a real bug. Remove the fail-loud block comment in the FA3 branch, whose selection criteria get_attention_backend() already documents, and keep a single line noting that the FA3 entry points are autograd functions and so the forward kwarg drives the backward too. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
softcap was inserted mid-signature in MultiheadAttention.forward and TransformerLayer.forward, next to the other attention arguments. That shifts every parameter after it, so an external caller passing encoder_output or is_first_microbatch positionally would silently bind it to softcap instead. Both accept arbitrary objects, so it would be a wrong answer rather than a TypeError. Nothing in this repo calls either forward with enough positional arguments to hit it, but both classes are public API. Move softcap to the end of both forward signatures. Diffing against main confirms no existing parameter changes index in either. The constructors are unaffected and keep softcap grouped with window_size and bottom_right_diagonal, since it sits past every positional tuple used to construct these modules. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review feedback from NVIDIA#3391. Cut model_configs_softcap from six configs to two: softcap_1_0 for a high cap through flash_attn_func, and softcap_3_1 with padding_causal for a low cap through flash_attn_varlen_func. Fold model_configs_softcap_reference from three to two by merging GQA into the causal config, since GQA is orthogonal to softcapping. Merge test_dpa_softcap_vs_reference, test_dpa_softcap_bias_ordering and test_dpa_softcap_qk_layer_scaling into one test parametrized over {plain, bias_outside_cap, qk_layer_scaling}. A helper returns the DPA kwargs, the forward kwargs and a right and a wrong reference closure per variant, so the shared work of forcing a backend, building inputs and asserting the two references differ by more than the tolerance happens once. The bias variant reuses _softcap_reference_attention through new bias and cap_includes_bias arguments rather than duplicating its GQA and causal handling. Both mutation signals are preserved. The special variants run on softcap_ref_1_0, the same shape the original margins were measured on, so capping the bias still separates the two references by about 0.98 and dropping the cap / layer_number division by about 0.44, against a threshold of 0.20. layer_number is hoisted to _SOFTCAP_QK_LAYER_NUMBER with a note that 3 is too small, since that value is load-bearing and would otherwise look arbitrary. Backward stays on the plain variant only, matching the previous coverage. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds test_transformer_layer_softcap_plumbing. It builds a decoder TransformerLayer with a softcap, hooks both self_attention.core_attention and inter_attention.core_attention with a forward pre-hook, and asserts each sees the value, once from the constructor and once from a forward override. Only arrival is checked; the numerics are covered by the DotProductAttention tests. Cross-attention is the half worth asserting. It is reached through a separate call site from self-attention, so a refactor can drop the cap there while self-attention keeps working, and nothing else in the suite would notice. Main restructured both forward methods during this branch's life, so that is a live risk rather than a hypothetical one. Writing the test showed that passing softcap through common_attention_kwargs was not merely redundant but harmful: it pre-set each MultiheadAttention's self.softcap, so deleting the cross-attention pass-through still produced the right answer on the constructor path and the assertion could not fail. Removing that entry makes both assertions catch the regression, and leaves softcap matching how window_size is threaded, stored on self and passed per call rather than at construction. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
for more information, see https://pre-commit.ci
The test inherited NVTE_FLASH_ATTN / NVTE_FUSED_ATTN / NVTE_UNFUSED_ATTN from whichever softcap test ran before it, since none of them restore those vars. A nonzero cap also drops FusedAttention and FA4 in the backend filter, so a leftover NVTE_UNFUSED_ATTN=0 could leave no eligible backend at all and the forward would raise "No dot product attention backend is available" rather than fail an assertion. The outcome also depended on which parametrization of the preceding test happened to run last. Set the three variables explicitly, matching the convention of the other tests in this file, leaving flash and unfused both enabled so at least one backend is available with or without flash-attn. The assertions stay backend agnostic because the hook fires on DotProductAttention itself. Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
/te-ci pytorch |
|
Tightened the softcap comments in Two corrections to things this PR previously asserted, and one further fix. FA4 does implement softcap. Both non-CP entry points TE calls expose it, lowered to a score mod for forward and backward. It is excluded on three Blackwell shapes rather than one: SM100 forward and SM100 backward at The FA3 probe establishes API presence, not build configuration. It reads the Python wrapper, so a The probe now covers all three FA3 entry points ( The two numerical variants and the plumbing test are validated on an H100 by mutation, with the suite green as the baseline: capping the bias, removing the Collecting the open items in one place, since they are spread across several threads.
|
Halve the comment volume added to backends.py and utils.py, keeping only the facts that are not inferable from the code: that FA2 adds ALiBi outside the cap, and why the cap is divided by layer_number. Which backend supports what is already expressed by get_attention_backend and its debug messages. Also correct two inaccuracies found while auditing the surviving text. FA4 does implement softcap on both entry points TE calls, forward and backward, so it is disqualified because TE does not plumb the cap to it, not because the library lacks it; the log message said otherwise. And the FA3 signature probe detects whether the installed release exposes the kwarg at all, not whether the build set FLASHATTENTION_DISABLE_SOFTCAP, which still exposes it and rejects a nonzero cap at dispatch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
softcap is injected into fa_3_optional_forward_kwargs without regard to which entry point was selected, so the probe should cover every function the cap can reach. flash_attn_with_kvcache is the one chosen when inference_params is set, and it was validated only by proxy through the other two. No shipped FA3 build changes behaviour: all three entry points gained softcap in 2.7.3/hopper, which is TE's documented FA3 floor. Before that none of them had it. The symbol is already imported and None-guarded, so the probe cannot fire on an uninstalled FA3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
639f601 to
8805255
Compare
Cutting model_configs_softcap to two configs dropped head_dim 128 as a side effect, leaving every softcap config at 64. FA2 and FA3 compile a separate softcap kernel per head_dim bucket, and above 128 FA2 additionally forces the non-even-MN predicated path, so a bug in those kernels is invisible at 64. Widen the existing padding config instead of adding a third, so the count stays at two. Neither justification for the pair changes: head_dim plays no part in the flash_attn_func vs flash_attn_varlen_func dispatch, and the 0.01 cap rationale is head_dim invariant because softmax_scale cancels the sqrt(d) growth of the dot product. 128 rather than 256 because get_attention_backend disables FA3 for deterministic backward at head_dim >= 256, which would leave only UnfusedDotProductAttention and skip the comparison in the NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 CI pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
The test set NVTE_FLASH_ATTN, NVTE_FUSED_ATTN and NVTE_UNFUSED_ATTN and never restored them, leaking NVTE_FUSED_ATTN=0 into every test that ran after it. Its own comment names that hazard as the reason it sets them explicitly, then reproduces it. Wrap the body in try/finally, restore the previous values, and invalidate the backend selection cache on the way out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
tanh is odd, so UnfusedDotProductAttention's `cap * tanh(x / cap)` gives a bit-identical result for -c and +c, i.e. it caps at the absolute value. FlashAttention only enables capping under `softcap > 0.0`, so it applies no cap at all. get_attention_backend gates on `softcap != 0.0`, so a negative value passed every filter and the two backends silently disagreed, which is the exact failure mode the rest of this PR exists to prevent. Raise on a negative cap where softcap is resolved, which is the choke point every caller reaches, including MultiheadAttention and TransformerLayer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Nitin Vegesna <nvegesna@nvidia.com>
Adds a
softcapkwarg toDotProductAttentionso models with attention-logit soft-capping (cap·tanh(x/cap), e.g. Gemma2) can run on TE's fused flash-attention kernels instead of falling back to an unfused/non-TE path.Companion PRs (needed together for an end-to-end model to pick this up):
TransformerConfig.attn_logit_softcappingand maps it to thissoftcapkwargattn_logit_softcappingWhat changed
DotProductAttention(init + forward) andAttentionParamsgain asoftcap: float = 0.0kwarg.softcap=0.0is a no-op — existing behavior is unchanged.UnfusedDotProductAttentionappliescap * tanh(scores / cap)to the already-scaled logits, so it serves as the in-tree reference implementation (and the numerical reference the tests compare the flash backends against). With qk-layer-scaling thelayer_numberfactor is divided out of the cap, since that path defers scaling to the softmax.get_attention_backend: whensoftcap != 0, FusedAttention (cuDNN), FA4, and FA3 (unless the checks below pass) are disqualified and selection steers to FA2 or unfused (also disqualifies FA2 builds too old to carry the softcap kernel). This is a deliberate safety net — softcap must never be silently dropped, nor hit aNotImplementedErrorat runtime.softcappresent on all three FA3 entry points TE dispatches to),max(head_dim_qk, head_dim_v) <= 256, and non-CP; forward threadssoftcapinto FA3's kwargs, backward is handled automatically by the existing Hopper autograd function. No new env var — FA3 eligibility is governed by the existingNVTE_FLASH_ATTN_V3(default1). Since TE already prefers FA3 over FA2 on sm90, FA3 is the default softcap backend on Hopper when a softcap-capable FA3 build is installed; that is intentional.NVTE_FLASH_ATTN_V3=0steers to FA2.tests/pytorch/attention/test_attention.py.test_dpa_softcapsweeps the available backends through the existingtest_dot_product_attentionharness (forward + backward parity against the unfused reference); softcap always disqualifies FusedAttention, so it opts out of the harness's fused-unavailable fallback to keep the dQ/dK/dV comparison.test_dpa_softcap_zero_backend_selectionassertssoftcap=0.0leaves FusedAttention selectable and a nonzero cap does not.test_dpa_softcap_vs_referencecompares forward and dQ/dK/dV against a closed-form pure-PyTorch oracle one backend at a time, soUnfusedDotProductAttentionstays covered on machines without flash-attn; it uses its ownrandninputs because the shared harness's0.1 * randnputs logits at O(1e-2), where a cap of 50 moves the output by ~1e-8 and a dropped cap would be undetectable.Supported configurations
flash-attn >= 2.6.0(first FA2 release exposing asoftcapkwarg). The default path wherever FA3 is not eligible (i.e. off sm90, or no softcap-capable FA3 build). Requires zero attention dropout while training — see below.cp_comm_typevalues:p2p,all_gather,a2a,a2a+p2p(p2panda2a+p2pshare one autograd function). Sameflash-attn >= 2.6.0requirement; forward and backward both carrysoftcap.flash_attn_func,flash_attn_varlen_funcandflash_attn_with_kvcacheall exposesoftcap(signature probe, fail-closed) andmax(head_dim_qk, head_dim_v) <= 256. FA3 is Hopper (sm90)-only upstream and governed by the existingNVTE_FLASH_ATTN_V3(default1). When eligible it takes precedence over FA2 on Hopper — deliberate; both paths were exercised on Hopper (see Validation). Any check failing steers to FA2 (>= 2.6.0) or unfused.Not supported:
get_attention_backendwhensoftcap != 0.0, so selection steers to FA2 (or unfused) rather than silently dropping the cap.NotImplementedError(use FA2).get_attention_backendwhensoftcap != 0.0. FA4 does implement softcap, but TE does not plumb the cap into the FA4 call path (fa_4_optional_forward_kwargsnever carries it), so without this filter FA4 would be selected on SM100 (NVTE_FLASH_ATTN_V4defaults to1) and silently drop it. See the follow-up section below.csrc/flash_attn/flash_api.cpp), so FA2 is disqualified and selection steers to unfused. Dropout reaches the kernel as0.0in eval, so inference configs are unaffected. Since unfused does not support CP, CP + softcap + dropout while training has no eligible backend and raises rather than crashing inside flash-attn.ONNX export force-selects the unfused backend, which now honors
softcapviatorch.tanh(exportable as the ONNXTanhop), so export is expected to work — but there is no ONNX softcap test in this PR.softcap = 0.0(the default) disables softcapping: backend selection and numerics are identical to today.Validation
test_dpa_softcap— forward and backward parity against the unfused reference, across whichever backends are available on the test machine.test_dpa_softcap_vs_reference— forward and dQ/dK/dV against a closed-form reference, forsoftcapin{0.0, 0.5}, with logits at O(1) sotanhruns in its saturating region.softcap=0.0compares against a reference that never appliestanh, and the nonzero case asserts the cap moves the reference output by more than the comparison tolerance, so the test cannot pass an implementation that drops the cap.test_dpa_softcap_zero_backend_selection— thesoftcap=0.0no-op claim for backend selection, which is the half the filter actually changed.On the Hopper target both the FA2 and the FA3 softcap paths were exercised. FA3 taking precedence over FA2 there is an intentional design choice.
Follow-up, not in this PR: FA4
Wiring softcap through to FA4 is left to a separate PR rather than folded in here. FA4 exposes a
softcapargument on the non-CP entry points TE calls and implements it for forward and backward, so this is a plumbing task on the TE side rather than a missing kernel. Three Blackwell shapes are excluded: the SM100 forward and the SM100 backward athead_dim=256, and the MLA path withhead_dim_v=512. Until that plumbing lands,get_attention_backenddisqualifies FA4 whenever the cap is nonzero, so it can never be selected and silently drop the cap.