[NPU] [DLLM]DLLM LLaDA2.x graph mode support with NPU speedup modifications (#18485)

Co-authored-by: Zhang-Xiaoxue <xiaoxuezhang17@outlook.com>
Co-authored-by: dawncc <dawn.cc022@gmail.com>
Co-authored-by: lixinqi7 <li_xinqi7@163.com>
Co-authored-by: rangejay <rangejay1st@163.com>
This commit is contained in:
wenxuewuhd
2026-03-09 22:41:05 +08:00
committed by GitHub
parent d116a8cd94
commit 11b76d24dc
8 changed files with 250 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ from sgl_kernel_npu.attention.sinks_attention import (
)
from sglang.srt.configs.model_config import AttentionArch
from sglang.srt.dllm.config import DllmConfig
from sglang.srt.hardware_backend.npu.attention.ascend_torch_native_backend import (
AscendTorchNativeAttnBackend,
)
@@ -252,6 +253,13 @@ class AscendAttnBackend(AttentionBackend):
if self.use_mla:
self.ringmla_mask = self.ascend_attn_mask_builder.ringmla_mask
# dllm model config
self.dllm_config = DllmConfig.from_server_args(model_runner.server_args)
self.is_dllm_model = False
if self.dllm_config is not None:
self.is_dllm_model = True
self.dllm_block_size = self.dllm_config.block_size
def get_verify_buffers_to_fill_after_draft(self):
"""
Return buffers for verify attention kernels that needs to be filled after draft.
@@ -353,6 +361,20 @@ class AscendAttnBackend(AttentionBackend):
metadata = ForwardMetadata()
metadata.block_tables = self.graph_metadata["block_tables"][:bs, :]
if self.is_dllm_model:
max_len = int(seq_lens[:bs].max().item())
max_seq_pages = (max_len + self.page_size - 1) // self.page_size
metadata.block_tables[:bs, :max_seq_pages].copy_(
(
self.req_to_token[req_pool_indices[:bs], :max_len][
:, :: self.page_size
]
// self.page_size
).to(torch.int32)
)
metadata.block_tables[:bs, max_seq_pages:].fill_(0)
metadata.block_tables[bs:, :].fill_(0)
metadata.seq_lens_cpu_list = seq_lens.cpu().int().tolist()
metadata.seq_lens = seq_lens
if (
@@ -374,6 +396,15 @@ class AscendAttnBackend(AttentionBackend):
dtype=torch.int32,
device=seq_lens.device,
)
if forward_mode.is_dllm_extend():
extend_seq_lens_cpu_int = torch.tensor(
[self.dllm_block_size for i in range(bs)],
dtype=torch.int32,
device=seq_lens.device,
)
metadata.seq_lens_list_cumsum = (
torch.cumsum(extend_seq_lens_cpu_int, dim=0).int().tolist()
)
self.graph_metadata[bs] = metadata
self.forward_metadata = metadata
@@ -401,8 +432,10 @@ class AscendAttnBackend(AttentionBackend):
self.req_to_token[req_pool_indices[:bs], :max_len][:, :: self.page_size]
// self.page_size
)
metadata.block_tables[:bs, max_seq_pages:].fill_(0)
metadata.block_tables[bs:, :].fill_(0)
if forward_mode.is_target_verify():
seq_lens = seq_lens + self.speculative_num_draft_tokens
metadata.seq_lens[:bs].copy_(seq_lens[:bs])
@@ -729,6 +762,17 @@ class AscendAttnBackend(AttentionBackend):
if is_mla_preprocess_enabled():
# MLAPO and MLAPROLOG do save kv_cache
save_kv_cache = False
if self.is_dllm_model:
return self.forward_dllm(
q,
k,
v,
layer,
forward_batch,
save_kv_cache,
q_rope=q_rope,
k_rope=k_rope,
)
if topk_indices is not None:
return self.forward_sparse(
q,
@@ -823,7 +867,6 @@ class AscendAttnBackend(AttentionBackend):
or layer.attn_type == AttentionType.ENCODER_ONLY
):
causal = False
# there are some accuracy issues in cross attention scene to use torch_npu._npu_flash_attention_qlens
# forward_batch.encoder_lens is not None in cross attention scend, we add native attn to solve accuracy issues
# Model skywork-reward-gemma2-2-27B also suffers from precision anomalies, thus the torch native backend becomes beneficial approach.
@@ -841,7 +884,6 @@ class AscendAttnBackend(AttentionBackend):
dtype=query.dtype,
device=query.device,
)
torch_npu._npu_flash_attention_qlens(
query=query,
key_cache=k_cache,
@@ -1085,6 +1127,65 @@ class AscendAttnBackend(AttentionBackend):
return attn_output
def forward_dllm(
self,
q,
k,
v,
layer: RadixAttention,
forward_batch: ForwardBatch,
save_kv_cache: bool = True,
# For multi_head latent attention
q_rope: Optional[torch.Tensor] = None,
k_rope: Optional[torch.Tensor] = None,
topk_indices: Optional[torch.Tensor] = None,
):
if save_kv_cache:
forward_batch.token_to_kv_pool.set_kv_buffer(
layer, forward_batch.out_cache_loc, k, v
)
k_cache = forward_batch.token_to_kv_pool.get_key_buffer(layer.layer_id)
v_cache = forward_batch.token_to_kv_pool.get_value_buffer(layer.layer_id)
query = q.reshape(-1, layer.tp_q_head_num, layer.qk_head_dim)
if self.forward_metadata.seq_lens_cpu_int is None:
# capture
actual_seq_lengths_kv = self.forward_metadata.seq_lens_cpu_list
else:
# eagle
actual_seq_lengths_kv = (
self.forward_metadata.seq_lens_cpu_int.cpu().int().tolist()
)
if self.forward_metadata.extend_seq_lens_cpu_int is None:
# capture & replay
actual_seq_lengths = self.forward_metadata.seq_lens_list_cumsum
else:
actual_seq_lengths = (
torch.cumsum(self.forward_metadata.extend_seq_lens_cpu_int, dim=0)
.int()
.tolist()
)
attn_output, _ = torch.ops.npu.npu_fused_infer_attention_score(
query,
k_cache.view(-1, self.page_size, layer.tp_k_head_num * layer.qk_head_dim),
v_cache.view(-1, self.page_size, layer.tp_v_head_num * layer.v_head_dim),
block_table=self.forward_metadata.block_tables,
block_size=self.page_size,
num_heads=layer.tp_q_head_num,
num_key_value_heads=layer.tp_k_head_num,
input_layout="TND",
atten_mask=None,
scale=layer.scaling,
actual_seq_lengths=actual_seq_lengths,
actual_seq_lengths_kv=actual_seq_lengths_kv,
)
attn_output = attn_output.view(-1, layer.tp_q_head_num * layer.v_head_dim)
return attn_output
def forward_mtp(
self,
q,

View File

@@ -83,10 +83,16 @@ class NPUGraphRunner(CudaGraphRunner):
self.use_fia = get_bool_env_var("ASCEND_USE_FIA", "False")
def _init_arch_map(self):
self.attr_name: Dict[str, str] = {
AttentionArch.MLA: "actual_seq_lengths_kv",
AttentionArch.MHA: "context_lens",
}
if self.is_dllm:
self.attr_name: Dict[str, str] = {
AttentionArch.MLA: "actual_seq_lengths_kv",
AttentionArch.MHA: "actual_seq_lengths_kv",
}
else:
self.attr_name: Dict[str, str] = {
AttentionArch.MLA: "actual_seq_lengths_kv",
AttentionArch.MHA: "context_lens",
}
self.attr_type: Dict[str, Union[list, torch.Tensor]] = {
AttentionArch.MLA: [],
AttentionArch.MHA: torch.Tensor(),
@@ -188,8 +194,15 @@ class NPUGraphRunner(CudaGraphRunner):
output = self.output_buffers[self.bs]
if isinstance(output, LogitsProcessorOutput):
if self.is_dllm:
next_token_logits = None
full_logits = output.full_logits[: self.raw_num_token]
else:
full_logits = None
next_token_logits = output.next_token_logits[: self.raw_num_token]
return LogitsProcessorOutput(
next_token_logits=output.next_token_logits[: self.raw_num_token],
next_token_logits=next_token_logits,
full_logits=full_logits,
hidden_states=(
output.hidden_states[: self.raw_num_token]
if output.hidden_states is not None

View File

@@ -209,6 +209,7 @@ from sglang.srt.utils import (
set_random_seed,
suppress_other_loggers,
)
from sglang.srt.utils.common import is_npu
from sglang.srt.utils.hf_transformers_utils import (
get_processor,
get_tokenizer,
@@ -224,6 +225,8 @@ TEST_RETRACT = envs.SGLANG_TEST_RETRACT.get()
TEST_RETRACT_INTERVAL = envs.SGLANG_TEST_RETRACT_INTERVAL.get()
TEST_RETRACT_NO_PREFILL_BS = envs.SGLANG_TEST_RETRACT_NO_PREFILL_BS.get()
_is_npu = is_npu()
@dataclass
class EmbeddingBatchResult:
@@ -410,6 +413,24 @@ class Scheduler(
def init_model_config(self):
self.model_config = ModelConfig.from_server_args(self.server_args)
if _is_npu:
# make sure the page size is not larger than block_size and chunked_prefill_size on NPU backend
# the npu backend request the defined page size to be no larger than block_size and chunked_prefill_size
from sglang.srt.dllm.config import DllmConfig
self.dllm_config = ( # For diffusion LLM
DllmConfig.from_server_args(self.server_args)
if self.server_args.dllm_algorithm is not None
else None
)
if self.dllm_config:
if self.dllm_config.block_size < self.page_size:
logger.warning(
"WARNING: "
f"The page size {self.page_size} should not be larger than dllm block size {self.dllm_config.block_size}."
f"Page size now falls back to {self.dllm_config.block_size}"
)
self.page_size = self.dllm_config.block_size
def init_ipc_channels(self, port_args: PortArgs):
context = zmq.Context(2)

View File

@@ -470,7 +470,7 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
if batch.dllm_config is not None:
block_size = batch.dllm_config.block_size
# Use int64 for AMD rotary embedding kernel compatibility
positions_dtype = torch.int64 if is_hip() else torch.int32
positions_dtype = torch.int64 if is_hip() or _is_npu else torch.int32
ret.positions = torch.tensor(
[
i

View File

@@ -77,11 +77,18 @@ from sglang.srt.models.utils import (
enable_fused_set_kv_buffer,
)
from sglang.srt.server_args import get_global_server_args
from sglang.srt.utils import add_prefix, is_cuda, is_non_idle_and_non_empty, make_layers
from sglang.srt.utils import (
add_prefix,
is_cuda,
is_non_idle_and_non_empty,
is_npu,
make_layers,
)
LoraConfig = None
logger = logging.getLogger(__name__)
_is_cuda = is_cuda()
_is_npu = is_npu()
class LLaDA2MoeMLP(nn.Module):
@@ -190,6 +197,11 @@ class LLaDA2MoeSparseMoeBlock(nn.Module):
self.routed_scaling_factor = getattr(config, "routed_scaling_factor", 1.0)
self.score_function = getattr(config, "score_function", None)
# fused_topk_npu() conducting norm before scale with routed_scaling_factor by default
# norm_topk_prob=True will renorm the routed_scaling_factor thus need to keep norm_topk_prob=False
if _is_npu:
self.norm_topk_prob = False
if config.hidden_act != "silu":
raise ValueError(
f"Unsupported activation: {config.hidden_act}. "

View File

@@ -3124,6 +3124,12 @@ class ServerArgs:
"Attention backend is set to triton for diffusion LLM inference on AMD GPUs"
)
self.attention_backend = "triton"
elif is_npu():
if self.attention_backend != "ascend":
logger.warning(
"Attention backend is overridden to 'ascend' when running on NPU for diffusion LLM inference."
)
self.attention_backend = "ascend"
elif not self.disable_cuda_graph:
if self.attention_backend != "flashinfer":
logger.warning(