7171# token count limits within bmm_transpose operator
7272BMM_TRANS_MAX_SUPPORTED_TOKENS = 1024
7373
74+ O_PROJ_ACLNN_INPUT_PARAMS = (
75+ "aclnn_input_scale" ,
76+ "aclnn_input_scale_reciprocal" ,
77+ "aclnn_input_offset" ,
78+ )
79+ O_PROJ_INPUT_SHARDED_QUANT_PARAMS = ("weight_scale_second" , "weight_scale" )
80+
7481
7582class AscendSFABackend (AttentionBackend ):
7683 accept_output_buffer : bool = True
@@ -432,8 +439,7 @@ class AscendSFAImpl(MLAAttentionImpl):
432439 """
433440
434441 # Supports forward using the all-gather o_proj weight for decode requests when Sharded CP is enabled.
435- o_proj_full_pool : torch .Tensor | None = None
436- o_proj_full_weight_scale_pool : torch .Tensor | None = None
442+ o_proj_full_pools : dict [tuple [str , int | None , torch .dtype , int , tuple [int , ...]], torch .Tensor ] = {}
437443
438444 # q_hadamard and k_hadamard tensor shared when dsa c8 enabled
439445 q_hadamard : torch .Tensor | None = None
@@ -537,7 +543,6 @@ def __init__(
537543 # use original TP o_proj weight in PD mix stage, and full gather
538544 # for o_proj weight for prefill stage.
539545 self .enable_dsa_cp_with_o_proj_tp = enable_dsa_cp_with_o_proj_tp ()
540- self ._o_proj_dynamic_quant = False
541546
542547 if self .enable_dsa_cp :
543548 self .local_num_heads = self .num_heads * self .tp_size
@@ -612,7 +617,7 @@ def process_weights_after_loading(self, act_dtype: torch.dtype):
612617 if is_hidden_layer (layer ):
613618 post_process_after_loading_for_shard_weight_series (layer )
614619 else :
615- self ._maybe_init_o_proj_tp_full_params ()
620+ self ._init_o_proj_tp_full_params ()
616621
617622 if self .enable_mlapo :
618623 quant_method = getattr (
@@ -813,46 +818,6 @@ def rope_single(
813818 x = torch_npu .npu_interleave_rope (x , cos , sin )
814819 return x .view (B , N , D )
815820
816- def _check_o_proj_dynamic_quant (self ) -> bool :
817- return hasattr (self .o_proj , "weight_scale" )
818-
819- def _maybe_init_o_proj_tp_full_params (self ):
820- if self ._check_o_proj_dynamic_quant ():
821- self ._o_proj_dynamic_quant = True
822- self ._init_dynamic_quant_o_proj_tp_full_params ()
823- else :
824- self ._init_o_proj_tp_full_params ()
825-
826- def _init_dynamic_quant_o_proj_tp_full_params (self ):
827- """
828- Initialize TP-mode and Full-mode parameters for o_proj weight,
829- preparing for weight switching in PD mix stage.
830-
831- For PD mix stage:
832- - Use original TP o_proj weight for decode phase
833- - Need full-gather o_proj weight from all TP ranks for prefill phase
834- """
835- if AscendSFAImpl .o_proj_full_pool is None :
836- sample = self .o_proj .weight
837- AscendSFAImpl .o_proj_full_pool = torch .empty (
838- (sample .shape [0 ] * self .tp_size , sample .shape [1 ]), dtype = sample .dtype , device = sample .device
839- )
840- if AscendSFAImpl .o_proj_full_weight_scale_pool is None :
841- sample = self .o_proj .weight_scale
842- AscendSFAImpl .o_proj_full_weight_scale_pool = torch .empty (
843- (sample .shape [0 ] * self .tp_size , sample .shape [1 ], sample .shape [2 ]),
844- dtype = sample .dtype ,
845- device = sample .device ,
846- )
847-
848- # Save TP-mode parameters (original sharded weights)
849- self .o_proj_tp_weight = self .o_proj .weight .clone ().detach ()
850- self .o_proj_tp_weight_scale = self .o_proj .weight_scale .clone ().detach ()
851-
852- # Initially switch to TP mode for graph capture
853- self .o_proj .weight .set_ (self .o_proj_tp_weight )
854- self .o_proj .weight_scale .set_ (self .o_proj_tp_weight_scale )
855-
856821 def _init_o_proj_tp_full_params (self ):
857822 """
858823 Initialize TP-mode and Full-mode parameters for o_proj weight,
@@ -862,79 +827,84 @@ def _init_o_proj_tp_full_params(self):
862827 - Use original TP o_proj weight for decode phase
863828 - Need full-gather o_proj weight from all TP ranks for prefill phase
864829 """
865- if AscendSFAImpl .o_proj_full_pool is None :
866- sample = self .o_proj .weight
867- AscendSFAImpl .o_proj_full_pool = torch .empty (
868- (sample .shape [0 ] * self .tp_size , sample .shape [1 ]), dtype = sample .dtype , device = sample .device
830+ sample = self .o_proj .weight
831+ self .o_proj_full_weight_gather_dim = 1 if self ._is_o_proj_unquantized () else 0
832+ if self .o_proj_full_weight_gather_dim == 0 :
833+ full_shape = (sample .shape [0 ] * self .tp_size , sample .shape [1 ])
834+ gather_shape = full_shape
835+ else :
836+ full_shape = (sample .shape [0 ], sample .shape [1 ] * self .tp_size )
837+ gather_shape = (sample .shape [1 ] * self .tp_size , sample .shape [0 ])
838+ # Main and MTP layers can use different quantized o_proj weight layouts,
839+ # so key the shared full-gather pool by gather dimension, dtype, and shape.
840+ pool_key = (
841+ sample .device .type ,
842+ sample .device .index ,
843+ sample .dtype ,
844+ self .o_proj_full_weight_gather_dim ,
845+ full_shape ,
846+ )
847+ if pool_key not in AscendSFAImpl .o_proj_full_pools :
848+ AscendSFAImpl .o_proj_full_pools [pool_key ] = torch .empty (
849+ gather_shape , dtype = sample .dtype , device = sample .device
869850 )
851+ self .o_proj_full_gather_pool = AscendSFAImpl .o_proj_full_pools [pool_key ]
852+ if self .o_proj_full_weight_gather_dim == 0 :
853+ self .o_proj_full_pool = self .o_proj_full_gather_pool
854+ else :
855+ self .o_proj_full_pool = self .o_proj_full_gather_pool .transpose (0 , 1 )
870856
871857 # Save TP-mode parameters (original sharded weights)
872858 self .o_proj_tp_weight = self .o_proj .weight .clone ().detach ()
873- self .o_proj_tp_aclnn_input_scale = self .o_proj .aclnn_input_scale .clone ().detach ()
874- self .o_proj_tp_aclnn_input_scale_reciprocal = self .o_proj .aclnn_input_scale_reciprocal .clone ().detach ()
875- self .o_proj_tp_aclnn_input_offset = self .o_proj .aclnn_input_offset .clone ().detach ()
859+ if self .o_proj_full_weight_gather_dim == 0 :
860+ self .o_proj_tp_weight_gather_input = self .o_proj_tp_weight
861+ else :
862+ self .o_proj_tp_weight_gather_input = self .o_proj_tp_weight .transpose (0 , 1 ).contiguous ()
863+ self .o_proj_tp_aclnn_input_params = {}
864+ self .o_proj_full_aclnn_input_params = {}
865+ for param_name in O_PROJ_ACLNN_INPUT_PARAMS :
866+ param = getattr (self .o_proj , param_name , None )
867+ if param is None :
868+ continue
869+ self .o_proj_tp_aclnn_input_params [param_name ] = param .clone ().detach ()
870+ self .o_proj_full_aclnn_input_params [param_name ] = param .repeat (self .tp_size )
871+
872+ self .o_proj_tp_input_sharded_quant_params = {}
873+ self .o_proj_full_input_sharded_quant_params = {}
874+ for param_name in O_PROJ_INPUT_SHARDED_QUANT_PARAMS :
875+ param = getattr (self .o_proj , param_name , None )
876+ if param is None or getattr (param , "input_dim" , None ) != 1 :
877+ continue
878+ self .o_proj_tp_input_sharded_quant_params [param_name ] = param .clone ().detach ()
879+ self .o_proj_full_input_sharded_quant_params [param_name ] = torch .empty (
880+ (param .shape [0 ] * self .tp_size , * param .shape [1 :]), dtype = param .dtype , device = param .device
881+ )
876882
877883 # Initially switch to TP mode for graph capture
878884 self .o_proj .weight .set_ (self .o_proj_tp_weight )
879- self .o_proj .aclnn_input_scale .set_ (self .o_proj_tp_aclnn_input_scale )
880- self .o_proj .aclnn_input_scale_reciprocal .set_ (self .o_proj_tp_aclnn_input_scale_reciprocal )
881- self .o_proj .aclnn_input_offset .set_ (self .o_proj_tp_aclnn_input_offset )
882-
883- # Precompute Full-mode quantization parameters by repeating TP parameters across all TP ranks
884- self .o_proj_full_aclnn_input_scale = self .o_proj .aclnn_input_scale .repeat (self .tp_size )
885- self .o_proj_full_aclnn_input_scale_reciprocal = self .o_proj .aclnn_input_scale_reciprocal .repeat (self .tp_size )
886- self .o_proj_full_aclnn_input_offset = self .o_proj .aclnn_input_offset .repeat (self .tp_size )
887-
888- def _handle_dynamic_quant_o_proj_weight_switch_and_forward (
889- self ,
890- attn_output : torch .Tensor ,
891- output : torch .Tensor ,
892- o_proj_full_handle : torch .distributed .Work | None ,
893- o_proj_full_weight_scale_handle : torch .distributed .Work | None ,
894- should_shard_weight : bool ,
895- ) -> tuple [torch .Tensor , bool ]:
896- """
897- Handle o_proj weight switching between TP-mode and Full-mode, and execute forward computation.
898- """
899- # Gather o_proj weight from all TP ranks for Full-mode computation
900- if should_shard_weight :
901- # Wait for the completion of o_proj weight all-gather operation
902- if o_proj_full_handle is not None :
903- o_proj_full_handle .wait ()
904- if o_proj_full_weight_scale_handle is not None :
905- o_proj_full_weight_scale_handle .wait ()
885+ self ._switch_o_proj_params (self .o_proj_tp_aclnn_input_params )
886+ self ._switch_o_proj_params (self .o_proj_tp_input_sharded_quant_params )
906887
907- # Switch o_proj to Full-mode (gathered weight from all TP ranks)
908- self . o_proj . weight . set_ ( AscendSFAImpl . o_proj_full_pool )
909- self .o_proj . weight_scale . set_ (AscendSFAImpl . o_proj_full_weight_scale_pool )
888+ def _switch_o_proj_params ( self , params : dict [ str , torch . Tensor ]):
889+ for param_name , param in params . items ():
890+ getattr ( self .o_proj , param_name ). set_ (param )
910891
911- # Apply quantization method and execute forward computation
912- output [...] = self .o_proj .quant_method .quant_method .apply (self .o_proj , attn_output )
892+ def _get_o_proj_linear_method (self ):
893+ quant_method = self .o_proj .quant_method
894+ return getattr (quant_method , "quant_method" , quant_method )
913895
914- # Switch o_proj back to TP-mode for subsequent decode operations
915- self .o_proj .weight .set_ (self .o_proj_tp_weight )
916- self .o_proj .weight_scale .set_ (self .o_proj_tp_weight_scale )
896+ def _is_o_proj_unquantized (self ) -> bool :
897+ return isinstance (self ._get_o_proj_linear_method (), UnquantizedLinearMethod )
917898
918- return output , False
919- else :
920- # For decode scenario: perform all-to-all communication on o_proj input activations
921- # Reshape for all-to-all: [batch * seq, tp_size, head_dim] -> [tp_size, batch * seq, head_dim]
922- send = (
923- attn_output .view (- 1 , self .tp_size , self .num_heads * self .v_head_dim )
924- .permute (1 , 0 , 2 )
925- .reshape (- 1 , self .num_heads * self .v_head_dim )
926- )
927-
928- attn_output = torch .empty_like (send )
929- torch .distributed .all_to_all_single (attn_output , send , group = get_tp_group ().device_group )
930-
931- return attn_output , True
899+ def _apply_o_proj_full_weight (self , attn_output : torch .Tensor ) -> torch .Tensor :
900+ return self ._get_o_proj_linear_method ().apply (self .o_proj , attn_output )
932901
933902 def _handle_o_proj_weight_switch_and_forward (
934903 self ,
935904 attn_output : torch .Tensor ,
936905 output : torch .Tensor ,
937906 o_proj_full_handle : torch .distributed .Work | None ,
907+ o_proj_full_param_handles : list [torch .distributed .Work | None ] | None ,
938908 should_shard_weight : bool ,
939909 ) -> tuple [torch .Tensor , bool ]:
940910 """
@@ -945,21 +915,22 @@ def _handle_o_proj_weight_switch_and_forward(
945915 # Wait for the completion of o_proj weight all-gather operation
946916 if o_proj_full_handle is not None :
947917 o_proj_full_handle .wait ()
918+ for handle in o_proj_full_param_handles or []:
919+ if handle is not None :
920+ handle .wait ()
948921
949922 # Switch o_proj to Full-mode (gathered weight from all TP ranks)
950- self .o_proj .weight .set_ (AscendSFAImpl .o_proj_full_pool )
951- self .o_proj .aclnn_input_scale .set_ (self .o_proj_full_aclnn_input_scale )
952- self .o_proj .aclnn_input_scale_reciprocal .set_ (self .o_proj_full_aclnn_input_scale_reciprocal )
953- self .o_proj .aclnn_input_offset .set_ (self .o_proj_full_aclnn_input_offset )
923+ self .o_proj .weight .set_ (self .o_proj_full_pool )
924+ self ._switch_o_proj_params (self .o_proj_full_aclnn_input_params )
925+ self ._switch_o_proj_params (self .o_proj_full_input_sharded_quant_params )
954926
955927 # Apply quantization method and execute forward computation
956- output [...] = self .o_proj . quant_method . quant_method . apply ( self . o_proj , attn_output )
928+ output [...] = self ._apply_o_proj_full_weight ( attn_output )
957929
958930 # Switch o_proj back to TP-mode for subsequent decode operations
959931 self .o_proj .weight .set_ (self .o_proj_tp_weight )
960- self .o_proj .aclnn_input_scale .set_ (self .o_proj_tp_aclnn_input_scale )
961- self .o_proj .aclnn_input_scale_reciprocal .set_ (self .o_proj_tp_aclnn_input_scale_reciprocal )
962- self .o_proj .aclnn_input_offset .set_ (self .o_proj_tp_aclnn_input_offset )
932+ self ._switch_o_proj_params (self .o_proj_tp_aclnn_input_params )
933+ self ._switch_o_proj_params (self .o_proj_tp_input_sharded_quant_params )
963934
964935 return output , False
965936 else :
@@ -1284,7 +1255,7 @@ def forward(
12841255
12851256 # all-gather o_proj weight for prefill stage of PD mix node
12861257 o_proj_full_handle = None
1287- o_proj_full_weight_scale_handle = None
1258+ o_proj_full_param_handles = None
12881259 # if is PD mix stage, using original TP o_proj weight, and also need to full gather for o_proj
12891260 # weight for prefill stage.
12901261 full_gather_o_proj_enabled = self .enable_dsa_cp_with_o_proj_tp and attn_metadata .attn_state not in {
@@ -1423,14 +1394,18 @@ def forward(
14231394 reach_layer_for_shard_weight_series (layer )
14241395 elif full_gather_o_proj_enabled :
14251396 _ , o_proj_full_handle = all_gather_async (
1426- self .o_proj_tp_weight , get_tp_group (), output = AscendSFAImpl .o_proj_full_pool
1397+ self .o_proj_tp_weight_gather_input ,
1398+ get_tp_group (),
1399+ output = self .o_proj_full_gather_pool ,
14271400 )
1428- if self ._o_proj_dynamic_quant :
1429- _ , o_proj_full_weight_scale_handle = all_gather_async (
1430- self .o_proj_tp_weight_scale ,
1401+ o_proj_full_param_handles = []
1402+ for param_name , param in self .o_proj_tp_input_sharded_quant_params .items ():
1403+ _ , param_handle = all_gather_async (
1404+ param ,
14311405 get_tp_group (),
1432- output = AscendSFAImpl . o_proj_full_weight_scale_pool ,
1406+ output = self . o_proj_full_input_sharded_quant_params [ param_name ] ,
14331407 )
1408+ o_proj_full_param_handles .append (param_handle )
14341409
14351410 if kv_cache is not None :
14361411 assert fused_kv_no_split is not None
@@ -1544,21 +1519,13 @@ def forward(
15441519 # When using SFA-CP with pd mixed, o_proj has two cases:
15451520 # 1. prefill: o_proj is a TP weight, we need to all-gather o_proj weight to switch TP=1.
15461521 # 2. decode: all-to-all the hidden_state before the o_proj forward.
1547- if self ._o_proj_dynamic_quant :
1548- result , require_o_proj_forward = self ._handle_dynamic_quant_o_proj_weight_switch_and_forward (
1549- attn_output = attn_output ,
1550- output = output ,
1551- o_proj_full_handle = o_proj_full_handle ,
1552- o_proj_full_weight_scale_handle = o_proj_full_weight_scale_handle ,
1553- should_shard_weight = full_gather_o_proj_enabled ,
1554- )
1555- else :
1556- result , require_o_proj_forward = self ._handle_o_proj_weight_switch_and_forward (
1557- attn_output = attn_output ,
1558- output = output ,
1559- o_proj_full_handle = o_proj_full_handle ,
1560- should_shard_weight = full_gather_o_proj_enabled ,
1561- )
1522+ result , require_o_proj_forward = self ._handle_o_proj_weight_switch_and_forward (
1523+ attn_output = attn_output ,
1524+ output = output ,
1525+ o_proj_full_handle = o_proj_full_handle ,
1526+ o_proj_full_param_handles = o_proj_full_param_handles ,
1527+ should_shard_weight = full_gather_o_proj_enabled ,
1528+ )
15621529 if not require_o_proj_forward :
15631530 return result
15641531 attn_output = result
0 commit comments