5353
5454logger = logging .getLogger (__name__ )
5555
56- # Ascend npu_fused_infer_attention_score with input_layout="TND" only supports
57- # head_dim in {64, 128, 192} (plus the special qD=kD=192, vD=128 case).
58- _PFA_TND_SUPPORTED_HEAD_DIMS = frozenset ({64 , 128 , 192 })
59-
6056# Check torch_npu availability and setup NPU compatibility
6157_TORCH_NPU_AVAILABLE = False
6258try :
@@ -324,7 +320,9 @@ def build(
324320
325321 # Determine attention state
326322 attn_state = self ._determine_attn_state (
327- num_decodes , num_prefills , num_decode_tokens , num_prefill_tokens
323+ num_decodes , num_prefills , num_decode_tokens , num_prefill_tokens ,
324+ num_computed_tokens_cpu = getattr (
325+ common_attn_metadata , 'num_computed_tokens_cpu' , None ),
328326 )
329327
330328 # Create attention mask based on state
@@ -368,13 +366,21 @@ def _determine_attn_state(
368366 num_prefills : int ,
369367 num_decode_tokens : int ,
370368 num_prefill_tokens : int ,
369+ num_computed_tokens_cpu : Optional [torch .Tensor ] = None ,
371370 ) -> AscendAttentionState :
372371 """Determine attention state based on batch composition."""
373372 if num_prefills == 0 :
374373 return AscendAttentionState .DecodeOnly
374+ # PrefillNoCache is only valid when every request starts from token 0.
375+ # A pure-prefill batch may still contain prefix-cache hits or chunked
376+ # prefill continuations; those must attend to the paged KV cache via
377+ # the ChunkedPrefill path (same handling as vllm-ascend).
378+ if (num_computed_tokens_cpu is not None
379+ and len (num_computed_tokens_cpu ) > 0
380+ and int (num_computed_tokens_cpu .max ()) > 0 ):
381+ return AscendAttentionState .ChunkedPrefill
375382 elif num_decodes == 0 and num_prefill_tokens > 0 :
376- # Pure prefill - check if cache hit or no cache
377- # For simplicity, use ChunkedPrefill as default
383+ # Fresh prefill with no cached context
378384 return AscendAttentionState .PrefillNoCache
379385 else :
380386 # Mixed decode and prefill
@@ -563,13 +569,6 @@ def __init__(
563569 self .num_queries_per_kv = self .num_heads // self .num_kv_heads
564570 self .key_cache = None
565571 self .value_cache = None
566- self ._use_fusion_fallback = self .head_size not in _PFA_TND_SUPPORTED_HEAD_DIMS
567- if self ._use_fusion_fallback :
568- logger .info (
569- "AscendAttentionBackendImpl: head_size=%d is not supported by "
570- "npu_fused_infer_attention_score TND layout, falling back to "
571- "head-split attention (2x%d)." , self .head_size ,
572- self .head_size // 2 )
573572
574573 @classmethod
575574 def update_graph_params (
@@ -707,130 +706,6 @@ def reshape_and_cache(
707706 )
708707 return key , value
709708
710- def _gather_kv_cache (
711- self ,
712- attn_metadata : AscendMetadata ,
713- ) -> Tuple [torch .Tensor , torch .Tensor ]:
714- """Gather paged KV cache into dense (T_kv, num_kv_heads, head_size).
715-
716- Only the blocks actually referenced by each request's sequence length
717- are gathered. The block table is padded to ``max_num_blocks_per_req``,
718- so gathering the whole table would allocate ``batch_size`` times more
719- memory than necessary and can OOM on long-context configs.
720- """
721- if self .key_cache is None or self .value_cache is None :
722- raise RuntimeError (
723- "key_cache/value_cache is not initialized for KV gather" )
724-
725- seq_lens = attn_metadata .seq_lens_list
726- batch_size = len (seq_lens )
727- block_size = self .key_cache .shape [1 ]
728- block_table = attn_metadata .block_tables [:batch_size ]
729-
730- k_list : List [torch .Tensor ] = []
731- v_list : List [torch .Tensor ] = []
732- for i , sl in enumerate (seq_lens ):
733- num_blocks = (sl + block_size - 1 ) // block_size
734- block_ids = block_table [i , :num_blocks ].long ()
735- gathered_k = self .key_cache [block_ids ].view (
736- - 1 , self .num_kv_heads , self .head_size )[:sl ]
737- gathered_v = self .value_cache [block_ids ].view (
738- - 1 , self .num_kv_heads , self .head_size )[:sl ]
739- k_list .append (gathered_k )
740- v_list .append (gathered_v )
741-
742- return torch .cat (k_list , dim = 0 ), torch .cat (v_list , dim = 0 )
743-
744- def _split_heads_for_pfa (
745- self ,
746- x : torch .Tensor ,
747- num_heads : int ,
748- ) -> torch .Tensor :
749- """Split head dimension to a supported PFA TND head size.
750-
751- Ascend's npu_fused_infer_attention_score TND layout only supports
752- head_dim in {64, 128, 192}. For unsupported head sizes such as 256,
753- we split each head into two heads of half the dimension and double the
754- head count, which is mathematically equivalent for self-attention.
755-
756- Accepts either 2D input [T, num_heads * head_size] or 3D input
757- [T, num_heads, head_size]. Output shape: [T, num_heads * 2,
758- head_size // 2].
759- """
760- head_size_128 = self .head_size // 2
761- if x .dim () == 2 :
762- x = x .view (- 1 , num_heads , self .head_size )
763- return x .view (- 1 , num_heads , 2 , head_size_128 ).reshape (
764- - 1 , num_heads * 2 , head_size_128 )
765-
766- def _merge_heads_from_pfa (
767- self ,
768- x : torch .Tensor ,
769- ) -> torch .Tensor :
770- """Merge split heads back to original head dimension.
771-
772- Input shape: [T, num_heads * 2, head_size // 2]
773- Output shape: [T, num_heads * head_size]
774- """
775- head_size_128 = self .head_size // 2
776- return x .view (- 1 , self .num_heads , 2 , head_size_128 ).reshape (
777- - 1 , self .num_heads * self .head_size )
778-
779- def _forward_fusion_attention (
780- self ,
781- query : torch .Tensor ,
782- key : torch .Tensor ,
783- value : torch .Tensor ,
784- attn_metadata : AscendMetadata ,
785- output : torch .Tensor ,
786- ) -> torch .Tensor :
787- """Forward pass for unsupported head sizes using head-split PFA.
788-
789- Splits head_dim into two supported heads and runs
790- npu_fused_infer_attention_score (PFA) with the standard TND path.
791- This keeps the implementation compatible with ACL graph capture while
792- avoiding the per-batch dense causal mask limitation of
793- npu_fusion_attention.
794- """
795- num_tokens = attn_metadata .actual_seq_lengths_q [- 1 ]
796- query = query [:num_tokens ]
797-
798- if attn_metadata .attn_state == AscendAttentionState .PrefillNoCache :
799- key = key [:num_tokens ]
800- value = value [:num_tokens ]
801- actual_seq_kvlen = attn_metadata .actual_seq_lengths_q
802- else :
803- key , value = self ._gather_kv_cache (attn_metadata )
804- actual_seq_kvlen = [
805- sum (attn_metadata .seq_lens_list [:i + 1 ])
806- for i in range (len (attn_metadata .seq_lens_list ))
807- ]
808-
809- query = self ._split_heads_for_pfa (query , self .num_heads )
810- key = self ._split_heads_for_pfa (key , self .num_kv_heads )
811- value = self ._split_heads_for_pfa (value , self .num_kv_heads )
812-
813- attn_output , _ = torch_npu .npu_fused_infer_attention_score (
814- query = query ,
815- key = key ,
816- value = value ,
817- atten_mask = attn_metadata .attn_mask ,
818- block_table = None ,
819- input_layout = "TND" ,
820- block_size = AscendAttentionBackend .get_supported_block_size ()[0 ],
821- actual_seq_lengths = attn_metadata .actual_seq_lengths_q ,
822- actual_seq_lengths_kv = actual_seq_kvlen ,
823- num_key_value_heads = self .num_kv_heads * 2 ,
824- num_heads = self .num_heads * 2 ,
825- scale = self .scale ,
826- sparse_mode = 3 ,
827- )
828-
829- attn_output = self ._merge_heads_from_pfa (attn_output )
830- attn_output = attn_output .view (num_tokens , self .num_heads , self .head_size )
831- output [:num_tokens ] = attn_output [:num_tokens ]
832- return output
833-
834709 def forward_fused_infer_attention (
835710 self ,
836711 query : torch .Tensor ,
@@ -840,11 +715,6 @@ def forward_fused_infer_attention(
840715 output : torch .Tensor ,
841716 ) -> torch .Tensor :
842717 """Forward pass using fused_infer_attention_score."""
843- if (self ._use_fusion_fallback
844- and attn_metadata .attn_state != AscendAttentionState .DecodeOnly ):
845- return self ._forward_fusion_attention (
846- query , key , value , attn_metadata , output )
847-
848718 key , value , block_size , block_table , actual_seq_lengths_kv = \
849719 self ._get_fia_params (key , value , attn_metadata )
850720
0 commit comments