Skip to content

Commit 1326a33

Browse files
upgrade vllm to 0.20.2 on ascend platform (#307)
<!-- Copyright 2026 FlagOS Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> ### PR Category Vendor ### Description test cases: Test | Model | Type | Result -- | -- | -- | -- Text math (7×8) | 27B | Text | ✅ 56 Text knowledge (Capital of France) | 27B | Text | ✅ Paris Text math (7×8) | 35B | Text | ✅ 56 Text knowledge (Capital of France) | 35B | Text | ✅ Paris Image description | 27B | Image | ✅ Correct (black text, white background) Image OCR | 27B | Image | ✅ "Hello" Image description | 35B | Image | ✅ Correct (black text, white background) Image OCR | 35B | Image | ✅ "Hello" Concurrent text (8 requests) | 27B | Text | ✅ 8/8 Concurrent images (8 requests) | 27B | Image | ✅ 8/8 Concurrent mixed (8 requests) | 27B | Mixed | ✅ 8/8 Concurrent text (8 requests) | 35B | Text | ✅ 8/8 Concurrent images (8 requests) | 35B | Image | ✅ 8/8 Concurrent mixed (8 requests) | 35B | Mixed | ✅ 8/8 Prime numbers | 27B | Text | ✅ 2, 3, 5, 7, 11 Prime numbers | 35B | Text | ✅ 2, 3, 5, 7, 11 Code generation | 27B | Text | ✅ Valid Python Code generation | 35B | Text | ✅ Valid Python ### Usage ``` export VLLM_PLUGINS=fl export VLLM_FL_PLATFORM=ascend vllm serve /models/Qwen3.6-35B-A3B \ --host 0.0.0.0 \ --trust-remote-code \ --max-model-len 4096 \ --enforce-eager \ --served-model-name qwen \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.8 \ --no-enable-chunked-prefill \ --no-async-scheduling \ --no-enable-prefix-caching ```
1 parent 456bf68 commit 1326a33

13 files changed

Lines changed: 1751 additions & 181 deletions

File tree

vllm_fl/dispatch/backends/vendor/ascend/ascend.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,83 @@ def attention_backend(self, use_mla: bool = False, use_sparse: bool = False) ->
146146
raise NotImplementedError("MLA with sparse attention is not implemented for Ascend yet.")
147147
return "vllm_fl.dispatch.backends.vendor.ascend.impl.attention.AscendMLABackend"
148148
return "vllm_fl.dispatch.backends.vendor.ascend.impl.attention.AscendAttentionBackend"
149+
150+
def invoke_fused_moe_triton_kernel(
151+
self,
152+
A,
153+
B,
154+
C,
155+
A_scale,
156+
B_scale,
157+
topk_weights,
158+
sorted_token_ids,
159+
expert_ids,
160+
num_tokens_post_padded,
161+
mul_routed_weight,
162+
top_k,
163+
config,
164+
compute_type=None,
165+
use_fp8_w8a8=False,
166+
use_int8_w8a8=False,
167+
use_int8_w8a16=False,
168+
use_int4_w4a16=False,
169+
per_channel_quant=False,
170+
block_shape=None,
171+
B_bias=None,
172+
):
173+
"""Ascend NPU fused MoE kernel using torch.mm.
174+
175+
Replaces the FlagGems Triton kernel which overflows the NPU's
176+
unified buffer on certain model shapes.
177+
"""
178+
from .impl.fused_moe_kernel import invoke_fused_moe_torch
179+
invoke_fused_moe_torch(
180+
A, B, C, A_scale, B_scale, topk_weights,
181+
sorted_token_ids, expert_ids, num_tokens_post_padded,
182+
mul_routed_weight, top_k, config,
183+
use_fp8_w8a8=use_fp8_w8a8,
184+
use_int8_w8a8=use_int8_w8a8,
185+
B_bias=B_bias,
186+
)
187+
188+
def moe_align_block_size(
189+
self,
190+
topk_ids,
191+
block_size,
192+
num_experts,
193+
expert_map=None,
194+
pad_sorted_ids=False,
195+
ignore_invalid_experts=False,
196+
):
197+
"""Pure-torch moe_align_block_size for Ascend NPU.
198+
199+
Replaces the FlagGems Triton kernel which causes DDR address OOB
200+
errors on Ascend NPU hardware.
201+
"""
202+
from .impl.fused_moe_kernel import moe_align_block_size_torch
203+
return moe_align_block_size_torch(
204+
topk_ids, block_size, num_experts, expert_map,
205+
pad_sorted_ids, ignore_invalid_experts,
206+
)
207+
208+
def moe_sum(self, inp, out):
209+
"""Pure-torch moe_sum: sum over top_k dimension."""
210+
# inp is (M, top_k, N), out is (M, N)
211+
# Avoid out= parameter which can cause NPU issues
212+
result = inp.sum(dim=1)
213+
out.copy_(result)
214+
215+
def topk_softmax(
216+
self, topk_weights, topk_indices, token_expert_indices, gating_output,
217+
renormalize=False,
218+
):
219+
"""Pure-torch topk_softmax for Ascend NPU."""
220+
scores = torch.softmax(gating_output.float(), dim=-1)
221+
topk = topk_weights.shape[1]
222+
tk_weights, tk_indices = torch.topk(scores, k=topk, dim=-1)
223+
topk_weights.copy_(tk_weights.to(topk_weights.dtype))
224+
topk_indices.copy_(tk_indices.to(topk_indices.dtype))
225+
if renormalize:
226+
s = topk_weights.sum(dim=-1, keepdim=True)
227+
topk_weights.div_(s.clamp(min=1e-8))
228+
return topk_weights, topk_indices

vllm_fl/dispatch/backends/vendor/ascend/impl/attention.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from vllm.config import VllmConfig, get_current_vllm_config
3838
from vllm.utils.math_utils import cdiv
3939
from vllm.v1.attention.backend import AttentionCGSupport
40+
from vllm.v1.attention.backends.registry import AttentionBackendEnum, register_backend
4041
from vllm.v1.attention.backends.utils import CommonAttentionMetadata
4142

4243
from vllm_fl.dispatch.backends.vendor.ascend.impl.attention_mask import (
@@ -215,6 +216,7 @@ class AscendAttentionMetadataBuilder:
215216
# ACL graph support - ALWAYS means full graph capture is supported
216217
aclgraph_support: ClassVar[AttentionCGSupport] = AttentionCGSupport.ALWAYS
217218
reorder_batch_threshold: ClassVar[int] = 1
219+
supports_update_block_table: bool = False
218220

219221
@staticmethod
220222
def get_cudagraph_support(vllm_config, kv_cache_spec) -> AttentionCGSupport:
@@ -433,7 +435,7 @@ class AscendAttentionBackend(AttentionBackend):
433435

434436
@staticmethod
435437
def get_name() -> str:
436-
return "ASCEND_FL"
438+
return "CUSTOM"
437439

438440
@staticmethod
439441
def get_impl_cls() -> Type["AscendAttentionBackendImpl"]:
@@ -443,6 +445,15 @@ def get_impl_cls() -> Type["AscendAttentionBackendImpl"]:
443445
def get_builder_cls() -> Type["AscendAttentionMetadataBuilder"]:
444446
return AscendAttentionMetadataBuilder
445447

448+
@staticmethod
449+
def get_supported_kernel_block_sizes() -> list[int]:
450+
# Ascend fused_infer_attention_score and paged_attention kernels
451+
# are validated for block size 128 in vllm-ascend. Allowing the
452+
# default MultipleOf(1) lets the V1 engine pick unsupported merged
453+
# storage block sizes (e.g. 784 for Qwen3.5 hybrid models), which
454+
# causes aclnnFusedInferAttentionScoreV3 to fail with error 561002.
455+
return [128]
456+
446457
@staticmethod
447458
def get_kv_cache_shape(
448459
num_blocks: int,
@@ -488,6 +499,12 @@ def get_supported_block_size() -> list[int]:
488499
return [128]
489500

490501

502+
register_backend(
503+
AttentionBackendEnum.CUSTOM,
504+
"vllm_fl.dispatch.backends.vendor.ascend.impl.attention.AscendAttentionBackend",
505+
)
506+
507+
491508
class AscendAttentionBackendImpl(AttentionImpl):
492509
"""
493510
Ascend attention implementation using native torch_npu operators.
@@ -561,20 +578,16 @@ def _get_fia_params(
561578
value = self.value_cache.view(num_block, block_size, -1)
562579
actual_seq_lengths_kv = attn_metadata.seq_lens_list
563580
elif attn_metadata.attn_state == AscendAttentionState.DecodeOnly:
564-
# num_block, block_size, _, _ = self.key_cache.shape
565-
# key = self.key_cache.view(num_block, block_size, -1)
566-
# value = self.value_cache.view(num_block, block_size, -1)
567-
key = self.key_cache.view(-1, block_size, 256)
568-
value = self.value_cache.view(-1, block_size, 256)
581+
num_block, block_size, _, _ = self.key_cache.shape
582+
key = self.key_cache.view(num_block, block_size, -1)
583+
value = self.value_cache.view(num_block, block_size, -1)
569584
block_table = attn_metadata.block_tables
570585
actual_seq_lengths_kv = attn_metadata.seq_lens_list
571586
else:
572587
# ChunkedPrefill
573-
# num_block, block_size, _, _ = self.key_cache.shape
574-
# key = self.key_cache.view(num_block, block_size, -1)
575-
# value = self.value_cache.view(num_block, block_size, -1)
576-
key = self.key_cache.view(-1, block_size, 256)
577-
value = self.value_cache.view(-1, block_size, 256)
588+
num_block, block_size, _, _ = self.key_cache.shape
589+
key = self.key_cache.view(num_block, block_size, -1)
590+
value = self.value_cache.view(num_block, block_size, -1)
578591
block_table = attn_metadata.block_tables
579592
actual_seq_lengths_kv = attn_metadata.seq_lens_list
580593

@@ -593,16 +606,16 @@ def reshape_and_cache(
593606
self.key_cache, self.value_cache = kv_cache[0], kv_cache[1]
594607
slots = attn_metadata.slot_mapping
595608
# torch_npu requires int32 for slot_indices
596-
# TODO(yxa): block_table.py: CUDA uses int64, NPU uses int32.
597609
if slots.dtype != torch.int32:
598610
slots = slots.to(torch.int32)
599-
# Use torch_npu reshape_and_cache
611+
612+
num_actual = attn_metadata.num_actual_tokens
600613
torch_npu._npu_reshape_and_cache(
601-
key=key[:attn_metadata.num_actual_tokens],
602-
value=value[:attn_metadata.num_actual_tokens],
614+
key=key[:num_actual],
615+
value=value[:num_actual],
603616
key_cache=self.key_cache,
604617
value_cache=self.value_cache,
605-
slot_indices=slots[:attn_metadata.num_actual_tokens]
618+
slot_indices=slots[:num_actual]
606619
)
607620
return key, value
608621

@@ -625,9 +638,9 @@ def forward_fused_infer_attention(
625638
key = key[:num_tokens]
626639
value = value[:num_tokens]
627640

628-
# Determine sparse_mode based on mask availability
629-
# sparse_mode=3 requires attn_mask; sparse_mode=0 does not
630-
# sparse_mode = 3 if attn_metadata.attn_mask is not None else 0
641+
# sparse_mode: 3 = causal with mask, 0 = no mask
642+
sparse_mode = 3 if attn_metadata.attn_mask is not None else 0
643+
631644
attn_output, _ = torch_npu.npu_fused_infer_attention_score(
632645
query=query,
633646
key=key,
@@ -641,7 +654,7 @@ def forward_fused_infer_attention(
641654
num_key_value_heads=self.num_kv_heads,
642655
num_heads=self.num_heads,
643656
scale=self.scale,
644-
sparse_mode=3,
657+
sparse_mode=sparse_mode,
645658
)
646659

647660
attn_output = attn_output.view(num_tokens, self.num_heads, self.head_size)
@@ -779,8 +792,9 @@ def forward(
779792
return output.fill_(0)
780793

781794
# Reshape and cache KV
782-
if attn_metadata != AscendAttentionState.DecodeOnly:
783-
kv_cache = [i.contiguous() for i in kv_cache]
795+
# Note: kv_cache[0]/[1] may be non-contiguous views of a
796+
# [2, num_blocks, ...] tensor. _npu_reshape_and_cache handles
797+
# them directly via slot_indices — no contiguous copy needed.
784798
if key is not None and value is not None:
785799
key = key.contiguous()
786800
value = value.contiguous()

vllm_fl/dispatch/backends/vendor/ascend/impl/causal_conv1d.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,97 @@ def grid(META):
713713
if unsqueeze:
714714
out = out.squeeze(-1)
715715
return out.to(original_x_dtype)
716+
717+
718+
def causal_conv1d_update_ref(
719+
x: torch.Tensor,
720+
conv_state: torch.Tensor,
721+
weight: torch.Tensor,
722+
bias: torch.Tensor | None = None,
723+
activation: bool | str | None = None,
724+
conv_state_indices: torch.Tensor | None = None,
725+
num_accepted_tokens: torch.Tensor | None = None,
726+
query_start_loc: torch.Tensor | None = None,
727+
max_query_len: int = -1,
728+
pad_slot_id: int = PAD_SLOT_ID,
729+
block_idx_last_scheduled_token: torch.Tensor | None = None,
730+
initial_state_idx: torch.Tensor | None = None,
731+
validate_data=False,
732+
):
733+
"""Pure-PyTorch causal_conv1d_update for decode (single token per seq).
734+
735+
Handles the simple case: x is [batch, dim] (single token), conv_state
736+
is [num_cache_lines, dim, width-1], and we do a sliding window update.
737+
"""
738+
if isinstance(activation, bool):
739+
activation = "silu" if activation is True else None
740+
elif activation is not None:
741+
assert activation in ["silu", "swish"]
742+
743+
original_x_dtype = x.dtype
744+
x = x.to(conv_state.dtype)
745+
unsqueeze = query_start_loc is None and x.dim() == 2
746+
if unsqueeze:
747+
x = x.unsqueeze(-1) # [batch, dim, 1]
748+
749+
if query_start_loc is not None:
750+
# varlen mode
751+
batch = conv_state_indices.size(0)
752+
dim = x.size(1)
753+
else:
754+
batch, dim, seqlen = x.shape
755+
756+
_, width = weight.shape
757+
state_len = width - 1
758+
759+
out = torch.empty_like(x)
760+
761+
if query_start_loc is None:
762+
# Simple batched mode: x is [batch, dim, seqlen]
763+
seqlen = x.shape[-1]
764+
for b_idx in range(batch):
765+
if conv_state_indices is not None:
766+
s_idx = conv_state_indices[b_idx].item()
767+
if pad_slot_id is not None and s_idx == pad_slot_id:
768+
out[b_idx] = 0
769+
continue
770+
else:
771+
s_idx = b_idx
772+
773+
state = conv_state[s_idx] # [dim, state_len]
774+
for t in range(seqlen):
775+
# Shift state left, append new token
776+
state = torch.cat([state[:, 1:], x[b_idx, :, t:t+1]], dim=-1)
777+
# Dot product with weight for each channel (depthwise conv)
778+
val = (state * weight).sum(dim=-1) # [dim]
779+
if bias is not None:
780+
val = val + bias
781+
if activation in ["silu", "swish"]:
782+
val = val * torch.sigmoid(val)
783+
out[b_idx, :, t] = val
784+
conv_state[s_idx] = state
785+
else:
786+
# Varlen mode
787+
cu_cpu = query_start_loc.cpu().tolist()
788+
for b_idx in range(batch):
789+
s_idx = conv_state_indices[b_idx].item()
790+
if pad_slot_id is not None and s_idx == pad_slot_id:
791+
continue
792+
793+
bos = cu_cpu[b_idx] if b_idx < len(cu_cpu) else cu_cpu[-1]
794+
eos = cu_cpu[b_idx + 1] if b_idx + 1 < len(cu_cpu) else cu_cpu[-1]
795+
796+
state = conv_state[s_idx] # [dim, state_len]
797+
for t in range(bos, eos):
798+
state = torch.cat([state[:, 1:], x[t:t+1, :].T], dim=-1)
799+
val = (state * weight).sum(dim=-1)
800+
if bias is not None:
801+
val = val + bias
802+
if activation in ["silu", "swish"]:
803+
val = val * torch.sigmoid(val)
804+
out[t, :] = val # varlen: out is [num_tokens, dim]
805+
conv_state[s_idx] = state
806+
807+
if unsqueeze:
808+
out = out.squeeze(-1)
809+
return out.to(original_x_dtype)

0 commit comments

Comments
 (0)