fix(spec_v2): support EAGLE with spec_v2

1. add pass for spec_v2 in base_attn

2. fix: EAGLE with spec_v2 overlap Grammar accept_token failed

Signed-off-by: wxiwnd <wxiwnd@outlook.com>
This commit is contained in:
laoyao0822
2026-03-25 21:17:09 +08:00
committed by wxiwnd
parent 101100e25b
commit ef607c35c9
3 changed files with 124 additions and 14 deletions

View File

@@ -137,9 +137,9 @@ class DecodeReqToTokenPool:
# Indices of reqs that already have a req_pool_idx and will reuse
# their existing slot (e.g. chunked prefill continuing across chunks).
reusing = [i for i, r in enumerate(reqs) if r.req_pool_idx is not None]
assert (
len(reusing) <= 1
), "only one chunked request may reuse req_pool_idx in a batch"
assert len(reusing) <= 1, (
"only one chunked request may reuse req_pool_idx in a batch"
)
assert all(
reqs[i].is_chunked > 0 or reqs[i].kv_committed_len > 0 for i in reusing
), "reusing request must be chunked or have committed KV"
@@ -166,7 +166,6 @@ class DecodeReqToTokenPool:
class HybridMambaDecodeReqToTokenPool(HybridReqToTokenPool):
def __init__(
self,
size: int,
@@ -793,9 +792,9 @@ class DecodePreallocQueue:
"""Pre-allocate the memory for req_to_token and token_kv_pool"""
req_pool_indices = self.req_to_token_pool.alloc([req])
assert (
req_pool_indices is not None
), "req_pool_indices is full! There is a bug in memory estimation."
assert req_pool_indices is not None, (
"req_pool_indices is full! There is a bug in memory estimation."
)
# Alloc all tokens for the prebuilt req (except for the reserved input token for decoding)
fill_len = len(req.origin_input_ids) + max(len(req.output_ids) - 1, 0)
@@ -814,9 +813,9 @@ class DecodePreallocQueue:
extend_num_tokens=fill_len,
)
assert (
kv_loc is not None
), "KV cache is full! There is a bug in memory estimation."
assert kv_loc is not None, (
"KV cache is full! There is a bug in memory estimation."
)
self.req_to_token_pool.write((req.req_pool_idx, slice(0, len(kv_loc))), kv_loc)
@@ -1008,7 +1007,6 @@ class DecodeTransferQueue:
class SchedulerDisaggregationDecodeMixin:
@torch.no_grad()
def event_loop_normal_disagg_decode(self: Scheduler):
"""A normal scheduler loop for decode worker in disaggregation mode."""
@@ -1023,6 +1021,18 @@ class SchedulerDisaggregationDecodeMixin:
# Get the next batch to run
batch = self.get_next_disagg_decode_batch_to_run()
self.cur_batch = batch
disable_overlap_for_batch = self.is_disable_overlap_for_batch(batch)
def pop_and_process():
# Process the results of the last batch
tmp_batch, tmp_result = self.result_queue.popleft()
self.process_batch_result(tmp_batch, tmp_result)
# If we need grammar sync (spec + grammar), process the last batch
# results first so that the grammar state is up-to-date before
# generating the bitmask for the current batch.
if disable_overlap_for_batch:
pop_and_process()
# Launch the current batch
if batch:
@@ -1040,6 +1050,11 @@ class SchedulerDisaggregationDecodeMixin:
self.result_queue = deque()
self.last_batch: Optional[ScheduleBatch] = None
def pop_and_process():
# Process the results of the last batch
tmp_batch, tmp_result = self.result_queue.popleft()
self.process_batch_result(tmp_batch, tmp_result)
while True:
# Receive requests
recv_reqs = self.recv_requests()
@@ -1050,6 +1065,13 @@ class SchedulerDisaggregationDecodeMixin:
# Get the next batch to run
batch = self.get_next_disagg_decode_batch_to_run()
self.cur_batch = batch
disable_overlap_for_batch = self.is_disable_overlap_for_batch(batch)
# If we need grammar sync (spec + grammar), process the last batch
# results first so that the grammar state is up-to-date before
# generating the bitmask for the current batch.
if self.last_batch and disable_overlap_for_batch:
pop_and_process()
# Launch the current batch
if batch:
@@ -1060,8 +1082,8 @@ class SchedulerDisaggregationDecodeMixin:
# Process the last batch
if self.last_batch:
tmp_batch, tmp_result = self.result_queue.popleft()
self.process_batch_result(tmp_batch, tmp_result)
if not disable_overlap_for_batch:
pop_and_process()
elif batch is None:
self.self_check_during_idle()

View File

@@ -75,7 +75,10 @@ class AttentionBackend(ABC):
Here, we need to redo the computation of all metadata of the attention backend
that depends on tree mask and position buffers.
"""
raise NotImplementedError()
# if self.topk <= 1:
# return
# raise NotImplementedError()
pass
@debug_kernel_api
def forward(