Skip to content

PyTorch Dynamo Frontend - #2645

Draft
tbennun wants to merge 102 commits into
mainfrom
dynamo-frontend
Draft

tbennun wants to merge 102 commits into
mainfrom
dynamo-frontend

Conversation

@tbennun

@tbennun tbennun commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator
    import torch
    import dace.ml
    model = MyModule().eval()
    compiled = torch.compile(model, backend='dace', dynamic=True)
    with torch.no_grad():
        y = compiled(torch.randn(8, 128))       # compiles the SDFG
        y = compiled(torch.randn(13, 128))      # reuses it: the batch size is a symbol

Requires #2590

tbennun and others added 19 commits September 16, 2026 10:18
Every concrete schedule tree node type now has a factory that builds a tree
containing it, mostly by converting a small SDFG, and the tree is converted
back into a validated SDFG. Node types whose conversion is not yet supported
are strict xfails, so they must be removed from the list once supported. Also
adds a roundtrip helper that compares the outputs of the original and the
converted SDFG.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…schedule trees to SDFGs

Exit gotos and state-if scopes raised NotImplementedError in StreeToSDFG.
Returns inside nested SDFGs were also flattened into exit gotos, which exit
the whole program instead of only the nested SDFG.

SDFG to schedule tree:
* A flattened nested SDFG that exits early now ends with a unique label, and
  its exits become gotos to that label.
* Break and continue blocks become break and continue nodes.
* The negated condition of exit state-ifs is now a well-formed code block.
* StateLabel may hold a label name instead of a state.

Schedule tree to SDFG:
* Forward gotos are first removed by moving the statements after a
  conditional into the branches that do not jump.
* Remaining gotos to the end of the SDFG being built become return blocks.
* Otherwise, the statements up to the label are converted into a nested SDFG
  (outside of maps as well), in which the gotos become return blocks.
* State-if scopes are converted like if scopes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
General blocks raised NotImplementedError in StreeToSDFG, and the trees
produced for them could not be converted back: only the start state of a
region was labeled, so gotos to other blocks had no target, and the start
block was not necessarily first.

SDFG to schedule tree:
* Every block of a general block is preceded by its label, starting with
  the start block.
* Conditional transitions are emitted before unconditional ones.
* Labels in general blocks are kept even if no goto jumps to them.
* Duplicate labels of all control flow blocks (not just states) are renamed.

Schedule tree to SDFG:
* A general block becomes an unstructured control flow region with one
  state per labeled segment. Gotos to its labels become inter-state edges,
  and statements after conditional gotos run in a state reached by the
  negation of their conditions. Falling off the end of a segment exits the
  region, and unreachable segments are removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Consume scopes raised NotImplementedError in StreeToSDFG.

SDFG to schedule tree:
* The consumed stream is emitted as a dynamic scope input (IN_stream) before
  the consume scope.

Schedule tree to SDFG:
* Map and consume scopes share the same conversion. Reads of the consumed
  stream inside the scope use the entry's OUT_stream connector. Without a
  stream input, the stream is inferred from the code nodes in the scope.
* Code generation supports only one read of the consumed element, and the
  element cannot be passed into a nested SDFG under the stream's name. In
  those cases, the element is first copied into a new scalar, which the rest
  of the scope reads.
* Copies into streams are described on the stream, as code generation
  expects for pushes.
* Nested SDFGs can now also be created for part of a dataflow scope.

Also fixes two issues found along the way:
* The state boundary analysis treated a node that reads and writes the same
  data as racing with itself. Maps like A[i] = A[i] + 1 were therefore
  converted into nested SDFGs. Reads are now only considered racy if they are
  not performed by an ancestor of the writing node.
* is_array_stream_view raised IndexError for a stream that is filled from an
  array and consumed by code through the same access node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… nested SDFG views from schedule trees

* Empty map scopes raised IndexError. Their entry is now connected
  directly to their exit.
* References set by the output of a tasklet or library node raised
  NotImplementedError. The output is now connected to the reference's set
  connector instead of being written as regular data.
* Symbol assignments at the end of an if/elif/else or loop body were
  performed after the conditional or loop (or raised NotImplementedError).
  They are now performed at the end of the body.
