Skip to content

Commit 26fefc2

Browse files
committed
[Ulysses] Carry the KV head count per call instead of a module global
deepspeedai#8241 gives Ulysses its own _ulysses_num_kv_heads, memoized on the first uneven all-to-all, and a TODO saying the first model to take that path then decides how every later one is sharded. This removes it. The count is threaded through DistributedAttention and _SeqAllToAll, resolved once in forward and replayed from ctx in backward, which is the part the gather direction cannot recover from an already-sharded tensor. AutoTPMeta is built only where get_shard_size_list needs it. It is the KV count, taken from the key tensor rather than the query tensor: under GQA a query head has to land on the rank holding its KV head, and Q // world_size would split the group. Q=6 / KV=3 over sp=2 partitions [4, 2], not [3, 3]. Only an indivisible count takes the uneven implementation. Keying on "a count was supplied" would route evenly-split models there too, and that path rejects async_op, which is how the overlapped q/k calls run. All in-tree call sites move with it. FPDT threads kv_projection_size // hidden_size_per_attention_head through its 17 direct calls and saves it on both custom autograd contexts. The two TestUlyssesAll2All_odd all-to-alls pass their count as well; the second one runs in the gather direction and used to read it back out of the global. The rank-consistent num_kv_heads >= sp_size check stays, before the first collective, so KV=1 over sp=2 fails on every rank rather than leaving one inside a collective while another divides by zero. Regressions land in test_ulysses.py, which nv-flash-attn and hpu-gaudi2-nightly already run: the even fast path with async_op, the uneven path, GQA Q=6/KV=3 with gradients on q, k and v, the synchronized KV=1 rejection, and a 3 -> 5 -> 3 two-model sequence. Megatron-DeepSpeed passes num_key_value_heads_per_partition at the DistributedAttention construction; that is a separate repo and not in this change. Signed-off-by: alanhuangyoo <alanhuangyoo@gmail.com>
1 parent 19e2735 commit 26fefc2

3 files changed

Lines changed: 293 additions & 69 deletions

File tree

deepspeed/sequence/fpdt_layer.py

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ def forward(ctx: Any,
155155
cpu_offloading=True):
156156

157157
do_save = layernorm_output.requires_grad
158+
# The all-to-all partitions by KV group, and only the caller knows that count: the
159+
# gather-direction and backward calls below see an already-sharded head dim.
160+
num_kv_heads = kv_projection_size // hidden_size_per_attention_head
158161

159162
if rotary_pos_emb is not None:
160163
pos_emb_cos, pos_emb_sin = rotary_pos_emb[0].permute(1, 0, 2, 3), rotary_pos_emb[1].permute(1, 0, 2, 3)
@@ -180,6 +183,7 @@ def forward(ctx: Any,
180183
ctx.dtype = layernorm_output.dtype
181184
ctx.projection_size = projection_size
182185
ctx.kv_projection_size = kv_projection_size
186+
ctx.num_kv_heads = num_kv_heads
183187

184188
global_q = []
185189
global_k = []
@@ -206,7 +210,7 @@ def forward(ctx: Any,
206210
q_chunk = qkv_chunk[:, :, :projection_size].contiguous().reshape(
207211
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
208212
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
209-
q_chunk = single_all_to_all(q_chunk, scatter_idx, gather_idx, 0, spg)
213+
q_chunk = single_all_to_all(q_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
210214
global_q_chunk_len = q_chunk.shape[1]
211215
if rotary_pos_emb is not None:
212216
q_chunk = apply_rotary_pos_emb(q_chunk,
@@ -217,7 +221,7 @@ def forward(ctx: Any,
217221
k_chunk = qkv_chunk[:, :, projection_size:projection_size + kv_projection_size].contiguous().reshape(
218222
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
219223
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
220-
k_chunk = single_all_to_all(k_chunk, scatter_idx, gather_idx, 0, spg)
224+
k_chunk = single_all_to_all(k_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
221225
if rotary_pos_emb is not None:
222226
k_chunk = apply_rotary_pos_emb(k_chunk,
223227
pos_emb_cos[:, global_q_chunk_len * i:global_q_chunk_len * (i + 1)],
@@ -227,7 +231,7 @@ def forward(ctx: Any,
227231
v_chunk = qkv_chunk[:, :, projection_size + kv_projection_size:].contiguous().reshape(
228232
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
229233
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
230-
v_chunk = single_all_to_all(v_chunk, scatter_idx, gather_idx, 0, spg)
234+
v_chunk = single_all_to_all(v_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
231235
global_v.append(v_chunk)
232236

233237
for k_i in range(len(global_k)):
@@ -276,7 +280,12 @@ def forward(ctx: Any,
276280

277281
for i in range(num_chunks):
278282
global_lse[i] = global_lse[i][:, :, :, 0].permute(0, 2, 1).contiguous()
279-
output[i] = single_all_to_all(global_o[i].to(ctx.dtype).contiguous(), gather_idx, scatter_idx, 0, spg)
283+
output[i] = single_all_to_all(global_o[i].to(ctx.dtype).contiguous(),
284+
gather_idx,
285+
scatter_idx,
286+
0,
287+
spg,
288+
num_kv_heads=num_kv_heads)
280289
output = torch.cat(output, dim=1)
281290

282291
head_dim = output.shape[-1]
@@ -312,6 +321,7 @@ def backward(ctx, grad_output):
312321

313322
projection_size = ctx.projection_size
314323
kv_projection_size = ctx.kv_projection_size
324+
num_kv_heads = ctx.num_kv_heads
315325

316326
layernorm_output = ctx.saved_tensors[0]
317327

@@ -338,7 +348,12 @@ def backward(ctx, grad_output):
338348
st = chunk_size * i
339349
ed = st + chunk_size
340350
grad_global_attn_output.append(
341-
single_all_to_all(grad_output[:, st:ed].contiguous(), scatter_idx, gather_idx, 0, spg))
351+
single_all_to_all(grad_output[:, st:ed].contiguous(),
352+
scatter_idx,
353+
gather_idx,
354+
0,
355+
spg,
356+
num_kv_heads=num_kv_heads))
342357

343358
del grad_output
344359

@@ -440,8 +455,8 @@ def backward(ctx, grad_output):
440455
else:
441456
dk[i] = dk[i].to(dtype)
442457
dv[i] = dv[i].to(dtype)
443-
dk[i] = single_all_to_all(dk[i].contiguous(), gather_idx, scatter_idx, 0, spg)
444-
dv[i] = single_all_to_all(dv[i].contiguous(), gather_idx, scatter_idx, 0, spg)
458+
dk[i] = single_all_to_all(dk[i].contiguous(), gather_idx, scatter_idx, 0, spg, num_kv_heads=num_kv_heads)
459+
dv[i] = single_all_to_all(dv[i].contiguous(), gather_idx, scatter_idx, 0, spg, num_kv_heads=num_kv_heads)
445460

446461
input_st = i * input_chunk_size
447462
input_ed = input_st + input_chunk_size
@@ -474,7 +489,12 @@ def backward(ctx, grad_output):
474489
ctx.pos_emb_sin[:, dq_seq_len * i:dq_seq_len * (i + 1)])
475490
else:
476491
dq[i] = dq[i].to(dtype)
477-
dq[i] = single_all_to_all(dq[i].to(dtype).contiguous(), gather_idx, scatter_idx, 0, spg)
492+
dq[i] = single_all_to_all(dq[i].to(dtype).contiguous(),
493+
gather_idx,
494+
scatter_idx,
495+
0,
496+
spg,
497+
num_kv_heads=num_kv_heads)
478498

479499
input_chunk = layernorm_output[:input_chunk_size].reshape(-1, layernorm_output.shape[-1])
480500
layernorm_output = layernorm_output[input_chunk_size:]
@@ -565,6 +585,9 @@ def forward(ctx: Any,
565585
cpu_offloading=True):
566586

567587
do_save = layernorm_output.requires_grad
588+
# The all-to-all partitions by KV group, and only the caller knows that count: the
589+
# gather-direction and backward calls below see an already-sharded head dim.
590+
num_kv_heads = kv_projection_size // hidden_size_per_attention_head
568591

569592
if rotary_pos_emb is not None:
570593
pos_emb_cos, pos_emb_sin = rotary_pos_emb[0].permute(1, 0, 2, 3), rotary_pos_emb[1].permute(1, 0, 2, 3)
@@ -590,6 +613,7 @@ def forward(ctx: Any,
590613
ctx.dtype = layernorm_output.dtype
591614
ctx.projection_size = projection_size
592615
ctx.kv_projection_size = kv_projection_size
616+
ctx.num_kv_heads = num_kv_heads
593617

594618
global_q = []
595619
global_k = []
@@ -628,18 +652,18 @@ def forward(ctx: Any,
628652
q_chunk = qkv_chunk[:, :, :projection_size].contiguous().reshape(
629653
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
630654
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
631-
q_chunk = single_all_to_all(q_chunk, scatter_idx, gather_idx, 0, spg)
655+
q_chunk = single_all_to_all(q_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
632656
global_q_chunk_len = q_chunk.shape[1]
633657

634658
k_chunk = qkv_chunk[:, :, projection_size:projection_size + kv_projection_size].contiguous().reshape(
635659
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
636660
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
637-
k_chunk = single_all_to_all(k_chunk, scatter_idx, gather_idx, 0, spg)
661+
k_chunk = single_all_to_all(k_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
638662

639663
v_chunk = qkv_chunk[:, :, projection_size + kv_projection_size:].contiguous().reshape(
640664
qkv_chunk.shape[0], qkv_chunk.shape[1], -1,
641665
hidden_size_per_attention_head).permute(1, 0, 2, 3).contiguous() # b, l, nh, hd
642-
v_chunk = single_all_to_all(v_chunk, scatter_idx, gather_idx, 0, spg)
666+
v_chunk = single_all_to_all(v_chunk, scatter_idx, gather_idx, 0, spg, num_kv_heads=num_kv_heads)
643667

644668
dist.barrier()
645669

@@ -739,8 +763,12 @@ def forward(ctx: Any,
739763
global_q[q_compute_chunk_idx].offload()
740764
q_compute_chunk_idx += 1
741765

742-
all2all_output = single_all_to_all(
743-
cur_attn_output.to(ctx.dtype).contiguous(), gather_idx, scatter_idx, 0, spg)
766+
all2all_output = single_all_to_all(cur_attn_output.to(ctx.dtype).contiguous(),
767+
gather_idx,
768+
scatter_idx,
769+
0,
770+
spg,
771+
num_kv_heads=num_kv_heads)
744772
final_output.append(all2all_output)
745773
with get_accelerator().stream(general_offload_stream):
746774
global_o.append(SequenceChunk(cur_attn_output.to(ctx.dtype)))
@@ -783,6 +811,7 @@ def backward(ctx, grad_output):
783811

784812
projection_size = ctx.projection_size
785813
kv_projection_size = ctx.kv_projection_size
814+
num_kv_heads = ctx.num_kv_heads
786815

787816
layernorm_output = ctx.layernorm_output
788817

@@ -821,8 +850,12 @@ def backward(ctx, grad_output):
821850
device=qkv_linear_weight.device,
822851
dtype=torch.float)
823852

824-
grad_global_attn_output_chunk = single_all_to_all(grad_output[:, :chunk_size].contiguous(), scatter_idx,
825-
gather_idx, 0, spg)
853+
grad_global_attn_output_chunk = single_all_to_all(grad_output[:, :chunk_size].contiguous(),
854+
scatter_idx,
855+
gather_idx,
856+
0,
857+
spg,
858+
num_kv_heads=num_kv_heads)
826859
get_accelerator().synchronize()
827860
grad_output = grad_output[:, chunk_size:]
828861

@@ -931,7 +964,11 @@ def backward(ctx, grad_output):
931964

932965
if grad_global_attn_output[next_q_compute_chunk_idx] is None:
933966
grad_global_attn_output_chunk = single_all_to_all(grad_output[:, :chunk_size].contiguous(),
934-
scatter_idx, gather_idx, 0, spg)
967+
scatter_idx,
968+
gather_idx,
969+
0,
970+
spg,
971+
num_kv_heads=num_kv_heads)
935972
dist.barrier()
936973
grad_output = grad_output[:, chunk_size:]
937974
grad_global_attn_output[next_q_compute_chunk_idx] = SequenceChunk(
@@ -976,9 +1013,24 @@ def backward(ctx, grad_output):
9761013
dk_accum = dk_accum.to(dtype)
9771014
dv_accum = dv_accum.to(dtype)
9781015

979-
dq_accum = single_all_to_all(dq_accum.contiguous(), gather_idx, scatter_idx, 0, spg)
980-
dk_accum = single_all_to_all(dk_accum.contiguous(), gather_idx, scatter_idx, 0, spg)
981-
dv_accum = single_all_to_all(dv_accum.contiguous(), gather_idx, scatter_idx, 0, spg)
1016+
dq_accum = single_all_to_all(dq_accum.contiguous(),
1017+
gather_idx,
1018+
scatter_idx,
1019+
0,
1020+
spg,
1021+
num_kv_heads=num_kv_heads)
1022+
dk_accum = single_all_to_all(dk_accum.contiguous(),
1023+
gather_idx,
1024+
scatter_idx,
1025+
0,
1026+
spg,
1027+
num_kv_heads=num_kv_heads)
1028+
dv_accum = single_all_to_all(dv_accum.contiguous(),
1029+
gather_idx,
1030+
scatter_idx,
1031+
0,
1032+
spg,
1033+
num_kv_heads=num_kv_heads)
9821034

9831035
general_offload_stream.synchronize()
9841036
compute_stream.wait_stream(general_offload_stream)

0 commit comments

Comments
 (0)