Files
vllm-project--vllm/vllm/v1/spec_decode/dynamic/utils.py
T
wehub-resource-sync 7ce4c8e27e
pre-commit / pre-run-check (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:55:37 +08:00

149 lines
5.5 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
DynamicSDSchedule = list[tuple[int, int, int]]
def validate_and_normalize_dynamic_sd_schedule(
num_speculative_tokens_per_batch_size: object,
) -> DynamicSDSchedule:
"""Validate and normalize a Dynamic SD batch-size schedule.
The schedule is expressed as a list of inclusive ranges:
``[(range_start, range_end, num_speculative_tokens), ...]``
"""
if num_speculative_tokens_per_batch_size is None:
raise ValueError(
"num_speculative_tokens_per_batch_size is required for "
"dynamic speculative decoding."
)
if not isinstance(num_speculative_tokens_per_batch_size, list):
raise ValueError(
"num_speculative_tokens_per_batch_size must be a non-empty list of "
"(range_start, range_end, num_speculative_tokens) entries."
)
if not num_speculative_tokens_per_batch_size:
raise ValueError("num_speculative_tokens_per_batch_size must not be empty.")
parsed_schedule: DynamicSDSchedule = []
for entry in num_speculative_tokens_per_batch_size:
if not isinstance(entry, list | tuple) or len(entry) != 3:
raise ValueError(
"Each num_speculative_tokens_per_batch_size entry must be a "
"3-item sequence: (range_start, range_end, num_speculative_tokens)."
)
range_start, range_end, num_speculative_tokens = (
int(entry[0]),
int(entry[1]),
int(entry[2]),
)
if range_start <= 0 or range_end <= 0:
raise ValueError(
f"Batch-size range ({range_start}, {range_end}) must be positive."
)
if range_start > range_end:
raise ValueError(
"Batch-size range start must be <= end for "
f"({range_start}, {range_end}, {num_speculative_tokens})."
)
if num_speculative_tokens < 0:
raise ValueError(
"num_speculative_tokens_per_batch_size values must be >= 0."
)
parsed_schedule.append((range_start, range_end, num_speculative_tokens))
parsed_schedule.sort(key=lambda entry: entry[0])
previous_end = 0
for range_start, range_end, _ in parsed_schedule:
if range_start <= previous_end:
raise ValueError("Batch-size ranges must be non-overlapping and sorted.")
previous_end = range_end
first_range_start = parsed_schedule[0][0]
if first_range_start != 1:
raise ValueError(
"The first batch-size range must start at 1 so every runtime "
"batch size has a defined schedule."
)
return parsed_schedule
def build_dynamic_sd_schedule_lookup(
num_speculative_tokens_per_batch_size: object,
vllm_max_batch_size: int,
vllm_num_speculative_tokens: int,
) -> list[int]:
"""Expand the configured schedule into a dense batch_size -> K lookup.
"dense_schedule" means a 1-indexed lookup table where index ``batch_size``
stores the exact K to use for that runtime batch size. This lets the
scheduler do a simple array lookup instead of searching the configured
ranges on every scheduling step.
"""
if vllm_max_batch_size <= 0:
raise ValueError("vllm_max_batch_size must be > 0.")
if vllm_num_speculative_tokens <= 0:
raise ValueError("vllm_num_speculative_tokens must be > 0.")
parsed_schedule = validate_and_normalize_dynamic_sd_schedule(
num_speculative_tokens_per_batch_size
)
# Index 0 is intentionally unused so that valid runtime batch sizes can be
# looked up directly as dense_schedule[batch_size].
dense_schedule = [0] * (vllm_max_batch_size + 1)
next_batch_size = 1
last_num_speculative_tokens: int | None = None
for range_start, range_end, num_speculative_tokens in parsed_schedule:
if range_start > next_batch_size and last_num_speculative_tokens is not None:
# Fill any gap before the next configured range by carrying forward
# the previous K. For example, [(1, 16, 3), (32, 128, 2)] should map
# batch sizes 17-31 to K=3.
for batch_size in range(
next_batch_size,
min(range_start, vllm_max_batch_size + 1),
):
dense_schedule[batch_size] = min(
vllm_num_speculative_tokens,
last_num_speculative_tokens,
)
# Fill the current configured inclusive range with its K value.
for batch_size in range(
max(range_start, next_batch_size),
min(range_end, vllm_max_batch_size) + 1,
):
dense_schedule[batch_size] = min(
vllm_num_speculative_tokens,
num_speculative_tokens,
)
next_batch_size = max(next_batch_size, range_end + 1)
last_num_speculative_tokens = num_speculative_tokens
if next_batch_size > vllm_max_batch_size:
break
if last_num_speculative_tokens is None:
raise ValueError(
"num_speculative_tokens_per_batch_size must contain at least "
"one valid batch-size range."
)
# Fill the tail after the final configured range by carrying forward the
# last K through vllm_max_batch_size.
for batch_size in range(next_batch_size, vllm_max_batch_size + 1):
dense_schedule[batch_size] = min(
vllm_num_speculative_tokens,
last_num_speculative_tokens,
)
return dense_schedule