[Spec] Reduce host-side overhead in ngram draft prep (#35207)

This commit is contained in:
Liangsheng Yin
2026-08-17 16:40:06 -07:00
committed by GitHub
parent b3c8f0d923
commit c0b6474b43
+44 -23
View File
@@ -241,6 +241,28 @@ class NGRAMWorker(BaseSpecWorker):
bs = len(batch.reqs) bs = len(batch.reqs)
stride = self.draft_token_num stride = self.draft_token_num
# Overlap mode processes results one iteration behind, so the last
# round's accepted tokens are not yet in req.output_ids and must be
# spliced in from spec_info. Sync mode and grammar batches process
# results before the next draft prep, so output_ids is already
# complete and splicing would duplicate the tail.
use_prev_tokens = self.enable_overlap and not batch.grammar_needs_sync()
# Accept-independent prep, hoisted above the blocking .cpu() below.
req_ids = [req.rid for req in batch.reqs]
# Only the last max_trie_depth tokens can match; the ids are
# array.array, so list() the tails for list concat below.
input_tails = [
list(req.origin_input_ids[-self.max_trie_depth :]) for req in batch.reqs
]
output_tails = [
list(req.output_ids[-self.max_trie_depth :]) for req in batch.reqs
]
base_lens = [
len(req.origin_input_ids) + len(req.output_ids) for req in batch.reqs
]
if use_prev_tokens:
prev_token_ids, prev_accept_lens = ( prev_token_ids, prev_accept_lens = (
batch.spec_info.accept_tokens, batch.spec_info.accept_tokens,
batch.spec_info.accept_lens, batch.spec_info.accept_lens,
@@ -252,36 +274,28 @@ class NGRAMWorker(BaseSpecWorker):
# _update_ngram_corpus after verify within the same forward call. # _update_ngram_corpus after verify within the same forward call.
self.prev_token_ids = prev_token_ids.tolist() self.prev_token_ids = prev_token_ids.tolist()
self.prev_accept_lens = prev_accept_lens.tolist() self.prev_accept_lens = prev_accept_lens.tolist()
assert bs == len(self.prev_accept_lens)
else:
# _update_ngram_corpus still reads the staging; fill it empty.
self.prev_token_ids = []
self.prev_accept_lens = [0] * bs
self.ngram_corpus.synchronize() self.ngram_corpus.synchronize()
req_ids = []
batch_tokens = [] batch_tokens = []
total_lens = [] total_lens = []
assert len(batch.reqs) == len(self.prev_accept_lens) for i in range(bs):
# Overlap mode processes results one iteration behind, so the last
# round's accepted tokens are not yet in req.output_ids and must be
# spliced in from spec_info. Sync mode and grammar batches process
# results before the next draft prep, so output_ids is already
# complete and splicing would duplicate the tail.
use_prev_tokens = self.enable_overlap and not batch.grammar_needs_sync()
i = 0
for req in batch.reqs:
prev_tokens = ( prev_tokens = (
self.prev_token_ids[i * stride : i * stride + self.prev_accept_lens[i]] self.prev_token_ids[i * stride : i * stride + self.prev_accept_lens[i]]
if use_prev_tokens if use_prev_tokens
else [] else []
) )
check_token = self._efficient_concat_last_n( check_token = self._efficient_concat_last_n(
list(req.origin_input_ids), input_tails[i],
list(req.output_ids[-self.max_trie_depth :]) + prev_tokens, output_tails[i] + prev_tokens,
self.max_trie_depth, self.max_trie_depth,
) )
req_ids.append(req.rid)
batch_tokens.append(check_token) batch_tokens.append(check_token)
i += 1 total_lens.append(base_lens[i] + len(prev_tokens))
total_lens.append(
len(req.origin_input_ids) + len(req.output_ids) + len(prev_tokens)
)
req_drafts, mask = self.ngram_corpus.batch_get( req_drafts, mask = self.ngram_corpus.batch_get(
req_ids, batch_tokens, total_lens req_ids, batch_tokens, total_lens
) )
@@ -309,6 +323,17 @@ class NGRAMWorker(BaseSpecWorker):
tree_mask = self.tree_mask_batch[bs] tree_mask = self.tree_mask_batch[bs]
draft_tokens = self.draft_tokens_batch[bs] draft_tokens = self.draft_tokens_batch[bs]
# Pre-sync: only needs seq_lens_cpu, so build before the blocking
# accept sync inside _prepare_draft_tokens.
ones_masks = None
if USE_FULL_MASK and not _is_cpu:
ones_masks = [
torch.ones(
(self.draft_token_num, batch.seq_lens_cpu[i]), device=self.device
)
for i in range(bs)
]
req_drafts, mask = self._prepare_draft_tokens(batch) req_drafts, mask = self._prepare_draft_tokens(batch)
tree_mask.copy_(torch.from_numpy(mask), non_blocking=True) tree_mask.copy_(torch.from_numpy(mask), non_blocking=True)
draft_tokens.copy_(torch.from_numpy(req_drafts), non_blocking=True) draft_tokens.copy_(torch.from_numpy(req_drafts), non_blocking=True)
@@ -335,13 +360,9 @@ class NGRAMWorker(BaseSpecWorker):
mask = mask.reshape(bs, self.draft_token_num, self.draft_token_num) mask = mask.reshape(bs, self.draft_token_num, self.draft_token_num)
# TODO(siyuan): the for loop here leads to significant overhead in large batch size. Can be written into a kernel. # TODO(siyuan): the for loop here leads to significant overhead in large batch size. Can be written into a kernel.
for i in range(bs): for i in range(bs):
seq_len = batch.seq_lens_cpu[i]
req_mask = torch.ones(
(self.draft_token_num, seq_len), device=self.device
)
req_mask = torch.cat( req_mask = torch.cat(
( (
req_mask, ones_masks[i],
torch.from_numpy(mask[i]).to( torch.from_numpy(mask[i]).to(
device=self.device, non_blocking=True device=self.device, non_blocking=True
), ),
@@ -393,7 +414,7 @@ class NGRAMWorker(BaseSpecWorker):
else [] else []
) )
put_ids = self._efficient_concat_last_n( put_ids = self._efficient_concat_last_n(
list(req.origin_input_ids), list(req.origin_input_ids[-self.max_trie_depth :]),
list(req.output_ids[-self.max_trie_depth :]) + prev_tokens, list(req.output_ids[-self.max_trie_depth :]) + prev_tokens,
self.max_trie_depth, self.max_trie_depth,
) )