[Spec] Move eagle verify prepare_for_verify/sample to eagle_utils free helpers (#28117)
This commit is contained in:
@@ -8,10 +8,7 @@ from sglang.srt.constrained.base_grammar_backend import BaseGrammarObject
|
|||||||
from sglang.srt.environ import envs
|
from sglang.srt.environ import envs
|
||||||
from sglang.srt.layers.attention.utils import create_flashinfer_kv_indices_triton
|
from sglang.srt.layers.attention.utils import create_flashinfer_kv_indices_triton
|
||||||
from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode
|
from sglang.srt.model_executor.forward_batch_info import CaptureHiddenMode
|
||||||
from sglang.srt.speculative.eagle_info_v2 import (
|
from sglang.srt.speculative.eagle_info_v2 import EagleDraftInputV2Mixin
|
||||||
EagleDraftInputV2Mixin,
|
|
||||||
EagleVerifyInputV2Mixin,
|
|
||||||
)
|
|
||||||
from sglang.srt.speculative.spec_info import SpecInput, SpecInputType
|
from sglang.srt.speculative.spec_info import SpecInput, SpecInputType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -30,7 +27,7 @@ def _draft_runner_of(worker):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class EagleVerifyInput(SpecInput, EagleVerifyInputV2Mixin):
|
class EagleVerifyInput(SpecInput):
|
||||||
draft_token: torch.Tensor
|
draft_token: torch.Tensor
|
||||||
custom_mask: torch.Tensor
|
custom_mask: torch.Tensor
|
||||||
positions: torch.Tensor
|
positions: torch.Tensor
|
||||||
@@ -54,6 +51,19 @@ class EagleVerifyInput(SpecInput, EagleVerifyInputV2Mixin):
|
|||||||
if self.num_tokens_per_req < 0:
|
if self.num_tokens_per_req < 0:
|
||||||
self.num_tokens_per_req = self.draft_token_num
|
self.num_tokens_per_req = self.draft_token_num
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_tree_depth(self) -> int:
|
||||||
|
"""Longest root-to-leaf chain of the verify tree, incl. the root;
|
||||||
|
bounds the accept_index row width. EAGLE trees are depth-bounded by
|
||||||
|
the draft loop. Algorithms with other tree shapes override this."""
|
||||||
|
return self.spec_steps + 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tree_topk(self) -> int:
|
||||||
|
"""Branching factor passed to the tree-verify kernels; -1 means an
|
||||||
|
irregular tree (no fixed per-level branching)."""
|
||||||
|
return self.topk
|
||||||
|
|
||||||
def get_spec_adjust_token_coefficient(self) -> Tuple[int, int]:
|
def get_spec_adjust_token_coefficient(self) -> Tuple[int, int]:
|
||||||
return self.draft_token_num, self.draft_token_num
|
return self.draft_token_num, self.draft_token_num
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,7 @@ from dataclasses import dataclass
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torch.nn.functional as F
|
|
||||||
|
|
||||||
from sglang.srt.distributed import get_tp_group
|
|
||||||
from sglang.srt.layers.dp_attention import (
|
|
||||||
get_attention_tp_group,
|
|
||||||
is_dp_attention_enabled,
|
|
||||||
)
|
|
||||||
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
|
||||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||||
from sglang.srt.mem_cache.common import (
|
from sglang.srt.mem_cache.common import (
|
||||||
alloc_paged_token_slots_extend,
|
alloc_paged_token_slots_extend,
|
||||||
@@ -19,50 +12,16 @@ from sglang.srt.mem_cache.common import (
|
|||||||
get_alloc_reserve_per_decode,
|
get_alloc_reserve_per_decode,
|
||||||
get_last_loc,
|
get_last_loc,
|
||||||
)
|
)
|
||||||
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
|
|
||||||
from sglang.srt.model_executor.forward_batch_info import (
|
|
||||||
CaptureHiddenMode,
|
|
||||||
ForwardBatch,
|
|
||||||
ForwardMode,
|
|
||||||
)
|
|
||||||
from sglang.srt.sampling.penaltylib.repetition_penalty import apply_scaling_penalties
|
|
||||||
from sglang.srt.server_args import get_global_server_args
|
|
||||||
from sglang.srt.speculative.eagle_utils import verify_tree_greedy_func
|
|
||||||
from sglang.srt.speculative.spec_utils import (
|
|
||||||
SIMULATE_ACC_LEN,
|
|
||||||
generate_simulated_accept_index,
|
|
||||||
prepare_mamba_track_for_verify,
|
|
||||||
)
|
|
||||||
from sglang.srt.speculative.triton_ops.cache_locs import (
|
from sglang.srt.speculative.triton_ops.cache_locs import (
|
||||||
assign_extend_cache_locs_func as assign_extend_cache_locs_func,
|
assign_extend_cache_locs_func as assign_extend_cache_locs_func,
|
||||||
)
|
)
|
||||||
from sglang.srt.speculative.triton_ops.eagle import (
|
from sglang.srt.speculative.triton_ops.eagle import (
|
||||||
fill_bonus_tokens as fill_bonus_tokens,
|
fill_bonus_tokens as fill_bonus_tokens,
|
||||||
)
|
)
|
||||||
from sglang.srt.utils.async_probe import (
|
|
||||||
maybe_detect_nan,
|
|
||||||
maybe_detect_oob,
|
|
||||||
sanitize_nan_logits,
|
|
||||||
)
|
|
||||||
from sglang.srt.utils.common import is_cuda, is_hip, is_musa, is_npu
|
|
||||||
|
|
||||||
_is_cuda = is_cuda()
|
|
||||||
_is_hip = is_hip()
|
|
||||||
_is_npu = is_npu()
|
|
||||||
_is_musa = is_musa()
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sglang.srt.managers.tp_worker import TpModelWorker
|
|
||||||
from sglang.srt.speculative.eagle_info import (
|
from sglang.srt.speculative.eagle_info import (
|
||||||
EagleDraftInput,
|
EagleDraftInput,
|
||||||
EagleVerifyInput,
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_cuda() or is_musa():
|
|
||||||
from sgl_kernel import (
|
|
||||||
top_k_renorm_prob,
|
|
||||||
top_p_renorm_prob,
|
|
||||||
tree_speculative_sampling_target_only,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -164,247 +123,3 @@ class EagleDraftInputV2Mixin:
|
|||||||
out_cache_loc,
|
out_cache_loc,
|
||||||
bs,
|
bs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class EagleVerifyInputV2Mixin:
|
|
||||||
@property
|
|
||||||
def max_tree_depth(self: EagleVerifyInput) -> int:
|
|
||||||
"""Longest root-to-leaf chain of the verify tree, incl. the root;
|
|
||||||
bounds the accept_index row width. EAGLE trees are depth-bounded by
|
|
||||||
the draft loop. Algorithms with other tree shapes override this."""
|
|
||||||
return self.spec_steps + 1
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tree_topk(self: EagleVerifyInput) -> int:
|
|
||||||
"""Branching factor passed to the tree-verify kernels; -1 means an
|
|
||||||
irregular tree (no fixed per-level branching)."""
|
|
||||||
return self.topk
|
|
||||||
|
|
||||||
def prepare_for_verify(
|
|
||||||
self: EagleVerifyInput,
|
|
||||||
req_to_token_pool: ReqToTokenPool,
|
|
||||||
batch: ScheduleBatch,
|
|
||||||
target_worker: TpModelWorker,
|
|
||||||
):
|
|
||||||
if not batch.forward_mode.is_idle():
|
|
||||||
# Assign cache locations
|
|
||||||
bs = len(batch.req_pool_indices)
|
|
||||||
batch.input_ids = self.draft_token
|
|
||||||
maybe_detect_oob(
|
|
||||||
batch.input_ids,
|
|
||||||
0,
|
|
||||||
batch.model_config.vocab_size,
|
|
||||||
"v2 prepare_for_verify input_ids",
|
|
||||||
)
|
|
||||||
device = batch.device
|
|
||||||
batch.out_cache_loc = assign_extend_cache_locs_func(
|
|
||||||
req_pool_indices=batch.req_pool_indices,
|
|
||||||
req_to_token=req_to_token_pool.req_to_token,
|
|
||||||
start_offset=batch.seq_lens,
|
|
||||||
end_offset=batch.seq_lens + self.draft_token_num,
|
|
||||||
batch_size=bs,
|
|
||||||
draft_token_num=self.draft_token_num,
|
|
||||||
device=device,
|
|
||||||
)
|
|
||||||
|
|
||||||
prepare_mamba_track_for_verify(batch)
|
|
||||||
|
|
||||||
# TBO's split_spec_info reads these; no-verify-sync leaves both None.
|
|
||||||
self.seq_lens_cpu = batch.seq_lens_cpu
|
|
||||||
self.seq_lens_sum = (
|
|
||||||
int(batch.seq_lens_cpu.sum())
|
|
||||||
if batch.seq_lens_cpu is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get a forward batch
|
|
||||||
batch.forward_mode = (
|
|
||||||
ForwardMode.IDLE
|
|
||||||
if batch.forward_mode.is_idle()
|
|
||||||
else ForwardMode.TARGET_VERIFY
|
|
||||||
)
|
|
||||||
capture_mode = (
|
|
||||||
CaptureHiddenMode.NULL
|
|
||||||
if target_worker.model_runner.spec_algorithm.is_standalone()
|
|
||||||
else CaptureHiddenMode.FULL
|
|
||||||
)
|
|
||||||
batch.capture_hidden_mode = capture_mode
|
|
||||||
verify_forward_batch = ForwardBatch.init_new(batch, target_worker.model_runner)
|
|
||||||
|
|
||||||
# Run attention backend plan and cuda graph preparation
|
|
||||||
can_run_cuda_graph = bool(
|
|
||||||
target_worker.model_runner.decode_cuda_graph_runner
|
|
||||||
and target_worker.model_runner.decode_cuda_graph_runner.can_run(
|
|
||||||
verify_forward_batch
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if can_run_cuda_graph:
|
|
||||||
target_worker.model_runner.decode_cuda_graph_runner.replay_prepare(
|
|
||||||
verify_forward_batch
|
|
||||||
)
|
|
||||||
verify_forward_batch.mark_forward_metadata_ready()
|
|
||||||
# Non-cuda-graph: defer init to forward_extend, which runs after
|
|
||||||
# `_forward_raw -> prepare_mlp_sync_batch` pads the batch. Initing
|
|
||||||
# here would use pre-pad shapes and trip DSv4 indexer shape match.
|
|
||||||
|
|
||||||
return verify_forward_batch, can_run_cuda_graph
|
|
||||||
|
|
||||||
def sample(
|
|
||||||
self: EagleVerifyInput,
|
|
||||||
batch: ScheduleBatch,
|
|
||||||
logits_output: LogitsProcessorOutput,
|
|
||||||
vocab_mask: torch.Tensor = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Verify and find accepted tokens based on logits output and batch
|
|
||||||
(which contains spec decoding information).
|
|
||||||
"""
|
|
||||||
device = batch.device
|
|
||||||
if batch.forward_mode.is_idle():
|
|
||||||
predict = torch.empty(0, dtype=torch.int32, device=device)
|
|
||||||
num_correct_drafts = torch.empty(0, dtype=torch.int32, device=device)
|
|
||||||
accept_index = torch.empty(0, dtype=torch.int32, device=device)
|
|
||||||
return predict, num_correct_drafts, accept_index
|
|
||||||
|
|
||||||
bs = len(batch.seq_lens)
|
|
||||||
sampling_info = batch.sampling_info
|
|
||||||
next_token_logits = logits_output.next_token_logits
|
|
||||||
|
|
||||||
sanitize_nan_logits(next_token_logits, "verify: target model logits")
|
|
||||||
|
|
||||||
# Apply penalty
|
|
||||||
# This is a relaxed version of penalties for speculative decoding.
|
|
||||||
if sampling_info.acc_additive_penalties is not None:
|
|
||||||
next_token_logits.add_(
|
|
||||||
torch.repeat_interleave(
|
|
||||||
sampling_info.acc_additive_penalties, self.draft_token_num, dim=0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if sampling_info.acc_scaling_penalties is not None:
|
|
||||||
apply_scaling_penalties(
|
|
||||||
next_token_logits,
|
|
||||||
torch.repeat_interleave(
|
|
||||||
sampling_info.acc_scaling_penalties, self.draft_token_num, dim=0
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if sampling_info.logit_bias is not None:
|
|
||||||
next_token_logits.add_(
|
|
||||||
torch.repeat_interleave(
|
|
||||||
sampling_info.logit_bias, self.draft_token_num, dim=0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply grammar mask if provided
|
|
||||||
if vocab_mask is not None:
|
|
||||||
assert self.grammar is not None
|
|
||||||
self.grammar.apply_vocab_mask(
|
|
||||||
logits=next_token_logits, vocab_mask=vocab_mask
|
|
||||||
)
|
|
||||||
|
|
||||||
candidates = self.draft_token.reshape(bs, self.draft_token_num)
|
|
||||||
predict_shape = list(next_token_logits.shape)[:-1]
|
|
||||||
predict = torch.zeros(predict_shape, dtype=torch.int32, device=device).flatten()
|
|
||||||
accept_index = torch.full(
|
|
||||||
(bs, self.max_tree_depth), -1, dtype=torch.int32, device=device
|
|
||||||
)
|
|
||||||
num_correct_drafts = torch.empty((bs,), dtype=torch.int32, device=device)
|
|
||||||
|
|
||||||
# Sample tokens
|
|
||||||
if sampling_info.is_all_greedy or _is_npu or _is_hip:
|
|
||||||
target_predict = torch.argmax(next_token_logits, dim=-1)
|
|
||||||
target_predict = target_predict.reshape(bs, self.draft_token_num)
|
|
||||||
predict, accept_index, num_correct_drafts = verify_tree_greedy_func(
|
|
||||||
predicts=predict, # mutable
|
|
||||||
accept_index=accept_index, # mutable
|
|
||||||
accept_token_num=num_correct_drafts, # mutable
|
|
||||||
candidates=candidates,
|
|
||||||
retrieve_index=self.retrieve_index,
|
|
||||||
retrieve_next_token=self.retrieve_next_token,
|
|
||||||
retrieve_next_sibling=self.retrieve_next_sibling,
|
|
||||||
target_predict=target_predict,
|
|
||||||
topk=self.tree_topk,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Apply temperature and get target probs
|
|
||||||
expanded_temperature = torch.repeat_interleave(
|
|
||||||
sampling_info.temperatures, self.draft_token_num, dim=0
|
|
||||||
) # (bs * num_draft_tokens, 1)
|
|
||||||
|
|
||||||
target_probs = F.softmax(
|
|
||||||
next_token_logits / expanded_temperature, dim=-1
|
|
||||||
) # (bs * num_draft_tokens, vocab_size)
|
|
||||||
maybe_detect_nan(target_probs, "v2 verify: target_probs after softmax")
|
|
||||||
target_probs = top_k_renorm_prob(
|
|
||||||
target_probs,
|
|
||||||
torch.repeat_interleave(
|
|
||||||
sampling_info.top_ks, self.draft_token_num, dim=0
|
|
||||||
),
|
|
||||||
) # (bs * num_draft_tokens, vocab_size)
|
|
||||||
maybe_detect_nan(target_probs, "v2 verify: target_probs after top_k_renorm")
|
|
||||||
target_probs = top_p_renorm_prob(
|
|
||||||
target_probs,
|
|
||||||
torch.repeat_interleave(
|
|
||||||
sampling_info.top_ps, self.draft_token_num, dim=0
|
|
||||||
),
|
|
||||||
)
|
|
||||||
maybe_detect_nan(target_probs, "v2 verify: target_probs after top_p_renorm")
|
|
||||||
target_probs = target_probs.reshape(bs, self.draft_token_num, -1)
|
|
||||||
draft_probs = torch.zeros_like(target_probs)
|
|
||||||
|
|
||||||
# coins for rejection sampling
|
|
||||||
coins = torch.rand_like(candidates, dtype=torch.float32, device=device)
|
|
||||||
# coins for final sampling
|
|
||||||
coins_for_final_sampling = torch.rand(
|
|
||||||
(bs,), dtype=torch.float32, device=device
|
|
||||||
)
|
|
||||||
|
|
||||||
tree_speculative_sampling_target_only(
|
|
||||||
predicts=predict, # mutable
|
|
||||||
accept_index=accept_index, # mutable
|
|
||||||
accept_token_num=num_correct_drafts, # mutable
|
|
||||||
candidates=candidates,
|
|
||||||
# kwarg LHS retained as `retrive_*` to match sgl_kernel op schema.
|
|
||||||
retrive_index=self.retrieve_index,
|
|
||||||
retrive_next_token=self.retrieve_next_token,
|
|
||||||
retrive_next_sibling=self.retrieve_next_sibling,
|
|
||||||
uniform_samples=coins,
|
|
||||||
uniform_samples_for_final_sampling=coins_for_final_sampling,
|
|
||||||
target_probs=target_probs,
|
|
||||||
draft_probs=draft_probs,
|
|
||||||
threshold_single=get_global_server_args().speculative_accept_threshold_single,
|
|
||||||
threshold_acc=get_global_server_args().speculative_accept_threshold_acc,
|
|
||||||
deterministic=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sync sampling results across TP ranks: different GPUs may
|
|
||||||
# produce slightly different target_probs due to floating-point
|
|
||||||
# non-determinism in softmax/top_k/top_p, causing different
|
|
||||||
# sampled tokens. Broadcast from rank 0 to ensure consistency.
|
|
||||||
tp_group = (
|
|
||||||
get_attention_tp_group()
|
|
||||||
if is_dp_attention_enabled()
|
|
||||||
else get_tp_group()
|
|
||||||
)
|
|
||||||
if tp_group.world_size > 1:
|
|
||||||
tp_group.broadcast(predict, src=0)
|
|
||||||
tp_group.broadcast(accept_index, src=0)
|
|
||||||
tp_group.broadcast(num_correct_drafts, src=0)
|
|
||||||
|
|
||||||
if SIMULATE_ACC_LEN > 0:
|
|
||||||
# Do simulation. The helper builds (and returns) a replacement
|
|
||||||
# accept_index of width spec_steps + 1, so pass max_tree_depth - 1
|
|
||||||
# to keep the simulated width identical to the real one.
|
|
||||||
accept_index = generate_simulated_accept_index(
|
|
||||||
accept_index=accept_index,
|
|
||||||
predict=predict, # mutable
|
|
||||||
num_correct_drafts=num_correct_drafts, # mutable
|
|
||||||
simulate_acc_len=SIMULATE_ACC_LEN,
|
|
||||||
bs=bs,
|
|
||||||
spec_steps=self.max_tree_depth - 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
# `num_correct_drafts` stays drafts-only inside this function; the returned
|
|
||||||
# tensor includes the trailing/bonus token via out-of-place +1 so the
|
|
||||||
# name no longer flips semantics mid-function (naming doc C2).
|
|
||||||
return predict, num_correct_drafts + 1, accept_index
|
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ from sglang.srt.utils import is_cuda, is_hip, is_musa, is_npu
|
|||||||
from sglang.srt.utils.async_probe import maybe_detect_oob
|
from sglang.srt.utils.async_probe import maybe_detect_oob
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
||||||
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
from sglang.srt.managers.schedule_batch import ScheduleBatch
|
||||||
|
from sglang.srt.managers.tp_worker import TpModelWorker
|
||||||
|
from sglang.srt.mem_cache.memory_pool import ReqToTokenPool
|
||||||
from sglang.srt.model_executor.model_runner import ModelRunner
|
from sglang.srt.model_executor.model_runner import ModelRunner
|
||||||
|
from sglang.srt.speculative.eagle_info import EagleVerifyInput
|
||||||
|
|
||||||
_is_cuda = is_cuda()
|
_is_cuda = is_cuda()
|
||||||
_is_hip = is_hip()
|
_is_hip = is_hip()
|
||||||
@@ -272,3 +276,260 @@ def get_draft_hidden_dim(model_runner: ModelRunner) -> int:
|
|||||||
num_aux = max(len(layer_ids), 1)
|
num_aux = max(len(layer_ids), 1)
|
||||||
return base * num_aux
|
return base * num_aux
|
||||||
return model_runner.model_config.spec_hidden_size
|
return model_runner.model_config.spec_hidden_size
|
||||||
|
|
||||||
|
|
||||||
|
def eagle_prepare_for_verify(
|
||||||
|
verify_input: EagleVerifyInput,
|
||||||
|
req_to_token_pool: ReqToTokenPool,
|
||||||
|
batch: ScheduleBatch,
|
||||||
|
target_worker: TpModelWorker,
|
||||||
|
):
|
||||||
|
from sglang.srt.model_executor.forward_batch_info import (
|
||||||
|
CaptureHiddenMode,
|
||||||
|
ForwardBatch,
|
||||||
|
ForwardMode,
|
||||||
|
)
|
||||||
|
from sglang.srt.speculative.spec_utils import prepare_mamba_track_for_verify
|
||||||
|
from sglang.srt.speculative.triton_ops.cache_locs import (
|
||||||
|
assign_extend_cache_locs_func,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not batch.forward_mode.is_idle():
|
||||||
|
# Assign cache locations
|
||||||
|
bs = len(batch.req_pool_indices)
|
||||||
|
batch.input_ids = verify_input.draft_token
|
||||||
|
maybe_detect_oob(
|
||||||
|
batch.input_ids,
|
||||||
|
0,
|
||||||
|
batch.model_config.vocab_size,
|
||||||
|
"v2 prepare_for_verify input_ids",
|
||||||
|
)
|
||||||
|
device = batch.device
|
||||||
|
batch.out_cache_loc = assign_extend_cache_locs_func(
|
||||||
|
req_pool_indices=batch.req_pool_indices,
|
||||||
|
req_to_token=req_to_token_pool.req_to_token,
|
||||||
|
start_offset=batch.seq_lens,
|
||||||
|
end_offset=batch.seq_lens + verify_input.draft_token_num,
|
||||||
|
batch_size=bs,
|
||||||
|
draft_token_num=verify_input.draft_token_num,
|
||||||
|
device=device,
|
||||||
|
)
|
||||||
|
|
||||||
|
prepare_mamba_track_for_verify(batch)
|
||||||
|
|
||||||
|
# TBO's split_spec_info reads these; no-verify-sync leaves both None.
|
||||||
|
verify_input.seq_lens_cpu = batch.seq_lens_cpu
|
||||||
|
verify_input.seq_lens_sum = (
|
||||||
|
int(batch.seq_lens_cpu.sum()) if batch.seq_lens_cpu is not None else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get a forward batch
|
||||||
|
batch.forward_mode = (
|
||||||
|
ForwardMode.IDLE if batch.forward_mode.is_idle() else ForwardMode.TARGET_VERIFY
|
||||||
|
)
|
||||||
|
capture_mode = (
|
||||||
|
CaptureHiddenMode.NULL
|
||||||
|
if target_worker.model_runner.spec_algorithm.is_standalone()
|
||||||
|
else CaptureHiddenMode.FULL
|
||||||
|
)
|
||||||
|
batch.capture_hidden_mode = capture_mode
|
||||||
|
verify_forward_batch = ForwardBatch.init_new(batch, target_worker.model_runner)
|
||||||
|
|
||||||
|
# Run attention backend plan and cuda graph preparation
|
||||||
|
can_run_cuda_graph = bool(
|
||||||
|
target_worker.model_runner.decode_cuda_graph_runner
|
||||||
|
and target_worker.model_runner.decode_cuda_graph_runner.can_run(
|
||||||
|
verify_forward_batch
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if can_run_cuda_graph:
|
||||||
|
target_worker.model_runner.decode_cuda_graph_runner.replay_prepare(
|
||||||
|
verify_forward_batch
|
||||||
|
)
|
||||||
|
verify_forward_batch.mark_forward_metadata_ready()
|
||||||
|
# Non-cuda-graph: defer init to forward_extend, which runs after
|
||||||
|
# `_forward_raw -> prepare_mlp_sync_batch` pads the batch. Initing
|
||||||
|
# here would use pre-pad shapes and trip DSv4 indexer shape match.
|
||||||
|
|
||||||
|
return verify_forward_batch, can_run_cuda_graph
|
||||||
|
|
||||||
|
|
||||||
|
def eagle_sample(
|
||||||
|
verify_input: EagleVerifyInput,
|
||||||
|
batch: ScheduleBatch,
|
||||||
|
logits_output: LogitsProcessorOutput,
|
||||||
|
vocab_mask: torch.Tensor = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Verify and find accepted tokens based on logits output and batch
|
||||||
|
(which contains spec decoding information).
|
||||||
|
"""
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
from sglang.srt.distributed import get_tp_group
|
||||||
|
from sglang.srt.layers.dp_attention import (
|
||||||
|
get_attention_tp_group,
|
||||||
|
is_dp_attention_enabled,
|
||||||
|
)
|
||||||
|
from sglang.srt.sampling.penaltylib.repetition_penalty import (
|
||||||
|
apply_scaling_penalties,
|
||||||
|
)
|
||||||
|
from sglang.srt.server_args import get_global_server_args
|
||||||
|
from sglang.srt.speculative.spec_utils import (
|
||||||
|
SIMULATE_ACC_LEN,
|
||||||
|
generate_simulated_accept_index,
|
||||||
|
)
|
||||||
|
from sglang.srt.utils.async_probe import maybe_detect_nan, sanitize_nan_logits
|
||||||
|
|
||||||
|
device = batch.device
|
||||||
|
if batch.forward_mode.is_idle():
|
||||||
|
predict = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
|
num_correct_drafts = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
|
accept_index = torch.empty(0, dtype=torch.int32, device=device)
|
||||||
|
return predict, num_correct_drafts, accept_index
|
||||||
|
|
||||||
|
bs = len(batch.seq_lens)
|
||||||
|
sampling_info = batch.sampling_info
|
||||||
|
next_token_logits = logits_output.next_token_logits
|
||||||
|
|
||||||
|
sanitize_nan_logits(next_token_logits, "verify: target model logits")
|
||||||
|
|
||||||
|
# Apply penalty
|
||||||
|
# This is a relaxed version of penalties for speculative decoding.
|
||||||
|
if sampling_info.acc_additive_penalties is not None:
|
||||||
|
next_token_logits.add_(
|
||||||
|
torch.repeat_interleave(
|
||||||
|
sampling_info.acc_additive_penalties,
|
||||||
|
verify_input.draft_token_num,
|
||||||
|
dim=0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if sampling_info.acc_scaling_penalties is not None:
|
||||||
|
apply_scaling_penalties(
|
||||||
|
next_token_logits,
|
||||||
|
torch.repeat_interleave(
|
||||||
|
sampling_info.acc_scaling_penalties, verify_input.draft_token_num, dim=0
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if sampling_info.logit_bias is not None:
|
||||||
|
next_token_logits.add_(
|
||||||
|
torch.repeat_interleave(
|
||||||
|
sampling_info.logit_bias, verify_input.draft_token_num, dim=0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply grammar mask if provided
|
||||||
|
if vocab_mask is not None:
|
||||||
|
assert verify_input.grammar is not None
|
||||||
|
verify_input.grammar.apply_vocab_mask(
|
||||||
|
logits=next_token_logits, vocab_mask=vocab_mask
|
||||||
|
)
|
||||||
|
|
||||||
|
candidates = verify_input.draft_token.reshape(bs, verify_input.draft_token_num)
|
||||||
|
predict_shape = list(next_token_logits.shape)[:-1]
|
||||||
|
predict = torch.zeros(predict_shape, dtype=torch.int32, device=device).flatten()
|
||||||
|
accept_index = torch.full(
|
||||||
|
(bs, verify_input.max_tree_depth), -1, dtype=torch.int32, device=device
|
||||||
|
)
|
||||||
|
num_correct_drafts = torch.empty((bs,), dtype=torch.int32, device=device)
|
||||||
|
|
||||||
|
# Sample tokens
|
||||||
|
if sampling_info.is_all_greedy or _is_npu or _is_hip:
|
||||||
|
target_predict = torch.argmax(next_token_logits, dim=-1)
|
||||||
|
target_predict = target_predict.reshape(bs, verify_input.draft_token_num)
|
||||||
|
predict, accept_index, num_correct_drafts = verify_tree_greedy_func(
|
||||||
|
predicts=predict, # mutable
|
||||||
|
accept_index=accept_index, # mutable
|
||||||
|
accept_token_num=num_correct_drafts, # mutable
|
||||||
|
candidates=candidates,
|
||||||
|
retrieve_index=verify_input.retrieve_index,
|
||||||
|
retrieve_next_token=verify_input.retrieve_next_token,
|
||||||
|
retrieve_next_sibling=verify_input.retrieve_next_sibling,
|
||||||
|
target_predict=target_predict,
|
||||||
|
topk=verify_input.tree_topk,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
from sgl_kernel import (
|
||||||
|
top_k_renorm_prob,
|
||||||
|
top_p_renorm_prob,
|
||||||
|
tree_speculative_sampling_target_only,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply temperature and get target probs
|
||||||
|
expanded_temperature = torch.repeat_interleave(
|
||||||
|
sampling_info.temperatures, verify_input.draft_token_num, dim=0
|
||||||
|
) # (bs * num_draft_tokens, 1)
|
||||||
|
|
||||||
|
target_probs = F.softmax(
|
||||||
|
next_token_logits / expanded_temperature, dim=-1
|
||||||
|
) # (bs * num_draft_tokens, vocab_size)
|
||||||
|
maybe_detect_nan(target_probs, "v2 verify: target_probs after softmax")
|
||||||
|
target_probs = top_k_renorm_prob(
|
||||||
|
target_probs,
|
||||||
|
torch.repeat_interleave(
|
||||||
|
sampling_info.top_ks, verify_input.draft_token_num, dim=0
|
||||||
|
),
|
||||||
|
) # (bs * num_draft_tokens, vocab_size)
|
||||||
|
maybe_detect_nan(target_probs, "v2 verify: target_probs after top_k_renorm")
|
||||||
|
target_probs = top_p_renorm_prob(
|
||||||
|
target_probs,
|
||||||
|
torch.repeat_interleave(
|
||||||
|
sampling_info.top_ps, verify_input.draft_token_num, dim=0
|
||||||
|
),
|
||||||
|
)
|
||||||
|
maybe_detect_nan(target_probs, "v2 verify: target_probs after top_p_renorm")
|
||||||
|
target_probs = target_probs.reshape(bs, verify_input.draft_token_num, -1)
|
||||||
|
draft_probs = torch.zeros_like(target_probs)
|
||||||
|
|
||||||
|
# coins for rejection sampling
|
||||||
|
coins = torch.rand_like(candidates, dtype=torch.float32, device=device)
|
||||||
|
# coins for final sampling
|
||||||
|
coins_for_final_sampling = torch.rand((bs,), dtype=torch.float32, device=device)
|
||||||
|
|
||||||
|
tree_speculative_sampling_target_only(
|
||||||
|
predicts=predict, # mutable
|
||||||
|
accept_index=accept_index, # mutable
|
||||||
|
accept_token_num=num_correct_drafts, # mutable
|
||||||
|
candidates=candidates,
|
||||||
|
# kwarg LHS retained as `retrive_*` to match sgl_kernel op schema.
|
||||||
|
retrive_index=verify_input.retrieve_index,
|
||||||
|
retrive_next_token=verify_input.retrieve_next_token,
|
||||||
|
retrive_next_sibling=verify_input.retrieve_next_sibling,
|
||||||
|
uniform_samples=coins,
|
||||||
|
uniform_samples_for_final_sampling=coins_for_final_sampling,
|
||||||
|
target_probs=target_probs,
|
||||||
|
draft_probs=draft_probs,
|
||||||
|
threshold_single=get_global_server_args().speculative_accept_threshold_single,
|
||||||
|
threshold_acc=get_global_server_args().speculative_accept_threshold_acc,
|
||||||
|
deterministic=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync sampling results across TP ranks: different GPUs may
|
||||||
|
# produce slightly different target_probs due to floating-point
|
||||||
|
# non-determinism in softmax/top_k/top_p, causing different
|
||||||
|
# sampled tokens. Broadcast from rank 0 to ensure consistency.
|
||||||
|
tp_group = (
|
||||||
|
get_attention_tp_group() if is_dp_attention_enabled() else get_tp_group()
|
||||||
|
)
|
||||||
|
if tp_group.world_size > 1:
|
||||||
|
tp_group.broadcast(predict, src=0)
|
||||||
|
tp_group.broadcast(accept_index, src=0)
|
||||||
|
tp_group.broadcast(num_correct_drafts, src=0)
|
||||||
|
|
||||||
|
if SIMULATE_ACC_LEN > 0:
|
||||||
|
# Do simulation. The helper builds (and returns) a replacement
|
||||||
|
# accept_index of width spec_steps + 1, so pass max_tree_depth - 1
|
||||||
|
# to keep the simulated width identical to the real one.
|
||||||
|
accept_index = generate_simulated_accept_index(
|
||||||
|
accept_index=accept_index,
|
||||||
|
predict=predict, # mutable
|
||||||
|
num_correct_drafts=num_correct_drafts, # mutable
|
||||||
|
simulate_acc_len=SIMULATE_ACC_LEN,
|
||||||
|
bs=bs,
|
||||||
|
spec_steps=verify_input.max_tree_depth - 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# `num_correct_drafts` stays drafts-only inside this function; the returned
|
||||||
|
# tensor includes the trailing/bonus token via out-of-place +1 so the
|
||||||
|
# name no longer flips semantics mid-function (naming doc C2).
|
||||||
|
return predict, num_correct_drafts + 1, accept_index
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ from sglang.srt.speculative.eagle_utils import (
|
|||||||
TreeMaskMode,
|
TreeMaskMode,
|
||||||
_eagle_prefill_tail_tokens,
|
_eagle_prefill_tail_tokens,
|
||||||
build_tree_kernel_efficient,
|
build_tree_kernel_efficient,
|
||||||
|
eagle_prepare_for_verify,
|
||||||
|
eagle_sample,
|
||||||
organize_draft_results,
|
organize_draft_results,
|
||||||
per_step_draft_out_cache_loc,
|
per_step_draft_out_cache_loc,
|
||||||
)
|
)
|
||||||
@@ -1215,7 +1217,8 @@ class EAGLEWorkerV2(BaseSpecWorker):
|
|||||||
# Batch 1: Target verify
|
# Batch 1: Target verify
|
||||||
# Prepare for target verify in a separate stream
|
# Prepare for target verify in a separate stream
|
||||||
with self.plan_stream_ctx:
|
with self.plan_stream_ctx:
|
||||||
verify_forward_batch, can_run_cuda_graph = verify_input.prepare_for_verify(
|
verify_forward_batch, can_run_cuda_graph = eagle_prepare_for_verify(
|
||||||
|
verify_input,
|
||||||
self.req_to_token_pool,
|
self.req_to_token_pool,
|
||||||
batch,
|
batch,
|
||||||
self.target_worker,
|
self.target_worker,
|
||||||
@@ -1264,7 +1267,7 @@ class EAGLEWorkerV2(BaseSpecWorker):
|
|||||||
|
|
||||||
# Run target verify batch in the main compute stream (GPU compute).
|
# Run target verify batch in the main compute stream (GPU compute).
|
||||||
# Metadata init is skipped iff cuda-graph already ran replay_prepare —
|
# Metadata init is skipped iff cuda-graph already ran replay_prepare —
|
||||||
# prepare_for_verify marked the batch in exactly that case; the
|
# eagle_prepare_for_verify marked the batch in exactly that case; the
|
||||||
# non-cuda-graph path stays unmarked and gets forward_extend's init
|
# non-cuda-graph path stays unmarked and gets forward_extend's init
|
||||||
# (post-pad).
|
# (post-pad).
|
||||||
forward_batch_output = self.target_worker.forward_batch_generation(
|
forward_batch_output = self.target_worker.forward_batch_generation(
|
||||||
@@ -1301,7 +1304,7 @@ class EAGLEWorkerV2(BaseSpecWorker):
|
|||||||
predict,
|
predict,
|
||||||
accept_lens,
|
accept_lens,
|
||||||
accept_index,
|
accept_index,
|
||||||
) = verify_input.sample(batch, logits_output, vocab_mask)
|
) = eagle_sample(verify_input, batch, logits_output, vocab_mask)
|
||||||
new_seq_lens = batch.seq_lens + accept_lens
|
new_seq_lens = batch.seq_lens + accept_lens
|
||||||
|
|
||||||
# Update mamba state for hybrid GDN models after verification
|
# Update mamba state for hybrid GDN models after verification
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ from sglang.srt.speculative.eagle_info import (
|
|||||||
EagleVerifyInput,
|
EagleVerifyInput,
|
||||||
)
|
)
|
||||||
from sglang.srt.speculative.eagle_info_v2 import fill_bonus_tokens
|
from sglang.srt.speculative.eagle_info_v2 import fill_bonus_tokens
|
||||||
from sglang.srt.speculative.eagle_utils import TreeMaskMode, build_tree_kernel_efficient
|
from sglang.srt.speculative.eagle_utils import (
|
||||||
|
TreeMaskMode,
|
||||||
|
build_tree_kernel_efficient,
|
||||||
|
eagle_prepare_for_verify,
|
||||||
|
eagle_sample,
|
||||||
|
)
|
||||||
from sglang.srt.speculative.multi_layer_eagle_draft_extend_cuda_graph_runner import (
|
from sglang.srt.speculative.multi_layer_eagle_draft_extend_cuda_graph_runner import (
|
||||||
MultiLayerEagleMultiStepDraftExtendCudaGraphRunner,
|
MultiLayerEagleMultiStepDraftExtendCudaGraphRunner,
|
||||||
)
|
)
|
||||||
@@ -807,7 +812,8 @@ class MultiLayerEagleWorkerV2(BaseSpecWorker):
|
|||||||
# Batch 1: Target verify
|
# Batch 1: Target verify
|
||||||
# Prepare for target verify in a separate stream
|
# Prepare for target verify in a separate stream
|
||||||
with self.plan_stream_ctx:
|
with self.plan_stream_ctx:
|
||||||
verify_forward_batch, can_run_cuda_graph = verify_input.prepare_for_verify(
|
verify_forward_batch, can_run_cuda_graph = eagle_prepare_for_verify(
|
||||||
|
verify_input,
|
||||||
self.req_to_token_pool,
|
self.req_to_token_pool,
|
||||||
batch,
|
batch,
|
||||||
self.target_worker,
|
self.target_worker,
|
||||||
@@ -834,7 +840,7 @@ class MultiLayerEagleWorkerV2(BaseSpecWorker):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
# NOTE: metadata init is skipped here unconditionally, although
|
# NOTE: metadata init is skipped here unconditionally, although
|
||||||
# prepare_for_verify only plans when cuda-graph replay_prepare ran.
|
# eagle_prepare_for_verify only plans when cuda-graph replay_prepare ran.
|
||||||
# eagle_worker_v2 re-inits the non-graph path instead (post-pad); this
|
# eagle_worker_v2 re-inits the non-graph path instead (post-pad); this
|
||||||
# worker has not adopted that fix, so preserve its behavior verbatim.
|
# worker has not adopted that fix, so preserve its behavior verbatim.
|
||||||
# On NPU with --disable-cuda-graph, non-graph verify needs metadata init
|
# On NPU with --disable-cuda-graph, non-graph verify needs metadata init
|
||||||
@@ -856,7 +862,7 @@ class MultiLayerEagleWorkerV2(BaseSpecWorker):
|
|||||||
predict,
|
predict,
|
||||||
accept_lens,
|
accept_lens,
|
||||||
accept_index,
|
accept_index,
|
||||||
) = verify_input.sample(batch, logits_output)
|
) = eagle_sample(verify_input, batch, logits_output)
|
||||||
new_seq_lens = batch.seq_lens + accept_lens
|
new_seq_lens = batch.seq_lens + accept_lens
|
||||||
|
|
||||||
if not batch.forward_mode.is_idle():
|
if not batch.forward_mode.is_idle():
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ import torch
|
|||||||
|
|
||||||
from sglang.srt.constrained.base_grammar_backend import BaseGrammarObject
|
from sglang.srt.constrained.base_grammar_backend import BaseGrammarObject
|
||||||
from sglang.srt.layers.attention.utils import create_flashinfer_kv_indices_triton
|
from sglang.srt.layers.attention.utils import create_flashinfer_kv_indices_triton
|
||||||
from sglang.srt.speculative.eagle_info_v2 import (
|
from sglang.srt.speculative.eagle_info_v2 import EagleDraftInputV2Mixin
|
||||||
EagleDraftInputV2Mixin,
|
|
||||||
EagleVerifyInputV2Mixin,
|
|
||||||
)
|
|
||||||
from sglang.srt.speculative.spec_info import SpecInput, SpecInputType
|
from sglang.srt.speculative.spec_info import SpecInput, SpecInputType
|
||||||
|
|
||||||
|
|
||||||
class NgramVerifyInput(SpecInput, EagleDraftInputV2Mixin, EagleVerifyInputV2Mixin):
|
class NgramVerifyInput(SpecInput, EagleDraftInputV2Mixin):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
draft_token: torch.Tensor = None,
|
draft_token: torch.Tensor = None,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from sglang.srt.observability.req_time_stats import set_time_batch
|
|||||||
from sglang.srt.server_args import ServerArgs
|
from sglang.srt.server_args import ServerArgs
|
||||||
from sglang.srt.speculative.base_spec_worker import BaseSpecWorker, EagleDraftWorkerBase
|
from sglang.srt.speculative.base_spec_worker import BaseSpecWorker, EagleDraftWorkerBase
|
||||||
from sglang.srt.speculative.cpp_ngram.ngram_corpus import NgramCorpus
|
from sglang.srt.speculative.cpp_ngram.ngram_corpus import NgramCorpus
|
||||||
|
from sglang.srt.speculative.eagle_utils import eagle_sample
|
||||||
from sglang.srt.speculative.ngram_info import NgramVerifyInput
|
from sglang.srt.speculative.ngram_info import NgramVerifyInput
|
||||||
from sglang.srt.speculative.spec_utils import (
|
from sglang.srt.speculative.spec_utils import (
|
||||||
commit_mamba_states_after_verify,
|
commit_mamba_states_after_verify,
|
||||||
@@ -432,7 +433,7 @@ class NGRAMWorker(BaseSpecWorker):
|
|||||||
predict,
|
predict,
|
||||||
accept_lens,
|
accept_lens,
|
||||||
accept_index,
|
accept_index,
|
||||||
) = verify_input.sample(batch, logits_output, vocab_mask)
|
) = eagle_sample(verify_input, batch, logits_output, vocab_mask)
|
||||||
new_seq_lens = batch.seq_lens + accept_lens
|
new_seq_lens = batch.seq_lens + accept_lens
|
||||||
commit_mamba_states_after_verify(
|
commit_mamba_states_after_verify(
|
||||||
self.target_worker,
|
self.target_worker,
|
||||||
|
|||||||
Reference in New Issue
Block a user