This document is relevant for: Trn2, Trn3
Attention DP#
Overview#
Attention DP shards Q and O projection weights across TP * attention_dp devices instead of just TP. When KV heads exceed TP, KV weights are also sharded across attention DP. The KV cache is always batch-sharded (each DP rank stores its own batches) with full per-TP KV heads. This eliminates redundant weight copies across DP groups, reducing HBM memory and bandwidth usage.
Decode-only. Prefill does not use attention DP.
The NKI attention API calls this same batch partition KVDP. It is an internal
name for attention DP, not a separate user setting. DCP instead
shards sequence positions within a batch.
Case study: GQA model with Q=64, KV=8 running on 64 devices with TP=8, DP=8.
With standard independent DP: each DP group replicates all Q/O weights (8 copies of the two largest attention matrices). With attention DP=8: Q/O weights are sharded across all 64 devices. Each device holds 1 Q head and 1 O column instead of 8. Zero Q/O replication.
Problem Statement#
For GQA models with few KV heads (e.g., Q=64, KV=8), Q and O projections dominate attention weight memory (64 heads vs 8 KV heads). Standard DP replicates these weights across all DP groups:
Component |
Standard TP=8 DP=8 |
Q-only a2a (TP=8 DDP=8) |
Q+K+V a2a (TP=2 DDP=4) |
|---|---|---|---|
Q weight per device |
8 Q heads |
1 Q head |
8 Q heads |
O weight per device |
8 O columns |
1 O column |
8 O columns |
KV weight per device |
1 KV head |
1 KV head (unchanged) |
1 KV head (sharded) |
KV cache per device |
own batch |
own batch |
own batch, full TP heads |
Q/O copies across DP |
8x replicated |
1x (zero) |
1x (zero) |
KV weight copies |
8x replicated |
8x replicated |
1x (zero) |
Attention DP eliminates this redundancy at the cost of additional collectives (all-gather, all-to-all, reduce-scatter) during decode.
Configuration#
NeuronConfig(attention_dp_size=4)
attention_dp_size=1: disabled (standard independent DP)attention_dp_size=N: Q/O sharded acrossTP * NdevicesConstraint:
attention_dp_sizemust dividedp_sizeConstraint:
TP * attention_dp_sizemust not exceednum_attention_heads
Two Variants (Auto-Detected)#
The code auto-detects which variant to use based on num_kv_heads vs tp_size:
Condition |
Variant |
What’s sharded across attention DP |
KV flow |
|---|---|---|---|
|
Q-only a2a |
Q and O weights |
K/V sliced to local batch |
|
Q+K+V a2a |
Q, K, V, and O weights |
K/V also all-to-all’d |
In the Q+K+V variant, KV weights are sharded to num_kv_heads / (TP * attention_dp) per rank. After all-to-all, each device has num_kv_heads / TP KV heads (the standard per-TP amount). The KV cache stores the gathered heads so attention runs locally without gathering prior context every step.
Process Groups#
Full attention DP (attention_dp_size == dp_size): Zero Q/O Replication#
# TP=8, DP=4, attention DP=4 — 32 ranks, one supergroup
TP rank: 0 1 2 3 4 5 6 7
─────────────────────────────────────────
DP0: [ 0 1 2 3 4 5 6 7 ]
DP1: [ 8 9 10 11 12 13 14 15 ]
DP2: [16 17 18 19 20 21 22 23 ]
DP3: [24 25 26 27 28 29 30 31 ]
│ │
attention DP columns (8 groups, one per TP position)
[0,8,16,24] ... [7,15,23,31]
All 4 DP groups form one supergroup. Q/O weights are sharded across all TP * attention DP = 32 devices with zero replication.
Partial attention DP (attention_dp_size < dp_size): Multiple Supergroups#
# TP=8, DP=8, attention DP=4 — 64 ranks, 2 independent supergroups of 32
TP rank: 0 1 2 3 4 5 6 7
─────────────────────────────────────────
Supergroup 0:
DP0: [ 0 1 2 3 4 5 6 7 ]
DP1: [ 8 9 10 11 12 13 14 15 ]
DP2: [16 17 18 19 20 21 22 23 ]
DP3: [24 25 26 27 28 29 30 31 ]
│ │
[0,8,16,24] ... [7,15,23,31]
─ ─ ─ ─ ─ No communication across this boundary ─ ─ ─ ─ ─
Supergroup 1:
DP4: [32 33 34 35 36 37 38 39 ]
DP5: [40 41 42 43 44 45 46 47 ]
DP6: [48 49 50 51 52 53 54 55 ]
DP7: [56 57 58 59 60 61 62 63 ]
│ │
[32,40,48,56] ... [39,47,55,63]
Q/O weights are sharded within each supergroup (TP * attention DP = 32 devices) and replicated 2x across the two supergroups. Rank 0 and rank 32 load the same Q head.
Summary#
Group |
Type |
Ranks (example for rank 0) |
Collectives |
Where |
|---|---|---|---|---|
Attention TP Supergroup |
|
|
|
Model code |
attention DP column |
|
|
Batch |
Model code + torch fallback |
Note: The attention component TP group replaces the previous two-step collective (reduce-scatter across DP column + all-reduce across TP). A single
all_reduceacross the supergroup achieves the same result. The DP column group is now used only for batch transitions via_dp_transition, not for the O-projection collective. See Component-Level DP Sharding for details on the batch state machine.
Decode-Only Flow#
attention DP is decode-only. Prefill uses standard TP with no attention DP awareness.
Per-Layer Decode Flow#
Caller (_dp_transition) ensures input is gathered to attn_dp:
X: [DDP*B_local, S_tkg, H]
Step 1 — Fused QKV projection on gathered X:
[DDP*B_local, S_tkg, H] @ W_qkv → Q, K, V for all DDP batches
Q: num_q_heads / (TP * DDP) heads per rank
K, V: num_kv_heads / (TP * DDP) heads if kv_needs_a2a, else num_kv_heads / TP
Step 2 — All-to-all Q across attention DP column group:
Swap "few heads x many batches" → "many heads x own batch"
Q: [DDP*B_local, q_small, S, d] → [B_local, q_standard, S, d]
Step 2b — (if kv_needs_a2a) All-to-all K, V across attention DP column group:
K: [DDP*B_local, kv_small, S, d] → [B_local, kv_standard, S, d]
V: same as K
Step 2c — (if !kv_needs_a2a) Select local K, V:
K = K[local_batch_slice]
V = V[local_batch_slice]
Step 3 — RoPE on local Q and K:
Uses local batch's cos/sin only. No cos/sin gathering needed.
Step 4 — Standard attention (completely standard, no attention DP awareness):
Q (all heads, own batch) x KV cache (own batch) → attention output
Block KV cache, GQA expansion, softmax — all unchanged.
Step 5 — Reverse all-to-all across attention DP column group:
Swap "many heads x own batch" → "few heads x many batches"
Step 6 — O projection on all DDP batches:
[DDP*B_local, S, q_small * d] @ W_o → [DDP*B_local, S, H]
Step 7 — Attention TP component TP group all-reduce:
Sums both TP weight partials and DP weight-shard partials in one collective.
[DDP*B_local, S, H] → [DDP*B_local, S, H]
Output stays gathered at attn_dp.
Caller (_dp_transition) handles any transition to the next module's dp_size.
Change from previous design: Steps 1 (all-gather) and 8-9 (reduce-scatter + TP all-reduce) were replaced. The caller now handles batch gathering via
_dp_transition, and the two output collectives were merged into a single component TP group all-reduce. See Component-Level DP Sharding for the batch state machine design.
Weight Sharding#
QKV weight stays fused as a single tensor. Weight loading uses effective rank:
effective_rank = attention_dp_rank + tp_rank * attention_dp_size
Q-only a2a variant (KV fits in TP)#
Example: Q=64, KV=8, TP=8, attention_dp=8
q_size = (64/64) * d = 1d ← 1 Q head per rank (sharded across TP*DDP)
kv_size = (8/8) * d = 1d ← 1 KV head per rank (sharded across TP only)
KV weights loaded by TP rank. Q/O loaded by effective rank.
Q+K+V a2a variant (KV exceeds TP)#
Example: Q=32, KV=8, TP=2, attention_dp=4
q_size = (32/8) * d = 4d ← 4 Q heads per rank (sharded across TP*DDP)
kv_size = (8/8) * d = 1d ← 1 KV head per rank (sharded across TP*DDP)
Both Q and KV weights loaded by effective rank. The KV cache stores
num_kv_heads / TP heads (the gathered amount after a2a), not the
sharded per-rank amount.
O projection is always sharded across TP*DDP:
o_proj_weight: [q_heads_per_rank * d, H]
q_heads_per_rank = num_q_heads / (TP * attention_dp)
Implementation Files#
File |
Change |
|---|---|
|
|
|
|
|
|
|
Torch fallback attention DP flow (all-to-all Q, local K/V selection, reverse all-to-all) |
|
|
KV Cache#
The KV cache layout is the same in both variants — each rank stores num_kv_heads / TP heads for its local batch (standard TP+DP layout). Attention DP does not change cache sharding:
Cache per rank: [num_blocks, num_kv_heads / TP, block_len, d_head]
The two variants differ in how newly projected K/V tokens reach the cache:
Q-only a2a (TP=8, DDP=8, KV=8):
Weight projects: 1 KV head (= num_kv / TP)
Cache stores: 1 KV head
Flow: project → slice to local batch → write to cache (no KV collective)
Q+K+V a2a (TP=2, DDP=4, KV=8):
Weight projects: 1 KV head (= num_kv / (TP * DDP))
Cache stores: 4 KV heads (= num_kv / TP)
Flow: project 1 head → a2a gathers to 4 heads → write 4 heads to cache
In the Q+K+V variant, the weight projects fewer heads than the cache stores. The a2a gathers newly projected tokens (S_tkg, typically 1) to the full per-TP head count before writing to cache. Prior context already in the cache reads locally — only new tokens go through the a2a each step.
Constraints#
Decode-only: Prefill is not modified. With attention DP-sharded weights, prefill would need weight gathering or a separate flow.
Q head count:
TP * attention_dpmust not exceednum_attention_heads.KV divisibility: For Q+K+V a2a,
num_kv_headsmust be divisible byTP * attention_dp. When it’s not, KV falls back to TP-only sharding (Q-only a2a variant).DP divisibility:
attention_dp_sizemust dividedp_size(for partial supergroups).KV cache: Always stores
num_kv_heads / TPheads per rank (post-gather), regardless of variant. Weight sharding saves weight memory, not cache memory. Cache memory is reduced by batch-sharding (each DP rank stores fewer batches).
When to Use Attention DP#
Models with large Q/O and small K/V (GQA with high Q:KV ratio) — eliminates redundant Q/O weight copies across DP groups.
Models with large KV heads (small GQA ratio) where you want to shard KV weights while keeping the cache TP-sharded — the Q+K+V a2a variant shards KV weights across attention DP while the cache retains the standard TP head sharding.
When KV optimizations like sliding window are used (Q/O weight loads dominate perf).
Better option than DCP when concurrency is large (avoids additional DCP collectives).
Required synchronization already exists for MoE models with EP across DP.
This document is relevant for: Trn2, Trn3