Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
b141669
[Perf][Async] Implement zero-bubble async speculative decoding
izhuhaoran Jan 15, 2026
679054f
skip compute_slot_mapping for async_spec_zero_bubble_mode
izhuhaoran Jan 16, 2026
527b55d
remove seq_lens_cpu & num_computed_tokens_cpu for async_spec_zero_bub…
izhuhaoran Jan 16, 2026
8c2ce4c
Get rid of async_spec_zero_bubble_mode option
MatthewBonanni Jan 21, 2026
1554ab7
Fully async version
MatthewBonanni Jan 22, 2026
315e7e6
Increase max_concurrent_batches
MatthewBonanni Jan 22, 2026
15d8ee9
Handle reordering
MatthewBonanni Jan 23, 2026
abb8ba7
Merge branch 'main' into async-eagle-mod
MatthewBonanni Jan 23, 2026
dd8b7c3
Fix
MatthewBonanni Jan 23, 2026
1573015
Merge branch 'main' into async-eagle-mod
MatthewBonanni Jan 23, 2026
317b452
Cleanup
MatthewBonanni Jan 23, 2026
e7e39ce
Cleanup
MatthewBonanni Jan 23, 2026
6fb3042
Fix
MatthewBonanni Jan 23, 2026
7ff5674
Cleanup
MatthewBonanni Jan 26, 2026
c491421
Fix hybrid
MatthewBonanni Jan 26, 2026
a2e861e
Disable mamba cache mode align
MatthewBonanni Jan 27, 2026
0c45e7e
Treat num_accepted_tokens like num_computed_tokens
MatthewBonanni Jan 27, 2026
1eccc1d
Treat num_accepted_tokens like num_computed_tokens
MatthewBonanni Jan 27, 2026
7005b52
Treat num_accepted_tokens like num_computed_tokens
MatthewBonanni Jan 27, 2026
4e4d8d5
Cleanup
MatthewBonanni Jan 27, 2026
c134792
Eliminate compute_slot_mapping
MatthewBonanni Jan 27, 2026
ea7f670
Rename compute_slot_mapping_gpu to compute_slot_mapping
MatthewBonanni Jan 27, 2026
a113c3f
Restore comments
MatthewBonanni Jan 27, 2026
1153da9
Restore comments
MatthewBonanni Jan 27, 2026
e04f1f2
Add TODO comment
MatthewBonanni Jan 27, 2026
a224144
Fix (num_accepted_tokens shouldn't be int64)
MatthewBonanni Jan 27, 2026
c1f550b
Cleanup
MatthewBonanni Jan 27, 2026
ab2d19a
Fix
MatthewBonanni Jan 27, 2026
3c732eb
Fix
MatthewBonanni Jan 29, 2026
843a151
Fix
MatthewBonanni Jan 29, 2026
4c522a2
Fix
MatthewBonanni Jan 29, 2026
c329c93
Cleanup
MatthewBonanni Jan 30, 2026
98c146b
Use CpuGpuBuffer for arange
MatthewBonanni Jan 30, 2026
5f1d06f
Make seq_lens GPU-only and introduce optimistic_seq_lens
MatthewBonanni Jan 30, 2026
050959c
Restructure if block
MatthewBonanni Jan 30, 2026
718087f
Fix TypeError
MatthewBonanni Feb 5, 2026
31c8674
Fix order of operations error
MatthewBonanni Feb 5, 2026
4c6bd9d
Fix positions and seq_lens calculation
MatthewBonanni Feb 5, 2026
03adb2e
Improve _get_cumsum_and_arange
MatthewBonanni Feb 9, 2026
e76d32e
Rename
MatthewBonanni Feb 9, 2026
526fea3
Use query_pos
MatthewBonanni Feb 9, 2026
48df0ab
Eliminate CPU-side num_computed_tokens from GPUModelRunner. Update op…
MatthewBonanni Feb 10, 2026
f3a2684
Fix M-RoPE and XD-RoPE
MatthewBonanni Feb 10, 2026
9f20781
Factor out _compute_batch_index_mapping
MatthewBonanni Feb 11, 2026
779ee44
Fix optimistic_seq_lens_cpu update
MatthewBonanni Feb 11, 2026
2fab694
Fix acceptance length
MatthewBonanni Feb 11, 2026
65f7b3d
Fix
MatthewBonanni Feb 13, 2026
8cc914c
Use CpuGpuBuffer
MatthewBonanni Feb 13, 2026
2dacc94
Merge branch 'main' into async-eagle-mod
MatthewBonanni Feb 13, 2026
fa849f8
Fix
MatthewBonanni Feb 13, 2026
ccc9575
Use preallocated buffer
MatthewBonanni Feb 13, 2026
bc13824
Use triton kernel instead of pytorch ops
MatthewBonanni Feb 16, 2026
3f0be40
Use buffers to avoid sync
MatthewBonanni Feb 16, 2026
664eaf1
Always use placeholders
MatthewBonanni Feb 17, 2026
66ae34b
Comment
MatthewBonanni Feb 17, 2026
c645e05
Bugfix: add arange scratch buffer
MatthewBonanni Feb 18, 2026
04e023b
Use buffers to prevent allocation on the fly
MatthewBonanni Feb 18, 2026
60ca5ec
Re-add indices_match fast path
MatthewBonanni Feb 18, 2026
82da483
Clean up input batch
MatthewBonanni Feb 18, 2026
f7d028e
Merge branch 'main' into async-eagle-mod
MatthewBonanni Feb 23, 2026
0cdf7de
Simplify BatchIndexMapping and num_computed_tokens tracking
LucasWilkinson Feb 27, 2026
d0b43ec
Merge pull request #2 from LucasWilkinson/simplify-batch-index-mapping
MatthewBonanni Mar 4, 2026
7fde95a
Fix acceptance length
MatthewBonanni Mar 4, 2026
e20565d
Deduplicate
MatthewBonanni Mar 4, 2026
fa02bb8
Make positions GPU-only
MatthewBonanni Mar 4, 2026
a74cd15
Eliminate has_prev_draft_tokens
MatthewBonanni Mar 4, 2026
6aa50c2
Add CPU correction
MatthewBonanni Mar 5, 2026
3cee2b2
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 5, 2026
d4304e4
Skip unnecessary copy
MatthewBonanni Mar 5, 2026
56bd8f8
Rename
MatthewBonanni Mar 5, 2026
a694e92
Docstring
MatthewBonanni Mar 5, 2026
836fed2
Use optimistic_seq_lens_cpu and clean up runner
MatthewBonanni Mar 5, 2026
02e6ee2
Fix arange_size
MatthewBonanni Mar 5, 2026
39807a8
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 5, 2026
09ffa74
Undo disable
MatthewBonanni Mar 5, 2026
1023895
Rename
MatthewBonanni Mar 9, 2026
168ecc2
Rename
MatthewBonanni Mar 9, 2026
79a2127
Docstring
MatthewBonanni Mar 9, 2026
d84e371
Add comment
MatthewBonanni Mar 9, 2026
504a7e1
Move to utils
MatthewBonanni Mar 9, 2026
83c52fd
Comment
MatthewBonanni Mar 9, 2026
a1cd3c8
Clean up unnecessary change
MatthewBonanni Mar 9, 2026
f0f9978
Add comment
MatthewBonanni Mar 9, 2026
1c7d2a7
Restore fast path
MatthewBonanni Mar 9, 2026
74fb29a
Remove unrelated fast path
MatthewBonanni Mar 9, 2026
5f22dce
Clean up
MatthewBonanni Mar 9, 2026
65f18f1
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 10, 2026
741f181
Update comment
MatthewBonanni Mar 10, 2026
1a31810
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 11, 2026
865f2c1
Clean up commit_slot_mapping (dead code)
MatthewBonanni Mar 11, 2026
70c12ed
Accumulate rejections and only issue copies when previous has been co…
MatthewBonanni Mar 11, 2026
279b867
Fix
MatthewBonanni Mar 11, 2026
790a8ab
Revert "Accumulate rejections and only issue copies when previous has…
MatthewBonanni Mar 12, 2026
abc93ec
Update CPU side with _finalize_async_spec_cpu_state
MatthewBonanni Mar 12, 2026
e2d5dbc
Clean up
MatthewBonanni Mar 12, 2026
8980abb
Clean up
MatthewBonanni Mar 12, 2026
7a3412a
Comments
MatthewBonanni Mar 12, 2026
1cac626
Clean up
MatthewBonanni Mar 12, 2026
4192b65
Comment
MatthewBonanni Mar 12, 2026
1c2499d
Use deferred_spec_decode_corrections per Lucas's suggestion
MatthewBonanni Mar 18, 2026
30dae23
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 18, 2026
f852fcc
Fix type
MatthewBonanni Mar 20, 2026
5152276
Clean up
MatthewBonanni Mar 20, 2026
39b8b38
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 20, 2026
377d609
Fix mamba align
MatthewBonanni Mar 20, 2026
51eb871
Fix ngram during correction
MatthewBonanni Mar 20, 2026
6811111
Fix mamba
MatthewBonanni Mar 20, 2026
26741ad
Fix ngram
MatthewBonanni Mar 20, 2026
dfd0896
Clean up
MatthewBonanni Mar 20, 2026
ed3fb14
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 20, 2026
6bed7ac
Fix
MatthewBonanni Mar 23, 2026
7e29a71
Merge branch 'main' into async-eagle-mod
MatthewBonanni Mar 23, 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
24 changes: 24 additions & 0 deletions vllm/config/vllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,30 @@ def __post_init__(self):
else:
self.parallel_config.disable_nccl_for_dp_synchronization = False

if (
self.speculative_config is not None
and self.scheduler_config.async_scheduling
):
if (
self.model_config is not None
and not self.model_config.disable_cascade_attn
):
logger.warning_once(
"Disabling cascade attention (not yet compatible with "
"async speculative decoding).",
scope="local",
)
self.model_config.disable_cascade_attn = True

if self.cache_config.mamba_cache_mode == "align":
Comment thread
MatthewBonanni marked this conversation as resolved.
Outdated
logger.warning_once(
"Mamba cache mode 'align' is not compatible with async speculative "
"decoding, as it relies on cpu num_computed_tokens.",
scope="local",
)
self.cache_config.mamba_cache_mode = "none"
self.cache_config.enable_prefix_caching = False

Comment thread
MatthewBonanni marked this conversation as resolved.
from vllm.platforms import current_platform

if (
Expand Down
2 changes: 1 addition & 1 deletion vllm/v1/executor/multiproc_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ def check_health(self) -> None:
def max_concurrent_batches(self) -> int:
# PP requires PP-size concurrent batches to fill the pipeline.
pp_size = self.parallel_config.pipeline_parallel_size
return 2 if pp_size <= 1 and self.scheduler_config.async_scheduling else pp_size
return 3 if pp_size <= 1 and self.scheduler_config.async_scheduling else pp_size
Comment thread
MatthewBonanni marked this conversation as resolved.
Outdated

def _get_output_rank(self) -> int:
# Only returns ModelRunnerOutput from TP rank=0 and PP rank=-1
Expand Down
15 changes: 11 additions & 4 deletions vllm/v1/spec_decode/eagle.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,11 +648,18 @@ def prepare_next_token_ids_padded(
"""
# Precompute get_token_id for when there is no valid next token
num_reqs = gpu_input_batch.num_reqs
num_computed_tokens_cpu = gpu_input_batch.num_computed_tokens_cpu_tensor[
:num_reqs
]
query_lens_cpu = (
common_attn_metadata.query_start_loc_cpu[1 : num_reqs + 1]
- common_attn_metadata.query_start_loc_cpu[:num_reqs]
)
seq_lens_cpu = num_computed_tokens_cpu + query_lens_cpu
seq_lens_list = seq_lens_cpu.tolist()
Comment thread
MatthewBonanni marked this conversation as resolved.
Outdated
self.backup_next_token_ids.np[:num_reqs] = np.array(
[
requests[gpu_input_batch.req_ids[i]].get_token_id(
common_attn_metadata.seq_lens_cpu[i].item()
)
requests[gpu_input_batch.req_ids[i]].get_token_id(seq_lens_list[i])
Comment thread
MatthewBonanni marked this conversation as resolved.
for i in range(num_reqs)
],
dtype=np.int32,
Expand Down Expand Up @@ -737,7 +744,7 @@ def prepare_inputs_padded(
num_reqs=common_attn_metadata.num_reqs,
num_actual_tokens=total_num_tokens,
max_query_len=new_query_len_per_req.max().item(),
max_seq_len=common_attn_metadata.seq_lens_cpu.max().item(),
max_seq_len=common_attn_metadata.max_seq_len,
block_table_tensor=common_attn_metadata.block_table_tensor,
slot_mapping=common_attn_metadata.slot_mapping[:total_num_tokens],
causal=True,
Expand Down
14 changes: 7 additions & 7 deletions vllm/v1/worker/block_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def swap_row(self, src: int, tgt: int) -> None:
self.block_table.np[src_tgt] = self.block_table.np[tgt_src]

def compute_slot_mapping(
self, req_indices: np.ndarray, positions: np.ndarray
self, req_indices: torch.Tensor, positions: torch.Tensor
) -> None:
# E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2]
# -> [0, 0, K, K, K + 1, K + 1, K + 2, 2 * K, 2 * K, 2 * K + 1]
Expand All @@ -154,7 +154,7 @@ def compute_slot_mapping(
+ positions // virtual_block_size
)

block_numbers = self.block_table.np.ravel()[block_table_indices]
block_numbers = self.block_table.gpu.view(-1)[block_table_indices]
# Use virtual_block_size for mask calculation, which marks local
# tokens.
virtual_block_offsets = positions % virtual_block_size
Expand All @@ -174,20 +174,20 @@ def compute_slot_mapping(
# Calculate slot_mapping
slot_mapping = block_numbers * self.block_size + block_offsets
# Write final slots, use -1 for not-local
self.slot_mapping.np[: req_indices.shape[0]] = np.where(
self.slot_mapping.gpu[: req_indices.shape[0]] = torch.where(
mask, slot_mapping, -1
)
else:
block_table_indices = (
req_indices * self.max_num_blocks_per_req + positions // self.block_size
)

block_numbers = self.block_table.np.ravel()[block_table_indices]
block_numbers = self.block_table.gpu.view(-1)[block_table_indices]
block_offsets = positions % self.block_size
np.add(
torch.add(
block_numbers * self.block_size,
block_offsets,
out=self.slot_mapping.np[: req_indices.shape[0]],
out=self.slot_mapping.gpu[: req_indices.shape[0]],
)

def commit_block_table(self, num_reqs: int) -> None:
Expand Down Expand Up @@ -320,7 +320,7 @@ def swap_row(self, src: int, tgt: int) -> None:
block_table.swap_row(src, tgt)

def compute_slot_mapping(
self, req_indices: np.ndarray, positions: np.ndarray
self, req_indices: torch.Tensor, positions: torch.Tensor
) -> None:
for block_table in self.block_tables:
block_table.compute_slot_mapping(req_indices, positions)
Expand Down
39 changes: 26 additions & 13 deletions vllm/v1/worker/gpu_input_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def __init__(

# Speculative decoding
self.num_accepted_tokens_cpu_tensor = torch.ones(
(max_num_reqs,), dtype=torch.int64, device="cpu", pin_memory=pin_memory
(max_num_reqs,), dtype=torch.int32, device="cpu", pin_memory=pin_memory
)
self.num_accepted_tokens_cpu = self.num_accepted_tokens_cpu_tensor.numpy()

Expand Down Expand Up @@ -938,6 +938,9 @@ def update_async_output_token_ids(self) -> None:
In async scheduling case, update output_token_ids in sampling metadata
from prior steps sampled token ids once they've finished copying to CPU.
This is called right before they are needed by the logits processors.

For async spec decode mode: extends output_token_ids with actual tokens.
For non-async spec decode mode: replaces -1 placeholders with actual tokens.
"""
output_token_ids = self.sampling_metadata.output_token_ids
if self.sampled_token_ids_cpu is None or not output_token_ids:
Expand All @@ -951,10 +954,6 @@ def update_async_output_token_ids(self) -> None:
if prev_index is None:
continue
req_output_token_ids = output_token_ids[index]
if not req_output_token_ids or req_output_token_ids[-1] != -1:
# Final output id is not a placeholder, some tokens must have
# been discarded after a kv-load failure.
continue
Comment thread
MatthewBonanni marked this conversation as resolved.
if sampled_token_ids is None:
assert self.async_copy_ready_event is not None
self.async_copy_ready_event.synchronize()
Expand All @@ -964,14 +963,28 @@ def update_async_output_token_ids(self) -> None:
if not new_ids:
continue
num_sampled_ids = len(new_ids) if new_ids[-1] != -1 else new_ids.index(-1)
# Also account for case where there may be a smaller number of
# output placeholders (tokens can be discarded after a kv-load failure).
first_placeholder = req_output_token_ids.index(-1)
num_placeholders = len(req_output_token_ids) - first_placeholder
num_to_replace = min(num_sampled_ids, num_placeholders)
del new_ids[num_to_replace:]
end_index = first_placeholder + num_to_replace
req_output_token_ids[first_placeholder:end_index] = new_ids
if num_sampled_ids == 0:
continue

# Check if there are placeholders to replace.
has_placeholders = req_output_token_ids and req_output_token_ids[-1] == -1

if has_placeholders:
# Replace placeholder token id(s) with actual sampled id(s).
# Also account for case where there may be a smaller number of
# output placeholders (tokens can be discarded after kv-load failure).
first_placeholder = req_output_token_ids.index(-1)
num_placeholders = len(req_output_token_ids) - first_placeholder
num_to_replace = min(num_sampled_ids, num_placeholders)
end_index = first_placeholder + num_to_replace
req_output_token_ids[first_placeholder:end_index] = new_ids[
:num_to_replace
]
else:
# No placeholders - extend with actual sampled tokens.
# This is used in async spec decode mode where we don't add
# placeholders upfront.
req_output_token_ids.extend(new_ids[:num_sampled_ids])

def update_async_spec_token_ids(self, draft_token_ids: list[list[int]]) -> None:
"""
Expand Down
Loading