Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4dfb1ca
[Ascend] MTP speculative decoding support (qwen3_5_mtp_final_2)
tangzhiyi11 Apr 18, 2026
6e04b7e
[Ascend] Patch MTP graph and sampling runtime
tangzhiyi11 Apr 19, 2026
f3aa217
[Ascend] Reduce MTP replay state snapshot peak memory
tangzhiyi11 Apr 19, 2026
4661c9a
[Ascend] fix gdn kernel in speculative decoding
wanfengcxz Apr 28, 2026
5e44121
fix graph
May 10, 2026
80a8171
fix: ensure state is contiguous
wanfengcxz May 15, 2026
9615f98
refactor fill buffers
wanfengcxz May 15, 2026
aaee81f
impl ring buffer for gdn state
wanfengcxz May 15, 2026
1549019
impl ring buffer for conv1d state
wanfengcxz May 18, 2026
33ec8ad
Refactor GDN and conv1d computation flow
wanfengcxz May 18, 2026
8ce27fc
refactor gdn buffer
wanfengcxz May 18, 2026
55b332f
fix ascend graph
wanfengcxz May 19, 2026
02e9ef9
fix graph
wanfengcxz May 21, 2026
9fa7c7a
remove unused patch
wanfengcxz May 21, 2026
cfe7dd3
impl reject_sample triton function on ascend
wanfengcxz May 21, 2026
a56a6ac
fix mismatch shape error in chunked prefill
wanfengcxz May 21, 2026
6a2b036
refactor: lazy load mtp ops
wanfengcxz May 22, 2026
569a17c
update qwen35 patch func
wanfengcxz May 25, 2026
5729141
fix no mtp
wanfengcxz May 27, 2026
0550c58
refactor graph buffer and kernel
wanfengcxz May 28, 2026
f630d72
fix DPTP comm buffer size and padding_batch_size
wanfengcxz Jun 4, 2026
d39a030
update qwen35 patch
wanfengcxz Jun 4, 2026
63e5113
fix aclnnGather error 161002 by sanitizing negative indices
wanfengcxz Jun 4, 2026
9fba6fa
remove comma
wanfengcxz Jun 4, 2026
013c61c
fix padding
Jun 11, 2026
a335214
fix global decoding
Jun 22, 2026
dee74a1
fallback reject_sample to torch
Jun 25, 2026
cc845d5
refactor reject sample
wanfengcxz Jul 1, 2026
1ac406e
refactor: remove redundant if branches
wanfengcxz Jul 3, 2026
24c4656
format code
wanfengcxz Jul 3, 2026
cd6d96d
format code
wanfengcxz Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 111 additions & 37 deletions dlinfer/framework/lmdeploy_ext/cudagraph/ascend_cudagraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ def aclgraph_use_torch_npu_update():


# AscendCudaGraphMixin methods for cudagraph buffer management.
def AscendCudaGraphMixin_support_cuda_graph(
self,
input_ids: Tensor,
position_ids: Tensor,
past_key_values: List[List[Tensor]],
attn_metadata: Any = None,
inputs_embeds: Tensor = None,
**kwargs,
):
"""Allow multi-token decode graph only when runtime length updates exist."""
if attn_metadata is None:
return False

# Graph capture wraps the MoE EP dispatch/combine collective, so all DP ranks
# must enter or skip the graph together. Gate on the DP-global decode state
# (any rank prefill => all prefill) rather than this rank's local is_decoding.
if not get_step_ctx_manager().current_context().global_is_decoding():
return False
is_decoding = getattr(attn_metadata, "is_decoding", False)
is_multi_token = getattr(attn_metadata, "is_multi_token_decoding", False)
if is_multi_token and not aclgraph_use_torch_npu_update():
return False
return is_decoding or is_multi_token


def AscendCudaGraphMixin_make_buffers_cudagraph(
self, graph_meta: CudaGraphMeta, *args, **kwargs
) -> BuffType:
Expand All @@ -58,29 +83,44 @@ def AscendCudaGraphMixin_make_buffers_cudagraph(
(max_batches, num_blocks), dtype=torch.int32, device=device
)

input_buffers["q_seqlens"] = torch.ones(
max_batches, dtype=torch.int32, device=device
)

input_buffers["kv_seqlens"] = torch.ones(max_batches, dtype=torch.int32)
Comment thread
wanfengcxz marked this conversation as resolved.

input_buffers["q_start_loc"] = torch.arange(
max_batches + 1, dtype=torch.int32, device=device
)

input_buffers["kv_start_indices"] = -torch.ones(
(max_batches), dtype=torch.int32, device=device
(max_tokens), dtype=torch.int32, device=device
)

input_buffers["x_active_mask"] = torch.zeros(
(max_batches), dtype=torch.bool, device=device
(max_tokens), dtype=torch.bool, device=device
)

input_buffers["attention_mask"] = torch.triu(
torch.ones(2048, 2048, dtype=torch.bool, device=device), diagonal=1
)

# ssm
if graph_meta.is_ssm:
input_buffers["state_ids"] = torch.full(
(max_batches,), -1, dtype=torch.int64, device=device
)
input_buffers["cache_seqlens"] = torch.zeros(
max_batches, dtype=torch.int32, device=device
)

if max_batches != max_tokens:
max_q_seq_len = max_tokens // max_batches
input_buffers["q_seqlens"] = (
torch.arange(1, max_batches + 1, dtype=torch.int32) * max_q_seq_len
)
Comment thread
wanfengcxz marked this conversation as resolved.
input_buffers["q_start_loc"] = (
torch.arange(max_batches + 1, dtype=torch.int32, device=device)
* max_q_seq_len
)

else:
input_buffers["q_seqlens"] = torch.arange(1, max_batches + 1, dtype=torch.int32)
Comment thread
wanfengcxz marked this conversation as resolved.
input_buffers["q_start_loc"] = torch.arange(
max_batches + 1, dtype=torch.int32, device=device
)

# mrope
if graph_meta.use_mrope:
Expand Down Expand Up @@ -108,6 +148,9 @@ def AscendCudaGraphMixin_fill_buffers_cudagraph(
moe_metadata = get_step_ctx_manager().current_context().moe_metadata
x_active_mask: Tensor = moe_metadata.x_active_mask
q_start_loc: Tensor = attn_metadata.q_start_loc
cache_seqlens: Tensor = attn_metadata.cache_seqlens

is_multi_token_decoding = attn_metadata.is_multi_token_decoding

input_buffers: BuffType = graph_meta.input_buffers

Expand All @@ -121,27 +164,34 @@ def AscendCudaGraphMixin_fill_buffers_cudagraph(
input_buffers["input_ids"][:, num_tokens:max_num_tokens].random_(
0, graph_meta.vocab_size
)

input_buffers["input_ids"][:, :num_tokens] = input_ids
input_buffers["position_ids"].zero_()
input_buffers["position_ids"][:, :num_tokens] = position_ids
input_buffers["block_offsets"].zero_()
input_buffers["block_offsets"][:batch_size, :num_blocks] = block_offsets

input_buffers["kv_seqlens"].fill_(0)
input_buffers["kv_seqlens"][:batch_size] = kv_seqlens
input_buffers["kv_start_indices"].fill_(-1)
input_buffers["kv_start_indices"][:batch_size] = kv_start_indices
input_buffers["kv_start_indices"][: kv_start_indices.size(0)] = kv_start_indices
if x_active_mask is not None:
input_buffers["x_active_mask"].fill_(0)
input_buffers["x_active_mask"][:batch_size] = x_active_mask
input_buffers["x_active_mask"][: x_active_mask.size(0)] = x_active_mask

# ssm
if graph_meta.is_ssm:
input_buffers["q_start_loc"][: batch_size + 1] = q_start_loc
input_buffers["q_start_loc"][batch_size + 1 :] = q_start_loc[-1]

state_ids = kwargs["state_ids"]
input_buffers["state_ids"].fill_(-1)
input_buffers["state_ids"][: state_ids.size(0)].copy_(state_ids)
input_buffers["state_ids"].fill_(0)
input_buffers["state_ids"][:batch_size].copy_(state_ids)

if is_multi_token_decoding:
input_buffers["cache_seqlens"].fill_(0)
input_buffers["cache_seqlens"][:batch_size].copy_(cache_seqlens)

attn_metadata.cache_seqlens = input_buffers["cache_seqlens"]

if is_multi_token_decoding:
attn_metadata.attention_mask = [input_buffers["attention_mask"]]

if inputs_embeds is not None:
emb_size = inputs_embeds.size(-1)
Expand All @@ -151,15 +201,13 @@ def AscendCudaGraphMixin_fill_buffers_cudagraph(
1, max_num_tokens, emb_size
)
input_buffers["inputs_embeds"][:, :num_tokens] = inputs_embeds
# create inputs
# Use compatible size but cap at graph's max_batchs to avoid buffer overflow
new_batch_size = min(get_ascend_compatible_size(batch_size), graph_meta.max_batchs)

attn_metadata.block_offsets = input_buffers["block_offsets"]
attn_metadata.kv_seqlens = input_buffers["kv_seqlens"]
attn_metadata.kv_start_indices = input_buffers["kv_start_indices"]
moe_metadata.x_active_mask = input_buffers["x_active_mask"]
attn_metadata.q_start_loc = input_buffers["q_start_loc"]
attn_metadata.q_seqlens = input_buffers["q_seqlens"]

new_inputs = dict(
past_key_values=past_key_values,
Expand All @@ -175,7 +223,6 @@ def AscendCudaGraphMixin_fill_buffers_cudagraph(

new_inputs.update(kwargs)

# ssm: override kwargs' variable-length state_ids with the fixed-size buffer
if graph_meta.is_ssm:
new_inputs["state_ids"] = input_buffers["state_ids"]

Expand All @@ -194,7 +241,6 @@ def AscendCudaGraphMixin_update_context_cudagraph(self, graph_meta, context):
"""update step context with input buffers."""
input_buffers = graph_meta.input_buffers
context.block_offsets = input_buffers["block_offsets"]
context.q_seqlens = input_buffers["q_seqlens"]
context.kv_seqlens = input_buffers["kv_seqlens"]
context.q_start_loc = input_buffers["q_start_loc"]
context.kv_start_indices = input_buffers["kv_start_indices"]
Expand All @@ -209,6 +255,7 @@ def AscendCudaGraphMixin_update_context_cudagraph(self, graph_meta, context):
context.mrope_position_ids = input_buffers["mrope_position_ids"]


CudaGraphMixin.support_cuda_graph = AscendCudaGraphMixin_support_cuda_graph
CudaGraphMixin.make_buffers_cudagraph = AscendCudaGraphMixin_make_buffers_cudagraph
CudaGraphMixin.fill_buffers_cudagraph = AscendCudaGraphMixin_fill_buffers_cudagraph
CudaGraphMixin.update_context_cudagraph = AscendCudaGraphMixin_update_context_cudagraph
Expand Down Expand Up @@ -358,7 +405,7 @@ def forward(self, **kwargs):
]
)
else:
update_attn_params(self.update_stream, self.meta, self.max_tokens)
update_attn_params(self.update_stream, self.meta, self.max_batches)
self._graph.replay()
output_buffers = self.meta.output_buffers
output = self.model.get_outputs_cudagraph(output_buffers, **kwargs)
Expand Down Expand Up @@ -427,40 +474,64 @@ def _get_capture_tokens(self, batch_size: int):
def get_graph_key(
self,
input_ids: torch.Tensor,
attn_metadata: Any,
**kwargs,
):
"""Get graph key."""
context = self.ctx_mgr.current_context()
is_decoding = context.is_decoding
num_tokens = input_ids.numel()
context = get_step_ctx_manager().current_context()
# Keep the graph key consistent across DP ranks (the captured graph holds the
# MoE EP collective), mirroring the global gate used in support_cuda_graph.
is_decoding = context.global_is_decoding()
is_multi_token_decoding = attn_metadata.is_multi_token_decoding
meta = self.get_meta()
enable_microbatch = get_step_ctx_manager().current_context().enable_microbatch
enable_microbatch = context.enable_microbatch

if is_multi_token_decoding:
q_seqlens = attn_metadata.q_seqlens
max_q_seq_len = attn_metadata.max_q_seq_len
batch_size = q_seqlens.size(0)
if meta.padding_batch_size is None:
new_batch_size = self._get_capture_tokens(batch_size)
else:
new_batch_size = self._get_capture_tokens(meta.padding_batch_size)
return (
new_batch_size,
is_multi_token_decoding,
enable_microbatch,
max_q_seq_len,
)

num_tokens = input_ids.numel()
if meta.padding_batch_size is None:
new_num_tokens = self._get_capture_tokens(num_tokens)
else:
new_num_tokens = self._get_capture_tokens(meta.padding_batch_size)
return (new_num_tokens, is_decoding, enable_microbatch)
return (new_num_tokens, is_decoding, enable_microbatch, 1)

def __call__(self, **kwargs):
"""call."""
enable_graph = self.enable_graph(**kwargs)
# Graph capture/replay holds the MoE EP collective; gate on the DP-global
# decode state so every DP rank enters or skips the graph together.
context = get_step_ctx_manager().current_context()
enable_graph = context.global_is_decoding() and self.enable_graph(**kwargs)

if not enable_graph:
with record_function("forward_eager"):
ret = self.model(**kwargs)
return self.model.make_output_buffers(ret)

graph_key = self.get_graph_key(**kwargs)
max_tokens = graph_key[0]
is_decoding = graph_key[1]
max_batches = graph_key[0]
is_decoding_or_multi_token_decoding = graph_key[1]
max_q_seq_len = graph_key[3]
max_tokens = max_batches * max_q_seq_len
if graph_key not in self._runner_map:
max_batches = max_tokens if is_decoding else self.max_batches
runner = AscendSingleGraphRunner(
self.model,
max_batches=max_batches,
max_tokens=max_tokens,
num_blocks=self.num_blocks,
is_decoding=is_decoding,
is_decoding=is_decoding_or_multi_token_decoding,
pool=self.graph_pool_handle,
model_config=self.model_config,
device=self.device,
Expand Down Expand Up @@ -510,13 +581,16 @@ def update_inputs(self, inputs):
"""Update inputs."""
if self.backend_config.eager_mode:
return inputs
is_decoding = inputs.is_decoding
is_decoding = inputs.global_is_decoding()
dp_meta = inputs.dp_meta
if is_decoding and dp_meta is not None:
meta = self.get_meta()
padding_batch_size = meta.padding_batch_size
tp_size = self._get_capture_tokens(padding_batch_size)
dp_meta.tp_sizes = [tp_size] * len(dp_meta.tp_sizes)

batch_size = inputs.seq_length.size(0)
query_len = inputs.input_ids.numel() // batch_size
tp_size = self._get_capture_tokens(padding_batch_size) * query_len
dp_meta.sync_tp_size(tp_size)
return inputs

def get_capture_batch_sizes(self) -> List[int]:
Expand Down
Loading
Loading