* Nested SDFG views (NView to NViewEnd) were only applied within the nested
  SDFGs created for map scopes, so accesses outside of maps used the
  un-viewed container. Their regions are now converted into nested SDFGs.

With this, every schedule tree node type is converted in the coverage test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Converting an SDFG to a schedule tree and back lost SDFG-level metadata and
named regions.

* Schedule tree roots now store global, init, and exit code blocks, which
  are merged per code generation target from all nested SDFGs, and are
  written back to the SDFG.
* The callback mapping (merged from nested SDFGs) and argument names are
  now filled in and written back.
* Named regions become NamedRegionScope nodes, which are converted back
  into named regions. Function call regions remain flattened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Control flow raising lifts irreducible control flow into an
UnstructuredControlFlow region, which InlineControlFlowRegions then inlined
again, so SDFG.simplify() never converged on such SDFGs. Add a
no_inline_unstructured option (enabled by default), like the existing
options for loops and conditional blocks.

The sibling loops test is simplified as well. Its loop body continued
unconditionally next to the conditional jump into the second loop, which is
ambiguous: dead state elimination removed the jump. The body now continues
only if the jump is not taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… test

All schedule tree node types are converted to SDFGs now, so the list of
expected failures is empty.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
StreeToSDFG raised NotImplementedError for StateBoundaryBehavior.EMPTY_MEMLET.
With that behavior, state boundaries that neither precede control flow nor
perform symbol assignments are now kept within the state. Nodes after such a
boundary are ordered after the nodes before it: every top-level sink before
the boundary gets an empty memlet edge to every top-level source after it
(scopes are represented by their exit and entry nodes). Writes after a
boundary use new access nodes, so that the edges cannot form cycles.
Dataflow scopes with boundaries still become nested SDFGs, and the boundary
before such a nested SDFG remains a state transition. The behavior can be
selected in ScheduleTreeRoot.as_sdfg.

Also fixes the state boundary analysis, which did not register the reads and
writes of the node right after an inserted boundary. A subsequent conflicting
write therefore got no boundary, e.g., for three consecutive writes to the
same element (as produced by nested SDFGs with multiple states).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolve the conflict in the state boundary analysis in favor of main
(#2611), which fixes the same missing registration of the node after an
inserted boundary and also checks that previous writes are ordered
before the node through the dataflow.

Update the scalar connector dealiasing tests from main (#2608) to the
trailing StateLabel that nested SDFG exits now jump to on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flow

New package dace/frontend/ml/torch/dynamo registers a 'dace' torch.compile
backend. ATen graphs from AOTAutograd (with an Inductor-style decomposition
table) are lowered into a DaCe schedule tree and converted to an SDFG:

- Dynamo shape/stride symbols become DaCe symbols (one SDFG per guard set,
  no recompilation across shapes or strides)
- pointwise ops with broadcasting, casts, fills, arange; reductions via the
  Reduce library node; mm/bmm/addmm via BLAS MatMul; views via ViewNode
- torch.cond / while_loop / scan / map lowered to IfScope / WhileScope /
  ForScope (native ConditionalBlock / LoopRegion in the SDFG)
- runtime wrapper allocates outputs as torch tensors and calls the SDFG

Exposed as dace.ml.compile(); tests in tests/torch_dynamo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ransformer block

- Allocate every container with the FakeTensor's (symbolic) strides: FX view
  metadata is relative to torch's layout (clone/pointwise preserve stride
  order), so contiguous allocation broke permute->clone->permute chains.
  Returned tensors now also match torch's expected strides.
- keepdim reductions write through a view of the final container.
- Native lowerings for split/split_with_sizes/unbind/narrow and a real
  as_strided lowering that honors storage_offset (split decomposed to it).
- Skip identity views (full-range slices); prims.var/sum/amax, any/all,
  prims pointwise aliases, sym_float/sym_int, float-valued shape exprs.
- Rename ops/symbolic.py to ops/symint.py to avoid confusion with
  dace/symbolic.py (which is untouched; torch stays optional).
- M2 tests: views/slices, softmax+layer_norm, bmm, SDPA, encoder block.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- convolution (N-d, stride/padding/dilation/groups) as a zero-padded copy
  plus nested maps accumulating with a WCR sum; bias folded into the init
- max_pool{1,2,3}d_with_indices / avg_pool as one map per output element
  with the static window unrolled into memlets (-inf/0 padded input)
- index.Tensor (embedding, index_select, advanced indexing with one index
  tensor) and gather via indirect memlets; argmax/argmin as Reduce + WCR-min
  map (no loops inside tasklets)
- translate torch's own sympy Max/Min in shape expressions
- M4 tests: conv1d/2d (groups, dilation), pooling, embedding/gather/argmax,
  LeNet compiled once across batch sizes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds doc/frontend/torch_dynamo.rst (usage, lowering overview, limitations)
to the user guide and frontend.torch_dynamo.{simplify,onnx_fallback}
configuration defaults consumed by DaceBackend when not given explicitly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ort graphs)

