Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
16e4f7b
[PCP] Fewer prefill collectives per layer, no reindex readback
yitingw1 Oct 28, 2026
dc90ce2
[PCP] DSpark: project context KV on each rank's own rows
yitingw1 Oct 28, 2026
7017852
[PCP] Grouped gather: fall back off aiter's 2/4/8 ranks; check row co…
yitingw1 Oct 28, 2026
222feeb
fix(config): key the compile cache on the KV cache dtype
yitingw1 Oct 28, 2026
b491763
[PCP] TBO: hold comm-stream gather outputs until the compute stream r…
yitingw1 Oct 28, 2026
6b49222
[PCP] TBO: run the PCP comm phases through tbo_on_comm
yitingw1 Oct 28, 2026
9d1db12
Merge remote-tracking branch 'origin/main' into yitingw1/pcp_opt
yitingw1 Oct 28, 2026
e182354
[PCP] TBO: hold every comm-stream output; one wire format for grouped…
yitingw1 Oct 29, 2026
e8ff821
test(tbo): stub aiter's communication_op in the TP all-reduce test
yitingw1 Oct 29, 2026
7e2920f
[PCP] Grouped gather: drop the 6-rank special case, rely on aiter
yitingw1 Oct 29, 2026
fd0893d
test(h2d): give the PCP reindex test the host prefill indptr
yitingw1 Oct 30, 2026
319ffe1
test(h2d): bring the V4 indexer publication GPU test up to date
yitingw1 Oct 30, 2026
fd912b7
DSV4 PCP: assert host and GPU paged-prefill indptrs agree in length
yitingw1 Nov 2, 2026
c889ff9
Merge branch 'main' into yitingw1/pcp_opt
yitingw1 Nov 2, 2026
65fed29
Merge remote-tracking branch 'origin/main' into yitingw1/pcp_opt
yitingw1 Nov 10, 2026
c9b9de7
DSV4 PCP: stage only this rank's paged-prefill rows on main's stage/f…
yitingw1 Nov 10, 2026
aee343c
Drop the duplicate kv_cache_dtype compile-hash factor left by the mai…
yitingw1 Nov 10, 2026
1e582ae
Merge remote-tracking branch 'origin/main' into yitingw1/pcp_opt
yitingw1 Nov 11, 2026
5e4e8bb
fix(spec_decode): check target_pcp_split before PCP world size in pcp…
yitingw1 Nov 11, 2026
c5b86da
fix(v4 attn): make _apply_pcp_reindex take cu_q / block_tables explic…
yitingw1 Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions atom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2822,6 +2822,10 @@ def compute_hash(self) -> str:
factors.append(bool(getattr(self.dcp_config, "indexer_dcp_only", False)))
factors.append(self.enable_dp_attention)
factors.append(self.index_cache_dtype)
# The KV cache dtype is fixed at trace time too: the DSpark drafter
# branches on it (`dspark_fp8_planned`), so a bf16 run over an fp8 run's
# cache loads a graph built for the other window layout.
factors.append(self.kv_cache_dtype)
# DeepSeek-V4.1 decoder SWA bounded replay compiles the backbone as two
# graphs (early + late layers) instead of one; both compile under the
# `backbone` tag, so the flag must key the artifacts.
Expand Down
72 changes: 60 additions & 12 deletions atom/distributed/pcp_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
get_prefill_context_model_parallel_world_size,
)

from atom.utils import upload_numpy


