草稿
[WIP]Fix merge pr_494 HCCL added #1
gengli8创建于 3月9日
[WIP]Fix merge pr_494 HCCL added #1
草稿
gengli8创建于 3月9日
从codefix2合入到dsv32_tmp
共 18 个文件变更+634-327
@@ -204,7 +204,7 @@ class AclGraphModelWrapperExp(ModelWrapper):
204 model_inputs.last_hidden_states = hidden_states.to(self.device)204 model_inputs.last_hidden_states = hidden_states.to(self.device)
205 kwargs["hidden_states"] = model_inputs.last_hidden_states205 kwargs["hidden_states"] = model_inputs.last_hidden_states
206 206 
207- forward_context = self.model_runner.build_forward_context(model_inputs)207+ forward_context = self.model_runner.build_forward_context(model_inputs, **kwargs)
208 # NOTE: `PluginManager` will modify `model_inputs.input_lengths`.208 # NOTE: `PluginManager` will modify `model_inputs.input_lengths`.
209 # Assign `forward_context.seq_lens` to `model_inputs.input_lengths` so that they can share same address.209 # Assign `forward_context.seq_lens` to `model_inputs.input_lengths` so that they can share same address.
210 model_inputs.input_lengths = forward_context.attn_metadata.seq_lens210 model_inputs.input_lengths = forward_context.attn_metadata.seq_lens
@@ -199,6 +199,7 @@ class SfaMetadata(AttentionMetadata):
199 # NOTE: This class is used for building attention metadata in the future.199 # NOTE: This class is used for building attention metadata in the future.
200 actual_seq_lengths_kv: torch.Tensor | None = None200 actual_seq_lengths_kv: torch.Tensor | None = None
201 actual_seq_lengths_query: torch.Tensor | None = None201 actual_seq_lengths_query: torch.Tensor | None = None
202+ seq_lens_list: np.ndarray = None
202 cp_input_dict: Dict | None = None203 cp_input_dict: Dict | None = None
203 num_speculative_tokens: int = 0204 num_speculative_tokens: int = 0
204 num_actual_tokens: int = 0205 num_actual_tokens: int = 0
@@ -207,14 +208,15 @@ class SfaMetadata(AttentionMetadata):
207 @staticmethod208 @staticmethod
208 def from_model_input(model_inputs, cos_table, sin_table, mask, num_speculative_tokens=0):209 def from_model_input(model_inputs, cos_table, sin_table, mask, num_speculative_tokens=0):
209 if model_inputs.is_prefill:210 if model_inputs.is_prefill:
210- actual_seq_lengths_kv = torch.tensor(model_inputs.context_length, dtype=torch.int32)211+ actual_seq_lengths_query = torch.tensor(model_inputs.context_length, dtype=torch.int32)
211 else:212 else:
212- actual_seq_lengths_kv = (213+ actual_seq_lengths_query = (
213 torch.tensor(model_inputs.q_lens, dtype=torch.int32)214 torch.tensor(model_inputs.q_lens, dtype=torch.int32)
214 if num_speculative_tokens > 0215 if num_speculative_tokens > 0
215 else torch.tensor([1] * model_inputs.block_tables.shape[0], dtype=torch.int32)216 else torch.tensor([1] * model_inputs.block_tables.shape[0], dtype=torch.int32)
216 )217 )
217- actual_seq_lengths_query = torch.cumsum(actual_seq_lengths_kv, dim=0, dtype=torch.int32)218+ actual_seq_lengths_query = torch.cumsum(actual_seq_lengths_query, dim=0, dtype=torch.int32)
219+ actual_seq_lengths_kv = torch.tensor(model_inputs.context_length, dtype=torch.int32)
218 220
219 cp_size = get_parallel_info_manager().attn_cp.group_size221 cp_size = get_parallel_info_manager().attn_cp.group_size
220 cp_input_dict = None222 cp_input_dict = None
@@ -230,6 +232,7 @@ class SfaMetadata(AttentionMetadata):
230 main_stream = model_inputs.main_stream232 main_stream = model_inputs.main_stream
231 return SfaMetadata(233 return SfaMetadata(
232 seq_lens=model_inputs.context_length,234 seq_lens=model_inputs.context_length,
235+ seq_lens_list=model_inputs.context_length,
233 slot_mapping=model_inputs.slots,236 slot_mapping=model_inputs.slots,
234 block_tables=model_inputs.block_tables,237 block_tables=model_inputs.block_tables,
235 attn_mask=mask,238 attn_mask=mask,
@@ -260,7 +263,6 @@ class SfaMetadata(AttentionMetadata):
260 self.block_tables.record_stream(main_stream)263 self.block_tables.record_stream(main_stream)
261 self.slot_mapping = torch.tensor(self.slot_mapping, dtype=torch.int32).to(device)264 self.slot_mapping = torch.tensor(self.slot_mapping, dtype=torch.int32).to(device)
262 self.slot_mapping.record_stream(main_stream)265 self.slot_mapping.record_stream(main_stream)
263- self.seq_lens_list = self.seq_lens
264 self.seq_lens = torch.tensor(self.seq_lens, dtype=torch.int32).to(device)266 self.seq_lens = torch.tensor(self.seq_lens, dtype=torch.int32).to(device)
265 self.seq_lens.record_stream(main_stream)267 self.seq_lens.record_stream(main_stream)
266 268 
@@ -280,8 +282,7 @@ class SfaMetadata(AttentionMetadata):
280 282 
281 def copy(self, num_actual_tokens, num_tokens):283 def copy(self, num_actual_tokens, num_tokens):
282 # NOTE: only D2D operation is allowed, should be refactored later284 # NOTE: only D2D operation is allowed, should be refactored later
283- 285+ max_len = self.seq_lens_list.max()
284- max_len = self.seq_lens_list.max().item()
285 max_seq_pages = (max_len + 128 - 1) // 128286 max_seq_pages = (max_len + 128 - 1) // 128
286 num_reqs = num_actual_tokens // (self.num_speculative_tokens + 1)287 num_reqs = num_actual_tokens // (self.num_speculative_tokens + 1)
287 288 
@@ -293,7 +294,7 @@ class SfaMetadata(AttentionMetadata):
293 num_actual_tokens=self.num_speculative_tokens + 1)294 num_actual_tokens=self.num_speculative_tokens + 1)
294 reqs_padding_length = actual_len - self.actual_seq_lengths_kv.shape[0]295 reqs_padding_length = actual_len - self.actual_seq_lengths_kv.shape[0]
295 296
296- seq_lens_pad = torch.tensor([0] * reqs_padding_length, dtype=torch.int32).npu()297+ seq_lens_pad = torch.tensor([0] * reqs_padding_length, dtype=torch.int32, device="npu")
297 self.seq_lens = torch.cat([self.seq_lens, seq_lens_pad])298 self.seq_lens = torch.cat([self.seq_lens, seq_lens_pad])
298 input_buffer_seq_lens = input_buffer.get("seq_lens")299 input_buffer_seq_lens = input_buffer.get("seq_lens")
299 input_buffer_seq_lens[:actual_len].copy_(self.seq_lens[:actual_len])300 input_buffer_seq_lens[:actual_len].copy_(self.seq_lens[:actual_len])
@@ -303,25 +304,24 @@ class SfaMetadata(AttentionMetadata):
303 self.seq_lens_list = self.seq_lens_list.tolist() + [0] * (actual_len - num_actual_tokens)304 self.seq_lens_list = self.seq_lens_list.tolist() + [0] * (actual_len - num_actual_tokens)
304 305 
305 actual_seq_lengths_kv_pad = torch.tensor(306 actual_seq_lengths_kv_pad = torch.tensor(
306- [self.num_speculative_tokens + 1] * reqs_padding_length, dtype=torch.int32).npu()307+ [self.num_speculative_tokens + 1] * reqs_padding_length, dtype=torch.int32, device="npu")
307 308 
308 self.actual_seq_lengths_kv = torch.cat([self.actual_seq_lengths_kv, actual_seq_lengths_kv_pad])309 self.actual_seq_lengths_kv = torch.cat([self.actual_seq_lengths_kv, actual_seq_lengths_kv_pad])
309 if last_req_tokens > 0:310 if last_req_tokens > 0:
310 self.actual_seq_lengths_kv[-1] = last_req_tokens311 self.actual_seq_lengths_kv[-1] = last_req_tokens
311- self.actual_seq_lengths_query = torch.cumsum(self.actual_seq_lengths_kv, dim=0, dtype=torch.int32).npu()
312 312 
313 input_buffer_actual_seq_lengths_kv = input_buffer.get("actual_seq_lengths_kv")313 input_buffer_actual_seq_lengths_kv = input_buffer.get("actual_seq_lengths_kv")
314- input_buffer_actual_seq_lengths_kv[:actual_len].copy_(self.actual_seq_lengths_kv[:actual_len])314+ input_buffer_actual_seq_lengths_kv[:self.actual_seq_lengths_kv.shape[-1]].copy_(self.actual_seq_lengths_kv[:self.actual_seq_lengths_kv.shape[-1]])
315 self.actual_seq_lengths_kv = input_buffer_actual_seq_lengths_kv[:actual_len]315 self.actual_seq_lengths_kv = input_buffer_actual_seq_lengths_kv[:actual_len]
316 316 
317 input_buffer_actual_seq_lengths_query = input_buffer.get("actual_seq_lengths_query")317 input_buffer_actual_seq_lengths_query = input_buffer.get("actual_seq_lengths_query")
318- input_buffer_actual_seq_lengths_query[:actual_len].copy_(self.actual_seq_lengths_query[:actual_len])318+ 
319+ input_buffer_actual_seq_lengths_query[:self.actual_seq_lengths_query.shape[-1]].copy_(self.actual_seq_lengths_query[:self.actual_seq_lengths_query.shape[-1]])
319 self.actual_seq_lengths_query = input_buffer_actual_seq_lengths_query[:actual_len]320 self.actual_seq_lengths_query = input_buffer_actual_seq_lengths_query[:actual_len]
320 321 
321 input_buffer_block_tables = input_buffer.get("block_tables")322 input_buffer_block_tables = input_buffer.get("block_tables")
323+ input_buffer_block_tables.fill_(0)
322 input_buffer_block_tables[:num_reqs, :self.block_tables.shape[-1]].copy_(self.block_tables)324 input_buffer_block_tables[:num_reqs, :self.block_tables.shape[-1]].copy_(self.block_tables)
323- input_buffer_block_tables[:num_reqs, max_seq_pages:].fill_(0)
324- input_buffer_block_tables[num_reqs:, :].fill_(0)
325 self.block_tables = input_buffer_block_tables[:actual_len, :]325 self.block_tables = input_buffer_block_tables[:actual_len, :]
326 326 
327 unit_size = len(self.mc2_mask)327 unit_size = len(self.mc2_mask)
@@ -679,8 +679,7 @@ class SfaBackendImpl(SelectAttentionImpl):
679 k.view(-1, k.shape[-1]))679 k.view(-1, k.shape[-1]))
680 680 
681 weights = self.indexer.weights_proj(hidden_state)681 weights = self.indexer.weights_proj(hidden_state)
682- actual_seq_lengths_key = attn_metadata.actual_seq_lengths_kv \682+ actual_seq_lengths_key = attn_metadata.actual_seq_lengths_kv
683- if forward_context.is_prefill else attn_metadata.seq_lens
684 683
685 if forward_context.is_prefill and self.cp_size > 1:684 if forward_context.is_prefill and self.cp_size > 1:
686 q = gather_tensor(q, cp_input_dict["cp_load_balance_idx"])685 q = gather_tensor(q, cp_input_dict["cp_load_balance_idx"])
@@ -1114,4 +1113,4 @@ class SfaBackendImpl(SelectAttentionImpl):
1114 attn_metadata=attn_metadata1113 attn_metadata=attn_metadata
1115 )1114 )
1116 output = self.mla_epilog(o_proj_input)1115 output = self.mla_epilog(o_proj_input)
1117- return output1116+ return output
@@ -14,7 +14,6 @@ from typing import List
14import torch14import torch
15import torch_npu15import torch_npu
16import torch.distributed as dist16import torch.distributed as dist
17-import mie_ops
18 17 
19from mindie_llm.runtime.layers.custom_layer import CustomLayer18from mindie_llm.runtime.layers.custom_layer import CustomLayer
20from mindie_llm.runtime.layers.fused_moe.moe_comm_method import (19from mindie_llm.runtime.layers.fused_moe.moe_comm_method import (
@@ -34,6 +33,10 @@ from mindie_llm.runtime.utils.distributed.parallel_info_manager import ParallelT
34from mindie_llm.runtime.utils.distributed.utils import even_divide33from mindie_llm.runtime.utils.distributed.utils import even_divide
35from mindie_llm.runtime.model_runner.forward_context import get_forward_context34from mindie_llm.runtime.model_runner.forward_context import get_forward_context
36 35 
36+# Recommended value for fused operator output buffer size (in elements).
37+# Determined empirically based on current npu_dispatch_ffn_combine operator constraints.
38+MAX_OUTPUT_SIZE: int = 65536
39+ 
37 40 
38class FusedMoE(CustomLayer):41class FusedMoE(CustomLayer):
39 def __init__(42 def __init__(
@@ -49,7 +52,6 @@ class FusedMoE(CustomLayer):
49 prefix: str = "",52 prefix: str = "",
50 suffix: List[str] = None,53 suffix: List[str] = None,
51 activation: str = "silu",54 activation: str = "silu",
52- is_fused_op_enabled: bool = False,
53 ):55 ):
54 super().__init__()56 super().__init__()
55 57 
@@ -69,6 +71,11 @@ class FusedMoE(CustomLayer):
69 self.activation = activation71 self.activation = activation
70 self.num_experts = num_experts72 self.num_experts = num_experts
71 self.topk = topk_num73 self.topk = topk_num
74+ # Max receivable tokens per device.
75+ # Formula is MAX_OUTPUT_SIZE / load_balance_factor / topk
76+ # Note: load_balance_factor=2 is recommended by the fused operator spec
77+ # to account for load balancing skew (reserves 2x the average per-card capacity).
78+ self.max_num_tokens_per_device = MAX_OUTPUT_SIZE // 2 // self.topk
72 self.expert_list = assign_experts(self.num_experts, self.moe_ep_size)[self.moe_ep_rank]79 self.expert_list = assign_experts(self.num_experts, self.moe_ep_size)[self.moe_ep_rank]
73 self.num_local_experts = len(self.expert_list)80 self.num_local_experts = len(self.expert_list)
74 self.expert_map = torch.full(size=(self.num_experts,), fill_value=-1, device='npu')81 self.expert_map = torch.full(size=(self.num_experts,), fill_value=-1, device='npu')
@@ -78,10 +85,9 @@ class FusedMoE(CustomLayer):
78 else:85 else:
79 # Get moe quant method through gate proj weights of expert 086 # Get moe quant method through gate proj weights of expert 0
80 self.quant_method = self.quant_config.get_quant_method(self, prefix=f"{self.prefix}.0.{self.suffix[0]}")87 self.quant_method = self.quant_config.get_quant_method(self, prefix=f"{self.prefix}.0.{self.suffix[0]}")
81- 
82- self.is_fused_op_enabled = is_fused_op_enabled
83 # Only created when MC2 used fused op88 # Only created when MC2 used fused op
84 self._moe_ep_group = None89 self._moe_ep_group = None
90+ 
85 self._create_weights()91 self._create_weights()
86 self._post_init()92 self._post_init()
87 93 
@@ -117,9 +123,13 @@ class FusedMoE(CustomLayer):
117 topk_ids: torch.Tensor,123 topk_ids: torch.Tensor,
118 ) -> torch.Tensor:124 ) -> torch.Tensor:
119 moe_comm_type = select_moe_comm_method(125 moe_comm_type = select_moe_comm_method(
120- quant_type=None126+ quant_type=None,
127+ max_num_tokens_per_device=self.max_num_tokens_per_device
121 )128 )
122- if moe_comm_type == MoECommType.MC2 and self.is_fused_op_enabled:129+ 
130+ if moe_comm_type == MoECommType.FUSED_MC2:
131+ import mie_ops
132+ # FUSED_MC2 mode: Directly invoke the fused dispatch + FFN + combine operator
123 self._create_moe_ep_group()133 self._create_moe_ep_group()
124 final_hidden_states = torch.empty_like(hidden_states)134 final_hidden_states = torch.empty_like(hidden_states)
125 torch.ops.mie_ops.npu_dispatch_ffn_combine(135 torch.ops.mie_ops.npu_dispatch_ffn_combine(
@@ -131,7 +141,7 @@ class FusedMoE(CustomLayer):
131 scale2=[self.fused_down_weight_scale],141 scale2=[self.fused_down_weight_scale],
132 probs=topk_weights.to(self.weight_dtype).to(torch.float32),142 probs=topk_weights.to(self.weight_dtype).to(torch.float32),
133 group=self._moe_ep_group,143 group=self._moe_ep_group,
134- max_output_size=65536,144+ max_output_size=MAX_OUTPUT_SIZE,
135 out=final_hidden_states,145 out=final_hidden_states,
136 )146 )
137 return final_hidden_states147 return final_hidden_states
@@ -222,11 +232,12 @@ class FusedMoE(CustomLayer):
222 bias_dtype=self.weight_dtype,232 bias_dtype=self.weight_dtype,
223 weight_loader=None,233 weight_loader=None,
224 )234 )
235+ 
225 def _create_moe_ep_group(self):236 def _create_moe_ep_group(self):
226 if self._moe_ep_group is not None:237 if self._moe_ep_group is not None:
227 return238 return
228 parallel_info_manager = get_parallel_info_manager()239 parallel_info_manager = get_parallel_info_manager()
229- device_group = parallel_info_manager.moe_ep_mc2.process_group240+ device_group = parallel_info_manager.get(ParallelType.MOE_EP_MC2).process_group
230 local_rank = dist.get_rank(group=device_group)241 local_rank = dist.get_rank(group=device_group)
231 backend = device_group._get_backend(torch.device("npu"))242 backend = device_group._get_backend(torch.device("npu"))
232 self._moe_ep_group = backend.get_hccl_comm_name(local_rank)243 self._moe_ep_group = backend.get_hccl_comm_name(local_rank)
@@ -1,7 +1,3 @@
1-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
2-# SPDX-License-Identifier: Apache-2.0
3-#
4-# Implement part of this file based on vllm-project/vllm
5# Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. All rights reserved.1# Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. All rights reserved.
6# MindIE is licensed under Mulan PSL v2.2# MindIE is licensed under Mulan PSL v2.
7# You can use this software according to the terms and conditions of the Mulan PSL v2.3# You can use this software according to the terms and conditions of the Mulan PSL v2.
@@ -12,32 +8,21 @@
12# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.8# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
13# See the Mulan PSL v2 for more details.9# See the Mulan PSL v2 for more details.
14 10 
15-from enum import Enum11+from typing import Optional, Dict, Type
16-from typing import Optional
17 12 
18-from mindie_llm.runtime.utils.npu.device_utils import DeviceType, get_npu_node_info
19from mindie_llm.runtime.layers.fused_moe.token_dispatcher import (13from mindie_llm.runtime.layers.fused_moe.token_dispatcher import (
20 MoETokenDispatcher,14 MoETokenDispatcher,
21 TokenDispatcherWithAllGather,15 TokenDispatcherWithAllGather,
22 TokenDispatcherWithMC2,16 TokenDispatcherWithMC2,
23 TokenDispatcherWithAll2AllV17 TokenDispatcherWithAll2AllV
24)18)
25-from mindie_llm.runtime.utils.distributed import get_parallel_info_manager19+from mindie_llm.runtime.layers.fused_moe.token_dispatcher import MoETokenDispatcher
26-from mindie_llm.runtime.utils.distributed.parallel_info_manager import ParallelType20+from mindie_llm.runtime.layers.fused_moe.moe_comm_strategy import MOE_COMM_STRATEGIES, MoECommType
27-from mindie_llm.runtime.layers.quantization.ms_model_slim.quant_type import QuantType
28-from mindie_llm.runtime.model_runner.forward_context import get_forward_context
29-from mindie_llm.runtime.model_runner.forward_context_exp import get_mc2_token_capacity
30from mindie_llm.utils.log.logging import logger21from mindie_llm.utils.log.logging import logger
31 22 
32 23 
33-class MoECommType(Enum):24+# Communication type to dispatcher class mapping
34- ALLGATHER = "allgather"25+_COMM_TYPE_TO_DISPATCHER: Dict[MoECommType, Type[MoETokenDispatcher]] = {
35- MC2 = "mc2"
36- ALLTOALL = "all2all"
37- FUSED_ALLTOALL = "fused_all2all"
38- 
39- 
40-_COMM_TYPE_TO_DISPATCHER = {
41 MoECommType.ALLGATHER: TokenDispatcherWithAllGather,26 MoECommType.ALLGATHER: TokenDispatcherWithAllGather,
42 MoECommType.MC2: TokenDispatcherWithMC2,27 MoECommType.MC2: TokenDispatcherWithMC2,
43 MoECommType.ALLTOALL: TokenDispatcherWithAll2AllV,28 MoECommType.ALLTOALL: TokenDispatcherWithAll2AllV,
@@ -46,80 +31,47 @@ _COMM_TYPE_TO_DISPATCHER = {
46 31 
47def select_moe_comm_method(32def select_moe_comm_method(
48 quant_type: Optional[str] = None,33 quant_type: Optional[str] = None,
49- is_fused_op_enabled: bool = False) -> Optional[MoECommType]:34+ max_num_tokens_per_device: Optional[int] = None
50- """1. If expert parallel is not enabled, we use all-gather since MC2 and all-to-all35+) -> Optional[MoECommType]:
51- are designed for expert parallelism.36+ """Select optimal MoE communication method.
52- 2. If expert parallel is enabled, we need to consider the soc version and the37+
53- number of tokens. This is based on the observation that all-gather is more
54- efficient than all-to-all when running on _910B.
55- 
56- a. For _910B, we choose from MC2 and all-gather.
57- 
58- b. For _910_93, we choose from MC2 and all-to-all.
59- 
60- In both cases, we use MC2 when in prefill phase.
61- 
62 Args:38 Args:
63- quant_type (Optional[str], optional): The quantization type. Defaults to None.39+ quant_type: Quantization type (e.g., W4A8_DYNAMIC).
64- 40+ max_num_tokens_per_device: Max tokens per device limit.
65- Raises:41+
66- ValueError: If the soc version is unsupported.
67- 
68 Returns:42 Returns:
69- MoECommType: The selected MoE communication method.43+ Selected MoECommType.
44+
45+ Raises:
46+ RuntimeError: No applicable strategy found.
70 """47 """
71- ascend_device_type = get_npu_node_info().get_device_type()48+ # Strategy traversal: return first applicable strategy
72- parallel_info_manager = get_parallel_info_manager()49+ for strategy_cls in MOE_COMM_STRATEGIES:
73- is_prefill = get_forward_context().is_prefill50+ if strategy_cls.is_applicable(
74- enable_expert_parallel = parallel_info_manager.get(ParallelType.MOE_EP).is_enabled()51+ quant_type=quant_type,
75- 52+ max_num_tokens_per_device=max_num_tokens_per_device
76- # NOTE: Due to unsolved error for mc2 in prefill phase, we disable mc2 in prefill phase.53+ ):
77- # NOTE: Temporarily not use parameter num_tokens to select MoECommType.54+ selected = strategy_cls.get_comm_type()
78- if not enable_expert_parallel:55+ return selected
79- moe_comm_type = MoECommType.ALLGATHER56+ error_msg = (
80- elif ascend_device_type in {DeviceType.ASCEND_910B}:57+ "MoE strategy selection failed, please check the config."
81- if not is_prefill and parallel_info_manager.world_size >= 16:58+ )
82- moe_comm_type = MoECommType.MC259+ logger.error(error_msg)
83- else:60+ raise RuntimeError(error_msg)
84- if quant_type == QuantType.W4A8_DYNAMIC:
85- moe_comm_type = MoECommType.ALLTOALL
86- else:
87- moe_comm_type = MoECommType.ALLGATHER
88- elif ascend_device_type in {DeviceType.ASCEND_910_93}:
89- # NOTE: Fused op is not enabled in prefill stage
90- if get_forward_context().is_prefill:
91- moe_comm_type = MoECommType.ALLTOALL
92- elif get_forward_context().batch_descriptor.num_tokens <= get_mc2_token_capacity():
93- moe_comm_type = MoECommType.MC2
94- else:
95- moe_comm_type = MoECommType.ALLTOALL
96- else:
97- raise ValueError(f"Unsupported soc_version: {ascend_device_type}")
98- 
99- # NOTE: MoECommType.ALLGATHER: Currently does not support enabling DP.
100- if moe_comm_type == MoECommType.ALLGATHER and parallel_info_manager.get(ParallelType.ATTN_DP).is_enabled():
101- moe_comm_type = MoECommType.ALLTOALL
102- logger.debug('Currently do not support MoECommType.ALLGATHER with dp.')
103- 
104- # NOTE: MoECommType.MC2 and MoECommType.ALLTOALL: Do not support moe_tp > 1,
105- # and require flash_comm to be enabled.
106- if moe_comm_type in (MoECommType.MC2, MoECommType.ALLTOALL):
107- if parallel_info_manager.get(ParallelType.MOE_TP).is_enabled():
108- err_msg = 'MoECommType.MC2 and MoECommType.ALLTOALL: Do not support moe_tp > 1,'
109- # try to change to MoECommType.ALLGATHER
110- if parallel_info_manager.get(ParallelType.ATTN_DP).is_enabled():
111- logger.error(err_msg)
112- raise RuntimeError(err_msg)
113- else:
114- logger.warning(err_msg)
115- moe_comm_type = MoECommType.ALLGATHER
116- 
117- return moe_comm_type
118 61 
119 62 
120def get_cached_dispatcher(63def get_cached_dispatcher(
121- moe_comm_type: Optional[MoECommType]) -> Optional[MoETokenDispatcher]:64+ moe_comm_type: Optional[MoECommType]
65+) -> Optional[MoETokenDispatcher]:
66+ """Get dispatcher instance for given communication type.
67+
68+ Args:
69+ moe_comm_type: Communication type enum.
70+
71+ Returns:
72+ Dispatcher instance or None if type unsupported.
73+ """
122 if moe_comm_type is None or moe_comm_type not in _COMM_TYPE_TO_DISPATCHER:74 if moe_comm_type is None or moe_comm_type not in _COMM_TYPE_TO_DISPATCHER:
123 return None75 return None
124 dispatcher_cls = _COMM_TYPE_TO_DISPATCHER[moe_comm_type]76 dispatcher_cls = _COMM_TYPE_TO_DISPATCHER[moe_comm_type]
125- return dispatcher_cls()77+ return dispatcher_cls()
@@ -0,0 +1,183 @@
1+# Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. All rights reserved.
2+# MindIE is licensed under Mulan PSL v2.
3+# You can use this software according to the terms and conditions of the Mulan PSL v2.
4+# You may obtain a copy of Mulan PSL v2 at:
5+# http://license.coscl.org.cn/MulanPSL2
6+# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
7+# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
8+# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9+# See the Mulan PSL v2 for more details.
10+ 
11+from typing import Optional
12+ 
13+from enum import Enum
14+from mindie_llm.runtime.utils.npu.device_utils import DeviceType
15+from mindie_llm.runtime.layers.quantization.ms_model_slim.quant_type import QuantType
16+from mindie_llm.runtime.model_runner.forward_context_exp import get_mc2_token_capacity
17+from mindie_llm.utils.log.logging import logger
18+ 
19+from mindie_llm.runtime.model_runner.forward_context import get_forward_context
20+from mindie_llm.runtime.utils.distributed import get_parallel_info_manager
21+from mindie_llm.runtime.utils.distributed.parallel_info_manager import ParallelType
22+from mindie_llm.runtime.utils.npu.device_utils import get_npu_node_info
23+ 
24+ 
25+class MoECommType(Enum):
26+ """Supported MoE communication operator types."""
27+ ALLGATHER = "allgather"
28+ MC2 = "mc2"
29+ ALLTOALL = "all2all"
30+ FUSED_ALLTOALL = "fused_all2all"
31+ FUSED_MC2 = "fused_mc2"
32+ 
33+ 
34+class MoECommStrategyBase:
35+ """Base class for MoE communication strategy selection."""
36+
37+ @staticmethod
38+ def is_applicable(quant_type: Optional[str], max_num_tokens_per_device: Optional[int]) -> bool:
39+ """Check if strategy applies to given context."""
40+ raise NotImplementedError
41+
42+ @staticmethod
43+ def get_comm_type() -> MoECommType:
44+ """Return communication type for this strategy."""
45+ raise NotImplementedError
46+ 
47+ 
48+class FusedMC2Strategy(MoECommStrategyBase):
49+ """Fused MC2 strategy: highest priority for Ascend 910C.
50+
51+ Requirements: EP group size <= 32, no MoE TP, tokens within capacity.
52+ """
53+
54+ @staticmethod
55+ def is_applicable(quant_type: Optional[str], max_num_tokens_per_device: Optional[int]) -> bool:
56+ device_type = get_npu_node_info().get_device_type()
57+ parallel_mgr = get_parallel_info_manager()
58+ forward_ctx = get_forward_context()
59+
60+ # EP must be enabled for any MoE communication strategy
61+ if not parallel_mgr.get(ParallelType.MOE_EP).is_enabled():
62+ return False
63+ # Hardware constraint: only Ascend 910C (910_93) supports fused MC2
64+ if device_type != DeviceType.ASCEND_910_93:
65+ return False
66+
67+ # EP group size limit for fused operator efficiency
68+ is_ep_valid = parallel_mgr.get(ParallelType.MOE_EP_MC2).group_size <= 32
69+ # Token count must fit within per-device capacity
70+ tp_size = parallel_mgr.get(ParallelType.ATTN_TP).group_size
71+ num_tokens_per_device = (forward_ctx.batch_descriptor.num_tokens + tp_size - 1) // tp_size
72+ is_num_tokens_valid = num_tokens_per_device <= max_num_tokens_per_device
73+ if not (is_ep_valid and is_num_tokens_valid):
74+ return False
75+ # Incompatible with MoE TP parallelism
76+ if parallel_mgr.get(ParallelType.MOE_TP).is_enabled():
77+ return False
78+ return True
79+
80+ @staticmethod
81+ def get_comm_type() -> MoECommType:
82+ return MoECommType.FUSED_MC2
83+ 
84+ 
85+class MC2Strategy(MoECommStrategyBase):
86+ """MC2 strategy: for Ascend 910B/910C decode phase.
87+
88+ 910B: requires world size >= 16. 910C: strict token capacity check.
89+ """
90+
91+ @staticmethod
92+ def is_applicable(quant_type: Optional[str], max_num_tokens_per_device: Optional[int]) -> bool:
93+ device_type = get_npu_node_info().get_device_type()
94+ parallel_mgr = get_parallel_info_manager()
95+ forward_ctx = get_forward_context()
96+ if not parallel_mgr.get(ParallelType.MOE_EP).is_enabled():
97+ return False
98+
99+ if device_type == DeviceType.ASCEND_910B:
100+ # 910B: decode phase only, large cluster required
101+ if forward_ctx.is_prefill or parallel_mgr.world_size < 16:
102+ return False
103+ elif device_type == DeviceType.ASCEND_910_93:
104+ # 910C: decode phase only
105+ if forward_ctx.is_prefill:
106+ return False
107+ # Strict capacity check: fail fast if exceeded
108+ if forward_ctx.batch_descriptor.num_tokens > get_mc2_token_capacity():
109+ error_msg = (
110+ f"MC2 operator limitation: tokens per card ({forward_ctx.batch_descriptor.num_tokens})"
111+ f" exceed the limit of mc2_token_capacity = {get_mc2_token_capacity()}. "
112+ f"Please reduce the batch_size during the decode stage."
113+ )
114+ logger.error(error_msg)
115+ raise ValueError(error_msg)
116+ else:
117+ logger.error("Unsupported device type: {device_type}")
118+ raise RuntimeError("Unsupported device type: {device_type}")
119+
120+ # Incompatible with MoE TP parallelism
121+ if parallel_mgr.get(ParallelType.MOE_TP).is_enabled():
122+ return False
123+ return True
124+
125+ @staticmethod
126+ def get_comm_type() -> MoECommType:
127+ return MoECommType.MC2
128+ 
129+ 
130+class All2AllStrategy(MoECommStrategyBase):
131+ """All2All strategy: fallback for specific scenarios.
132+
133+ 910B: W4A8_DYNAMIC quant or Attn DP enabled.
134+ 910C: prefill phase when fused MC2 unavailable.
135+ """
136+
137+ @staticmethod
138+ def is_applicable(quant_type: Optional[str], max_num_tokens_per_device: Optional[int]) -> bool:
139+ device_type = get_npu_node_info().get_device_type()
140+ parallel_mgr = get_parallel_info_manager()
141+ if not parallel_mgr.get(ParallelType.MOE_EP).is_enabled():
142+ return False
143+
144+ if device_type == DeviceType.ASCEND_910B:
145+ # 910B: prefer W4A8_DYNAMIC; allow fallback if Attn DP enabled
146+ if quant_type != QuantType.W4A8_DYNAMIC:
147+ return parallel_mgr.get(ParallelType.ATTN_DP).is_enabled()
148+
149+ # Compatibility rule: MoE TP + All2All requires special handling
150+ if parallel_mgr.get(ParallelType.MOE_TP).is_enabled():
151+ # Attn DP + MoE TP > 1 is not supported for MC2/All2All
152+ if parallel_mgr.get(ParallelType.ATTN_DP).is_enabled():
153+ err_msg = 'MoECommType.MC2 and MoECommType.ALLTOALL: Do not support moe_tp > 1'
154+ logger.error(err_msg)
155+ raise RuntimeError(err_msg)
156+ return False
157+ return True
158+
159+ @staticmethod
160+ def get_comm_type() -> MoECommType:
161+ return MoECommType.ALLTOALL
162+ 
163+ 
164+class AllGatherStrategy(MoECommStrategyBase):
165+ """AllGather strategy: universal fallback with lowest priority."""
166+
167+ @staticmethod
168+ def is_applicable(quant_type: Optional[str], max_num_tokens_per_device: Optional[int]) -> bool:
169+ # Fallback: applicable when no other strategy matches
170+ return True
171+
172+ @staticmethod
173+ def get_comm_type() -> MoECommType:
174+ return MoECommType.ALLGATHER
175+ 
176+ 
177+# Strategy priority order: first applicable strategy is selected
178+MOE_COMM_STRATEGIES = [
179+ FusedMC2Strategy, # P0: Optimal (910C Fused)
180+ MC2Strategy, # P1: High perf (large cluster/decode)
181+ All2AllStrategy, # P2: Specific quant/prefill
182+ AllGatherStrategy, # P3: Universal fallback
183+]
@@ -1,3 +1,4 @@
1+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
1# SPDX-License-Identifier: Apache-2.02# SPDX-License-Identifier: Apache-2.0
2# Copyright (c) 2024; NVIDIA CORPORATION. All rights reserved.3# Copyright (c) 2024; NVIDIA CORPORATION. All rights reserved.
3# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved.4# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved.
@@ -596,10 +597,11 @@ class TokenDispatcherWithAll2AllV(Singleton, MoETokenDispatcher):
596 global_input_tokens_local_experts_indices):597 global_input_tokens_local_experts_indices):
597 # Early return if no local experts or no tokens598 # Early return if no local experts or no tokens
598 if num_local_experts <= 1:599 if num_local_experts <= 1:
599- return global_input_tokens, dynamic_scale, None600+ return global_input_tokens, None
600 601 
601 # Handle quantized case602 # Handle quantized case
602 if self.with_quant:603 if self.with_quant:
604+ expert_idx_2d = global_input_tokens_local_experts_indices.unsqueeze(-1)
603 active_num = global_input_tokens_local_experts_indices.numel()605 active_num = global_input_tokens_local_experts_indices.numel()
604 606 
605 # Handle case with no active tokens607 # Handle case with no active tokens
@@ -607,16 +609,21 @@ class TokenDispatcherWithAll2AllV(Singleton, MoETokenDispatcher):
607 reversed_global_input_permutation_mapping = global_input_tokens_local_experts_indices609 reversed_global_input_permutation_mapping = global_input_tokens_local_experts_indices
608 return global_input_tokens, dynamic_scale, reversed_global_input_permutation_mapping610 return global_input_tokens, dynamic_scale, reversed_global_input_permutation_mapping
609 611 
610- dynamic_scale, _ = torch_npu.npu_moe_token_permute(612+ # Process with active tokens
611- dynamic_scale.unsqueeze(-1),613+ (global_input_tokens, reversed_global_input_permutation_mapping,
612- global_input_tokens_local_experts_indices)614+ _, expanded_scale) = torch_npu.npu_moe_init_routing_v2(
613- dynamic_scale = dynamic_scale.squeeze(-1)615+ global_input_tokens,
614- 616+ expert_idx_2d,
615- # Handle non-quantized case617+ scale=dynamic_scale,
616- global_input_tokens, reversed_global_input_permutation_mapping = torch_npu.npu_moe_token_permute(618+ active_num=active_num,
617- global_input_tokens,619+ expert_capacity=0,
618- global_input_tokens_local_experts_indices)620+ expert_num=num_local_experts,
619- return global_input_tokens, dynamic_scale, reversed_global_input_permutation_mapping621+ expert_tokens_num_type=1,
622+ expert_tokens_num_flag=True,
623+ active_expert_range=[0, num_local_experts],
624+ quant_mode=-1,
625+ row_idx_type=0)
626+ return global_input_tokens, expanded_scale, reversed_global_input_permutation_mapping
620 627 
621 # Handle non-quantized case628 # Handle non-quantized case
622 global_input_tokens, reversed_global_input_permutation_mapping = torch_npu.npu_moe_token_permute(629 global_input_tokens, reversed_global_input_permutation_mapping = torch_npu.npu_moe_token_permute(
@@ -25,7 +25,6 @@ from typing import List
25import torch25import torch
26import torch_npu26import torch_npu
27from torch import nn27from torch import nn
28-from torch.nn.functional import pad
29 28 
30from mindie_llm.runtime.layers.fused_moe.fused_moe_method_base import FusedMoEMethodBase29from mindie_llm.runtime.layers.fused_moe.fused_moe_method_base import FusedMoEMethodBase
31from mindie_llm.runtime.layers.linear.linear_method_base import LinearMethodBase30from mindie_llm.runtime.layers.linear.linear_method_base import LinearMethodBase
@@ -44,7 +43,7 @@ from mindie_llm.runtime.utils.distributed.utils import even_divide
44from mindie_llm.utils.log.logging import logger43from mindie_llm.utils.log.logging import logger
45 44 
46 45 
47-SUPPORT_NZ_NPU_LIST = ("Ascend910B3", "Ascend910B4_1", "Ascend910_9382", "Ascend910_9362")46+SUPPORT_NZ_NPU_LIST = ("Ascend910B3", "Ascend910B4-1", "Ascend910_9382", "Ascend910_9362")
48MXFP8_GROUP_SIZE = 3247MXFP8_GROUP_SIZE = 32
49 48 
50 49 
@@ -467,13 +466,26 @@ class W8A8PerTokenFusedMoEMethod(FusedMoEMethodBase):
467 else:466 else:
468 pertoken_scale = dynamic_scale467 pertoken_scale = dynamic_scale
469 468 
470- hidden_states, swiglu_out_scale, _ = torch_npu.npu_grouped_matmul_swiglu_quant(469+ hidden_states = torch_npu.npu_grouped_matmul(
471- x=x,470+ x=[x],
472- weight=layer.gate_up_weight,471+ weight=[layer.gate_up_weight],
473- bias=None,472+ split_item=2, # output a single tensor
474- group_list=cumsum_group_list(group_list, group_list_type, 0),473+ group_list_type=group_list_type,
474+ group_type=0, # the axis to group
475+ group_list=group_list,
476+ output_dtype=torch.int32)[0]
477+ 
478+ hidden_states, swiglu_out_scale = torch_npu.npu_dequant_swiglu_quant(
479+ x=hidden_states,
475 weight_scale=layer.gate_up_weight_scale,480 weight_scale=layer.gate_up_weight_scale,
476- x_scale=pertoken_scale)481+ activation_scale=pertoken_scale,
482+ bias=None,
483+ quant_scale=None,
484+ quant_offset=None,
485+ group_index=group_list,
486+ activate_left=True, # whether to left-activate
487+ quant_mode=1, # 0: static quant, 1: dynamic quant
488+ )
477 489 
478 hidden_states = torch_npu.npu_grouped_matmul(490 hidden_states = torch_npu.npu_grouped_matmul(
479 x=[hidden_states],491 x=[hidden_states],
@@ -492,22 +504,18 @@ class W8A8PerTokenFusedMoEMethod(FusedMoEMethodBase):
492 def process_weights_after_loading(self, layer):504 def process_weights_after_loading(self, layer):
493 layer.gate_up_weight.data = layer.gate_up_weight.data.transpose(-2, -1).contiguous()505 layer.gate_up_weight.data = layer.gate_up_weight.data.transpose(-2, -1).contiguous()
494 layer.down_weight.data = layer.down_weight.data.transpose(-2, -1).contiguous()506 layer.down_weight.data = layer.down_weight.data.transpose(-2, -1).contiguous()
495- layer.gate_up_weight_scale.data = layer.gate_up_weight_scale.data.squeeze(-1).to(torch.float32)507+ layer.gate_up_weight_scale.data = layer.gate_up_weight_scale.data.view(
496- layer.down_weight_scale.data = layer.down_weight_scale.data.squeeze(-1)508+ layer.gate_up_weight_scale.data.shape[0], -1).to(torch.float32)
497- 509+ layer.down_weight_scale.data = layer.down_weight_scale.data.view(
498- if layer.is_fused_op_enabled:510+ layer.down_weight_scale.data.shape[0], -1)
499- layer.gate_up_weight_scale.data = layer.gate_up_weight_scale.data.view(511+ layer.register_parameter(
500- layer.gate_up_weight_scale.data.shape[0], -1)512+ "fused_gate_up_weight_scale",
501- layer.down_weight_scale.data = layer.down_weight_scale.data.view(513+ ScalerParameter(scale_from_float_to_int64(layer.gate_up_weight_scale.data))
502- layer.down_weight_scale.data.shape[0], -1)514+ )
503- layer.register_parameter(515+ layer.register_parameter(
504- "fused_gate_up_weight_scale", 516+ "fused_down_weight_scale",
505- ScalerParameter(scale_from_float_to_int64(layer.gate_up_weight_scale.data))517+ ScalerParameter(scale_from_float_to_int64(layer.down_weight_scale.data))
506- )518+ )
507- layer.register_parameter(
508- "fused_down_weight_scale",
509- ScalerParameter(scale_from_float_to_int64(layer.down_weight_scale.data))
510- )
511 519 
512 soc_name = get_npu_node_info().soc_name520 soc_name = get_npu_node_info().soc_name
513 if soc_name in SUPPORT_NZ_NPU_LIST:521 if soc_name in SUPPORT_NZ_NPU_LIST:
@@ -520,44 +528,9 @@ class W8A8PerTokenFusedMoEMethod(FusedMoEMethodBase):
520 528 
521 529 
522def scale_from_float_to_int64(scale):530def scale_from_float_to_int64(scale):
531+ """Converts float32 tensor to int64"""
523 import numpy as np532 import numpy as np
524 scale = torch.from_numpy(533 scale = torch.from_numpy(
525 np.frombuffer(scale.cpu().to(torch.float32).numpy().tobytes(),534 np.frombuffer(scale.cpu().to(torch.float32).numpy().tobytes(),
526 dtype=np.int32).astype(np.int64)).to(scale.device)535 dtype=np.int32).astype(np.int64)).to(scale.device)
527 return scale536 return scale
528- 
529- 
530-def cumsum_group_list(group_list: torch.Tensor,
531- src_list_type: int,
532- dst_list_type: int,
533- active_num: int = 0,
534- expert_num: int = 0) -> torch.Tensor:
535- if src_list_type not in [0, 1, 2]:
536- raise ValueError(
537- f"group_list_type should be in [0, 1, 2], but received {src_list_type}"
538- )
539- 
540- if src_list_type == dst_list_type:
541- return group_list
542- if src_list_type == 1 and dst_list_type == 0:
543- return group_list.cumsum(dim=0)
544- if src_list_type == 0 and dst_list_type == 1:
545- group_diff = torch.diff(group_list)
546- new_group = torch.cat([group_list[0].unsqueeze(0), group_diff], dim=0)
547- return new_group
548- if src_list_type == 2 and dst_list_type == 0:
549- experts = pad(group_list[:, 0], (1, 0))
550- tokens = pad(group_list[:, 1].cumsum(dim=0), (1, 0))
551- target_list = torch.full(size=(expert_num, ),
552- fill_value=active_num,
553- dtype=group_list.dtype,
554- device=group_list.device)
555- 
556- for i, (start, end) in enumerate(zip(experts[:-1], experts[1:])):
557- if end > start:
558- target_list[start:end] = tokens[i]
559- 
560- return target_list
561- raise NotImplementedError(
562- f"Conversion from src_list_type={src_list_type} to dst_list_type={dst_list_type} is not implemented yet. "
563- "This feature is under development.")
@@ -93,7 +93,7 @@ class Sampler:
93 def greedy_select_on_device(logits: torch.Tensor, metadata: SamplingMetadata):93 def greedy_select_on_device(logits: torch.Tensor, metadata: SamplingMetadata):
94 batch_size = len(logits)94 batch_size = len(logits)
95 token_ids = logits.argmax(dim=-1).to(torch.int64)95 token_ids = logits.argmax(dim=-1).to(torch.int64)
96- logprobs = torch.full((batch_size,), -9999.0, dtype=torch.float32)96+ logprobs = torch.full((batch_size,), -9999.0, dtype=torch.float32, device="npu")
97 top_token_ids = torch.zeros((batch_size, 0), dtype=torch.int64)97 top_token_ids = torch.zeros((batch_size, 0), dtype=torch.int64)
98 top_logprobs = torch.zeros((batch_size, 0), dtype=torch.float32)98 top_logprobs = torch.zeros((batch_size, 0), dtype=torch.float32)
99 sampling_output = SamplingOutput(99 sampling_output = SamplingOutput(
@@ -107,6 +107,7 @@ class Sampler:
107 top_logprobs=top_logprobs,107 top_logprobs=top_logprobs,
108 cumulative_logprobs=torch.zeros(batch_size, dtype=torch.float32),108 cumulative_logprobs=torch.zeros(batch_size, dtype=torch.float32),
109 num_new_tokens=torch.ones(batch_size, dtype=torch.int64, device="npu"),109 num_new_tokens=torch.ones(batch_size, dtype=torch.int64, device="npu"),
110+ num_new_tokens_numpy=np.ones(batch_size, dtype=np.int64),
110 num_top_tokens=metadata.num_top_tokens if metadata is not None else None111 num_top_tokens=metadata.num_top_tokens if metadata is not None else None
111 )112 )
112 return sampling_output113 return sampling_output
@@ -25,10 +25,10 @@ _MC2_TOKEN_CAPACITY = None
25 25 
26 26 
27def set_mc2_token_capacity(max_num_reqs, uniform_decode_query_len):27def set_mc2_token_capacity(max_num_reqs, uniform_decode_query_len):
28- max_num_tokens = min(max_num_reqs * uniform_decode_query_len, 512)28+ max_num_tokens = max_num_reqs * uniform_decode_query_len
29- tp_size = get_parallel_info_manager().get(ParallelType.MOE_TP).group_size29+ tp_size = get_parallel_info_manager().get(ParallelType.ATTN_TP).group_size
30 # Use integer arithmetic for ceiling division.30 # Use integer arithmetic for ceiling division.
31- num_tokens_per_tp_rank = (max_num_tokens + tp_size - 1) // tp_size31+ num_tokens_per_tp_rank = min((max_num_tokens + tp_size - 1) // tp_size, 512)
32 global _MC2_TOKEN_CAPACITY32 global _MC2_TOKEN_CAPACITY
33 _MC2_TOKEN_CAPACITY = num_tokens_per_tp_rank * tp_size33 _MC2_TOKEN_CAPACITY = num_tokens_per_tp_rank * tp_size
34 34 
@@ -19,6 +19,7 @@ from mindie_llm.runtime.model_runner.forward_metadata.module_metadata import Mod
19@dataclass19@dataclass
20class MtpMetadata(ModuleMetadata):20class MtpMetadata(ModuleMetadata):
21 last_hidden_states: torch.Tensor = None21 last_hidden_states: torch.Tensor = None
22+ draft_token_indices = None
22 23 
23 @staticmethod24 @staticmethod
24 def from_model_input(model_inputs):25 def from_model_input(model_inputs):
@@ -306,11 +306,6 @@ class ModelRunnerExp:
306 Logits tensor, or tuple of (logits, hidden_states) if speculative tokens enabled.306 Logits tensor, or tuple of (logits, hidden_states) if speculative tokens enabled.
307 """307 """
308 set_forward_context(forward_context)308 set_forward_context(forward_context)
309- if not self.is_draft_model:
310- torch.npu.current_stream().synchronize()
311- preprocess_done = torch.npu.Event()
312- preprocess_done.record(torch.npu.current_stream())
313- torch.npu.current_stream().wait_event(preprocess_done)
314 309 
315 hidden_states = self.model(input_ids, position_ids)310 hidden_states = self.model(input_ids, position_ids)
316 if forward_context.dp_metadata is not None:311 if forward_context.dp_metadata is not None:
@@ -327,7 +322,7 @@ class ModelRunnerExp:
327 return logits, hidden_states322 return logits, hidden_states
328 return logits323 return logits
329 324 
330- def build_forward_context(self, model_inputs: ModelInput) -> 'ForwardContext':325+ def build_forward_context(self, model_inputs: ModelInput, **kwargs) -> 'ForwardContext':
331 """Build forward context from model inputs.326 """Build forward context from model inputs.
332 327
333 Do operations like H2D to prepare for the forward function.328 Do operations like H2D to prepare for the forward function.
@@ -345,7 +340,10 @@ class ModelRunnerExp:
345 padding_tokens = forward_context.num_actual_tokens340 padding_tokens = forward_context.num_actual_tokens
346 if forward_context.dp_metadata is not None:341 if forward_context.dp_metadata is not None:
347 padding_tokens = forward_context.dp_metadata.max_tokens_across_dp_cpu342 padding_tokens = forward_context.dp_metadata.max_tokens_across_dp_cpu
348- num_tokens = self.model.get_padded_graph_size(padding_tokens)343+ if self._is_aclgraph_enabled:
344+ num_tokens = self.model.get_padded_graph_size(padding_tokens)
345+ else:
346+ num_tokens = forward_context.num_actual_tokens
349 forward_context.batch_descriptor = BatchDescriptor(num_tokens,347 forward_context.batch_descriptor = BatchDescriptor(num_tokens,
350 forward_context.batch_descriptor.is_flash_comm_enabled)348 forward_context.batch_descriptor.is_flash_comm_enabled)
351 forward_context.attn_metadata.num_tokens = num_tokens349 forward_context.attn_metadata.num_tokens = num_tokens
@@ -415,6 +413,8 @@ class ModelRunnerExp:
415 413 
416 def _init_buffer(self) -> None:414 def _init_buffer(self) -> None:
417 """Initialize input buffers for graph mode."""415 """Initialize input buffers for graph mode."""
416+ if not self._is_aclgraph_enabled:
417+ return
418 ForwardContext.register(self._max_num_token, self.device, self._mindie_llm_config.hf_config)418 ForwardContext.register(self._max_num_token, self.device, self._mindie_llm_config.hf_config)
419 input_buffer.register("input_ids", torch.zeros(self._max_num_token, dtype=torch.int32, device=self.device))419 input_buffer.register("input_ids", torch.zeros(self._max_num_token, dtype=torch.int32, device=self.device))
420 input_buffer.register("position_ids", torch.zeros(self._max_num_token, dtype=torch.int64, device=self.device))420 input_buffer.register("position_ids", torch.zeros(self._max_num_token, dtype=torch.int64, device=self.device))
@@ -337,6 +337,8 @@ class MtpWorkerExp(BaseWorkerProxy):
337 self.mapping = get_parallel_info_manager()337 self.mapping = get_parallel_info_manager()
338 self.distributed_enable = kwargs.get("distributed_enable", False)338 self.distributed_enable = kwargs.get("distributed_enable", False)
339 self.is_dp_and_server_centralized = self.mapping.has_dp() and not self.distributed_enable339 self.is_dp_and_server_centralized = self.mapping.has_dp() and not self.distributed_enable
340+ 
341+ self._offsets = torch.arange(self.num_speculative_tokens + 1, device="npu")
340 342
341 def __getattr__(self, name):343 def __getattr__(self, name):
342 return getattr(self.main_model_runner, name)344 return getattr(self.main_model_runner, name)
@@ -443,15 +445,16 @@ class MtpWorkerExp(BaseWorkerProxy):
443 return slot_list445 return slot_list
444 slot_num_per_batch = self.num_speculative_tokens * 2446 slot_num_per_batch = self.num_speculative_tokens * 2
445 used_slot_num_per_iter = self.num_speculative_tokens + 1447 used_slot_num_per_iter = self.num_speculative_tokens + 1
446- offsets = torch.arange(used_slot_num_per_iter).npu()
447 shift_num = (slot_input.size(0) - used_slot_num_per_iter) // slot_num_per_batch + 1448 shift_num = (slot_input.size(0) - used_slot_num_per_iter) // slot_num_per_batch + 1
448- shift_values = torch.arange(shift_num).npu()449+ shift_values = torch.arange(shift_num, device="npu")
449 for mtp_idx in range(self.num_speculative_tokens):450 for mtp_idx in range(self.num_speculative_tokens):
450 if slot_input.size(0) > 1:451 if slot_input.size(0) > 1:
451 starts = mtp_idx + shift_values * slot_num_per_batch452 starts = mtp_idx + shift_values * slot_num_per_batch
452- valid_starts = starts[starts + used_slot_num_per_iter <= slot_input.size(0)]453+ indices = starts.view(-1, 1) + self._offsets
453- indices = valid_starts.view(-1, 1) + offsets454+ mask = (indices >= 0) & (indices < slot_input.size(0))
454- slot_new = slot_input[indices].flatten()455+ safe_indices = indices.clamp(0, slot_input.size(0) - 1)
456+ slot_all = slot_input[safe_indices]
457+ slot_new = (slot_all * mask.to(slot_all.dtype)).flatten()
455 slot_list.append(slot_new)458 slot_list.append(slot_new)
456 else:459 else:
457 slot_list.append(slot_input)460 slot_list.append(slot_input)
@@ -479,9 +482,7 @@ class MtpWorkerExp(BaseWorkerProxy):
479 forward_context.attn_metadata.block_tables = sub_model_inputs.block_tables482 forward_context.attn_metadata.block_tables = sub_model_inputs.block_tables
480 forward_context.mtp_metadata.last_hidden_states = hidden_states_mtp_input483 forward_context.mtp_metadata.last_hidden_states = hidden_states_mtp_input
481 forward_context.lm_head_indices = lm_head_indices484 forward_context.lm_head_indices = lm_head_indices
482- forward_context.attn_metadata.actual_seq_lengths_kv = q_lens485+ padding_tokens = num_actual_tokens = input_ids_mtp.shape[0]
483- forward_context.attn_metadata.actual_seq_lengths_query = \
484- torch.cumsum(forward_context.attn_metadata.actual_seq_lengths_kv, dim=0, dtype=torch.int32)
485 # draft forward486 # draft forward
486 for mtp_idx in range(self.num_speculative_tokens):487 for mtp_idx in range(self.num_speculative_tokens):
487 slot_mapping = slot_list[mtp_idx]488 slot_mapping = slot_list[mtp_idx]
@@ -493,7 +494,6 @@ class MtpWorkerExp(BaseWorkerProxy):
493 npu_cache, input_ids_mtp, positions_mtp, forward_context, **draft_kwargs)494 npu_cache, input_ids_mtp, positions_mtp, forward_context, **draft_kwargs)
494 else:495 else:
495 # If an input is not changed, we can skip padding.496 # If an input is not changed, we can skip padding.
496- padding_tokens = num_actual_tokens = input_ids_mtp.shape[0]
497 if forward_context.dp_metadata is not None:497 if forward_context.dp_metadata is not None:
498 padding_tokens = forward_context.dp_metadata.num_tokens_across_dp_cpu.max().item()498 padding_tokens = forward_context.dp_metadata.num_tokens_across_dp_cpu.max().item()
499 num_tokens = self.draft_model_runner.model.get_padded_graph_size(padding_tokens)499 num_tokens = self.draft_model_runner.model.get_padded_graph_size(padding_tokens)
@@ -555,7 +555,7 @@ class MtpWorkerExp(BaseWorkerProxy):
555 def update_model_input_ids_by_draft(self, 555 def update_model_input_ids_by_draft(self,
556 input_ids, 556 input_ids,
557 input_ids_draft_out, 557 input_ids_draft_out,
558- kwargs) -> tuple[torch.Tensor, torch.Tensor]:558+ forward_context) -> tuple[torch.Tensor, torch.Tensor]:
559 """559 """
560 Update the input token id of main model by draft model output.560 Update the input token id of main model by draft model output.
561 561 
@@ -571,20 +571,17 @@ class MtpWorkerExp(BaseWorkerProxy):
571 # 'draft_token_indices' indicates that the index of token for current dp group.571 # 'draft_token_indices' indicates that the index of token for current dp group.
572 # This flag will be removed after support for deploying DP-in-and-DP-out is implemented.572 # This flag will be removed after support for deploying DP-in-and-DP-out is implemented.
573 if self.is_dp_and_server_centralized:573 if self.is_dp_and_server_centralized:
574- dp_rank_ids = kwargs.get('dp_rank_ids')574+ if forward_context.mtp_metadata.draft_token_indices.shape[0] == 0:
575- draft_token_indices = torch.where(dp_rank_ids == self.mapping.attn_dp.rank)[0]
576- if draft_token_indices.numel() == 0:
577- # It indicates the current device is running Dummy Data.
578 return input_ids, input_ids_draft_out575 return input_ids, input_ids_draft_out
579- else:576+
580- draft_token_indices = None
581 input_ids_reshaped = input_ids.view(-1, self.num_speculative_tokens + 1)577 input_ids_reshaped = input_ids.view(-1, self.num_speculative_tokens + 1)
582 input_ids_draft_out_reshaped = input_ids_draft_out.view(-1, self.num_speculative_tokens)578 input_ids_draft_out_reshaped = input_ids_draft_out.view(-1, self.num_speculative_tokens)
583- if draft_token_indices is not None:579+ if forward_context.mtp_metadata.draft_token_indices is not None:
584- input_ids_draft_out_reshaped = input_ids_draft_out_reshaped[draft_token_indices]580+ input_ids_draft_out_reshaped = \
581+ input_ids_draft_out_reshaped[forward_context.mtp_metadata.draft_token_indices]
585 input_ids_reshaped[:, 1:] = input_ids_draft_out_reshaped582 input_ids_reshaped[:, 1:] = input_ids_draft_out_reshaped
586 input_ids = input_ids_reshaped.flatten()583 input_ids = input_ids_reshaped.flatten()
587- return input_ids, input_ids_draft_out 584+ return input_ids, input_ids_draft_out
588 585 
589 def forward_mtp_decode(586 def forward_mtp_decode(
590 self,587 self,
@@ -613,7 +610,7 @@ class MtpWorkerExp(BaseWorkerProxy):
613 610
614 # update args and main model forward611 # update args and main model forward
615 input_ids, input_ids_draft_out = \612 input_ids, input_ids_draft_out = \
616- self.update_model_input_ids_by_draft(input_ids, input_ids_draft_out, kwargs)613+ self.update_model_input_ids_by_draft(input_ids, input_ids_draft_out, forward_context)
617 614 
618 logits, hidden_states = self.main_model_runner.forward(615 logits, hidden_states = self.main_model_runner.forward(
619 npu_cache, input_ids, position_ids, forward_context, **kwargs)616 npu_cache, input_ids, position_ids, forward_context, **kwargs)
@@ -635,7 +632,7 @@ class MtpWorkerExp(BaseWorkerProxy):
635 else:632 else:
636 return self.forward_mtp_decode(npu_cache, input_ids, position_ids, forward_context, **kwargs)633 return self.forward_mtp_decode(npu_cache, input_ids, position_ids, forward_context, **kwargs)
637 634 
638- def build_forward_context(self, model_inputs):635+ def build_forward_context(self, model_inputs, **kwargs):
639 '''636 '''
640 Do operations like H2D to prepare for the forward function.637 Do operations like H2D to prepare for the forward function.
641 It will be used by eager mode and aclgraph mode638 It will be used by eager mode and aclgraph mode
@@ -643,6 +640,12 @@ class MtpWorkerExp(BaseWorkerProxy):
643 forward_context = self.main_model_runner.build_forward_context(model_inputs)640 forward_context = self.main_model_runner.build_forward_context(model_inputs)
644 sub_forward_context = self.draft_model_runner.build_forward_context(model_inputs)641 sub_forward_context = self.draft_model_runner.build_forward_context(model_inputs)
645 forward_context.sub_forward_context = sub_forward_context642 forward_context.sub_forward_context = sub_forward_context
643+ if self.is_dp_and_server_centralized:
644+ dp_rank_ids = kwargs.get('dp_rank_ids')
645+ forward_context.mtp_metadata.draft_token_indices = \
646+ torch.where(dp_rank_ids == self.mapping.attn_dp.rank)[0]
647+ else:
648+ forward_context.mtp_metadata.draft_token_indices = None
646 return forward_context649 return forward_context
647 650 
648 651 
@@ -23,7 +23,6 @@ from mindie_llm.runtime.layers.fused_moe.experts_selector import select_experts
23from mindie_llm.runtime.layers.fused_moe.fused_moe import FusedMoE, assign_experts23from mindie_llm.runtime.layers.fused_moe.fused_moe import FusedMoE, assign_experts
24from mindie_llm.runtime.model_runner.forward_context import get_forward_context24from mindie_llm.runtime.model_runner.forward_context import get_forward_context
25from mindie_llm.runtime.utils.distributed import get_parallel_info_manager25from mindie_llm.runtime.utils.distributed import get_parallel_info_manager
26-from mindie_llm.runtime.utils.npu.device_utils import DeviceType, get_npu_node_info
27from mindie_llm.runtime.layers.attention import get_global_attn_dict26from mindie_llm.runtime.layers.attention import get_global_attn_dict
28from mindie_llm.runtime.layers.parameter import BaseParameter27from mindie_llm.runtime.layers.parameter import BaseParameter
29 28 
@@ -131,8 +130,6 @@ class DeepseekV3Moe(nn.Module):
131 self.expert_num = config.n_routed_experts130 self.expert_num = config.n_routed_experts
132 self.expert_list = assign_experts(config.n_routed_experts,131 self.expert_list = assign_experts(config.n_routed_experts,
133 parallel_info.moe_ep.group_size)[parallel_info.moe_ep.rank]132 parallel_info.moe_ep.group_size)[parallel_info.moe_ep.rank]
134- ascend_device_type = get_npu_node_info().get_device_type()
135- self.is_fused_op_enabled = True if ascend_device_type in {DeviceType.ASCEND_910_93} else False
136 self.experts = FusedMoE(133 self.experts = FusedMoE(
137 num_experts=config.n_routed_experts,134 num_experts=config.n_routed_experts,
138 topk_num=self.topk_num,135 topk_num=self.topk_num,
@@ -140,8 +137,7 @@ class DeepseekV3Moe(nn.Module):
140 intermediate_size=config.moe_intermediate_size,137 intermediate_size=config.moe_intermediate_size,
141 quant_config=quant_config,138 quant_config=quant_config,
142 prefix=f"{prefix}.experts",139 prefix=f"{prefix}.experts",
143- suffix=["gate_proj", "down_proj", "up_proj"],140+ suffix=["gate_proj", "down_proj", "up_proj"],)
144- is_fused_op_enabled=self.is_fused_op_enabled)
145 141 
146 self.gate = ReplicatedLinear(142 self.gate = ReplicatedLinear(
147 config.hidden_size,143 config.hidden_size,
@@ -38,7 +38,7 @@ class ParallelType(str, Enum):
38 LM_HEAD_TP = "lm_head_tp"38 LM_HEAD_TP = "lm_head_tp"
39 MOE_TP = "moe_tp"39 MOE_TP = "moe_tp"
40 MOE_EP = "moe_ep"40 MOE_EP = "moe_ep"
41- # DP_ALL_REDUCE = "dp_all_reduce"41+ MOE_EP_MC2 = "moe_ep_mc2"
42 42 
43 43 
44@dataclass44@dataclass
@@ -147,7 +147,8 @@ class ParallelInfoManager:
147 ParallelType.ATTN_INNER_SP: self.attn_inner_sp,147 ParallelType.ATTN_INNER_SP: self.attn_inner_sp,
148 ParallelType.MOE_TP: self.moe_tp,148 ParallelType.MOE_TP: self.moe_tp,
149 ParallelType.MOE_EP: self.moe_ep,149 ParallelType.MOE_EP: self.moe_ep,
150- ParallelType.WORLD: self.world150+ ParallelType.WORLD: self.world,
151+ ParallelType.MOE_EP_MC2: self.moe_ep_mc2,
151 }152 }
152 153 
153 @staticmethod154 @staticmethod
@@ -175,7 +176,7 @@ class ParallelInfoManager:
175 return None176 return None
176 177 
177 @staticmethod178 @staticmethod
178- def _create_npu_process_group(parallel_info: ParallelInfo) -> ProcessGroup:179+ def _create_npu_process_group(parallel_info: ParallelInfo, is_all_reduce: bool = False) -> ProcessGroup:
179 """Creates an HCCL process group with custom buffer size.180 """Creates an HCCL process group with custom buffer size.
180 Need to traverse all groups to establish communication domains, 181 Need to traverse all groups to establish communication domains,
181 even if your rank is not present within them, otherwise it will 182 even if your rank is not present within them, otherwise it will
@@ -184,7 +185,11 @@ class ParallelInfoManager:
184 cur_process_group = None185 cur_process_group = None
185 for group_idx, group_ranks in enumerate(parallel_info.rank_per_group):186 for group_idx, group_ranks in enumerate(parallel_info.rank_per_group):
186 options = dist_c.ProcessGroupHCCL.Options()187 options = dist_c.ProcessGroupHCCL.Options()
187- options.hccl_config = {"hccl_buffer_size": parallel_info.buffer_size}188+ if is_all_reduce:
189+ options.hccl_config = {"hccl_buffer_size": parallel_info.buffer_size,
190+ "hccl_op_expansion_mode": 1}
191+ else:
192+ options.hccl_config = {"hccl_buffer_size": parallel_info.buffer_size}
188 process_group = dist.new_group(ranks=group_ranks, pg_options=options)193 process_group = dist.new_group(ranks=group_ranks, pg_options=options)
189 if group_idx == parallel_info.current_group_id:194 if group_idx == parallel_info.current_group_id:
190 cur_process_group = process_group195 cur_process_group = process_group
@@ -280,5 +285,5 @@ class ParallelInfoManager:
280 parallel_info.rank = parallel_info.rank_per_group[parallel_info.current_group_id].index(self.rank)285 parallel_info.rank = parallel_info.rank_per_group[parallel_info.current_group_id].index(self.rank)
281 286 
282 parallel_info.process_group = self._create_npu_process_group(parallel_info)287 parallel_info.process_group = self._create_npu_process_group(parallel_info)
283- parallel_info.preprocess_group = self._create_npu_process_group(parallel_info)288+ parallel_info.preprocess_group = self._create_npu_process_group(parallel_info, is_all_reduce=True)
284 return parallel_info289 return parallel_info
@@ -14,6 +14,7 @@ from .decoding_policy import DecodingPolicy
14from ..plugin import Plugin14from ..plugin import Plugin
15from ....utils.log.logging import logger15from ....utils.log.logging import logger
16from ....utils.tensor import backend16from ....utils.tensor import backend
17+from ....utils.env import ENV
17 18 
18 19 
19class MtpPlugin(Plugin):20class MtpPlugin(Plugin):
@@ -38,6 +39,9 @@ class MtpPlugin(Plugin):
38 self.rank = generator_backend.rank39 self.rank = generator_backend.rank
39 self.max_seq_len = kwargs.get('max_seq_len')40 self.max_seq_len = kwargs.get('max_seq_len')
40 41 
42+ self.token_range = torch.arange(self.num_speculative_tokens + 1, device="npu")
43+ self.valid_gather_indices = torch.zeros((200, 1), device="npu")
44+ 
41 def model_inputs_update(self, model_inputs, input_metadata, sampling_metadata, cache_ids, input_len_mask, **kwargs):45 def model_inputs_update(self, model_inputs, input_metadata, sampling_metadata, cache_ids, input_len_mask, **kwargs):
42 hit_mask = kwargs.get('hit_mask')46 hit_mask = kwargs.get('hit_mask')
43 model_inputs, q_len, attention_mask = (47 model_inputs, q_len, attention_mask = (
@@ -54,6 +58,56 @@ class MtpPlugin(Plugin):
54 current_dp_sequence_ids,58 current_dp_sequence_ids,
55 current_all_sequence_ids,59 current_all_sequence_ids,
56 last_all_sequence_ids60 last_all_sequence_ids
61+ ):
62+ if ENV.model_runner_exp:
63+ return self.prepare_masks_for_filling_exp(
64+ model_inputs,
65+ current_dp_sequence_ids,
66+ current_all_sequence_ids,
67+ last_all_sequence_ids)
68+ masks = {}
69+ if last_all_sequence_ids is not None:
70+ speculative_length = self.num_speculative_tokens + 1
71+ hit_mask = np.isin(current_dp_sequence_ids, last_all_sequence_ids)
72+ if hit_mask.any():
73+ hit_sequence_ids = current_dp_sequence_ids[hit_mask]
74+ hit_indices = np.where(hit_sequence_ids[:, None] == last_all_sequence_ids[None, :])[1]
75+ hit_mask_per_token = np.repeat(hit_mask, speculative_length)
76+ hit_size = len(hit_indices)
77+ hit_increments = np.repeat(
78+ np.arange(speculative_length).reshape(-1, 1), hit_size, axis=1).transpose().reshape(-1)
79+ hit_indices_per_token = np.repeat(hit_indices * speculative_length, speculative_length) + hit_increments
80+ masks['hit_mask'] = hit_mask
81+ masks['hit_mask_tensor'] = self.generator_backend.to_tensor(hit_mask)
82+ all_hit_mask = np.isin(current_all_sequence_ids, last_all_sequence_ids)
83+ all_hit_sequence_ids = current_all_sequence_ids[all_hit_mask]
84+ masks['all_hit_mask_tensor'] = self.generator_backend.to_tensor(all_hit_mask)
85+ all_hit_indices = np.where(all_hit_sequence_ids[:, None] == last_all_sequence_ids[None, :])[1]
86+ masks['all_hit_indices'] = all_hit_indices
87+ masks['hit_indices'] = hit_indices
88+ masks['hit_indices_per_token_tensor'] = self.generator_backend.to_tensor(hit_indices_per_token)
89+ masks['hit_mask_per_token_tensor'] = self.generator_backend.to_tensor(hit_mask_per_token)
90+ masks['hit_speculative_length'] = np.full(len(all_hit_indices), speculative_length)
91+ masks['hit_dp_speculative_length'] = np.full(len(hit_indices), speculative_length)
92+ hit_arange = np.arange(hit_size)
93+ masks['hit_arange_tensor'] = self.generator_backend.to_tensor(hit_arange)
94+ hit_mask_mod = self.generator_backend.to_tensor(
95+ np.arange(len(hit_mask_per_token)) % speculative_length != 0)
96+ masks['hit_mask_mod'] = hit_mask_mod
97+ hit_block_tables = model_inputs.block_tables_array[hit_mask]
98+ candidate_slots = self.infer_context.block_table_to_slots(hit_block_tables).reshape(hit_size, -1)
99+ hit_block_indices = np.repeat(hit_arange, speculative_length)
100+ masks['candidate_slots'] = self.generator_backend.to_tensor(candidate_slots)
101+ masks['hit_block_indices'] = self.generator_backend.to_tensor(hit_block_indices)
102+ masks['hit_increments'] = self.generator_backend.to_tensor(hit_increments)
103+ return masks
104+ 
105+ def prepare_masks_for_filling_exp(
106+ self,
107+ model_inputs,
108+ current_dp_sequence_ids,
109+ current_all_sequence_ids,
110+ last_all_sequence_ids
57 ):111 ):
58 masks = {}112 masks = {}
59 if last_all_sequence_ids is not None:113 if last_all_sequence_ids is not None:
@@ -70,15 +124,21 @@ class MtpPlugin(Plugin):
70 hit_indices_per_token = np.repeat(hit_indices * speculative_length, speculative_length) + hit_increments124 hit_indices_per_token = np.repeat(hit_indices * speculative_length, speculative_length) + hit_increments
71 masks['hit_mask'] = hit_mask125 masks['hit_mask'] = hit_mask
72 masks['hit_mask_tensor'] = self.generator_backend.to_tensor(hit_mask)126 masks['hit_mask_tensor'] = self.generator_backend.to_tensor(hit_mask)
127+ masks['hit_mask_local_tensor'] = torch.masked_select(torch.arange(hit_mask.shape[0], device="npu"), masks['hit_mask_tensor'])
73 masks['hit_indices'] = hit_indices128 masks['hit_indices'] = hit_indices
129+ masks['hit_indices_tensor'] = self.generator_backend.to_tensor(hit_indices)
74 masks['hit_indices_per_token_tensor'] = self.generator_backend.to_tensor(hit_indices_per_token)130 masks['hit_indices_per_token_tensor'] = self.generator_backend.to_tensor(hit_indices_per_token)
75 masks['hit_mask_per_token_tensor'] = self.generator_backend.to_tensor(hit_mask_per_token)131 masks['hit_mask_per_token_tensor'] = self.generator_backend.to_tensor(hit_mask_per_token)
132+ masks['hit_local_indices_per_token_tensor'] = torch.masked_select(torch.arange(hit_mask_per_token.shape[0], device="npu"), masks['hit_mask_per_token_tensor'])
76 masks['hit_dp_speculative_length'] = torch.tensor(np.full(len(hit_indices), speculative_length), device="npu")133 masks['hit_dp_speculative_length'] = torch.tensor(np.full(len(hit_indices), speculative_length), device="npu")
77 hit_arange = np.arange(hit_size)134 hit_arange = np.arange(hit_size)
78 masks['hit_arange_tensor'] = self.generator_backend.to_tensor(hit_arange)135 masks['hit_arange_tensor'] = self.generator_backend.to_tensor(hit_arange)
79 hit_mask_mod = self.generator_backend.to_tensor(136 hit_mask_mod = self.generator_backend.to_tensor(
80 np.arange(len(hit_mask_per_token)) % speculative_length != 0)137 np.arange(len(hit_mask_per_token)) % speculative_length != 0)
81 masks['hit_mask_mod'] = hit_mask_mod138 masks['hit_mask_mod'] = hit_mask_mod
139+ hit_mask_per_token[hit_mask_mod.cpu().numpy()] = False
140+ hit_mask_per_token_tensor = self.generator_backend.to_tensor(hit_mask_per_token)
141+ masks['hit_mask_per_token_mod_tensor'] = torch.masked_select(torch.arange(hit_mask_per_token_tensor.shape[0], device="npu"), hit_mask_per_token_tensor)
82 hit_block_tables = model_inputs.block_tables_array[hit_mask]142 hit_block_tables = model_inputs.block_tables_array[hit_mask]
83 candidate_slots = self.infer_context.block_table_to_slots(hit_block_tables).reshape(hit_size, -1)143 candidate_slots = self.infer_context.block_table_to_slots(hit_block_tables).reshape(hit_size, -1)
84 hit_block_indices = np.repeat(hit_arange, speculative_length)144 hit_block_indices = np.repeat(hit_arange, speculative_length)
@@ -86,11 +146,11 @@ class MtpPlugin(Plugin):
86 masks['hit_block_indices'] = self.generator_backend.to_tensor(hit_block_indices)146 masks['hit_block_indices'] = self.generator_backend.to_tensor(hit_block_indices)
87 masks['hit_increments'] = self.generator_backend.to_tensor(hit_increments)147 masks['hit_increments'] = self.generator_backend.to_tensor(hit_increments)
88 if all_hit_mask.any():148 if all_hit_mask.any():
89- masks['all_hit_mask'] = all_hit_mask149+ masks["all_hit_mask"] = all_hit_mask
90 masks['all_hit_mask_tensor'] = self.generator_backend.to_tensor(all_hit_mask)150 masks['all_hit_mask_tensor'] = self.generator_backend.to_tensor(all_hit_mask)
91 all_hit_sequence_ids = current_all_sequence_ids[all_hit_mask]151 all_hit_sequence_ids = current_all_sequence_ids[all_hit_mask]
92 all_hit_indices = np.where(all_hit_sequence_ids[:, None] == last_all_sequence_ids[None, :])[1]152 all_hit_indices = np.where(all_hit_sequence_ids[:, None] == last_all_sequence_ids[None, :])[1]
93- masks['all_hit_indices'] = torch.tensor(all_hit_indices)153+ masks['all_hit_indices'] = torch.tensor(all_hit_indices, device="npu")
94 masks['hit_speculative_length'] = torch.tensor(np.full(len(all_hit_indices), speculative_length), device="npu")154 masks['hit_speculative_length'] = torch.tensor(np.full(len(all_hit_indices), speculative_length), device="npu")
95 return masks155 return masks
96 156 
@@ -158,7 +218,12 @@ class MtpPlugin(Plugin):
158 block_offsets = backend.repeat_interleave(offset_start_indices, speculative_length) + hit_increments218 block_offsets = backend.repeat_interleave(offset_start_indices, speculative_length) + hit_increments
159 candidate_slots = filling_masks.get('candidate_slots')219 candidate_slots = filling_masks.get('candidate_slots')
160 hit_block_indices = filling_masks.get('hit_block_indices')220 hit_block_indices = filling_masks.get('hit_block_indices')
161- model_inputs.slots[hit_mask_per_token] = candidate_slots[hit_block_indices, block_offsets]221+ hit_local_indices_per_token_tensor = filling_masks.get('hit_local_indices_per_token_tensor')
222+ model_inputs.forward_context.attn_metadata.slot_mapping.scatter_(
223+ 0,
224+ hit_local_indices_per_token_tensor,
225+ candidate_slots[hit_block_indices, block_offsets]
226+ )
162 else:227 else:
163 model_inputs.cached_context_length[hit_mask] += hit_num_tokens_tensor228 model_inputs.cached_context_length[hit_mask] += hit_num_tokens_tensor
164 indices = np.where(hit_mask)[0]229 indices = np.where(hit_mask)[0]
@@ -180,6 +245,11 @@ class MtpPlugin(Plugin):
180 * self.infer_context.spcp_parallel_info.sp_size245 * self.infer_context.spcp_parallel_info.sp_size
181 ][self.infer_context.spcp_parallel_info.sp_rank]246 ][self.infer_context.spcp_parallel_info.sp_rank]
182 247 
248+ actual_seq_lengths_query = torch.ones_like(model_inputs.input_lengths, dtype=torch.int32)
249+ actual_seq_lengths_query = torch.cumsum(actual_seq_lengths_query, dim=0, dtype=torch.int32)
250+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_query = actual_seq_lengths_query * speculative_length
251+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_kv = model_inputs.input_lengths
252+ model_inputs.forward_context.attn_metadata.seq_lens = model_inputs.input_lengths
183 hit_mask_per_token[hit_mask_mod] = False253 hit_mask_per_token[hit_mask_mod] = False
184 model_inputs.input_ids[hit_mask_per_token] = hit_token_ids[hit_arange_tensor, hit_num_tokens_tensor - 1]254 model_inputs.input_ids[hit_mask_per_token] = hit_token_ids[hit_arange_tensor, hit_num_tokens_tensor - 1]
185 255 
@@ -200,18 +270,33 @@ class MtpPlugin(Plugin):
200 all_hit_mask = filling_masks.get('all_hit_mask')270 all_hit_mask = filling_masks.get('all_hit_mask')
201 sampling_output = model_output_wrapper.sampling_output271 sampling_output = model_output_wrapper.sampling_output
202 if all_hit_mask is not None:272 if all_hit_mask is not None:
273+ # Get the device tensor of subtrahend of lmhead indices.
274+ # The default prefill_head_indices is calculated assuming the number of tokens is speculative_length.
275+ # The true head indices equals to the default prefill_head_indices minus head_indices_subtrahend.
203 all_hit_indices = filling_masks.get('all_hit_indices')276 all_hit_indices = filling_masks.get('all_hit_indices')
204 hit_speculative_length = filling_masks.get('hit_speculative_length')277 hit_speculative_length = filling_masks.get('hit_speculative_length')
205- all_hit_num_tokens = sampling_output.num_new_tokens[all_hit_indices]278+ all_hit_num_tokens = torch.index_select(
206- all_hit_mask_tensor = filling_masks.get('all_hit_mask_tensor')279+ sampling_output.num_new_tokens,
280+ dim=0,
281+ index=all_hit_indices
282+ )
207 head_indices_subtrahend = hit_speculative_length - all_hit_num_tokens283 head_indices_subtrahend = hit_speculative_length - all_hit_num_tokens
208- mtp_model_inputs.prefill_head_indices[all_hit_mask_tensor] -= head_indices_subtrahend284+ mtp_model_inputs.prefill_head_indices.scatter_add_(
285+ dim=0,
286+ index=all_hit_indices,
287+ src=-head_indices_subtrahend.to(torch.int32)
288+ )
209 if hit_mask is not None:289 if hit_mask is not None:
210 model_output_hidden_states = model_output_wrapper.model_output.hidden_states290 model_output_hidden_states = model_output_wrapper.model_output.hidden_states
211 291 
212 # Move the hit_token_ids of mtp model to device.292 # Move the hit_token_ids of mtp model to device.
213 hit_indices = filling_masks.get('hit_indices')293 hit_indices = filling_masks.get('hit_indices')
214- hit_token_ids = sampling_output.token_ids[hit_indices]294+ hit_indices_tensor = filling_masks.get('hit_indices_tensor')
295+ hit_token_ids = torch.index_select(
296+ sampling_output.token_ids,
297+ dim=0,
298+ index=hit_indices_tensor
299+ )
215 hit_token_ids_cols = hit_token_ids.shape[1]300 hit_token_ids_cols = hit_token_ids.shape[1]
216 if hit_token_ids_cols < speculative_length:301 if hit_token_ids_cols < speculative_length:
217 hit_token_ids = backend.nn.functional.pad(302 hit_token_ids = backend.nn.functional.pad(
@@ -222,38 +307,67 @@ class MtpPlugin(Plugin):
222 hit_token_ids = hit_token_ids[:, :speculative_length]307 hit_token_ids = hit_token_ids[:, :speculative_length]
223 308 
224 # Move the hit_num_tokens and hit_num_tokens_per_token to device.309 # Move the hit_num_tokens and hit_num_tokens_per_token to device.
225- hit_num_tokens_tensor = sampling_output.num_new_tokens[hit_indices]310+ hit_num_tokens_tensor = torch.index_select(
311+ sampling_output.num_new_tokens,
312+ dim=0,
313+ index=hit_indices_tensor
314+ )
226 hit_num_tokens_per_token = backend.repeat_interleave(hit_num_tokens_tensor, speculative_length)315 hit_num_tokens_per_token = backend.repeat_interleave(hit_num_tokens_tensor, speculative_length)
316+ hit_num_tokens_numpy = sampling_output.num_new_tokens_numpy[hit_indices]
227 317 
228 # Get masks.318 # Get masks.
229- hit_mask_tensor = filling_masks.get('hit_mask_tensor')
230- hit_mask_per_token = filling_masks.get('hit_mask_per_token_tensor')
231 hit_indices_per_token = filling_masks.get('hit_indices_per_token_tensor')319 hit_indices_per_token = filling_masks.get('hit_indices_per_token_tensor')
320+ hit_mask_per_token = filling_masks.get('hit_mask_per_token_tensor')
232 hit_arange_tensor = filling_masks.get('hit_arange_tensor')321 hit_arange_tensor = filling_masks.get('hit_arange_tensor')
233- hit_mask_mod = filling_masks.get('hit_mask_mod')322+ hit_mask_local_tensor = filling_masks.get('hit_mask_local_tensor')
234- 323+ hit_mask_tensor = filling_masks.get('hit_mask_tensor')
324+ hit_local_indices_per_token_tensor = filling_masks.get('hit_local_indices_per_token_tensor')
235 # assignation325 # assignation
236- hit_token_ids_tensor = hit_token_ids.reshape(-1)326+ hit_token_ids_flatten = hit_token_ids.flatten()
237- mtp_model_inputs.input_ids[hit_mask_per_token] = hit_token_ids_tensor327+ mtp_model_inputs.input_ids.scatter_(
238- hit_hidden_states = model_output_hidden_states[hit_indices_per_token]328+ dim=0,
239- hidden_states[hit_mask_per_token] = hit_hidden_states329+ index=hit_local_indices_per_token_tensor,
330+ src=hit_token_ids_flatten
331+ )
332+ 
333+ if model_output_wrapper.input_metadata.is_prefill:
334+ model_output_hidden_states = model_output_hidden_states.repeat(self.num_speculative_tokens + 1, 1)
335+ hidden_states = hidden_states.repeat(self.num_speculative_tokens + 1, 1)
336+ hidden_dim = hidden_states.shape[-1]
337+ hit_hidden_states = backend.index_select(
338+ model_output_hidden_states, 0, hit_indices_per_token
339+ )
340+ scatter_index = hit_local_indices_per_token_tensor.unsqueeze(-1).expand(-1, hidden_dim)
341+ hidden_states.scatter_(0, scatter_index, hit_hidden_states)
342+ model_kwargs['hidden_states'] = hidden_states
343+ 
344+ # if lm_head_local_dp is not None and not input_metadata.is_dummy_batch:
240 if lm_head_local_dp is not None and not (len(lm_head_local_dp) == 1 and lm_head_local_dp[0] == 0):345 if lm_head_local_dp is not None and not (len(lm_head_local_dp) == 1 and lm_head_local_dp[0] == 0):
241 hit_dp_speculative_length = filling_masks.get('hit_dp_speculative_length')346 hit_dp_speculative_length = filling_masks.get('hit_dp_speculative_length')
242- lm_head_local_dp[hit_mask] -= \347+ lm_head_local_dp.scatter_add_(
243- hit_dp_speculative_length - hit_num_tokens_tensor348+ dim=0,
349+ index=hit_mask_local_tensor,
350+ src=hit_num_tokens_tensor - hit_dp_speculative_length
351+ )
244 352 
245- model_inputs.position_ids[hit_mask_per_token] += hit_num_tokens_per_token353+ model_inputs.position_ids.scatter_add_(0, hit_local_indices_per_token_tensor, hit_num_tokens_per_token)
246- model_inputs.context_length[hit_mask] += hit_num_tokens_tensor.cpu().numpy()354+ model_inputs.context_length[hit_mask] += hit_num_tokens_numpy
247- model_inputs.input_lengths[hit_mask_tensor] += hit_num_tokens_tensor
248 model_inputs.max_seq_len = max(model_inputs.context_length)355 model_inputs.max_seq_len = max(model_inputs.context_length)
356+ model_inputs.input_lengths.scatter_add_(0, hit_mask_local_tensor, hit_num_tokens_tensor.to(torch.int32))
249 model_inputs.forward_context.attn_metadata.max_seq_len = model_inputs.max_seq_len357 model_inputs.forward_context.attn_metadata.max_seq_len = model_inputs.max_seq_len
250 if self.infer_context.spcp_parallel_info.scp_size == 1:358 if self.infer_context.spcp_parallel_info.scp_size == 1:
251 offset_start_indices = model_inputs.input_lengths[hit_mask_tensor] - speculative_length359 offset_start_indices = model_inputs.input_lengths[hit_mask_tensor] - speculative_length
360+ offset_start_indices = torch.index_select(model_inputs.input_lengths, 0, hit_mask_local_tensor) - speculative_length
252 hit_increments = filling_masks.get('hit_increments')361 hit_increments = filling_masks.get('hit_increments')
253 block_offsets = backend.repeat_interleave(offset_start_indices, speculative_length) + hit_increments362 block_offsets = backend.repeat_interleave(offset_start_indices, speculative_length) + hit_increments
254 candidate_slots = filling_masks.get('candidate_slots')363 candidate_slots = filling_masks.get('candidate_slots')
255 hit_block_indices = filling_masks.get('hit_block_indices')364 hit_block_indices = filling_masks.get('hit_block_indices')
256- model_inputs.slots[hit_mask_per_token] = candidate_slots[hit_block_indices, block_offsets]365+ hit_local_indices_per_token_tensor = filling_masks.get('hit_local_indices_per_token_tensor')
366+ model_inputs.forward_context.attn_metadata.slot_mapping.scatter_(
367+ 0,
368+ hit_local_indices_per_token_tensor,
369+ candidate_slots[hit_block_indices, block_offsets]
370+ )
257 else:371 else:
258 model_inputs.cached_context_length[hit_mask] += hit_num_tokens_tensor372 model_inputs.cached_context_length[hit_mask] += hit_num_tokens_tensor
259 indices = np.where(hit_mask)[0]373 indices = np.where(hit_mask)[0]
@@ -274,11 +388,21 @@ class MtpPlugin(Plugin):
274 )388 )
275 * self.infer_context.spcp_parallel_info.sp_size389 * self.infer_context.spcp_parallel_info.sp_size
276 ][self.infer_context.spcp_parallel_info.sp_rank]390 ][self.infer_context.spcp_parallel_info.sp_rank]
391+ actual_seq_lengths_query = torch.ones_like(model_inputs.input_lengths, dtype=torch.int32)
392+ actual_seq_lengths_query = torch.cumsum(actual_seq_lengths_query, dim=0, dtype=torch.int32)
393+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_query = actual_seq_lengths_query * speculative_length
394+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_kv = model_inputs.input_lengths
395+ if not model_output_wrapper.input_metadata.is_prefill:
396+ model_inputs.forward_context.sub_forward_context.attn_metadata.actual_seq_lengths_kv = model_inputs.input_lengths #- self.num_speculative_tokens + 1
397+ model_inputs.forward_context.sub_forward_context.attn_metadata.actual_seq_lengths_query = model_inputs.forward_context.attn_metadata.actual_seq_lengths_query
398+ model_inputs.forward_context.attn_metadata.seq_lens = model_inputs.input_lengths
277 399 
278- hit_mask_per_token[hit_mask_mod] = False400+ hit_mask_per_token_mod_tensor = filling_masks.get('hit_mask_per_token_mod_tensor')
279- model_inputs.input_ids[hit_mask_per_token] = hit_token_ids[hit_arange_tensor, hit_num_tokens_tensor - 1]401+ model_inputs.input_ids.scatter_(0, hit_mask_per_token_mod_tensor, hit_token_ids[hit_arange_tensor, hit_num_tokens_tensor - 1])
280 402 
281 def sample_preprocess(self, logits, result, sampling_metadata, input_metadata):403 def sample_preprocess(self, logits, result, sampling_metadata, input_metadata):
404+ if ENV.model_runner_exp:
405+ return self.sample_preprocess_exp(logits, result, sampling_metadata, input_metadata)
282 self.sampling_param = sampling_metadata406 self.sampling_param = sampling_metadata
283 self.decoding_policy.sampling_param = self.sampling_param407 self.decoding_policy.sampling_param = self.sampling_param
284 self.input_metadata = input_metadata408 self.input_metadata = input_metadata
@@ -303,6 +427,27 @@ class MtpPlugin(Plugin):
303 427 
304 return logits428 return logits
305 429 
430+ def sample_preprocess_exp(self, logits, result, sampling_metadata, input_metadata):
431+ self.input_metadata = input_metadata
432+ self.sampling_param = sampling_metadata
433+ self.decoding_policy.sampling_param = self.sampling_param
434+ if isinstance(result, tuple):
435+ logits = result[0]
436+ else:
437+ logits = result
438+ if sampling_metadata is None or sampling_metadata.is_prefill:
439+ return logits
440+ return logits
441+ 
442+ def compose_model_inputs_exp(self, sampling_metadata):
443+ all_sequence_ids = sampling_metadata.all_sequence_ids
444+ batch_size = len(all_sequence_ids)
445+ logits_num_per_batch = [self.num_speculative_tokens + 1] * batch_size
446+ req_id_new = np.concatenate([[req_id] * n for req_id, n in zip(all_sequence_ids, logits_num_per_batch)])
447+ sampling_metadata.all_sequence_ids = req_id_new
448+ sampling_metadata.parent_sequence_ids = req_id_new
449+ return sampling_metadata
450+ 
306 def plugin_verify(self, sampling_output, cache_ids, result):451 def plugin_verify(self, sampling_output, cache_ids, result):
307 sampling_output.repeating_indices = np.arange(len(cache_ids))452 sampling_output.repeating_indices = np.arange(len(cache_ids))
308 if self.input_metadata.is_prefill:453 if self.input_metadata.is_prefill:
@@ -332,80 +477,53 @@ class MtpPlugin(Plugin):
332 self.decoding_policy.stop_criteria(sampling_output, output_space_left, next_tokens_indices)477 self.decoding_policy.stop_criteria(sampling_output, output_space_left, next_tokens_indices)
333 self.reshape_speculative_outputs(sampling_output, next_tokens_indices)478 self.reshape_speculative_outputs(sampling_output, next_tokens_indices)
334 479 
335- def plugin_verify_exp(self, sampling_output, cache_ids, result):480+ def plugin_verify_exp(self, sampling_output, cache_ids, result):
336 sampling_output.repeating_indices = np.arange(len(cache_ids))481 sampling_output.repeating_indices = np.arange(len(cache_ids))
337 if self.input_metadata.is_prefill:482 if self.input_metadata.is_prefill:
338 return483 return
339- draft_token = result[2].cpu()484+ draft_token = result[2]
340 next_tokens_uncheck = sampling_output.token_ids485 next_tokens_uncheck = sampling_output.token_ids
341 input_metadata = self.input_metadata486 input_metadata = self.input_metadata
342- next_tokens_indices = []
343 out_seq_len = 1 if input_metadata.is_prefill else (self.num_speculative_tokens + 1)487 out_seq_len = 1 if input_metadata.is_prefill else (self.num_speculative_tokens + 1)
344- start_pos = 0
345- draft_token_num_per_batch = self.num_speculative_tokens
346- for batch in range(input_metadata.batch_size):
347- end = start_pos + out_seq_len
348- next_guess_by_batch = next_tokens_uncheck[start_pos:end].cpu()
349 488 
350- verify_guess_tokens = \489+ batch_size = next_tokens_uncheck.shape[0] // out_seq_len
351- draft_token[batch * draft_token_num_per_batch: (batch + 1) * draft_token_num_per_batch].view(-1)490+ next_guess_all = next_tokens_uncheck.view(batch_size, out_seq_len)
491+ verify_guess_all = draft_token.view(batch_size, self.num_speculative_tokens)
492+ matches = (verify_guess_all == next_guess_all[:, :self.num_speculative_tokens])
493+ indices_counts = torch.cumprod(matches.to(torch.int32), dim=1).sum(dim=1) + 1
352 494 
353- indices = self.decoding_policy.verify_greedy_one_batch(verify_guess_tokens, next_guess_by_batch)495+ max_num_tokens = out_seq_len
354- next_tokens_indices.append(list(range(start_pos, start_pos + indices + 1)))496+ token_range = self.token_range[:max_num_tokens].unsqueeze(0).expand(batch_size, -1)
355- start_pos += out_seq_len497+ mask = token_range < indices_counts.unsqueeze(1)
356- output_token_len = self.infer_context.get_output_len_count(cache_ids)498+ batch_offsets_cpu = torch.arange(batch_size) * out_seq_len
357- output_space_left1 = self.input_metadata.batch_max_output_lens - output_token_len499+ raw_token_ids = sampling_output.token_ids.view(batch_size, out_seq_len)
358- output_space_left2 = self.max_seq_len - \500+ raw_logprobs = sampling_output.logprobs.view(batch_size, out_seq_len)
359- self.infer_context.get_seq_lens(cache_ids)
360- output_space_left = np.minimum(output_space_left1, output_space_left2)
361- self.decoding_policy.stop_criteria(sampling_output, output_space_left, next_tokens_indices)
362- self.reshape_speculative_outputs_exp(sampling_output, next_tokens_indices)
363 501 
364- def reshape_speculative_outputs_exp(502+ sampling_output.token_ids = torch.where(mask, raw_token_ids, self.pad_token_id)
365- self,503+ sampling_output.logprobs = torch.where(mask, raw_logprobs, -9999.0)
366- sampling_output,504+ if sampling_output.top_token_ids.shape[-1] > 0:
367- next_tokens_indices,505+ k_dim = sampling_output.top_token_ids.shape[-1]
368- num_new_tokens=None506+ raw_top_tokens = sampling_output.top_token_ids.view(batch_size, out_seq_len, k_dim)
369- ) -> None:507+ raw_top_probs = sampling_output.top_logprobs.view(batch_size, out_seq_len, k_dim)
370- num_new_tokens = [len(indices) for indices in next_tokens_indices]508+ 
371- max_num_tokens = max(num_new_tokens)509+ full_mask_k = mask.unsqueeze(-1)
372- sequence_ids = []510+ sampling_output.top_token_ids = torch.where(full_mask_k, raw_top_tokens, self.pad_token_id)
373- parent_sequence_ids = []511+ sampling_output.top_logprobs = torch.where(full_mask_k, raw_top_probs, -9999.0)
374- group_indices = []512+ else:
375- token_ids = []513+ sampling_output.top_token_ids = torch.zeros((batch_size, max_num_tokens, 0),
376- logprobs = []514+ dtype=torch.int64, device="npu")
377- top_token_ids = []515+ sampling_output.top_logprobs = torch.zeros((batch_size, max_num_tokens, 0),
378- top_logprobs = []516+ dtype=torch.float32, device="npu")
379- for i, indices in enumerate(next_tokens_indices):517+ 
380- if sampling_output.sequence_ids is not None:518+ sampling_output.num_new_tokens = indices_counts
381- sequence_ids.append(sampling_output.sequence_ids[indices[0]])519+ if sampling_output.sequence_ids is not None:
382- parent_sequence_ids.append(sampling_output.parent_sequence_ids[indices[0]])520+ seq_ids_npu = torch.as_tensor(sampling_output.sequence_ids)
383- group_indices.append((i, i + 1))521+ parent_ids_npu = torch.as_tensor(sampling_output.parent_sequence_ids)
384- padded_array = backend.nn.functional.pad(sampling_output.token_ids[indices],522+
385- pad=(0, max_num_tokens - len(indices)), mode='constant', value=self.pad_token_id)523+ sampling_output.sequence_ids = seq_ids_npu[batch_offsets_cpu].reshape(batch_size).numpy()
386- token_ids.append(padded_array)524+ sampling_output.parent_sequence_ids = parent_ids_npu[batch_offsets_cpu].reshape(batch_size).numpy()
387- padded_array = backend.nn.functional.pad(sampling_output.logprobs[indices],525+ 
388- pad=(0, max_num_tokens - len(indices)), mode='constant', value=-9999.0)526+ sampling_output.group_indices = [(i, i + 1) for i in range(batch_size)]
389- logprobs.append(padded_array)
390- if 0 not in sampling_output.top_token_ids.shape:
391- padded_array = np.pad(sampling_output.top_token_ids[indices],
392- pad_width=((0, max_num_tokens - len(indices)), (0, 0)),
393- constant_values=self.pad_token_id)
394- top_token_ids.append(padded_array)
395- padded_array = np.pad(sampling_output.top_logprobs[indices],
396- pad_width=((0, max_num_tokens - len(indices)), (0, 0)), constant_values=-9999.0)
397- top_logprobs.append(padded_array)
398- else:
399- top_token_ids.append(np.zeros((max_num_tokens, 0), dtype=np.int64))
400- top_logprobs.append(np.zeros((max_num_tokens, 0), dtype=np.float32))
401- sampling_output.sequence_ids = np.array(sequence_ids)
402- sampling_output.parent_sequence_ids = np.array(parent_sequence_ids)
403- sampling_output.group_indices = group_indices
404- sampling_output.token_ids = backend.stack(token_ids).to(backend.int64)
405- sampling_output.logprobs = backend.stack(logprobs)
406- sampling_output.top_token_ids = backend.from_numpy(np.array(top_token_ids))
407- sampling_output.top_logprobs = backend.from_numpy(np.array(top_logprobs))
408- sampling_output.num_new_tokens = backend.tensor(num_new_tokens, device="npu")
409 527 
410 def plugin_cache_update(self, cache_ids, sampling_output, la_cache_input, is_prefill=False):528 def plugin_cache_update(self, cache_ids, sampling_output, la_cache_input, is_prefill=False):
411 result, _ = la_cache_input529 result, _ = la_cache_input
@@ -15,6 +15,7 @@ import copy
15from typing import Iterable, Optional, Any15from typing import Iterable, Optional, Any
16from dataclasses import fields16from dataclasses import fields
17import torch17import torch
18+import torch.nn.functional as F
18import numpy as np19import numpy as np
19import numpy.typing as npt 20import numpy.typing as npt
20from mindie_llm.runtime.layers.attention.backend.sparse_attention import main_stream21from mindie_llm.runtime.layers.attention.backend.sparse_attention import main_stream
@@ -86,11 +87,14 @@ class PluginManager:
86 def unsqueeze_sampling_output(sampling_output: SamplingOutput):87 def unsqueeze_sampling_output(sampling_output: SamplingOutput):
87 if ENV.model_runner_exp and ENV.async_inference:88 if ENV.model_runner_exp and ENV.async_inference:
88 sampling_output.token_ids = torch.unsqueeze(sampling_output.token_ids, 1)89 sampling_output.token_ids = torch.unsqueeze(sampling_output.token_ids, 1)
90+ sampling_output.logprobs = torch.unsqueeze(sampling_output.logprobs, 1)
91+ sampling_output.top_token_ids = torch.unsqueeze(sampling_output.top_token_ids, 1)
92+ sampling_output.top_logprobs = torch.unsqueeze(sampling_output.top_logprobs, 1)
89 else:93 else:
90 sampling_output.token_ids = np.expand_dims(sampling_output.token_ids, 1)94 sampling_output.token_ids = np.expand_dims(sampling_output.token_ids, 1)
91- sampling_output.logprobs = np.expand_dims(sampling_output.logprobs, 1)95+ sampling_output.logprobs = np.expand_dims(sampling_output.logprobs, 1)
92- sampling_output.top_token_ids = np.expand_dims(sampling_output.top_token_ids, 1)96+ sampling_output.top_token_ids = np.expand_dims(sampling_output.top_token_ids, 1)
93- sampling_output.top_logprobs = np.expand_dims(sampling_output.top_logprobs, 1)97+ sampling_output.top_logprobs = np.expand_dims(sampling_output.top_logprobs, 1)
94 98 
95 @staticmethod99 @staticmethod
96 def _to_host(data_instance: Any):100 def _to_host(data_instance: Any):
@@ -342,6 +346,12 @@ class PluginManager:
342 else:346 else:
343 model_inputs, sampling_metadata, trace_ids = \347 model_inputs, sampling_metadata, trace_ids = \
344 self.infer_context.compose_model_inputs(input_metadata, cache_ids, warmup=False, hit_mask=hit_mask)348 self.infer_context.compose_model_inputs(input_metadata, cache_ids, warmup=False, hit_mask=hit_mask)
349+ if ENV.model_runner_exp and (sampling_metadata is not None) and (not sampling_metadata.is_prefill):
350+ for plugin in self.plugin_list:
351+ plugin_instance = getattr(self, plugin, None)
352+ method = getattr(plugin_instance, 'compose_model_inputs_exp', None)
353+ if method is not None:
354+ sampling_metadata = method(sampling_metadata)
345 res = (cache_ids, model_inputs, sampling_metadata, trace_ids)355 res = (cache_ids, model_inputs, sampling_metadata, trace_ids)
346 return res356 return res
347 357 
@@ -430,7 +440,8 @@ class PluginManager:
430 model_inputs, input_metadata, sampling_metadata, cache_ids, input_len_mask, **kwargs)440 model_inputs, input_metadata, sampling_metadata, cache_ids, input_len_mask, **kwargs)
431 (q_len, spec_mask) = input_len_mask441 (q_len, spec_mask) = input_len_mask
432 # 需要确保虚推 context_length 至少为 1,否则会导致模型内部维度计算错误 (coreDim = 0)442 # 需要确保虚推 context_length 至少为 1,否则会导致模型内部维度计算错误 (coreDim = 0)
433- if input_metadata.all_sequence_ids is not None and not input_metadata.is_prefill:443+ if (input_metadata.all_sequence_ids is not None) and \
444+ (input_metadata is not None) and (not input_metadata.is_prefill):
434 has_simulate = any(sid == SIMULATE_SEQUENCE_ID for sid in input_metadata.all_sequence_ids)445 has_simulate = any(sid == SIMULATE_SEQUENCE_ID for sid in input_metadata.all_sequence_ids)
435 if has_simulate and model_inputs.context_length[0] == 0:446 if has_simulate and model_inputs.context_length[0] == 0:
436 model_inputs.context_length[0] = 1447 model_inputs.context_length[0] = 1
@@ -670,6 +681,12 @@ class PluginManager:
670 model_inputs.context_length[hit_sequence_ids_mask] += 1681 model_inputs.context_length[hit_sequence_ids_mask] += 1
671 model_inputs.max_seq_len = max(model_inputs.context_length)682 model_inputs.max_seq_len = max(model_inputs.context_length)
672 model_inputs.forward_context.attn_metadata.max_seq_len = model_inputs.max_seq_len683 model_inputs.forward_context.attn_metadata.max_seq_len = model_inputs.max_seq_len
684+ actual_seq_lengths_query = torch.ones_like(model_inputs.input_lengths, dtype=torch.int32)
685+ actual_seq_lengths_query = F.pad(torch.cumsum(actual_seq_lengths_query, dim=0, dtype=torch.int32), (0, 0), value=0)
686+ seq_lens_q = actual_seq_lengths_query[1:] - actual_seq_lengths_query[:-1]
687+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_query = actual_seq_lengths_query
688+ model_inputs.forward_context.attn_metadata.actual_seq_lengths_kv = model_inputs.input_lengths
689+ model_inputs.forward_context.attn_metadata.seq_lens = model_inputs.input_lengths
673 690 
674 def _get_token_num_per_seq(self, input_metadata):691 def _get_token_num_per_seq(self, input_metadata):
675 # computed_blocks为None时prefixcache无命中,batch_seq_len即为q_len692 # computed_blocks为None时prefixcache无命中,batch_seq_len即为q_len
@@ -684,4 +701,4 @@ class PluginManager:
684 ).astype(np.int64)701 ).astype(np.int64)
685 cache_len_eaqual_mask = input_metadata.batch_is_prefill & (token_num_per_seq == 0)702 cache_len_eaqual_mask = input_metadata.batch_is_prefill & (token_num_per_seq == 0)
686 token_num_per_seq[cache_len_eaqual_mask] = self.generator_backend.block_size703 token_num_per_seq[cache_len_eaqual_mask] = self.generator_backend.block_size
687- return token_num_per_seq704+ return token_num_per_seq
@@ -86,15 +86,34 @@ class OutputFilter:
86 end_reason[eos_idx] = ResponseConfig.EOS86 end_reason[eos_idx] = ResponseConfig.EOS
87 return filter_ids_arr87 return filter_ids_arr
88 88 
89- def filter_by_length(self, cache_ids, batch_max_output_lens, num_new_tokens, filter_ids_arr, end_reason):89+ def filter_by_length(self, cache_ids, batch_max_output_lens, sampling_output, filter_ids_arr, end_reason):
90+ def adjust_num_new_tokens(cached_lens, num_new_tokens, max_seq_len):
91+ """
92+ 调整 num_new_tokens 的值,确保 cached_seq_lens + num_new_tokens <= max_seq_len。
93+
94+ 参数:
95+ cached_seq_lens (np.ndarray): 已缓存的序列长度数组。
96+ num_new_tokens (np.ndarray): 新的 token 数量数组。
97+ max_seq_len (int): 最大序列长度。
98+
99+ 返回:
100+ np.ndarray: 调整后的 num_new_tokens。
101+ """
102+ available_tokens = max_seq_len - cached_lens
103+ available_tokens = np.maximum(available_tokens, 0)
104+ adjusted_num_new_tokens = np.minimum(num_new_tokens, available_tokens)
105+ return adjusted_num_new_tokens
90 output_len_count = self.tg_infer_context.get_output_len_count(cache_ids)106 output_len_count = self.tg_infer_context.get_output_len_count(cache_ids)
91 cached_seq_lens = self.tg_infer_context.get_seq_lens(cache_ids)107 cached_seq_lens = self.tg_infer_context.get_seq_lens(cache_ids)
108+ sampling_output.num_new_tokens = adjust_num_new_tokens(cached_seq_lens, sampling_output.num_new_tokens, self.cache_config.max_seq_len)
109+ sampling_output.num_new_tokens = adjust_num_new_tokens(output_len_count, sampling_output.num_new_tokens, batch_max_output_lens)
110+ sampling_output.num_new_tokens = adjust_num_new_tokens(output_len_count, sampling_output.num_new_tokens, self.cache_config.max_gen_len)
92 exceed_seq_limit_idx = np.nonzero(111 exceed_seq_limit_idx = np.nonzero(
93- cached_seq_lens + num_new_tokens >= self.cache_config.max_seq_len)[0]112+ cached_seq_lens + sampling_output.num_new_tokens >= self.cache_config.max_seq_len)[0]
94 exceed_user_output_limit_idx = np.nonzero(113 exceed_user_output_limit_idx = np.nonzero(
95- output_len_count + num_new_tokens >= batch_max_output_lens)[0]114+ output_len_count + sampling_output.num_new_tokens >= batch_max_output_lens)[0]
96 exceed_global_output_limit_idx = np.nonzero(115 exceed_global_output_limit_idx = np.nonzero(
97- output_len_count + num_new_tokens >= self.cache_config.max_gen_len)[0]116+ output_len_count + sampling_output.num_new_tokens >= self.cache_config.max_gen_len)[0]
98 117 
99 if exceed_seq_limit_idx.size != 0:118 if exceed_seq_limit_idx.size != 0:
100 filter_ids_arr = np.union1d(filter_ids_arr, exceed_seq_limit_idx)119 filter_ids_arr = np.union1d(filter_ids_arr, exceed_seq_limit_idx)
@@ -168,6 +187,8 @@ class OutputFilter:
168 metadata: InputMetadata,187 metadata: InputMetadata,
169 sampling_output: SamplingOutput188 sampling_output: SamplingOutput
170 ):189 ):
190+ for eos in self.eos_token_id:
191+ sampling_output.truncate_after_eos(eos)
171 filter_ids_arr = np.array([], dtype=np.int32)192 filter_ids_arr = np.array([], dtype=np.int32)
172 if sampling_output.repeating_indices is not None:193 if sampling_output.repeating_indices is not None:
173 cache_ids = cache_ids[sampling_output.repeating_indices]194 cache_ids = cache_ids[sampling_output.repeating_indices]
@@ -184,7 +205,7 @@ class OutputFilter:
184 filter_ids_arr, truncation_indices = self.filter_by_stop(205 filter_ids_arr, truncation_indices = self.filter_by_stop(
185 cache_ids, sampling_output.token_ids, sampling_output.num_new_tokens, filter_ids_arr, end_reason)206 cache_ids, sampling_output.token_ids, sampling_output.num_new_tokens, filter_ids_arr, end_reason)
186 filter_ids_arr = self.filter_by_length(207 filter_ids_arr = self.filter_by_length(
187- cache_ids, batch_max_output_lens, sampling_output.num_new_tokens, filter_ids_arr, end_reason)208+ cache_ids, batch_max_output_lens, sampling_output, filter_ids_arr, end_reason)
188 209 
189 # 对于没有完成prefill的req,不可以清除后处理参数210 # 对于没有完成prefill的req,不可以清除后处理参数
190 if metadata.is_mix is not None:211 if metadata.is_mix is not None:
@@ -194,4 +215,4 @@ class OutputFilter:
194 215 
195 if filter_ids_arr.shape[0] == 0:216 if filter_ids_arr.shape[0] == 0:
196 filter_ids_arr = None217 filter_ids_arr = None
197- return end_reason, filter_ids_arr, truncation_indices218+ return end_reason, filter_ids_arr, truncation_indices
@@ -25,8 +25,28 @@ class SamplingOutput:
25 top_logprobs: np.ndarray25 top_logprobs: np.ndarray
26 cumulative_logprobs: np.ndarray26 cumulative_logprobs: np.ndarray
27 num_new_tokens: np.ndarray27 num_new_tokens: np.ndarray
28+ num_new_tokens_numpy: np.ndarray = None
28 num_top_tokens: np.ndarray = None29 num_top_tokens: np.ndarray = None
29 seeds: np.ndarray = None30 seeds: np.ndarray = None
30 31 
31 def to_deprecated(self):32 def to_deprecated(self):
32- return self.token_ids, self.logprobs33+ return self.token_ids, self.logprobs
34+ 
35+ def truncate_after_eos(self, eos_token_id: int):
36+ """
37+ Truncates the token_ids after the first occurrence of the EOS token in each row,
38+ and updates num_new_tokens accordingly.
39+
40+ :param eos_token_id: The ID of the EOS (End Of Sequence) token.
41+ """
42+ for i in range(self.token_ids.shape[0]):
43+ # Find the index of the first occurrence of EOS token in the row
44+ eos_index = np.where(self.token_ids[i] == eos_token_id)[0]
45+ if eos_index.size > 0:
46+ eos_index = eos_index[0]
47+ # Truncate the token_ids after the first EOS
48+ self.token_ids[i, eos_index + 1:] = 0 # Optionally set to 0 or any padding value
49+ self.num_new_tokens[i] = eos_index + 1 # Update the num_new_tokens
50+ else:
51+ # If no EOS token is found, num_new_tokens remains unchanged
52+ pass