main
py 127 lines 4.28 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import pytest
5 import torch
6 from transformers import AutoTokenizer
7
8 import vllm.utils.gpu_sync_debug as gsd
9 from vllm.utils.gpu_sync_debug import with_gpu_sync_check
10 from vllm.v1.spec_decode.vocab_mapping import VocabMapping, _detect_space_prefix
11
12
13 @pytest.mark.parametrize(
14 "model_name,expected_prefix",
15 [
16 # BPE tokenizer (GPT-2 family) uses Ġ (U+0120)
17 ("HuggingFaceTB/SmolLM2-135M-Instruct", ("Ġ",)),
18 # SentencePiece tokenizer (LLaMA family) uses ▁ (U+2581)
19 ("TinyLlama/TinyLlama-1.1B-Chat-v1.0", ("▁",)),
20 # BPE tokenizer (Qwen family) uses Ġ (U+0120)
21 ("Qwen/Qwen2.5-0.5B-Instruct", ("Ġ",)),
22 ],
23 )
24 def test_detect_space_prefix_real_tokenizers(model_name, expected_prefix):
25 tokenizer = AutoTokenizer.from_pretrained(model_name)
26 result = _detect_space_prefix(tokenizer)
27 assert result == expected_prefix, (
28 f"{model_name}: expected {expected_prefix!r}, got {result!r}"
29 )
30
31
32 def test_detect_space_prefix_fallback_on_failure():
33 """When tokenizer lacks encode(), fall back to both known prefixes."""
34
35 class BrokenTokenizer:
36 def encode(self, text, **kwargs):
37 raise RuntimeError("broken")
38
39 result = _detect_space_prefix(BrokenTokenizer())
40 assert result == ("Ġ", "▁")
41
42
43 def test_detect_space_prefix_empty_encode():
44 """When encode returns empty list, fall back."""
45
46 class EmptyTokenizer:
47 def encode(self, text, **kwargs):
48 return []
49
50 result = _detect_space_prefix(EmptyTokenizer())
51 assert result == ("Ġ", "▁")
52
53
54 class _FakeTokenizer:
55 """Minimal tokenizer exposing only what ``VocabMapping`` needs."""
56
57 def __init__(self, vocab, unk_token_id=None, eos_token_id=None):
58 self._vocab = dict(vocab)
59 self.unk_token_id = unk_token_id
60 self.eos_token_id = eos_token_id
61
62 def get_vocab(self):
63 return dict(self._vocab)
64
65 def encode(self, text, **kwargs):
66 raise RuntimeError("no encode")
67
68 def convert_ids_to_tokens(self, ids):
69 raise RuntimeError("no convert")
70
71
72 @pytest.fixture(params=["cpu", "cuda"])
73 def vocab_mapping(request):
74 device = request.param
75 if device == "cuda" and not torch.cuda.is_available():
76 pytest.skip("requires CUDA")
77
78 # Intersection: {a, b}. x/y/z are target-only, u/v are draft-only
79 target = _FakeTokenizer(
80 vocab={"a": 0, "b": 1, "x": 2, "y": 3, "z": 4}, unk_token_id=0
81 )
82 draft = _FakeTokenizer(vocab={"a": 0, "b": 1, "u": 2, "v": 3}, unk_token_id=2)
83 return VocabMapping(target, draft, 5, 4, device)
84
85
86 @pytest.mark.parametrize(
87 "target_ids,expected",
88 [
89 ([0, 1, 2, 3, 4], [0, 1, 2, 2, 2]), # mixed
90 ([0, 1], [0, 1]), # all present
91 ([2, 3, 4], [2, 2, 2]), # all missing
92 ],
93 )
94 def test_map_target_to_draft_ids(monkeypatch, vocab_mapping, target_ids, expected):
95 device = vocab_mapping.device
96 monkeypatch.setattr(gsd, "_SYNC_CHECK_MODE", "error" if device == "cuda" else None)
97 monkeypatch.setattr(gsd, "_sync_check_enabled", True)
98
99 ids = torch.tensor(target_ids, dtype=torch.int32, device=device)
100 out = with_gpu_sync_check(lambda: vocab_mapping.map_target_to_draft_ids(ids))()
101
102 assert out.tolist() == expected
103 assert out.dtype == torch.int32, f"expected torch.int32, got {out.dtype}"
104 assert out.device.type == device, f"expected {device} device, got {out.device}"
105 assert (out != -1).all()
106
107
108 @pytest.mark.parametrize(
109 "draft_ids,expected",
110 [
111 ([0, 1, 2, 3], [0, 1, 0, 0]), # mixed
112 ([0, 1], [0, 1]), # all present
113 ([2, 3], [0, 0]), # all missing
114 ],
115 )
116 def test_map_draft_to_target_ids(monkeypatch, vocab_mapping, draft_ids, expected):
117 device = vocab_mapping.device
118 monkeypatch.setattr(gsd, "_SYNC_CHECK_MODE", "error" if device == "cuda" else None)
119 monkeypatch.setattr(gsd, "_sync_check_enabled", True)
120
121 ids = torch.tensor(draft_ids, dtype=torch.int32, device=device)
122 out = with_gpu_sync_check(lambda: vocab_mapping.map_draft_to_target_ids(ids))()
123
124 assert out.tolist() == expected
125 assert out.dtype == torch.int32, f"expected torch.int32, got {out.dtype}"
126 assert out.device.type == device, f"expected {device} device, got {out.device}"
127 assert (out != -1).all()