class PcpBalGroup(NamedTuple):
"""One request group for PCP+TBO request-boundary split prefill.
Expand Down Expand Up @@ -105,7 +107,12 @@ def pcp_round_robin_split(
# Divisibility by pcp_size is guaranteed upstream by pcp_pad_len (callers
# pad before splitting); the view below would error if violated.
rest = tuple(input_.shape[1:])
return input_.view(-1, pcp_size, *rest)[:, pcp_rank].contiguous()
# Not `.contiguous()`: a one-row shard counts as contiguous while its row
# stride is still pcp_size, which kernels that check strides reject (aiter's
# cached-positions RoPE).
return input_.view(-1, pcp_size, *rest)[:, pcp_rank].clone(
memory_format=torch.contiguous_format
)


def pcp_allgather_rerange(
Expand All @@ -127,16 +134,47 @@ def pcp_allgather_rerange(
group = get_pcp_group()
# aiter all_gather(dim=0) returns rank-major concat: [pcp*L, *rest].
gathered = group.all_gather(input_.contiguous(), dim=0)
local_len = input_.shape[0]
rest = tuple(input_.shape[1:])
return _rerange(gathered, pcp_size, input_.shape[0])


def gather_rows(gather, rows):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gather_rows now lives in pcp_utils, but its callers include DP (dp_gather_routed_rows) and the TP/DP comm_fused_moe, so comm_fused_moe imports a general gather from the PCP module. It belongs in a general distributed comm module (next to token_transport or similar), with PCP importing it too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to atom/distributed/comm_utils.py as a transport-agnostic helper. It now only reinterprets 1-byte dtypes as uint8 and passes everything else through unchanged; the float carriers are gone.

"""`gather(list of tensors) -> list of tensors` over `rows`, one-byte
dtypes carried as uint8: aiter's grouped `all_gatherv` copies bytes
whatever the dtype, but its PyNCCL fallback has no fp8 / e8m0 / fp4x2."""
gathered = gather(
[t.view(torch.uint8) if t.element_size() == 1 else t for t in rows]
)
return tuple(g.view(t.dtype) for g, t in zip(gathered, rows))


def pcp_allgather_rerange_many(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pcp_allgather_rerange (group.all_gather) and pcp_allgather_rerange_many (device_communicator.all_gatherv via gather_rows) do the same thing by two transports, with different dtype handling and fallbacks. The single-tensor one could be _many([x])[0]. Callers that gather several tensors back to back should then use one call:

  • drafter.py:490, which this PR touches, gathers each aux buffer separately;
  • attention_mla.py:3079-3081, three gathers;
  • deepseek_v2.py:1732-1733 and :2857-2861.

Fixing it here and leaving those (Fix-then-sweep) keeps both paths alive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pcp_allgather_rerange(x) is now pcp_allgather_rerange_many([x])[0], and the back-to-back call sites you listed (MLA, V2 indexer and hs+aux, the sglang plugin, the V4 extend-K path) now issue one grouped gather. The few remaining single calls are either not back-to-back or, in model_runner, per-group gathers with different row counts that _many can't batch.

inputs: list[torch.Tensor], pcp_size: int | None = None
) -> list[torch.Tensor]:
"""`pcp_allgather_rerange` of every tensor in `inputs` (same row count) in
one grouped collective."""
if pcp_size is None:
pcp_size = get_pcp_world_size()
if pcp_size <= 1:
return list(inputs)
assert all(t.shape[0] == inputs[0].shape[0] for t in inputs), [
t.shape[0] for t in inputs
]
comm = get_pcp_group().device_communicator

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aiter has no kernel for pcp 6 yet, and we pin no aiter version. keep the per-tensor fallback until #7149 lands?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. -pcp 6 depends on ROCm/aiter#7149, which lands first. Restoring the fallback would not make old aiter work either: there the 6-rank FusedMoE all_gatherv throws, and the 6-rank 1-stage custom all-reduce reads out of bounds during decode graph capture, so -pcp 6 with custom AR is already broken on main.

gathered = gather_rows(
lambda ts: comm.all_gatherv(ts, dim=0), [t.contiguous() for t in inputs]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pcp 6 will die inside aiter without #7149, and nothing checks the size at startup. validate at engine init?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. -pcp 6 depends on ROCm/aiter#7149, which lands first. Restoring the fallback would not make old aiter work either: there the 6-rank FusedMoE all_gatherv throws, and the 6-rank 1-stage custom all-reduce reads out of bounds during decode graph capture, so -pcp 6 with custom AR is already broken on main.

)
return [_rerange(g, pcp_size, t.shape[0]) for g, t in zip(gathered, inputs)]


def _rerange(gathered: torch.Tensor, pcp_size: int, local_len: int) -> torch.Tensor:
# rank-major [pcp, L, *rest] -> transpose -> token-major [L, pcp, *rest]
# -> flatten to [L*pcp, *rest] == original global order.
out = (
rest = tuple(gathered.shape[1:])
return (
gathered.view(pcp_size, local_len, *rest)
.transpose(0, 1)
.reshape(pcp_size * local_len, *rest)
)
return out


# ==== MoE-path PCP collectives (rank-major gather + reduce_scatter) ====
Expand Down Expand Up @@ -246,10 +284,22 @@ def pcp_pad_dense(t: torch.Tensor, n_pad: int) -> torch.Tensor:
return torch.cat([t, t.new_zeros(n_pad, *t.shape[1:])], dim=0)


def pcp_local_indptr(indptr: np.ndarray, owned: np.ndarray) -> np.ndarray:
"""Host side of `pcp_reindex_ragged`: the prefix sum of the `owned`
queries' segment lengths in `indptr`. Owned ids past its last query are
padding queries, whose segments are empty."""
lens = np.diff(indptr)
lens = np.pad(lens, (0, max(int(owned.max(initial=-1)) + 1 - lens.shape[0], 0)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nothing checks the host indptr matches the device one. add a cheap shape/last-value assert so a mismatch fails loud instead of giving wrong indices?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in fd912b7: a length check at both reindex entries (rank and PCP+TBO group). It reads shapes only; comparing values would need a device read-back, which is the sync this PR removes.

out = np.zeros(owned.shape[0] + 1, dtype=np.int32)
np.cumsum(lens[owned], out=out[1:])
return out


def pcp_reindex_ragged(
kv_indptr: torch.Tensor, # [T_global + 1] int32 — global per-query prefix sum
kv_indices: torch.Tensor, # [kv_indptr[-1]] — ragged packed values
owned_q: torch.Tensor, # [T_local] long — global query ids this rank owns
indptr_local_cpu: np.ndarray, # [T_local + 1] — `pcp_local_indptr`
) -> tuple[torch.Tensor, torch.Tensor]:
"""Reindex a ragged (indptr, indices) pair down to this rank's queries.

