Skip to content

dynamo: keep native NCCL collectives out of Myelin ForeignNode fusion (cast-boundary)#4388

Draft
pkisfaludi-nv wants to merge 3 commits into
pytorch:mainfrom
pkisfaludi-nv:myelin-cast-boundary-native-collectives
Draft

dynamo: keep native NCCL collectives out of Myelin ForeignNode fusion (cast-boundary)#4388
pkisfaludi-nv wants to merge 3 commits into
pytorch:mainfrom
pkisfaludi-nv:myelin-cast-boundary-native-collectives

Conversation

@pkisfaludi-nv

Copy link
Copy Markdown

DRAFT — do not merge (author reviews). Depends on #4380.

Summary

On top of #4380 (which routes native NCCL collectives at the op's process subgroup), this adds the remaining changes that let the sharded LTX-2.3 DiT lower to a single TensorRT engine with native in-engine collectives (no TRT-LLM, no ONNX, no NCCL plugin). All changes are in py/torch_tensorrt/dynamo/conversion/impl/nccl_ops.py.

The three changes

  1. Myelin fusion cast-boundary — _coll_boundary(). Myelin fuses a native IDistCollectiveLayer into a ForeignNode and then fails with CollectiveOperation ... not supported In toMyelinCommKind (see Native DistCollective fails at build time when fused into a Myelin ForeignNode (large multi-device graph) #4381). Inserting a genuine dtype reformat (fp32 by default, _COLL_BOUNDARY_DTYPE) on the collective's input/output creates a precision-change boundary Myelin will not fuse across (identity / same-dtype casts get folded away — only a real reformat works). This lets the full 48-block DiT compile to one engine with collectives intact.
  2. num_ranks before get_output(). layer.num_ranks must be set before layer.get_output(0) so Myelin can infer the collective's output shape (e.g. all_gather dim0 = in_dim0 * num_ranks). Upstream sets it after, tripping AllGather requires world_size (>1) to be set for shape inference ... Call myelinGraphSetWorldSize().
  3. AG_VIA_ALLREDUCE=1 (opt-in). Even with (2), TRT-11 Myelin can reject a fused native ALL_GATHER shape (needs the graph world_size, which torch-tensorrt never sets at build). The opt-in workaround expresses all_gather as scatter-this-rank's-slice-into-a-zero-padded-buffer then ALL_REDUCE(SUM) — numerically identical, and ALL_REDUCE's output shape == input shape so no build-time world_size is needed. Used for the video→audio KV gather in CP.

Also adds _mark_collective_boundary() (env TRT_COLL_BOUNDARY) as a debug-mark helper.

Relationship to #4380 / #4381

Validation

  • CP8: full 22B DiT → 1 TRT engine, trt_engine_submodules=1, torch_coll_tokens=0 (zero PyTorch-side collectives), ENGINE_RAN_OK; parity vs eager CP8 cos 0.99602 (video) / 0.99965 (audio) — PASS. (8× A100-SXM4-80GB.)
  • CP4×TP2: single-engine cosine-correct end-to-end when combined with the internal TensorRT/Myelin per-subgroup-comm routing.

Notes

  • The cast-boundary (fp32 default) and AG_VIA_ALLREDUCE are env-gated and default-safe (world-group / no-op) so CP8 and existing single-group paths are unchanged.
  • Applied uniformly to the native DistCollective converters (all_gather / reduce_scatter / all_reduce / all_to_all and the scatter/gather variants) for consistency; the num_ranks reorder is a safe correctness fix for all.

pkisfaludi-nv and others added 2 commits July 4, 2026 04:12
The native DistCollective converters hardcoded the world group
(groups = np.arange(world_size), num_ranks = world_size), so a collective
could only ever run over all ranks. This breaks 2-D device meshes where a
collective runs over a subgroup -- e.g. Ulysses/context-parallel all_to_all
over a CP subgroup while tensor-parallel all_reduce uses a separate TP
subgroup. TensorRT then rejects it, e.g.:
  "All to All requires first input dimension to be divisible by nbRanks"
because nbRanks was the world size (8) rather than the subgroup size (4).

Resolve the participating ranks from the collective's group_name (already
carried by the tensorrt::fused_nccl_* custom ops) and pass them to
add_dist_collective, setting num_ranks = len(groups). Falls back to the world
group when the group can't be resolved. Threads group_name from each fused-op
converter (all_gather/reduce_scatter/all_reduce/all_to_all/scatter/gather).

