forked from Karylab-cklius/vllm
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
166a8e954b |
@@ -173,7 +173,12 @@ impl ResolvedPlaceholder {
|
|||||||
let marker_token_id = context.tokenizer().token_to_id(&token).ok_or_else(|| {
|
let marker_token_id = context.tokenizer().token_to_id(&token).ok_or_else(|| {
|
||||||
multimodal!("placeholder token `{token}` is not in the tokenizer vocabulary")
|
multimodal!("placeholder token `{token}` is not in the tokenizer vocabulary")
|
||||||
})?;
|
})?;
|
||||||
let embed_token_id = raw.placeholder_token_id_for(&metadata, modality)? as u32;
|
let embed_token_id = raw.placeholder_token_id_for(&metadata, modality)?;
|
||||||
|
let embed_token_id = u32::try_from(embed_token_id).map_err(|_| {
|
||||||
|
multimodal!(
|
||||||
|
"placeholder token id `{embed_token_id}` for `{modality}` must be non-negative"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
token,
|
token,
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def test_arch_mapping_applies_before_callable_override():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.cpu_test
|
@pytest.mark.cpu_test
|
||||||
def test_inkling_override_exposes_only_first_mtp_depth():
|
def test_inkling_override_exposes_checkpoint_mtp_depths():
|
||||||
text_config = _make_hf_config(
|
text_config = _make_hf_config(
|
||||||
architectures=["InklingForCausalLM"],
|
architectures=["InklingForCausalLM"],
|
||||||
model_type="inkling_model",
|
model_type="inkling_model",
|
||||||
@@ -107,7 +107,7 @@ def test_inkling_override_exposes_only_first_mtp_depth():
|
|||||||
assert out is text_config
|
assert out is text_config
|
||||||
assert out.model_type == "inkling_mtp"
|
assert out.model_type == "inkling_mtp"
|
||||||
assert out.architectures == ["InklingMTPModel"]
|
assert out.architectures == ["InklingMTPModel"]
|
||||||
assert out.n_predict == 1
|
assert out.n_predict == 8
|
||||||
assert out.num_nextn_predict_layers == 8
|
assert out.num_nextn_predict_layers == 8
|
||||||
assert out.chain_hidden_post_norm is False
|
assert out.chain_hidden_post_norm is False
|
||||||
assert out.local_layer_ids == [0, 2, 4]
|
assert out.local_layer_ids == [0, 2, 4]
|
||||||
|
|||||||
@@ -1422,16 +1422,15 @@ class CompilationConfig:
|
|||||||
"and make sure compilation mode is VLLM_COMPILE"
|
"and make sure compilation mode is VLLM_COMPILE"
|
||||||
)
|
)
|
||||||
|
|
||||||
# MRV1 adjusts cudagraph sizes to be a multiple of uniform_decode_query_len
|
# Round cudagraph sizes to a multiple of uniform_decode_query_len so a
|
||||||
# to avoid: https://github.com/vllm-project/vllm/issues/28207 and temp-fix:
|
# uniform decode batch cannot dispatch to a "mixed" descriptor whose token
|
||||||
# https://github.com/vllm-project/vllm/issues/28207#issuecomment-3504004536
|
# count is not a multiple of the decode span, which corrupts per-request
|
||||||
# Will be removed in the near future when we have separate cudagraph capture
|
# conv/attention state (https://github.com/vllm-project/vllm/issues/28207).
|
||||||
# sizes for decode and mixed prefill-decode.
|
# Enabled for MRV2 as well: its cudagraph_utils.py sizing does not on its
|
||||||
# MRV2 handles cudagraph capture sizing in cudagraph_utils.py
|
# own keep uniform batches off mixed descriptors for multi-module MTP.
|
||||||
# and doesn't need below: https://github.com/vllm-project/vllm/pull/45953
|
# Temporary until decode and mixed prefill-decode have separate sizes.
|
||||||
if (
|
if (
|
||||||
not use_v2_model_runner
|
cudagraph_mode.decode_mode() == CUDAGraphMode.FULL
|
||||||
and cudagraph_mode.decode_mode() == CUDAGraphMode.FULL
|
|
||||||
and uniform_decode_query_len > 1
|
and uniform_decode_query_len > 1
|
||||||
):
|
):
|
||||||
self.adjust_cudagraph_sizes_for_spec_decode(
|
self.adjust_cudagraph_sizes_for_spec_decode(
|
||||||
|
|||||||
+14
-10
@@ -555,6 +555,8 @@ class SpeculativeConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if hf_config.model_type in ("inkling_mm_model", "inkling_model"):
|
if hf_config.model_type in ("inkling_mm_model", "inkling_model"):
|
||||||
|
# MTP weights live on the text backbone. Promote text_config and
|
||||||
|
# expose the nested MTP fields on the flat draft config.
|
||||||
mtp_config = getattr(hf_config, "mtp_config", None) or {}
|
mtp_config = getattr(hf_config, "mtp_config", None) or {}
|
||||||
hf_config = getattr(hf_config, "text_config", hf_config)
|
hf_config = getattr(hf_config, "text_config", hf_config)
|
||||||
checkpoint_depths = mtp_config.get("num_nextn_predict_layers", 0)
|
checkpoint_depths = mtp_config.get("num_nextn_predict_layers", 0)
|
||||||
@@ -563,12 +565,13 @@ class SpeculativeConfig:
|
|||||||
hf_config.model_type = "inkling_mtp"
|
hf_config.model_type = "inkling_mtp"
|
||||||
hf_config.update(
|
hf_config.update(
|
||||||
{
|
{
|
||||||
# Inkling currently exposes only the first checkpoint depth.
|
"n_predict": checkpoint_depths,
|
||||||
"n_predict": 1,
|
|
||||||
"num_nextn_predict_layers": checkpoint_depths,
|
"num_nextn_predict_layers": checkpoint_depths,
|
||||||
"chain_hidden_post_norm": mtp_config.get(
|
"chain_hidden_post_norm": mtp_config.get(
|
||||||
"chain_hidden_post_norm", False
|
"chain_hidden_post_norm", False
|
||||||
),
|
),
|
||||||
|
# The MTP depth blocks carry their own sliding-window
|
||||||
|
# pattern, which differs from the backbone's layer pattern.
|
||||||
"local_layer_ids": mtp_config.get("local_layer_ids", []),
|
"local_layer_ids": mtp_config.get("local_layer_ids", []),
|
||||||
"architectures": ["InklingMTPModel"],
|
"architectures": ["InklingMTPModel"],
|
||||||
}
|
}
|
||||||
@@ -974,14 +977,6 @@ class SpeculativeConfig:
|
|||||||
"`num_speculative_tokens` was not provided"
|
"`num_speculative_tokens` was not provided"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
|
||||||
self.draft_model_config.hf_config.model_type == "inkling_mtp"
|
|
||||||
and self.num_speculative_tokens != 1
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"Inkling MTP currently supports exactly one speculative token"
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.method == "dspark":
|
if self.method == "dspark":
|
||||||
# DSpark is a semi-autoregressive *block* drafter. A
|
# DSpark is a semi-autoregressive *block* drafter. A
|
||||||
# speculative length smaller than the checkpoint's block
|
# speculative length smaller than the checkpoint's block
|
||||||
@@ -1308,6 +1303,15 @@ class SpeculativeConfig:
|
|||||||
# TODO(ben): Refactor this so the naming is clearer
|
# TODO(ben): Refactor this so the naming is clearer
|
||||||
return self.method in ("eagle", "eagle3", "mtp", "dflash", "dspark")
|
return self.method in ("eagle", "eagle3", "mtp", "dflash", "dspark")
|
||||||
|
|
||||||
|
def num_speculative_prefill_steps(self) -> int:
|
||||||
|
if self.method == "mtp" and self.draft_model_config is not None:
|
||||||
|
n_predict = getattr(
|
||||||
|
self.draft_model_config.hf_config, "num_nextn_predict_layers", None
|
||||||
|
)
|
||||||
|
if n_predict:
|
||||||
|
return min(int(n_predict), self.num_speculative_tokens)
|
||||||
|
return 1
|
||||||
|
|
||||||
def use_dflash(self) -> bool:
|
def use_dflash(self) -> bool:
|
||||||
return self.method == "dflash"
|
return self.method == "dflash"
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ class InklingDecoderLayer(nn.Module):
|
|||||||
# Caller folds mlp_output (pre-reduce, pre-sconv) into the next
|
# Caller folds mlp_output (pre-reduce, pre-sconv) into the next
|
||||||
# fused sconv+add+rmsnorm.
|
# fused sconv+add+rmsnorm.
|
||||||
return hidden_states, (mlp_output, self.mlp_sconv)
|
return hidden_states, (mlp_output, self.mlp_sconv)
|
||||||
|
# Standalone (MTP) tail: finish the sublayer without a norm.
|
||||||
return _sconv_add_norm(
|
return _sconv_add_norm(
|
||||||
mlp_output, hidden_states, self.mlp_sconv, None, positions
|
mlp_output, hidden_states, self.mlp_sconv, None, positions
|
||||||
)[1]
|
)[1]
|
||||||
@@ -236,10 +237,12 @@ class InklingReplicatedEmbedding(nn.Module):
|
|||||||
"""Full-vocab embedding table replicated on every TP rank.
|
"""Full-vocab embedding table replicated on every TP rank.
|
||||||
|
|
||||||
Trades the full table per rank (~2.3 GiB at V=201k / H=6144 bf16, vs a
|
Trades the full table per rank (~2.3 GiB at V=201k / H=6144 bf16, vs a
|
||||||
1/tp shard) for no masked lookup or per-lookup TP all-reduce, and keeps the
|
1/tp shard) for no masked lookup and no per-lookup TP all-reduce — one
|
||||||
full table on-rank for the fused gather+norm kernel. Bit-exact vs
|
all-reduce per MTP draft step plus one per verify pass — and keeps the
|
||||||
vocab-parallel: the all-reduce there only ever summed one real row against
|
full table on-rank for the fused gather+norm kernels (``embed_rmsnorm``,
|
||||||
exact zeros. The LM head stays vocab-sharded.
|
``embed_dual_rmsnorm_cat``). Bit-exact vs vocab-parallel: the all-reduce
|
||||||
|
there only ever summed one real row against exact zeros. The LM head
|
||||||
|
stays vocab-sharded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, num_embeddings: int, embedding_dim: int) -> None:
|
def __init__(self, num_embeddings: int, embedding_dim: int) -> None:
|
||||||
@@ -611,7 +614,8 @@ class InklingForConditionalGeneration(_TmlForCausalLMBase, SupportsMultiModal):
|
|||||||
def get_language_model(self) -> nn.Module:
|
def get_language_model(self) -> nn.Module:
|
||||||
# This class IS the causal LM (the towers are side branches), so the
|
# This class IS the causal LM (the towers are side branches), so the
|
||||||
# language model is self — callers expect a module exposing ``.model``
|
# language model is self — callers expect a module exposing ``.model``
|
||||||
# and ``.lm_head``.
|
# / ``.lm_head`` (e.g. the MTP/eagle loader shares embeddings via
|
||||||
|
# ``get_language_model().model.embed_tokens``).
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
@@ -672,8 +676,6 @@ def _load_inkling_weights(
|
|||||||
|
|
||||||
yield name, weight
|
yield name, weight
|
||||||
|
|
||||||
# The release checkpoint also carries auxiliary prediction-head weights;
|
|
||||||
# they are not part of the causal LM served by this implementation.
|
|
||||||
loader = AutoWeightsLoader(module, skip_prefixes=["model.mtp."])
|
loader = AutoWeightsLoader(module, skip_prefixes=["model.mtp."])
|
||||||
loaded |= loader.load_weights(_iter_loadable_weights())
|
loaded |= loader.load_weights(_iter_loadable_weights())
|
||||||
|
|
||||||
|
|||||||
@@ -475,9 +475,9 @@ class InklingMoE(nn.Module):
|
|||||||
lambda: self.sink_experts(x, gammas),
|
lambda: self.sink_experts(x, gammas),
|
||||||
self._sink_events[0],
|
self._sink_events[0],
|
||||||
self._sink_events[1],
|
self._sink_events[1],
|
||||||
self._sink_stream
|
self._sink_stream,
|
||||||
if num_tokens <= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD
|
enable_parallel=num_tokens
|
||||||
else None,
|
<= envs.VLLM_SHARED_EXPERTS_STREAM_TOKEN_THRESHOLD,
|
||||||
)
|
)
|
||||||
self._routed_sel = None
|
self._routed_sel = None
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||||
"""Inkling MTP (Multi-Token Prediction) draft model (NVIDIA).
|
"""Inkling MTP (Multi-Token Prediction) draft model (NVIDIA).
|
||||||
|
|
||||||
Implements the first MTP depth from the reference ``mtp_model.py`` shipped with
|
Mirrors the reference ``mtp_model.py`` shipped with the checkpoint: each MTP
|
||||||
the checkpoint. It owns ``hidden_norm`` / ``embed_norm`` RMSNorms, a ``2H -> H``
|
depth ``i`` owns ``hidden_norm`` / ``embed_norm`` RMSNorms, an ``input_proj``
|
||||||
input projection, and a full Inkling transformer block with a dense bf16 MLP.
|
(``2H -> H``) and a full Inkling transformer block (dense bf16 MLP, with the
|
||||||
|
same short convolutions as the backbone; its attention is full or sliding-window
|
||||||
|
per depth, selected by ``mtp_config.local_layer_ids``). When enabled, a shared
|
||||||
|
``chain_norm`` is applied after every depth; its output is both the logits input
|
||||||
|
and the previous hidden state fed to the next depth.
|
||||||
|
|
||||||
The draft shares the target's token embedding table and LM head
|
The draft shares the target's token embedding table and LM head
|
||||||
(``load_eagle_model`` wires those references) and applies the backbone
|
(``load_eagle_model`` wires those references) and applies the backbone
|
||||||
@@ -50,6 +54,16 @@ def _mtp_depth_from_name(name: str) -> int | None:
|
|||||||
return int(m.group(1)) if m else None
|
return int(m.group(1)) if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def _select_mtp_depth_count(n_predict: int, num_spec: int | None) -> int:
|
||||||
|
num_layers = min(n_predict, num_spec) if num_spec else n_predict
|
||||||
|
if num_layers <= 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Inkling MTP requires num_nextn_predict_layers and "
|
||||||
|
"num_speculative_tokens to select at least one depth layer."
|
||||||
|
)
|
||||||
|
return num_layers
|
||||||
|
|
||||||
|
|
||||||
class InklingMTPDepthLayer(nn.Module):
|
class InklingMTPDepthLayer(nn.Module):
|
||||||
"""One MTP depth: norm both inputs, fuse (2H->H), run a Inkling block."""
|
"""One MTP depth: norm both inputs, fuse (2H->H), run a Inkling block."""
|
||||||
|
|
||||||
@@ -94,14 +108,30 @@ class InklingMultiTokenPredictor(nn.Module):
|
|||||||
vllm_config.speculative_config.draft_model_config.hf_config
|
vllm_config.speculative_config.draft_model_config.hf_config
|
||||||
)
|
)
|
||||||
self.config = config
|
self.config = config
|
||||||
if vllm_config.speculative_config.num_speculative_tokens != 1:
|
# The checkpoint ships num_nextn_predict_layers depth blocks, but only
|
||||||
raise ValueError(
|
# the first ``num_speculative_tokens`` are exercised (step i uses depth
|
||||||
"Inkling MTP currently supports exactly one speculative token"
|
# i). Build only those to save memory — each depth is a full Inkling block
|
||||||
)
|
# with its own (large) full-history sconv caches and KV cache.
|
||||||
|
n_predict = config.num_nextn_predict_layers
|
||||||
|
num_spec = vllm_config.speculative_config.num_speculative_tokens
|
||||||
|
self.num_mtp_layers = _select_mtp_depth_count(n_predict, num_spec)
|
||||||
self.chain_hidden_post_norm = config.chain_hidden_post_norm
|
self.chain_hidden_post_norm = config.chain_hidden_post_norm
|
||||||
|
|
||||||
|
# Depth blocks whose attention is sliding-window (swa_* head config)
|
||||||
|
# rather than full; keyed by MTP depth via the checkpoint's
|
||||||
|
# mtp_config.local_layer_ids (promoted onto the draft config). Mirrors
|
||||||
|
# InklingModel's local_ids split, but over MTP depths, not backbone
|
||||||
|
# layers.
|
||||||
local_ids = set(config.local_layer_ids)
|
local_ids = set(config.local_layer_ids)
|
||||||
|
|
||||||
|
# Keyed by depth index (str) to mirror the checkpoint layout.
|
||||||
self.layers = nn.ModuleDict(
|
self.layers = nn.ModuleDict(
|
||||||
{"0": InklingMTPDepthLayer(config, f"{prefix}.layers.0", 0 in local_ids)}
|
{
|
||||||
|
str(idx): InklingMTPDepthLayer(
|
||||||
|
config, f"{prefix}.layers.{idx}", idx in local_ids
|
||||||
|
)
|
||||||
|
for idx in range(self.num_mtp_layers)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
self.chain_norm = (
|
self.chain_norm = (
|
||||||
InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
InklingRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||||
@@ -199,9 +229,8 @@ class InklingMultiTokenPredictor(nn.Module):
|
|||||||
# auto-enumerated as a draft attention layer); its per-token metadata is
|
# auto-enumerated as a draft attention layer); its per-token metadata is
|
||||||
# built by the speculator's build_attn_metadata and read from the
|
# built by the speculator's build_attn_metadata and read from the
|
||||||
# forward context, so nothing extra is threaded here.
|
# forward context, so nothing extra is threaded here.
|
||||||
if spec_step_idx != 0:
|
depth = spec_step_idx % self.num_mtp_layers
|
||||||
raise ValueError("Inkling MTP only supports spec_step_idx=0")
|
layer = self.layers[str(depth)]
|
||||||
layer = self.layers["0"]
|
|
||||||
combined = self.fused_input_cat(
|
combined = self.fused_input_cat(
|
||||||
layer, previous_hidden_states, input_ids, inputs_embeds
|
layer, previous_hidden_states, input_ids, inputs_embeds
|
||||||
)
|
)
|
||||||
@@ -353,8 +382,8 @@ def _load_inkling_mtp_weights(
|
|||||||
# Only consume the MTP weights; everything else belongs to the target.
|
# Only consume the MTP weights; everything else belongs to the target.
|
||||||
if ".mtp." not in name:
|
if ".mtp." not in name:
|
||||||
continue
|
continue
|
||||||
# Only the first checkpoint depth is used for MTP=1.
|
# Skip depth blocks beyond the ones we built (num_speculative_tokens).
|
||||||
if depth is not None and depth != 0:
|
if depth is not None and depth >= module.model.num_mtp_layers:
|
||||||
continue
|
continue
|
||||||
# model.mtp.chain_norm.weight -> model.chain_norm.weight
|
# model.mtp.chain_norm.weight -> model.chain_norm.weight
|
||||||
# model.mtp.layers.{i}.X -> model.layers.{i}.X
|
# model.mtp.layers.{i}.X -> model.layers.{i}.X
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ def maybe_execute_in_parallel(
|
|||||||
event0: torch.cuda.Event,
|
event0: torch.cuda.Event,
|
||||||
event1: torch.cuda.Event,
|
event1: torch.cuda.Event,
|
||||||
aux_stream: torch.cuda.Stream | None = None,
|
aux_stream: torch.cuda.Stream | None = None,
|
||||||
|
enable_parallel: bool = True,
|
||||||
) -> tuple[Any, Any]:
|
) -> tuple[Any, Any]:
|
||||||
"""Run two functions potentially in parallel on separate CUDA streams.
|
"""Run two functions potentially in parallel on separate CUDA streams.
|
||||||
|
|
||||||
@@ -40,11 +41,12 @@ def maybe_execute_in_parallel(
|
|||||||
event1: CUDA event recorded after fn1 so default stream can wait.
|
event1: CUDA event recorded after fn1 so default stream can wait.
|
||||||
aux_stream: The second CUDA stream for fn1.
|
aux_stream: The second CUDA stream for fn1.
|
||||||
Multi-stream is disabled when aux_stream is None.
|
Multi-stream is disabled when aux_stream is None.
|
||||||
|
enable_parallel: Opt-in switch for the multi-stream path. Defaults to True.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple of (fn0_result, fn1_result).
|
Tuple of (fn0_result, fn1_result).
|
||||||
"""
|
"""
|
||||||
if aux_stream is not None:
|
if aux_stream is not None and enable_parallel:
|
||||||
event0.record()
|
event0.record()
|
||||||
result0 = fn0()
|
result0 = fn0()
|
||||||
with torch.cuda.stream(aux_stream):
|
with torch.cuda.stream(aux_stream):
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class KVCacheCoordinator(ABC):
|
|||||||
for g in kv_cache_config.kv_cache_groups
|
for g in kv_cache_config.kv_cache_groups
|
||||||
)
|
)
|
||||||
self.scheduler_block_size = scheduler_block_size
|
self.scheduler_block_size = scheduler_block_size
|
||||||
|
self.num_spec_prefill_steps = 1
|
||||||
|
|
||||||
self.block_pool = BlockPool(
|
self.block_pool = BlockPool(
|
||||||
num_gpu_blocks=kv_cache_config.num_blocks,
|
num_gpu_blocks=kv_cache_config.num_blocks,
|
||||||
@@ -281,9 +282,14 @@ class KVCacheCoordinator(ABC):
|
|||||||
(including tokens that are already cached).
|
(including tokens that are already cached).
|
||||||
"""
|
"""
|
||||||
for manager in self.single_type_managers:
|
for manager in self.single_type_managers:
|
||||||
|
# Only cache tokens with finalized KV. The last num_spec_prefill_steps - 1
|
||||||
|
# tokens can be re-prefilled by speculative modules.
|
||||||
|
num_tokens_to_cache = max(
|
||||||
|
0, num_computed_tokens - (self.num_spec_prefill_steps - 1)
|
||||||
|
)
|
||||||
manager.cache_blocks(
|
manager.cache_blocks(
|
||||||
request,
|
request,
|
||||||
num_computed_tokens,
|
num_tokens_to_cache,
|
||||||
retention_interval=self.retention_interval,
|
retention_interval=self.retention_interval,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -661,9 +667,20 @@ class HybridKVCacheCoordinator(KVCacheCoordinator):
|
|||||||
# EAGLE groups match one block past each aligned boundary and drop
|
# EAGLE groups match one block past each aligned boundary and drop
|
||||||
# it, so make that lookahead block eligible to be cached.
|
# it, so make that lookahead block eligible to be cached.
|
||||||
if manager.use_eagle and aligned_num_computed_tokens > 0:
|
if manager.use_eagle and aligned_num_computed_tokens > 0:
|
||||||
|
# Only cache tokens with finalized KV. The last
|
||||||
|
# num_spec_prefill_steps - 1 tokens can be re-prefilled by
|
||||||
|
# speculative modules.
|
||||||
|
num_finalized_computed_tokens = max(
|
||||||
|
0, num_computed_tokens - (self.num_spec_prefill_steps - 1)
|
||||||
|
)
|
||||||
|
aligned_num_finalized_computed_tokens = (
|
||||||
|
num_finalized_computed_tokens
|
||||||
|
// self.scheduler_block_size
|
||||||
|
* self.scheduler_block_size
|
||||||
|
)
|
||||||
num_tokens_to_cache = min(
|
num_tokens_to_cache = min(
|
||||||
num_computed_tokens,
|
num_finalized_computed_tokens,
|
||||||
aligned_num_computed_tokens + manager.block_size,
|
aligned_num_finalized_computed_tokens + manager.block_size,
|
||||||
)
|
)
|
||||||
# The manager already knows the fine hit granularity
|
# The manager already knows the fine hit granularity
|
||||||
# (``scheduler_block_size``); retention is passed separately so it
|
# (``scheduler_block_size``); retention is passed separately so it
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ class KVCacheManager:
|
|||||||
max_in_flight_tokens: int | None = None,
|
max_in_flight_tokens: int | None = None,
|
||||||
enable_caching: bool = True,
|
enable_caching: bool = True,
|
||||||
use_eagle: bool = False,
|
use_eagle: bool = False,
|
||||||
|
num_spec_prefill_steps: int = 1,
|
||||||
log_stats: bool = False,
|
log_stats: bool = False,
|
||||||
enable_kv_cache_events: bool = False,
|
enable_kv_cache_events: bool = False,
|
||||||
dcp_world_size: int = 1,
|
dcp_world_size: int = 1,
|
||||||
@@ -158,6 +159,7 @@ class KVCacheManager:
|
|||||||
hash_block_size=hash_block_size,
|
hash_block_size=hash_block_size,
|
||||||
metrics_collector=self.metrics_collector,
|
metrics_collector=self.metrics_collector,
|
||||||
)
|
)
|
||||||
|
self.coordinator.num_spec_prefill_steps = num_spec_prefill_steps
|
||||||
self.num_kv_cache_groups = len(kv_cache_config.kv_cache_groups)
|
self.num_kv_cache_groups = len(kv_cache_config.kv_cache_groups)
|
||||||
self.block_pool = self.coordinator.block_pool
|
self.block_pool = self.coordinator.block_pool
|
||||||
self.kv_cache_config = kv_cache_config
|
self.kv_cache_config = kv_cache_config
|
||||||
|
|||||||
@@ -2086,6 +2086,26 @@ def get_kv_cache_configs(
|
|||||||
# Check if the KV cache specs are registered correctly.
|
# Check if the KV cache specs are registered correctly.
|
||||||
# This is to prevent that some layers are initialized with unregistered specs.
|
# This is to prevent that some layers are initialized with unregistered specs.
|
||||||
KVCacheSpecRegistry.check_kv_cache_spec_registry(merged_kv_cache_specs)
|
KVCacheSpecRegistry.check_kv_cache_spec_registry(merged_kv_cache_specs)
|
||||||
|
# Under multi-module MTP with prefix caching, tag every sliding-window spec
|
||||||
|
# with the store-side retention lag so pool sizing, the runtime admission
|
||||||
|
# cap, and block eviction all agree that these groups retain a few extra
|
||||||
|
# trailing blocks (single source of truth: the spec field). No-op (lag 0)
|
||||||
|
# otherwise, so non-MTP models are unchanged. Done before grouping so the lag
|
||||||
|
# flows through spec unification (which preserves it via ``replace``).
|
||||||
|
|
||||||
|
# When speculating with more than 1 speculative module (e.g. multi-layered MTP)
|
||||||
|
# tag every SlidingWindowSpec with how many extra tokens to retain in the window.
|
||||||
|
extra_retained_tokens = (
|
||||||
|
vllm_config.speculative_config.num_speculative_prefill_steps() - 1
|
||||||
|
if vllm_config.speculative_config is not None
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
for layer_name, layer_spec in merged_kv_cache_specs.items():
|
||||||
|
if isinstance(layer_spec, SlidingWindowSpec):
|
||||||
|
merged_kv_cache_specs[layer_name] = replace(
|
||||||
|
layer_spec, extra_retained_tokens=extra_retained_tokens
|
||||||
|
)
|
||||||
|
|
||||||
# Get global KV cache groups. This also handles spec unification for
|
# Get global KV cache groups. This also handles spec unification for
|
||||||
# hybrid models when disable_hybrid_kv_cache_manager is enabled.
|
# hybrid models when disable_hybrid_kv_cache_manager is enabled.
|
||||||
# After this call, merged_kv_cache_specs may be modified in-place.
|
# After this call, merged_kv_cache_specs may be modified in-place.
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ class Scheduler(SchedulerInterface):
|
|||||||
speculative_config = vllm_config.speculative_config
|
speculative_config = vllm_config.speculative_config
|
||||||
self.use_eagle = False
|
self.use_eagle = False
|
||||||
self.num_spec_tokens = vllm_config.num_speculative_tokens
|
self.num_spec_tokens = vllm_config.num_speculative_tokens
|
||||||
|
self.num_spec_prefill_steps = 1
|
||||||
self.num_lookahead_tokens = 0
|
self.num_lookahead_tokens = 0
|
||||||
self.dynamic_sd_lookup: list[int] | None = None
|
self.dynamic_sd_lookup: list[int] | None = None
|
||||||
if speculative_config is not None:
|
if speculative_config is not None:
|
||||||
@@ -245,6 +246,9 @@ class Scheduler(SchedulerInterface):
|
|||||||
if speculative_config.use_eagle():
|
if speculative_config.use_eagle():
|
||||||
self.use_eagle = True
|
self.use_eagle = True
|
||||||
self.num_lookahead_tokens = self.num_spec_tokens
|
self.num_lookahead_tokens = self.num_spec_tokens
|
||||||
|
self.num_spec_prefill_steps = (
|
||||||
|
speculative_config.num_speculative_prefill_steps()
|
||||||
|
)
|
||||||
if speculative_config.uses_draft_model():
|
if speculative_config.uses_draft_model():
|
||||||
self.num_lookahead_tokens = self.num_spec_tokens
|
self.num_lookahead_tokens = self.num_spec_tokens
|
||||||
if speculative_config.use_dflash():
|
if speculative_config.use_dflash():
|
||||||
@@ -268,6 +272,7 @@ class Scheduler(SchedulerInterface):
|
|||||||
max_in_flight_tokens=vllm_config.max_in_flight_tokens,
|
max_in_flight_tokens=vllm_config.max_in_flight_tokens,
|
||||||
enable_caching=self.cache_config.enable_prefix_caching,
|
enable_caching=self.cache_config.enable_prefix_caching,
|
||||||
use_eagle=self.use_eagle,
|
use_eagle=self.use_eagle,
|
||||||
|
num_spec_prefill_steps=self.num_spec_prefill_steps,
|
||||||
log_stats=self.log_stats,
|
log_stats=self.log_stats,
|
||||||
enable_kv_cache_events=self.enable_kv_cache_events,
|
enable_kv_cache_events=self.enable_kv_cache_events,
|
||||||
dcp_world_size=self.dcp_world_size,
|
dcp_world_size=self.dcp_world_size,
|
||||||
|
|||||||
@@ -853,6 +853,10 @@ class SlidingWindowManager(SingleTypeKVCacheManager):
|
|||||||
def __init__(self, kv_cache_spec: SlidingWindowSpec, **kwargs) -> None:
|
def __init__(self, kv_cache_spec: SlidingWindowSpec, **kwargs) -> None:
|
||||||
super().__init__(kv_cache_spec, **kwargs)
|
super().__init__(kv_cache_spec, **kwargs)
|
||||||
self.sliding_window = kv_cache_spec.sliding_window
|
self.sliding_window = kv_cache_spec.sliding_window
|
||||||
|
# Extra trailing tokens to retain below the window (never attended) so a
|
||||||
|
# multi-module MTP store-side lag can still reconstruct the window from
|
||||||
|
# cached blocks.
|
||||||
|
self.extra_retained_tokens = kv_cache_spec.extra_retained_tokens
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _contiguous_blocks_for_hit(
|
def _contiguous_blocks_for_hit(
|
||||||
@@ -1048,13 +1052,22 @@ class SlidingWindowManager(SingleTypeKVCacheManager):
|
|||||||
attention computation since they are outside the sliding window.
|
attention computation since they are outside the sliding window.
|
||||||
Thus, get_num_skipped_tokens(7) == 4.
|
Thus, get_num_skipped_tokens(7) == 4.
|
||||||
|
|
||||||
|
The trailing edge of the window is extended by ``extra_retained_tokens``
|
||||||
|
so that those extra trailing tokens' blocks are retained (but not
|
||||||
|
attended). This is needed for multi-module spec decoding which can
|
||||||
|
re-prefill the last num_spec_prefill_tokens - 1 tokens from the end
|
||||||
|
of the sequence, and thus needs to delay freeing/caching of blocks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
num_computed_tokens: The number of tokens that have been computed.
|
num_computed_tokens: The number of tokens that have been computed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The number of tokens that will be skipped for attention computation.
|
The number of tokens that will be skipped for attention computation.
|
||||||
"""
|
"""
|
||||||
return max(0, num_computed_tokens - self.sliding_window + 1)
|
return max(
|
||||||
|
0,
|
||||||
|
num_computed_tokens - self.sliding_window + 1 - self.extra_retained_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
def get_num_common_prefix_blocks(self, running_request_id: str) -> int:
|
def get_num_common_prefix_blocks(self, running_request_id: str) -> int:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -547,6 +547,12 @@ class ChunkedLocalAttentionSpec(AttentionSpec):
|
|||||||
class SlidingWindowSpec(AttentionSpec):
|
class SlidingWindowSpec(AttentionSpec):
|
||||||
sliding_window: int
|
sliding_window: int
|
||||||
head_size_v: int = None # type: ignore[assignment]
|
head_size_v: int = None # type: ignore[assignment]
|
||||||
|
# The trailing edge of the window is extended by ``extra_retained_tokens``
|
||||||
|
# so that those extra trailing tokens' blocks are retained (but not
|
||||||
|
# attended). This is needed for multi-module spec decoding which can
|
||||||
|
# re-prefill the last num_spec_prefill_tokens - 1 tokens from the end
|
||||||
|
# of the sequence, and thus needs to delay freeing/caching of blocks.
|
||||||
|
extra_retained_tokens: int = 0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if self.head_size_v is None:
|
if self.head_size_v is None:
|
||||||
@@ -588,8 +594,13 @@ class SlidingWindowSpec(AttentionSpec):
|
|||||||
"""
|
"""
|
||||||
# During chunked prefill, we hold KV for the last `sliding_window-1`
|
# During chunked prefill, we hold KV for the last `sliding_window-1`
|
||||||
# computed tokens plus the in-flight tokens (frees happen on the
|
# computed tokens plus the in-flight tokens (frees happen on the
|
||||||
# processed-token basis); never more than `max_model_len`.
|
# processed-token basis); never more than `max_model_len`. An additional
|
||||||
num_tokens = min(self.sliding_window - 1 + max_in_flight_tokens, max_model_len)
|
# `extra_retained_tokens` trailing tokens are kept alive below the
|
||||||
|
# window for multi-module spec decoding, and must be accounted here too.
|
||||||
|
num_tokens = min(
|
||||||
|
self.sliding_window - 1 + self.extra_retained_tokens + max_in_flight_tokens,
|
||||||
|
max_model_len,
|
||||||
|
)
|
||||||
# +1 because the sliding window may not start from the beginning of
|
# +1 because the sliding window may not start from the beginning of
|
||||||
# the block. E.g. block size 4 and num_token 4 needs two blocks
|
# the block. E.g. block size 4 and num_token 4 needs two blocks
|
||||||
# [XXCD][EF] to store the 6-token window [CDEF].
|
# [XXCD][EF] to store the 6-token window [CDEF].
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from vllm.forward_context import BatchDescriptor, set_forward_context
|
|||||||
from vllm.logger import init_logger
|
from vllm.logger import init_logger
|
||||||
from vllm.multimodal import MULTIMODAL_REGISTRY
|
from vllm.multimodal import MULTIMODAL_REGISTRY
|
||||||
from vllm.triton_utils import tl, triton
|
from vllm.triton_utils import tl, triton
|
||||||
|
from vllm.v1.attention.backends.utils import PAD_SLOT_ID
|
||||||
from vllm.v1.worker.gpu.attn_utils import build_slot_mappings_by_layer
|
from vllm.v1.worker.gpu.attn_utils import build_slot_mappings_by_layer
|
||||||
from vllm.v1.worker.gpu.cudagraph_utils import (
|
from vllm.v1.worker.gpu.cudagraph_utils import (
|
||||||
BatchExecutionDescriptor,
|
BatchExecutionDescriptor,
|
||||||
@@ -40,6 +41,17 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
self.supports_mm_inputs = MULTIMODAL_REGISTRY.supports_multimodal_inputs(
|
self.supports_mm_inputs = MULTIMODAL_REGISTRY.supports_multimodal_inputs(
|
||||||
self.draft_model_config
|
self.draft_model_config
|
||||||
)
|
)
|
||||||
|
# HACK: the Inkling MTP draft has no MM processor of its own (its draft
|
||||||
|
# config is flattened text-only), but it consumes the target's merged
|
||||||
|
# embeddings at draft prefill — treat it as MM-capable whenever the
|
||||||
|
# target is.
|
||||||
|
if (
|
||||||
|
not self.supports_mm_inputs
|
||||||
|
and self.draft_model_config.hf_config.model_type == "inkling_mtp"
|
||||||
|
):
|
||||||
|
self.supports_mm_inputs = MULTIMODAL_REGISTRY.supports_multimodal_inputs(
|
||||||
|
vllm_config.model_config
|
||||||
|
)
|
||||||
if self.supports_mm_inputs:
|
if self.supports_mm_inputs:
|
||||||
self.inputs_embeds = torch.zeros(
|
self.inputs_embeds = torch.zeros(
|
||||||
self.max_num_tokens, self.hidden_size, dtype=self.dtype, device=device
|
self.max_num_tokens, self.hidden_size, dtype=self.dtype, device=device
|
||||||
@@ -48,6 +60,23 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
self.prefill_cudagraph_manager: SpeculatorCudaGraphManager | None = None
|
self.prefill_cudagraph_manager: SpeculatorCudaGraphManager | None = None
|
||||||
self.decode_cudagraph_manager: SpeculatorCudaGraphManager | None = None
|
self.decode_cudagraph_manager: SpeculatorCudaGraphManager | None = None
|
||||||
|
|
||||||
|
self.num_spec_prefill_steps = (
|
||||||
|
self.speculative_config.num_speculative_prefill_steps()
|
||||||
|
)
|
||||||
|
self.cached_draft_input_ids = torch.zeros(
|
||||||
|
self.max_num_reqs,
|
||||||
|
self.num_spec_prefill_steps - 1,
|
||||||
|
dtype=torch.int64,
|
||||||
|
device=self.device,
|
||||||
|
)
|
||||||
|
self.cached_target_hidden_states = torch.zeros(
|
||||||
|
self.max_num_reqs,
|
||||||
|
self.num_spec_prefill_steps - 1,
|
||||||
|
self.hidden_size,
|
||||||
|
dtype=self.dtype,
|
||||||
|
device=self.device,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def advance_draft_positions(self) -> bool:
|
def advance_draft_positions(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -92,23 +121,29 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
# For FULL graphs, the entire routine is recorded as one graph.
|
# For FULL graphs, the entire routine is recorded as one graph.
|
||||||
# For PIECEWISE, only the model's compiled regions are captured
|
# For PIECEWISE, only the model's compiled regions are captured
|
||||||
# and the rest (compute_logits, gumbel_sample) runs eagerly.
|
# and the rest (compute_logits, gumbel_sample) runs eagerly.
|
||||||
# Draft prefill reuses the target model's attention metadata at
|
# When num_spec_prefill_steps > 1 (e.g. multi-module MTP), the
|
||||||
# runtime, so capture builds its dummy metadata through the target
|
# speculator builds its attention metadata using its own builders
|
||||||
# model runner's builders and buffers.
|
# and buffers. Otherwise, the target model's attention metadata
|
||||||
|
# can be reused, so capture builds its dummy metadata through the
|
||||||
|
# target model runner's builders and buffers.
|
||||||
assert self.prefill_cudagraph_manager is not None
|
assert self.prefill_cudagraph_manager is not None
|
||||||
if self.prefill_cudagraph_manager.use_breakable_cg:
|
if self.prefill_cudagraph_manager.use_breakable_cg:
|
||||||
self.prefill_cudagraph_manager.init_breakable_cg_runner(self.model)
|
self.prefill_cudagraph_manager.init_breakable_cg_runner(self.model)
|
||||||
self.prefill_cudagraph_manager.capture(
|
self.prefill_cudagraph_manager.capture(
|
||||||
self._prefill,
|
self._prefill,
|
||||||
self.model_state,
|
self.model_state,
|
||||||
self.target_input_buffers,
|
self.input_buffers
|
||||||
|
if self.num_spec_prefill_steps > 1
|
||||||
|
else self.target_input_buffers,
|
||||||
self.block_tables,
|
self.block_tables,
|
||||||
self.target_attn_groups,
|
self.attn_groups
|
||||||
|
if self.num_spec_prefill_steps > 1
|
||||||
|
else self.target_attn_groups,
|
||||||
self.kv_cache_config,
|
self.kv_cache_config,
|
||||||
progress_bar_desc="Capturing prefill CUDA graphs",
|
progress_bar_desc="Capturing prefill CUDA graphs",
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.num_speculative_steps == 1:
|
if self.num_speculative_steps <= self.num_spec_prefill_steps:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Capture the decode draft generation routine (model forward +
|
# Capture the decode draft generation routine (model forward +
|
||||||
@@ -116,7 +151,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
# step.
|
# step.
|
||||||
assert self.decode_cudagraph_manager is not None
|
assert self.decode_cudagraph_manager is not None
|
||||||
self.decode_cudagraph_manager.capture(
|
self.decode_cudagraph_manager.capture(
|
||||||
self._generate_draft,
|
self._decode,
|
||||||
self.model_state,
|
self.model_state,
|
||||||
self.input_buffers,
|
self.input_buffers,
|
||||||
self.block_tables,
|
self.block_tables,
|
||||||
@@ -160,6 +195,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
self.draft_max_seq_len = min(
|
self.draft_max_seq_len = min(
|
||||||
max_seq_len + self.num_speculative_steps, self.max_model_len
|
max_seq_len + self.num_speculative_steps, self.max_model_len
|
||||||
)
|
)
|
||||||
|
skip_attn = dummy_run and skip_attn_for_dummy_run
|
||||||
|
|
||||||
# NOTE(woosuk): To avoid CPU-GPU synchronization without CPU knowing the
|
# NOTE(woosuk): To avoid CPU-GPU synchronization without CPU knowing the
|
||||||
# number of rejected tokens, we maintain the size of input_ids and
|
# number of rejected tokens, we maintain the size of input_ids and
|
||||||
@@ -174,7 +210,6 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
hidden_states = last_hidden_states
|
hidden_states = last_hidden_states
|
||||||
self.hidden_states[:num_tokens].copy_(hidden_states)
|
|
||||||
|
|
||||||
self._copy_request_inputs(
|
self._copy_request_inputs(
|
||||||
num_reqs,
|
num_reqs,
|
||||||
@@ -183,16 +218,21 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
seeds,
|
seeds,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get the input ids and last token indices for the speculator.
|
# Get the input ids, last token indices, and input hidden states for
|
||||||
|
# the speculator.
|
||||||
prepare_prefill_inputs(
|
prepare_prefill_inputs(
|
||||||
self.last_token_indices,
|
self.last_token_indices,
|
||||||
self.current_draft_step,
|
self.hidden_states,
|
||||||
self.input_buffers,
|
self.input_buffers,
|
||||||
|
hidden_states,
|
||||||
|
self.cached_target_hidden_states,
|
||||||
|
self.cached_draft_input_ids,
|
||||||
input_batch,
|
input_batch,
|
||||||
num_sampled,
|
num_sampled,
|
||||||
num_rejected,
|
num_rejected,
|
||||||
last_sampled,
|
last_sampled,
|
||||||
next_prefill_tokens,
|
next_prefill_tokens,
|
||||||
|
self.num_spec_prefill_steps,
|
||||||
self.max_num_reqs,
|
self.max_num_reqs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -215,6 +255,38 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
need_eager=is_profile,
|
need_eager=is_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not skip_attn and self.num_spec_prefill_steps > 1:
|
||||||
|
# Re-prefill shifts the draft positions/seq_lens, so the target's
|
||||||
|
# slot mappings and attention metadata can no longer be reused.
|
||||||
|
# Rebuild both from the draft buffers written by
|
||||||
|
# prepare_prefill_inputs, keeping the target's (non-uniform) query
|
||||||
|
# layout since the draft query_start_loc mirrors the target's.
|
||||||
|
index_mapping = self.idx_mapping[:num_reqs]
|
||||||
|
last_token_indices = self.last_token_indices[:num_reqs]
|
||||||
|
slot_mappings_tensor = self.block_tables.compute_slot_mappings(
|
||||||
|
index_mapping,
|
||||||
|
self.input_buffers.query_start_loc,
|
||||||
|
self.input_buffers.positions,
|
||||||
|
prefill_batch_desc.num_tokens,
|
||||||
|
)
|
||||||
|
# Apply padding values to slots not corresponding to real draft
|
||||||
|
# tokens to prevent stale value writes.
|
||||||
|
pad_trailing_draft_slots(
|
||||||
|
slot_mappings_tensor,
|
||||||
|
self.input_buffers.query_start_loc,
|
||||||
|
last_token_indices,
|
||||||
|
num_reqs,
|
||||||
|
)
|
||||||
|
slot_mappings = build_slot_mappings_by_layer(
|
||||||
|
slot_mappings_tensor, self.kv_cache_config
|
||||||
|
)
|
||||||
|
attn_metadata = self._build_draft_attn_metadata(
|
||||||
|
num_reqs=num_reqs,
|
||||||
|
num_reqs_padded=prefill_batch_desc.num_reqs or num_reqs,
|
||||||
|
num_tokens_padded=prefill_batch_desc.num_tokens,
|
||||||
|
query_start_loc_np=input_batch.query_start_loc_np,
|
||||||
|
)
|
||||||
|
|
||||||
self._prepare_eplb_forward(input_batch.num_tokens)
|
self._prepare_eplb_forward(input_batch.num_tokens)
|
||||||
|
|
||||||
if prefill_batch_desc.cg_mode == CUDAGraphMode.FULL:
|
if prefill_batch_desc.cg_mode == CUDAGraphMode.FULL:
|
||||||
@@ -235,9 +307,9 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
mm_inputs=mm_inputs,
|
mm_inputs=mm_inputs,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.num_speculative_steps == 1:
|
if self.num_speculative_steps <= self.num_spec_prefill_steps:
|
||||||
# Early exit.
|
# Early exit.
|
||||||
return self.draft_tokens[:num_reqs, :1]
|
return self.draft_tokens[:num_reqs, : self.num_speculative_steps]
|
||||||
|
|
||||||
# Prepare the inputs for the decode steps.
|
# Prepare the inputs for the decode steps.
|
||||||
prepare_decode_inputs(
|
prepare_decode_inputs(
|
||||||
@@ -265,7 +337,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
# Generate the remaining num_speculative_steps - 1 draft tokens.
|
# Generate the remaining num_speculative_steps - 1 draft tokens.
|
||||||
self._multi_step_decode(
|
self._multi_step_decode(
|
||||||
num_reqs,
|
num_reqs,
|
||||||
dummy_run and skip_attn_for_dummy_run,
|
skip_attn,
|
||||||
decode_batch_desc,
|
decode_batch_desc,
|
||||||
num_tokens_across_dp,
|
num_tokens_across_dp,
|
||||||
)
|
)
|
||||||
@@ -281,6 +353,8 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
num_tokens_across_dp: torch.Tensor | None,
|
num_tokens_across_dp: torch.Tensor | None,
|
||||||
cudagraph_runtime_mode: CUDAGraphMode = CUDAGraphMode.NONE,
|
cudagraph_runtime_mode: CUDAGraphMode = CUDAGraphMode.NONE,
|
||||||
mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None,
|
mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None,
|
||||||
|
embed_mm: bool = False,
|
||||||
|
spec_module_idx: int = 0,
|
||||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
batch_descriptor = BatchDescriptor(num_tokens=num_tokens)
|
batch_descriptor = BatchDescriptor(num_tokens=num_tokens)
|
||||||
with set_forward_context(
|
with set_forward_context(
|
||||||
@@ -293,7 +367,11 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
batch_descriptor=batch_descriptor,
|
batch_descriptor=batch_descriptor,
|
||||||
):
|
):
|
||||||
inputs_embeds = None
|
inputs_embeds = None
|
||||||
if self.supports_mm_inputs:
|
# Only draft prefill consumes (target-merged) MM embeddings; the
|
||||||
|
# decode steps always draft text tokens and keep the raw-ids path
|
||||||
|
# (per-callsite, not data-dependent, so each cudagraph family
|
||||||
|
# captures the branch it will replay).
|
||||||
|
if self.supports_mm_inputs and embed_mm:
|
||||||
# Merge multimodal embeddings with input ids.
|
# Merge multimodal embeddings with input ids.
|
||||||
mm_embeds, is_mm_embed = mm_inputs or (None, None)
|
mm_embeds, is_mm_embed = mm_inputs or (None, None)
|
||||||
num_input_tokens = (
|
num_input_tokens = (
|
||||||
@@ -312,6 +390,10 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
hidden_states=self.hidden_states[:num_tokens],
|
hidden_states=self.hidden_states[:num_tokens],
|
||||||
inputs_embeds=inputs_embeds,
|
inputs_embeds=inputs_embeds,
|
||||||
)
|
)
|
||||||
|
if spec_module_idx > 0:
|
||||||
|
# Pass the speculative module index to the model to indicate which
|
||||||
|
# module to use for the forward pass.
|
||||||
|
model_inputs["spec_step_idx"] = spec_module_idx
|
||||||
if cudagraph_runtime_mode == CUDAGraphMode.PIECEWISE:
|
if cudagraph_runtime_mode == CUDAGraphMode.PIECEWISE:
|
||||||
# Draft prefill with PIECEWISE cudagraph (compiled PW or breakable),
|
# Draft prefill with PIECEWISE cudagraph (compiled PW or breakable),
|
||||||
# chosen inside run_pw_graph.
|
# chosen inside run_pw_graph.
|
||||||
@@ -342,33 +424,67 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None,
|
mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
last_token_indices = self.last_token_indices[:num_reqs]
|
last_token_indices = self.last_token_indices[:num_reqs]
|
||||||
positions = self.input_buffers.positions[last_token_indices]
|
sample_positions = self.input_buffers.positions[last_token_indices]
|
||||||
idx_mapping = self.idx_mapping[:num_reqs]
|
idx_mapping = self.idx_mapping[:num_reqs]
|
||||||
|
|
||||||
last_hidden_states, hidden_states = self._run_model(
|
if self.num_spec_prefill_steps > 1:
|
||||||
num_tokens,
|
# Snapshot the trailing draft tokens/hidden states (for the next
|
||||||
attn_metadata,
|
# prefill's re-prefill gap) before the step loop overwrites them.
|
||||||
slot_mappings,
|
cache_prefill_state(
|
||||||
num_tokens_across_dp=num_tokens_across_dp,
|
self.input_buffers,
|
||||||
cudagraph_runtime_mode=cudagraph_runtime_mode,
|
self.hidden_states,
|
||||||
mm_inputs=mm_inputs,
|
self.cached_draft_input_ids,
|
||||||
)
|
self.cached_target_hidden_states,
|
||||||
sample_hidden_states = last_hidden_states[last_token_indices]
|
last_token_indices,
|
||||||
|
idx_mapping,
|
||||||
|
num_reqs,
|
||||||
|
self.num_spec_prefill_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
for step in range(self.num_spec_prefill_steps):
|
||||||
|
# Update the current draft step.
|
||||||
|
self.current_draft_step.fill_(step)
|
||||||
|
|
||||||
|
# Run the model forward pass.
|
||||||
|
last_hidden_states, hidden_states = self._run_model(
|
||||||
|
num_tokens,
|
||||||
|
attn_metadata,
|
||||||
|
slot_mappings,
|
||||||
|
num_tokens_across_dp=num_tokens_across_dp,
|
||||||
|
cudagraph_runtime_mode=cudagraph_runtime_mode,
|
||||||
|
mm_inputs=mm_inputs,
|
||||||
|
embed_mm=True,
|
||||||
|
spec_module_idx=step,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sample draft tokens for the current step.
|
||||||
|
sample_hidden_states = last_hidden_states[last_token_indices]
|
||||||
|
draft_tokens = self.sample_draft(
|
||||||
|
sample_hidden_states,
|
||||||
|
sample_positions,
|
||||||
|
idx_mapping,
|
||||||
|
self.temperature,
|
||||||
|
self.seeds,
|
||||||
|
self.current_draft_step,
|
||||||
|
self.draft_logits,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.draft_tokens[:num_reqs, step] = draft_tokens
|
||||||
|
if step < self.num_spec_prefill_steps - 1:
|
||||||
|
self.hidden_states[:num_tokens] = hidden_states
|
||||||
|
update_prefill_inputs(
|
||||||
|
draft_tokens,
|
||||||
|
self.input_buffers,
|
||||||
|
last_token_indices,
|
||||||
|
num_reqs,
|
||||||
|
)
|
||||||
|
sample_positions += 1
|
||||||
|
|
||||||
self.draft_tokens[:num_reqs, 0] = self.sample_draft(
|
|
||||||
sample_hidden_states,
|
|
||||||
positions,
|
|
||||||
idx_mapping,
|
|
||||||
self.temperature,
|
|
||||||
self.seeds,
|
|
||||||
self.current_draft_step,
|
|
||||||
self.draft_logits,
|
|
||||||
)
|
|
||||||
if last_hidden_states is hidden_states:
|
if last_hidden_states is hidden_states:
|
||||||
self.hidden_states[:num_reqs] = sample_hidden_states
|
self.hidden_states[:num_reqs] = sample_hidden_states
|
||||||
else:
|
else:
|
||||||
self.hidden_states[:num_reqs] = hidden_states[last_token_indices]
|
self.hidden_states[:num_reqs] = hidden_states[last_token_indices]
|
||||||
self.input_buffers.positions[:num_reqs] = positions
|
self.input_buffers.positions[:num_reqs] = sample_positions
|
||||||
|
|
||||||
def _multi_step_decode(
|
def _multi_step_decode(
|
||||||
self,
|
self,
|
||||||
@@ -383,7 +499,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
|
|
||||||
attn_metadata = None
|
attn_metadata = None
|
||||||
slot_mappings_by_layer = None
|
slot_mappings_by_layer = None
|
||||||
for step in range(1, self.num_speculative_steps):
|
for step in range(self.num_spec_prefill_steps, self.num_speculative_steps):
|
||||||
# Rebuild every step when positions advance, or just once
|
# Rebuild every step when positions advance, or just once
|
||||||
# on the first step when positions are constant (Gemma4 MTP).
|
# on the first step when positions are constant (Gemma4 MTP).
|
||||||
if not skip_attn and (self.advance_draft_positions or step == 1):
|
if not skip_attn and (self.advance_draft_positions or step == 1):
|
||||||
@@ -396,7 +512,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
slot_mappings_by_layer = build_slot_mappings_by_layer(
|
slot_mappings_by_layer = build_slot_mappings_by_layer(
|
||||||
slot_mappings, self.kv_cache_config
|
slot_mappings, self.kv_cache_config
|
||||||
)
|
)
|
||||||
attn_metadata = self._build_draft_attn_metadata(
|
attn_metadata = self._build_uniform_draft_attn_metadata(
|
||||||
num_reqs=num_reqs,
|
num_reqs=num_reqs,
|
||||||
num_reqs_padded=batch_desc.num_reqs or num_reqs,
|
num_reqs_padded=batch_desc.num_reqs or num_reqs,
|
||||||
num_tokens_padded=batch_desc.num_tokens,
|
num_tokens_padded=batch_desc.num_tokens,
|
||||||
@@ -410,7 +526,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
assert self.decode_cudagraph_manager is not None
|
assert self.decode_cudagraph_manager is not None
|
||||||
self.decode_cudagraph_manager.run_fullgraph(batch_desc)
|
self.decode_cudagraph_manager.run_fullgraph(batch_desc)
|
||||||
else:
|
else:
|
||||||
self._generate_draft(
|
self._decode(
|
||||||
num_reqs,
|
num_reqs,
|
||||||
batch_desc.num_tokens,
|
batch_desc.num_tokens,
|
||||||
attn_metadata,
|
attn_metadata,
|
||||||
@@ -419,7 +535,7 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
cudagraph_runtime_mode=batch_desc.cg_mode,
|
cudagraph_runtime_mode=batch_desc.cg_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _generate_draft(
|
def _decode(
|
||||||
self,
|
self,
|
||||||
num_reqs: int,
|
num_reqs: int,
|
||||||
num_tokens_padded: int,
|
num_tokens_padded: int,
|
||||||
@@ -471,13 +587,14 @@ class AutoRegressiveSpeculator(DraftModelSpeculator):
|
|||||||
@triton.jit
|
@triton.jit
|
||||||
def _prepare_prefill_inputs_kernel(
|
def _prepare_prefill_inputs_kernel(
|
||||||
last_token_indices_ptr,
|
last_token_indices_ptr,
|
||||||
draft_current_step_ptr,
|
|
||||||
draft_input_ids_ptr,
|
draft_input_ids_ptr,
|
||||||
draft_positions_ptr,
|
draft_positions_ptr,
|
||||||
draft_query_start_loc_ptr,
|
draft_query_start_loc_ptr,
|
||||||
draft_seq_lens_ptr,
|
draft_seq_lens_ptr,
|
||||||
target_input_ids_ptr,
|
target_input_ids_ptr,
|
||||||
target_positions_ptr,
|
target_positions_ptr,
|
||||||
|
cached_draft_input_ids_ptr,
|
||||||
|
cached_draft_input_ids_stride0,
|
||||||
idx_mapping_ptr,
|
idx_mapping_ptr,
|
||||||
last_sampled_ptr,
|
last_sampled_ptr,
|
||||||
next_prefill_tokens_ptr,
|
next_prefill_tokens_ptr,
|
||||||
@@ -486,6 +603,7 @@ def _prepare_prefill_inputs_kernel(
|
|||||||
query_start_loc_ptr,
|
query_start_loc_ptr,
|
||||||
seq_lens_ptr,
|
seq_lens_ptr,
|
||||||
max_num_reqs,
|
max_num_reqs,
|
||||||
|
num_spec_prefill_steps,
|
||||||
BLOCK_SIZE: tl.constexpr,
|
BLOCK_SIZE: tl.constexpr,
|
||||||
):
|
):
|
||||||
req_idx = tl.program_id(0)
|
req_idx = tl.program_id(0)
|
||||||
@@ -500,6 +618,7 @@ def _prepare_prefill_inputs_kernel(
|
|||||||
# Get the true query length and next token after accounting for rejected tokens.
|
# Get the true query length and next token after accounting for rejected tokens.
|
||||||
num_rejected = tl.load(num_rejected_ptr + req_idx)
|
num_rejected = tl.load(num_rejected_ptr + req_idx)
|
||||||
query_len -= num_rejected
|
query_len -= num_rejected
|
||||||
|
num_reprefills = 0 if num_spec_prefill_steps == 1 else max(0, num_rejected - 1)
|
||||||
|
|
||||||
num_sampled = tl.load(num_sampled_ptr + req_idx)
|
num_sampled = tl.load(num_sampled_ptr + req_idx)
|
||||||
if num_sampled > 0:
|
if num_sampled > 0:
|
||||||
@@ -509,31 +628,57 @@ def _prepare_prefill_inputs_kernel(
|
|||||||
# Get the next prefill token.
|
# Get the next prefill token.
|
||||||
next_token = tl.load(next_prefill_tokens_ptr + req_state_idx)
|
next_token = tl.load(next_prefill_tokens_ptr + req_state_idx)
|
||||||
|
|
||||||
# Shift target_input_ids by one.
|
# Shift target_input_ids by one + the number of tokens to be re-prefilled.
|
||||||
for i in range(1, query_len, BLOCK_SIZE):
|
for i in range(1, query_len, BLOCK_SIZE):
|
||||||
block = i + tl.arange(0, BLOCK_SIZE)
|
block = i + tl.arange(0, BLOCK_SIZE)
|
||||||
mask = block < query_len
|
mask = block < query_len
|
||||||
input_ids = tl.load(target_input_ids_ptr + query_start + block, mask=mask)
|
input_ids = tl.load(target_input_ids_ptr + query_start + block, mask=mask)
|
||||||
tl.store(draft_input_ids_ptr + query_start + block - 1, input_ids, mask=mask)
|
tl.store(
|
||||||
|
draft_input_ids_ptr + query_start + block - 1 + num_reprefills,
|
||||||
|
input_ids,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
|
||||||
last_token_index = query_start + query_len - 1
|
last_token_index = query_start + query_len - 1 + num_reprefills
|
||||||
tl.store(last_token_indices_ptr + req_idx, last_token_index)
|
tl.store(last_token_indices_ptr + req_idx, last_token_index)
|
||||||
tl.store(draft_input_ids_ptr + last_token_index, next_token)
|
tl.store(draft_input_ids_ptr + last_token_index, next_token)
|
||||||
|
|
||||||
# Copy positions.
|
# Copy positions, shifted over by the number of tokens to be re-prefilled.
|
||||||
for i in range(0, query_len, BLOCK_SIZE):
|
for i in range(0, query_len, BLOCK_SIZE):
|
||||||
block = i + tl.arange(0, BLOCK_SIZE)
|
block = i + tl.arange(0, BLOCK_SIZE)
|
||||||
mask = block < query_len
|
mask = block < query_len
|
||||||
target_pos = tl.load(target_positions_ptr + query_start + block, mask=mask)
|
target_pos = tl.load(target_positions_ptr + query_start + block, mask=mask)
|
||||||
tl.store(draft_positions_ptr + query_start + block, target_pos, mask=mask)
|
tl.store(
|
||||||
|
draft_positions_ptr + query_start + block + num_reprefills,
|
||||||
|
target_pos,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fill the re-prefill gap with the cached token ids from the previous
|
||||||
|
# decode step. These tokens sit immediately before the query's first
|
||||||
|
# token, so their positions are contiguous and derived here.
|
||||||
|
first_position = tl.load(target_positions_ptr + query_start)
|
||||||
|
for i in range(num_reprefills):
|
||||||
|
cache_read_slot = num_spec_prefill_steps - 1 - num_reprefills + i
|
||||||
|
cached_token_id = tl.load(
|
||||||
|
cached_draft_input_ids_ptr
|
||||||
|
+ req_state_idx * cached_draft_input_ids_stride0
|
||||||
|
+ cache_read_slot
|
||||||
|
)
|
||||||
|
tl.store(draft_input_ids_ptr + query_start + i, cached_token_id)
|
||||||
|
tl.store(
|
||||||
|
draft_positions_ptr + query_start + i,
|
||||||
|
first_position - num_reprefills + i,
|
||||||
|
)
|
||||||
|
|
||||||
# Copy query start locations.
|
# Copy query start locations.
|
||||||
tl.store(draft_query_start_loc_ptr + req_idx, query_start)
|
tl.store(draft_query_start_loc_ptr + req_idx, query_start)
|
||||||
# Copy sequence lengths.
|
# Copy sequence lengths. Re-prefilled tokens are packed into the query
|
||||||
tl.store(draft_seq_lens_ptr + req_idx, seq_len)
|
# window without widening it, so the effective KV length shrinks by
|
||||||
|
# num_reprefills to keep RoPE positions aligned with the attention's
|
||||||
|
# implicit (seq_len - query_len) causal positions.
|
||||||
|
tl.store(draft_seq_lens_ptr + req_idx, seq_len - num_reprefills)
|
||||||
if req_idx == (num_reqs - 1):
|
if req_idx == (num_reqs - 1):
|
||||||
# Reset the current draft step to 0.
|
|
||||||
tl.store(draft_current_step_ptr, 0)
|
|
||||||
# Pad query_start_loc for CUDA graphs.
|
# Pad query_start_loc for CUDA graphs.
|
||||||
for i in range(num_reqs, max_num_reqs + 1, BLOCK_SIZE):
|
for i in range(num_reqs, max_num_reqs + 1, BLOCK_SIZE):
|
||||||
block = i + tl.arange(0, BLOCK_SIZE)
|
block = i + tl.arange(0, BLOCK_SIZE)
|
||||||
@@ -551,11 +696,126 @@ def _prepare_prefill_inputs_kernel(
|
|||||||
tl.store(last_token_indices_ptr + block, 0, mask=mask)
|
tl.store(last_token_indices_ptr + block, 0, mask=mask)
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _prepare_prefill_hidden_states_kernel(
|
||||||
|
draft_input_hidden_states_ptr,
|
||||||
|
draft_input_hidden_states_stride0,
|
||||||
|
target_hidden_states_ptr,
|
||||||
|
target_hidden_states_stride0,
|
||||||
|
cached_target_hidden_states_ptr,
|
||||||
|
cached_target_hidden_states_stride0,
|
||||||
|
cached_target_hidden_states_stride1,
|
||||||
|
idx_mapping_ptr,
|
||||||
|
num_rejected_ptr,
|
||||||
|
query_start_loc_ptr,
|
||||||
|
num_spec_prefill_steps,
|
||||||
|
hidden_size,
|
||||||
|
BLOCK_SIZE: tl.constexpr,
|
||||||
|
):
|
||||||
|
req_idx = tl.program_id(0)
|
||||||
|
block_idx = tl.program_id(1)
|
||||||
|
block = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||||||
|
mask = block < hidden_size
|
||||||
|
|
||||||
|
req_state_idx = tl.load(idx_mapping_ptr + req_idx)
|
||||||
|
|
||||||
|
query_start = tl.load(query_start_loc_ptr + req_idx)
|
||||||
|
query_end = tl.load(query_start_loc_ptr + req_idx + 1)
|
||||||
|
query_len = query_end - query_start
|
||||||
|
|
||||||
|
num_rejected = tl.load(num_rejected_ptr + req_idx)
|
||||||
|
query_len -= num_rejected
|
||||||
|
num_reprefills = 0 if num_spec_prefill_steps == 1 else max(0, num_rejected - 1)
|
||||||
|
|
||||||
|
# Fill the re-prefill gap with the cached hidden states from the previous
|
||||||
|
# decode step, mirroring the token ids and positions inserted by
|
||||||
|
# _prepare_prefill_inputs_kernel.
|
||||||
|
for i in range(num_reprefills):
|
||||||
|
cache_read_slot = num_spec_prefill_steps - 1 - num_reprefills + i
|
||||||
|
cached_hidden_state = tl.load(
|
||||||
|
cached_target_hidden_states_ptr
|
||||||
|
+ req_state_idx * cached_target_hidden_states_stride0
|
||||||
|
+ cache_read_slot * cached_target_hidden_states_stride1
|
||||||
|
+ block,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
tl.store(
|
||||||
|
draft_input_hidden_states_ptr
|
||||||
|
+ (query_start + i) * draft_input_hidden_states_stride0
|
||||||
|
+ block,
|
||||||
|
cached_hidden_state,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(query_len):
|
||||||
|
hidden_state = tl.load(
|
||||||
|
target_hidden_states_ptr
|
||||||
|
+ (query_start + i) * target_hidden_states_stride0
|
||||||
|
+ block,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
tl.store(
|
||||||
|
draft_input_hidden_states_ptr
|
||||||
|
+ (query_start + num_reprefills + i) * draft_input_hidden_states_stride0
|
||||||
|
+ block,
|
||||||
|
hidden_state,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _pad_trailing_draft_slots_kernel(
|
||||||
|
slot_mappings_ptr,
|
||||||
|
slot_mappings_stride0,
|
||||||
|
query_start_loc_ptr,
|
||||||
|
last_token_indices_ptr,
|
||||||
|
PAD_ID,
|
||||||
|
BLOCK_SIZE: tl.constexpr,
|
||||||
|
):
|
||||||
|
group_idx = tl.program_id(0)
|
||||||
|
req_idx = tl.program_id(1)
|
||||||
|
# Slots computed from stale token positions in the range
|
||||||
|
# [last_token_index + 1, query_end) can result in writes to blocks.
|
||||||
|
# Pad these slot values so that attention kernels ignore them.
|
||||||
|
start = tl.load(last_token_indices_ptr + req_idx) + 1
|
||||||
|
end = tl.load(query_start_loc_ptr + req_idx + 1)
|
||||||
|
base = slot_mappings_ptr + group_idx * slot_mappings_stride0
|
||||||
|
for i in range(start, end, BLOCK_SIZE):
|
||||||
|
offs = i + tl.arange(0, BLOCK_SIZE)
|
||||||
|
mask = offs < end
|
||||||
|
tl.store(base + offs, PAD_ID, mask=mask)
|
||||||
|
|
||||||
|
|
||||||
|
def pad_trailing_draft_slots(
|
||||||
|
# [num_groups, num_tokens_padded]
|
||||||
|
slot_mappings: torch.Tensor,
|
||||||
|
# [num_reqs + 1]
|
||||||
|
query_start_loc: torch.Tensor,
|
||||||
|
# [num_reqs]
|
||||||
|
last_token_indices: torch.Tensor,
|
||||||
|
num_reqs: int,
|
||||||
|
) -> None:
|
||||||
|
num_groups = slot_mappings.shape[0]
|
||||||
|
_pad_trailing_draft_slots_kernel[(num_groups, num_reqs)](
|
||||||
|
slot_mappings,
|
||||||
|
slot_mappings.stride(0),
|
||||||
|
query_start_loc,
|
||||||
|
last_token_indices,
|
||||||
|
PAD_SLOT_ID,
|
||||||
|
BLOCK_SIZE=256,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prepare_prefill_inputs(
|
def prepare_prefill_inputs(
|
||||||
# [num_reqs]
|
# [num_reqs]
|
||||||
last_token_indices: torch.Tensor,
|
last_token_indices: torch.Tensor,
|
||||||
current_draft_step: torch.Tensor,
|
draft_input_hidden_states: torch.Tensor,
|
||||||
input_buffers: InputBuffers,
|
input_buffers: InputBuffers,
|
||||||
|
target_hidden_states: torch.Tensor,
|
||||||
|
# [max_num_reqs, num_spec_prefill_steps - 1, hidden_size]
|
||||||
|
cached_target_hidden_states: torch.Tensor | None,
|
||||||
|
# [max_num_reqs, num_spec_prefill_steps - 1]
|
||||||
|
cached_draft_input_ids: torch.Tensor | None,
|
||||||
input_batch: InputBatch,
|
input_batch: InputBatch,
|
||||||
# [num_reqs]
|
# [num_reqs]
|
||||||
num_sampled: torch.Tensor,
|
num_sampled: torch.Tensor,
|
||||||
@@ -565,18 +825,21 @@ def prepare_prefill_inputs(
|
|||||||
last_sampled: torch.Tensor,
|
last_sampled: torch.Tensor,
|
||||||
# [max_num_reqs]
|
# [max_num_reqs]
|
||||||
next_prefill_tokens: torch.Tensor,
|
next_prefill_tokens: torch.Tensor,
|
||||||
|
num_spec_prefill_steps,
|
||||||
max_num_reqs,
|
max_num_reqs,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
num_reqs = input_batch.num_reqs
|
num_reqs = input_batch.num_reqs
|
||||||
|
hidden_size = target_hidden_states.shape[-1]
|
||||||
_prepare_prefill_inputs_kernel[(num_reqs,)](
|
_prepare_prefill_inputs_kernel[(num_reqs,)](
|
||||||
last_token_indices,
|
last_token_indices,
|
||||||
current_draft_step,
|
|
||||||
input_buffers.input_ids,
|
input_buffers.input_ids,
|
||||||
input_buffers.positions,
|
input_buffers.positions,
|
||||||
input_buffers.query_start_loc,
|
input_buffers.query_start_loc,
|
||||||
input_buffers.seq_lens,
|
input_buffers.seq_lens,
|
||||||
input_batch.input_ids,
|
input_batch.input_ids,
|
||||||
input_batch.positions,
|
input_batch.positions,
|
||||||
|
cached_draft_input_ids,
|
||||||
|
cached_draft_input_ids.stride(0) if cached_draft_input_ids is not None else 0,
|
||||||
input_batch.idx_mapping,
|
input_batch.idx_mapping,
|
||||||
last_sampled,
|
last_sampled,
|
||||||
next_prefill_tokens,
|
next_prefill_tokens,
|
||||||
@@ -585,11 +848,176 @@ def prepare_prefill_inputs(
|
|||||||
input_batch.query_start_loc,
|
input_batch.query_start_loc,
|
||||||
input_batch.seq_lens,
|
input_batch.seq_lens,
|
||||||
max_num_reqs,
|
max_num_reqs,
|
||||||
|
num_spec_prefill_steps,
|
||||||
BLOCK_SIZE=1024,
|
BLOCK_SIZE=1024,
|
||||||
)
|
)
|
||||||
|
if num_spec_prefill_steps > 1:
|
||||||
|
hidden_block_size = 1024
|
||||||
|
num_dim_blocks = triton.cdiv(hidden_size, hidden_block_size)
|
||||||
|
_prepare_prefill_hidden_states_kernel[(num_reqs, num_dim_blocks)](
|
||||||
|
draft_input_hidden_states,
|
||||||
|
draft_input_hidden_states.stride(0),
|
||||||
|
target_hidden_states,
|
||||||
|
target_hidden_states.stride(0),
|
||||||
|
cached_target_hidden_states,
|
||||||
|
cached_target_hidden_states.stride(0)
|
||||||
|
if cached_target_hidden_states is not None
|
||||||
|
else 0,
|
||||||
|
cached_target_hidden_states.stride(1)
|
||||||
|
if cached_target_hidden_states is not None
|
||||||
|
else 0,
|
||||||
|
input_batch.idx_mapping,
|
||||||
|
num_rejected,
|
||||||
|
input_batch.query_start_loc,
|
||||||
|
num_spec_prefill_steps,
|
||||||
|
hidden_size,
|
||||||
|
BLOCK_SIZE=hidden_block_size,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
num_tokens = input_batch.num_tokens
|
||||||
|
draft_input_hidden_states[:num_tokens].copy_(target_hidden_states[:num_tokens])
|
||||||
return last_token_indices
|
return last_token_indices
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _cache_prefill_state_kernel(
|
||||||
|
draft_input_ids_ptr,
|
||||||
|
draft_input_hidden_states_ptr,
|
||||||
|
draft_input_hidden_states_stride0,
|
||||||
|
cached_draft_input_ids_ptr,
|
||||||
|
cached_draft_input_ids_stride0,
|
||||||
|
cached_target_hidden_states_ptr,
|
||||||
|
cached_target_hidden_states_stride0,
|
||||||
|
cached_target_hidden_states_stride1,
|
||||||
|
idx_mapping_ptr,
|
||||||
|
last_token_indices_ptr,
|
||||||
|
query_start_loc_ptr,
|
||||||
|
num_spec_prefill_steps,
|
||||||
|
hidden_size,
|
||||||
|
BLOCK_SIZE: tl.constexpr,
|
||||||
|
):
|
||||||
|
req_idx = tl.program_id(0)
|
||||||
|
block_idx = tl.program_id(1)
|
||||||
|
block = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||||||
|
mask = block < hidden_size
|
||||||
|
|
||||||
|
req_state_idx = tl.load(idx_mapping_ptr + req_idx)
|
||||||
|
if req_state_idx < 0:
|
||||||
|
# Skip cudagraph padded requests.
|
||||||
|
return
|
||||||
|
|
||||||
|
query_start = tl.load(query_start_loc_ptr + req_idx)
|
||||||
|
last_token_index = tl.load(last_token_indices_ptr + req_idx)
|
||||||
|
|
||||||
|
# Snapshot the last num_spec_prefill_steps - 1 input draft tokens/hidden
|
||||||
|
# states, indexed by request state, so the next prefill can re-prefill
|
||||||
|
# rejected positions.
|
||||||
|
cache_window_size = num_spec_prefill_steps - 1
|
||||||
|
window_start = last_token_index - cache_window_size + 1
|
||||||
|
for i in range(max(window_start, query_start), last_token_index + 1):
|
||||||
|
cache_write_slot = i - window_start
|
||||||
|
if block_idx == 0:
|
||||||
|
input_id = tl.load(draft_input_ids_ptr + i)
|
||||||
|
tl.store(
|
||||||
|
cached_draft_input_ids_ptr
|
||||||
|
+ req_state_idx * cached_draft_input_ids_stride0
|
||||||
|
+ cache_write_slot,
|
||||||
|
input_id,
|
||||||
|
)
|
||||||
|
hidden_state = tl.load(
|
||||||
|
draft_input_hidden_states_ptr
|
||||||
|
+ i * draft_input_hidden_states_stride0
|
||||||
|
+ block,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
tl.store(
|
||||||
|
cached_target_hidden_states_ptr
|
||||||
|
+ req_state_idx * cached_target_hidden_states_stride0
|
||||||
|
+ cache_write_slot * cached_target_hidden_states_stride1
|
||||||
|
+ block,
|
||||||
|
hidden_state,
|
||||||
|
mask=mask,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cache_prefill_state(
|
||||||
|
input_buffers: InputBuffers,
|
||||||
|
# [num_tokens, hidden_size]
|
||||||
|
draft_input_hidden_states: torch.Tensor,
|
||||||
|
# [max_num_reqs, num_spec_prefill_steps - 1]
|
||||||
|
cached_draft_input_ids: torch.Tensor,
|
||||||
|
# [max_num_reqs, num_spec_prefill_steps - 1, hidden_size]
|
||||||
|
cached_target_hidden_states: torch.Tensor,
|
||||||
|
# [num_reqs]
|
||||||
|
last_token_indices: torch.Tensor,
|
||||||
|
# [num_reqs]
|
||||||
|
idx_mapping: torch.Tensor,
|
||||||
|
num_reqs: int,
|
||||||
|
num_spec_prefill_steps: int,
|
||||||
|
) -> None:
|
||||||
|
hidden_size = draft_input_hidden_states.shape[-1]
|
||||||
|
hidden_block_size = 1024
|
||||||
|
_cache_prefill_state_kernel[
|
||||||
|
(num_reqs, triton.cdiv(hidden_size, hidden_block_size))
|
||||||
|
](
|
||||||
|
input_buffers.input_ids,
|
||||||
|
draft_input_hidden_states,
|
||||||
|
draft_input_hidden_states.stride(0),
|
||||||
|
cached_draft_input_ids,
|
||||||
|
cached_draft_input_ids.stride(0),
|
||||||
|
cached_target_hidden_states,
|
||||||
|
cached_target_hidden_states.stride(0),
|
||||||
|
cached_target_hidden_states.stride(1),
|
||||||
|
idx_mapping,
|
||||||
|
last_token_indices,
|
||||||
|
input_buffers.query_start_loc,
|
||||||
|
num_spec_prefill_steps,
|
||||||
|
hidden_size,
|
||||||
|
BLOCK_SIZE=hidden_block_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@triton.jit
|
||||||
|
def _update_prefill_inputs_kernel(
|
||||||
|
input_ids_ptr,
|
||||||
|
query_start_loc_ptr,
|
||||||
|
last_token_indices_ptr,
|
||||||
|
draft_tokens_ptr,
|
||||||
|
BLOCK_SIZE: tl.constexpr,
|
||||||
|
):
|
||||||
|
req_idx = tl.program_id(0)
|
||||||
|
query_start = tl.load(query_start_loc_ptr + req_idx)
|
||||||
|
# Use the post-rejection last token index so the shift and insertion align
|
||||||
|
# with the position the draft token was sampled from.
|
||||||
|
last_token_index = tl.load(last_token_indices_ptr + req_idx)
|
||||||
|
query_len = last_token_index - query_start + 1
|
||||||
|
|
||||||
|
# Shift input token ids to the left by one position and
|
||||||
|
# insert the last sampled draft token.
|
||||||
|
for i in range(1, query_len, BLOCK_SIZE):
|
||||||
|
block = i + tl.arange(0, BLOCK_SIZE)
|
||||||
|
mask = block < query_len
|
||||||
|
input_ids = tl.load(input_ids_ptr + query_start + block, mask=mask)
|
||||||
|
tl.store(input_ids_ptr + query_start + block - 1, input_ids, mask=mask)
|
||||||
|
draft_token = tl.load(draft_tokens_ptr + req_idx)
|
||||||
|
tl.store(input_ids_ptr + last_token_index, draft_token)
|
||||||
|
|
||||||
|
|
||||||
|
def update_prefill_inputs(
|
||||||
|
draft_tokens: torch.Tensor,
|
||||||
|
input_buffers: InputBuffers,
|
||||||
|
last_token_indices: torch.Tensor,
|
||||||
|
num_reqs: int,
|
||||||
|
) -> None:
|
||||||
|
_update_prefill_inputs_kernel[(num_reqs,)](
|
||||||
|
input_buffers.input_ids,
|
||||||
|
input_buffers.query_start_loc,
|
||||||
|
last_token_indices,
|
||||||
|
draft_tokens,
|
||||||
|
BLOCK_SIZE=1024,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@triton.jit
|
@triton.jit
|
||||||
def _prepare_decode_inputs_kernel(
|
def _prepare_decode_inputs_kernel(
|
||||||
draft_tokens_ptr,
|
draft_tokens_ptr,
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ class DFlashSpeculator(DraftModelSpeculator):
|
|||||||
num_reqs, self.num_speculative_steps
|
num_reqs, self.num_speculative_steps
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_draft_attn_metadata(
|
def _build_uniform_draft_attn_metadata(
|
||||||
self,
|
self,
|
||||||
num_reqs: int,
|
num_reqs: int,
|
||||||
num_reqs_padded: int,
|
num_reqs_padded: int,
|
||||||
@@ -275,7 +275,7 @@ class DFlashSpeculator(DraftModelSpeculator):
|
|||||||
if not self.draft_attn_layer_names:
|
if not self.draft_attn_layer_names:
|
||||||
return None
|
return None
|
||||||
assert num_query_per_req is None # Omitted for DFlash, read from self instead
|
assert num_query_per_req is None # Omitted for DFlash, read from self instead
|
||||||
return super()._build_draft_attn_metadata(
|
return super()._build_uniform_draft_attn_metadata(
|
||||||
num_reqs,
|
num_reqs,
|
||||||
num_reqs_padded,
|
num_reqs_padded,
|
||||||
num_tokens_padded,
|
num_tokens_padded,
|
||||||
@@ -423,7 +423,7 @@ class DFlashSpeculator(DraftModelSpeculator):
|
|||||||
|
|
||||||
# Rebuild the draft attention metadata even when replaying the FULL
|
# Rebuild the draft attention metadata even when replaying the FULL
|
||||||
# graph so that any attention metadata builder state is updated.
|
# graph so that any attention metadata builder state is updated.
|
||||||
draft_attn_metadata = self._build_draft_attn_metadata(
|
draft_attn_metadata = self._build_uniform_draft_attn_metadata(
|
||||||
num_reqs=num_reqs,
|
num_reqs=num_reqs,
|
||||||
num_reqs_padded=num_reqs_padded,
|
num_reqs_padded=num_reqs_padded,
|
||||||
num_tokens_padded=num_tokens_padded,
|
num_tokens_padded=num_tokens_padded,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
import torch.nn as nn
|
import torch.nn as nn
|
||||||
|
|
||||||
@@ -122,9 +123,7 @@ class DraftModelSpeculator(BaseSpeculator):
|
|||||||
dtype=torch.int64,
|
dtype=torch.int64,
|
||||||
device=device,
|
device=device,
|
||||||
)
|
)
|
||||||
self.arange = torch.arange(
|
self.np_arange = np.arange(self.max_num_reqs + 1, dtype=np.int32)
|
||||||
self.max_num_reqs + 1, dtype=torch.int32, device="cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.draft_logits: torch.Tensor | None = None
|
self.draft_logits: torch.Tensor | None = None
|
||||||
if self.speculative_config.draft_sample_method == "probabilistic":
|
if self.speculative_config.draft_sample_method == "probabilistic":
|
||||||
@@ -205,7 +204,7 @@ class DraftModelSpeculator(BaseSpeculator):
|
|||||||
self.target_input_buffers = target_input_buffers
|
self.target_input_buffers = target_input_buffers
|
||||||
self.target_attn_groups = target_attn_groups
|
self.target_attn_groups = target_attn_groups
|
||||||
|
|
||||||
def _build_draft_attn_metadata(
|
def _build_uniform_draft_attn_metadata(
|
||||||
self,
|
self,
|
||||||
num_reqs: int,
|
num_reqs: int,
|
||||||
num_reqs_padded: int,
|
num_reqs_padded: int,
|
||||||
@@ -213,13 +212,30 @@ class DraftModelSpeculator(BaseSpeculator):
|
|||||||
num_query_per_req: int = 1,
|
num_query_per_req: int = 1,
|
||||||
causal: bool | Mapping[int, bool] = True,
|
causal: bool | Mapping[int, bool] = True,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
# Uniform query: query_start_loc[i] = min(i, num_reqs) * num_query_per_req.
|
return self._build_draft_attn_metadata(
|
||||||
# Clamp keeps the series non-decreasing past num_reqs, which some
|
num_reqs,
|
||||||
# attention backends require.
|
num_reqs_padded,
|
||||||
query_start_loc_cpu = (
|
num_tokens_padded,
|
||||||
torch.clamp(self.arange[: num_reqs_padded + 1], max=num_reqs)
|
self.np_arange[: num_reqs_padded + 1] * num_query_per_req,
|
||||||
* num_query_per_req
|
causal=causal,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _build_draft_attn_metadata(
|
||||||
|
self,
|
||||||
|
num_reqs: int,
|
||||||
|
num_reqs_padded: int,
|
||||||
|
num_tokens_padded: int,
|
||||||
|
query_start_loc_np: np.ndarray,
|
||||||
|
causal: bool | Mapping[int, bool] = True,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
# Fresh tensor: no aliasing with the caller's array, sized to the padded
|
||||||
|
# request count, with the tail clamped to the last real cumulative value.
|
||||||
|
query_start_loc_cpu = torch.empty(num_reqs_padded + 1, dtype=torch.int32)
|
||||||
|
query_start_loc_cpu[: num_reqs + 1] = torch.from_numpy(
|
||||||
|
query_start_loc_np[: num_reqs + 1]
|
||||||
|
)
|
||||||
|
query_start_loc_cpu[num_reqs:] = query_start_loc_cpu[num_reqs]
|
||||||
|
max_query_len = int((query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]).max())
|
||||||
block_tables = [
|
block_tables = [
|
||||||
x[:num_reqs_padded] for x in self.block_tables.input_block_tables
|
x[:num_reqs_padded] for x in self.block_tables.input_block_tables
|
||||||
]
|
]
|
||||||
@@ -232,7 +248,7 @@ class DraftModelSpeculator(BaseSpeculator):
|
|||||||
: num_reqs_padded + 1
|
: num_reqs_padded + 1
|
||||||
],
|
],
|
||||||
query_start_loc_cpu=query_start_loc_cpu,
|
query_start_loc_cpu=query_start_loc_cpu,
|
||||||
max_query_len=num_query_per_req,
|
max_query_len=max_query_len,
|
||||||
seq_lens=self.input_buffers.seq_lens[:num_reqs_padded],
|
seq_lens=self.input_buffers.seq_lens[:num_reqs_padded],
|
||||||
max_seq_len=self.draft_max_seq_len,
|
max_seq_len=self.draft_max_seq_len,
|
||||||
block_tables=block_tables,
|
block_tables=block_tables,
|
||||||
@@ -313,7 +329,6 @@ class DraftModelSpeculator(BaseSpeculator):
|
|||||||
self.temperature.copy_(temperature)
|
self.temperature.copy_(temperature)
|
||||||
self.seeds.copy_(seeds)
|
self.seeds.copy_(seeds)
|
||||||
self.idx_mapping[:num_reqs].copy_(idx_mapping)
|
self.idx_mapping[:num_reqs].copy_(idx_mapping)
|
||||||
if self.draft_logits is not None:
|
# idx_mapping for CG padded requests points to -1, which is ignored
|
||||||
# idx_mapping for CG padded requests points to -1, which is ignored
|
# during sampling to prevent writing stale values to draft logits.
|
||||||
# during sampling to prevent writing stale values to draft logits.
|
self.idx_mapping[num_reqs:].fill_(-1)
|
||||||
self.idx_mapping[num_reqs:].fill_(-1)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user