Expand All @@ -262,17 +312,15 @@ def pcp_reindex_ragged(
Used to shard the per-query prefill index buffers (kv_indptr/kv_indices
_prefix_swa / _extend) to 1/W while the values themselves still point into
the full KV (paged unified_kv) / full extend kv tensor.

`indptr_local` comes from the host (`indptr_local_cpu`), so sizing the
output reads nothing back from the device.
"""
device = kv_indptr.device
owned_q = owned_q.to(device)
starts = kv_indptr[owned_q] # [T_local]
ends = kv_indptr[owned_q + 1] # [T_local]
lens = ends - starts # [T_local] per-owned-query segment length
indptr_local = torch.zeros(
owned_q.shape[0] + 1, dtype=kv_indptr.dtype, device=device
)
torch.cumsum(lens, dim=0, out=indptr_local[1:])
total = int(indptr_local[-1].item())
indptr_local = upload_numpy(indptr_local_cpu, device)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance item 1: upload_numpy here runs every prefill forward, for each of the four fields. Its docstring reserves it for one-off and size-varying uploads ("A buffer uploaded every forward should be a CpuGpuBuffer"). With the builder producing the local indptr (comment on deepseek_v4_attn.py:406), this upload and the arange / searchsorted / gather that follow go away. If it stays, it needs a persistent CpuGpuBuffer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The per-step reindex kernel and its asserts are removed (see the indptr comment above), and so is the csa_indices.fill_(-1); totals are read from the host-side staging arrays, so this path has no D2H.

total = int(indptr_local_cpu[-1])
if total == 0:
return indptr_local, kv_indices.new_empty(0)
# Build a gather map: for each output slot, which source index to read.
Expand Down
30 changes: 27 additions & 3 deletions atom/model_ops/attentions/deepseek_v4_attn.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
get_pcp_rank,
get_pcp_world_size,
pcp_is_enabled,
pcp_local_indptr,
pcp_owned_rows_by_request,
pcp_pad_dense,
pcp_pad_indptr,
Expand Down Expand Up @@ -388,6 +389,9 @@ class AttentionMetaData_DSV4(AttentionMetaData):
(same `kv` shared by all 3 ratios; one builder pass)."""
kv_indptr_extend: torch.Tensor | None = None
"""[total_tokens + 1] int32 GPU — packed cumsum of `extend_count`."""
paged_prefill_indptr_cpu: dict[str, np.ndarray] | None = None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paged_prefill_indptr_cpu mirrors the four kv_indptr_* fields in a string-keyed dict, so two copies have to be kept in step by hand. The length asserts added in fd912b7 are there to catch them drifting. "paged" also doesn't fit kv_indptr_extend. The builder already has these indptrs in numpy before upload. Computing the rank-local (or PCP+TBO group-local) indptr there and uploading only that would remove the mirror, the asserts and the per-step re-uploads in pcp_reindex_ragged (performance item 1) in one change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We went further than mirroring: each rank now stages its own indptrs from numpy through the CpuGpuBuffer publication group and launches the paged-prefill index kernel only over the tokens it owns, so the host mirror, the pad/reindex kernels and upload_numpy are all removed. What remains per step is a small arange + gather for the indexer's positions. On V4-Pro -tp 1 -pcp 8, ISL 16K, mbt 4096, TTFT drops 9.8 % at concurrency 1 and 11.1 % at concurrency 8 against the PR base (11.0 % / 8.5 % with --enable-tbo prefill). That arm also carries the grouped-gather changes, so it is the combined effect.

"""Host copies of the four `kv_indptr_*` above, keyed by field name. PCP
reindexes from these, so it never reads the GPU copies back."""


class DeepseekV4Backend(AttentionBackend):
Expand Down Expand Up @@ -2868,10 +2872,13 @@ def _apply_pcp_reindex(
# on its own pcp pad). Either way the divisor is pcp_size.
padded_total = pcp_pad_len(total_tokens, pcp_size)
n_pad = padded_total - total_tokens
# This rank's round-robin queries, generated on the GPU.
# This rank's round-robin queries, on the GPU and on the host.
owned_q = torch.arange(get_pcp_rank(), padded_total, pcp_size, device=device)
owned_cpu = np.arange(get_pcp_rank(), padded_total, pcp_size)

# --- ragged per-query buffers: pad indptr to padded_total, then 1/W ---
indptr_cpu = attn_metadata.paged_prefill_indptr_cpu
Comment thread
yitingw1 marked this conversation as resolved.
Resolved
local_indptr_cpu = {}
for ind_attr, idx_attr in (
("kv_indptr_prefix_swa", "kv_indices_prefix_swa"),
("kv_indptr_prefix_csa", "kv_indices_prefix_csa"),
Expand All @@ -2883,9 +2890,14 @@ def _apply_pcp_reindex(
if indptr is None or indices is None:
continue
indptr = pcp_pad_indptr(indptr, n_pad) # dummy queries: 0-length KV
new_indptr, new_indices = pcp_reindex_ragged(indptr, indices, owned_q)
local_cpu = pcp_local_indptr(indptr_cpu[ind_attr], owned_cpu)
new_indptr, new_indices = pcp_reindex_ragged(
indptr, indices, owned_q, local_cpu
)
setattr(attn_metadata, ind_attr, new_indptr)
setattr(attn_metadata, idx_attr, new_indices)
local_indptr_cpu[ind_attr] = local_cpu
attn_metadata.paged_prefill_indptr_cpu = local_indptr_cpu

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overwrites the full-batch indptr in place, a second call would silently give too-short kv. keep it local instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each metadata object is reindexed once: the non-TBO path reindexes the full batch once, and the PCP+TBO path reindexes each group's fresh ub once while reading the untouched full src. The device indptrs are already replaced in place (as on main). Writing the host copy back keeps the two in agreement; keeping it local would leave a host copy describing the full batch next to rank-local device indptrs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this field is documented as the global host copies, now we write local ids into it.. use a separate field?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworded in fd912b7: the host copies mirror whichever kv_indptr_* the metadata currently carries; after either reindex (rank or PCP+TBO group) both describe the same rank- or group-local queries.


# --- dense per-token fields: pad then round-robin-slice to 1/W ---
if attn_metadata.skip_prefix_len_csa is not None:
Expand Down Expand Up @@ -3184,6 +3196,8 @@ def _build_ubatch_prefill_metadata_balanced(

# ---- per-token DSV4 fields sliced by the GLOBAL token range [gts,gte) ----
owned = torch.arange(gts, gte, device=device)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance item 2: a group's queries are the contiguous global range [gts, gte), and the host indptr is already here (src_cpu). For a contiguous range, the local indices are indices[src_cpu[gts]:src_cpu[gte]], a view, and the local indptr is src_cpu[gts:gte+1] - src_cpu[gts]. No torch.arange(gts, gte), no pcp_local_indptr, and no pcp_reindex_ragged kernels or upload. The general reindex is only needed for the round-robin rank split.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of slicing the whole-batch meta, each TBO group now stages its own ub{i}_ paged-prefill rows with the same per-rank split. test_v4_tbo_group_paged_prefill_indices_match_the_whole_batch checks the result against the whole-batch build for ws 2/3/4/6/8.

owned_cpu = np.arange(gts, gte)
ub.paged_prefill_indptr_cpu = {}
for ind_attr, idx_attr in (
("kv_indptr_prefix_swa", "kv_indices_prefix_swa"),
("kv_indptr_prefix_csa", "kv_indices_prefix_csa"),
Expand All @@ -3194,7 +3208,11 @@ def _build_ubatch_prefill_metadata_balanced(
indices = getattr(src, idx_attr, None)
if indptr is None or indices is None:
continue
ni, nx = pcp_reindex_ragged(indptr, indices, owned)
local_cpu = pcp_local_indptr(
src.paged_prefill_indptr_cpu[ind_attr], owned_cpu
)
ni, nx = pcp_reindex_ragged(indptr, indices, owned, local_cpu)
ub.paged_prefill_indptr_cpu[ind_attr] = local_cpu
# kv_indices_extend are ROW offsets into the per-fwd kv_full tensor.
# In the full metadata they index the WHOLE sequence's kv_full [0,T);
# for this group kv_full only holds the group's tokens (global order
Expand Down Expand Up @@ -3856,6 +3874,12 @@ def _build_paged_prefill_meta(
attn_metadata.kv_indptr_prefix_csa = csa_indptr
attn_metadata.kv_indices_prefix_hca = hca_indices[:hca_total]
attn_metadata.kv_indptr_prefix_hca = hca_indptr
attn_metadata.paged_prefill_indptr_cpu = {
"kv_indptr_extend": ext_indptr_np,
"kv_indptr_prefix_swa": swa_indptr_np,
"kv_indptr_prefix_csa": csa_indptr_np,
"kv_indptr_prefix_hca": hca_indptr_np,
}
attn_metadata.skip_prefix_len_csa = skip_csa_gpu
attn_metadata.envelope_rows = envelope_rows

Expand Down
2 changes: 1 addition & 1 deletion atom/model_ops/fused_moe/comm_fused_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from aiter.ops.flydsl.moe_common import GateMode

from atom.config import get_current_atom_config
from atom.model_ops.fused_moe.routed_rows import gather_rows
from atom.distributed.pcp_utils import gather_rows
from atom.model_ops.topK import reassign_gathered_shared_ids
from atom.utils import envs
from atom.utils.custom_register import direct_register_custom_op
Expand Down
12 changes: 0 additions & 12 deletions atom/model_ops/fused_moe/routed_rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,6 @@ def pack_routed_rows(
return rows if x_scale is None else (*rows, x_scale)


_CARRIERS = {4: torch.float32, 2: torch.bfloat16, 1: torch.bfloat16}


def gather_rows(gather, rows):
"""`gather(list of tensors) -> list of tensors` over `rows`, each carried
as the float dtype of its byte width (int32 ids as fp32; fp8 / e8m0 byte
pairs as bf16, so a one-byte row's width must be even): a gather moves
bytes, but PyNCCL and the custom all-gather take float dtypes only."""
gathered = gather([t.view(_CARRIERS[t.element_size()]) for t in rows])
return tuple(g.view(t.dtype) for g, t in zip(gathered, rows))


def resolve_routed_rows_wire(
in_dtype: torch.dtype,
hidden_dim: int,
Expand Down
24 changes: 7 additions & 17 deletions atom/model_ops/module_dispatch_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,36 +220,26 @@ def tbo_all_reduce(x: torch.Tensor) -> torch.Tensor:
if not tbo_active():
return tensor_model_parallel_all_reduce(x)

# Default "inline": correct Plan-A baseline (no overlap, never hangs). Only
# move the AR onto the comm stream when explicitly opted into "overlap".
# "inline": the plain all-reduce on the compute stream (no overlap).
# "overlap" (default): on the comm stream, overlapping the partner ubatch.
if envs.ATOM_TBO_TP_AR_MODE != "overlap":
return tensor_model_parallel_all_reduce(x)

from atom.utils.tbo.ubatching import (
tbo_current_ubatch_id,
tbo_get_comm_stream,
tbo_get_ubatch_tp_comm,
tbo_switch_to_compute_sync,
tbo_yield_and_switch_from_compute_to_comm,
tbo_on_comm,
)

ubatch_id = tbo_current_ubatch_id()

ub_comm = tbo_get_ubatch_tp_comm(ubatch_id)
ub_comm = tbo_get_ubatch_tp_comm(tbo_current_ubatch_id())
if ub_comm is None:
# world_size == 1: nothing to reduce.
return x

# Hand the CPU baton to the partner ubatch and move onto the comm stream,
# so this AR overlaps the partner's compute.
tbo_yield_and_switch_from_compute_to_comm()
comm_stream = tbo_get_comm_stream()
# out-of-place all_reduce on this ubatch's dedicated communicator, launched
# on the comm stream (current stream after the switch above).
x = ub_comm.all_reduce(x, stream=comm_stream)

tbo_switch_to_compute_sync()
return x
# Out-of-place all_reduce on this ubatch's dedicated communicator, on the
# comm stream, overlapping the partner ubatch's compute.
return tbo_on_comm(lambda: ub_comm.all_reduce(x, stream=tbo_get_comm_stream()))


def _tbo_all_reduce_fake(x: torch.Tensor) -> torch.Tensor:
Expand Down
Loading
Loading