main
py 61 lines 1.83 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Guidance backend must not choke on speculative -1 padding.
4
5 Speculative drafters (e.g. ngram_gpu) pad unfilled draft slots with a -1
6 sentinel. llguidance raises ``OverflowError`` on a negative id, so
7 ``GuidanceGrammar.validate_tokens`` must stop at the first sentinel. The mock
8 matcher below emulates that raise, so each test fails on the unpatched backend
9 and passes with the guard.
10 """
11
12 import pytest
13
14 from vllm.v1.structured_output.backend_guidance import GuidanceGrammar
15
16 pytestmark = pytest.mark.skip_global_cleanup
17
18
19 class _Matcher:
20 """Stand-in for ``llguidance.LLMatcher`` that rejects negative ids."""
21
22 def __init__(self) -> None:
23 self.tokens: list[int] | None = None
24
25 def is_stopped(self) -> bool:
26 return False
27
28 def validate_tokens(self, tokens: list[int]) -> int:
29 if any(t < 0 for t in tokens):
30 raise OverflowError("llguidance rejects negative token ids")
31 self.tokens = tokens
32 return len(tokens)
33
34 def get_error(self):
35 return None
36
37
38 class _Tokenizer:
39 eos_token = 0
40
41
42 def _grammar(matcher: _Matcher) -> GuidanceGrammar:
43 return GuidanceGrammar(ll_matcher=matcher, ll_tokenizer=_Tokenizer(), vocab_size=16)
44
45
46 def test_validate_tokens_stops_at_trailing_padding() -> None:
47 matcher = _Matcher()
48 assert _grammar(matcher).validate_tokens([3, 4, -1, -1]) == [3, 4]
49 assert matcher.tokens == [3, 4]
50
51
52 def test_validate_tokens_single_negative_does_not_raise() -> None:
53 matcher = _Matcher()
54 assert _grammar(matcher).validate_tokens([-1]) == []
55 assert matcher.tokens is None
56
57
58 def test_validate_tokens_all_padding_returns_empty() -> None:
59 matcher = _Matcher()
60 assert _grammar(matcher).validate_tokens([-1, -1]) == []
61 assert matcher.tokens is None