Dynamo decomposes linear before the backend sees it, but torch.export's IR
keeps aten.linear unless the decomposition table contains it. Verified that
exported programs now import through GraphImporter with parameters as
inputs and Dim sizes as symbols.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ExtUnparser special-cases numbers.Number for NumPy 2 scalars, but numpy.bool_
is not a Number, so a constant-propagated bool (e.g. from torch.zeros_like on
a bool scalar) was unparsed as 'np.False_' and broke generated C++. Also
normalize numpy scalars in the Dynamo frontend's tasklet literals.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed dimensions, torch.export Dim)

Dynamo alone decides which sizes are symbolic (duck shaping, 0/1 specialization) and names them s0, s1, ...
dace.ml.compile(model, dynamic_shapes=...) gives that control to the user with torch.export's vocabulary:
'all' makes every dimension of every argument its own symbol (weights stay static) and a per-argument
specification names dimensions (strings or Dim objects with bounds). The names become the DaCe symbol names.
Requires torch >= 2.13 (pinned in the ml extra).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@tbennun tbennun added the no-ci Do not run any CI or actions for this PR label Oct 3, 2026
tbennun and others added 10 commits October 4, 2026 11:41
… ATen semantics

- amax/amin (and max/min, argmax/argmin, softmax via amax) passed identity=None
  to Reduce, which then folds into the uninitialized output: results depended on
  leftover memory. Use the dtype's -inf/inf (or integer/bool extremes).
- Tensor constants of the graph (get_attr, e.g. torch.tensor([...]) in the
  program) crashed under fake mode and were never passed at runtime. They are now
  transient containers that are also compile-time constants of the SDFG
  (ScheduleTreeRoot.constants -> SDFG.constants_prop); the unused constant-input
  path of the runtime is removed. Constant arrays now emit bool and non-finite
  elements as valid C++ literals.
- round rounds half to even (rint); integer floor division floors (py_floor);
  maximum/minimum/clamp/relu propagate NaN, fmax/fmin ignore it.
- sym_storage_offset returns the tensor's storage offset instead of 0.
- Remove unused imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ependent capture tests

- torch 2.14 wraps fake-tensor errors raised during speculation in
  FakeTensorObservedException, a subclass of the ObservedException passthrough,
  so a failed capture was re-raised instead of falling back. Such errors now take
  the fallback path: the jump is blacklisted and the frame's analysis restarts
  (re-raising made Dynamo skip the whole frame), so the stock handler
  graph-breaks there on the next pass.
- The capture tests no longer depend on CPython code generation (supported
  versions are 3.10-3.14; e.g. 3.10/3.12/3.14 copy small exit blocks into break
  sites). Each test asserts with dis the instruction pattern it exercises
  (jump out of the loop, early return, back-edges), and new tests cover break
  and continue separately.
- Remove unused imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Python 3.12/3.13 end a rotated while loop with `POP_JUMP_IF_FALSE exit;
JUMP_BACKWARD body`, so the loop test at the bottom of the body has no
successor equal to the loop entry and was not recognized as the back-edge
(3.10/3.11 jump back conditionally, 3.14 re-evaluates the single loop test).
Jump successors are now compared after following unconditional jumps and
no-ops, which makes the three layouts equivalent. The capture tests pass on
3.10 through 3.14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
capture(module_or_fn, *args) returns the functional ATen graph that Dynamo and
AOTAutograd produce (fullgraph, inference) without compiling or executing it:
the capturing backend stops after AOTAutograd's forward graph. The result
records the source of every graph input (argument, parameter, buffer, module
attribute, global, or tensor size/stride, with names matching
named_parameters()), Dynamo's guards, and the shape assumptions and symbol
ranges. CapturedProgram builds the SDFG, binds inputs from their sources
(parameters by reference), and can compile itself.

The torch.compile backend describes its graphs the same way, so SDFG arguments
are now named after their sources (x, fc1_weight, ...) instead of FX
placeholders. Symbol naming moves from shapes.py to the new sources.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A module found in a program's closure is wrapped in an SDFG-convertible adapter
and captured with TorchDynamo for the data descriptors of the call site:
symbolic sizes keep their DaCe names, integer arguments become symbols, and the
outputs are returned as __return arrays. Parameters and buffers are closure
arrays passed by reference.

Dynamo's guards on the module (attribute values such as training, submodule
types) and on global state become part of the program's cache key through a new
SDFGConvertible.__sdfg_guards__ hook, which DaceProgram collects after parsing
and evaluates on every call, so a change re-parses the program.

Also make create_datadescriptor recognize torch.Tensor subclasses such as
nn.Parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…G-convertible objects

- Lists, tuples, and dicts passed to an SDFG-convertible object now reach
  __sdfg__ as structures of data descriptors (string-literal dict keys become
  strings) instead of container names, and their elements bind to separate
  arguments of the returned SDFG (xs[0] -> xs_0, d['w'] -> d_w; see
  structured_argument_name).
- Closure arrays that a convertible only reports in __sdfg_closure__ after
  conversion (i.e., not through closure_resolver during preprocessing) were
  added to the SDFG but never passed at call time. They now join the program's
  closure with an evaluator that queries the convertible on every call, and
  their descriptors are part of the cache key.

The new tests use plain-Python convertibles; the guard test also covers the
__sdfg_guards__ hook of the previous commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes in modules used by @dace.program

Modules called from a @dace.program can now take lists, tuples, and dicts of
arrays (captured with nested example values and named as the Python frontend
binds the elements), and may read global tensors or plain tensor attributes:
those become closure arrays reported after conversion and are re-evaluated on
every call.

Shape guards are simplified with the shape environment's replacements before
translation, which removes spurious warnings about sizes that Dynamo unified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_is_slice accepted any View whose strides all appear among the Array's strides,
including permutations such as a transpose (strides (1, n) of an array with
strides (n, 1)). RedundantReadSlice and RedundantWriteSlice then rewrote the
memlets without permuting their subsets, so a copy through a transposed view
copied the untransposed data. A slice must keep the order of the Array's
dimensions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….compile)

AOTAutograd's backward graphs were run eagerly; they are now lowered and
compiled like the forward graphs (each with the symbol names of its forward
graph, bound at forward time since AOTAutograd may compile the backward lazily).

- New lowerings for backward operators: convolution_backward (input, weight, and
  bias gradients as maps with write-conflict resolution), max-pool backward
  (scatter through the saved indices), and index_put with one index tensor
  (accumulating, e.g., embedding gradients).
- Attention is traced with the math SDPA kernel, so its backward consists of
  operators the frontend lowers instead of the fused CPU flash-attention
  backward.
- CompiledGraph takes AOTAutograd's boxed calling convention (as an instance
  attribute, which survives AOTAutograd's functools.wraps wrappers) and still
  accepts positional inputs.

Tests compare outputs and gradients with eager PyTorch for MLPs, layer norm with
softmax, a CNN (padding, stride, groups, max-pool), a transformer block, and an
embedding with repeated tokens, compiling forward and backward once across
sizes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion connectors, and cast stripping

