[Spec] Anchor GLM-5.2 MTP IndexShare topk on the draft-extend step (#29787)
Co-authored-by: kpham-sgl <264503018+kpham-sgl@users.noreply.github.com> Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com> Co-authored-by: Xinyuan Tong <xinyuantong.cs@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
kpham-sgl
Xinyuan Tong
Xinyuan Tong
Claude Fable 5
parent
4145e595cf
commit
16372b4c5f
@@ -737,7 +737,7 @@ class TboForwardBatchPreparer:
|
|||||||
"split_index", # for split prefill
|
"split_index", # for split prefill
|
||||||
"orig_seq_lens", # only used by qwen-1m, thus not care
|
"orig_seq_lens", # only used by qwen-1m, thus not care
|
||||||
"return_pooled_hidden_states",
|
"return_pooled_hidden_states",
|
||||||
"reuse_mtp_topk_indices", # forward-level flag, inherited by both child batches
|
"reuse_dsa_topk_indices", # forward-level flag, inherited by both child batches
|
||||||
]:
|
]:
|
||||||
output_dict[key] = getattr(batch, key)
|
output_dict[key] = getattr(batch, key)
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ class RelayPayload:
|
|||||||
topk_index: Optional[torch.Tensor] = None
|
topk_index: Optional[torch.Tensor] = None
|
||||||
hidden_states: Optional[torch.Tensor] = None
|
hidden_states: Optional[torch.Tensor] = None
|
||||||
draft_probs: Optional[torch.Tensor] = None
|
draft_probs: Optional[torch.Tensor] = None
|
||||||
|
dsa_topk_indices: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_draft_input(cls, draft_input: EagleDraftInput) -> RelayPayload:
|
def from_draft_input(cls, draft_input: EagleDraftInput) -> RelayPayload:
|
||||||
@@ -120,6 +121,7 @@ class RelayPayload:
|
|||||||
topk_index=draft_input.topk_index,
|
topk_index=draft_input.topk_index,
|
||||||
hidden_states=draft_input.hidden_states,
|
hidden_states=draft_input.hidden_states,
|
||||||
draft_probs=getattr(draft_input, "draft_probs", None),
|
draft_probs=getattr(draft_input, "draft_probs", None),
|
||||||
|
dsa_topk_indices=getattr(draft_input, "dsa_topk_indices", None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -216,6 +218,15 @@ class FutureMap:
|
|||||||
device=self.device,
|
device=self.device,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.dsa_topk_indices_buf = None
|
||||||
|
if payload.dsa_topk_indices is not None:
|
||||||
|
seed0 = payload.dsa_topk_indices[0]
|
||||||
|
self.dsa_topk_indices_buf = torch.empty(
|
||||||
|
(self.req_pool_size, *seed0.shape),
|
||||||
|
dtype=payload.dsa_topk_indices.dtype,
|
||||||
|
device=self.device,
|
||||||
|
)
|
||||||
|
|
||||||
def _resolve_spec_extras(self, batch: ScheduleBatch) -> None:
|
def _resolve_spec_extras(self, batch: ScheduleBatch) -> None:
|
||||||
if self.spec_algo.is_ngram():
|
if self.spec_algo.is_ngram():
|
||||||
# FIXME: remove once precomputed draft is supported.
|
# FIXME: remove once precomputed draft is supported.
|
||||||
@@ -255,6 +266,8 @@ class FutureMap:
|
|||||||
draft_input.bonus_tokens = self.output_tokens_buf[indices]
|
draft_input.bonus_tokens = self.output_tokens_buf[indices]
|
||||||
if self.need_hidden_states and not self.need_topk:
|
if self.need_hidden_states and not self.need_topk:
|
||||||
draft_input.hidden_states = self.hidden_states_buf[indices]
|
draft_input.hidden_states = self.hidden_states_buf[indices]
|
||||||
|
if self.dsa_topk_indices_buf is not None:
|
||||||
|
draft_input.dsa_topk_indices = self.dsa_topk_indices_buf[indices]
|
||||||
if _DEBUG_ASSERT:
|
if _DEBUG_ASSERT:
|
||||||
_assert_nonneg_and_invalidate(
|
_assert_nonneg_and_invalidate(
|
||||||
draft_input.bonus_tokens, self.output_tokens_buf, indices
|
draft_input.bonus_tokens, self.output_tokens_buf, indices
|
||||||
@@ -338,3 +351,10 @@ class FutureMap:
|
|||||||
)
|
)
|
||||||
if self.draft_probs_buf is not None and payload.draft_probs is not None:
|
if self.draft_probs_buf is not None and payload.draft_probs is not None:
|
||||||
self.draft_probs_buf[indices] = payload.draft_probs
|
self.draft_probs_buf[indices] = payload.draft_probs
|
||||||
|
if (
|
||||||
|
self.dsa_topk_indices_buf is not None
|
||||||
|
and payload.dsa_topk_indices is not None
|
||||||
|
):
|
||||||
|
self.dsa_topk_indices_buf[indices] = payload.dsa_topk_indices.to(
|
||||||
|
self.dsa_topk_indices_buf.dtype
|
||||||
|
)
|
||||||
|
|||||||
@@ -431,8 +431,8 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
|
|||||||
return_hidden_states_before_norm: bool = False
|
return_hidden_states_before_norm: bool = False
|
||||||
|
|
||||||
# Gate for reusing the first MTP draft step's indexer topk across steps;
|
# Gate for reusing the first MTP draft step's indexer topk across steps;
|
||||||
# the carried topk lives on spec_info (see EagleDraftInput.mtp_topk_indices).
|
# the carried topk lives on spec_info (see EagleDraftInput.dsa_topk_indices).
|
||||||
reuse_mtp_topk_indices: Optional[bool] = False
|
reuse_dsa_topk_indices: Optional[bool] = False
|
||||||
|
|
||||||
# === Forward-derived (built in init_new on the forward stream; FB-owned) ===
|
# === Forward-derived (built in init_new on the forward stream; FB-owned) ===
|
||||||
# Position information
|
# Position information
|
||||||
|
|||||||
@@ -237,13 +237,22 @@ class DeepseekModelNextN(nn.Module):
|
|||||||
residual,
|
residual,
|
||||||
zero_allocator,
|
zero_allocator,
|
||||||
prev_topk_indices=(
|
prev_topk_indices=(
|
||||||
forward_batch.spec_info.mtp_topk_indices
|
forward_batch.spec_info.dsa_topk_indices
|
||||||
if forward_batch.reuse_mtp_topk_indices
|
if forward_batch.reuse_dsa_topk_indices
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if forward_batch.reuse_mtp_topk_indices:
|
if forward_batch.reuse_dsa_topk_indices:
|
||||||
forward_batch.spec_info.mtp_topk_indices = topk_indices
|
forward_batch.spec_info.dsa_topk_indices = topk_indices
|
||||||
|
|
||||||
|
# MTP IndexShare: on draft-extend, publish the last-token DSA
|
||||||
|
# indexer top-k to seed (avoid recomputing in) the draft-decode loop.
|
||||||
|
if forward_batch.forward_mode.is_extend(include_draft_extend_v2=True):
|
||||||
|
seed_buf = forward_batch.spec_info.dsa_seed_topk_capture
|
||||||
|
if seed_buf is not None and topk_indices is not None:
|
||||||
|
sel = forward_batch.spec_info.dsa_seed_topk_select
|
||||||
|
src = topk_indices if sel is None else topk_indices[sel]
|
||||||
|
seed_buf[: src.shape[0]].copy_(src)
|
||||||
|
|
||||||
if not forward_batch.forward_mode.is_idle():
|
if not forward_batch.forward_mode.is_idle():
|
||||||
if residual is not None:
|
if residual is not None:
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class EagleDraftInputBuffers(ForwardInputBuffers):
|
|||||||
hidden_states: Optional[torch.Tensor]
|
hidden_states: Optional[torch.Tensor]
|
||||||
global_num_tokens_gpu: Optional[torch.Tensor]
|
global_num_tokens_gpu: Optional[torch.Tensor]
|
||||||
global_num_tokens_for_logprob_gpu: Optional[torch.Tensor]
|
global_num_tokens_for_logprob_gpu: Optional[torch.Tensor]
|
||||||
|
dsa_seed_topk: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
|
|
||||||
class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
||||||
@@ -228,6 +229,16 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
(self.max_bs,), self.seq_len_fill_value, dtype=torch.int64, device="cpu"
|
(self.max_bs,), self.seq_len_fill_value, dtype=torch.int64, device="cpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dsa_seed_topk = (
|
||||||
|
torch.zeros(
|
||||||
|
(self.max_bs, self.eagle_worker.dsa_index_topk),
|
||||||
|
dtype=torch.int32,
|
||||||
|
device=model_runner.device,
|
||||||
|
)
|
||||||
|
if self.eagle_worker.seed_dsa_topk_from_draft_extend
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
self.buffers = EagleDraftInputBuffers(
|
self.buffers = EagleDraftInputBuffers(
|
||||||
input_ids=input_ids,
|
input_ids=input_ids,
|
||||||
req_pool_indices=req_pool_indices,
|
req_pool_indices=req_pool_indices,
|
||||||
@@ -245,6 +256,7 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
hidden_states=hidden_states,
|
hidden_states=hidden_states,
|
||||||
global_num_tokens_gpu=global_num_tokens_gpu,
|
global_num_tokens_gpu=global_num_tokens_gpu,
|
||||||
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
||||||
|
dsa_seed_topk=dsa_seed_topk,
|
||||||
)
|
)
|
||||||
self.buffers.share_buffers()
|
self.buffers.share_buffers()
|
||||||
|
|
||||||
@@ -372,6 +384,8 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
hidden_states=hidden_states,
|
hidden_states=hidden_states,
|
||||||
capture_hidden_mode=capture_mode,
|
capture_hidden_mode=capture_mode,
|
||||||
)
|
)
|
||||||
|
if self.buffers.dsa_seed_topk is not None:
|
||||||
|
spec_info.dsa_topk_indices = self.buffers.dsa_seed_topk[:num_seqs]
|
||||||
|
|
||||||
sampling_info = SamplingBatchInfo(
|
sampling_info = SamplingBatchInfo(
|
||||||
temperatures=self.temperatures[:num_seqs],
|
temperatures=self.temperatures[:num_seqs],
|
||||||
@@ -504,6 +518,8 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
buffers.draft_probs.zero_()
|
buffers.draft_probs.zero_()
|
||||||
if buffers.hidden_states is not None:
|
if buffers.hidden_states is not None:
|
||||||
buffers.hidden_states.zero_()
|
buffers.hidden_states.zero_()
|
||||||
|
if buffers.dsa_seed_topk is not None:
|
||||||
|
buffers.dsa_seed_topk.zero_()
|
||||||
buffers.req_pool_indices.zero_()
|
buffers.req_pool_indices.zero_()
|
||||||
|
|
||||||
num_tokens = bs * self.num_tokens_per_bs
|
num_tokens = bs * self.num_tokens_per_bs
|
||||||
@@ -563,6 +579,12 @@ class EAGLEDraftCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
and forward_batch.spec_info.hidden_states is not None
|
and forward_batch.spec_info.hidden_states is not None
|
||||||
):
|
):
|
||||||
buffers.hidden_states[:raw_bs].copy_(forward_batch.spec_info.hidden_states)
|
buffers.hidden_states[:raw_bs].copy_(forward_batch.spec_info.hidden_states)
|
||||||
|
if buffers.dsa_seed_topk is not None:
|
||||||
|
seed = forward_batch.spec_info.dsa_topk_indices
|
||||||
|
if seed is not None:
|
||||||
|
buffers.dsa_seed_topk[:raw_bs].copy_(seed)
|
||||||
|
else:
|
||||||
|
buffers.dsa_seed_topk[:raw_bs].zero_()
|
||||||
# Only rejection sampling reads temperatures (renorm_draft_probs); skip
|
# Only rejection sampling reads temperatures (renorm_draft_probs); skip
|
||||||
# the copy otherwise to keep the non-RS path free of extra work.
|
# the copy otherwise to keep the non-RS path free of extra work.
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class EagleDraftExtendInputBuffers(ForwardInputBuffers):
|
|||||||
next_token_logits_buffer: torch.Tensor
|
next_token_logits_buffer: torch.Tensor
|
||||||
global_num_tokens_gpu: Optional[torch.Tensor]
|
global_num_tokens_gpu: Optional[torch.Tensor]
|
||||||
global_num_tokens_for_logprob_gpu: Optional[torch.Tensor]
|
global_num_tokens_for_logprob_gpu: Optional[torch.Tensor]
|
||||||
|
dsa_seed_topk_capture: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
|
|
||||||
class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
||||||
@@ -234,6 +235,17 @@ class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
(self.max_bs,), self.seq_len_fill_value, dtype=torch.int64, device="cpu"
|
(self.max_bs,), self.seq_len_fill_value, dtype=torch.int64, device="cpu"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dsa_seed_topk_capture = (
|
||||||
|
torch.full(
|
||||||
|
(self.max_num_token, self.eagle_worker.dsa_index_topk),
|
||||||
|
-1,
|
||||||
|
dtype=torch.int32,
|
||||||
|
device=model_runner.device,
|
||||||
|
)
|
||||||
|
if self.eagle_worker.seed_dsa_topk_from_draft_extend
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
self.buffers = EagleDraftExtendInputBuffers(
|
self.buffers = EagleDraftExtendInputBuffers(
|
||||||
input_ids=input_ids,
|
input_ids=input_ids,
|
||||||
req_pool_indices=req_pool_indices,
|
req_pool_indices=req_pool_indices,
|
||||||
@@ -249,6 +261,7 @@ class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
next_token_logits_buffer=next_token_logits_buffer,
|
next_token_logits_buffer=next_token_logits_buffer,
|
||||||
global_num_tokens_gpu=global_num_tokens_gpu,
|
global_num_tokens_gpu=global_num_tokens_gpu,
|
||||||
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
||||||
|
dsa_seed_topk_capture=dsa_seed_topk_capture,
|
||||||
)
|
)
|
||||||
self.buffers.share_buffers()
|
self.buffers.share_buffers()
|
||||||
|
|
||||||
@@ -385,6 +398,11 @@ class EAGLEDraftExtendCudaGraphRunner(DecodeCudaGraphRunner):
|
|||||||
padded_static_len=self.padded_static_len,
|
padded_static_len=self.padded_static_len,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.buffers.dsa_seed_topk_capture is not None:
|
||||||
|
spec_info.dsa_seed_topk_capture = self.buffers.dsa_seed_topk_capture[
|
||||||
|
:num_tokens
|
||||||
|
]
|
||||||
|
|
||||||
def run_once():
|
def run_once():
|
||||||
self.draft_extend_attn_backend.init_forward_metadata_in_graph(forward_batch)
|
self.draft_extend_attn_backend.init_forward_metadata_in_graph(forward_batch)
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class EagleDraftInput(SpecInput):
|
|||||||
|
|
||||||
# Survives across draft steps: spec_info is shared by reference across the
|
# Survives across draft steps: spec_info is shared by reference across the
|
||||||
# per-step forwards (each runs on a copied ForwardBatch, dropping writebacks).
|
# per-step forwards (each runs on a copied ForwardBatch, dropping writebacks).
|
||||||
mtp_topk_indices: Optional[torch.Tensor] = None
|
dsa_topk_indices: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
# Per-req bonus token (the "+1" target prediction at end of each accept
|
# Per-req bonus token (the "+1" target prediction at end of each accept
|
||||||
# chain); the worker copies it here post-extend for next iter's draft.
|
# chain); the worker copies it here post-extend for next iter's draft.
|
||||||
@@ -233,6 +233,8 @@ class EagleDraftInput(SpecInput):
|
|||||||
if self.hidden_states is not None:
|
if self.hidden_states is not None:
|
||||||
self.hidden_states = self.hidden_states[: len(new_indices)]
|
self.hidden_states = self.hidden_states[: len(new_indices)]
|
||||||
self.bonus_tokens = self.bonus_tokens[: len(new_indices)]
|
self.bonus_tokens = self.bonus_tokens[: len(new_indices)]
|
||||||
|
if self.dsa_topk_indices is not None:
|
||||||
|
self.dsa_topk_indices = self.dsa_topk_indices[: len(new_indices)]
|
||||||
else:
|
else:
|
||||||
# in some cases(e.g draft_extend), we have not filtered the batch by `unfinished_index`
|
# in some cases(e.g draft_extend), we have not filtered the batch by `unfinished_index`
|
||||||
self.topk_p = self.topk_p[new_indices]
|
self.topk_p = self.topk_p[new_indices]
|
||||||
@@ -242,6 +244,8 @@ class EagleDraftInput(SpecInput):
|
|||||||
if self.hidden_states is not None:
|
if self.hidden_states is not None:
|
||||||
self.hidden_states = self.hidden_states[new_indices]
|
self.hidden_states = self.hidden_states[new_indices]
|
||||||
self.bonus_tokens = self.bonus_tokens[new_indices]
|
self.bonus_tokens = self.bonus_tokens[new_indices]
|
||||||
|
if self.dsa_topk_indices is not None:
|
||||||
|
self.dsa_topk_indices = self.dsa_topk_indices[new_indices]
|
||||||
|
|
||||||
def merge_batch(self, spec_info: "EagleDraftInput"):
|
def merge_batch(self, spec_info: "EagleDraftInput"):
|
||||||
if self.future_indices is not None:
|
if self.future_indices is not None:
|
||||||
@@ -260,6 +264,7 @@ class EagleDraftInput(SpecInput):
|
|||||||
self.topk_p = spec_info.topk_p
|
self.topk_p = spec_info.topk_p
|
||||||
self.topk_index = spec_info.topk_index
|
self.topk_index = spec_info.topk_index
|
||||||
self.draft_probs = spec_info.draft_probs
|
self.draft_probs = spec_info.draft_probs
|
||||||
|
self.dsa_topk_indices = spec_info.dsa_topk_indices
|
||||||
return
|
return
|
||||||
if len(spec_info.topk_index) == 0:
|
if len(spec_info.topk_index) == 0:
|
||||||
return
|
return
|
||||||
@@ -272,6 +277,12 @@ class EagleDraftInput(SpecInput):
|
|||||||
)
|
)
|
||||||
self.topk_p = torch.cat([self.topk_p, spec_info.topk_p])
|
self.topk_p = torch.cat([self.topk_p, spec_info.topk_p])
|
||||||
self.topk_index = torch.cat([self.topk_index, spec_info.topk_index])
|
self.topk_index = torch.cat([self.topk_index, spec_info.topk_index])
|
||||||
|
if self.dsa_topk_indices is not None and spec_info.dsa_topk_indices is not None:
|
||||||
|
self.dsa_topk_indices = torch.cat(
|
||||||
|
[self.dsa_topk_indices, spec_info.dsa_topk_indices]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.dsa_topk_indices = None
|
||||||
if self.draft_probs is not None and spec_info.draft_probs is not None:
|
if self.draft_probs is not None and spec_info.draft_probs is not None:
|
||||||
self.draft_probs = torch.cat([self.draft_probs, spec_info.draft_probs])
|
self.draft_probs = torch.cat([self.draft_probs, spec_info.draft_probs])
|
||||||
|
|
||||||
@@ -317,6 +328,9 @@ class EagleDraftExtendInput(SpecInput):
|
|||||||
num_tokens_per_req: int = -1
|
num_tokens_per_req: int = -1
|
||||||
num_tokens_for_logprob_per_req: int = 1
|
num_tokens_for_logprob_per_req: int = 1
|
||||||
|
|
||||||
|
dsa_seed_topk_capture: Optional[torch.Tensor] = None
|
||||||
|
dsa_seed_topk_select: Optional[torch.Tensor] = None
|
||||||
|
|
||||||
# None for draft-extend's idle batch; attention backends fall back to
|
# None for draft-extend's idle batch; attention backends fall back to
|
||||||
# rebuilding plain metadata from seq_lens when this is None.
|
# rebuilding plain metadata from seq_lens when this is None.
|
||||||
kv_indptr: torch.Tensor = None
|
kv_indptr: torch.Tensor = None
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from sglang.srt.hardware_backend.npu.graph_runner.eagle_draft_npu_graph_runner i
|
|||||||
)
|
)
|
||||||
from sglang.srt.hardware_backend.npu.graph_runner.npu_graph_runner import NPUGraphRunner
|
from sglang.srt.hardware_backend.npu.graph_runner.npu_graph_runner import NPUGraphRunner
|
||||||
from sglang.srt.kv_canary.runner.canary_manager import context_tuple
|
from sglang.srt.kv_canary.runner.canary_manager import context_tuple
|
||||||
|
from sglang.srt.layers.attention.dsa.utils import dsa_use_prefill_cp
|
||||||
from sglang.srt.layers.attention.flashinfer_backend import FlashInferAttnBackend
|
from sglang.srt.layers.attention.flashinfer_backend import FlashInferAttnBackend
|
||||||
from sglang.srt.layers.attention.tokenspeed_mla_backend import TokenspeedMLABackend
|
from sglang.srt.layers.attention.tokenspeed_mla_backend import TokenspeedMLABackend
|
||||||
from sglang.srt.layers.attention.triton_backend import TritonAttnBackend
|
from sglang.srt.layers.attention.triton_backend import TritonAttnBackend
|
||||||
@@ -191,16 +192,9 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
|
|
||||||
# Alias for better readability
|
# Alias for better readability
|
||||||
self.draft_runner = self.draft_worker.model_runner
|
self.draft_runner = self.draft_worker.model_runner
|
||||||
# Reuse the first draft step's NSA/DSA indexer topk across the rest;
|
self._init_dsa_index_share_state()
|
||||||
# topk == 1 only (select_top_k_tokens reorders rows, desyncing indices).
|
# Eager draft-extend seed buffer (graph paths use their own static ones).
|
||||||
self.index_share_for_mtp_iteration = (
|
self.dsa_extend_topk_buf: Optional[torch.Tensor] = None
|
||||||
getattr(
|
|
||||||
self.draft_runner.model_config.hf_config,
|
|
||||||
"index_share_for_mtp_iteration",
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
and self.topk == 1
|
|
||||||
)
|
|
||||||
self.draft_tp_context = (
|
self.draft_tp_context = (
|
||||||
draft_tp_context if server_args.enable_dp_attention else empty_context
|
draft_tp_context if server_args.enable_dp_attention else empty_context
|
||||||
)
|
)
|
||||||
@@ -264,6 +258,23 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
if (c := self.draft_runner.canary_manager) is not None:
|
if (c := self.draft_runner.canary_manager) is not None:
|
||||||
c.mark_init_finished()
|
c.mark_init_finished()
|
||||||
|
|
||||||
|
def _init_dsa_index_share_state(self) -> None:
|
||||||
|
# Populate DSA index-share fields from the draft runner's hf_config.
|
||||||
|
# Reused by the attention unit-test harnesses, which skip __init__.
|
||||||
|
hf_config = self.draft_runner.model_config.hf_config
|
||||||
|
# Reuse the first draft step's DSA indexer topk across the rest;
|
||||||
|
# topk == 1 only (select_top_k_tokens reorders rows, desyncing indices).
|
||||||
|
self.index_share_for_mtp_iteration = (
|
||||||
|
getattr(hf_config, "index_share_for_mtp_iteration", False)
|
||||||
|
and self.topk == 1
|
||||||
|
)
|
||||||
|
# GLM-5.2 MTP IndexShare: seed reused indexer top-k from draft-extend
|
||||||
|
# (last verified token), not draft-decode step 0.
|
||||||
|
self.dsa_index_topk = getattr(hf_config, "index_topk", None)
|
||||||
|
self.seed_dsa_topk_from_draft_extend = (
|
||||||
|
self.index_share_for_mtp_iteration and self.dsa_index_topk is not None
|
||||||
|
)
|
||||||
|
|
||||||
def _rebuild_topk1_chain_buffers(self) -> None:
|
def _rebuild_topk1_chain_buffers(self) -> None:
|
||||||
# For topk=1 the draft tree degenerates to a chain, so parent_list and
|
# For topk=1 the draft tree degenerates to a chain, so parent_list and
|
||||||
# top_scores_index are runtime-invariant. Must be rebuilt after any
|
# top_scores_index are runtime-invariant. Must be rebuilt after any
|
||||||
@@ -629,8 +640,13 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
# Forward multiple steps
|
# Forward multiple steps
|
||||||
scores = None
|
scores = None
|
||||||
if self.index_share_for_mtp_iteration:
|
if self.index_share_for_mtp_iteration:
|
||||||
forward_batch.reuse_mtp_topk_indices = True
|
forward_batch.reuse_dsa_topk_indices = True
|
||||||
spec_info.mtp_topk_indices = None
|
# Keep the draft-extend seed so step 0 reuses it; else recompute it.
|
||||||
|
if not (
|
||||||
|
self.seed_dsa_topk_from_draft_extend
|
||||||
|
and spec_info.dsa_topk_indices is not None
|
||||||
|
):
|
||||||
|
spec_info.dsa_topk_indices = None
|
||||||
for i in range(self.speculative_num_steps):
|
for i in range(self.speculative_num_steps):
|
||||||
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
input_ids, hidden_states, scores, tree_info = select_top_k_tokens(
|
||||||
i, topk_p, topk_index, hidden_states, scores, self.topk
|
i, topk_p, topk_index, hidden_states, scores, self.topk
|
||||||
@@ -706,8 +722,8 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
forward_batch.positions.add_(1)
|
forward_batch.positions.add_(1)
|
||||||
|
|
||||||
if self.index_share_for_mtp_iteration:
|
if self.index_share_for_mtp_iteration:
|
||||||
spec_info.mtp_topk_indices = None
|
spec_info.dsa_topk_indices = None
|
||||||
forward_batch.reuse_mtp_topk_indices = False
|
forward_batch.reuse_dsa_topk_indices = False
|
||||||
|
|
||||||
# Organize the results
|
# Organize the results
|
||||||
if (
|
if (
|
||||||
@@ -794,6 +810,23 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
if mm_input_embeds is not None:
|
if mm_input_embeds is not None:
|
||||||
forward_batch.mm_input_embeds = mm_input_embeds
|
forward_batch.mm_input_embeds = mm_input_embeds
|
||||||
|
|
||||||
|
# Seed the first draft-decode loop from each request's last prefill
|
||||||
|
# position. Gather last-per-req before the copy (prefill can be long).
|
||||||
|
# Skipped under context-parallel prefill (token layout wouldn't match).
|
||||||
|
seed_from_extend = (
|
||||||
|
self.seed_dsa_topk_from_draft_extend
|
||||||
|
and not forward_batch.forward_mode.is_idle()
|
||||||
|
and not dsa_use_prefill_cp(forward_batch)
|
||||||
|
)
|
||||||
|
if seed_from_extend:
|
||||||
|
bs = forward_batch.batch_size
|
||||||
|
forward_batch.spec_info.dsa_seed_topk_capture = (
|
||||||
|
self._get_dsa_extend_topk_buf(bs)
|
||||||
|
)
|
||||||
|
forward_batch.spec_info.dsa_seed_topk_select = (
|
||||||
|
torch.cumsum(forward_batch.extend_seq_lens, dim=0) - 1
|
||||||
|
).long()
|
||||||
|
|
||||||
canary_ctx = (
|
canary_ctx = (
|
||||||
context_tuple(
|
context_tuple(
|
||||||
c.with_ops_outside_graph(
|
c.with_ops_outside_graph(
|
||||||
@@ -810,6 +843,10 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
maybe_detect_nan(logits_output.next_token_logits, "draft_extend_for_prefill")
|
maybe_detect_nan(logits_output.next_token_logits, "draft_extend_for_prefill")
|
||||||
maybe_detect_inf(logits_output.next_token_logits, "draft_extend_for_prefill")
|
maybe_detect_inf(logits_output.next_token_logits, "draft_extend_for_prefill")
|
||||||
|
|
||||||
|
prefill_dsa_topk = None
|
||||||
|
if seed_from_extend:
|
||||||
|
prefill_dsa_topk = self.dsa_extend_topk_buf[:bs].clone()
|
||||||
|
|
||||||
# Assemble the next-iter draft spec_info from the extend output.
|
# Assemble the next-iter draft spec_info from the extend output.
|
||||||
use_rejection_sampling = self.server_args.speculative_use_rejection_sampling
|
use_rejection_sampling = self.server_args.speculative_use_rejection_sampling
|
||||||
probs = renorm_draft_probs(
|
probs = renorm_draft_probs(
|
||||||
@@ -829,8 +866,22 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
bonus_tokens=next_token_ids,
|
bonus_tokens=next_token_ids,
|
||||||
num_tokens_per_req=1,
|
num_tokens_per_req=1,
|
||||||
num_tokens_for_logprob_per_req=1,
|
num_tokens_for_logprob_per_req=1,
|
||||||
|
dsa_topk_indices=prefill_dsa_topk,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _get_dsa_extend_topk_buf(self, num_tokens: int) -> torch.Tensor:
|
||||||
|
"""Lazily-grown int32 [num_tokens, index_topk] eager draft-extend seed buffer."""
|
||||||
|
buf = self.dsa_extend_topk_buf
|
||||||
|
if buf is None or buf.shape[0] < num_tokens:
|
||||||
|
buf = torch.full(
|
||||||
|
(num_tokens, self.dsa_index_topk),
|
||||||
|
-1,
|
||||||
|
dtype=torch.int32,
|
||||||
|
device=self.device,
|
||||||
|
)
|
||||||
|
self.dsa_extend_topk_buf = buf
|
||||||
|
return buf[:num_tokens]
|
||||||
|
|
||||||
def _draft_extend_for_decode(
|
def _draft_extend_for_decode(
|
||||||
self, batch: ScheduleBatch, batch_result: GenerationBatchResult
|
self, batch: ScheduleBatch, batch_result: GenerationBatchResult
|
||||||
):
|
):
|
||||||
@@ -882,6 +933,13 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch)
|
and self.cuda_graph_runner_for_draft_extend.can_run_graph(forward_batch)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Eager path publishes the indexer top-k into a worker buffer (the graph
|
||||||
|
# path uses the runner's static buffer). Gathered at select_index below.
|
||||||
|
if self.seed_dsa_topk_from_draft_extend and not can_cuda_graph:
|
||||||
|
forward_batch.spec_info.dsa_seed_topk_capture = (
|
||||||
|
self._get_dsa_extend_topk_buf(forward_batch.input_ids.shape[0])
|
||||||
|
)
|
||||||
|
|
||||||
canary_ctx = (
|
canary_ctx = (
|
||||||
context_tuple(
|
context_tuple(
|
||||||
c.with_ops_outside_graph(
|
c.with_ops_outside_graph(
|
||||||
@@ -912,6 +970,19 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
f"draft_extend_for_decode (cuda_graph={can_cuda_graph})",
|
f"draft_extend_for_decode (cuda_graph={can_cuda_graph})",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Gather the per-request last-position indexer top-k as the next loop's
|
||||||
|
# seed (select_index already picks the last accepted position per req).
|
||||||
|
dsa_seed_topk_indices = None
|
||||||
|
if self.seed_dsa_topk_from_draft_extend:
|
||||||
|
if can_cuda_graph:
|
||||||
|
dsa_extend_topk_capture = (
|
||||||
|
self.cuda_graph_runner_for_draft_extend.buffers.dsa_seed_topk_capture
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
dsa_extend_topk_capture = forward_batch.spec_info.dsa_seed_topk_capture
|
||||||
|
# Fancy indexing returns a fresh tensor (detached from the buffer).
|
||||||
|
dsa_seed_topk_indices = dsa_extend_topk_capture[select_index]
|
||||||
|
|
||||||
# Reorganize the spec info for the next batch
|
# Reorganize the spec info for the next batch
|
||||||
draft_logits_output.next_token_logits = draft_logits_output.next_token_logits[
|
draft_logits_output.next_token_logits = draft_logits_output.next_token_logits[
|
||||||
select_index
|
select_index
|
||||||
@@ -961,6 +1032,8 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
|||||||
)
|
)
|
||||||
if self.server_args.speculative_use_rejection_sampling:
|
if self.server_args.speculative_use_rejection_sampling:
|
||||||
next_draft_input.draft_probs = ret_draft_probs
|
next_draft_input.draft_probs = ret_draft_probs
|
||||||
|
if self.seed_dsa_topk_from_draft_extend:
|
||||||
|
next_draft_input.dsa_topk_indices = dsa_seed_topk_indices
|
||||||
|
|
||||||
|
|
||||||
class EAGLEWorkerV2(BaseSpecWorker):
|
class EAGLEWorkerV2(BaseSpecWorker):
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ class StandaloneDraftWorker(EagleDraftWorker):
|
|||||||
)
|
)
|
||||||
and self.topk == 1
|
and self.topk == 1
|
||||||
)
|
)
|
||||||
|
self.dsa_index_topk = None
|
||||||
|
self.seed_dsa_topk_from_draft_extend = False
|
||||||
|
self.dsa_extend_topk_buf = None
|
||||||
|
|
||||||
def alloc_memory_pool(
|
def alloc_memory_pool(
|
||||||
self,
|
self,
|
||||||
|
|||||||
+2
@@ -20,6 +20,7 @@ from sglang.srt.speculative.eagle_draft_extend_cuda_graph_runner import (
|
|||||||
EAGLEDraftExtendCudaGraphRunner,
|
EAGLEDraftExtendCudaGraphRunner,
|
||||||
)
|
)
|
||||||
from sglang.srt.speculative.eagle_info import EagleDraftExtendInput
|
from sglang.srt.speculative.eagle_info import EagleDraftExtendInput
|
||||||
|
from sglang.srt.speculative.eagle_worker_v2 import EagleDraftWorker
|
||||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||||
from sglang.srt.speculative.spec_utils import fast_topk
|
from sglang.srt.speculative.spec_utils import fast_topk
|
||||||
|
|
||||||
@@ -496,6 +497,7 @@ class _EagleDraftExtendV2WorkerHarness:
|
|||||||
self.eagle_use_aux_hidden_state = False
|
self.eagle_use_aux_hidden_state = False
|
||||||
self.hot_token_id = None
|
self.hot_token_id = None
|
||||||
self.draft_runner.model = model_forward
|
self.draft_runner.model = model_forward
|
||||||
|
EagleDraftWorker._init_dsa_index_share_state(self)
|
||||||
|
|
||||||
|
|
||||||
def _build_eagle_draft_extend_fixture(
|
def _build_eagle_draft_extend_fixture(
|
||||||
|
|||||||
@@ -188,15 +188,7 @@ class _EagleDraftWorkerHarness:
|
|||||||
self._topk1_parents_prealloc = None
|
self._topk1_parents_prealloc = None
|
||||||
self._topk1_score_indices_prealloc = None
|
self._topk1_score_indices_prealloc = None
|
||||||
EagleDraftWorker._rebuild_topk1_chain_buffers(self)
|
EagleDraftWorker._rebuild_topk1_chain_buffers(self)
|
||||||
# draft_forward reads this (set in EagleDraftWorker.__init__, skipped here).
|
EagleDraftWorker._init_dsa_index_share_state(self)
|
||||||
self.index_share_for_mtp_iteration = (
|
|
||||||
getattr(
|
|
||||||
self.model_config.hf_config,
|
|
||||||
"index_share_for_mtp_iteration",
|
|
||||||
False,
|
|
||||||
)
|
|
||||||
and self.topk == 1
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def draft_model_runner(self):
|
def draft_model_runner(self):
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ class TestEagleDraftCudaGraphRunner(CustomTestCase):
|
|||||||
hidden_states=torch.empty(CAPTURE_BS, 2, dtype=torch.float32),
|
hidden_states=torch.empty(CAPTURE_BS, 2, dtype=torch.float32),
|
||||||
req_pool_indices=torch.empty(CAPTURE_BS, dtype=torch.int32),
|
req_pool_indices=torch.empty(CAPTURE_BS, dtype=torch.int32),
|
||||||
seq_lens_cpu=torch.empty(CAPTURE_BS, dtype=torch.int32),
|
seq_lens_cpu=torch.empty(CAPTURE_BS, dtype=torch.int32),
|
||||||
|
dsa_seed_topk=None,
|
||||||
)
|
)
|
||||||
runner.capture_bs = [1, CAPTURE_BS]
|
runner.capture_bs = [1, CAPTURE_BS]
|
||||||
runner.num_tokens_per_bs = 1
|
runner.num_tokens_per_bs = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user