@@ -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