Skip to content

[PyTorch][torch.compile] Support for DotProductAttention on flash and unfused backends - #3286

Open
pggPL wants to merge 30 commits into
NVIDIA:mainfrom
pggPL:dpa_torch_compile
Open

[PyTorch][torch.compile] Support for DotProductAttention on flash and unfused backends#3286
pggPL wants to merge 30 commits into
NVIDIA:mainfrom
pggPL:dpa_torch_compile

Conversation

@pggPL

@pggPL pggPL commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Description

DotProductAttention.forward no longer carries @no_torch_dynamo, so torch.compile captures the whole module -- input unpacking, qkv layout, backend selection and the backend itself -- with fullgraph=True on the FlashAttention and UnfusedDotProductAttention backends.

FusedAttention stays an eager island (@no_torch_dynamo on its own forward): the graph break moves from the entry of DotProductAttention down to the backend call, so everything around it is now compiled.

Follow-up to #3189 (traceable get_attention_backend), #3200 (declarative packed QKV), #3201 (unfused DPA) and #3268 (device-side padding mask).

Type of change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)

Changes

Removing the decorator exposed several things that could not be traced. Each is fixed at the source rather than worked around:

  • DotProductAttention.init_fp8_metadata called get_fp8_recipe() unconditionally, which builds a default Recipe when no autocast is active -- and constructing one is not traceable. With FP8 off it now returns through the base class, which is where the rest of that function ended up anyway.

  • get_qkv_layout recognizes packed q/k/v from UntypedStorage.data_ptr and Tensor.storage_offset, neither of which dynamo can trace, and neither of which means anything on the fake tensors used while tracing. While tracing, the layout comes from the format instead; packed inputs are declared through qkv_layer/kv_layer ([PyTorch][torch.compile] DotProductAttention: declarative packed QKV/KV inputs #3200). ONNX export keeps its own path -- the exporter runs through dynamo, so it sets is_compiling() as well.

  • get_indices, which packs bshd/sbhd inputs for FlashAttention's varlen entry point, built the index list with a Python comprehension over sequence lengths held in a GPU tensor: a device synchronization per sequence in eager, and data-dependent while tracing. It is now built on the device, verified exhaustively against the previous implementation (10304 cases, 0 mismatches).

  • get_full_cu_seqlens keyed its cache on torch.is_inference_mode_enabled(), a fundamental graph break. The cache only saves an allocation, so while tracing the tensor is built directly.

  • The fused sub-backend enum does not survive a graph break. _get_fused_attn_backend is decorated with @torch.compiler.assume_constant_result, and dynamo reconstructs such a value by re-emitting the call that produced it -- which is only valid inside the frame that made it. Crossing the break into FusedAttention, the resumed frame received the producing function instead of the enum member, and it reached fused_attn_fwd. get_attention_backend now returns the sub-backend as a plain int, which is baked into the graph as a literal; FusedAttnBackend.cast turns it back into a member, as fused_attn_fwd/bwd already did.

  • Backend selection is logged through the no-op logger while tracing, as get_attention_backend does: logging.Logger methods graph-break.

  • lazy_compile skips its nested torch.compile while tracing. Filling its closure cell is a side effect dynamo rejects inside a higher-order op, so compiling a module whose autograd.Function backward makes the first call to a jit_fuser'd function failed with Unsupported: HOP: Unsafe side effect. Here that is PackTensors.backward on the FlashAttention varlen path; an earlier eager run hides it by filling the cell beforehand. The nested torch.compile is inlined into the outer graph either way, so calling the function directly while tracing is equivalent -- and in eager nothing changes.

  • no_torch_dynamo takes a when predicate, so a method can be an eager island for some calls only. The predicate receives the call's arguments keyed by parameter name and returns the reason a particular call cannot be traced.

Falling back to eager

Four kinds of call cannot be compiled and run as an eager island instead, each with a warning naming what to do differently:

why how to stay compiled
FP8 attention (fp8_dpa/fp8_mha) quantizers, fp8_meta mutation and Float8Tensor crossing the graph boundary, none of it covered by tests --
context parallelism (cp_group set) the implementation is built around P2P communication and its own CUDA stream, neither of which dynamo traces --
packed q/k/v passed as plain views recognizing them takes the data pointers dynamo cannot read declare the packing via qkv_layer/kv_layer
thd without max_seqlen_q/max_seqlen_kv deriving it reads the sequence lengths off cu_seqlens: a device synchronization, and a data-dependent value while tracing pass max_seqlen_q/max_seqlen_kv

The predicate is deliberately narrow for FP8: it asks the recipe for fp8_dpa/fp8_mha rather than whether an autocast is active, so FP8 GEMMs with attention in high precision -- the common training setup -- stay on the compiled path.

Without fullgraph, these calls work and warn. With fullgraph=True they raise, which is what asking for a full graph means.

Testing

tests/pytorch/test_torch_compile.py, 56 passed / 23 skipped on RTX Ada (sm89, flash-attn 2.7.4):

  • the configuration matrix -- 14 configurations described with the same ModelConfig the eager attention tests use, against each backend that supports them, which get_available_attention_backends() decides. It covers bshd/sbhd/thd, causal / no_mask / padding / sliding window / arbitrary masks, GQA, cross attention, alibi, post-scale bias, MLA head dims, off-by-one softmax, one shared cu_seqlens tensor for q and kv, and all four declared packed layouts. Compiled with fullgraph=True, compared against eager in forward and every input gradient;
  • CUDA graphs (mode="reduce-overhead") -- fresh inputs each iteration and compared against eager, since a replay reading stale buffers produces finite values and non-None gradients too; inductor's skip counter is asserted to be zero, so a declined capture cannot pass silently;
  • the compiled path around FusedAttention -- it is not compiled itself, but everything before and after the break is, and what crosses that break has to survive it;
  • the eager fallbacks -- that each fires exactly when it should, matches eager, and raises under fullgraph=True.

Backend selection is invalidated right before every compiled call, so it is traced rather than served from the cache the eager call populated.

Comparison is exact for backends whose kernel is the same either way. The unfused backend is built from PyTorch ops that inductor fuses and reassociates, so its absolute tolerance is taken from the tensor's own scale -- an off-by-one softmax differs by two bfloat16 roundings on individual elements, which no elementwise relative tolerance covers near zero.

attention/test_attention.py, attention/test_kv_cache.py, test_gqa.py and test_onnx_export.py show no new failures relative to main.

Not covered by this hardware and wanted from CI: FP8 and MXFP8 attention, the fused sub-backends, FlashAttention 3 and 4.

Known limitations

  • FusedAttention is not compiled; it is skipped from the matrix, and removing that skip is all that is needed once its forward traces. Registering tex.fused_attn_fwd/bwd as custom ops -- which is what lets flash-attn's kernels sit inside a graph -- would be the way there.
  • The unfused backend is compared to eager with a tolerance rather than exactly, for the reason above.

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

🤖 Generated with Claude Code

pggPL added 29 commits July 29, 2026 08:52
Make DotProductAttention itself traceable, so that torch.compile captures
the whole module -- input unpacking, qkv layout, backend selection and the
backend -- on the FlashAttention and UnfusedDotProductAttention backends.
FusedAttention stays an eager island, as before.

DotProductAttention.forward no longer carries @no_torch_dynamo. What that
uncovered, and the fixes:

- init_fp8_metadata unconditionally called get_fp8_recipe(), which builds a
  default Recipe when no autocast is active -- not traceable. It now returns
  early when FP8 is off everywhere, which is also what the rest of the
  function did in that case.
- get_qkv_layout detects packed q/k/v from data pointers and storage
  offsets, neither of which dynamo can trace (and neither is meaningful on
  fake tensors). While tracing, the layout is built from the format instead;
  packed inputs are declared explicitly via qkv_layer/kv_layer, and
  non-contiguous q/k/v are made contiguous so the layout stays truthful.
- get_full_cu_seqlens keyed its cache on torch.is_inference_mode_enabled(),
  a fundamental graph break; while tracing it builds the tensor directly.
- get_padding_mask read sequence lengths on the host (a device sync, and
  data-dependent under torch.compile). It is now vectorized.
- The fused sub-backend enum member does not survive a graph break: dynamo
  reconstructs the assume_constant_result value by re-emitting the call that
  produced it, so the resumed frame received the function itself instead of
  the enum -- which then reached fused_attn_fwd. get_attention_backend now
  keeps a plain int while tracing and casts back to FusedAttnBackend in
  eager mode only.
- Backend selection logging is skipped while tracing (logging.Logger methods
  graph-break, and int() of the sub-backend enum is untraceable too).

Tests: test_dpa_torch_compile covers 7 configurations (bshd/sbhd/thd,
causal/no_mask/sliding window, GQA, cross attention, packed qkv_layer)
against both backends with fullgraph=True, comparing forward and backward
against eager; plus a CUDA-graphs (reduce-overhead) test and a test that the
FusedAttention path stays correct around its graph break.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The override hand-rolled the state that TransformerEngineBaseModule already
sets when FP8 is off everywhere (the three flags, fp8_checkpoint and
fp8_initialized). Call super() instead, and read the flags straight off
FP8GlobalStateManager.quantization_state, as the other hot paths do.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…ayout

get_qkv_layout recognizes packed q/k/v by inspecting data pointers and storage
offsets, which dynamo cannot trace. Rather than assume such inputs are three
separate tensors -- a layout that would not describe memory -- detect the case
from what is traceable (a strided tensor that does not own its whole storage)
and run the whole module as an eager island, with a warning. Declaring the
packing via qkv_layer/kv_layer keeps the call on the compiled path.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Log arguments are evaluated regardless of the logger, so the no-op logger
from NVIDIA#3189 is not enough here -- skip the whole block while tracing.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
- utils.py no longer needs no_torch_dynamo: the eager island moved to
  dot_product_attention.py.
- _needs_eager_dpa reads q/k/v out of *args/**kwargs instead of naming them
  before *args, which pylint flags (W1113).

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The CUDA-graphs test only checked that the output was finite and that
gradients existed. A replay that reuses stale buffers -- the failure mode
that test is there to catch -- satisfies both, so it now compares against
eager on fresh inputs every iteration.

The remaining comparisons used the dtype defaults (rtol=1.6e-2 for bfloat16),
which is far looser than what these paths deliver: compiled and eager agree
bit-for-bit on every config and backend. FlashAttention now has to match
exactly, as it runs the same kernel either way; the unfused backend is
allowed a single bfloat16 rounding, since inductor may reassociate the
softmax sums.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Self attention on thd naturally passes the same tensor as cu_seqlens_q and
cu_seqlens_kv, and flash-attn's varlen entry point forwards both to the same
autograd.Function -- which dynamo refuses to trace ("duplicate tensor input"),
failing the compilation under fullgraph=True.

Hand the varlen call a copy of one of them while tracing. The clone is
b + 1 int32 elements, only on that path, and only under torch.compile.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Pull the deduplication into a helper and use it wherever both cumulative
sequence lengths are handed to flash-attn: the varlen path shared by v2 and
v3 training, and v4's varlen kwargs. The v3 KV-cache path derives the kv
lengths by subtraction, so its two arguments are distinct by construction.

v4 is not installed in the environment this was tested in, so that call site
is covered by construction rather than by a test; the helper is a no-op
unless the two arguments are the same object.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
FP8 attention brings quantizers, fp8_meta mutation and Float8Tensor across
the graph boundary, and none of it is exercised by this PR's tests. Rather
than trace it untested, route it to the eager island that packed q/k/v
already use.

The predicate is deliberately narrow: it asks the recipe for fp8_dpa/fp8_mha
rather than whether an autocast is active, so FP8 GEMMs with attention in
high precision -- the common training setup -- stay on the compiled path.

The decorator now takes a predicate that returns the reason, so the warning
names which of the two paths was taken.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…rsection

The configurations were chosen so that both backends could run all of them,
which meant nothing backend-specific was ever exercised: no biases, no
arbitrary masks, no MLA head dims, no sink softmax. Describe them with the
ModelConfig the eager attention tests already use, and let
get_available_attention_backends() decide which backend runs which, so a
configuration only one backend supports is covered rather than avoided.

Five such configurations are added. One of them immediately showed that the
tolerance for the unfused backend was too tight: an off-by-one softmax
differs from eager by two bfloat16 roundings on a single element out of
65536, because inductor reassociates the softmax sums. Only FlashAttention,
which runs the same kernel either way, keeps the exact comparison.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…utils

An off-by-one softmax exposed that comparing the unfused backend elementwise
does not hold up: inductor's reassociation of the softmax sums produces an
error proportional to those sums, which for gradient elements near zero is
far outside any relative tolerance -- while being one bfloat16 rounding at
the tensor's own scale.

Derive the absolute tolerance from the reference tensor's magnitude, and take
the dtype tolerances from tests/pytorch/utils.py rather than restating them.
FlashAttention and FusedAttention keep the exact comparison.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The configurations built cu_seqlens with every sequence at the maximum length,
so a padding mask was all-False: the padding branches ran, but on data that
could not tell a difference. They now use varying sequence lengths, which
immediately showed that FlashAttention on bshd/sbhd with a padding mask does
not trace: it packs the tensors first, and get_indices built the index list
with a Python comprehension over sequence lengths held in a GPU tensor -- a
host synchronization per sequence in eager, untraceable while compiling.
It is now built on the device, verified exhaustively against the previous
implementation (10304 cases, 0 mismatches) and free of synchronization.

The separate tests for one shared cu_seqlens tensor and for the fused backend
folded into the main one: the first is a configuration, and the second is the
backend axis, which now runs flash, fused and unfused with the graph break
that FusedAttention's eager island implies.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
On thd inputs without max_seqlen, DotProductAttention reads the sequence
lengths off cu_seqlens to derive it. Unlike the padding mask and the packing
indices, this one cannot be moved to the device: max_seqlen sizes tensors
downstream, so it has to be a Python int, and reading it is a device
synchronization -- a data-dependent value dynamo cannot trace. Route those
calls to the eager island, so passing max_seqlen is what keeps a call
compiled.

Arguments are now looked up by name or position, so the predicate sees them
whichever way the caller passed them.

The two eager-fallback tests merge into one: they set up different inputs but
assert the same thing -- a warning, results matching eager, and an error under
fullgraph.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Both tests ran a callable, went backward, copied the gradients out and cleared
them. That is now _run_and_capture(), with _assert_run_matches() comparing two
of its results, which halves the CUDA-graphs test.

The two stay separate tests: what CUDA graphs risk -- a replay reading stale
buffers -- does not depend on the attention configuration, so folding the mode
into the configuration matrix would multiply the cases without covering
anything new, and would report a replay bug as a failure of some unrelated
configuration.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The eager-fallback test hand-rolled the same run, backward and gradient copy.

The backend-level unfused test only checked that the output was finite and
that gradients existed, which a CUDA-graph replay reading stale buffers also
satisfies -- the weakness the DotProductAttention CUDA-graphs test was given
an eager comparison for. It now compares against eager as well.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The positions of forward's parameters were written out as a table of indices,
which anyone inserting a parameter into a thirty-argument signature would
silently invalidate: the predicate would go on reading whatever now sits at
index 5 or 10 and decide the fallback on it, with nothing to notice.

The decorator already has the function it wraps, so it takes the parameter
names from its signature and hands the predicate the call's arguments keyed by
name.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Nothing about it is specific to attention: it takes a predicate, and runs the
wrapped method as an eager island whenever that predicate gives a reason. It
belongs next to no_torch_dynamo, which it builds on, rather than buried in the
attention module where the modules that will need it next -- MultiheadAttention
and TransformerLayer, both of which still have blockers of their own -- would
not find it.

What stays in the attention module is the predicate, which is where the
attention-specific knowledge lives.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The test compiled every backend with fullgraph=True except fused, which was
passed fullgraph=False because its forward is an eager island. That put an
assumption about one backend into the comparison logic.

FusedAttention is skipped instead, next to the configurations a backend cannot
run, with the reason spelled out. Everything else is compiled the same way, and
supporting fused later is deleting the skip.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
FusedAttention is skipped from the backend matrix because it does not compile,
but it is the default on Hopper and Blackwell, and this change does affect it:
what used to be one eager island is now a traced prologue, a graph break at the
backend, and a traced remainder. Whatever crosses that break has to survive it,
which the sub-backend enum did not.

One test covers it, without a fullgraph and outside the matrix. Verified to
fail with the enum restored -- TypeError: int() argument must be ... not
'function', from cuDNN's entry point.

Also rename _distinct_cu_seqlens: it returns its arguments untouched in eager
and whenever they already differ, so it promised more than it does.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…end does

Skipping the block with is_compiling() introduced a second mechanism for a
problem this module already solves one way: get_attention_backend swaps in a
no-op logger while tracing. The skip was needed while the sub-backend argument
was an enum that did not survive tracing, and is not any more.

The logger, which was private to the utils module, is now named without the
underscore, since it is used from outside it.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
get_attention_backend cast it back to a FusedAttnBackend member unless it was
tracing, which made its return type depend on the execution mode -- something
every reader of that function then has to keep in mind, for no benefit inside
transformer_engine: fused_attn_fwd/bwd call cast() on whatever they are given,
and every comparison against a member works with an int on either side.

It now returns an int always, and the docstring says so.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The old name read as a run-on, and its '_if' suffix suggested a boolean
condition where the argument actually returns the reason. The new one reads as
a sentence where it is used and matches the warning the decorator emits.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
It was a decorator of its own, which meant a second name for what is the same
thing this module already does: disable dynamo for a frame. It is now the
conditional form of no_torch_dynamo -- @no_torch_dynamo(when=predicate) -- so
there is one decorator to find and one docstring to read.

The predicate receives the call's arguments keyed by parameter name and returns
the reason this call cannot be traced, or None to have it traced.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Packing was pinned to one layout by an assert in the input builder, so of the
twelve declarations DotProductAttention accepts -- qkv_layer or kv_layer,
interleaved at -3 or -2, in each of the three formats -- exactly one was
tested. The builder now takes what is packed and where it is interleaved, and
derives the shapes and the layout string the way DotProductAttention does.

Three configurations are added, so both packed inputs and both interleave
dimensions are covered.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
The ONNX exporter runs through dynamo, so torch.compiler.is_compiling() is
true during an export as well -- measured: inside torch.onnx.export(dynamo=True)
both it and is_in_onnx_export_mode() are set. The compile branch was written
first and took the export over: the layout came from the format instead of the
detection ONNX relies on, tensors were no longer made contiguous, and packed
views hit an assert where they used to be copied.

Export mode is the narrower context, so it is checked first. This restores the
previous behaviour for export while leaving the compiled path as it was.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Arguments are matched to parameter names by position, which is only right
while the signature has no *args in between -- and would misalign silently if
one were added. It is checked when the decorator is applied.

The docstring now also says that an argument the call left out is simply
absent, so a predicate reading it with .get() sees its default only as long as
that default is None.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
…ring

fullgraph=True says dynamo did not break the graph, but inductor can still
decline to capture -- a mutated input, a CPU scalar -- and run the thing
normally, which would leave the CUDA-graphs test passing while measuring
nothing. Its skip counter is asserted to be zero. Measured on this branch:
nothing is skipped, the tree has one root, and interleaving the eager
reference call between replays does not disturb it.

The tolerance docstring described which backends are compared exactly by
naming them; it now states the rule -- a backend whose kernel is the same
either way -- so it still holds once FusedAttention traces.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Filling lazy_compile's closure cell is a side effect Dynamo rejects inside
a higher-order op, so compiling a module whose autograd.Function backward
makes the first call to a jit_fuser'd function fails with

    Unsupported: HOP: Unsafe side effect
      Attempted to mutate CellVariable()

An earlier eager run hides this by filling the cell beforehand. The nested
torch.compile is inlined into the outer graph either way, so calling the
function directly while tracing is equivalent.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
@pggPL
pggPL requested a review from cyanguwa as a code owner July 30, 2026 13:05
@pggPL
pggPL requested a review from ksivaman as a code owner July 30, 2026 13:05
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR enables broader torch.compile coverage for DotProductAttention.

  • Conditionally preserves eager execution for unsupported FP8, context-parallel, undeclared packed-view, and incomplete THD calls.
  • Makes QKV layout handling, sequence-index generation, backend selection, fused-backend values, and lazy JIT helpers trace-compatible.
  • Adds forward, backward, CUDA-graph, backend, and eager-fallback coverage across FlashAttention and unfused configurations.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
transformer_engine/pytorch/attention/dot_product_attention/dot_product_attention.py Conditionally compiles DPA forward while retaining eager islands for unsupported configurations and making backend logging trace-safe.
transformer_engine/pytorch/attention/dot_product_attention/utils.py Makes backend values, QKV layout handling, sequence indices, and cumulative-length allocation compatible with Dynamo tracing.
transformer_engine/pytorch/attention/dot_product_attention/backends.py Avoids passing one cumulative-sequence tensor through two FlashAttention autograd inputs during compilation.
transformer_engine/pytorch/cpp_extensions/fused_attn.py Documents and normalizes fused-attention backend enum values across graph breaks.
transformer_engine/pytorch/jit.py Adds conditional Dynamo disabling and avoids nested lazy compilation while an outer graph is being captured.
tests/pytorch/test_torch_compile.py Adds broad compiled DPA correctness, CUDA-graph, fused-island, and fallback-path coverage.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[DotProductAttention call] --> B{Requires eager fallback?}
    B -- Yes --> C[Eager island with warning]
    B -- No --> D[Compile input handling and backend selection]
    D --> E{Selected backend}
    E -- FlashAttention --> F[Compiled FlashAttention path]
    E -- Unfused --> G[Compiled PyTorch attention path]
    E -- FusedAttention --> H[Eager FusedAttention island]
    F --> I[Compiled output handling]
    G --> I
    H --> I
Loading

Reviews (2): Last reviewed commit: "Run context parallel attention eagerly" | Re-trigger Greptile

The context parallel implementation uses P2P communication and its own CUDA
stream, neither of which dynamo traces, and FlashAttention reaches it from a
forward that is no longer excluded from the graph.

Signed-off-by: Pawel Gadzinski <pgadzinski@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant