DeepSeek's Choice of EP Scale: The Insight Between Prefill and Decode

Nov 8, 2026

Chinese version

When deploying a MoE model, the scale of expert parallelism (EP) has to be chosen separately for prefill and decode. In DeepInfer, DeepSeek picks opposite scales for the two phases: prefill uses the smallest EP that fits in memory, usually EP2 to EP8, while decode goes up to EP64 to EP128. This post explains the choice, starting from one question: how many tokens does each expert get per step?

This post is based on DeepInfer’s deployment document and the DeepSeek V4.1-Flash model configuration, and covers only the MoE layer: 384 routed experts, 6 selected per token, plus 1 shared expert; hidden size 5120, expert intermediate size 2304, expert weights in FP4. Hardware numbers are B200 specs (4.5 PFLOPS dense FP8, 8 TB/s memory bandwidth, 900 GB/s NVLink per direction). All times are peak-based estimates per layer per GPU.

Summary

1. Thinking about EP scale from DeepInfer

DeepInfer’s deployment document says little about parallelism: prefill uses EP2 to EP8, taking the smallest that HBM allows; decode uses EP64 to EP128, taking the largest that the available GPUs allow. Both phases run the same model with the same experts, yet the choices go in opposite directions.

A common explanation is that decode does little compute per step and so can be spread over more GPUs, while prefill moves a lot of data and should be spread over fewer. Both halves assume that a larger EP leaves less compute on each GPU. This post first shows that the assumption does not hold, then looks at what EP does in two parts: what stays the same as EP changes, and what changes with it.

2. Conserved as EP scales: compute per GPU

DeepInfer runs attention data-parallel with DP equal to EP, so each GPU serves its own share of requests and holds TT tokens of shape [T, 5120]. The gate selects 6 of the 384 routed experts for each token. Tokens are sent to the GPUs that host their experts; each GPU takes the rows for its own experts, runs two matrix multiplications per expert, and sends the results back, where they are added to the shared expert’s output.

TT is set by the requests on each GPU, so adding GPUs does not make it smaller; the official performance curves are likewise compared at a fixed number of requests per GPU. Under that condition, the whole EP group produces EP × T × 6 rows per layer, and with uniform routing each GPU receives on average

EP×T×6EP=6T\frac{EP \times T \times 6}{EP} = 6T

rows, plus TT rows for the shared expert, 7T7T in total. EP cancels out: going from EP8 to EP64 leaves the useful compute on each GPU unchanged. One row through one expert costs about 70.8 MFLOP, which is about 28 µs per layer for decode at 256 tokens per GPU and about 3.6 ms for prefill at 32,768.

What is conserved is the average useful compute per GPU. With uneven routing the busiest GPU computes more, which is the expert load imbalance discussed in the next section; and when an expert has very few rows, the grouped matrix multiplication pads them to its tile size, so the work actually executed is a little more than the useful compute. That effect is larger for decode at small EP, but it does not change the conclusions here.

Since EP does not reduce compute, its effect has to be found in what it does change.

3. Changing as EP scales: rows, copies and expert load imbalance

Rows per expert: what sets weight-read time

What EP changes is how the 6T6T rows are spread over experts. Each GPU holds 384 / EP experts, and each expert gets

r=EP×T×6384r = \frac{EP \times T \times 6}{384}

rows per step. For decode with 256 tokens per GPU, EP8 gives each GPU 48 experts with 32 rows each; EP64 gives each GPU 6 experts with 256 rows each.

rr matters because every expert’s weights are read from memory once per step, and rr is how many rows that one read serves. Let an expert have PP parameters. Computing rr rows takes 2Pr2Pr operations and reading the weights moves PbPb bytes, where bb is bytes per parameter. Setting the two times equal, PP cancels and gives the weight-reuse threshold:

r∗=b2×compute throughputmemory bandwidthr^{*} = \frac{b}{2} \times \frac{\text{compute throughput}}{\text{memory bandwidth}}

V4.1’s expert weights are FP4, about 0.53 bytes per parameter with scaling factors. With B200’s specs, r∗≈0.265×562.5≈150r^{*} \approx 0.265 \times 562.5 \approx 150 rows. This counts weights only; including the activations each row reads and writes raises the threshold to about 180 rows, which does not change the conclusions below. Above r∗r^{*}, the expert matrix multiplications are compute-bound; below it, time goes to reading weights. r/r∗r / r^{*} is roughly the ratio of compute time to weight-read time, and what a larger EP buys is pushing rr past r∗r^{*} so that weight reads drop below compute.

101001,00010,000248163264128EPRows per expert per step, rPrefill (32,768 tokens per card)Decode (256 tokens per card)r* ≈ 150 (weights only)≈ 180 (with activations)Prefill EP2: r = 1,024 rows, r / r* = 6.85Prefill EP4: r = 2,048 rows, r / r* = 13.71Prefill EP8: r = 4,096 rows, r / r* = 27.41Prefill EP16: r = 8,192 rows, r / r* = 54.83Prefill EP32: r = 16,384 rows, r / r* = 109.66Prefill EP64: r = 32,768 rows, r / r* = 219.31Prefill EP128: r = 65,536 rows, r / r* = 438.62Decode EP2: r = 8 rows, r / r* = 0.05Decode EP4: r = 16 rows, r / r* = 0.11Decode EP8: r = 32 rows, r / r* = 0.21Decode EP16: r = 64 rows, r / r* = 0.43Decode EP32: r = 128 rows, r / r* = 0.86Decode EP64: r = 256 rows, r / r* = 1.71Decode EP128: r = 512 rows, r / r* = 3.43Decode crosses at EP ≈ 38Official prefill: EP2–8Official decode: EP64–128
Figure 1: Rows per expert per step versus EP. Prefill has 32,768 tokens per GPU and decode 256; the dashed lines are the weight-reuse threshold.

The two lines in Figure 1 have the same slope, both growing linearly with EP, but sit 128 times apart. Prefill is far above the threshold already at EP2; decode crosses it only at EP ≈ 38.

Copies per token: what sets communication time

Of a token’s 6 experts, on average 6×(EP−1)/EP6 \times (EP - 1) / EP are on other GPUs. How many copies a token is sent depends on whether the implementation deduplicates per GPU, and both approaches are in use. Without deduplication, every token–expert pair is sent separately, as in DeepEP’s V1 low-latency mode and in Mega MoE:

cno dedup=6×(1−1EP)c_{\text{no dedup}} = 6 \times \left(1 - \frac{1}{EP}\right)

With per-GPU deduplication, a token is sent once to each GPU and expanded on arrival, as in DeepEP’s V1 normal mode and V2 direct mode:

cdedup=(EP−1)×[1−(1−1EP)6]c_{\text{dedup}} = (EP - 1) \times \left[1 - \left(1 - \frac{1}{EP}\right)^{6}\right]

Each copy is about 5.4 KB in dispatch (FP8) and about 10.2 KB in combine (BF16). Communication time is proportional to T×cT \times c and compute time to 7T7T; neither formula for cc involves TT, so with or without deduplication the ratio of communication to compute does not depend on TT, only on EP and link bandwidth.

0123456248163264128EPCopies per token, cNo dedup (Mega MoE, DeepEP V1 low-latency)Per-card dedup (DeepEP V1 normal, V2 direct)Upper bound 6 (6 experts per token)No dedup EP2: c = 3.00No dedup EP4: c = 4.50No dedup EP8: c = 5.25No dedup EP16: c = 5.62No dedup EP32: c = 5.81No dedup EP64: c = 5.91No dedup EP128: c = 5.95Per-card dedup EP2: c = 0.98Per-card dedup EP4: c = 2.47Per-card dedup EP8: c = 3.86Per-card dedup EP16: c = 4.82Per-card dedup EP32: c = 5.38Per-card dedup EP64: c = 5.68Per-card dedup EP128: c = 5.84EP4: 1.82× apartEP64: 1.04× apart
Figure 2: Copies per token versus EP.

In both cases cc grows with EP and approaches 6, but the saving from deduplication fades quickly as EP grows: at EP4 deduplication sends about 45% less (2.47 versus 4.50 copies), at EP64 only 4% less. Deduplication is therefore worth a lot at prefill’s small EP and almost nothing at decode’s large EP. Put the other way, Mega MoE, which does not deduplicate, loses almost nothing in decode but pays extra communication in prefill. DeepInfer uses Mega MoE inside the NVLink domain, so the numbers below assume no deduplication.

Expert load imbalance: how far the busiest GPU falls behind

All GPUs meet at combine, so a layer takes as long as its busiest GPU. If one expert carries hh times the average load, the load on its GPU relative to the average is

lb=1+h−1384/EPlb = 1 + \frac{h - 1}{384 / EP}

The more experts a GPU holds, the more easily one busy expert is absorbed. lblb also depends only on EP, not on TT.

PhaseEPrrr/r∗r / r^{*}Communication / computelblb (h=10h = 10)
Prefill21,0246.80.471.05
Prefill42,04813.70.711.09
Prefill84,096270.831.19
Decode8320.210.831.19
Decode321280.860.921.75
Decode642561.70.932.5
Decode1285123.40.944

Table 1: The three quantities EP changes. Communication / compute assumes Mega MoE without deduplication over NVLink. Prefill uses T = 32,768 per GPU, the DeepInfer benchmark default; decode uses T = 256 per GPU, the official curve’s point at 64 requests per GPU with 4 tokens verified per request per step.

The two EP8 rows in Table 1 make the point: both phases have the same communication-to-compute ratio and the same lblb, yet their r/r∗r / r^{*} differ by two orders of magnitude. Communication and expert load imbalance depend only on EP; rows per expert depend on EP × T. Figure 3 shows this directly: communication over compute is a single line shared by both phases, while weight reads over compute are two parallel lines 128 times apart.

0.010.1110248163264128EPRatio to compute timeWeight read ÷ compute: prefillWeight read ÷ compute: decodeCommunication ÷ compute: same in both phases (no dedup, NVLink)Equal to computeWeight read ÷ compute (prefill) EP2: 0.126Weight read ÷ compute (prefill) EP4: 0.0638Weight read ÷ compute (prefill) EP8: 0.0325Weight read ÷ compute (prefill) EP16: 0.0169Weight read ÷ compute (prefill) EP32: 0.00904Weight read ÷ compute (prefill) EP64: 0.00514Weight read ÷ compute (prefill) EP128: 0.00318Weight read ÷ compute (decode) EP2: 16.2Weight read ÷ compute (decode) EP4: 8.16Weight read ÷ compute (decode) EP8: 4.16Weight read ÷ compute (decode) EP16: 2.16Weight read ÷ compute (decode) EP32: 1.16Weight read ÷ compute (decode) EP64: 0.657Weight read ÷ compute (decode) EP128: 0.407Communication ÷ compute EP2: 0.473Communication ÷ compute EP4: 0.709Communication ÷ compute EP8: 0.827Communication ÷ compute EP16: 0.886Communication ÷ compute EP32: 0.916Communication ÷ compute EP64: 0.931Communication ÷ compute EP128: 0.938decode: drops below 1 between EP32 and EP64prefill: always far below 1Depends only on EP, not on T
Figure 3: Weight-read time and communication time relative to compute time, versus EP.

4. The trade-off in each phase

Putting weight reads, communication and compute together shows which one is slowest for each combination. Figure 4 marks, for each TT per GPU and EP, which of the three takes longest. On the left, inside the NVLink domain, the official prefill configuration sits in the compute-bound region and the official decode configuration has just left the weight-bound region. On the right, with a 100 GB/s NIC per GPU instead, almost every combination becomes communication-bound, which is why both phases stay inside the NVLink domain.

Compute slowestWeight read slowestCommunication slowestNVLink 900 GB/s64KEP2, T = 65,536: compute 7,215.5 µs, weight read 455.6 µs, communication 3,411.4 µsEP4, T = 65,536: compute 7,215.5 µs, weight read 230.0 µs, communication 5,117.1 µsEP8, T = 65,536: compute 7,215.5 µs, weight read 117.2 µs, communication 5,969.9 µsEP16, T = 65,536: compute 7,215.5 µs, weight read 60.8 µs, communication 6,396.3 µsEP32, T = 65,536: compute 7,215.5 µs, weight read 32.6 µs, communication 6,609.5 µsEP64, T = 65,536: compute 7,215.5 µs, weight read 18.5 µs, communication 6,716.1 µsEP128, T = 65,536: compute 7,215.5 µs, weight read 11.5 µs, communication 6,769.4 µs32KEP2, T = 32,768: compute 3,607.8 µs, weight read 455.6 µs, communication 1,705.7 µsEP4, T = 32,768: compute 3,607.8 µs, weight read 230.0 µs, communication 2,558.5 µsEP8, T = 32,768: compute 3,607.8 µs, weight read 117.2 µs, communication 2,984.9 µsEP16, T = 32,768: compute 3,607.8 µs, weight read 60.8 µs, communication 3,198.2 µsEP32, T = 32,768: compute 3,607.8 µs, weight read 32.6 µs, communication 3,304.8 µsEP64, T = 32,768: compute 3,607.8 µs, weight read 18.5 µs, communication 3,358.1 µsEP128, T = 32,768: compute 3,607.8 µs, weight read 11.5 µs, communication 3,384.7 µs16KEP2, T = 16,384: compute 1,803.9 µs, weight read 455.6 µs, communication 852.8 µsEP4, T = 16,384: compute 1,803.9 µs, weight read 230.0 µs, communication 1,279.3 µsEP8, T = 16,384: compute 1,803.9 µs, weight read 117.2 µs, communication 1,492.5 µsEP16, T = 16,384: compute 1,803.9 µs, weight read 60.8 µs, communication 1,599.1 µsEP32, T = 16,384: compute 1,803.9 µs, weight read 32.6 µs, communication 1,652.4 µsEP64, T = 16,384: compute 1,803.9 µs, weight read 18.5 µs, communication 1,679.0 µsEP128, T = 16,384: compute 1,803.9 µs, weight read 11.5 µs, communication 1,692.4 µs8KEP2, T = 8,192: compute 901.9 µs, weight read 455.6 µs, communication 426.4 µsEP4, T = 8,192: compute 901.9 µs, weight read 230.0 µs, communication 639.6 µsEP8, T = 8,192: compute 901.9 µs, weight read 117.2 µs, communication 746.2 µsEP16, T = 8,192: compute 901.9 µs, weight read 60.8 µs, communication 799.5 µsEP32, T = 8,192: compute 901.9 µs, weight read 32.6 µs, communication 826.2 µsEP64, T = 8,192: compute 901.9 µs, weight read 18.5 µs, communication 839.5 µsEP128, T = 8,192: compute 901.9 µs, weight read 11.5 µs, communication 846.2 µs4KEP2, T = 4,096: compute 451.0 µs, weight read 455.6 µs, communication 213.2 µsEP4, T = 4,096: compute 451.0 µs, weight read 230.0 µs, communication 319.8 µsEP8, T = 4,096: compute 451.0 µs, weight read 117.2 µs, communication 373.1 µsEP16, T = 4,096: compute 451.0 µs, weight read 60.8 µs, communication 399.8 µsEP32, T = 4,096: compute 451.0 µs, weight read 32.6 µs, communication 413.1 µsEP64, T = 4,096: compute 451.0 µs, weight read 18.5 µs, communication 419.8 µsEP128, T = 4,096: compute 451.0 µs, weight read 11.5 µs, communication 423.1 µs2KEP2, T = 2,048: compute 225.5 µs, weight read 455.6 µs, communication 106.6 µsEP4, T = 2,048: compute 225.5 µs, weight read 230.0 µs, communication 159.9 µsEP8, T = 2,048: compute 225.5 µs, weight read 117.2 µs, communication 186.6 µsEP16, T = 2,048: compute 225.5 µs, weight read 60.8 µs, communication 199.9 µsEP32, T = 2,048: compute 225.5 µs, weight read 32.6 µs, communication 206.5 µsEP64, T = 2,048: compute 225.5 µs, weight read 18.5 µs, communication 209.9 µsEP128, T = 2,048: compute 225.5 µs, weight read 11.5 µs, communication 211.5 µs1KEP2, T = 1,024: compute 112.7 µs, weight read 455.6 µs, communication 53.3 µsEP4, T = 1,024: compute 112.7 µs, weight read 230.0 µs, communication 80.0 µsEP8, T = 1,024: compute 112.7 µs, weight read 117.2 µs, communication 93.3 µsEP16, T = 1,024: compute 112.7 µs, weight read 60.8 µs, communication 99.9 µsEP32, T = 1,024: compute 112.7 µs, weight read 32.6 µs, communication 103.3 µsEP64, T = 1,024: compute 112.7 µs, weight read 18.5 µs, communication 104.9 µsEP128, T = 1,024: compute 112.7 µs, weight read 11.5 µs, communication 105.8 µs512EP2, T = 512: compute 56.4 µs, weight read 455.6 µs, communication 26.7 µsEP4, T = 512: compute 56.4 µs, weight read 230.0 µs, communication 40.0 µsEP8, T = 512: compute 56.4 µs, weight read 117.2 µs, communication 46.6 µsEP16, T = 512: compute 56.4 µs, weight read 60.8 µs, communication 50.0 µsEP32, T = 512: compute 56.4 µs, weight read 32.6 µs, communication 51.6 µsEP64, T = 512: compute 56.4 µs, weight read 18.5 µs, communication 52.5 µsEP128, T = 512: compute 56.4 µs, weight read 11.5 µs, communication 52.9 µs256EP2, T = 256: compute 28.2 µs, weight read 455.6 µs, communication 13.3 µsEP4, T = 256: compute 28.2 µs, weight read 230.0 µs, communication 20.0 µsEP8, T = 256: compute 28.2 µs, weight read 117.2 µs, communication 23.3 µsEP16, T = 256: compute 28.2 µs, weight read 60.8 µs, communication 25.0 µsEP32, T = 256: compute 28.2 µs, weight read 32.6 µs, communication 25.8 µsEP64, T = 256: compute 28.2 µs, weight read 18.5 µs, communication 26.2 µsEP128, T = 256: compute 28.2 µs, weight read 11.5 µs, communication 26.4 µs128EP2, T = 128: compute 14.1 µs, weight read 455.6 µs, communication 6.7 µsEP4, T = 128: compute 14.1 µs, weight read 230.0 µs, communication 10.0 µsEP8, T = 128: compute 14.1 µs, weight read 117.2 µs, communication 11.7 µsEP16, T = 128: compute 14.1 µs, weight read 60.8 µs, communication 12.5 µsEP32, T = 128: compute 14.1 µs, weight read 32.6 µs, communication 12.9 µsEP64, T = 128: compute 14.1 µs, weight read 18.5 µs, communication 13.1 µsEP128, T = 128: compute 14.1 µs, weight read 11.5 µs, communication 13.2 µs64EP2, T = 64: compute 7.0 µs, weight read 455.6 µs, communication 3.3 µsEP4, T = 64: compute 7.0 µs, weight read 230.0 µs, communication 5.0 µsEP8, T = 64: compute 7.0 µs, weight read 117.2 µs, communication 5.8 µsEP16, T = 64: compute 7.0 µs, weight read 60.8 µs, communication 6.2 µsEP32, T = 64: compute 7.0 µs, weight read 32.6 µs, communication 6.5 µsEP64, T = 64: compute 7.0 µs, weight read 18.5 µs, communication 6.6 µsEP128, T = 64: compute 7.0 µs, weight read 11.5 µs, communication 6.6 µs32EP2, T = 32: compute 3.5 µs, weight read 455.6 µs, communication 1.7 µsEP4, T = 32: compute 3.5 µs, weight read 230.0 µs, communication 2.5 µsEP8, T = 32: compute 3.5 µs, weight read 117.2 µs, communication 2.9 µsEP16, T = 32: compute 3.5 µs, weight read 60.8 µs, communication 3.1 µsEP32, T = 32: compute 3.5 µs, weight read 32.6 µs, communication 3.2 µsEP64, T = 32: compute 3.5 µs, weight read 18.5 µs, communication 3.3 µsEP128, T = 32: compute 3.5 µs, weight read 11.5 µs, communication 3.3 µs248163264128EPPDCross-node, 100 GB/s NICEP2, T = 65,536: compute 7,215.5 µs, weight read 455.6 µs, communication 30,702.3 µsEP4, T = 65,536: compute 7,215.5 µs, weight read 230.0 µs, communication 46,053.5 µsEP8, T = 65,536: compute 7,215.5 µs, weight read 117.2 µs, communication 53,729.0 µsEP16, T = 65,536: compute 7,215.5 µs, weight read 60.8 µs, communication 57,566.8 µsEP32, T = 65,536: compute 7,215.5 µs, weight read 32.6 µs, communication 59,485.7 µsEP64, T = 65,536: compute 7,215.5 µs, weight read 18.5 µs, communication 60,445.2 µsEP128, T = 65,536: compute 7,215.5 µs, weight read 11.5 µs, communication 60,924.9 µsEP2, T = 32,768: compute 3,607.8 µs, weight read 455.6 µs, communication 15,351.2 µsEP4, T = 32,768: compute 3,607.8 µs, weight read 230.0 µs, communication 23,026.7 µsEP8, T = 32,768: compute 3,607.8 µs, weight read 117.2 µs, communication 26,864.5 µsEP16, T = 32,768: compute 3,607.8 µs, weight read 60.8 µs, communication 28,783.4 µsEP32, T = 32,768: compute 3,607.8 µs, weight read 32.6 µs, communication 29,742.9 µsEP64, T = 32,768: compute 3,607.8 µs, weight read 18.5 µs, communication 30,222.6 µsEP128, T = 32,768: compute 3,607.8 µs, weight read 11.5 µs, communication 30,462.4 µsEP2, T = 16,384: compute 1,803.9 µs, weight read 455.6 µs, communication 7,675.6 µsEP4, T = 16,384: compute 1,803.9 µs, weight read 230.0 µs, communication 11,513.4 µsEP8, T = 16,384: compute 1,803.9 µs, weight read 117.2 µs, communication 13,432.3 µsEP16, T = 16,384: compute 1,803.9 µs, weight read 60.8 µs, communication 14,391.7 µsEP32, T = 16,384: compute 1,803.9 µs, weight read 32.6 µs, communication 14,871.4 µsEP64, T = 16,384: compute 1,803.9 µs, weight read 18.5 µs, communication 15,111.3 µsEP128, T = 16,384: compute 1,803.9 µs, weight read 11.5 µs, communication 15,231.2 µsEP2, T = 8,192: compute 901.9 µs, weight read 455.6 µs, communication 3,837.8 µsEP4, T = 8,192: compute 901.9 µs, weight read 230.0 µs, communication 5,756.7 µsEP8, T = 8,192: compute 901.9 µs, weight read 117.2 µs, communication 6,716.1 µsEP16, T = 8,192: compute 901.9 µs, weight read 60.8 µs, communication 7,195.9 µsEP32, T = 8,192: compute 901.9 µs, weight read 32.6 µs, communication 7,435.7 µsEP64, T = 8,192: compute 901.9 µs, weight read 18.5 µs, communication 7,555.6 µsEP128, T = 8,192: compute 901.9 µs, weight read 11.5 µs, communication 7,615.6 µsEP2, T = 4,096: compute 451.0 µs, weight read 455.6 µs, communication 1,918.9 µsEP4, T = 4,096: compute 451.0 µs, weight read 230.0 µs, communication 2,878.3 µsEP8, T = 4,096: compute 451.0 µs, weight read 117.2 µs, communication 3,358.1 µsEP16, T = 4,096: compute 451.0 µs, weight read 60.8 µs, communication 3,597.9 µsEP32, T = 4,096: compute 451.0 µs, weight read 32.6 µs, communication 3,717.9 µsEP64, T = 4,096: compute 451.0 µs, weight read 18.5 µs, communication 3,777.8 µsEP128, T = 4,096: compute 451.0 µs, weight read 11.5 µs, communication 3,807.8 µsEP2, T = 2,048: compute 225.5 µs, weight read 455.6 µs, communication 959.4 µsEP4, T = 2,048: compute 225.5 µs, weight read 230.0 µs, communication 1,439.2 µsEP8, T = 2,048: compute 225.5 µs, weight read 117.2 µs, communication 1,679.0 µsEP16, T = 2,048: compute 225.5 µs, weight read 60.8 µs, communication 1,799.0 µsEP32, T = 2,048: compute 225.5 µs, weight read 32.6 µs, communication 1,858.9 µsEP64, T = 2,048: compute 225.5 µs, weight read 18.5 µs, communication 1,888.9 µsEP128, T = 2,048: compute 225.5 µs, weight read 11.5 µs, communication 1,903.9 µsEP2, T = 1,024: compute 112.7 µs, weight read 455.6 µs, communication 479.7 µsEP4, T = 1,024: compute 112.7 µs, weight read 230.0 µs, communication 719.6 µsEP8, T = 1,024: compute 112.7 µs, weight read 117.2 µs, communication 839.5 µsEP16, T = 1,024: compute 112.7 µs, weight read 60.8 µs, communication 899.5 µsEP32, T = 1,024: compute 112.7 µs, weight read 32.6 µs, communication 929.5 µsEP64, T = 1,024: compute 112.7 µs, weight read 18.5 µs, communication 944.5 µsEP128, T = 1,024: compute 112.7 µs, weight read 11.5 µs, communication 952.0 µsEP2, T = 512: compute 56.4 µs, weight read 455.6 µs, communication 239.9 µsEP4, T = 512: compute 56.4 µs, weight read 230.0 µs, communication 359.8 µsEP8, T = 512: compute 56.4 µs, weight read 117.2 µs, communication 419.8 µsEP16, T = 512: compute 56.4 µs, weight read 60.8 µs, communication 449.7 µsEP32, T = 512: compute 56.4 µs, weight read 32.6 µs, communication 464.7 µsEP64, T = 512: compute 56.4 µs, weight read 18.5 µs, communication 472.2 µsEP128, T = 512: compute 56.4 µs, weight read 11.5 µs, communication 476.0 µsEP2, T = 256: compute 28.2 µs, weight read 455.6 µs, communication 119.9 µsEP4, T = 256: compute 28.2 µs, weight read 230.0 µs, communication 179.9 µsEP8, T = 256: compute 28.2 µs, weight read 117.2 µs, communication 209.9 µsEP16, T = 256: compute 28.2 µs, weight read 60.8 µs, communication 224.9 µsEP32, T = 256: compute 28.2 µs, weight read 32.6 µs, communication 232.4 µsEP64, T = 256: compute 28.2 µs, weight read 18.5 µs, communication 236.1 µsEP128, T = 256: compute 28.2 µs, weight read 11.5 µs, communication 238.0 µsEP2, T = 128: compute 14.1 µs, weight read 455.6 µs, communication 60.0 µsEP4, T = 128: compute 14.1 µs, weight read 230.0 µs, communication 89.9 µsEP8, T = 128: compute 14.1 µs, weight read 117.2 µs, communication 104.9 µsEP16, T = 128: compute 14.1 µs, weight read 60.8 µs, communication 112.4 µsEP32, T = 128: compute 14.1 µs, weight read 32.6 µs, communication 116.2 µsEP64, T = 128: compute 14.1 µs, weight read 18.5 µs, communication 118.1 µsEP128, T = 128: compute 14.1 µs, weight read 11.5 µs, communication 119.0 µsEP2, T = 64: compute 7.0 µs, weight read 455.6 µs, communication 30.0 µsEP4, T = 64: compute 7.0 µs, weight read 230.0 µs, communication 45.0 µsEP8, T = 64: compute 7.0 µs, weight read 117.2 µs, communication 52.5 µsEP16, T = 64: compute 7.0 µs, weight read 60.8 µs, communication 56.2 µsEP32, T = 64: compute 7.0 µs, weight read 32.6 µs, communication 58.1 µsEP64, T = 64: compute 7.0 µs, weight read 18.5 µs, communication 59.0 µsEP128, T = 64: compute 7.0 µs, weight read 11.5 µs, communication 59.5 µsEP2, T = 32: compute 3.5 µs, weight read 455.6 µs, communication 15.0 µsEP4, T = 32: compute 3.5 µs, weight read 230.0 µs, communication 22.5 µsEP8, T = 32: compute 3.5 µs, weight read 117.2 µs, communication 26.2 µsEP16, T = 32: compute 3.5 µs, weight read 60.8 µs, communication 28.1 µsEP32, T = 32: compute 3.5 µs, weight read 32.6 µs, communication 29.0 µsEP64, T = 32: compute 3.5 µs, weight read 18.5 µs, communication 29.5 µsEP128, T = 32: compute 3.5 µs, weight read 11.5 µs, communication 29.7 µs248163264128EPPDT per cardP: official prefill (EP4, T = 32,768)D: official decode (EP64, T = 256)
Figure 4: The slowest of the three for each combination of EP and T per GPU. Left: NVLink at 900 GB/s; right: across nodes with a 100 GB/s NIC. P and D mark the official prefill and decode configurations.

Prefill: weight reads are no longer the bottleneck, so take the smallest EP

Prefill handles tens of thousands of tokens per GPU per step; at EP4, rr is already more than ten times the threshold and weight reads take about a sixteenth of compute time. A larger EP saves little weight-read time, but communication over compute rises from 0.71 to 0.83 (from 0.39 to 0.61 even with per-GPU deduplication), and expert load imbalance grows too; the officially measured prefill moe_lb is 1.045 at EP2 and 1.074 at EP4. Prefill therefore takes the smallest EP that fits in memory, with the lower bound set by whether each GPU can hold the weights of 384 / EP experts.

Decode: weight reads are the bottleneck, so grow EP until they fall below compute

Decode’s TT is set by concurrency and latency targets, only 256 tokens per GPU. At EP8, rr is just 32 rows: weight reads take about 117 µs and compute only 28 µs. Since TT cannot grow, EP is the only way to push rr past the threshold: going from EP8 to EP64 brings weight reads down to about 19 µs, below compute. This is also how the deployment document explains Wide-EP: it substantially reduces per-step memory traffic in the MoE expert part. The official curves agree (Figure 5): at 64 requests per GPU, EP64 delivers about 15.7K tok/s per GPU and 246 tok/s per user, versus about 12.6K and 198 for EP8.

0K5K10K15K20K25K30K050100150200250300350Per-user speed (tok/s)Throughput per GPU (tok/s/GPU)EP64EP8EP64, 8 requests per card: 325 tok/s per user, 2,600 tok/s per GPUEP64, 16 requests per card: 315 tok/s per user, 5,050 tok/s per GPUEP64, 32 requests per card: 297 tok/s per user, 9,500 tok/s per GPUEP64, 64 requests per card: 246 tok/s per user, 15,700 tok/s per GPUEP64, 128 requests per card: 178 tok/s per user, 22,800 tok/s per GPUEP64, 256 requests per card: 103 tok/s per user, 26,300 tok/s per GPUEP8, 8 requests per card: 238 tok/s per user, 1,900 tok/s per GPUEP8, 16 requests per card: 230 tok/s per user, 3,700 tok/s per GPUEP8, 32 requests per card: 220 tok/s per user, 7,050 tok/s per GPUEP8, 64 requests per card: 198 tok/s per user, 12,650 tok/s per GPUEP8, 128 requests per card: 150 tok/s per user, 19,200 tok/s per GPUEP8, 256 requests per card: 98 tok/s per user, 24,900 tok/s per GPU64 requests: 15.7K, 24664 requests: 12.6K, 198Read from the decode chart in the DeepInfer README; approximate. Context 131,072, nextn = 3.
Figure 5: DeepInfer's official decode curves, read from the chart in the README; values are approximate.

Once rr passes r∗r^{*}, weight reads are below compute and a larger EP saves no more time: compute stays at 28 µs while communication and expert load imbalance keep growing. By the estimates here, the crossover lies between EP32 and EP64, and the officially recommended EP64 to EP128 sits just past it. In that range communication over compute is about 0.93, every layer synchronizes with 63 or more other GPUs, and one expert at 10 times the average load is enough to push lblb to 2.5. Decode therefore needs an NVLink domain of 64 to 128 GPUs and relies on EPLB to replicate busy experts and rebalance periodically; the officially measured decode moe_lb is 1.090.

EP
T per GPU
threshold ≈ 150 rows
Compute
Weight read
Communication

Figure 6: The three times per layer per GPU (B200, NVLink 900 GB/s). The dot marks rows per expert on a log scale.

Figure 6 lets you check both trade-offs yourself: with TT at 32K, compute is the longest of the three for every EP; with TT at 256, weight-read time drops quickly as EP grows from 8 until rr crosses the threshold, after which communication is close to compute and a larger EP saves no more time.

5. How the threshold moves with hardware and precision

The threshold r∗r^{*} depends only on weight precision and the hardware’s ratio of compute to memory bandwidth. Figure 7 lists a few common combinations. FP4 weights take half the bytes of FP8 and halve the threshold, which is why V4.1 needs only about 150 rows on B200; with FP4 activations as well, compute doubles and the threshold returns to about 300 rows. H20 has little compute relative to its memory bandwidth, and its threshold is only about 37 rows.

Weight-reuse threshold r* (rows) = bytes per parameter ÷ 2 × dense FLOPS ÷ memory bandwidth0100200300H20, FP8 weightsH20, FP8 weights: r* ≈ 37 rows37B200, FP4 weights × FP8 activationsB200, FP4 weights × FP8 activations: r* ≈ 149 rows149H200, FP8 weightsH200, FP8 weights: r* ≈ 206 rows206B200, FP8 weightsB200, FP8 weights: r* ≈ 281 rows281H100, FP8 weightsH100, FP8 weights: r* ≈ 295 rows295B200, FP4 weights × FP4 activationsB200, FP4 weights × FP4 activations: r* ≈ 299 rows299Blue: the configuration in this post. Dense peak and memory bandwidth from public specs.
Figure 7: Weight-reuse threshold for different GPUs and weight precisions, from public dense peak throughput and memory bandwidth.

Setting r=r∗r = r^{*} gives the smallest EP at which decode crosses the threshold:

EPmin⁡=r∗×3846×TEP_{\min} = \frac{r^{*} \times 384}{6 \times T}

At 256 tokens per GPU, FP4 experts on B200 need about EP38, FP8 experts on H100 about EP74, and H20 about EP9. With the same model and load, EP8 to EP16 already gets H20 past the threshold, and a larger EP saves little; H100 needs a larger EP than B200, but an H100 machine has only 8 GPUs, so EP74 must cross nodes and communication becomes the new bottleneck, much like the right side of Figure 4. For another model, replace 384 and 6 with its number of routed experts and experts selected per token.

6. How the estimates differ from real systems

The numbers above are idealized: peak compute and memory bandwidth, perfectly uniform routing, communication fully hidden behind compute, and only the MoE layer. A real system falls short on each, but most of these shortfalls change the values, not the direction of the comparisons.

Matrix multiplications and memory traffic both fall short of peak. The threshold r∗r^{*} depends on their ratio; at 70% of peak compute and 85% of peak bandwidth it is about 120 rows, and counting each row’s activation traffic raises it by about a fifth. With the threshold anywhere from 120 to 180 rows, decode needs EP between 30 and 45 to cross it: EP8 is far from enough and EP64 is enough, so the conclusion holds.

Routing is not perfectly uniform either. Expert load imbalance makes the busiest GPU compute more and receive more, scaling compute and communication by the same factor and leaving their ratio unchanged; with EPLB enabled, the officially measured factor is between 1.045 and 1.090, a limited effect.

Communication is not always fully hidden either. Every layer has a fixed cost for synchronization and launch that does not depend on how much data moves. For prefill, with about 3.6 ms of compute per layer, it is negligible; for decode, with only about 28 µs, it is a sizable share, and it grows with the number of GPUs that synchronize. This is one more reason decode stops at EP64 to EP128 rather than going further.

Finally, this post covers only the MoE layer. On the official curve, a decode step at 64 requests per GPU takes about 13 ms, of which the MoE layer is only a part; the rest is attention and a large fixed cost that does not depend on batch size. The numbers here are meant for judging which term is the bottleneck at each EP and which way to move EP, not as actual step times; the measured gap between EP64 and EP8 in the official curves points in the same direction as the derivation. How attention and these fixed costs affect the choice of EP is left for later.

Conclusion

The EP choices for prefill and decode look opposite but follow the same rule: EP does not change compute per GPU, only rows per expert, copies per token and expert load imbalance. Rows per expert scale with EP × T and set weight-read time; copies and expert load imbalance depend only on EP.

The reasoning is not specific to V4.1. DeepSeek’s V3/R1 inference system runs decode at EP144, with the same stated goal of giving each expert a large enough batch; NVIDIA’s Wide-EP on GB200 NVL72 and MegaScale-Infer’s separation of attention and experts onto different GPUs to pool tokens start from the same point. For another model or GPU, the two formulas in Section 5 give a rough sense of which way to move EP.

References

  1. DeepInfer, Inference deployment Q&A
  2. DeepInfer, README: Performance benchmarks
  3. DeepSeek, Day 6: DeepSeek-V3/R1 Inference System Overview
  4. NVIDIA, Scaling Large MoE Models with Wide Expert Parallelism on NVL72 Rack Scale Systems
  5. Zhu et al., MegaScale-Infer: Serving Mixture-of-Experts at Scale with Disaggregated Expert Parallelism