Skip to content

Commit 932838f

Browse files
committed
feat(spec_decode): add Eagle3 guided decoding with d2t bitmask translation
- Eagle3.get_outputs() now applies grammar mask before argmax and accept_token after d2t mapping, matching DeepseekMTP pattern - Add _translate_bitmask() to convert target-vocab bitmask to draft-vocab bitmask via scatter_add_ (vectorized, no loops) - Remove supports_grammar_mask flag; all proposers now support it - Fork guided processors unconditionally in spec_agent._async_model_forward - Move session_to_cleanup handling before get_processors in forward_decode - Bump xgrammar>=0.1.33 (fork() requirement) in all 5 runtime requirements - Add comprehensive tests: bitmask translation, Eagle3 get_outputs, fork independence, multi-step draft loop
1 parent 6e5ed47 commit 932838f

9 files changed

Lines changed: 663 additions & 16 deletions

File tree

lmdeploy/pytorch/spec_decode/proposers/base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ def draft_model_forward(
5757

5858
class BaseSpecProposer:
5959

60-
supports_grammar_mask: bool = True
61-
6260
def __init__(self, specdecode_config: SpecDecodeConfig, device: torch.device = None):
6361
self.specdecode_config = specdecode_config
6462
self.model = None

lmdeploy/pytorch/spec_decode/proposers/eagle3.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,52 @@
1616
@SPEC_PROPOSERS.register_module(name='eagle3')
1717
class Eagle3(DeepseekMTP):
1818

19-
supports_grammar_mask: bool = False
20-
2119
def build_model(self, empty_init: bool, target_model: torch.nn.Module = None, build_model_ctx=None):
2220
super().build_model(empty_init, target_model=target_model, build_model_ctx=build_model_ctx)
2321
self.draft_id_to_target_id = self.model.draft_id_to_target_id
22+
self._init_bitmask_translate_constants()
2423
if not self.model.include_embed_tokens:
2524
logger.info('Using embed_tokens from target model.')
2625
del self.model.model.embed_tokens
2726
self.model.model.embed_tokens = target_model.get_input_embeddings()
2827

28+
def _init_bitmask_translate_constants(self):
29+
d2t = self.draft_id_to_target_id
30+
self._d2t_words = d2t // 32
31+
self._d2t_bits = d2t % 32
32+
draft_vocab_size = d2t.size(0)
33+
draft_indices = torch.arange(draft_vocab_size, dtype=torch.int32)
34+
self._draft_words = draft_indices // 32
35+
self._draft_bits = draft_indices % 32
36+
37+
def _translate_bitmask(self, target_bitmask: torch.Tensor) -> torch.Tensor:
38+
"""Translate target-vocab bitmask to draft-vocab bitmask.
39+
40+
Args:
41+
target_bitmask: [batch, ceil(target_vocab/32)] int32 bitmask
42+
produced by xgr.GrammarMatcher.fill_next_token_bitmask.
43+
44+
Returns:
45+
draft_bitmask: [batch, ceil(draft_vocab/32)] int32 bitmask
46+
compatible with apply_batched_bitmap.
47+
"""
48+
d2t_words = self._d2t_words.to(target_bitmask.device)
49+
d2t_bits = self._d2t_bits.to(target_bitmask.device)
50+
draft_words = self._draft_words.to(target_bitmask.device)
51+
draft_bits = self._draft_bits.to(target_bitmask.device)
52+
53+
word_vals = target_bitmask[:, d2t_words]
54+
draft_valid = ((word_vals >> d2t_bits.unsqueeze(0)) & 1).to(torch.int32)
55+
56+
# scatter_add_ is correct because bit positions within the same word
57+
# never overlap, so addition ≡ bitwise OR.
58+
bits_to_set = draft_valid << draft_bits
59+
n_draft_words = (draft_valid.size(1) + 31) // 32
60+
out = target_bitmask.new_zeros(target_bitmask.size(0), n_draft_words)
61+
out.scatter_add_(1, draft_words.unsqueeze(0).expand(target_bitmask.size(0), -1),
62+
bits_to_set)
63+
return out
64+
2965
def get_target_hidden_size(self, model_config: ModelConfig):
3066
"""Get target hidden size."""
3167
hf_config = self.specdecode_config.model_config.hf_config
@@ -35,7 +71,8 @@ def get_target_hidden_size(self, model_config: ModelConfig):
3571
def get_outputs(self,
3672
model_outputs: dict[str, torch.Tensor],
3773
model_inputs: ModelInputs,
38-
extra_inputs: ExtraInputs = None):
74+
extra_inputs: ExtraInputs = None,
75+
guided_processors: dict | None = None):
3976
"""Get outputs."""
4077
hidden_states = model_outputs['hidden_states']
4178
hidden_states_prenorm = model_outputs['hidden_states_prenorm']
@@ -52,7 +89,20 @@ def get_outputs(self,
5289

5390
logits = self.get_logits(hidden_states)[0]
5491

92+
if guided_processors and self.guided_decoding_manager is not None:
93+
guided_manager = self.guided_decoding_manager
94+
guided_bitmask = guided_manager.allocate_batched_bitmap(logits.size(0))
95+
for idx, processor in guided_processors.items():
96+
guided_manager.fill_bitmap(processor, guided_bitmask, idx)
97+
draft_bitmask = self._translate_bitmask(guided_bitmask)
98+
guided_manager.apply_batched_bitmap(logits, draft_bitmask)
99+
55100
draft_token_ids = logits.argmax(dim=-1, keepdim=True)
56101
draft_token_ids = self.draft_id_to_target_id[draft_token_ids]
57102

103+
if guided_processors and self.guided_decoding_manager is not None:
104+
guided_manager = self.guided_decoding_manager
105+
for idx, processor in guided_processors.items():
106+
guided_manager.accept_token(processor, draft_token_ids[idx, 0].item())
107+
58108
return draft_token_ids, model_metas, hidden_states_prenorm

lmdeploy/pytorch/spec_decode/spec_agent.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,15 @@ def __compute_logprobs(raw_logprobs: torch.Tensor, token_ids: torch.LongTensor,
368368

369369
guided_processors = {}
370370
guided_manager = self.guided_decoding_manager
371-
if guided_manager and sampling_inputs.session_ctx is not None:
372-
guided_processors = guided_manager.get_processors(
373-
sampling_inputs.session_ctx, sampling_inputs.response_formats)
371+
if guided_manager:
372+
session_to_cleanup = sampling_inputs.session_to_cleanup
373+
if session_to_cleanup is not None:
374+
for session_id in session_to_cleanup:
375+
guided_manager.remove_processor(session_id)
376+
377+
if sampling_inputs.session_ctx is not None:
378+
guided_processors = guided_manager.get_processors(
379+
sampling_inputs.session_ctx, sampling_inputs.response_formats)
374380

375381
if model_inputs.is_decoding:
376382
if guided_processors:
@@ -539,11 +545,10 @@ async def _async_model_forward(self, inputs: ModelInputs, extra_inputs: ARSpecEx
539545
# create dummy draft tokens
540546
output_draft_ids = inputs.input_ids.new_zeros(1, self.num_spec_tokens)
541547
else:
542-
# Fork guided processors when the proposer supports grammar masking.
548+
# Fork guided processors for draft model.
543549
draft_guided_processors = None
544550
guided_manager = self.guided_decoding_manager
545-
if (guided_manager and sampling_inputs.session_ctx is not None
546-
and self.proposer.supports_grammar_mask):
551+
if guided_manager and sampling_inputs.session_ctx is not None:
547552
orig_processors = guided_manager.get_processors(
548553
sampling_inputs.session_ctx, sampling_inputs.response_formats)
549554
draft_guided_processors = {idx: proc.fork()

requirements/runtime_ascend.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ torch-npu>=2.3.1,<2.10.0
2727
torchvision>=0.18.1,<0.25.0
2828
transformers
2929
uvicorn
30-
xgrammar
30+
xgrammar>=0.1.33

requirements/runtime_camb.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ torch<=2.6.0,>=2.4.0
2626
torchvision<=0.21.0,>=0.15.0
2727
transformers
2828
uvicorn
29-
xgrammar
29+
xgrammar>=0.1.33

requirements/runtime_cuda.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ torchvision<=0.25.0,>=0.15.0
2929
transformers>=4.52.0
3030
triton<=3.6.0,>=3.0.0; sys_platform == "linux" and "aarch64" not in platform_machine and "arm" not in platform_machine
3131
uvicorn
32-
xgrammar
32+
xgrammar>=0.1.33

requirements/runtime_maca.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ torchvision<=0.21.0,>=0.15.0
2727
transformers
2828
triton>=2.1.0; sys_platform == "linux"
2929
uvicorn
30-
xgrammar
30+
xgrammar>=0.1.33

requirements/runtime_rocm.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ shortuuid
2121
tiktoken
2222
transformers
2323
uvicorn
24-
xgrammar
24+
xgrammar>=0.1.33

0 commit comments

Comments
 (0)