[DSV4] perf(tbo): overlap pure-TP all_reduce on TBO + Delay TP#1681
[DSV4] perf(tbo): overlap pure-TP all_reduce on TBO + Delay TP#1681ZhangLirong-amd wants to merge 4 commits into
Conversation
🏷️ CI GuideRuns automatically on every eligible PR before approval:
Heavy model tests:
|
There was a problem hiding this comment.
Pull request overview
This PR improves DeepSeek-V4 pure-TP + TBO prefill latency by overlapping the TP all-reduce with the partner ubatch’s compute via a new TBO-aware tbo_all_reduce custom op, and enables PrefillDelayer in TP-only + TBO mode to better coalesce prefills into effective ubatches.
Changes:
- Add a TBO-aware
torch.ops.aiter.tbo_all_reducepath that runs TP all-reduce on the TBO comm stream (opt-out viaATOM_TBO_TP_AR_MODE=inline). - Route DeepSeek-V4 attention (
wo_b) and MoEcombine_outputsTP reductions through the TBO-aware custom op in the pure TP+TBO (no DP) case. - Enable PrefillDelayer in TP-only + TBO mode (single-rank behavior: no cross-rank all_reduce), and add a benchmark scenario entry for TBO.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
atom/utils/tbo/ubatching.py |
Adds per-ubatch TP communicators and introduces an (currently unused) event intended for TP AR ordering. |
atom/utils/envs.py |
Updates PrefillDelayer docs; adds ATOM_TBO_TP_AR_MODE and a debug env var entry. |
atom/models/deepseek_v4.py |
Enables TBO-aware TP all-reduce routing for wo_b and MoE combine_outputs in the pure TP+TBO case. |
atom/model_ops/module_dispatch_ops.py |
Registers torch.ops.aiter.tbo_all_reduce custom op implementing overlap behavior under TBO. |
atom/model_ops/linear.py |
Adds tbo_aware flag to RowParallelLinear to route its TP reduction through the new custom op. |
atom/model_engine/prefill_delayer.py |
Adds TP-only (cpu_group=None) behavior and guards the DP all_reduce accordingly. |
atom/model_engine/engine_core.py |
Enables PrefillDelayer automatically in TP-only + TBO mode when allowed by env. |
.github/benchmark/models.json |
Adds a TBO benchmark variant configuration. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| global _TBO_TP_UBATCH_COMMS | ||
| if _TBO_TP_UBATCH_COMMS is not None: | ||
| return _TBO_TP_UBATCH_COMMS[ubatch_id] |
| # Fresh per-step event that serializes the pure-TP all_reduce across | ||
| # ubatches (see `_TP_AR_ORDER_EVENT` note above). A new event each step | ||
| # means the first ubatch's AR never waits on a prior step's recording. | ||
| _TP_AR_ORDER_EVENT = torch.cuda.Event() |
| # Debug: in split_attn_metadata, cross-check the host-computed per-ubatch | ||
| # max_seqlen_q/k against the device tensors and fall back to the device value | ||
| # (with a warning) on mismatch. Off by default — turn on to diagnose stale | ||
| # attach_tbo_cpu_lens snapshots. Adds two .item() D2H syncs per ubatch. | ||
| "ATOM_TBO_DEBUG_CHECK": lambda: ( | ||
| os.getenv("ATOM_TBO_DEBUG_CHECK", "0") == "1" | ||
| ), |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
atom/utils/tbo/ubatching.py:610
- make_tbo_contexts() allocates a fresh _TP_AR_ORDER_EVENT each step, but _TP_AR_ORDER_EVENT/tbo_get_tp_ar_order_event() are not referenced anywhere in the repo. This adds per-step overhead and is misleading; either wire the event into the pure-TP all_reduce ordering logic or stop creating it until it’s actually used.
global _NUM_UBATCHES, _CURRENT_CONTEXTS, _TP_AR_ORDER_EVENT
assert num_micro_batches > 1
_NUM_UBATCHES = num_micro_batches
# Grow the global context list if needed
while len(_CURRENT_CONTEXTS) < num_micro_batches:
_CURRENT_CONTEXTS.append(None)
# Fresh per-step event that serializes the pure-TP all_reduce across
# ubatches (see `_TP_AR_ORDER_EVENT` note above). A new event each step
# means the first ubatch's AR never waits on a prior step's recording.
_TP_AR_ORDER_EVENT = torch.cuda.Event()
| global _TBO_TP_UBATCH_COMMS | ||
| if _TBO_TP_UBATCH_COMMS is not None: | ||
| return _TBO_TP_UBATCH_COMMS[ubatch_id] | ||
|
|
||
| from aiter.dist.device_communicators.communicator_pynccl import ( | ||
| PyNcclCommunicator, | ||
| ) | ||
| from aiter.dist.parallel_state import get_tp_group | ||
|
|
||
| tp = get_tp_group() | ||
| if tp.world_size == 1: | ||
| _TBO_TP_UBATCH_COMMS = [None] * _NUM_UBATCHES | ||
| return None | ||
|
|
||
| # One independent communicator per ubatch. Construction runs a warmup | ||
| # all_reduce (a collective), so every rank must build the same number in the | ||
| # same order — guaranteed here because all ranks run TBO in lockstep and hit | ||
| # this on the same forward. Bound to the TP cpu_group (non-NCCL backend), | ||
| # exactly like the shared pynccl_comm. | ||
| comms = [] | ||
| for _ in range(_NUM_UBATCHES): | ||
| comms.append(PyNcclCommunicator(group=tp.cpu_group, device=tp.device)) | ||
| _TBO_TP_UBATCH_COMMS = comms | ||
| return _TBO_TP_UBATCH_COMMS[ubatch_id] |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
atom/utils/tbo/ubatching.py:318
- _TBO_TP_UBATCH_COMMS is lazily initialized from the TBO worker threads, but there is no synchronization protecting the first-time construction. Because PyNcclCommunicator construction runs a warmup collective (per the comment below), two ubatch threads racing here can interleave communicator creation differently across ranks and hang. Add a module-level lock dedicated to guarding communicator initialization.
# Per-ubatch independent pynccl TP communicators for the pure-TP all_reduce
# overlap (see `tbo_all_reduce`).
_TBO_TP_UBATCH_COMMS: "list | None" = None
atom/utils/tbo/ubatching.py:328
- tbo_get_ubatch_tp_comm() lazily builds PyNcclCommunicator instances without any mutual exclusion. Under TBO, both ubatch threads can hit this on the first forward, racing the constructor's warmup all_reduce and potentially deadlocking. Guard the initialization with the shared lock and double-check after acquiring it.
global _TBO_TP_UBATCH_COMMS
if _TBO_TP_UBATCH_COMMS is not None:
return _TBO_TP_UBATCH_COMMS[ubatch_id]
from aiter.dist.device_communicators.communicator_pynccl import (
atom/utils/tbo/ubatching.py:610
- _TP_AR_ORDER_EVENT is created fresh each step and exported via tbo_get_tp_ar_order_event(), but it is not used anywhere (the new TBO all_reduce path in module_dispatch_ops doesn’t consult it). This dead state + docstring (“serializes the pure-TP all_reduce across ubatches”) is misleading and risks future callers relying on an ordering guarantee that doesn’t exist. Either wire the event into the overlap all_reduce path (e.g., record/wait between ubatches) or remove the event + getter entirely.
# Fresh per-step event that serializes the pure-TP all_reduce across
# ubatches (see `_TP_AR_ORDER_EVENT` note above). A new event each step
# means the first ubatch's AR never waits on a prior step's recording.
_TP_AR_ORDER_EVENT = torch.cuda.Event()
| # Route wo_b's TP all_reduce through the TBO-aware custom op only for the | ||
| # pure TP+TBO case (see _tbo_aware_tp_reduce). Non-TBO / TBO+DP keep the | ||
| # original all_reduce untouched. | ||
| self.tbo_aware = _tbo_aware_tp_reduce(tp_size) | ||
| self.n_local_heads = args.n_heads // tp_size |
| if getattr(self, "tbo_aware", False): | ||
| y = torch.ops.aiter.tbo_all_reduce(y) | ||
| else: | ||
| y = get_tp_group().all_reduce(y, ca_fp8_quant=False) |
There was a problem hiding this comment.
let’s have a ATOM AR layer to deal with, so we don't have to add these "if..else.." for every model/op with AR
There was a problem hiding this comment.
i would like something like from ato,.model_ops.communication_op import tensor_model_parallel_all_reduce
| _TP_AR_ORDER_EVENT: "torch.cuda.Event | None" = None | ||
|
|
||
|
|
||
| def tbo_get_tp_ar_order_event() -> "torch.cuda.Event | None": |
Motivation
For each 16k prefill, time 567ms -> 495ms, about 15% improve for each prefill
Technical Details
Test Plan
Test Result
Submission Checklist