- Conditional tasklets: the condition of `out = a if cond else b` (also wrapped in
  a cast) or `if cond: out = a` may now be any expression of the tasklet's
  inputs, not only a boolean input connector. The code is parsed with ast
  instead of regular expressions, the backward tasklet reads every connector the
  condition uses, the branches may read different inputs, and only tasklets that
  read a boolean array take the conditional-array-assignment path.
- Data forwarding no longer asserts when a tasklet reads the same array through
  two connectors (e.g., `x * x`); the path into the backward map is shared.
- ReverseReduce swapped the required and given gradient connector names. This
  was invisible with unnamed connectors (as the NumPy frontend creates them) and
  failed for Reduce nodes with named `_in`/`_out` connectors.
- Casts were stripped with a non-greedy regular expression that ended at the
  first closing parenthesis, so `dace.float64((a - b) * (a - b))` was
  differentiated as `a - b*(a - b)`. They are now removed with an AST
  transformation, which also covers floating-point casts spelled with C type
  names (`double(...)`, `float(...)`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tbennun and others added 30 commits October 13, 2026 23:33
The expansions communicate a count of elements from a pointer. Simplification
may hand a collective a strided view (e.g., a transpose) in place of the
contiguous copy a frontend wrote, and a packed permutation is contiguous memory
but not in row-major order. pack_noncontiguous_buffers routes such buffers
through contiguous transients, with one transient for in-place buffers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI runs the MPI tests on two ranks. The subgroup of every rank but the last
has one member there, and send/recv runs over the world in reverse order, so
communicator ranks still differ from world ranks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ibrary nodes

Dynamo rewrites torch.distributed calls into _c10d_functional operators. They
now lower to dace.libraries.comm nodes:
- all_reduce(_coalesced), all_gather_into_tensor(_coalesced),
  reduce_scatter_tensor(_coalesced), broadcast and all_to_all_single (even
  splits as Alltoall, uneven as Alltoallv), including the autograd-aware
  variants of backward graphs; collectives are blocking, so wait_tensor of
  their results is the identity.
- isend, irecv and batch_p2p_ops (TORCHDYNAMO_ENABLE_P2P_COMPILATION=1) as
  Isend/Irecv; wait_tensor emits the Wait, which orders the readers of a
  received tensor after it.

Programs communicate over the communicators of torch's process groups, taken as
arguments (dace_comm_<group>_<backend>) and supplied by the runtime from a
per-process cache (dynamo/distributed.py): NCCL for GPU tensors, bootstrapped
over the process group itself (no MPI, works under torchrun); MPI for CPU
tensors, over the MPI ranks of the group.

Tests (tests/torch_dynamo/test_distributed.py, under mpirun with a gloo process
group) cover in-place all_reduce (sum/avg/max), gather/scatter/broadcast of a
strided view, even and uneven all-to-all, a subgroup, a ring exchange and
training through the autograd collectives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The c_char array field stopped at the first zero byte of the binary ID. Tested
on a GPU: collectives and a self-exchange over an NCCL communicator created in
Python, with no MPI in the program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AOTAutograd desugars DTensors into local tensors and functional collectives,
and passes the DeviceMesh of each DTensor in and out of the graph as an opaque
object so that it can rewrap the results. The importer binds such inputs as
constants and returns them as the arguments themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rank 0 broadcasts its compilation error instead of a build folder, and the
final barrier becomes an exchange of load outcomes, so a failure no longer
leaves the other ranks waiting. A rank that holds an SDFG checks that it is
the one rank 0 compiled. Only Get_rank, bcast and allgather are used, so
non-mpi4py communicators work too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aten.constant_pad_nd lowers to a fill and a copy of the kept part of the
input; negative amounts narrow it. Symbols get the sign Dynamo's value ranges
give them: a symbolic Python integer (e.g., a padding amount) may be
negative, and assuming otherwise simplified Max(-pad, 0) to 0 and wrote out
of bounds. Strides torch writes as Max(1, n) become n, so contiguous layouts
are recognized (the all-gather of an unevenly sharded DTensor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DaceBackendOptions.distributed_compilation (and the capture/export options)
compiles on rank 0 and loads the build on the other ranks through
dace.sdfg.utils.distributed_compile, over the default process group (so
torchrun programs work without MPI). A failure on any rank, or a program that
differs from rank 0's, raises on every rank.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allreduce, Allgather, ReduceScatter, Bcast, Alltoall and Alltoallv derive
from NonblockingCommNode, whose nonblocking property starts the operation
and writes a request that a Wait completes. MPI calls the MPI_I* variant
(Alltoallv keeps its count arrays alive until completion); NCCL launches the
operation right away and tracks its stream for the Wait, with a token
request. Nonblocking nodes need contiguous buffers (a packed copy would not
outlive the operation), and MPI cannot average without blocking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dace.libraries.comm.schedule.overlap_communication moves nonblocking starts
as early, and their waits as late, as data, symbol and view dependencies
allow within each scope; a wait inherits the buffers of the starts whose
requests it completes. Starts keep their order relative to other starts and
blocking operations (collectives start in the same order on every process),
and waits keep theirs. Every communication node then gets a state of its
own, which state fusion keeps (communication has side effects), so code
generation runs start, independent work and wait in this order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A start moves ahead of the work before it together with the nodes it
depends on, so that independent work that precedes its producers also runs
while the communication is in flight; a wait likewise moves behind the work
that does not depend on it. Relative orders within each group are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collectives lower to nonblocking comm nodes whose wait_tensor emits the
Wait (MPI averages reduce the sum, and the wait divides), like point-to-point
operations; every Wait reads all buffers of its operation, so transients
live until it. The importer runs dace.libraries.comm.schedule
.overlap_communication on the schedule tree of graphs that communicate, so
that independent work runs between start and wait.
DaceBackendOptions.overlap_communication=False keeps blocking collectives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A collective runs asynchronously on the stream of its node already, so a
nonblocking one is launched right away like any other and needs no request,
stream tracking or Wait: code generation orders its readers on the device
and runs independent work on other streams. A Wait (and Waitall) connected
to GPU memory makes its stream wait for the pending point-to-point streams
through events, instead of synchronizing them on the host, which also keeps
code generation from forcing it onto the default stream. Isend accepts
ordering output connectors, so that a send can be ordered before its wait
through the sent buffer: a request or token in host memory makes the host
wait for the stream. The overlap pass leaves NCCL nodes in the states of
their neighbors, whose end would make the host wait for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collectives on GPU tensors lower without a request or Wait: they run on the
stream of their node, whose readers code generation orders on the device.
Point-to-point operations have no requests either (a token in host memory
made the host wait for the stream): a send writes its buffer through an
ordering connector, which its Wait reads. MPI keeps nonblocking operations
with requests and waits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packing copies into and out of contiguous buffers are ordered on the GPU
stream of the node, so stream-ordered (NCCL or RCCL) expansions pack the
strided buffers of a nonblocking node like those of any other; only MPI,
whose Wait completes the operation later, still requires contiguous buffers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The requirement that a nonblocking node have contiguous buffers holds for
MPI only, so it moves out of pack_noncontiguous_buffers (and its
stream_ordered escape hatch) into NonblockingCommNode.validate_nonblocking,
which the MPI expansions call before packing, together with the request
check. NCCL/RCCL expansions pack as for any node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main replaced all_states() with a recursive states() (#2652).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…states()

A one-rank gloo group bootstraps NCCL in a single process, so the GPU test needs no MPI. Main replaced
all_states() with a recursive states() (#2652).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ept sliced operands

dace.libraries.ml.Convolution gets an ``activation`` property ('none'/'relu'). The pure expansion applies it while
writing the result; cuDNN uses cudnnConvolutionBiasActivationForward (with a zero bias from the plan when the layer
has none) and MIOpen a compiled fusion plan, both falling back to the separate calls when the library does not support
the configuration. FuseConvolutionActivation folds a ReLU map that is the only reader of a convolution's result into
the node, also when the map writes a slice of a concatenation; the convolution nodes therefore size their operands by
the memlet subset instead of the whole container. The TorchDynamo frontend applies the fusion when
``fuse_activations=True`` is passed to dace.ml.compile or dace.ml.to_sdfg (off by default until the fused vendor
kernels are measured broadly); the ATen Convolution wrapper carries the property.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

no-ci Do not run any CI or actions for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants