Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
bde9685
Add nvtx ranges for mtp
santhnm2 Apr 10, 2026
7587f2f
perf benchmarking
santhnm2 Apr 10, 2026
b8bdd12
Remove simpy dependency
santhnm2 Apr 10, 2026
e948bc9
cuda graphs for mtp
santhnm2 Apr 10, 2026
6c60f34
mtp cuda graphs
santhnm2 Apr 10, 2026
90ffe8a
fix cuda graphs
santhnm2 Apr 10, 2026
8ce0a4b
cuda graph fix
santhnm2 Apr 10, 2026
84129e7
Fix dummy_position_ids dtype
santhnm2 Apr 10, 2026
9bfa0fc
Try to compile more
santhnm2 Apr 10, 2026
cbc1270
remove extra ep sync
santhnm2 Apr 10, 2026
7687743
Add nvtx ranges
santhnm2 Apr 11, 2026
407e12d
Remove cpu sync
santhnm2 Apr 11, 2026
e54458c
Avoid h2d syncs
santhnm2 Apr 11, 2026
70afe4c
Linting
santhnm2 Apr 11, 2026
5e84f85
compile rewind_kv_cache
santhnm2 Apr 11, 2026
e2020b2
Linting
santhnm2 Apr 11, 2026
56b93ff
Fix verify_speculative_tokens
santhnm2 Apr 11, 2026
98a32ef
More fixes
santhnm2 Apr 11, 2026
3fefb93
Add torch.compile back
santhnm2 Apr 11, 2026
96668d6
Linting
santhnm2 Apr 11, 2026
9a95577
Try to fix tests
santhnm2 Apr 11, 2026
74edd58
Fix test
santhnm2 Apr 11, 2026
b6466d2
More test fixes
santhnm2 Apr 11, 2026
6941afb
Bug fix
santhnm2 Apr 11, 2026
94452d5
Linting
santhnm2 Apr 11, 2026
cd981ea
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 11, 2026
774ec53
kernelize
santhnm2 Apr 11, 2026
d9c2902
more perf optimizations
santhnm2 Apr 13, 2026
45cc1eb
Fix dtype
santhnm2 Apr 13, 2026
6c57910
Fix dtype
santhnm2 Apr 13, 2026
68923e2
Fix cuda graph
santhnm2 Apr 13, 2026
0066e42
Clean up CUDA graph
santhnm2 Apr 13, 2026
0a235ff
Clean up graph
santhnm2 Apr 13, 2026
0d6243a
Fix graph
santhnm2 Apr 13, 2026
5bfd41c
Debugging
santhnm2 Apr 13, 2026
95ed9a0
Fix graphs
santhnm2 Apr 13, 2026
4b1d093
Fix graphs
santhnm2 Apr 14, 2026
7913032
Remove debug prints
santhnm2 Apr 14, 2026
819ec4a
Merge remote-tracking branch 'upstream/main' into mtp_perf_v2
santhnm2 Apr 14, 2026
a39dffd
Clean up
santhnm2 Apr 14, 2026
9e6680c
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 14, 2026
b1e44cf
Merge branch 'mtp_perf_v2' into mtp_perf
santhnm2 Apr 14, 2026
9bd8aa4
Add unit test
santhnm2 Apr 14, 2026
e8690f8
Linting
santhnm2 Apr 14, 2026
89c4ced
Zero out padded values in test
santhnm2 Apr 14, 2026
610d434
Fix test
santhnm2 Apr 14, 2026
0656373
fix test
santhnm2 Apr 14, 2026
93f5a34
Fix test
santhnm2 Apr 14, 2026
bd8c4b9
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 14, 2026
bbbb92d
Fix test
santhnm2 Apr 14, 2026
24d481e
Fix tests
santhnm2 Apr 14, 2026
e872366
Fix tests
santhnm2 Apr 14, 2026
aacefb4
Linting
santhnm2 Apr 14, 2026
ac2048c
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 14, 2026
13122e7
Mark flaky tests
santhnm2 Apr 14, 2026
8dacd28
Merge with main
santhnm2 Apr 20, 2026
8d3a063
Fix rewind_kv_cache_bug
santhnm2 Apr 20, 2026
210415a
Move MTP cuda graph warmup inline
santhnm2 Apr 20, 2026
bcebf41
Linting
santhnm2 Apr 20, 2026
c82b70a
Add Triton tests
santhnm2 Apr 20, 2026
3a3bbdc
Linting
santhnm2 Apr 20, 2026
c05d8a5
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 20, 2026
43de7aa
NVLS all gathers
santhnm2 Apr 20, 2026
78fba30
Fix gather_output
santhnm2 Apr 20, 2026
4c0ac1a
Expand graph scope
santhnm2 Apr 20, 2026
03d52ab
Update tests
santhnm2 Apr 20, 2026
4abc573
Fix graph manager
santhnm2 Apr 20, 2026
d8b08ac
Fix chaining
santhnm2 Apr 20, 2026
836a914
Fix keys
santhnm2 Apr 20, 2026
bb597ca
Inference RS
santhnm2 Apr 20, 2026
3c3b23b
Fix flag
santhnm2 Apr 20, 2026
5391d6c
Merge remote-tracking branch 'upstream/main' into mtp_perf_v2
santhnm2 Apr 21, 2026
bc214cf
Formatting
santhnm2 Apr 21, 2026
2a77e5b
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 21, 2026
91029d4
Merge branch 'mtp_perf_v2' into mtp_perf
santhnm2 Apr 21, 2026
c0f4fff
Clean up and fixes
santhnm2 Apr 21, 2026
c39c8e5
Fix tests
santhnm2 Apr 21, 2026
084e605
Make MTP cuda graphs test engine level
santhnm2 Apr 21, 2026
432722d
Fix dtype
santhnm2 Apr 21, 2026
1a59fcc
Cast model to params_dtype
santhnm2 Apr 21, 2026
2f8e0c8
Fixes
santhnm2 Apr 21, 2026
57771f9
Try again to fix tests
santhnm2 Apr 21, 2026
314a2ee
Fix 0 token count graph error
santhnm2 Apr 21, 2026
f0a87e0
Fix casting
santhnm2 Apr 21, 2026
168028b
Fix input dtype
santhnm2 Apr 21, 2026
3d61c13
Add depth
santhnm2 Apr 21, 2026
43176d1
Delete cuda graphs for eager
santhnm2 Apr 21, 2026
d76da59
Fix delete cuda graphs
santhnm2 Apr 21, 2026
248d761
Fix deletion path
santhnm2 Apr 21, 2026
187b00d
Linting
santhnm2 Apr 21, 2026
4676931
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 21, 2026
aa1e8f6
Cleanup
santhnm2 Apr 21, 2026
1bc4a87
Linting
santhnm2 Apr 21, 2026
f97e3f6
Remove dead code
santhnm2 Apr 21, 2026
fcb2ec3
Clean up resources for test_parallel_inference
santhnm2 Apr 21, 2026
259c1f5
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 21, 2026
f2504c5
Fix EP in test
santhnm2 Apr 21, 2026
3d21b5d
Fix is_expert arg
santhnm2 Apr 21, 2026
fa22202
Fix case where weight is None
santhnm2 Apr 21, 2026
389d83e
Add depth kwarg
santhnm2 Apr 21, 2026
0825420
Nits
santhnm2 Apr 21, 2026
ffdefc5
Fix normalization in unit test
santhnm2 Apr 21, 2026
dbd9f20
Fix bias in unit test
santhnm2 Apr 21, 2026
9b6a2c4
Add no-op get_extra_state function
santhnm2 Apr 21, 2026
343c6bb
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 21, 2026
79c871d
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 21, 2026
2d8b102
Linting
santhnm2 Apr 21, 2026
6d2ace5
Address review comments
santhnm2 Apr 22, 2026
53bd51d
Address reviewer comments
santhnm2 Apr 22, 2026
157c0c5
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 22, 2026
8f5e7d3
Revert unnecessary changes
santhnm2 Apr 22, 2026
6d1f0b6
Linting
santhnm2 Apr 22, 2026
c5a2d5f
Clean up MTP graph manager tracking
santhnm2 Apr 22, 2026
b074789
More cleanup
santhnm2 Apr 22, 2026
ed0e028
Restore docstring
santhnm2 Apr 22, 2026
ffe4780
Fix skip_weight_param_allocation
santhnm2 Apr 22, 2026
3119052
Revert InferenceColumnParallelLinear changes
santhnm2 Apr 22, 2026
d991265
Linting
santhnm2 Apr 22, 2026
9a72316
Standardize inference_reduce_scatter_first_dim API
santhnm2 Apr 22, 2026
0c4b56c
Merge
santhnm2 Apr 22, 2026
071ab7f
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 22, 2026
ba6de96
Merge with main
santhnm2 Apr 23, 2026
4e56aee
Remove is_expert setting
santhnm2 Apr 23, 2026
9d90639
Address reviewer comments
santhnm2 Apr 23, 2026
adafdd1
Linting
santhnm2 Apr 23, 2026
1700d02
Add mtp_utils_pytorch.py
santhnm2 Apr 23, 2026
d75ed23
Linting
santhnm2 Apr 23, 2026
4bab4be
setup_method -> setup_cls
santhnm2 Apr 23, 2026
0afeb18
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 23, 2026
61bde08
Add classmethod decorator
santhnm2 Apr 23, 2026
652c9b5
More test NCCL cleanup
santhnm2 Apr 23, 2026
06e6907
Refactor test_dynamic_engine.py
santhnm2 Apr 23, 2026
96a01ea
clear nvte env vars
santhnm2 Apr 23, 2026
77473c3
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 24, 2026
490165b
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 24, 2026
8eb0c80
Fix test
santhnm2 Apr 24, 2026
ac467b1
Refactor test_text_generation_controller.py
santhnm2 Apr 24, 2026
3e28f26
Explicitly delete cuda graphs
santhnm2 Apr 24, 2026
15d0e83
Add cuda graph deletion to static engine
santhnm2 Apr 24, 2026
b4d0d69
More cleanup
santhnm2 Apr 24, 2026
1a8f7da
Add clear_nvte_env_vars to static test
santhnm2 Apr 24, 2026
9f64401
Add garbage collection to test_moe_inference.py
santhnm2 Apr 24, 2026
f193a01
Linting
santhnm2 Apr 24, 2026
5266808
Merge remote-tracking branch 'upstream/main' into mtp_perf
santhnm2 Apr 24, 2026
c6ad4f3
More cuda graph deletion
santhnm2 Apr 24, 2026
2cae20f
Fix copyright
santhnm2 Apr 24, 2026
f537865
Revert unnecessary cuda graph changes
santhnm2 Apr 24, 2026
84b1ef5
Merge with main
santhnm2 Apr 26, 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
12 changes: 9 additions & 3 deletions megatron/core/inference/batch_dimensions_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import torch