Validated on an 8xA100 CP4xTP2 run (LTX-2.3 DiT): supplying the CP subgroup's
ranks to add_dist_collective clears the divisibility build error for the
Ulysses all_to_all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (cast-boundary)

Adds, on top of pytorch#4380 (subgroup routing), the changes needed to lower the sharded
LTX-2.3 DiT to a single TensorRT engine with native in-engine collectives:

1. _coll_boundary(): insert a genuine dtype reformat (fp32 by default,
   _COLL_BOUNDARY_DTYPE) on each DistCollective's input/output. Myelin folds
   identity/same-dtype casts away, so only a real reformat keeps the collective out
   of a ForeignNode -> the 48-block DiT compiles to one engine with collectives
   intact. See pytorch#4381.
2. Set layer.num_ranks BEFORE layer.get_output(0) so Myelin can infer the collective
   output shape (all_gather dim0 = in_dim0 * num_ranks); upstream set it after,
   tripping "AllGather requires world_size (>1) ... call myelinGraphSetWorldSize()".
3. AG_VIA_ALLREDUCE=1 (opt-in): express all_gather as place-into-slot + ALL_REDUCE(SUM)
   whose output shape == input shape, so no build-time world_size is needed.
   Numerically identical to all_gather.

Also adds _mark_collective_boundary() (env TRT_COLL_BOUNDARY) as a debug-mark helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@meta-cla

meta-cla Bot commented Jul 7, 2026

Copy link
Copy Markdown

Hi @pkisfaludi-nv!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@github-actions github-actions Bot added component: conversion Issues re: Conversion stage component: core Issues re: The core compiler component: converters Issues re: Specific op converters component: api [Python] Issues re: Python API component: dynamo Issues relating to the `torch.compile` or `torch._dynamo.export` paths labels Jul 7, 2026
@narendasan narendasan requested a review from apbose July 7, 2026 17:12
@pkisfaludi-nv

Copy link
Copy Markdown
Author

We reproduced the follow-up setCommunicator() failure reported by @apbose on
Qwen3-30B-A3B and found a TensorRT core bug in addition to the original
ForeignNode build failure from #4381.

The engine does not derive mIsMdEngine from the presence of signal/wait
scaffolding. At deserialization it checks the serialized runners' getNbRanks().
For a Myelin runner, both the DistCollective and Attention translators assigned
one graph-wide mNbRanks field directly. Therefore a rank-1 attention op
translated after a rank-2 collective could overwrite the value and make a
compute-heavy collective engine deserialize as single-device even though its
comm_allreduce instruction remained present.

The core fix accumulates the maximum rank count across all MD-aware operations:

mNbRanks = std::max(mNbRanks, params.nbRanks);
mNbRanks = std::max(mNbRanks, contextParallelDegree);

Internal TensorRT MRs created with git trt mr:

The focused TensorRT regression constructs
ALL_REDUCE(nbRanks=2) -> ATTENTION(nbRanks=1). The old assignment fails
engine.isMdEngine(); the max-rank fix passes on A100.

Related CP4xTP2 subgroup-routing MRs are:

PR #4388's precision boundary is still useful: it prevents the unsupported
Myelin absorption that causes the original #4381 build failure. The new core MR
fixes the separate runtime MD-classification failure instead of relying on
isolation as a workaround.

I also added a Torch-TensorRT two-rank regression test in commit ed8d41a6d.
It verifies compute + native all-reduce compile into exactly one TRT engine,
that no collective escapes into the outer PyTorch graph, and eager parity.
Actual 2xA100 pytest result: 1 passed in 19.32s.

@github-actions github-actions Bot added the component: tests Issues re: Tests label Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: api [Python] Issues re: Python API component: conversion Issues re: Conversion stage component: converters Issues re: Specific op converters component: core Issues re: The core compiler component: dynamo Issues relating to the `torch.compile` or `torch._dynamo.export` paths component: tests Issues re: Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant