Skip to content

Commit 21582fe

Browse files
authored
[Bugfix][Feature] Adapt o_proj TP path for MXFP8 dynamic quant in SFA (vllm-project#10056)
### What this PR does / why we need it? This PR adapts the o_proj tensor-parallel path for MXFP8 dynamic quantization. It keeps contiguous TP-mode copies of o_proj.weight and o_proj.weight_scale, and switches between TP and EP parameter views when running the MXFP8 dynamic quantized o_proj path. This avoids using stale or incompatible parameter layouts when graph capture and expert-parallel execution require different o_proj views. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Not run locally. The change is scoped to the MXFP8 dynamic quantized o_proj path and should be covered by CI/NPU validation for the affected model path. - vLLM version: v0.22.1 - vLLM main: vllm-project/vllm@967c5c3 --------- Signed-off-by: yyt <yangyit139@gmail.com>
1 parent bdc1523 commit 21582fe

1 file changed

Lines changed: 110 additions & 7 deletions

File tree

vllm_ascend/attention/sfa_v1.py

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ class AscendSFAImpl(MLAAttentionImpl):
399399

400400
# Supports forward using the all-gather o_proj weight for decode requests when Sharded CP is enabled.
401401
o_proj_full_pool: torch.Tensor | None = None
402+
o_proj_full_weight_scale_pool: torch.Tensor | None = None
402403

403404
# q_hadamard and k_hadamard tensor shared when dsa c8 enabled
404405
q_hadamard: torch.Tensor | None = None
@@ -501,6 +502,7 @@ def __init__(
501502
# use original TP o_proj weight in PD mix stage, and full gather
502503
# for o_proj weight for prefill stage.
503504
self.enable_dsa_cp_with_o_proj_tp = enable_dsa_cp_with_o_proj_tp()
505+
self._o_proj_dynamic_quant = False
504506

505507
if self.enable_dsa_cp:
506508
self.local_num_heads = self.num_heads * self.tp_size
@@ -575,7 +577,7 @@ def process_weights_after_loading(self, act_dtype: torch.dtype):
575577
if is_hidden_layer(layer):
576578
post_process_after_loading_for_shard_weight_series(layer)
577579
else:
578-
self._init_o_proj_tp_full_params()
580+
self._maybe_init_o_proj_tp_full_params()
579581

580582
if self.enable_mlapo:
581583
quant_method = getattr(
@@ -776,6 +778,46 @@ def rope_single(
776778
x = torch_npu.npu_interleave_rope(x, cos, sin)
777779
return x.view(B, N, D)
778780

781+
def _check_o_proj_dynamic_quant(self) -> bool:
782+
return hasattr(self.o_proj, "weight_scale")
783+
784+
def _maybe_init_o_proj_tp_full_params(self):
785+
if self._check_o_proj_dynamic_quant():
786+
self._o_proj_dynamic_quant = True
787+
self._init_dynamic_quant_o_proj_tp_full_params()
788+
else:
789+
self._init_o_proj_tp_full_params()
790+
791+
def _init_dynamic_quant_o_proj_tp_full_params(self):
792+
"""
793+
Initialize TP-mode and Full-mode parameters for o_proj weight,
794+
preparing for weight switching in PD mix stage.
795+
796+
For PD mix stage:
797+
- Use original TP o_proj weight for decode phase
798+
- Need full-gather o_proj weight from all TP ranks for prefill phase
799+
"""
800+
if AscendSFAImpl.o_proj_full_pool is None:
801+
sample = self.o_proj.weight
802+
AscendSFAImpl.o_proj_full_pool = torch.empty(
803+
(sample.shape[0] * self.tp_size, sample.shape[1]), dtype=sample.dtype, device=sample.device
804+
)
805+
if AscendSFAImpl.o_proj_full_weight_scale_pool is None:
806+
sample = self.o_proj.weight_scale
807+
AscendSFAImpl.o_proj_full_weight_scale_pool = torch.empty(
808+
(sample.shape[0] * self.tp_size, sample.shape[1], sample.shape[2]),
809+
dtype=sample.dtype,
810+
device=sample.device,
811+
)
812+
813+
# Save TP-mode parameters (original sharded weights)
814+
self.o_proj_tp_weight = self.o_proj.weight.clone().detach()
815+
self.o_proj_tp_weight_scale = self.o_proj.weight_scale.clone().detach()
816+
817+
# Initially switch to TP mode for graph capture
818+
self.o_proj.weight.set_(self.o_proj_tp_weight)
819+
self.o_proj.weight_scale.set_(self.o_proj_tp_weight_scale)
820+
779821
def _init_o_proj_tp_full_params(self):
780822
"""
781823
Initialize TP-mode and Full-mode parameters for o_proj weight,
@@ -808,6 +850,51 @@ def _init_o_proj_tp_full_params(self):
808850
self.o_proj_full_aclnn_input_scale_reciprocal = self.o_proj.aclnn_input_scale_reciprocal.repeat(self.tp_size)
809851
self.o_proj_full_aclnn_input_offset = self.o_proj.aclnn_input_offset.repeat(self.tp_size)
810852

853+
def _handle_dynamic_quant_o_proj_weight_switch_and_forward(
854+
self,
855+
attn_output: torch.Tensor,
856+
output: torch.Tensor,
857+
o_proj_full_handle: torch.distributed.Work | None,
858+
o_proj_full_weight_scale_handle: torch.distributed.Work | None,
859+
should_shard_weight: bool,
860+
) -> tuple[torch.Tensor, bool]:
861+
"""
862+
Handle o_proj weight switching between TP-mode and Full-mode, and execute forward computation.
863+
"""
864+
# Gather o_proj weight from all TP ranks for Full-mode computation
865+
if should_shard_weight:
866+
# Wait for the completion of o_proj weight all-gather operation
867+
if o_proj_full_handle is not None:
868+
o_proj_full_handle.wait()
869+
if o_proj_full_weight_scale_handle is not None:
870+
o_proj_full_weight_scale_handle.wait()
871+
872+
# Switch o_proj to Full-mode (gathered weight from all TP ranks)
873+
self.o_proj.weight.set_(AscendSFAImpl.o_proj_full_pool)
874+
self.o_proj.weight_scale.set_(AscendSFAImpl.o_proj_full_weight_scale_pool)
875+
876+
# Apply quantization method and execute forward computation
877+
output[...] = self.o_proj.quant_method.quant_method.apply(self.o_proj, attn_output)
878+
879+
# Switch o_proj back to TP-mode for subsequent decode operations
880+
self.o_proj.weight.set_(self.o_proj_tp_weight)
881+
self.o_proj.weight_scale.set_(self.o_proj_tp_weight_scale)
882+
883+
return output, False
884+
else:
885+
# For decode scenario: perform all-to-all communication on o_proj input activations
886+
# Reshape for all-to-all: [batch * seq, tp_size, head_dim] -> [tp_size, batch * seq, head_dim]
887+
send = (
888+
attn_output.view(-1, self.tp_size, self.num_heads * self.v_head_dim)
889+
.permute(1, 0, 2)
890+
.reshape(-1, self.num_heads * self.v_head_dim)
891+
)
892+
893+
attn_output = torch.empty_like(send)
894+
torch.distributed.all_to_all_single(attn_output, send, group=get_tp_group().device_group)
895+
896+
return attn_output, True
897+
811898
def _handle_o_proj_weight_switch_and_forward(
812899
self,
813900
attn_output: torch.Tensor,
@@ -1146,6 +1233,7 @@ def forward(
11461233

11471234
# all-gather o_proj weight for prefill stage of PD mix node
11481235
o_proj_full_handle = None
1236+
o_proj_full_weight_scale_handle = None
11491237
# if is PD mix stage, using original TP o_proj weight, and also need to full gather for o_proj
11501238
# weight for prefill stage.
11511239
full_gather_o_proj_enabled = self.enable_dsa_cp_with_o_proj_tp and attn_metadata.attn_state not in {
@@ -1258,6 +1346,12 @@ def forward(
12581346
_, o_proj_full_handle = all_gather_async(
12591347
self.o_proj_tp_weight, get_tp_group(), output=AscendSFAImpl.o_proj_full_pool
12601348
)
1349+
if self._o_proj_dynamic_quant:
1350+
_, o_proj_full_weight_scale_handle = all_gather_async(
1351+
self.o_proj_tp_weight_scale,
1352+
get_tp_group(),
1353+
output=AscendSFAImpl.o_proj_full_weight_scale_pool,
1354+
)
12611355

12621356
if kv_cache is not None:
12631357
assert fused_kv_no_split is not None
@@ -1362,12 +1456,21 @@ def forward(
13621456
# When using SFA-CP with pd mixed, o_proj has two cases:
13631457
# 1. prefill: o_proj is a TP weight, we need to all-gather o_proj weight to switch TP=1.
13641458
# 2. decode: all-to-all the hidden_state before the o_proj forward.
1365-
result, require_o_proj_forward = self._handle_o_proj_weight_switch_and_forward(
1366-
attn_output=attn_output,
1367-
output=output,
1368-
o_proj_full_handle=o_proj_full_handle,
1369-
should_shard_weight=full_gather_o_proj_enabled,
1370-
)
1459+
if self._o_proj_dynamic_quant:
1460+
result, require_o_proj_forward = self._handle_dynamic_quant_o_proj_weight_switch_and_forward(
1461+
attn_output=attn_output,
1462+
output=output,
1463+
o_proj_full_handle=o_proj_full_handle,
1464+
o_proj_full_weight_scale_handle=o_proj_full_weight_scale_handle,
1465+
should_shard_weight=full_gather_o_proj_enabled,
1466+
)
1467+
else:
1468+
result, require_o_proj_forward = self._handle_o_proj_weight_switch_and_forward(
1469+
attn_output=attn_output,
1470+
output=output,
1471+
o_proj_full_handle=o_proj_full_handle,
1472+
should_shard_weight=full_gather_o_proj_enabled,
1473+
)
13711474
if not require_o_proj_forward:
13721475
return result
13731476
attn_output = result

0 commit comments

Comments
 (0)