Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
…ort #24082) Port PR #24082 onto the post-refactor scheduler component layout. The v0.5.11 fix only covered the Spec V1 grammar-finish path; it did not cover Spec V2 or the PD-disaggregated overlap decode event loop. - decode.py: in event_loop_overlap_disagg_decode, process the previously queued batch result (advancing the grammar) before launching the next Spec V2 grammar decode batch, and skip the duplicate last-batch pop so it is not processed twice. - batch_result_processor.py: in process_batch_result_decode, for is_spec_v2 and req.grammar is not None, accept proposed tokens one at a time and stop at grammar completion, trimming output_ids, the grammar FSM, reasoning state, and logprob bookkeeping to the accepted prefix. Tests: - CPU unit test for the trimming loop (runs the real process_batch_result_decode). - PD disagg + overlap + EAGLE Spec V2 (topk=1) + grammar regression test. Co-authored-by: Ashish Datta <1856117+ashishdatta@users.noreply.github.com>
…ort sgl-project#24082) Port PR sgl-project#24082 onto the post-refactor scheduler component layout. The v0.5.11 fix only covered the Spec V1 grammar-finish path; it did not cover Spec V2 or the PD-disaggregated overlap decode event loop. - decode.py: in event_loop_overlap_disagg_decode, process the previously queued batch result (advancing the grammar) before launching the next Spec V2 grammar decode batch, and skip the duplicate last-batch pop so it is not processed twice. - batch_result_processor.py: in process_batch_result_decode, for is_spec_v2 and req.grammar is not None, accept proposed tokens one at a time and stop at grammar completion, trimming output_ids, the grammar FSM, reasoning state, and logprob bookkeeping to the accepted prefix. Tests: - CPU unit test for the trimming loop (runs the real process_batch_result_decode). - PD disagg + overlap + EAGLE Spec V2 (topk=1) + grammar regression test. Co-authored-by: jimmy.shong <jimmy.shong@radixark.ai>
…ort #24082) Port PR #24082 onto the post-refactor scheduler component layout. The v0.5.11 fix only covered the Spec V1 grammar-finish path; it did not cover Spec V2 or the PD-disaggregated overlap decode event loop. - decode.py: in event_loop_overlap_disagg_decode, process the previously queued batch result (advancing the grammar) before launching the next Spec V2 grammar decode batch, and skip the duplicate last-batch pop so it is not processed twice. - batch_result_processor.py: in process_batch_result_decode, for is_spec_v2 and req.grammar is not None, accept proposed tokens one at a time and stop at grammar completion, trimming output_ids, the grammar FSM, reasoning state, and logprob bookkeeping to the accepted prefix. Tests: - CPU unit test for the trimming loop (runs the real process_batch_result_decode). - PD disagg + overlap + EAGLE Spec V2 (topk=1) + grammar regression test. Co-authored-by: Ashish Datta <1856117+ashishdatta@users.noreply.github.com>
3f0fd7c to
f0bb40c
Compare
|
Validated on current
Reverting just the two source changes reproduces the bug, so the fix is load-bearing. Both changes are independently necessary — the disagg grammar-sync is guarded by the PD regression, the Spec V2 token-trim by the unit test. |
|
/tag-and-rerun-ci |
…st test ctx-len - batch_result_processor.py: extract the Spec V2 grammar token-accept loop into _accept_spec_v2_grammar_tokens(); replace the grammar_advanced flag with an is_spec_v2_grammar predicate; de-duplicate the accept_token error handling. - test_disaggregation_spec_grammar.py: use --context-length 2048 so the target and EAGLE3-draft ModelConfigs agree, instead of overriding SGLANG_ALLOW_OVERWRITE_LONGER_CONTEXT_LEN to ignore the mismatch. - decode.py: drop a restating comment on the need_grammar_sync guard. The need_grammar_sync overlap-ordering mechanism (from sgl-project#24082) is unchanged; turning it into an enforced scheduler invariant is tracked as follow-up.
_resolve_spec_overlap_tokens advances req.kv_committed_len by (accept_lens - 1)
based on the full spec-accepted count, before _accept_spec_v2_grammar_tokens
knows the grammar may terminate partway through. When grammar terminates
mid-list, the dropped tokens are committed-but-unused and leak when the request
releases — caught by SchedulerInvariantChecker._report_leak("pool") on current
main (added in sgl-project#25623 after the original port was written).
Decrement kv_committed_len by len(proposed) - len(accept_tokens) at the end of
the helper so the resolver's over-commit is undone for the dropped suffix. No
effect when grammar doesn't terminate mid-list (dropped == 0).
Repro on the merge head (7e2f8df) without the fix: TestEagleConstrainedDecodingV2
hits `pool memory leak detected ... total=187911, available=187648, evictable=262`
(1 slot imbalance). With the fix: 12/12 V2 cases pass on the same hardware.
| if not need_grammar_sync: | ||
| tmp_batch, tmp_result = self.result_queue.popleft() | ||
| self.process_batch_result(tmp_batch, tmp_result) |
There was a problem hiding this comment.
Why not just set self.last_batch = None in previous if need_grammar_sync block?
| need_grammar_sync = ( | ||
| batch | ||
| and batch.is_spec_v2 | ||
| and batch.has_grammar | ||
| and batch.forward_mode.is_decode() | ||
| and len(self.result_queue) > 0 | ||
| ) | ||
| if need_grammar_sync: | ||
| tmp_batch, tmp_result = self.result_queue.popleft() | ||
| self.process_batch_result(tmp_batch, tmp_result) | ||
|
|
There was a problem hiding this comment.
Something like this:
| need_grammar_sync = ( | |
| batch | |
| and batch.is_spec_v2 | |
| and batch.has_grammar | |
| and batch.forward_mode.is_decode() | |
| and len(self.result_queue) > 0 | |
| ) | |
| if need_grammar_sync: | |
| tmp_batch, tmp_result = self.result_queue.popleft() | |
| self.process_batch_result(tmp_batch, tmp_result) | |
| need_grammar_sync = ( | |
| batch | |
| and batch.is_spec_v2 | |
| and batch.has_grammar | |
| and batch.forward_mode.is_decode() | |
| and self.last_batch is not None | |
| and len(self.result_queue) > 0 | |
| ) | |
| if need_grammar_sync: | |
| tmp_batch, tmp_result = self.result_queue.popleft() | |
| self.process_batch_result(tmp_batch, tmp_result) | |
| self.last_batch = None | |
There was a problem hiding this comment.
On second thought, without token-by-token acceptance, grammar could terminate at a token, but the bonus following token is already appended to output_ids. The trimming logic is correct in this case. But I am not fully sure about the modification of these if not is_spec_v2_grammar blocks, you should find some experts to review this part.
There was a problem hiding this comment.
cc @fzyzcjy if you have time, can you take a look at this?
There was a problem hiding this comment.
I think we'd better add this test to test_disaggregation_basic, instead of creating another one. This could help us save some CI resources.
There was a problem hiding this comment.
Done, TestDisaggregationSpecV2Grammar lives in test_disaggregation_basic.py now. Bumped the module's est_time to 750 to cover the added class. Each class still runs different server flags.
ShangmingCai
left a comment
There was a problem hiding this comment.
cc: @Ubospica, do you have time to help us review this PR?
…gg sync, merge tests - decode.py (per @ShangmingCai): tighten `need_grammar_sync` predicate with `self.last_batch is not None`, set `self.last_batch = None` inside the block, and remove the `if not need_grammar_sync` guard from the later "Process the last batch" branch. Reads cleaner and removes the only special-case fallthrough. - batch_result_processor.py (per @ShangmingCai): revert the Spec V2 grammar trimming branch + `_accept_spec_v2_grammar_tokens` helper + kv_committed_len rollback. Experiment: with the disagg sync in place, the trim never fires in the PR's primary path (Option A: 0 TRIM_PROBE hits across 3 passes). In the non-disagg Eagle V2 path it fired once across 12 tests, but plain main passes that test without the trim too (12 passed, 0 leaks, 0 grammar errors), and Option A passes equivalently (3 passed, 0 leaks). The kv leak the rollback patched was self-induced by the trim's over-commit. - test_disaggregation_basic.py (per @ShangmingCai): merge `TestDisaggregationSpecV2Grammar` here as a new class so the PD fixture amortizes across the existing Spec PD tests instead of spinning up another module. Bump `est_time` to 750s. - delete `test_disaggregation_spec_grammar.py` (merged) and `test_batch_result_processor_spec_grammar.py` (no helper to test).
@ShangmingCai edited their earlier comment to "On second thought, without token-by-token acceptance, grammar could terminate at a token, but the bonus following token is already appended to output_ids. The trimming logic is correct in this case." Restoring the trim/helper/rollback I had removed in 7e7a487: the spec-v2 bonus-token after grammar termination needs to be dropped from output_ids, the grammar FSM, and KV bookkeeping — my earlier experiment didn't trigger that exact case so it looked dead, but the scenario is real and the trim is the correct guard. Re-adds: - batch_result_processor.py: _accept_spec_v2_grammar_tokens helper, the is_spec_v2_grammar predicate + elif branch in process_batch_result_decode, the `if not is_spec_v2_grammar` guards on reasoning/finish, the grammar terminal-flag sync (in lieu of _apply_decode_grammar), and the kv_committed_len rollback for the dropped tokens. - test_batch_result_processor_spec_grammar.py: CPU unit test exercising the helper (3 tokens proposed, grammar terminates after 2, assert trimmed prefix + finish state + grammar advance). The decode.py simplification and the test_disaggregation_basic.py merged class from 7e7a487 are kept as-is. @ShangmingCai flagged the `if not is_spec_v2_grammar` guard pattern for expert review (cc @Ubospica already pinged) — guards are intentional so per-token state isn't double-applied after the helper, happy to refactor if there's a cleaner shape.
|
/rerun-test test/registered/disaggregation/test_disaggregation_basic.py |
|
Results for 🚀 |
|
Thanks for your contribution @ashishdatta and the review request from @ShangmingCai @Jiminator! This fix is definitely useful. The fix generally looks great to me, and I am further looking into the details of it. |
# Conflicts: # test/registered/disaggregation/test_disaggregation_basic.py
Align spec grammar handling with the current spec_algorithm API and remove a disagg sync path made redundant by the mainline overlap guard. Record the intentional KV rollback owner and keep the unit fake aligned with the current result processor hook.
Spec V2 is now the default speculative path, and the old SGLANG_ENABLE_SPEC_V2 descriptor was removed on main. Launch the regression test directly so it stays compatible after merging main.
Ubospica
left a comment
There was a problem hiding this comment.
The logic generally looks great to me. Just a few comments
| logger.error( | ||
| f"Grammar accept_token failed for req {req.rid} with token {proposed}: {e}" | ||
| ) | ||
| self.abort_request(AbortReq(rid=req.rid)) |
There was a problem hiding this comment.
Do we need to call update_finish_state() here?
| accept_tokens.append(token_id) | ||
| self._maybe_update_reasoning_tokens(req, token_id) | ||
| req.grammar.accept_token(token_id) | ||
| req.update_finish_state() |
There was a problem hiding this comment.
Maybe we should accept token first, then append it into output_ids and accept_tokens, in case the token get rejected?
| self.assertTrue(req.grammar.finished) | ||
| # Logprob bookkeeping sees only the retained tokens. | ||
| self.assertEqual(req.logprob.output_token_logprobs_val, [-0.1, -0.2]) | ||
| self.assertEqual(req.logprob.output_token_logprobs_idx, [101, 102]) |
There was a problem hiding this comment.
shall we check kv_committed_len as well
| """Unit test for the Spec V2 + grammar trimming in process_batch_result_decode. | ||
|
|
||
| Companion to the GPU regression in | ||
| test/registered/disaggregation/test_disaggregation_spec_grammar.py (PR #24082). |
There was a problem hiding this comment.
Looks the file is deleted
Accept grammar tokens before mutating request output state, assert KV commit rollback in the CPU regression, and fix the stale disaggregation test reference called out in review.
|
@ashishdatta pushed a small cleanup pass on the tests/comments. Main changes: moved the processor stub test to CPU CI, folded the spec_verify_ct check into the existing e2e requests, and trimmed the added comments/docstrings. Could you take a quick look? |
Consume the pending FINISH_ABORT after grammar accept_token fails so the decode-result cleanup sees the request as finished in the same pass.
# Conflicts: # test/registered/disaggregation/test_disaggregation_basic.py
Co-authored-by: jimmy.shong <jimmy.shong@radixark.ai> Co-authored-by: Jimmy Shong <69131491+Jiminator@users.noreply.github.com> Co-authored-by: Xinyuan Tong <xinyuantong.cs@gmail.com> Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com> Co-authored-by: Codex <codex@example.com>
Co-authored-by: jimmy.shong <jimmy.shong@radixark.ai> Co-authored-by: Jimmy Shong <69131491+Jiminator@users.noreply.github.com> Co-authored-by: Xinyuan Tong <xinyuantong.cs@gmail.com> Co-authored-by: Xinyuan Tong <115166877+JustinTong0323@users.noreply.github.com> Co-authored-by: Codex <codex@example.com>
Motivation
Fix structured output generation when speculative decoding is used in disaggregated decode mode.
Spec-v2 decode can accept multiple draft tokens in one step. With grammar-constrained decoding, the scheduler must process grammar state updates before launching the next overlapping disagg decode batch, and it must stop accepting speculative tokens once the grammar reaches a terminal state.
Modifications
Accuracy Tests
Not a model-forward or kernel accuracy change. Adds a CPU unit test and a PD-disagg + overlap + Spec V2 (
topk=1) + grammar regression; validated single-node and cross-node on H200 (see PR comment). Validated changed files with:Speed Tests and Profiling
No expected steady-state speed impact. The grammar sync path only applies to spec-v2 + grammar + disaggregated overlap decode when a prior result is pending.
No benchmarking run.
Checklist
CI States
Latest PR Test (Base): ✅ Run #27744167980
Latest PR Test (Extra): ✅ Run #27744167737