from megatron.core.utils import get_pg_size
from megatron.core.utils import get_pg_size, round_up_to_nearest_multiple


@dataclass(order=True, frozen=True)
Expand Down Expand Up @@ -85,6 +85,10 @@ def is_valid(
Returns:
True if the config is valid, False otherwise
"""
# A dimension with no tokens serves no requests.
if self.token_count <= 0:
return False

# Check if total requests exceed maximum
if self.prefill_req_count + self.decode_req_count > max_requests:
return False
Expand Down Expand Up @@ -269,7 +273,9 @@ def _calculate_cuda_graph_token_counts(
)
# Align each entry to TP size
cuda_graph_token_counts = list(
dict.fromkeys(math.ceil(s / tp_size) * tp_size for s in cuda_graph_token_counts)
dict.fromkeys(
round_up_to_nearest_multiple(s, tp_size) for s in cuda_graph_token_counts
)
)
# Clamp to max tokens
cuda_graph_token_counts = [
Expand All @@ -291,7 +297,7 @@ def _calculate_cuda_graph_token_counts(
math.ceil(int(cuda_graph_step_size) / CUDAGraphBatchDimensionBuilder.CUDA_GRAPH_ROUNDER)
)
# Make sure divisible by TP size
cuda_graph_step_size = math.ceil(cuda_graph_step_size / tp_size) * tp_size
cuda_graph_step_size = round_up_to_nearest_multiple(cuda_graph_step_size, tp_size)

# round down cuda graph max tokens to be multiple of TP size
cuda_graph_max_tokens = (cuda_graph_max_tokens // tp_size) * tp_size
Expand Down
46 changes: 45 additions & 1 deletion megatron/core/inference/engines/dynamic_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
unset_inference_cuda_graphed_iteration_for_ep_inference,
)
from megatron.core.process_groups_config import ProcessGroupCollection
from megatron.core.transformer.cuda_graphs import delete_cuda_graphs
from megatron.core.transformer.cuda_graphs import delete_cuda_graphs, graph_capture
from megatron.core.transformer.enums import CudaGraphScope
from megatron.core.transformer.moe.router_replay import RouterReplay, RouterReplayAction
from megatron.core.utils import (
Expand All @@ -63,7 +63,9 @@
internal_api,
nvtx_range_pop,
nvtx_range_push,
round_up_to_nearest_multiple,
trace_async_exceptions,
unwrap_model,
)

from .async_zmq_communicator import AsyncZMQCommunicator
Expand Down Expand Up @@ -365,6 +367,21 @@ def create_cuda_graphs(self, reset_context: bool = True):
unwrapped_model = controller.inference_wrapped_model.model
set_inference_cuda_graphed_iteration_for_ep_inference(unwrapped_model)

# MTP warmup preparation: capture MTP CUDA graphs alongside the
# decoder graphs within the same loop rather than in a separate pass.
unwrapped = unwrap_model(controller.inference_wrapped_model.model)
mtp_warmup_enabled = (
controller.num_mtp_heads > 0
and (controller.num_speculative_tokens or 0) > 0
and hasattr(unwrapped, 'mtp')
)
if mtp_warmup_enabled:
tp_size = get_pg_size(controller.inference_wrapped_model.tp_group)
sp_enabled = model_config.sequence_parallel and tp_size > 1
mtp_pass_depth = not unwrapped.mtp.mtp_use_repeated_layer
mtp_warmup_depths = range(controller._num_mtp_depths) if mtp_pass_depth else [None]
mtp_seen_batch_sizes = set()

tbar = enumerate(context.cuda_graph_batch_dimensions_list)
if HAVE_TQDM:
tbar = tqdm(tbar, total=len(context.cuda_graph_batch_dimensions_list))
Expand All @@ -390,12 +407,39 @@ def create_cuda_graphs(self, reset_context: bool = True):
# Forward pass -> logits.
controller._dynamic_step_forward_logits(input_ids, position_ids)

# MTP CUDA graph warmup for this batch dimension.
if mtp_warmup_enabled:
n = cuda_graph_batch_dimension.req_count
if sp_enabled:
n = round_up_to_nearest_multiple(n, tp_size)
if n > 0 and n not in mtp_seen_batch_sizes:
mtp_seen_batch_sizes.add(n)
device = torch.cuda.current_device()
batch_dim = n // tp_size if sp_enabled else n
# Use zeros (not empty) — garbage token IDs cause OOB embedding lookups during graph capture/replay.
for depth in mtp_warmup_depths:
with graph_capture():
Comment thread
sidsingh-nvidia marked this conversation as resolved.
unwrapped.compute_mtp_single_step(
hidden_states=torch.zeros(
(batch_dim, 1, model_config.hidden_size),
device=device,
dtype=model_config.params_dtype,
),
next_token_ids=torch.zeros((1, n), device=device, dtype=torch.long),
position_ids=torch.zeros((1, n), device=device, dtype=torch.int64),
depth=depth,
)

context.reset()

# Disable inference dispatcher after graph capture
if is_inference_optimized_ep:
unset_inference_cuda_graphed_iteration_for_ep_inference(unwrapped_model)

if mtp_warmup_enabled and mtp_seen_batch_sizes:
controller.has_mtp_cuda_graphs = True
logging.info("> MTP CUDA graph warmup: %d batch size(s)", len(mtp_seen_batch_sizes))

# Memory usage.
time_end = time.time()
mem_stats_end = torch.cuda.memory_stats()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import torch


def rewind_kv_cache(
accepted_counts,
prefill_status,
last_kv_block_offset,
kv_length_offsets,
kv_block_counts,
last_kv_block_id,
kv_block_ids,
num_speculative_tokens,
block_size_tokens,
num_active_requests=None,
):
"""Update the KV cache bookkeeping for speculative decoding.

After forward pass with speculative tokens, some tokens may be rejected.
This function "rewinds" the KV cache bookkeeping to reflect only the accepted tokens.

When speculative tokens are rejected, we need to:
1. Update kv_length_offsets (total sequence length)
2. Update last_kv_block_offset (position within last block)
3. If rewinding crosses a block boundary:
- Reduce kv_block_counts
- Update last_kv_block_id to point to the previous block
- Clear the entry in kv_block_ids for the released block

Mutates the input tensors in-place.

Returns (blocks_to_release, remove_mask).
"""
N = accepted_counts.shape[0]
if num_active_requests is None:
num_active_requests = N

blocks_to_release = torch.empty_like(last_kv_block_id)
remove_mask = torch.empty(N, device=accepted_counts.device, dtype=torch.bool)

for i in range(N):
if i >= num_active_requests:
blocks_to_release[i] = 0
remove_mask[i] = False
continue

accepted = accepted_counts[i].item()
prefill = prefill_status[i].item()
last_offset = last_kv_block_offset[i].item()
kv_length = kv_length_offsets[i].item()
block_count = kv_block_counts[i].item()
last_block = last_kv_block_id[i].item()

# Number of tokens to rewind (rejected speculative tokens).
# For prefill requests, no speculative tokens were forwarded through the model,
# so there is nothing to rewind.
num_to_rewind = 0 if prefill == 1 else num_speculative_tokens - accepted

# Save the original offset BEFORE modifying to correctly detect block boundary crossing.
# A request crosses back to a previous block if: original_offset - num_to_rewind < 0
diff = last_offset - num_to_rewind
remove = diff < 0

# Update the offsets
new_offset = diff % block_size_tokens
last_kv_block_offset[i] = new_offset
kv_length_offsets[i] = kv_length - num_to_rewind

# For requests that crossed back to a previous block, we need to:
# 1. Reduce the block count by 1
# 2. Get the block ID to release (current last_kv_block_id)
# 3. Update last_kv_block_id to point to the previous block
# 4. Clear the entry in kv_block_ids for the released block
# 5. Release the block back to the allocator
blocks_to_release[i] = last_block

# Reduce block counts for requests that crossed back
new_block_count = block_count - 1 if remove else block_count
kv_block_counts[i] = new_block_count

# Update last_kv_block_id to point to the previous block (at index new_count - 1)
prev_idx = max(new_block_count - 1, 0)
prev_block_id = kv_block_ids[i, prev_idx].item()
last_kv_block_id[i] = prev_block_id if remove else last_block

# Clear the released block entry (at index new_count, which was the old last block)
scatter_idx = min(new_block_count, kv_block_ids.shape[1] - 1)
if remove:
kv_block_ids[i, scatter_idx] = -1

remove_mask[i] = remove

return blocks_to_release, remove_mask


# pylint: disable=line-too-long
def verify_speculative_tokens(
input_tokens, output_tokens, num_decode_requests, num_prefill_requests, num_speculative_tokens
):
"""Verify speculative tokens against input tokens and compute acceptance.

Creates an accepted tokens mask where:
- For prefill requests, the token is always accepted.
- For decode requests, the first token (base token) is always accepted, then we compare
sampled tokens with input tokens and accept consecutive matches.
Then finds the index of the last accepted token per request.

Example (assume 1, 2, and 0 spec tokens are accepted in the first 3 decode requests):
input_tokens_required: [ a5 a6s a7s | b3 b4s b5s | c6 c7s c8s | d2 | e4 ] # Size 11
Output tokens [ a6o a7o a8o | b40 b5o b6o | c7o c8o c9o | d3o | e5o ]
Output tokens right shift [ d3o a6o a7o | a8o b40 b5o | b6o c7o c8o | c9o | d3o ]
Accepted tokens mask [ 1 1 0 | 1 1 1 | 1 0 0 | 1 | 1 ]
Last one indices [ 1 | 5 | 6 | 9 | 10 ]

Returns:
tuple: (last_one_indices, accepted_tokens_mask, input_tokens) where
last_one_indices contains the index of the last accepted token per request.
"""
if input_tokens.ndim == 2:
input_tokens = input_tokens.squeeze(0)

stride = num_speculative_tokens + 1
active_request_count = num_decode_requests + num_prefill_requests
decode_len = num_decode_requests * stride

# Initialize mask with False to prevent boundary bleed
accepted_tokens_mask = torch.zeros_like(input_tokens, dtype=torch.bool)

# Safe decode token verification without cross-batch boundary contamination
decode_mask_2d = None
if num_decode_requests > 0:
decode_inputs = input_tokens[:decode_len].reshape(num_decode_requests, stride)
decode_outputs = output_tokens[:decode_len].reshape(num_decode_requests, stride)

# Shift outputs right by 1 *within* each request to align sampled tokens with input targets
decode_outputs_shifted = decode_outputs.roll(1, dims=1)
decode_mask_2d = decode_inputs == decode_outputs_shifted
# The first token (base token) is always accepted
decode_mask_2d[:, 0] = True
# Enforce consecutive acceptance: cummin propagates False to the right
decode_mask_2d = decode_mask_2d.cummin(dim=1).values
accepted_tokens_mask[:decode_len] = decode_mask_2d.flatten()

# Make all prefill tokens accepted
if num_prefill_requests > 0:
accepted_tokens_mask[decode_len:] = True

last_one_indices = torch.full(
(active_request_count,), -1, device=input_tokens.device, dtype=torch.long
)

if num_decode_requests > 0:
# Summing the consecutive mask gives the count; subtract 1 for the local index
local_last_indices = decode_mask_2d.sum(dim=1) - 1
row_offsets = torch.arange(num_decode_requests, device=input_tokens.device) * stride
last_one_indices[:num_decode_requests] = row_offsets + local_last_indices

if num_prefill_requests > 0:
prefill_valid = torch.nonzero(accepted_tokens_mask[decode_len:]).squeeze(-1) + decode_len
last_one_indices[num_decode_requests:] = prefill_valid

return last_one_indices, accepted_tokens_mask, input_tokens


# pylint: disable=line-too-long
def prepare_next_forward_pass(
num_decode_requests,
output_tokens,
required_logit_indices,
last_one_indices,
accepted_tokens_mask,
input_tokens,
sampled_tokens_buf,
last_accepted_seq_buf,
accepted_tokens_per_request,
accepted_token_counts,
num_speculative_tokens,
):
"""Prepare data for the next forward pass after speculative token verification.

For each active request:
- Store the final sampled tokens for the next forward pass.
- Store the last accepted positions in the packed sequence for serial
MTP computation after verification.

For decode requests, extract accepted tokens and counts:
input_tokens_required: [ a5 a6s a7s | b3 b4s b5s | c6 c7s c8s | d2 | e4 ]
Accepted tokens mask [ 1 1 0 | 1 1 1 | 1 0 0 | 1 | 1 ]
Accepted tokens [ [a6s -1] | [b4s b5s] | [-1 -1] ] # Only decode requests (prefill defaults to -1)
Accepted token counts [ 1 | 2 | 0 ] # Prefill defaults to 0

Writes results into the pre-allocated buffers provided by the caller.
"""
active_request_count = last_one_indices.shape[0]
stride = num_speculative_tokens + 1

for pid in range(active_request_count):
idx = last_one_indices[pid].item()

# Store the final sampled tokens for the next forward pass.
sampled_tokens_buf[pid] = output_tokens[idx]

# Store the last accepted positions in the packed sequence for serial
# MTP computation after verification.
last_accepted_seq_buf[pid] = required_logit_indices[idx]

# Extract accepted tokens and counts for decode requests.
# For prefill it is always set to 1. For decode, the first token is always accepted,
# then we compare with input tokens and accept the next tokens if its a match.
if pid < num_decode_requests:
base = pid * stride
# Skip the first token of every decode request (i.e a5, b3, c6)
for s in range(num_speculative_tokens):
pos = base + 1 + s
if accepted_tokens_mask[pos]:
accepted_tokens_per_request[pid, s] = input_tokens[pos]
else:
accepted_tokens_per_request[pid, s] = -1

count = 0
for s in range(num_speculative_tokens):
if accepted_tokens_per_request[pid, s].item() != -1:
count += 1
accepted_token_counts[pid] = count


def mamba_state_selective_copy(
intermediate_states, current_states, prefill_status, state_idx, accepted_counts, num_layers
):
"""Mamba speculative rewind state update.

For each decode request, copies
`intermediate[layer, slot, accepted_count, ...]` →
`current[layer, slot, ...]` for every Mamba layer.
"""
N = prefill_status.shape[0]
for i in range(N):
if prefill_status[i].item() == 1:
continue
slot = state_idx[i].item()
accepted = accepted_counts[i].item()
for layer in range(num_layers):
current_states[layer, slot] = intermediate_states[layer, slot, accepted]
Loading
Loading