main
py 173 lines 5.85 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import pytest
5
6 from tests.utils import single_gpu_only
7 from vllm import SamplingParams
8 from vllm.config import CompilationConfig
9 from vllm.platforms import current_platform
10
11 from ..utils import (
12 evaluate_llm_for_gsm8k,
13 get_spec_decode_metric_value,
14 get_test_prompts,
15 )
16
17
18 @pytest.fixture
19 def disable_vllm_compile_cache_on_rocm(request: pytest.FixtureRequest) -> None:
20 if current_platform.is_rocm():
21 request.getfixturevalue("disable_vllm_compile_cache")
22
23
24 @pytest.mark.parametrize(
25 "speculative_config",
26 [
27 {
28 "method": "ngram",
29 "prompt_lookup_max": 5,
30 "prompt_lookup_min": 3,
31 "num_speculative_tokens": 3,
32 },
33 {
34 "method": "suffix",
35 "suffix_decoding_max_spec_factor": 2.0,
36 },
37 ],
38 )
39 @pytest.mark.usefixtures("disable_vllm_compile_cache_on_rocm")
40 @single_gpu_only
41 def test_ngram_and_suffix_correctness(
42 speculative_config: dict,
43 model_name: str,
44 vllm_runner,
45 ):
46 with vllm_runner(
47 model_name,
48 block_size=None,
49 # Keep LLM defaults; VllmRunner only provides lifecycle cleanup here.
50 trust_remote_code=False,
51 enable_chunked_prefill=None,
52 speculative_config=speculative_config,
53 max_model_len=4096,
54 # Preserve LLM's default compilation/cudagraph configuration. Without
55 # this, VllmRunner injects its reduced test-only capture sizes.
56 compilation_config=CompilationConfig(),
57 ) as runner:
58 evaluate_llm_for_gsm8k(runner.llm)
59
60
61 @pytest.mark.parametrize("async_scheduling", [True], ids=["async"])
62 @single_gpu_only
63 def test_ngram_gpu_default_with_async_scheduling(
64 async_scheduling: bool,
65 vllm_runner,
66 ):
67 """Test ngram_gpu speculative decoding (k=3) correctness with and without
68 async scheduling, validated via GSM8K accuracy.
69 Uses Qwen/Qwen3-8B (ref GSM8K accuracy: 87%-92%).
70 """
71 qwen3_model = "Qwen/Qwen3-8B"
72 with vllm_runner(
73 qwen3_model,
74 block_size=None,
75 trust_remote_code=False,
76 speculative_config={
77 "method": "ngram_gpu",
78 "prompt_lookup_max": 3,
79 "prompt_lookup_min": 2,
80 "num_speculative_tokens": 2,
81 },
82 max_model_len=4096,
83 async_scheduling=async_scheduling,
84 enable_chunked_prefill=None,
85 compilation_config=CompilationConfig(),
86 ) as spec_runner:
87 # Assert the resolved async_scheduling config matches what was requested.
88 assert (
89 spec_runner.llm.llm_engine.vllm_config.scheduler_config.async_scheduling
90 == async_scheduling
91 )
92 evaluate_llm_for_gsm8k(spec_runner.llm, expected_accuracy_threshold=0.8)
93
94
95 @single_gpu_only
96 def test_suffix_decoding_acceptance(
97 sampling_config: SamplingParams,
98 model_name: str,
99 vllm_runner,
100 ):
101 """Check that suffix decoding caching takes effect and improves acceptance
102 lengths and acceptance rates over multiple runs of the same prompts.
103 """
104 test_prompts = get_test_prompts(mm_enabled=False)
105
106 with vllm_runner(
107 model_name,
108 block_size=None,
109 trust_remote_code=False,
110 speculative_config={
111 "method": "suffix",
112 "suffix_decoding_max_spec_factor": 2.0,
113 "suffix_decoding_max_cached_requests": 1000,
114 },
115 max_model_len=1024,
116 disable_log_stats=False,
117 enable_chunked_prefill=None,
118 compilation_config=CompilationConfig(),
119 ) as spec_runner:
120 # Run several times and check that the accepted tokens increase.
121 num_draft = []
122 num_accept = []
123 for _ in range(10): # Run multiple times to warm up the cache.
124 spec_runner.llm.chat(test_prompts, sampling_config)
125 # Collect draft and acceptance stats.
126 metrics = spec_runner.llm.get_metrics()
127 num_draft.append(
128 get_spec_decode_metric_value(
129 metrics, "vllm:spec_decode_num_draft_tokens"
130 )
131 )
132 num_accept.append(
133 get_spec_decode_metric_value(
134 metrics, "vllm:spec_decode_num_accepted_tokens"
135 )
136 )
137
138 # Calculate the acceptance rates for the first and last runs.
139 first_accept_tokens = num_accept[0]
140 first_draft_tokens = num_draft[0]
141 assert first_draft_tokens > 0, (
142 "Suffix decoder produced no draft tokens on the first run: "
143 f"accepted={first_accept_tokens}, drafted={first_draft_tokens}"
144 )
145 first_accept_rate = first_accept_tokens / first_draft_tokens
146
147 # Take the diff since the stats are cumulative.
148 last_accept_tokens = num_accept[-1] - num_accept[-2]
149 last_draft_tokens = num_draft[-1] - num_draft[-2]
150 assert last_draft_tokens > 0, (
151 "Suffix decoder produced no draft tokens on the last run: "
152 f"accepted_delta={last_accept_tokens}, drafted_delta={last_draft_tokens}; "
153 f"cumulative_drafted={num_draft[-2:]}"
154 )
155 last_accept_rate = last_accept_tokens / last_draft_tokens
156 summary = (
157 f"first accepted/drafted={first_accept_tokens}/{first_draft_tokens} "
158 f"(rate={first_accept_rate:.3f}); last delta accepted/drafted="
159 f"{last_accept_tokens}/{last_draft_tokens} (rate={last_accept_rate:.3f})"
160 )
161
162 # Expect the acceptance length to improve.
163 assert first_accept_tokens < last_accept_tokens, (
164 f"Expected accepted tokens to increase after cache warmup; {summary}"
165 )
166
167 # Expect the acceptance rate to improve.
168 assert first_accept_rate < last_accept_rate, (
169 f"Expected acceptance rate to increase after cache warmup; {summary}"
170 )
171
172 # Heuristic: expect at least 80.0% acceptance rate at the end.
173 assert last_accept_rate > 0.80, f"Expected final acceptance rate > 0.80; {summary}"