import math from dataclasses import dataclass from typing import TYPE_CHECKING, ClassVar, TypeAlias import torch import torch.distributed as dist import torch.nn.functional as F import torch_npu import vllm.envs as envs_vllm from vllm.config import VllmConfig, get_current_vllm_config from vllm.distributed import get_tensor_model_parallel_world_size from vllm.forward_context import get_forward_context from vllm.triton_utils import HAS_TRITON from vllm.v1.attention.backend import AttentionBackend, AttentionCGSupport, AttentionMetadataBuilder from vllm.v1.kv_cache_interface import AttentionSpec from vllm_ascend.ascend_config import get_ascend_config from vllm_ascend.attention.abstract import DSAAttentionImpl from vllm_ascend.attention.attention_mask import AttentionMaskBuilder from vllm_ascend.attention.attention_v1 import AscendAttentionState from vllm_ascend.attention.utils import ( AscendCommonAttentionMetadata, maybe_save_kv_layer_to_connector, notify_kv_cache_written, split_decodes_and_prefills, wait_for_kv_layer_from_connector, ) from vllm_ascend.core.kv_cache_interface import AscendMLAAttentionSpec from vllm_ascend.device.device_op import DeviceOperator from vllm_ascend.distributed.parallel_state import get_otp_group from vllm_ascend.memcache_comm_fence import record_attention_compute_start from vllm_ascend.ops.cv_linear import CVLinearWrapper from vllm_ascend.ops.linear import AscendUnquantizedLinearMethod from vllm_ascend.ops.rope_dsv4 import get_cos_and_sin_dsa, get_full_cos_and_sin_dsa from vllm_ascend.quantization.methods.w8a8_dynamic import AscendW8A8DynamicLinearMethod from vllm_ascend.utils import ( AscendDeviceType, get_ascend_device_type, get_potential_max_tokens, npu_stream_switch, olora_tp_enable, oproj_tp_enable, ) from vllm_ascend.worker.npu_input_batch import NPUInputBatch if TYPE_CHECKING: from vllm.v1.core.sched.output import SchedulerOutput from vllm_ascend.ops.triton.rms_norm import triton_q_rms if HAS_TRITON: from vllm_ascend.ops.triton.rms_norm import triton_q_rms # noqa: F811 else: triton_q_rms = None # type: ignore BUILD_METADATA_STEP_PREFILL = 0 BUILD_METADATA_STEP_DECODE = 1 _DSV4_DSA_OVERLAP_STREAM = None def dsv4_dsa_overlap_stream() -> torch.npu.Stream: global _DSV4_DSA_OVERLAP_STREAM if _DSV4_DSA_OVERLAP_STREAM is None: _DSV4_DSA_OVERLAP_STREAM = torch_npu.npu.Stream() return _DSV4_DSA_OVERLAP_STREAM # mypy: disable-error-code="has-type" def hadamard_transform_ref( x: torch.Tensor, hadamard: torch.Tensor, scale: float = 1.0, ): x_shape = x.shape dim = x.shape[-1] x = x.reshape(-1, dim) log_dim = math.ceil(math.log2(dim)) dim_padded = 2**log_dim if dim != dim_padded: x = F.pad(x, (0, dim_padded - dim)) out = F.linear(x, hadamard) out = out * scale return out[..., :dim].reshape(*x_shape) def rotate_activation(x: torch.Tensor, hadamard: torch.Tensor) -> torch.Tensor: hidden_size = x.size(-1) return hadamard_transform_ref(x, hadamard=hadamard, scale=hidden_size**-0.5) def hadamard_linear(x: torch.Tensor, hadamard: torch.Tensor) -> tuple[torch.Tensor, tuple[int, ...], int]: """ Part 1 of rotate_activation: Execute F.linear (matrix multiplication). This runs in main stream, parallel with aux_stream kv_scatter. Returns: Tuple of (linear_output, original_shape, original_dim) """ x_shape = x.shape dim = x.shape[-1] x = x.reshape(-1, dim) log_dim = math.ceil(math.log2(dim)) dim_padded = 2**log_dim if dim != dim_padded: x = F.pad(x, (0, dim_padded - dim)) out = F.linear(x, hadamard) return out, x_shape, dim def hadamard_scale(out: torch.Tensor, x_shape: tuple[int, ...], dim: int, scale: float = 1.0) -> torch.Tensor: """ Part 2 of rotate_activation: Execute scale multiplication and reshape. This runs in main stream after aux_stream completes. """ out = out * scale return out[..., :dim].reshape(*x_shape) def _is_w8a8_dynamic(linear) -> bool: """True iff ``linear`` is wired up with ``AscendW8A8DynamicLinearMethod``.""" qm = getattr(linear, "quant_method", None) if qm is None or isinstance(qm, AscendUnquantizedLinearMethod): return False inner = getattr(qm, "quant_method", None) return isinstance(inner, AscendW8A8DynamicLinearMethod) def pad_to_blocks(x: torch.Tensor, length_list: torch.Tensor, block_size: int = 128): """ Pads a ragged/packed tensor into fixed-size blocks. Args: x: Input tensor of shape [t, n, d] where t = sum(length_list). length_list: Tensor of shape [bs] containing valid sequence lengths. block_size: The size of each block (default 128). Returns: padded_blocks: Tensor of shape [total_blocks, block_size, n, d]. """ # 1. Validation if x.shape[0] != length_list.sum(): raise ValueError(f"Input dimension 0 ({x.shape[0]}) does not match sum of length_list ({length_list.sum()})") bs = length_list.shape[0] n, d = x.shape[1], x.shape[2] # 2. Calculate how many blocks are needed for each request # Formula: ceil(length / block_size) -> (length + block_size - 1) // block_size blocks_per_req = (length_list + block_size - 1) // block_size total_blocks = blocks_per_req.sum() + 1 # 3. Allocate output tensor with zeros (this handles the padding automatically) # Shape: [total_blocks, block_size, n, d] out = torch.zeros((total_blocks, block_size, n, d), dtype=x.dtype, device=x.device) # 4. Fill data input_offset = 0 block_offset = 1 for i in range(bs): length = length_list[i] num_blocks = blocks_per_req[i] if length > 0: # Slice the valid data for this request from the packed input # Shape: [length, n, d] req_data = x[input_offset : input_offset + length] # Select the assigned blocks in the output # Shape: [num_blocks, block_size, n, d] target_blocks = out[block_offset : block_offset + num_blocks] # View as a flat sequence to easily copy the data # Shape: [num_blocks * block_size, n, d] target_flat = target_blocks.view(-1, n, d) # Copy valid data into the beginning of the allocated blocks # The rest remains zeros target_flat[:length] = req_data # Update pointers input_offset += length block_offset += num_blocks return out class AscendDSABackend(AttentionBackend): accept_output_buffer: bool = True @staticmethod def get_name() -> str: # HACK(Ronald1995): vllm `initialize_kv_cache` method in model runner v2 make # attention name assertion, we just set name to FLASH_ATTN to avoid assertion error. # rectify this when vllm disable the assertion. return "ASCEND_DSA" if not envs_vllm.VLLM_USE_V2_MODEL_RUNNER else "FLASH_ATTN" @staticmethod def get_builder_cls(): from vllm_ascend.utils import enable_dsa_cp if enable_dsa_cp(): from vllm_ascend.attention.context_parallel.dsa_cp import AscendDSACPMetadataBuilder return AscendDSACPMetadataBuilder return AscendDSAMetadataBuilder @staticmethod def get_kv_cache_shape(num_blocks: int, block_size: int, num_kv_heads: int, head_size: int) -> tuple[int, ...]: return num_blocks, block_size, num_kv_heads, head_size @staticmethod def get_scale_shape(num_blocks: int, block_size: int, scale_size: int) -> tuple[int, ...]: return num_blocks, block_size, scale_size @staticmethod def get_impl_cls() -> type["DSAAttentionImpl"]: from vllm_ascend.utils import enable_dsa_cp if enable_dsa_cp(): from vllm_ascend.attention.context_parallel.dsa_cp import AscendDSACPImpl return AscendDSACPImpl return AscendDSAImpl @staticmethod def get_supported_kernel_block_sizes() -> list[int]: return [2, 4, 8, 16, 32, 64, 128] @dataclass class AscendDSAPrefillMetadata: """Prefill Specific Metadata for Ascend""" attn_mask: torch.Tensor query_lens: torch.Tensor seq_lens: torch.Tensor context_lens: torch.Tensor input_positions: torch.Tensor query_start_loc: torch.Tensor block_table: torch.Tensor slot_mapping: torch.Tensor | None block_size: int max_query_len: int max_seq_lens: int num_compressed_tokens: int | None = None sin: torch.Tensor = None cos: torch.Tensor = None full_compress_sin: torch.Tensor = None full_compress_cos: torch.Tensor = None start_pos: torch.Tensor | None = None num_reqs_actual: int | None = None sas_metadata: torch.Tensor = None qli_metadata: torch.Tensor = None cu_c4_cmp_seqlen_list: torch.Tensor = None cu_c128_cmp_seqlen_list: torch.Tensor = None @dataclass class AscendDSADecodeMetadata: # Input positions for rotrary embeddings since for MLA the rotary # position embeddings are applied inside the attention backend input_positions: torch.Tensor block_table: torch.Tensor seq_lens: torch.Tensor max_seqlen_kv: int max_seqlen_q: int seq_lens_list: list[int] max_seq_lens: int slot_mapping: torch.Tensor | None block_size: int num_compressed_tokens: int | None = None query_start_loc: torch.tensor = None query_start_loc_cpu: torch.tensor = None attn_mask: torch.Tensor | None = None sin: torch.Tensor = None cos: torch.Tensor = None full_compress_sin: torch.Tensor = None full_compress_cos: torch.Tensor = None cp_seq_len: torch.Tensor = None batch_seq_mask: torch.Tensor = None start_pos: torch.Tensor = None num_reqs_actual: int | None = None sas_metadata: torch.Tensor = None qli_metadata: torch.Tensor = None @dataclass class AscendDSAMetadata: """Metadata for MLACommon. NOTE: Please read the comment at the top of the file before trying to understand this class """ num_actual_tokens: int # Number of tokens excluding padding. slot_mapping: torch.Tensor query_start_loc: torch.Tensor seq_lens: torch.Tensor block_tables: torch.Tensor sin: torch.Tensor cos: torch.Tensor num_decodes: int num_decode_tokens: int num_prefills: int # For logging. num_input_tokens: int = 0 # Number of tokens including padding. query_lens: list[int] | None = None # The dimension of the attention heads head_dim: int | None = None attn_mask: torch.Tensor = None # chunked prefill by default if no attn_states passed attn_state: AscendAttentionState = AscendAttentionState.ChunkedPrefill decode: AscendDSADecodeMetadata | None = None prefill: AscendDSAPrefillMetadata | None = None reshape_cache_event: torch.npu.Event = None # metadata for dsv4 indexer hadamard: torch.Tensor | None = None start_pos: torch.Tensor | None = None def __post_init__(self): pass DSAMetadataList: TypeAlias = list[AscendDSAMetadata] DSAPrepareResult: TypeAlias = tuple[ torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, AscendDSAMetadata, bool, ] def _require_prefill_metadata(metadata: AscendDSAMetadata) -> AscendDSAPrefillMetadata: assert metadata.prefill is not None return metadata.prefill def _require_decode_metadata(metadata: AscendDSAMetadata) -> AscendDSADecodeMetadata: assert metadata.decode is not None return metadata.decode class AscendDSAMetadataBuilder(AttentionMetadataBuilder[AscendDSAMetadata]): # Does this backend/builder support ACL Graphs for attention (default: no). aclgraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.UNIFORM_BATCH hadamard = None start_pos_prefill: torch.Tensor | None = None start_pos_decode: torch.Tensor | None = None decode_sas_metadata: torch.Tensor | None = None decode_qli_metadata: torch.Tensor | None = None prefill_ratio_to_sas_metadata: dict | None = None decode_ratio_to_sas_metadata: dict | None = None block_size: int = 128 """ NOTE: Please read the comment at the top of the file before trying to understand this class """ def __init__( self, kv_cache_spec: AscendMLAAttentionSpec, layer_names: list[str], vllm_config: VllmConfig, device: torch.device, metadata_cls: type[AscendDSAMetadata] | None = None, supports_dcp_with_varlen: bool = False, ): self.kv_cache_spec = kv_cache_spec self.metadata_cls = metadata_cls if metadata_cls is not None else AscendDSAMetadata self.vllm_config = vllm_config self.model_config = vllm_config.model_config self.device = device scheduler_config = vllm_config.scheduler_config # self.block_size = vllm_config.cache_config.block_size self.max_blocks = (vllm_config.model_config.max_model_len + self.block_size - 1) // self.block_size self.speculative_config = vllm_config.speculative_config self.decode_threshold = 1 self.spec_slot_mapping = None if get_ascend_device_type() in {AscendDeviceType.A5}: self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens,) # type: ignore else: self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens, 2) # type: ignore if self.speculative_config: spec_token_num = self.speculative_config.num_speculative_tokens self.spec_slot_mapping = [ torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) for _ in range(spec_token_num) ] self.spec_sas_metadata = [ torch.zeros(1024, dtype=torch.int32, device=self.device) for _ in range(spec_token_num) ] self.decode_threshold += spec_token_num assert self.decode_threshold <= 16, ( f"decode_threshold exceeded \ npu_fused_infer_attention_score TND layout's limit of 16, \ got {self.decode_threshold}" ) self.reorder_batch_threshold = self.decode_threshold self.rope_dim = self.model_config.hf_text_config.qk_rope_head_dim self.cos_cache = None self.sin_cache = None self.cu_seq_lens_cpu: torch.Tensor = None self.num_decodes = 0 self.num_prefills = 0 self.num_decode_tokens = 0 self.num_prefill_tokens = 0 self.context_lens_cpu: torch.Tensor = None self.num_actual_tokens: int | None = None self.block_table: torch.Tensor = None self.slot_mapping: torch.Tensor = None self.graph_pad_size = 0 self.query_lens: torch.Tensor = None self.seq_lens: torch.Tensor = None self.attn_mask_builder = AttentionMaskBuilder(self.device) self.compressor_ratio = getattr(kv_cache_spec, "compress_ratio", 0) hf_config = self.model_config.hf_config if AscendDSAMetadataBuilder.hadamard is None: if hf_config.model_type == "deepseek_v4": indexer_head_dim = hf_config.index_head_dim try: from scipy.linalg import hadamard # type: ignore[import-untyped] except ImportError as e: raise ImportError("Please install scipy") from e log_dim = math.ceil(math.log2(indexer_head_dim)) dim_padded = 2**log_dim if self.vllm_config.model_config.enable_sleep_mode: # Sleep mode allocates KV inside CaMemAllocator; tag Hadamard so # sleep/wake does not treat it as KV cache. from vllm_ascend.device_allocator.camem import CaMemAllocator allocator = CaMemAllocator.get_instance() with allocator.use_allocation_tag(CaMemAllocator.sleep_persistent_tag): AscendDSAMetadataBuilder.hadamard = torch.tensor( hadamard(dim_padded, dtype=float), dtype=torch.float, device=self.device ).to(torch.bfloat16) else: AscendDSAMetadataBuilder.hadamard = torch.tensor( hadamard(dim_padded, dtype=float), dtype=torch.float, device=self.device ).to(torch.bfloat16) self.start_pos_prefill = torch.zeros(scheduler_config.max_num_seqs, dtype=torch.int32, device=self.device) self.start_pos_decode = torch.zeros(scheduler_config.max_num_seqs, dtype=torch.int32, device=self.device) self.decode_sas_metadata = torch.zeros(1024, dtype=torch.int32, device=self.device) self.decode_qli_metadata = torch.zeros(1024, dtype=torch.int32, device=self.device) self.cu_seqlens_ori_kv = torch.tensor([], device=self.device) self.cu_seqlens_cmp_kv = torch.tensor([], device=self.device) self.seqused_q = torch.tensor([], device=self.device) self._zero_i32 = torch.tensor([0], device=self.device, dtype=torch.int32) # Note(qcs): we use two dimension slot_mapping for kvcache with shape # [block_nums, block_size, head_num, head_dim] self.slot_mapping = torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) @classmethod def get_cudagraph_support( cls: type["AscendDSAMetadataBuilder"], vllm_config: VllmConfig, kv_cache_spec: AttentionSpec, ) -> AttentionCGSupport: # Explicit override in case the underlying builder specialized this getter. # @override omitted only because of mypy limitation due to type variable. return AttentionCGSupport.UNIFORM_BATCH def reorder_batch(self, input_batch: "NPUInputBatch", scheduler_output: "SchedulerOutput") -> bool: # We now want to reorder the batch so that the "decode" requests are at # the front and the "prefill" requests are at the using the least amount # swaps possible. (NOTE for now we loosely use "decode" to mean requests # where attention is likely memory-bound and "prefill" to mean requests # where attention is likely compute-bound, TODO(lucas): figure out a # better naming here) decodes = [] prefills = [] for i, req_id in enumerate(input_batch.req_ids): num_tokens = scheduler_output.num_scheduled_tokens[req_id] if num_tokens <= self.decode_threshold: decodes.append(i) else: prefills.append(i) # We hope that this is fairly minimal since decodes # should be around for a number of iterations so hopefully they are # relatively stationary (and new request are generally appended to the # persistent batch so already should be at the back) # To achieve this we loop over the decodes in descending order and # the prefills in ascending order. We swap decodes from the "back" # i.e. past where the last decode should be in the reodorered with # prefills from the front of the batch. # `decodes` and `prefills` are already in ascending order just based on # the above loop num_decodes = len(decodes) num_prefills = len(prefills) first_prefill = 0 modified_batch = False for i in range(1, min(num_decodes, num_prefills) + 1): # If the decode is at the "back" of the batch, i, we can swap it # with the prefill closest to the front of the batch if decodes[num_decodes - i] >= num_decodes: input_batch.swap_states(prefills[first_prefill], decodes[num_decodes - i]) first_prefill += 1 modified_batch = True else: break # Save for next `build` call # TODO(lucas): this is a bit of a hack, we should probably have a # better way of doing this return modified_batch def set_num_actual_tokens( self, common_attn_metadata: AscendCommonAttentionMetadata, ): self.num_actual_tokens = common_attn_metadata.num_actual_tokens def _num_compressor_metadata_rows( self, build_step: int, common_attn_metadata: AscendCommonAttentionMetadata, ) -> int: if build_step == BUILD_METADATA_STEP_PREFILL: num_tokens = self.num_prefill_tokens num_reqs = self.num_prefills elif build_step == BUILD_METADATA_STEP_DECODE: num_tokens = self.num_decode_tokens num_reqs = self.num_decodes else: raise ValueError(f"Unsupported DSA metadata build step: {build_step}") return min(num_tokens, num_tokens // self.compressor_ratio + num_reqs) def build( self, common_prefix_len: int, common_attn_metadata: AscendCommonAttentionMetadata, fast_build: bool = False, **kwargs, ) -> AscendDSAMetadata: num_reqs = common_attn_metadata.num_reqs query_start_loc = common_attn_metadata.query_start_loc num_reqs_actual = kwargs.get("num_reqs_actual") self.prefill_ratio_to_sas_metadata = kwargs.get("prefill_ratio_to_sas_metadata") self.decode_ratio_to_sas_metadata = kwargs.get("decode_ratio_to_sas_metadata") assert self.prefill_ratio_to_sas_metadata is not None assert self.decode_ratio_to_sas_metadata is not None self.block_size = kwargs.get("block_size", 128) self.common_ratio_to_sas_metadata = kwargs.get("common_ratio_to_sas_metadata") assert self.common_ratio_to_sas_metadata is not None if self.common_ratio_to_sas_metadata.get("num_decodes", None) is None: self.num_decodes, self.num_prefills, self.num_decode_tokens, self.num_prefill_tokens = ( split_decodes_and_prefills(common_attn_metadata, decode_threshold=self.decode_threshold) ) self.common_ratio_to_sas_metadata["num_decodes"] = self.num_decodes self.common_ratio_to_sas_metadata["num_prefills"] = self.num_prefills self.common_ratio_to_sas_metadata["num_decode_tokens"] = self.num_decode_tokens self.common_ratio_to_sas_metadata["num_prefill_tokens"] = self.num_prefill_tokens self.set_num_actual_tokens(common_attn_metadata) assert self.num_decodes + self.num_prefills == num_reqs assert self.num_decode_tokens + self.num_prefill_tokens == common_attn_metadata.num_actual_tokens num_input_tokens = common_attn_metadata.num_input_tokens input_positions = common_attn_metadata.positions[:num_input_tokens].long() self.common_ratio_to_sas_metadata["input_positions"] = input_positions if self.num_prefills: cos, sin = get_cos_and_sin_dsa(input_positions) else: cos, sin = get_cos_and_sin_dsa(input_positions, True) self.common_ratio_to_sas_metadata["cos"] = cos self.common_ratio_to_sas_metadata["sin"] = sin self.seq_lens = common_attn_metadata.seq_lens[:num_reqs] self.common_ratio_to_sas_metadata["seq_lens"] = self.seq_lens query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu query_seq_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1] self.query_lens = query_seq_lens_cpu[:num_reqs] self.common_ratio_to_sas_metadata["query_lens"] = self.query_lens else: self.num_decodes, self.num_prefills, self.num_decode_tokens, self.num_prefill_tokens = ( self.common_ratio_to_sas_metadata["num_decodes"], self.common_ratio_to_sas_metadata["num_prefills"], self.common_ratio_to_sas_metadata["num_decode_tokens"], self.common_ratio_to_sas_metadata["num_prefill_tokens"], ) self.set_num_actual_tokens(common_attn_metadata) num_input_tokens = common_attn_metadata.num_input_tokens input_positions = self.common_ratio_to_sas_metadata["input_positions"] cos, sin = self.common_ratio_to_sas_metadata["cos"], self.common_ratio_to_sas_metadata["sin"] self.seq_lens = self.common_ratio_to_sas_metadata["seq_lens"] self.query_lens = self.common_ratio_to_sas_metadata["query_lens"] # NOTE: Currently, MTP-fullgraph is incompatibility pcp slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] self.slot_mapping[:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping(slot_mapping, self.block_size) self.graph_pad_size = common_attn_metadata.graph_pad_size block_table_size = self.get_block_table_size(common_attn_metadata, BUILD_METADATA_STEP_PREFILL) self.block_table = common_attn_metadata.block_table_tensor[:block_table_size] prefill_metadata = None if self.num_prefills > 0: prefill_metadata = self.build_prefill_metadata( common_prefix_len, common_attn_metadata, num_reqs_actual, ) decode_metadata = None if self.num_decodes > 0: decode_metadata = self.build_decode_metadata(common_prefix_len, common_attn_metadata, num_reqs_actual) return self.metadata_cls( # type: ignore num_input_tokens=common_attn_metadata.num_input_tokens, num_actual_tokens=self.num_actual_tokens, query_lens=self.query_lens, slot_mapping=None, head_dim=self.model_config.get_head_size(), num_decodes=self.num_decodes, num_decode_tokens=self.num_decode_tokens, num_prefills=self.num_prefills, attn_mask=None, attn_state=common_attn_metadata.attn_state, prefill=prefill_metadata, decode=decode_metadata, query_start_loc=query_start_loc, block_tables=None, seq_lens=self.seq_lens, cos=cos, sin=sin, hadamard=AscendDSAMetadataBuilder.hadamard, ) def build_prefill_metadata( self, common_prefix_len: int, common_attn_metadata: AscendCommonAttentionMetadata, num_reqs_actual: int | None, ) -> AscendDSAPrefillMetadata: assert self.prefill_ratio_to_sas_metadata is not None assert self.decode_ratio_to_sas_metadata is not None query_start_loc = common_attn_metadata.query_start_loc # reqs_start: the start request position of prefill request reqs_start = self.num_decodes # reqs_start: the start token position of prefill request tokens_start = self.num_decode_tokens if self.prefill_ratio_to_sas_metadata.get("prefill_input_positions", None) is None: input_positions = common_attn_metadata.positions[: self.num_actual_tokens].long() max_query_len = self.query_lens[reqs_start:].max().item() # Prefer _seq_lens_cpu (always available, updated during draft # iterations) over seq_lens_cpu (None in async spec decode mode). if common_attn_metadata._seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata._seq_lens_cpu elif common_attn_metadata.seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata.seq_lens_cpu else: _seq_lens_cpu = common_attn_metadata.seq_lens.cpu() max_seq_lens = _seq_lens_cpu[reqs_start:].max().item() self.prefill_ratio_to_sas_metadata["input_positions"] = input_positions self.prefill_ratio_to_sas_metadata["max_query_len"] = max_query_len self.prefill_ratio_to_sas_metadata["max_seq_lens"] = max_seq_lens prefill_query_start_loc = query_start_loc[reqs_start:] - query_start_loc[reqs_start] prefill_input_positions = input_positions[tokens_start:] self.prefill_ratio_to_sas_metadata["prefill_input_positions"] = prefill_input_positions self.prefill_ratio_to_sas_metadata["prefill_query_start_loc"] = prefill_query_start_loc cos, sin = get_cos_and_sin_dsa(prefill_input_positions) self.prefill_ratio_to_sas_metadata["cos"] = cos self.prefill_ratio_to_sas_metadata["sin"] = sin prefill_seq_lens = self.seq_lens[reqs_start:] num_prefill = prefill_seq_lens.shape[0] self.prefill_ratio_to_sas_metadata["prefill_seq_lens"] = prefill_seq_lens self.prefill_ratio_to_sas_metadata["num_prefill"] = num_prefill else: input_positions = self.prefill_ratio_to_sas_metadata["input_positions"] max_query_len = self.prefill_ratio_to_sas_metadata["max_query_len"] max_seq_lens = self.prefill_ratio_to_sas_metadata["max_seq_lens"] prefill_input_positions = self.prefill_ratio_to_sas_metadata["prefill_input_positions"] prefill_query_start_loc = self.prefill_ratio_to_sas_metadata["prefill_query_start_loc"] cos = self.prefill_ratio_to_sas_metadata["cos"] sin = self.prefill_ratio_to_sas_metadata["sin"] prefill_seq_lens = self.prefill_ratio_to_sas_metadata["prefill_seq_lens"] num_prefill = self.prefill_ratio_to_sas_metadata["num_prefill"] assert self.start_pos_prefill is not None self.start_pos_prefill.fill_(0) seq_lens_q = prefill_query_start_loc[1:] - prefill_query_start_loc[:-1] self.start_pos_prefill[:num_prefill] = self.seq_lens[reqs_start:] - seq_lens_q num_prefills_actual = num_prefill if num_reqs_actual is not None: num_prefills_actual = max(min(num_reqs_actual - reqs_start, num_prefill), 0) if num_prefills_actual < num_prefill: self.start_pos_prefill[num_prefills_actual:num_prefill].fill_(0) self.block_table[ reqs_start + num_prefills_actual : reqs_start + num_prefill, ..., ].fill_(0) layer_name = f"c{self.compressor_ratio}" full_compress_cos, full_compress_sin = None, None if self.compressor_ratio > 1: # Keep only graph inputs here. The compressor metadata op itself is # launched in forward at the real compressor consumer. num_compressed_tokens = self._num_compressor_metadata_rows( BUILD_METADATA_STEP_PREFILL, common_attn_metadata, ) full_compress_cos, full_compress_sin = get_full_cos_and_sin_dsa(layer_name) prefill_slot_mapping = None else: num_compressed_tokens = self.num_prefill_tokens prefill_slot_mapping = self.slot_mapping[tokens_start : tokens_start + self.num_prefill_tokens] tp_size = get_tensor_model_parallel_world_size() n_local_heads = self.model_config.hf_config.num_attention_heads // tp_size index_topk = self.model_config.hf_config.index_topk cu_c4_cmp_seqlen_list = None cu_c128_cmp_seqlen_list = None metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) if self.compressor_ratio <= 1: if self.prefill_ratio_to_sas_metadata.get(layer_name) is None: self.prefill_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=prefill_query_start_loc, cu_seqlens_ori_kv=prefill_query_start_loc, cu_seqlens_cmp_kv=None, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[reqs_start:], max_seqlen_q=seq_lens_q.max(), max_seqlen_kv=self.seq_lens[reqs_start:].max(), batch_size=len(self.seq_lens[reqs_start:]), cmp_ratio=1, ori_mask_mode=4, # 4:sliding window ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=False, ) sas_metadata = self.prefill_ratio_to_sas_metadata[layer_name] elif self.compressor_ratio == 4: if self.prefill_ratio_to_sas_metadata.get(layer_name) is None: self.prefill_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=prefill_query_start_loc, cu_seqlens_ori_kv=prefill_query_start_loc, cu_seqlens_cmp_kv=cu_c4_cmp_seqlen_list, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[reqs_start:], max_seqlen_q=seq_lens_q.max(), max_seqlen_kv=self.seq_lens[reqs_start:].max(), batch_size=len(self.seq_lens[reqs_start:]), cmp_topk=index_topk, # topk=index_topk, cmp_ratio=4, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=True, ) sas_metadata = self.prefill_ratio_to_sas_metadata[layer_name] else: if self.prefill_ratio_to_sas_metadata.get(layer_name) is None: self.prefill_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=prefill_query_start_loc, cu_seqlens_ori_kv=prefill_query_start_loc, cu_seqlens_cmp_kv=cu_c128_cmp_seqlen_list, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[reqs_start:], max_seqlen_q=seq_lens_q.max(), max_seqlen_kv=self.seq_lens[reqs_start:].max(), batch_size=len(self.seq_lens[reqs_start:]), cmp_ratio=128, # ori_mask_mode=4, # 4:sliding window cmp_mask_mode=3, # 3:causal ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=True, ) sas_metadata = self.prefill_ratio_to_sas_metadata[layer_name] if self.prefill_ratio_to_sas_metadata.get("qli") is None: self.prefill_ratio_to_sas_metadata["qli"] = torch.ops._C_ascend.npu_vllm_quant_lightning_indexer_metadata( actual_seq_lengths_query=prefill_query_start_loc[1:].clone(), actual_seq_lengths_key=self.seq_lens[reqs_start:].clone(), num_heads_q=self.model_config.hf_config.index_n_heads, # 64 num_heads_k=1, head_dim=self.model_config.hf_config.index_head_dim, # 128 query_quant_mode=0, key_quant_mode=0, batch_size=len(self.seq_lens[reqs_start:]), max_seqlen_q=seq_lens_q.max().item(), max_seqlen_k=self.seq_lens[reqs_start:].max().item(), layout_query="TND", layout_key="PA_BSND", sparse_count=self.model_config.hf_config.index_topk, # 512 sparse_mode=3, pre_tokens=(1 << 63) - 1, next_tokens=(1 << 63) - 1, cmp_ratio=4, device=str(self.seqused_q.device), ) qli_metadata = self.prefill_ratio_to_sas_metadata.get("qli") return AscendDSAPrefillMetadata( attn_mask=None, query_lens=self.query_lens[reqs_start:].to(torch.int32), seq_lens=self.seq_lens[reqs_start:], context_lens=self.seq_lens[reqs_start:], input_positions=prefill_input_positions, block_table=self.block_table[reqs_start:, ...], slot_mapping=prefill_slot_mapping, block_size=self.block_size, num_compressed_tokens=num_compressed_tokens, max_query_len=max_query_len, max_seq_lens=max_seq_lens, query_start_loc=prefill_query_start_loc, sin=sin, cos=cos, full_compress_sin=full_compress_sin, full_compress_cos=full_compress_cos, start_pos=self.start_pos_prefill[:num_prefill], num_reqs_actual=num_prefills_actual, sas_metadata=sas_metadata, qli_metadata=qli_metadata, cu_c4_cmp_seqlen_list=cu_c4_cmp_seqlen_list, cu_c128_cmp_seqlen_list=cu_c128_cmp_seqlen_list, ) def build_decode_metadata( self, common_prefix_len: int, common_attn_metadata: AscendCommonAttentionMetadata, num_reqs_actual: int | None, ) -> AscendDSADecodeMetadata: assert self.decode_ratio_to_sas_metadata is not None if self.decode_ratio_to_sas_metadata.get("query_start_loc", None) is None: query_start_loc = common_attn_metadata.query_start_loc[: self.num_decodes + 1] self.decode_ratio_to_sas_metadata["query_start_loc"] = query_start_loc input_positions = common_attn_metadata.positions[: self.num_decode_tokens].long() self.decode_ratio_to_sas_metadata["input_positions"] = input_positions cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=True) self.decode_ratio_to_sas_metadata["cos"] = cos self.decode_ratio_to_sas_metadata["sin"] = sin query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu[: self.num_decodes + 1] # Prefer _seq_lens_cpu (always available, updated during draft # iterations) over seq_lens_cpu (None in async spec decode mode). if common_attn_metadata._seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata._seq_lens_cpu elif common_attn_metadata.seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata.seq_lens_cpu else: _seq_lens_cpu = common_attn_metadata.seq_lens.cpu() max_seq_lens = _seq_lens_cpu[: self.num_decodes].max().item() seq_lens_list = _seq_lens_cpu[: self.num_decodes].tolist() self.decode_ratio_to_sas_metadata["query_start_loc_cpu"] = query_start_loc_cpu self.decode_ratio_to_sas_metadata["max_seq_lens"] = max_seq_lens self.decode_ratio_to_sas_metadata["seq_lens_list"] = seq_lens_list max_seqlen_kv = torch.max(_seq_lens_cpu[: self.num_decodes]).item() max_seqlen_q = torch.max(query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]).item() self.decode_ratio_to_sas_metadata["max_seqlen_kv"] = max_seqlen_kv self.decode_ratio_to_sas_metadata["max_seqlen_q"] = max_seqlen_q seq_lens_q = query_start_loc[1:] - query_start_loc[:-1] start_pos_decode = self.seq_lens[: self.num_decodes] - seq_lens_q self.decode_ratio_to_sas_metadata["start_pos_decode"] = start_pos_decode else: query_start_loc = self.decode_ratio_to_sas_metadata["query_start_loc"] input_positions = self.decode_ratio_to_sas_metadata["input_positions"] cos = self.decode_ratio_to_sas_metadata["cos"] sin = self.decode_ratio_to_sas_metadata["sin"] query_start_loc_cpu = self.decode_ratio_to_sas_metadata["query_start_loc_cpu"] max_seq_lens = self.decode_ratio_to_sas_metadata["max_seq_lens"] seq_lens_list = self.decode_ratio_to_sas_metadata["seq_lens_list"] max_seqlen_kv = self.decode_ratio_to_sas_metadata["max_seqlen_kv"] max_seqlen_q = self.decode_ratio_to_sas_metadata["max_seqlen_q"] start_pos_decode = self.decode_ratio_to_sas_metadata["start_pos_decode"] block_table_size = self.get_block_table_size(common_attn_metadata, BUILD_METADATA_STEP_DECODE) cp_seq_len, batch_seq_mask = None, None assert self.start_pos_decode is not None self.start_pos_decode.fill_(0) self.start_pos_decode[: self.num_decodes] = start_pos_decode if num_reqs_actual is not None and num_reqs_actual < self.num_decodes: self.start_pos_decode[num_reqs_actual:].fill_(0) self.block_table[num_reqs_actual : self.num_decodes, ...].fill_(0) num_decodes_actual = min(num_reqs_actual, self.num_decodes) if num_reqs_actual is not None else self.num_decodes layer_name = f"c{self.compressor_ratio}" full_compress_cos, full_compress_sin = None, None if self.compressor_ratio > 1: # Keep only graph inputs here. The compressor metadata op itself is # launched in forward at the real compressor consumer. num_compressed_tokens = self._num_compressor_metadata_rows( BUILD_METADATA_STEP_DECODE, common_attn_metadata, ) full_compress_cos, full_compress_sin = get_full_cos_and_sin_dsa(layer_name) slot_mapping = None else: num_compressed_tokens = self.num_decode_tokens slot_mapping = DeviceOperator.pad_dsa_decode_slot_mapping( self.slot_mapping[: self.num_decode_tokens], self.num_decode_tokens, self.compressor_ratio, self.num_decodes, ) tp_size = get_tensor_model_parallel_world_size() n_local_heads = self.model_config.hf_config.num_attention_heads // tp_size index_topk = self.model_config.hf_config.index_topk assert self.decode_sas_metadata is not None cu_seqlens_ori_kv = DeviceOperator.get_dsa_decode_cu_seqlens_ori_kv( self.decode_ratio_to_sas_metadata, "cu_seqlens_ori_kv", self.seq_lens, self.num_decodes, self._zero_i32, self.cu_seqlens_ori_kv, ) metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) cu_seqlens_cmp_kv = DeviceOperator.get_dsa_decode_cu_seqlens_cmp_kv(self.cu_seqlens_cmp_kv) if self.compressor_ratio <= 1: if self.decode_ratio_to_sas_metadata.get(layer_name) is None: self.decode_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=query_start_loc, # cached cu_seqlens_ori_kv=cu_seqlens_ori_kv, cu_seqlens_cmp_kv=cu_seqlens_cmp_kv, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[: self.num_decodes], # cached max_seqlen_q=max_seqlen_q, max_seqlen_kv=max_seqlen_kv, batch_size=len(self.seq_lens[: self.num_decodes]), # cached cmp_ratio=1, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=False, ) self.decode_sas_metadata[:1024] = self.decode_ratio_to_sas_metadata[layer_name] elif self.compressor_ratio == 4: if self.decode_ratio_to_sas_metadata.get(layer_name) is None: self.decode_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=query_start_loc, # cached cu_seqlens_ori_kv=cu_seqlens_ori_kv, cu_seqlens_cmp_kv=cu_seqlens_cmp_kv, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[: self.num_decodes], # cached max_seqlen_q=max_seqlen_q, max_seqlen_kv=max_seqlen_kv, batch_size=len(self.seq_lens[: self.num_decodes]), # cached cmp_topk=index_topk, # topk=index_topk, cmp_ratio=4, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=True, ) self.decode_sas_metadata[:1024] = self.decode_ratio_to_sas_metadata[layer_name] else: if self.decode_ratio_to_sas_metadata.get(layer_name) is None: self.decode_ratio_to_sas_metadata[layer_name] = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=query_start_loc, cu_seqlens_ori_kv=cu_seqlens_ori_kv, cu_seqlens_cmp_kv=cu_seqlens_cmp_kv, seqused_q=self.seqused_q, seqused_kv=self.seq_lens[: self.num_decodes], max_seqlen_q=max_seqlen_q, max_seqlen_kv=max_seqlen_kv, batch_size=len(self.seq_lens[: self.num_decodes]), cmp_ratio=128, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=True, ) self.decode_sas_metadata[:1024] = self.decode_ratio_to_sas_metadata[layer_name] assert self.decode_qli_metadata is not None if self.decode_ratio_to_sas_metadata.get("qli") is None: self.decode_ratio_to_sas_metadata["qli"] = torch.ops._C_ascend.npu_vllm_quant_lightning_indexer_metadata( actual_seq_lengths_query=query_start_loc[1:].clone(), actual_seq_lengths_key=self.seq_lens[: self.num_decodes].clone(), num_heads_q=self.model_config.hf_config.index_n_heads, # 64 num_heads_k=1, head_dim=self.model_config.hf_config.index_head_dim, # 128 query_quant_mode=0, key_quant_mode=0, batch_size=len(self.seq_lens[: self.num_decodes]), max_seqlen_q=max_seqlen_q, max_seqlen_k=max_seqlen_kv, layout_query="TND", layout_key="PA_BSND", sparse_count=self.model_config.hf_config.index_topk, # 512 sparse_mode=3, pre_tokens=(1 << 63) - 1, next_tokens=(1 << 63) - 1, cmp_ratio=4, device=str(self.seqused_q.device), ) self.decode_qli_metadata[:1024] = self.decode_ratio_to_sas_metadata.get("qli") decode_metadata = AscendDSADecodeMetadata( input_positions=input_positions, block_table=self.block_table[:block_table_size, ...], slot_mapping=slot_mapping, block_size=self.block_size, num_compressed_tokens=num_compressed_tokens, seq_lens=self.seq_lens[: self.num_decodes], # cached seq_lens_list=seq_lens_list, max_seq_lens=max_seq_lens, max_seqlen_kv=max_seqlen_kv, max_seqlen_q=max_seqlen_q, attn_mask=None, query_start_loc=query_start_loc, # cached query_start_loc_cpu=query_start_loc_cpu, sin=sin[: self.num_decode_tokens, ...], cos=cos[: self.num_decode_tokens, ...], full_compress_sin=full_compress_sin, full_compress_cos=full_compress_cos, cp_seq_len=cp_seq_len, batch_seq_mask=batch_seq_mask, start_pos=self.start_pos_decode[: self.num_decodes], # cached num_reqs_actual=num_decodes_actual, sas_metadata=self.decode_sas_metadata, qli_metadata=self.decode_qli_metadata, ) return decode_metadata def build_for_drafting( self, common_attn_metadata: AscendCommonAttentionMetadata, draft_index: int, fast_build: bool = False, **kwargs, ) -> AscendDSADecodeMetadata: assert self.compressor_ratio <= 1, "vLLM-Ascend only support SWA-layer for Deepseek-V4 now." num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = split_decodes_and_prefills( common_attn_metadata, decode_threshold=self.decode_threshold ) num_input_tokens = common_attn_metadata.num_input_tokens input_positions = common_attn_metadata.positions[:num_input_tokens].long() if num_prefills: cos, sin = get_cos_and_sin_dsa(input_positions) else: # disable use_cache, otherwise, draft_index>0 will override draft_index=0 # take care of this, if full graph is needed then rope cache is inevitable cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=True, draft_index=draft_index) slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] self.spec_slot_mapping[draft_index - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index] slot_mapping, self.block_size ) prefill_metadata = None if num_prefills > 0: prefill_metadata = self.build_prefill_metadata_for_drafting( draft_index=draft_index, common_attn_metadata=common_attn_metadata, reqs_start=num_decodes, tokens_start=num_decode_tokens, num_prefill_tokens=num_prefill_tokens, ) decode_metadata = None if num_decodes > 0: decode_metadata = self.build_decode_metadata_for_drafting( draft_index=draft_index, common_attn_metadata=common_attn_metadata, num_decodes=num_decodes, num_decode_tokens=num_decode_tokens, ) return self.metadata_cls( # type: ignore num_input_tokens=common_attn_metadata.num_input_tokens, num_actual_tokens=common_attn_metadata.num_actual_tokens, query_lens=None, slot_mapping=None, head_dim=self.model_config.get_head_size(), num_decodes=num_decodes, num_decode_tokens=num_decode_tokens, num_prefills=num_prefills, attn_mask=None, attn_state=common_attn_metadata.attn_state, prefill=prefill_metadata, decode=decode_metadata, query_start_loc=None, block_tables=None, seq_lens=None, cos=cos, sin=sin, hadamard=None, ) def build_prefill_metadata_for_drafting( self, draft_index: int, common_attn_metadata: AscendCommonAttentionMetadata, **kwargs, ) -> AscendDSAPrefillMetadata: tp_size = get_tensor_model_parallel_world_size() n_local_heads = self.model_config.hf_config.num_attention_heads // tp_size reqs_start = kwargs.get("reqs_start") tokens_start = kwargs.get("tokens_start") num_prefill_tokens = kwargs.get("num_prefill_tokens") query_start_loc = common_attn_metadata.query_start_loc prefill_query_start_loc = query_start_loc[reqs_start:] - query_start_loc[reqs_start] seq_lens_q = prefill_query_start_loc[1:] - prefill_query_start_loc[:-1] seq_lens = common_attn_metadata.seq_lens[reqs_start:] num_actual_tokens = common_attn_metadata.num_actual_tokens input_positions = common_attn_metadata.positions[:num_actual_tokens].long() prefill_input_positions = input_positions[tokens_start:] cos, sin = get_cos_and_sin_dsa(prefill_input_positions) prefill_slot_mapping = self.spec_slot_mapping[draft_index - 1][tokens_start:num_prefill_tokens] # type: ignore[index] block_table = common_attn_metadata.block_table_tensor[: common_attn_metadata.num_reqs] metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) sas_metadata = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=prefill_query_start_loc, cu_seqlens_ori_kv=prefill_query_start_loc, cu_seqlens_cmp_kv=None, seqused_q=self.seqused_q, seqused_kv=seq_lens, max_seqlen_q=seq_lens_q.max(), max_seqlen_kv=seq_lens.max(), batch_size=len(seq_lens), cmp_ratio=1, ori_mask_mode=4, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=False, ) return AscendDSAPrefillMetadata( attn_mask=None, query_lens=None, seq_lens=seq_lens, context_lens=None, input_positions=None, # type: ignore[arg-type] block_table=block_table[reqs_start:, ...], slot_mapping=prefill_slot_mapping, block_size=self.block_size, max_query_len=None, # type: ignore[arg-type] max_seq_lens=None, # type: ignore[arg-type] query_start_loc=prefill_query_start_loc, sin=sin, cos=cos, start_pos=None, sas_metadata=sas_metadata, qli_metadata=None, cu_c4_cmp_seqlen_list=None, cu_c128_cmp_seqlen_list=None, ) def build_decode_metadata_for_drafting( self, draft_index: int, common_attn_metadata: AscendCommonAttentionMetadata, **kwargs, ) -> AscendDSADecodeMetadata: tp_size = get_tensor_model_parallel_world_size() n_local_heads = self.model_config.hf_config.num_attention_heads // tp_size num_decodes = kwargs.get("num_decodes") num_decode_tokens = kwargs.get("num_decode_tokens") num_decodes_typed = num_decodes or 0 num_decode_tokens_typed = num_decode_tokens or 0 query_start_loc = common_attn_metadata.query_start_loc[: num_decodes_typed + 1] seq_lens = common_attn_metadata.seq_lens query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu[: num_decodes_typed + 1] max_seqlen_q = torch.max(query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]).item() if common_attn_metadata._seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata._seq_lens_cpu elif common_attn_metadata.seq_lens_cpu is not None: _seq_lens_cpu = common_attn_metadata.seq_lens_cpu else: _seq_lens_cpu = common_attn_metadata.seq_lens.cpu() max_seqlen_kv = torch.max(_seq_lens_cpu[:num_decodes]).item() input_positions = common_attn_metadata.positions[:num_decode_tokens_typed].long() # disable use_cache, otherwise, draft_index>0 will override draft_index=0 # take care of this, if full graph is needed then rope cache is inevitable cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=True, draft_index=draft_index) slot_mapping = self.spec_slot_mapping[draft_index - 1][:num_decode_tokens_typed] # type: ignore[index] block_table = common_attn_metadata.block_table_tensor metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) decode_sas_metadata = metadata_op( **metadata_kwargs, num_heads_q=n_local_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=query_start_loc, cu_seqlens_ori_kv=self.cu_seqlens_ori_kv, cu_seqlens_cmp_kv=self.cu_seqlens_cmp_kv, seqused_q=self.seqused_q, seqused_kv=seq_lens[:num_decodes], max_seqlen_q=max_seqlen_q, max_seqlen_kv=max_seqlen_kv, batch_size=len(seq_lens[:num_decodes]), cmp_ratio=1, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.model_config.hf_config.sliding_window - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=False, ) self.spec_sas_metadata[draft_index - 1][:1024].copy_(decode_sas_metadata[:1024]) decode_sas_metadata = self.spec_sas_metadata[draft_index - 1] decode_metadata = AscendDSADecodeMetadata( input_positions=None, block_table=block_table[:num_decodes, ...], slot_mapping=slot_mapping, block_size=self.block_size, seq_lens=seq_lens[:num_decodes], seq_lens_list=None, # type: ignore[arg-type] max_seq_lens=None, # type: ignore[arg-type] max_seqlen_kv=None, # type: ignore[arg-type] max_seqlen_q=None, # type: ignore[arg-type] attn_mask=None, query_start_loc=query_start_loc, query_start_loc_cpu=None, sin=sin[:num_decode_tokens, ...], cos=cos[:num_decode_tokens, ...], cp_seq_len=None, batch_seq_mask=None, start_pos=None, sas_metadata=decode_sas_metadata, qli_metadata=None, ) return decode_metadata def get_block_table_size(self, common_attn_metadata: AscendCommonAttentionMetadata, build_metadata_step: int): if build_metadata_step == BUILD_METADATA_STEP_PREFILL: # If graph_pad_size > -1, mean is running in fullgraph mode. # NOTE: Maybe this block_table change can be removed when graph_pad_size > 1. # if self.graph_pad_size > common_attn_metadata.num_reqs and \ # self.speculative_config.disable_padded_drafter_batch: # return self.graph_pad_size return common_attn_metadata.num_reqs return self.num_decodes def build_for_graph_capture( self, common_attn_metadata: AscendCommonAttentionMetadata, attn_state: AscendAttentionState = AscendAttentionState.DecodeOnly, **kwargs, ): if attn_state in {AscendAttentionState.DecodeOnly, AscendAttentionState.SpecDecoding}: attn_metadata = self.build( common_prefix_len=0, common_attn_metadata=common_attn_metadata, **kwargs, ) else: raise NotImplementedError( "Currently we only support building dummy metadata for DecodeOnly and SpecDecoding state" ) assert attn_metadata is not None attn_metadata.attn_state = attn_state return attn_metadata class AscendDSAImpl(DSAAttentionImpl): """ NOTE: Please read the comment at the top of the file before trying to understand this class """ def __init__( self, n_heads: int, scale: float, n_local_heads: int, q_lora_rank: int, o_lora_rank: int, head_dim: int, rope_head_dim: int | None, nope_head_dim: int, n_groups: int, n_local_groups: int, window_size: int, compress_ratio: int, **kwargs, ): self.num_heads = n_heads self.n_local_heads = n_local_heads self.scale = scale self.o_lora_rank = o_lora_rank self.nope_head_dim = nope_head_dim self.rope_head_dim = rope_head_dim self.head_dim = head_dim self.n_group = n_groups self.n_local_groups = n_local_groups self.window_size = window_size self.q_lora_rank = q_lora_rank self.compress_ratio = compress_ratio self.softmax_scale = self.head_dim**-0.5 # MLA Args self.wq_a = kwargs["wq_a"] self.wq_b = kwargs["wq_b"] self.wkv = kwargs["wkv"] self.q_norm = kwargs["q_norm"] self.q_norm_without_weight = kwargs["q_norm_without_weight"] self.kv_norm = kwargs["kv_norm"] # CV wrapper: split wq_a/wkv/wq_b into quantize(Vector) + matmul(Cube) self.cv_wq_a = CVLinearWrapper(self.wq_a) self.cv_wkv = CVLinearWrapper(self.wkv) self.cv_wq_b = CVLinearWrapper(self.wq_b) self.indexer = kwargs.get("indexer") self.compressor = kwargs.get("compressor") self.wo_a = kwargs["wo_a"] self.wo_b = kwargs["wo_b"] self.eps = kwargs["eps"] self.attn_sink = kwargs["attn_sink"] ascend_config = get_ascend_config() self.multistream_dsv4_dsa_overlap = ascend_config.multistream_dsv4_dsa_overlap self.vllm_config = get_current_vllm_config() # indexer param if self.indexer is not None: self.indexer_heads: int = self.indexer.n_heads self.inderxer_dim: int = self.indexer.head_dim self.inderxer_wq_b = self.indexer.wq_b self.cv_inderxer_wq_b = CVLinearWrapper(self.inderxer_wq_b) self.weights_proj = self.indexer.weights_proj self.indexer_softmax_scale = self.inderxer_dim**-0.5 self.indexer_compress = self.indexer.compressor # indexer_compressor self.indexcom_ape = self.indexer.compressor.ape self.indexcom_wkv = self.indexer.compressor.wkv self.indexcom_wgate = self.indexer.compressor.wgate self.indexcom_norm = self.indexer.compressor.norm self.indexcom_head_dim = self.indexer.compressor.head_dim self.indexcom_rotate = self.indexer.compressor.rotate self.index_topk = self.indexer.index_topk # compress param if self.compressor is not None: self.compressor_head_dim = self.compressor.head_dim self.compressor_overlap = self.compressor.overlap self.compressor_rotate = self.compressor.rotate self.compressor_ape = self.compressor.ape self.compressor_wkv = self.compressor.wkv self.compressor_wgate = self.compressor.wgate self.compressor_norm = self.compressor.norm self.compressor_norm_eps = self.compressor.norm_eps # IndexCache: skip_topk indicates this layer reuses topk from a previous # indexer-bearing layer; use_index_cache marks whether the buffer must # be kept fresh on non-skip layers so downstream skip layers can read. self.skip_topk = kwargs.get("skip_topk", False) self.topk_indices_buffer = kwargs.get("topk_indices_buffer") self.use_index_cache = self.skip_topk or getattr( self.vllm_config.model_config.hf_config, "use_index_cache", False, ) @staticmethod def update_graph_params( update_stream, forward_context, num_tokens, vllm_config=None, speculative_config=None, num_dcp_pcp_tokens=None, draft_attn_metadatas=None, ): # dsa does not need to update graph params pass def _get_indexcache_topk_indices(self, num_tokens: int, offset: int = 0) -> torch.Tensor: if self.topk_indices_buffer is None: raise RuntimeError("IndexCache requires topk_indices_buffer when skip_topk is enabled.") topk_indices = self.topk_indices_buffer[offset : offset + num_tokens] if topk_indices.dim() == 2: topk_indices = topk_indices.unsqueeze(1) return topk_indices def _update_indexcache_topk_indices(self, topk_indices: torch.Tensor, offset: int = 0) -> None: if self.topk_indices_buffer is None: return num_tokens = topk_indices.shape[0] topk_tokens = topk_indices.shape[-1] topk_indices_to_cache = topk_indices topk_indices_buffer = self.topk_indices_buffer[offset : offset + num_tokens, :topk_tokens] if topk_indices_to_cache.dim() == 3 and topk_indices_buffer.dim() == 2: assert topk_indices_to_cache.shape[1] == 1 topk_indices_to_cache = topk_indices_to_cache.squeeze(1) topk_indices_buffer.copy_(topk_indices_to_cache) def _compute_compressor_metadata( self, metadata: AscendDSAPrefillMetadata | AscendDSADecodeMetadata, ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: assert metadata.full_compress_cos is not None assert metadata.full_compress_sin is not None assert metadata.num_compressed_tokens is not None assert metadata.start_pos is not None assert metadata.num_reqs_actual is not None full_compress_cos = metadata.full_compress_cos.view( metadata.full_compress_cos.shape[0], metadata.full_compress_cos.shape[-1], ) full_compress_sin = metadata.full_compress_sin.view( metadata.full_compress_sin.shape[0], metadata.full_compress_sin.shape[-1], ) return torch.ops._C_ascend.compressor_metadata( full_compress_cos, full_compress_sin, metadata.query_start_loc, metadata.start_pos, metadata.block_table, metadata.block_size, DeviceOperator.get_dsa_compressor_slot_mapping_format(), self.compress_ratio, metadata.num_compressed_tokens, metadata.num_reqs_actual, ) def process_weights_after_loading(self, act_dtype: torch.dtype): # Attention impls are not walked by vllm's process_weights_after_loading # dispatcher (only LinearMethodBase subclasses are). OTP buffers are # allocated lazily on the first _forward_o_proj call, which always runs # before ACL graph capture (profiling run triggers it). pass # TODO: cast to bfloat16 to speed up def rope_single(self, x, cos, sin, inverse=False): if inverse: sin = -sin tnd_layout = 1 if len(x.shape) == 3: num_tokens, num_heads, rotary_dim = x.shape else: tnd_layout = 0 _, num_tokens, num_heads, rotary_dim = x.shape x_rot = torch_npu.npu_rotary_mul( x.reshape(num_tokens, num_heads, 1, rotary_dim), cos, sin, rotary_mode="interleave" ) if tnd_layout: x = x_rot.reshape(num_tokens, -1, rotary_dim) else: x = x_rot.reshape(1, num_tokens, -1, rotary_dim) return x def _forward_o_proj(self, o_proj_input: torch.Tensor, output: torch.Tensor) -> torch.Tensor: num_tokens = o_proj_input.shape[0] group_hidden_dim = o_proj_input.shape[1] * o_proj_input.shape[2] // self.n_local_groups o_proj_input = o_proj_input.view(num_tokens, self.n_local_groups, group_hidden_dim) # A5 (Ascend950) uses an FP8-quantized o_proj path (dynamic MX quant # + quantized batch matmul). Preserve it as-is: it predates and is # orthogonal to the OTP / olora_tp paths below, so it must win first. if get_ascend_device_type() in {AscendDeviceType.A5}: o = o_proj_input o, swiglu_out_scale = torch_npu.npu_dynamic_mx_quant(o, dst_type=torch.float8_e4m3fn) o = torch_npu.npu_transpose_quant_batchmatmul( o, self.wo_a.weight, dtype=torch.bfloat16, bias=None, group_sizes=(0, 0, 32), x1_scale=swiglu_out_scale.view(torch.float8_e8m0fnu), x2_scale=self.wo_a.weight_scale.view(torch.float8_e8m0fnu), perm_x1=(1, 0, 2), perm_x2=(0, 1, 2), perm_y=(1, 0, 2), ) o = o.reshape(num_tokens, -1) output[...] = self.wo_b(o) elif oproj_tp_enable(): oproj_group = get_otp_group() oproj_tp_size = oproj_group.world_size if self.n_local_groups % oproj_tp_size != 0: raise ValueError( "n_local_groups must be divisible by " f"oproj_tensor_parallel_size, got {self.n_local_groups} " f"and {oproj_tp_size}." ) groups_per_rank = self.n_local_groups // oproj_tp_size o_proj_input = o_proj_input.view(num_tokens, oproj_tp_size, groups_per_rank, group_hidden_dim) # Pad to a static exchange size so the all_to_all / reduce_scatter # shapes are identical across all ACL graph buckets — variable # shapes desync the HCCL communicator during graph replay. # potential_max_tokens is computed once in the model runner __init__, # so reading it here is a cheap global lookup. exchange_num_tokens = get_potential_max_tokens() if exchange_num_tokens < num_tokens: raise ValueError( "oproj static exchange capacity must cover local tokens, " f"got {exchange_num_tokens} and {num_tokens}." ) # Lazily allocate static send/recv buffers on first call. The # profiling run hits this path before ACL graph capture, so the # buffers exist and keep a stable device address across all later # capture/replay cycles (graph replay requires the same address # that was recorded at capture; new_zeros per call would desync # the HCCL operator). if not hasattr(self, "_oproj_send_buf"): buf_shape = (oproj_tp_size, exchange_num_tokens, groups_per_rank, group_hidden_dim) self._oproj_send_buf = torch.zeros(buf_shape, dtype=o_proj_input.dtype, device=o_proj_input.device) self._oproj_recv_buf = torch.empty_like(self._oproj_send_buf) send = self._oproj_send_buf recv = self._oproj_recv_buf # In-place fill into the address-stable buffer: zero the padding # tail, then copy the real tokens. send.zero_() send[:, :num_tokens].copy_(o_proj_input.transpose(1, 0)) dist.all_to_all_single(recv.view(-1), send.view(-1), group=oproj_group.device_group) o_proj_input = recv.view(oproj_tp_size * exchange_num_tokens, groups_per_rank, group_hidden_dim) o_proj_input = torch_npu.npu_transpose_batchmatmul( o_proj_input, self.wo_a.weight, bias=None, scale=None, perm_x1=(1, 0, 2), perm_x2=(0, 1, 2), perm_y=(1, 0, 2), batch_split_factor=1, ) o_proj_input = o_proj_input.reshape(oproj_tp_size * exchange_num_tokens, -1) o_proj_output = self.wo_b(o_proj_input) # reduce_scatter via a raw dist collective into an address-stable # static buffer. oproj_group.reduce_scatter is a list-based wrapper # that allocates per call, which desyncs the HCCL operator recorded # at capture during ACL graph replay — the same reason all_to_all # and the embedding TP path use raw dist + static buffers. if not hasattr(self, "_oproj_rs_out_buf"): self._oproj_rs_out_buf = torch.empty( (exchange_num_tokens, o_proj_output.shape[-1]), dtype=o_proj_output.dtype, device=o_proj_output.device, ) dist.reduce_scatter_tensor(self._oproj_rs_out_buf, o_proj_output, group=oproj_group.device_group) output[...] = self._oproj_rs_out_buf[:num_tokens] elif olora_tp_enable(): o_proj_input = self.wo_a(o_proj_input) output[...] = self.wo_b(o_proj_input) else: o_proj_input = torch_npu.npu_transpose_batchmatmul( o_proj_input, self.wo_a.weight, bias=None, scale=None, perm_x1=(1, 0, 2), perm_x2=(0, 1, 2), perm_y=(1, 0, 2), batch_split_factor=1, ) o_proj_input = o_proj_input.reshape(num_tokens, -1) output[...] = self.wo_b(o_proj_input) return output def forward( # type: ignore[override] self, layer_name, hidden_states: torch.Tensor, # query in unified attn kv_cache: tuple[torch.Tensor, ...] | None, attn_metadata: DSAMetadataList, need_gather_q_kv: bool = False, output: torch.Tensor | None = None, ) -> torch.Tensor: assert output is not None, "Output tensor must be provided." output_padded = output forward_context = get_forward_context() o_proj_input_shape = (forward_context.num_tokens, self.n_local_heads, self.head_dim) if attn_metadata is None: # Profiling run: run o_proj on zero input so HCCL collectives are # captured by the ACL graph. Non-OTP just zeros the output. if oproj_tp_enable(): o_proj_input = torch.zeros(o_proj_input_shape, dtype=hidden_states.dtype, device=hidden_states.device) self._forward_o_proj(o_proj_input, output) else: output.fill_(0) return output if not isinstance(attn_metadata, list): attn_metadata = [attn_metadata] # Process for Flash Comm V1 has_prefill = attn_metadata[0].num_prefills > 0 has_decode = attn_metadata[0].num_decodes > 0 decode_tokens = attn_metadata[0].num_decode_tokens actual_tokens = attn_metadata[0].num_actual_tokens # Process for Flash Comm V1 hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states, need_gather_q_kv) prefill_hidden_states = hidden_states[decode_tokens:actual_tokens] decode_hidden_states = hidden_states[:decode_tokens] o_proj_input = torch.empty(o_proj_input_shape, dtype=hidden_states.dtype, device=hidden_states.device) assert kv_cache is not None, "kv_cache tensor tuple must be provided." wait_for_kv_layer_from_connector(layer_name) if has_prefill: assert attn_metadata[0].prefill is not None output_prefill = self._forward_prefill( layer_name, prefill_hidden_states, kv_cache, attn_metadata, ) # type: ignore[arg-type] o_proj_input[decode_tokens:actual_tokens] = output_prefill cos = attn_metadata[0].prefill.cos[layer_name] sin = attn_metadata[0].prefill.sin[layer_name] if has_decode: assert attn_metadata[0].decode is not None output_decode = self._forward_decode(layer_name, decode_hidden_states, kv_cache, attn_metadata) o_proj_input[:decode_tokens] = output_decode cos = attn_metadata[0].decode.cos[layer_name] sin = attn_metadata[0].decode.sin[layer_name] cos = attn_metadata[0].cos[layer_name] sin = attn_metadata[0].sin[layer_name] torch.ops._C_ascend.inplace_partial_rotary_mul( o_proj_input.unsqueeze(1), cos, -sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) # o self._forward_o_proj(o_proj_input, output) maybe_save_kv_layer_to_connector(layer_name, list(kv_cache)) return output_padded def _mla_prolog_multistream(self, hidden_states, cos, sin, swa_kv_cache, slot_mapping, is_prefill=False): """3-block multi-stream: 3-stage CV parallel + serial tail Block partition (V: Vector, C: Cube, AIV: AI Vector): Part1: q_quant[V] -> q_a_down[C] || kv_quant[V] Part2: q_norm[V] + q_b_quant[V] || kv_matmul[C] Part3: q_b_matmul[C] || kv_norm[V] + rope[V] + scatter[AIV] Tail: q_rms[V] + rope[V] (wait for auxiliary stream to complete) Each stream's data is self-contained; no cross-stream sync is needed between blocks. Only the tail wait_stream ensures scatter is complete. """ main_stream = torch.npu.current_stream() aux_stream = dsv4_dsa_overlap_stream() is_w8a8 = _is_w8a8_dynamic(self.wq_b) # Part1: q_quant[V] -> q_a_down[C] || kv_quant[V] q_quant, q_pertoken_scale = self.cv_wq_a.quantize(hidden_states) e_q_quant_done = main_stream.record_event() with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_q_quant_done) kv_quant, kv_pertoken_scale = self.cv_wkv.quantize(hidden_states) wq_a_result = self.cv_wq_a.matmul(q_quant, q_pertoken_scale) main_stream.wait_stream(aux_stream) # Part2: q_norm[V] + q_b_quant[V] || kv_matmul[C] e_part2_start = main_stream.record_event() with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_part2_start) kv = self.cv_wkv.matmul(kv_quant, kv_pertoken_scale) if is_prefill: qr = self.q_norm(wq_a_result) q_b_quant, q_b_scale = self.cv_wq_b.quantize(qr) qr_pertoken_scale = None elif is_w8a8: qr, qr_pertoken_scale = torch.ops._C_ascend.npu_rms_norm_dynamic_quant( wq_a_result, self.q_norm.weight, epsilon=self.eps ) q_b_quant, q_b_scale = qr, qr_pertoken_scale else: qr = self.q_norm(wq_a_result) q_b_quant, q_b_scale = qr, None qr_pertoken_scale = None main_stream.wait_stream(aux_stream) # Part3: q_b_matmul[C] || kv_norm[V] + rope[V] + scatter[AIV] e_part3_start = main_stream.record_event() with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_part3_start) kv = self.kv_norm(kv) assert self.rope_head_dim is not None kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) torch.ops._C_ascend.inplace_partial_rotary_mul( kv.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, slot_mapping) if is_prefill: q = self.cv_wq_b.matmul(q_b_quant, q_b_scale).unflatten(-1, (self.n_local_heads, self.head_dim)) elif is_w8a8: q = torch_npu.npu_quant_matmul( q_b_quant, self.wq_b.weight, self.wq_b.weight_scale, pertoken_scale=q_b_scale, bias=self.wq_b.bias, output_dtype=hidden_states.dtype, ).unflatten(-1, (self.n_local_heads, self.head_dim)) else: q = self.cv_wq_b.matmul(q_b_quant, q_b_scale).unflatten(-1, (self.n_local_heads, self.head_dim)) # Serial tail: wait for auxiliary stream then execute q_rms[V] + rope[V] main_stream.wait_stream(aux_stream) q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) torch.ops._C_ascend.inplace_partial_rotary_mul( q.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) return q, qr, qr_pertoken_scale def _forward_prefill( self, layer_name, hidden_states: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, ): compress_common_attn_metadata = None (compress_kv_cache, swa_kv_cache, state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( DeviceOperator.unpack_dsa_forward_kv_cache(kv_cache, self.compress_ratio) ) if self.compress_ratio == 4: # sorted keys: [attn, compressor.state_cache, indexer.compressor.state_cache, indexer.k_cache, swa_cache] (compressor_attn_metadata, compressor_kv_state_metadata, _, indexer_kv_scale_metadata, swa_metadata) = ( attn_metadata ) compress_common_attn_metadata = compressor_attn_metadata elif self.compress_ratio == 128: # sorted keys: [attn, compressor.state_cache, swa_cache] (compressor_attn_metadata, compressor_kv_state_metadata, swa_metadata) = attn_metadata compress_common_attn_metadata = compressor_attn_metadata else: # sorted keys: [swa_cache] (swa_metadata,) = attn_metadata compress_common_attn_metadata = swa_metadata common_prefill_metadata = _require_prefill_metadata(compress_common_attn_metadata) swa_prefill_metadata = _require_prefill_metadata(swa_metadata) cos = common_prefill_metadata.cos[layer_name] sin = common_prefill_metadata.sin[layer_name] actual_seq_lengths_query = common_prefill_metadata.query_start_loc actual_seq_lengths_key = common_prefill_metadata.seq_lens if self.multistream_dsv4_dsa_overlap: # mla prolog: q + kv dual-stream parallel q, qr, _ = self._mla_prolog_multistream( hidden_states, cos, sin, swa_kv_cache, swa_prefill_metadata.slot_mapping, is_prefill=True ) else: # mlaprolog share_hs_quant = _is_w8a8_dynamic(self.wq_a) and _is_w8a8_dynamic(self.wkv) if share_hs_quant: hs_int8, hs_pertoken_scale = torch_npu.npu_dynamic_quant(hidden_states) q_a = torch_npu.npu_quant_matmul( hs_int8, self.wq_a.weight, self.wq_a.weight_scale, pertoken_scale=hs_pertoken_scale, bias=self.wq_a.bias, output_dtype=hidden_states.dtype, ) else: q_a = self.wq_a(hidden_states) # q if _is_w8a8_dynamic(self.wq_b): qr, qr_pertoken_scale = torch.ops._C_ascend.npu_rms_norm_dynamic_quant( q_a, self.q_norm.weight, epsilon=self.eps ) q = torch_npu.npu_quant_matmul( qr, self.wq_b.weight, self.wq_b.weight_scale, pertoken_scale=qr_pertoken_scale, bias=self.wq_b.bias, output_dtype=hidden_states.dtype, ).unflatten(-1, (self.n_local_heads, self.head_dim)) else: qr = self.q_norm(q_a) q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.head_dim)) qr_pertoken_scale = None q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) torch.ops._C_ascend.inplace_partial_rotary_mul( q.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) # win kv & tok_dis if share_hs_quant: kv = torch_npu.npu_quant_matmul( hs_int8, self.wkv.weight, self.wkv.weight_scale, pertoken_scale=hs_pertoken_scale, bias=self.wkv.bias, output_dtype=hidden_states.dtype, ) else: kv = self.wkv(hidden_states) kv = self.kv_norm(kv) assert self.rope_head_dim is not None kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) torch.ops._C_ascend.inplace_partial_rotary_mul( kv.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) # swa exec kv DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, swa_prefill_metadata.slot_mapping) attn_op = DeviceOperator.get_dsa_sparse_attn_op() extra_attn_kwargs: dict = DeviceOperator.get_dsa_sparse_attn_base_kwargs() DeviceOperator.add_dsa_sparse_attn_extra_kwargs(extra_attn_kwargs, cu_seqlens_ori_kv=actual_seq_lengths_query) if self.compress_ratio <= 1: notify_kv_cache_written(layer_name) record_attention_compute_start() return attn_op( q, ori_kv=swa_kv_cache, ori_block_table=swa_prefill_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=common_prefill_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=max(self.compress_ratio, 1), ori_mask_mode=4, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] if self.compress_ratio > 1: compressor_prefill_metadata = _require_prefill_metadata(compressor_attn_metadata) compressor_state_prefill_metadata = _require_prefill_metadata(compressor_kv_state_metadata) compress_topk_idxs = None # Only call indexer_select_qli when compress_ratio == 4 (requires 5 elements in attn_metadata) if self.compress_ratio == 4: # IndexCache: prefill segment lives at buffer[num_decode_tokens:] # because dsa_v1 forward splits hidden_states as # [decode | prefill]. See AscendDSAImpl.forward. prefill_offset = attn_metadata[0].num_decode_tokens prefill_num_tokens = hidden_states.shape[0] if self.skip_topk: compress_topk_idxs = self._get_indexcache_topk_indices(prefill_num_tokens, offset=prefill_offset) else: if self.multistream_dsv4_dsa_overlap: indexer_q = self.cv_indexer_select_qli( # multistream version x=hidden_states, qr=qr, kv_cache=kv_cache, attn_metadata=attn_metadata, cos=cos, sin=sin, actual_seq_lengths_query=actual_seq_lengths_query, with_prefill=True, ) else: compress_topk_idxs = self.indexer_select_qli( # original version x=hidden_states, qr=qr, kv_cache=kv_cache, attn_metadata=attn_metadata, cos=cos, sin=sin, actual_seq_lengths_query=actual_seq_lengths_query, actual_seq_lengths_key=actual_seq_lengths_key, with_prefill=True, qr_pertoken_scale=qr_pertoken_scale, ) coff = 2 if self.compressor_overlap else 1 compress_cos, compress_sin, compress_slot_mapping = self._compute_compressor_metadata( compressor_prefill_metadata, ) # Inline compressor + scatter (c128, c4 non-dual) compressed_kv = torch.ops._C_ascend.compressor( hidden_states, self.compressor_wkv.weight, self.compressor_wgate.weight, state_cache.squeeze(-2), self.compressor_ape, self.compressor_norm.weight, compress_sin.view(-1, compress_sin.shape[-1]), compress_cos.view(-1, compress_cos.shape[-1]), state_block_table=compressor_state_prefill_metadata.block_table, cu_seqlens=actual_seq_lengths_query, seqused=None, start_pos=common_prefill_metadata.start_pos, rope_head_dim=self.rope_head_dim, cmp_ratio=self.compress_ratio, coff=coff, norm_eps=self.compressor_norm_eps, rotary_mode=2, cache_mode=1, ) # For multistream_dsv4_dsa_overlap with compress_ratio=4: # aux_stream: indexer_weights_proj (parallel with main q_quant + kv_scatter) # main stream: compressed_kv -> q_quant -> kv_scatter -> wait aux_stream -> lightning_indexer if self.multistream_dsv4_dsa_overlap and self.compress_ratio == 4 and not self.skip_topk: main_stream = torch.npu.current_stream() aux_stream = dsv4_dsa_overlap_stream() e_compressed_kv_done = main_stream.record_event() with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_compressed_kv_done) weights_proj_output = self.weights_proj(hidden_states) # Main stream: q_quant (between compressed_kv and kv_scatter) q_quant, q_scale = DeviceOperator.indexer_quantize_query(indexer_q) # A zero-row compressor output has no KV writes. Skip scatter # instead of passing None; A5 scatter dereferences x.view(). if compressed_kv.shape[0] > 0: DeviceOperator.dsa_kv_compress_scatter(compress_kv_cache, compressed_kv, compress_slot_mapping) if self.multistream_dsv4_dsa_overlap and self.compress_ratio == 4 and not self.skip_topk: # Wait aux_stream weights_proj done, then compute dot main_stream.wait_stream(aux_stream) weights = weights_proj_output * (self.indexer_softmax_scale * self.indexer_heads**-0.5) # lightning_indexer indexer_scale_prefill_metadata = _require_prefill_metadata(indexer_kv_scale_metadata) qlens = indexer_scale_prefill_metadata.query_start_loc[1:] kvlens = indexer_scale_prefill_metadata.seq_lens block_table = indexer_scale_prefill_metadata.block_table qli_metadata = indexer_scale_prefill_metadata.qli_metadata compress_topk_idxs, _ = torch.ops._C_ascend.npu_vllm_quant_lightning_indexer( query=q_quant, key=indexer_k_cache, weights=DeviceOperator.prepare_dsa_indexer_weights(weights), query_dequant_scale=DeviceOperator.prepare_dsa_indexer_query_scale(q_scale), key_dequant_scale=DeviceOperator.prepare_dsa_indexer_key_scale(indexer_scale_cache), actual_seq_lengths_query=qlens, actual_seq_lengths_key=kvlens, block_table=block_table, metadata=qli_metadata, query_quant_mode=0, key_quant_mode=0, layout_query="TND", layout_key="PA_BSND", sparse_count=self.index_topk, sparse_mode=3, pre_tokens=(1 << 63) - 1, next_tokens=(1 << 63) - 1, cmp_ratio=4, return_value=False, ) if self.compress_ratio == 4 and self.use_index_cache: self._update_indexcache_topk_indices(compress_topk_idxs, offset=prefill_offset) notify_kv_cache_written(layer_name) record_attention_compute_start() if self.compress_ratio == 4: DeviceOperator.add_dsa_sparse_attn_extra_kwargs( extra_attn_kwargs, cu_seqlens_cmp_kv=common_prefill_metadata.cu_c4_cmp_seqlen_list ) attn_output = attn_op( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, cmp_sparse_indices=compress_topk_idxs, ori_block_table=swa_prefill_metadata.block_table, cmp_block_table=compressor_prefill_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=common_prefill_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=self.compress_ratio, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] else: DeviceOperator.add_dsa_sparse_attn_extra_kwargs( extra_attn_kwargs, cu_seqlens_cmp_kv=common_prefill_metadata.cu_c128_cmp_seqlen_list ) attn_output = attn_op( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, ori_block_table=swa_prefill_metadata.block_table, cmp_block_table=compressor_prefill_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=common_prefill_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=self.compress_ratio, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] return attn_output def _forward_decode( self, layer_name, hidden_states: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, ): assert attn_metadata[0].decode is not None compress_common_attn_metadata = None (compress_kv_cache, swa_kv_cache, state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( DeviceOperator.unpack_dsa_forward_kv_cache(kv_cache, self.compress_ratio) ) if self.compress_ratio == 4: # sorted keys: [attn, compressor.state_cache, indexer.compressor.state_cache, indexer.k_cache, swa_cache] (compressor_attn_metadata, compressor_kv_state_metadata, _, indexer_kv_scale_metadata, swa_metadata) = ( attn_metadata ) compress_common_attn_metadata = compressor_attn_metadata elif self.compress_ratio == 128: # sorted keys: [attn, compressor.state_cache, swa_cache] (compressor_attn_metadata, compressor_kv_state_metadata, swa_metadata) = attn_metadata compress_common_attn_metadata = compressor_attn_metadata else: # sorted keys: [swa_cache] (swa_metadata,) = attn_metadata compress_common_attn_metadata = swa_metadata common_decode_metadata = _require_decode_metadata(compress_common_attn_metadata) swa_decode_metadata = _require_decode_metadata(swa_metadata) cos = common_decode_metadata.cos[layer_name] sin = common_decode_metadata.sin[layer_name] actual_seq_lengths_query = common_decode_metadata.query_start_loc actual_seq_lengths_key = common_decode_metadata.seq_lens if self.multistream_dsv4_dsa_overlap: # mla prolog: q + kv dual-stream parallel q, qr, qr_pertoken_scale = self._mla_prolog_multistream( hidden_states, cos, sin, swa_kv_cache, swa_decode_metadata.slot_mapping, is_prefill=False ) else: # Share one dynamic-quant of hidden_states between wq_a (main stream) # and wkv (attention stream) when both sides are W8A8 dynamic. share_hs_quant = _is_w8a8_dynamic(self.wq_a) and _is_w8a8_dynamic(self.wkv) if share_hs_quant: hs_int8, hs_pertoken_scale = torch_npu.npu_dynamic_quant(hidden_states) # q if _is_w8a8_dynamic(self.wq_b): if share_hs_quant: q_a = torch_npu.npu_quant_matmul( hs_int8, self.wq_a.weight, self.wq_a.weight_scale, pertoken_scale=hs_pertoken_scale, bias=self.wq_a.bias, output_dtype=hidden_states.dtype, ) else: q_a = self.wq_a(hidden_states) qr, qr_pertoken_scale = torch.ops._C_ascend.npu_rms_norm_dynamic_quant( q_a, self.q_norm.weight, epsilon=self.eps ) q = torch_npu.npu_quant_matmul( qr, self.wq_b.weight, self.wq_b.weight_scale, pertoken_scale=qr_pertoken_scale, bias=self.wq_b.bias, output_dtype=hidden_states.dtype, ).unflatten(-1, (self.n_local_heads, self.head_dim)) else: if share_hs_quant: q_a = torch_npu.npu_quant_matmul( hs_int8, self.wq_a.weight, self.wq_a.weight_scale, pertoken_scale=hs_pertoken_scale, bias=self.wq_a.bias, output_dtype=hidden_states.dtype, ) qr = q = self.q_norm(q_a) else: qr = q = self.q_norm(self.wq_a(hidden_states)) q = self.wq_b(q).unflatten(-1, (self.n_local_heads, self.head_dim)) qr_pertoken_scale = None q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) torch.ops._C_ascend.inplace_partial_rotary_mul( q.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) # win kv & tok_dis if share_hs_quant: kv = torch_npu.npu_quant_matmul( hs_int8, self.wkv.weight, self.wkv.weight_scale, pertoken_scale=hs_pertoken_scale, bias=self.wkv.bias, output_dtype=hidden_states.dtype, ) else: kv = self.wkv(hidden_states) kv = self.kv_norm(kv) assert self.rope_head_dim is not None kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) torch.ops._C_ascend.inplace_partial_rotary_mul( kv.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) # swa exec kv DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, swa_decode_metadata.slot_mapping) if self.compress_ratio > 1: compressor_decode_metadata = _require_decode_metadata(compressor_attn_metadata) compressor_state_decode_metadata = _require_decode_metadata(compressor_kv_state_metadata) compress_topk_idxs = None if self.compress_ratio == 4: # IndexCache: decode segment occupies buffer[:num_decode_tokens] decode_num_tokens = hidden_states.shape[0] if self.skip_topk: compress_topk_idxs = self._get_indexcache_topk_indices(decode_num_tokens, offset=0) else: if self.multistream_dsv4_dsa_overlap: indexer_q = self.cv_indexer_select_qli( # multistream version x=hidden_states, qr=qr, kv_cache=kv_cache, attn_metadata=attn_metadata, cos=cos, sin=sin, actual_seq_lengths_query=actual_seq_lengths_query, with_prefill=False, qr_pertoken_scale=qr_pertoken_scale, ) else: compress_topk_idxs = self.indexer_select_qli( # original version x=hidden_states, qr=qr, kv_cache=kv_cache, attn_metadata=attn_metadata, cos=cos, sin=sin, actual_seq_lengths_query=actual_seq_lengths_query, actual_seq_lengths_key=actual_seq_lengths_key, with_prefill=False, qr_pertoken_scale=qr_pertoken_scale, ) coff = 2 if self.compressor_overlap else 1 compress_cos, compress_sin, compress_slot_mapping = self._compute_compressor_metadata( compressor_decode_metadata, ) # Inline compressor + scatter (c128, c4 non-dual) compressed_kv = torch.ops._C_ascend.compressor( hidden_states, self.compressor_wkv.weight, self.compressor_wgate.weight, state_cache.squeeze(-2), self.compressor_ape, self.compressor_norm.weight, compress_sin.view(-1, compress_sin.shape[-1]), compress_cos.view(-1, compress_cos.shape[-1]), state_block_table=compressor_state_decode_metadata.block_table, cu_seqlens=actual_seq_lengths_query, seqused=None, start_pos=common_decode_metadata.start_pos, rope_head_dim=self.rope_head_dim, cmp_ratio=self.compress_ratio, coff=coff, norm_eps=self.compressor_norm_eps, rotary_mode=2, cache_mode=1, ) # For multistream_dsv4_dsa_overlap with compress_ratio=4: # aux_stream: indexer_weights_proj (parallel with main q_quant + kv_scatter) # main stream: compressed_kv -> q_quant -> kv_scatter -> wait aux_stream -> lightning_indexer if self.multistream_dsv4_dsa_overlap and self.compress_ratio == 4 and not self.skip_topk: main_stream = torch.npu.current_stream() aux_stream = dsv4_dsa_overlap_stream() e_compressed_kv_done = main_stream.record_event() with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_compressed_kv_done) weights_proj_output = self.weights_proj(hidden_states) # Main stream: q_quant (between compressed_kv and kv_scatter) q_quant, q_scale = DeviceOperator.indexer_quantize_query(indexer_q) # A zero-row compressor output has no KV writes. Skip scatter # instead of passing None; A5 scatter dereferences x.view(). if compressed_kv.shape[0] > 0: DeviceOperator.dsa_kv_compress_scatter(compress_kv_cache, compressed_kv, compress_slot_mapping) if self.multistream_dsv4_dsa_overlap and self.compress_ratio == 4 and not self.skip_topk: # Wait aux_stream weights_proj done main_stream.wait_stream(aux_stream) weights = weights_proj_output * (self.indexer_softmax_scale * self.indexer_heads**-0.5) # lightning_indexer indexer_scale_decode_metadata = _require_decode_metadata(indexer_kv_scale_metadata) qlens = indexer_scale_decode_metadata.query_start_loc[1:] kvlens = indexer_scale_decode_metadata.seq_lens block_table = indexer_scale_decode_metadata.block_table qli_metadata = indexer_scale_decode_metadata.qli_metadata compress_topk_idxs, _ = torch.ops._C_ascend.npu_vllm_quant_lightning_indexer( query=q_quant, key=indexer_k_cache, weights=DeviceOperator.prepare_dsa_indexer_weights(weights), query_dequant_scale=DeviceOperator.prepare_dsa_indexer_query_scale(q_scale), key_dequant_scale=DeviceOperator.prepare_dsa_indexer_key_scale(indexer_scale_cache), actual_seq_lengths_query=qlens, actual_seq_lengths_key=kvlens, block_table=block_table, metadata=qli_metadata, query_quant_mode=0, key_quant_mode=0, layout_query="TND", layout_key="PA_BSND", sparse_count=self.index_topk, sparse_mode=3, pre_tokens=(1 << 63) - 1, next_tokens=(1 << 63) - 1, cmp_ratio=4, return_value=False, ) if self.compress_ratio == 4 and self.use_index_cache: self._update_indexcache_topk_indices(compress_topk_idxs, offset=0) notify_kv_cache_written(layer_name) record_attention_compute_start() attn_op = DeviceOperator.get_dsa_sparse_attn_op() extra_attn_kwargs: dict = DeviceOperator.get_dsa_sparse_attn_base_kwargs() if self.compress_ratio <= 1: attn_output = attn_op( q, ori_kv=swa_kv_cache, ori_block_table=swa_decode_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=swa_decode_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=max(self.compress_ratio, 1), ori_mask_mode=4, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] elif self.compress_ratio == 4: attn_output = attn_op( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, cmp_sparse_indices=compress_topk_idxs, ori_block_table=swa_decode_metadata.block_table, cmp_block_table=compressor_decode_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=compressor_decode_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=self.compress_ratio, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] else: attn_output = attn_op( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, ori_block_table=swa_decode_metadata.block_table, cmp_block_table=compressor_decode_metadata.block_table, cu_seqlens_q=actual_seq_lengths_query, seqused_kv=actual_seq_lengths_key, sinks=self.attn_sink, metadata=compressor_decode_metadata.sas_metadata, softmax_scale=self.softmax_scale, cmp_ratio=self.compress_ratio, ori_mask_mode=4, cmp_mask_mode=3, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", **extra_attn_kwargs, )[0] return attn_output def _indexer_qkv_prepare( self, x: torch.Tensor, qr: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, cos: torch.Tensor, sin: torch.Tensor, actual_seq_lengths_query: torch.Tensor, with_prefill: bool = False, qr_pertoken_scale: torch.Tensor = None, ): (indexer_state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( DeviceOperator.unpack_dsa_indexer_kv_cache(kv_cache) ) ( _, _, indexer_kv_state_metadata, indexer_kv_scale_metadata, _, ) = attn_metadata if ( _is_w8a8_dynamic(self.inderxer_wq_b) and qr_pertoken_scale is not None and get_ascend_device_type() not in {AscendDeviceType.A5} ): q = torch_npu.npu_quant_matmul( qr, self.inderxer_wq_b.weight, self.inderxer_wq_b.weight_scale, pertoken_scale=qr_pertoken_scale, bias=self.inderxer_wq_b.bias, output_dtype=x.dtype, ) else: q = self.inderxer_wq_b(qr) q = q.view(-1, self.indexer_heads, self.indexcom_head_dim) # [T, N, D] torch.ops._C_ascend.inplace_partial_rotary_mul( q.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.indexcom_head_dim - self.rope_head_dim, self.indexcom_head_dim], ) q = rotate_activation(q, indexer_kv_scale_metadata.hadamard) coff = 2 if self.compressor_overlap else 1 if with_prefill: indexer_state_prefill_metadata = _require_prefill_metadata(indexer_kv_state_metadata) indexer_scale_prefill_metadata = _require_prefill_metadata(indexer_kv_scale_metadata) kv_block_table = indexer_state_prefill_metadata.block_table start_pos = indexer_scale_prefill_metadata.start_pos compressed_cos, compressed_sin, indexer_slot_mapping = self._compute_compressor_metadata( indexer_scale_prefill_metadata, ) else: indexer_state_decode_metadata = _require_decode_metadata(indexer_kv_state_metadata) indexer_scale_decode_metadata = _require_decode_metadata(indexer_kv_scale_metadata) kv_block_table = indexer_state_decode_metadata.block_table start_pos = indexer_scale_decode_metadata.start_pos compressed_cos, compressed_sin, indexer_slot_mapping = self._compute_compressor_metadata( indexer_scale_decode_metadata, ) kv = torch.ops._C_ascend.compressor( x, self.indexcom_wkv.weight, self.indexcom_wgate.weight, indexer_state_cache.squeeze(-2), self.indexcom_ape, self.indexcom_norm.weight, compressed_sin.view(-1, compressed_sin.shape[-1]), compressed_cos.view(-1, compressed_cos.shape[-1]), state_block_table=kv_block_table, cu_seqlens=actual_seq_lengths_query, seqused=None, start_pos=start_pos, rope_head_dim=self.rope_head_dim, cmp_ratio=self.compress_ratio, coff=coff, norm_eps=self.compressor_norm_eps, rotary_mode=2, cache_mode=1, ) if kv.numel() == 0: kv = None elif self.indexcom_rotate: kv = rotate_activation(kv, indexer_kv_scale_metadata.hadamard) return ( q, kv, indexer_k_cache, indexer_scale_cache, indexer_full_cache, indexer_kv_state_metadata, indexer_kv_scale_metadata, indexer_slot_mapping, with_prefill, ) def _indexer_qli_finish( self, q: torch.Tensor, kv: torch.Tensor | None, weights: torch.Tensor, indexer_k_cache: torch.Tensor, indexer_scale_cache: torch.Tensor, indexer_full_cache: torch.Tensor | None, indexer_kv_state_metadata, indexer_kv_scale_metadata, indexer_slot_mapping: torch.Tensor, with_prefill: bool, ): q, q_scale, kv, kv_scale = self._indexer_quant_scatter( q, kv, indexer_k_cache, indexer_scale_cache, indexer_full_cache, indexer_slot_mapping, ) return self._indexer_qli( q, weights, q_scale, indexer_k_cache, indexer_scale_cache, indexer_kv_scale_metadata, with_prefill, ) def _indexer_quant_scatter( self, q: torch.Tensor, kv: torch.Tensor | None, indexer_k_cache: torch.Tensor, indexer_scale_cache: torch.Tensor, indexer_full_cache: torch.Tensor | None, slot_mapping: torch.Tensor, ): return DeviceOperator.indexer_quant_scatter( q, kv, indexer_k_cache, indexer_scale_cache, indexer_full_cache, slot_mapping ) def _indexer_qli( self, q: torch.Tensor, weights: torch.Tensor, q_scale: torch.Tensor, indexer_k_cache: torch.Tensor, indexer_scale_cache: torch.Tensor, indexer_kv_scale_metadata, with_prefill: bool, ): if with_prefill: assert indexer_kv_scale_metadata.prefill is not None qlens = indexer_kv_scale_metadata.prefill.query_start_loc[1:] kvlens = indexer_kv_scale_metadata.prefill.seq_lens block_table = indexer_kv_scale_metadata.prefill.block_table qli_metadata = indexer_kv_scale_metadata.prefill.qli_metadata else: assert indexer_kv_scale_metadata.decode is not None qlens = indexer_kv_scale_metadata.decode.query_start_loc[1:] kvlens = indexer_kv_scale_metadata.decode.seq_lens block_table = indexer_kv_scale_metadata.decode.block_table qli_metadata = indexer_kv_scale_metadata.decode.qli_metadata topk_idxs, _ = torch.ops._C_ascend.npu_vllm_quant_lightning_indexer( query=q, key=indexer_k_cache, weights=DeviceOperator.prepare_dsa_indexer_weights(weights), query_dequant_scale=DeviceOperator.prepare_dsa_indexer_query_scale(q_scale), key_dequant_scale=DeviceOperator.prepare_dsa_indexer_key_scale(indexer_scale_cache), actual_seq_lengths_query=qlens, actual_seq_lengths_key=kvlens, block_table=block_table, metadata=qli_metadata, query_quant_mode=0, key_quant_mode=0, layout_query="TND", layout_key="PA_BSND", sparse_count=self.index_topk, sparse_mode=3, pre_tokens=(1 << 63) - 1, next_tokens=(1 << 63) - 1, cmp_ratio=4, return_value=False, ) return topk_idxs def indexer_select_qli( self, x: torch.Tensor, qr: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, cos: torch.Tensor, sin: torch.Tensor, actual_seq_lengths_query: torch.Tensor, actual_seq_lengths_key: torch.Tensor | None = None, with_prefill: bool = False, qr_pertoken_scale: torch.Tensor = None, ): q, kv, ik, isc, ifc, indexer_kv_state_meta, isc_meta, indexer_slot_mapping, wp = self._indexer_qkv_prepare( x, qr, kv_cache, attn_metadata, cos, sin, actual_seq_lengths_query, with_prefill, qr_pertoken_scale, ) weights = self.weights_proj(x) * (self.indexer_softmax_scale * self.indexer_heads**-0.5) return self._indexer_qli_finish( q, kv, weights, ik, isc, ifc, indexer_kv_state_meta, isc_meta, indexer_slot_mapping, wp, ) def cv_indexer_select_qli( self, x: torch.Tensor, qr: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, cos: torch.Tensor, sin: torch.Tensor, actual_seq_lengths_query: torch.Tensor, with_prefill: bool = False, qr_pertoken_scale: torch.Tensor = None, ): """ Multistream version: 4-block segmentation, main stream and aux stream alternate submission to achieve V/C engine parallel Core strategy: - Part0: Main pre-compute qr_quant[V] + compressor[C/mixed] + kv_hadamard[V] - Part1: Main matmul[C] ∥ Aux kv_quant[V] + scatter_k_cache[AIV] - Part2: Main rope[V] (serial) - Part3: Main q_hadamard[C] ∥ Aux scatter_scale_cache[AIV] - Part4: Caller runs weights_proj + q_quant + indexer """ (indexer_state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( DeviceOperator.unpack_dsa_indexer_kv_cache(kv_cache) ) # sorted keys: [attn, compressor.state_cache, indexer.compressor.state_cache, indexer.k_cache, swa_cache] (_, _, indexer_kv_state_metadata, indexer_kv_scale_metadata, _) = attn_metadata main_stream = torch.npu.current_stream() aux_stream = dsv4_dsa_overlap_stream() # ===== Part0: Pre-compute on main ===== if _is_w8a8_dynamic(self.inderxer_wq_b) and qr_pertoken_scale is not None: qr_quant_ready = qr qr_scale_ready = qr_pertoken_scale else: qr_quant_ready, qr_scale_ready = self.cv_inderxer_wq_b.quantize(qr) coff = 2 if self.compressor_overlap else 1 if with_prefill: indexer_state_prefill_metadata = _require_prefill_metadata(indexer_kv_state_metadata) indexer_scale_prefill_metadata = _require_prefill_metadata(indexer_kv_scale_metadata) kv_block_table = indexer_state_prefill_metadata.block_table start_pos = indexer_scale_prefill_metadata.start_pos compressed_cos, compressed_sin, slot_mapping_indexer = self._compute_compressor_metadata( indexer_scale_prefill_metadata, ) else: indexer_state_decode_metadata = _require_decode_metadata(indexer_kv_state_metadata) indexer_scale_decode_metadata = _require_decode_metadata(indexer_kv_scale_metadata) kv_block_table = indexer_state_decode_metadata.block_table start_pos = indexer_scale_decode_metadata.start_pos compressed_cos, compressed_sin, slot_mapping_indexer = self._compute_compressor_metadata( indexer_scale_decode_metadata, ) kv = torch.ops._C_ascend.compressor( x, self.indexcom_wkv.weight, self.indexcom_wgate.weight, indexer_state_cache.squeeze(-2), self.indexcom_ape, self.indexcom_norm.weight, compressed_sin.view(-1, compressed_sin.shape[-1]), compressed_cos.view(-1, compressed_cos.shape[-1]), state_block_table=kv_block_table, cu_seqlens=actual_seq_lengths_query, seqused=None, start_pos=start_pos, rope_head_dim=self.rope_head_dim, cmp_ratio=self.compress_ratio, coff=coff, norm_eps=self.compressor_norm_eps, rotary_mode=2, cache_mode=1, ) if kv.numel() == 0: kv = None elif self.indexcom_rotate: kv = rotate_activation(kv, indexer_kv_scale_metadata.hadamard) # ===== Part1: matmul[C] ∥ kv_quant[V] + scatter_k_cache[AIV] ===== # Record event before main stream operations for aux_stream to wait e_kv_ready = main_stream.record_event() # Aux: kv_quant + scatter_k_cache (parallel with main matmul + rope) if kv is not None: with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_kv_ready) kv, kv_scale = DeviceOperator.indexer_quant_scatter_part1( kv, indexer_k_cache, indexer_full_cache, slot_mapping_indexer ) # Main: matmul q from qr (directly submit, V/C different engines dispatch naturally) if _is_w8a8_dynamic(self.inderxer_wq_b) and qr_pertoken_scale is not None: q = torch_npu.npu_quant_matmul( qr_quant_ready, self.inderxer_wq_b.weight, self.inderxer_wq_b.weight_scale, pertoken_scale=qr_scale_ready, bias=self.inderxer_wq_b.bias, output_dtype=x.dtype, ) else: q = self.cv_inderxer_wq_b.matmul(qr_quant_ready, qr_scale_ready) # qr_matmul if kv is not None: main_stream.wait_stream(aux_stream) q = q.view(-1, self.indexer_heads, self.indexcom_head_dim) # ===== Part2: rope[V] (main only) ===== torch.ops._C_ascend.inplace_partial_rotary_mul( # rope q.unsqueeze(1), cos, sin, rotary_mode="interleave", partial_slice=[self.indexcom_head_dim - self.rope_head_dim, self.indexcom_head_dim], ) # Wait for aux_stream kv_scatter to complete before proceeding if kv is not None: main_stream.wait_stream(aux_stream) e_rope_done = main_stream.record_event() # ===== Part3: q_hadamard[C] ∥ scatter_scale_cache[AIV] ===== # Note: On A5, indexer_compress_epilog_v2 in Part1 handles both k_cache # and scale_cache in one fused operation, so Part3 is skipped # (kv_scale is None on A5 from indexer_quant_scatter_part1). if kv is not None and kv_scale is not None: with npu_stream_switch(aux_stream, enabled=True): torch.npu.current_stream().wait_event(e_rope_done) DeviceOperator.dsa_indexer_scatter_scale_part3(kv_scale, indexer_scale_cache, slot_mapping_indexer) # Main: q_hadamard[Part1 - linear] (directly submit, C/AIV different engines dispatch naturally) # Part1: F.linear - parallel with aux_stream kv_scatter hidden_size = q.size(-1) q_linear, q_shape, q_dim = hadamard_linear(q, indexer_kv_scale_metadata.hadamard) if kv is not None: main_stream.wait_stream(aux_stream) # Main: q_hadamard[Part2 - scale] (after aux_stream completes) # Part2: scale * reshape - dot multiplication q = hadamard_scale(q_linear, q_shape, q_dim, scale=hidden_size**-0.5) return q