A training step is a fight over three budgets: memory per device, compute per device, and communication between devices. Every parallelism strategy is a different way of paying those budgets for the same arithmetic.

To keep the strategies comparable, we use a single example. Most of the post comes back to one linear layer:

X[B,E]  @  W[E,E]Y[B,E]X[B, E] \; @ \; W[E, E] \rightarrow Y[B, E]

The cost is roughly 2BE22 B E^2 floating point operations. That is the compute we want to keep the hardware fed with as we spread it across devices.

Three letters carry the whole post. BB is the per-step batch — the number of rows of XX, i.e. the tokens processed together. EE is the hidden dimension, the width shared by XX and WW and the axis the matmul contracts over. And PP — which shows up the moment we start splitting work across hardware — is the number of devices a given axis is spread across. A matmul’s cost grows with BB and with E2E^2; a collective’s cost grows with whatever it has to move. Every “useful ratio” below is just those two growth rates raced against each other.

The question every strategy answers in its own way:

What is split, what is replicated, what communication repairs the split, and can that communication be hidden under matmul compute?

The “communication” piece is always one of a small set of collectives: all-gather, reduce-scatter, all-reduce, and all-to-all. See the appendix to get an intuitive feel for these collectives.

Data Parallelism

Replicate the weights, split the batch. Each device sees a different slice of XX‘s rows, multiplies by the same WW, and produces its slice of YY:

Data ParallelismEach chip owns a slice of the batch. W is replicated everywhere.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
this chip's data (shard or replica)materializedpartial sumelsewhere

Across chips, every WW is identical; each chip owns a different chunk of the batch dimension. The forward pass is literally just parallelized. A repair step comes at the end: the gradients of the replicated WW must take into account the full batch (which we sharded across devices), so an all-reduce of W\partial W fixes this.

The win is throughput, not memory. Every device still stores the full model, the full gradient, and the full optimizer state.

That backward-pass all-reduce of W\partial W is DP’s only cross-device cost, and — as the timeline at the end of this section shows — most of it can be hidden under compute.

The useful ratio is B/PB / P. The forward pass costs nothing across devices — each chip just multiplies its own rows by its own copy of WW. The bill comes on the backward pass: because every chip computed W\partial W from a different slice of the batch, the chips must agree on a single summed gradient, and that agreement is the all-reduce of W\partial W. Here is the asymmetry that decides everything. The per-chip compute is 2BE2/P\sim 2 B E^2 / P: the layer’s matmul is the 2BE2\sim 2 B E^2 FLOPs from above, and splitting the batch across PP devices leaves each one with B/PB / P rows of work. But the gradient being all-reduced is “closer to E2E^2” — W\partial W has the same shape as WW, an E×EE \times E grid of numbers, and that size does not depend on the batch at all. So as you add devices, the compute on each chip shrinks (it carries the BB) while the collective does not (it carries only E2E^2). DP therefore wants B/PB / P large enough that the local matmul stays well above the collective it has to hide.

That all-reduce need not stall the backward pass. Walk two layers, XY1Y2X \to Y_1 \to Y_2: computing layer 1’s activation gradient Y1=Y2W2\partial Y_1 = \partial Y_2 \, W_2^{\top} needs only the local Y2\partial Y_2 and the chip’s own replica of W2W_2 — not the reduced W2\partial W_2, which the optimizer only consumes at the very end of the step. So each layer’s W\partial W all-reduce can fire and run concurrently with the next layer’s backward compute, hiding underneath it. The timeline below shows that staircase — comm streams tucked behind compute, layer by layer. The one all-reduce that can’t hide is the first layer’s — its W\partial W is produced last in the backward walk, with no further backward compute to tuck it under, so a small tail stays exposed at the end of every step, and real implementations bucket gradients to keep that tail short.

Data Parallelism — Backward Pass Overlap
Forward needs no comm at all. The interesting overlap is in backward: each layer's gradient all-reduce can run while earlier layers' backwards are still computing.
time →computeall-reduce
Backward pass about to start; no gradients yet.
Step 1 / 5
computecommunication

FSDP

Stop replicating. The batch is still sharded as in DP, but now WW is sliced too — each chip stores only a strip of WW‘s rows. In the picture below, YY starts empty: chip ii has its XX slice and its WW shard, but it cannot compute its YY yet — the matmul Xi@WiX_i @ W_i doesn’t even compose. We must all-gather WW to materialize the full WW on every chip; only then can each chip produce its batch slice of YY.

FSDPBatch sharded as in DP, but W's rows are partitioned across chips. The full W is gathered just in time, used, and freed.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
this chip's data (shard or replica)materializedpartial sumelsewhere

The mental model that makes this work: at any moment, only one or two units are materialized (yellow in the picture above) — a unit being whatever granularity you wrap, often a layer or a small group of them. The other many units stay sharded. Memory shrinks by the shard count.

The performance question is whether the gather for layer N+1N{+}1 can finish while layer NN is still computing. The timeline below starts in the naive schedule — each matmul waits for its all-gather to land, leaving big idle gaps in the compute stream. Click Overlapped to slide the gathers underneath the matmuls: the compute stream becomes continuous and the step takes roughly half as long.

FSDP — Pipelined All-Gather
Layer N+1's weights are gathered during layer N's matmul. As long as the all-gather is shorter than the matmul, compute never waits.
time →computematmul L1matmul L2matmul L3matmul L4all-gatherAG W1AG W2AG W3AG W4
Total time roughly doubles. Compute waits for each all-gather to finish.
computecommunication

The same per-unit all-gather will return in the backward pass — computing X\partial X also needs the full WW — so the backward pass either repeats the gather or reuses a forward-cached copy.

The useful ratio is B/PB / P — the same as DP. Per-layer compute is again 2BE2/P\sim 2 B E^2 / P: the 2BE2\sim 2 B E^2 matmul, split PP ways. The communication is the all-gather that rebuilds WW before the layer can run — and although each chip stores only its E2/P\sim E^2 / P row-strip, the all-gather has to deliver the whole E×EE \times E weight to every chip, so the volume that actually moves is E2\sim E^2, the size of WW itself. That does not shrink as you add devices, exactly like DP’s all-reduce of W\partial W. Race the 2BE2/P\sim 2 B E^2 / P compute against the E2\sim E^2 collective and you land on B/PB / P again. So FSDP does not win a better compute-versus-communication race than DP — if anything it pays a little more, roughly one and a half of these collectives per layer once you count the backward pass (gather WW going forward; gather WW again and reduce-scatter W\partial W going back). What it buys is memory: the same roofline as DP, but with the model, gradients, and optimizer state sharded PP ways instead of replicated on every chip. As in DP, you want the per-chip batch B/PB / P large enough that each matmul stays well above the gather it has to hide.

Tensor Parallelism

A different shape. Tensor parallelism splits the matrix itself — and there are two dual ways to slice it. Split WW by columns and the input stays whole; split WW by rows and the input must arrive already sharded. Real systems use both, in a specific order, and the pairing is the whole trick — so we take them one at a time.

The column-parallel linear is the one most readers picture. XX is replicated — every chip holds the full input — and each chip keeps the matching columns of WW:

X[B,E]  @  Wi[E,E/P]Yi[B,E/P]X \, [B, E] \; @ \; W_i \, [E, E/P] \rightarrow Y_i \, [B, E/P]

YiY_i is a clean slice of the output — chip ii‘s columns, exact, no partial sums. The forward pass needs no collective at all, provided the next operation is happy consuming a column-sharded YY; if it needs the full thing, an all-gather rebuilds it. The bill moves to the backward pass, where the input gradient X\partial X must be all-reduced across chips.

Tensor Parallelism (column-parallel)X is replicated on every chip — nothing about the input is sharded. W is split by output columns; each chip computes a clean column slice of Y. Forward needs no collective if the next op consumes the slice; an all-gather rebuilds the full Y.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
this chip's data (shard or replica)materializedpartial sumelsewhere

The row-parallel linear is the mirror image. Now the input is sliced along the contraction dimension EE — each chip holds a column slice XiX_i, paired with the matching rows of WW:

Xi[B,E/P]  @  Wi[E/P,E]Y(i)[B,E]X_i \, [B, E/P] \; @ \; W_i \, [E/P, E] \rightarrow Y^{(i)} \, [B, E]

Notice the shape: Y(i)Y^{(i)} is the full output, not a slice. But it is a partial sum — only the ii-th of PP terms in the contraction. None of the chips alone has the correct answer.

Tensor Parallelism (row-parallel)X arrives already sliced along the contraction dimension E — in practice, as the column-parallel layer's output. W's rows are partitioned to match. Each chip computes a full-shape partial sum of Y; a reduce-scatter (or all-reduce) combines them.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
this chip's data (shard or replica)materializedpartial sumelsewhere

Reduce-scatter is exactly the collective the matrix shape demands: a sum across devices that re-shards the output. Above, the collective combines the striped partials and each chip is left with one column of the final YY.

Why carry two schemes? Because they compose. Stack a column-parallel linear in front of a row-parallel one and watch the shapes click: the first takes a replicated XX and emits a column-sharded intermediate — exactly the EE-sharded input the second one wants. No collective in between; even an elementwise nonlinearity slots into the gap, since it never mixes columns. The row-parallel layer then produces partial sums, and a single all-reduce finishes the pair. Run either scheme alone and you’d pay a collective per matmul; run them as a pair — Megatron’s pattern for every MLP and attention block — and two matmuls cost one. Look back at the two diagrams: the column-parallel widget’s resting YY is the row-parallel widget’s starting XX, same shards, same colors. That is also the answer to a fair question about the row-parallel picture — “who sharded my input?”: nobody, it fell out of the previous layer that way.

In the standalone row-parallel scheme — reduce after every matmul, so the next layer starts from a clean sharded input — we cannot overlap between layers: the next layer’s input is exactly the current layer’s reduce-scattered output, so there is nothing yet to communicate ahead of. The overlap that remains happens inside the layer, with the matmul work split into a staircase that interleaves partial computation with a reduce-scatter on the previous partial. Those chunks split the token rows purely to pipeline the work — every chip still holds the full batch; only the hidden dimension EE is sharded across devices.

TP (row-parallel) — Build the within-layer staircase
Split the matmul into chunks along the batch dim. Step through the algorithm; the yellow ring marks the chunk being computed this tick, and the dashed block marks the chunk being reduce-scattered at the same time.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
time →computereduce-scattermm c1
Step 0 — Chunk 1 begins computing on each chip. No comm yet — the first partial is still being produced.
Step 0 / 5

The useful ratio is E/PE / P. Compute is 2BE2/P\sim 2 B E^2 / P, since tensor parallelism splits one of WW‘s EE-sized axes across the PP chips — the contraction axis for row-parallel, the output axis for column-parallel. The collective — whichever scheme sends it: the all-gather of YY, the reduce-scatter of YY, or the pair’s single all-reduce — moves an activation of size BE\sim B E — an output with one entry per (row, hidden-unit) pair, and crucially independent of PP. Race compute against communication and the BB cancels, leaving a factor that scales as E/PE / P: a larger hidden dimension EE gives the matmul more work to hide the activation exchange behind, while a larger PP thins each local matmul and squeezes that margin back down. The column→row pairing halves how often you pay it, but the race itself is unchanged — the ratio is E/PE/P either way.

Context Parallelism

So far the batch has been our only axis to split. But those rows are really tokens laid out along a sequence, and a long sequence is its own axis to shard. Tracking it takes on a few more dimensions: input tensors X[B,S/P,E]X[B, S/P, E], and Q,K,VQ,K,V tensors [B,A,S/P,H][B, A, S/P, H] — where AA is the number of attention heads and HH the head dimension — sharded across PP devices. From here BB counts sequences rather than individual token rows: a batch element is a whole sequence of SS positions, so the total token count is BSB \cdot S. (You will also see this called sequence parallelism — the two terms are used interchangeably for sharding the sequence axis. Confusingly, in Megatron sequence parallelism also names a narrower trick that shards only the norm and dropout layers.) The diagrams below drop the leading batch axis BB — it rides along untouched and only clutters the transposes that matter — so each grid is the [S,E][S, E] face of a single batch element: the sequence SS runs down the rows, and every device owns a horizontal band of S/PS/P of them.

For the dense layers, the split is free. A linear layer treats every token independently, so sharding the sequence is just data parallelism wearing a different hat — each device multiplies its own tokens by the replicated WW with no communication at all. (Free on the forward pass, at least: because WW is replicated, the dense layers still owe DP’s backward all-reduce of W\partial W, exactly as in data parallelism.)

Context ParallelismEach horizontal band is one chip's slice of the sequence — a real device shard, unlike TP's within-layer chunks. For the dense layer this is just DP: W is replicated and no communication is needed; attention is where CP pays.
Chip 0
X
@
·
W
=
·
Y
Chip 1
X
@
·
W
=
·
Y
this chip's data (shard or replica)materializedpartial sumelsewhere

In order to compute attention, though, every query must attend to every key across the whole sequence — but each device only holds the Q,K,VQ,K,V for its own span. That is what forces communication, and there are two different methods for repairing the split: one streams the missing keys and values past each device a block at a time, the other transposes the sharding so each device holds the whole sequence for a slice of the heads.

Ring Attention

Ring attention passes its K,VK,V shard around a ring of devices. At each step, a device attends its local QQ shard against whichever K,VK,V block currently sits on it and folds the result into a running output, then forwards that block onward.

Ring attention — Q stays home, K,V rotates
Four devices, each owning S/P of the sequence. Every step a device attends its queries against the K,V block it currently holds, folds the result into its running output, and forwards that block to the next device — while the next block is already arriving. Cell color marks the device a block originated on.
K,V hops →
Device 0
Q0
[S/P, H]
·
K,V
blk 0
·
O0 · keys folded
2/8 keys
Device 1
Q1
[S/P, H]
·
K,V
blk 1
·
O1 · keys folded
2/8 keys
Device 2
Q2
[S/P, H]
·
K,V
blk 2
·
O2 · keys folded
2/8 keys
Device 3
Q3
[S/P, H]
·
K,V
blk 3
·
O3 · keys folded
2/8 keys
block from device 0device 1device 2device 3O still rescalingactive this tick
Every device attends its own K,V block first — the S/P positions it already holds — so compute starts with no communication. Underneath, each block is already copying to its clockwise neighbor (device 3 wraps to device 0).
Step 1 / 5

The communication hides. Each device forwards its current K,VK,V block to its neighbor while it is still attending to that block, so the next hop lands just as the next matmul needs it. The first block is already local — no hop needed to start — and the last block needs no onward send, so with P1P-1 hops tucked under PP attention matmuls there is no exposed communication at either end — provided each hop lands inside the matmul that hides it, which is the case where the ratio below is on your side.

The useful ratio is S/PS/P. Each device owns S/PS/P of the positions, so per ring hop its attention compute scales like BA(S/P)2HB \cdot A \cdot (S/P)^2 \cdot H — every one of its local queries attends to the S/PS/P keys in the block it holds — while the K,VK,V block it forwards is only BA(S/P)H\sim B \cdot A \cdot (S/P) \cdot H numbers. Compute grows with the square of the per-device sequence length and communication only linearly, so a longer per-device span (large S/PS/P) buys room to hide each hop; adding devices thins the local matmul quadratically but the hop only linearly, squeezing that margin back down. (This draws full, unmasked attention for clarity; under a causal mask the earliest blocks attend to almost nothing and the latest to everything, so production ring attention reorders the sequence — zig-zag or striped assignment — to keep every hop’s compute roughly equal.)

Ulysses Attention

Ulysses repairs the split with a different collective: an all-to-all that transposes the sharded axis. For Q,K,VQ,K,V it turns [B,A,S/P,H][B,A/P,S,H][B, A, S/P, H] \rightarrow [B, A/P, S, H] — each device trades “my positions, all heads” for “all positions, my head group”. Every device can then run full attention locally for its shard of the heads, producing O[B,A/P,S,H]O[B, A/P, S, H]; a second all-to-all flips it back to O[B,A,S/P,H]O[B, A, S/P, H], after which the standard multi-head concatenation and output projection return [B,S/P,E][B, S/P, E].

Ulysses attention — two all-to-alls swap seq ↔ head sharding
Four devices, S = 8 tokens, A = 4 heads. Every card draws the full logical tensor: rows are tokens, colored by the device that owns them; Q,K,V and O columns are heads; pale cells live on another device. Watch device d's slice flip from a row band (my tokens, all heads) to a column (all tokens, my head group) and back. Shape labels name each device's resident shard — batch dim B and head dim H are never drawn, so every Q,K,V/O cell is just one head's H-vector; this is Ulysses's maximal-sharding case P = A, one head per device, the most devices it allows.
sequence-sharded
Device 0
X
[S/P, E]
@
·
W_qkv
[E, A·H] ×3
=
·
Q,K,V
not yet computed
Device 1
X
[S/P, E]
@
·
W_qkv
[E, A·H] ×3
=
·
Q,K,V
not yet computed
Device 2
X
[S/P, E]
@
·
W_qkv
[E, A·H] ×3
=
·
Q,K,V
not yet computed
Device 3
X
[S/P, E]
@
·
W_qkv
[E, A·H] ×3
=
·
Q,K,V
not yet computed
device 0's tokensdevice 1device 2device 3replicated weightelsewheremoved by all-to-all
Sequence-sharded, exactly like the dense layers before it: device d owns two of the eight token rows of X [S/P, E], and W_qkv is replicated (yellow). Q,K,V is not computed yet — every grid drawn is the full logical tensor, and pale cells simply live elsewhere.
Step 1 / 6

Here there is nothing to overlap. Attention’s input is exactly the first all-to-all’s output, and the next layer’s input is exactly the second’s — the same “nothing to run ahead of” bind that tensor parallelism hit between layers. As drawn, both all-to-alls are hard syncs: every device stalls through the first and again through the second. (A production kernel can chunk the transpose to claw some of that back, but the honest baseline is both all-to-alls fully exposed.)

So the roofline argument is amortization, not hiding. The exposed traffic is 4BA(S/P)H\sim 4 \cdot B \cdot A \cdot (S/P) \cdot H (three tensors out — Q,K,VQ,K,V — and the single output tensor back) against local attention compute of 4B(A/P)S2H\sim 4 \cdot B \cdot (A/P) \cdot S^2 \cdot H (attention is two matmuls, the QKQK^{\top} scores and the V\cdot V that follows). The PP‘s cancel and the ratio scales as SS: you do not hide the all-to-all, you drown it under attention’s S2S^2 growth, and long sequences drown it well. The catch is structural — Ulysses gives each device one head group, so it can shard across at most as many devices as there are heads: PAP \le A (and PP must divide AA for an even split; grouped- and multi-query attention, with their handful of K,VK,V heads, tighten the cap further). Head count, not memory, caps its reach.

Ring and Ulysses split the same job two ways, and the choice between them is the one expert parallelism will meet again. The ring hides all of its communication but ships more of it: a fresh K,VK,V block every hop, 2BASH\sim 2 \cdot B \cdot A \cdot S \cdot H per device once you sum the ring. Ulysses ships the bare minimum, 4BA(S/P)H\sim 4 \cdot B \cdot A \cdot (S/P) \cdot H, but pays it fully exposed. That is a factor of P/2\sim P/2 more bytes for the ring, traded for hiding every one of them. Long sequences favor drowning the exposed cost (Ulysses) until you run out of heads at P=AP = A; past that, only the ring keeps scaling. Both run the same collectives in reverse on the backward pass — the all-to-all sandwich inverts, the ring rotates K,V\partial K, \partial V the other way — and obey the same ratios, so we stay in the forward pass throughout.

Expert Parallelism

A Mixture-of-Experts layer replaces the single FFN with many, plus a router that sends each token to just one or two of them. The experts will not all fit on one device, so expert parallelism shards them — each device hosts a slice. (The all-to-all widget below draws the simplest case, top-1 routing — exactly one expert per token, so its combine is pure delivery. The all-gather/reduce-scatter widget draws top-2 — two experts per token, their outputs summed — because top-2 is where that method’s reduce-scatter earns the “reduce” in its name.) Just like context parallelism, there are two ways to reunite each token with its expert. One is an exact mirror of an attention method — the all-to-all sandwich is Ulysses relabeled. The other echoes ring attention rather than mirroring it: it hides its communication under compute and ships more bytes than its all-to-all sibling, the same trade the ring made, but what it moves is the tokens themselves, gathered to every device — not a K,VK,V block rotating past them.

All-to-all routing

The first method is an all-to-all sandwich, the analogue of Ulysses: dispatch every token to its expert’s device, run the expert FFN locally, then combine the outputs back home.

Expert parallelism — all-to-all dispatch, local FFN, all-to-all combine
Four devices, one expert each — the maximal case, since expert parallelism can't shard past one device per expert. Every card draws the full logical token tensor [S=8, E]; rows are colored by the expert the router assigns them to (before the router speaks, and once outputs land back home, they wear their home device's color). Watch device d's band flip from 'my tokens' (home) to 'my expert's tokens' (assigned) and back — the same seq ↔ head transpose Ulysses runs, just relabeled.
sequence-sharded
Device 0
X
[S/P, E]
W_d
expert 0
Device 1
X
[S/P, E]
W_d
expert 1
Device 2
X
[S/P, E]
W_d
expert 2
Device 3
X
[S/P, E]
W_d
expert 3
expert 0 / device 0expert 1 / device 1expert 2 / device 2expert 3 / device 3elsewheremoved by all-to-all
Sequence-sharded, exactly like the dense layers before it: device d owns two of the eight token rows of X [S/P, E] — its home band, HOME(i) = floor(i/SP) — and already holds its own expert's weight W_d [E, E] — drawn once, standing in for the FFN's two E-wide matmuls (expert d lives on device d). Every grid drawn is the full logical token tensor, and pale cells simply live elsewhere.
Step 1 / 6

As drawn, both all-to-alls are exposed, for the same reason Ulysses’ are: the FFN has no input until dispatch lands, and the combine is a sync on the slowest expert — one hot expert stalls everyone behind it. The bet is amortization: each routed token pays an FFN costing E2\sim E^2 (its two E×EE \times E matmuls) while the two all-to-alls only ship its E\sim E-sized activation there and back, so the ratio scales as EE — a wide hidden dimension drowns the round trip, provided the router keeps the experts balanced. Toggle the widget’s one hot expert mode to watch that proviso fail: the combine can’t complete until the overloaded device drains its queue, and everyone else waits.

All-gather / reduce-scatter routing

The second method is an all-gather / reduce-scatter sandwich, and it inverts the dispatch: instead of sending each token to its expert, send every token to every device. An all-gather replicates the token shards so each device holds the full sequence; each device then keeps just the tokens routed to its own expert, drops the rest, and runs its expert FFN — the weights never move. A reduce-scatter finishes the job, and the reduce is not decoration — the widget draws it happening. Under top-2 each token’s output is the sum of two contributions living on two different devices: each device produces a full-length output that is zero outside its own expert’s rows, and the reduce-scatter sums those summands in flight, landing each token’s finished row — one row, not two — back on its home device. (Run the same collective at the all-to-all figure’s top-1 and each sum is one real row plus zeros — routing home by arithmetic. The collective doesn’t change; the number of genuine summands does.) There is still a ring turning here, because that is how these collectives are built — shards passing neighbor-to-neighbor — but what rides it is tokens, not weights.

Expert parallelism — all-gather tokens, local FFN, reduce-scatter outputs
Four devices, one expert each — and this time the router picks top-2: every token goes to two experts, and its output is the sum of what they return. The drawn widths tell that story: the token is one column, the router's picks are two, the finished output is one again — 1 → 2 → 1, and the picks are the only token grid ever drawn two-wide (the token grids collapse the hidden dim E to one column; only the weight W_d keeps its true E×E square — the drawn token widths are schematic, and E lives in the labels). Same router as the all-to-all above for each token's first choice, plus a second pick. Every device gathers ALL the tokens — once each, no matter how many experts want them — keeps the ones its own expert was picked for, and a reduce-scatter sums each token's two contributions in flight and lands the finished row back home. The tokens do all the traveling, shard by shard around a ring; the expert weights W_d never move.
sequence-sharded
Device 0
X
[S/P, E]
W_d
[E, E] · expert 0
Device 1
X
[S/P, E]
W_d
[E, E] · expert 1
Device 2
X
[S/P, E]
W_d
[E, E] · expert 2
Device 3
X
[S/P, E]
W_d
[E, E] · expert 3
expert 0 / device 0expert 1 / device 1expert 2 / device 2expert 3 / device 3elsewhere / dropped / zeromoved by all-gather / reduce-scatterpartial sum — a contribution still in flight
Sequence-sharded, the same opening position as the all-to-all above: device d owns two of the eight token rows of X [S/P, E] — its home band — and holds its own expert's weight W_d [E, E], drawn as a small 2×2 square matrix standing in for the FFN's two E-wide matmuls. Each token is drawn one cell wide — the hidden dim lives in the labels, because this figure spends its drawn columns on the routing: watch the token's width go 1 → 2 → 1. W_d is drawn at every step of this figure for a reason: it never moves. What travels here is tokens.
Step 1 / 6

Now the communication hides, and it hides because we have seen both of these staircases before: the all-gather chunks and streams exactly like FSDP’s all-gather of WW, the reduce-scatter like tensor parallelism’s reduce-scatter of YY, each shard’s hop tucking under the FFN chunk that overlaps it. The useful ratio is E/PE/P. Per device the expert FFN costs kBSE2/P\sim k B S E^2 / P — an E2\sim E^2 FFN for each of the kBS/P\sim k B S / P (token, expert) pairs the router creates — while the two collectives still move 2BSE\sim 2 \cdot B S E: the whole token set in, the whole output set back, neither of them growing with kk. Race them and the ratio scales as E/PE/P — top-kk only fattens the compute side of the race — which is no coincidence: this is tensor parallelism’s ratio because this is tensor parallelism, with the experts sitting in the sharded-weight seat.

The trade against the all-to-all is bytes for exposure — the choice we met with Ulysses and the ring, in new clothes. Per device, the all-to-all ships the minimum, 2kBSE/P\sim 2 k \cdot B S E / P — each token’s kk copies to their experts and back — and pays for it fully exposed; the all-gather/reduce-scatter ships P×\sim P\times more, 2BSE\sim 2 \cdot B S E, but hides every byte under compute, and its neighbor-to-neighbor traffic keeps the network calm where P2P^2 simultaneous all-to-all flows contend for it. One thing it does not buy is hot-expert immunity: each device still runs its expert over every token globally routed to it, so a skewed router overloads the hot expert’s host exactly as the all-to-all’s dispatch does — the imbalance belongs to the routing, and no collective routes around it. The gather holds one further edge: its cost is independent of top-kk. Every token is gathered once whether it routes to one expert or four, while the all-to-all must dispatch (and combine) kk copies — so as kk grows, the P×\sim P\times byte premium shrinks toward P/k\sim P/k and this method pulls ahead. The figure above draws exactly this: eight tokens gathered once, sixteen expert-outputs reduced back to eight rows. Backward is the transpose of forward, just as it was for FSDP and TP: all-gather the output gradients, reduce-scatter the input gradients, same collectives, same ratios.

Bringing It Together

Five strategies, one roofline. Every one of them splits the same 2BE22 B E^2 matmul across devices, and every one pays for the split with a collective. The whole post is the single question of whether that collective’s cost stays under the compute it rides alongside — and each “useful ratio” is just per-device compute scaling divided by collective scaling, the two growth rates raced against each other.

StrategyWhat it splitsCollective(s)Hidden under compute?Useful ratio
Data parallelismthe batchall-reduce W\partial Wyes, bar a small end-of-step tailB/PB/P
FSDPthe batch and the weightsall-gather WW, reduce-scatter W\partial WyesB/PB/P
Tensor parallelismthe hidden dimension EEone all-reduce per column→row pair (standalone: reduce-scatter YY)yes, within the layerE/PE/P
Ring attentionthe sequenceK,VK,V ring hopsyesS/PS/P
Ulyssessequence \leftrightarrow headstwo all-to-allsno — amortized under S2S^2SS
Expert (all-to-all)the expertstwo all-to-allsno — amortized under E2E^2EE
Expert (all-gather/reduce-scatter)the expertsall-gather tokens, reduce-scatter outputsyesE/PE/P

Two families fall out of the last two columns. The rings and staircases — DP, FSDP, TP, ring attention, all-gather/reduce-scatter experts — arrange the work so a collective always has a matmul to hide beneath, and win when the per-device compute is fat enough to cover it. The all-to-alls — Ulysses and expert dispatch — have nothing to run ahead of, so they pay the communication in the open and rely on a superlinear compute term (S2S^2 for attention, E2E^2 for the FFN) to amortize it away. Either way the knob is the same: keep the quantity that carries the compute — batch per device, hidden dimension, sequence length — large enough that arithmetic outruns the wire.

Further Reading

Appendix: The Collectives

Every collective the post links to lives here, and all four now earn their place above: all-reduce, all-gather, and reduce-scatter drive DP, FSDP, and TP respectively, while the all-to-all is the workhorse of the expert-parallel routing — and the context-parallel transpose — you just stepped through.

Step through each panel; the captions describe what changes between frames.

The four collectives, in one picture
Each chip is one 4-unit rectangle showing the data it currently holds. Step through each panel to watch the colors spread and mix. Every reference to a collective in the post above links to one of these panels.
solid = owned data (AG, A2A)hatched = partial sum (1 contributor)hatched stripes = partial sum (multiple contributors)solid yellow = fully reducedgray = empty (sent away)
All-gather (ring algorithm)
Each chip starts owning one slot (its own color). At each round, every chip sends a slot one hop clockwise; senders keep their copy, so the number of populated slots per chip grows by one each round. After 3 rounds every rectangle is a rainbow of all four sources.
Chip 0
Chip 1
Chip 2
Chip 3
Start: each chip owns exactly one slot — its own (chip i has slot i, in its own color). The other three slots are empty (gray).
Step 0 / 3
Reduce-scatter (ring algorithm)
Every chip starts with the full vector as its own partial sum (rectangle fully chip-colored). At each round, partial sums spread one hop clockwise and mix with the receiver's contribution. After 3 rounds, each chip ends up with one fully reduced slot (yellow); the rest are sent away.
Chip 0
Chip 1
Chip 2
Chip 3
Start: every chip's rectangle is fully its own color — its own partial contribution to each of the 4 slots.
Step 0 / 3
All-reduce (ring algorithm)
Reduce-scatter (3 rounds) followed by all-gather (3 rounds): partial sums first mix down to one yellow slot per chip, then yellow broadcasts back out to fill every rectangle. 6 rounds total.
Chip 0
Chip 1
Chip 2
Chip 3
Same starting state as reduce-scatter — every chip's rectangle is fully its own color.
Step 0 / 6
All-to-all (pairwise swap schedule)
All-to-all transposes the sharding axis: each chip starts holding one shard of dim A split across destinations along dim B, and ends holding one shard of dim B with contributions from every source along dim A. Concretely below, every chip starts with its rectangle fully its own color (everything originated here, sliced by destination). At each round, pairs of chips swap one piece each.
Chip 0
Chip 1
Chip 2
Chip 3
Initial: each chip's rectangle is fully its own color — every piece originated here. Each piece is destined for a specific chip (the slots are numbered by destination).
Step 0 / 3