| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from dataclasses import dataclass |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from tests.evals.gsm8k.gsm8k_eval import evaluate_gsm8k_offline |
| 9 | from vllm.config import CompilationConfig |
| 10 | from vllm.platforms import current_platform |
| 11 | |
| 12 | from ...utils import compute_acceptance_len, compute_acceptance_rate |
| 13 | |
| 14 | REGRESSION_TOLERANCE = 0.95 |
| 15 | |
| 16 | |
| 17 | @dataclass(frozen=True) |
| 18 | class DSparkCorrectnessConfig: |
| 19 | model: str |
| 20 | draft_model: str |
| 21 | reference_accuracy: float |
| 22 | reference_acceptance_rate: float |
| 23 | reference_acceptance_len: float |
| 24 | num_speculative_tokens: int = 7 |
| 25 | max_model_len: int = 4096 |
| 26 | max_num_seqs: int | None = None |
| 27 | num_questions: int = 1319 |
| 28 | max_tokens: int = 256 |
| 29 | use_chat_completions: bool = False |
| 30 | chat_template_kwargs: dict[str, object] | None = None |
| 31 | attention_backend: str | None = None |
| 32 | gpu_memory_utilization: float = 0.92 |
| 33 | enforce_eager: bool = False |
| 34 | disable_flashinfer_sampler: bool = False |
| 35 | language_model_only: bool = False |
| 36 | |
| 37 | |
| 38 | # References from 12 full GSM8K runs at temperature 1.0: accuracy 0.782-0.814, |
| 39 | # acceptance rate 0.418-0.434, acceptance length 3.928-4.037. |
| 40 | QWEN3_DSPARK_DEEPSPEC = DSparkCorrectnessConfig( |
| 41 | model="Qwen/Qwen3-4B-FP8", |
| 42 | draft_model="deepseek-ai/dspark_qwen3_4b_block7", |
| 43 | reference_accuracy=0.801, |
| 44 | reference_acceptance_rate=0.428, |
| 45 | reference_acceptance_len=3.994, |
| 46 | attention_backend="FLASH_ATTN", |
| 47 | ) |
| 48 | |
| 49 | # References from five 200-question runs at temperature 1.0: accuracy |
| 50 | # 0.900-0.955, acceptance rate 0.578-0.595, acceptance length 5.044-5.167. |
| 51 | GEMMA4_DSPARK_DEEPSPEC = DSparkCorrectnessConfig( |
| 52 | model="RedHatAI/gemma-4-12B-it-NVFP4", |
| 53 | draft_model="deepseek-ai/dspark_gemma4_12b_block7", |
| 54 | reference_accuracy=0.92, |
| 55 | reference_acceptance_rate=0.58, |
| 56 | reference_acceptance_len=5.0, |
| 57 | max_num_seqs=32, |
| 58 | num_questions=200, |
| 59 | use_chat_completions=True, |
| 60 | gpu_memory_utilization=0.85, |
| 61 | enforce_eager=True, |
| 62 | disable_flashinfer_sampler=True, |
| 63 | language_model_only=True, |
| 64 | ) |
| 65 | |
| 66 | # Reference from 200 GSM8K questions at temperature 1.0. |
| 67 | QWEN3_6_DSPARK_SPECULATORS = DSparkCorrectnessConfig( |
| 68 | model="RedHatAI/Qwen3.6-35B-A3B-NVFP4", |
| 69 | draft_model="RedHatAI/Qwen3.6-35B-A3B-speculator.dspark", |
| 70 | reference_accuracy=0.85, |
| 71 | reference_acceptance_rate=0.41, |
| 72 | reference_acceptance_len=4.25, |
| 73 | num_speculative_tokens=8, |
| 74 | max_num_seqs=32, |
| 75 | num_questions=200, |
| 76 | use_chat_completions=True, |
| 77 | chat_template_kwargs={"enable_thinking": False}, |
| 78 | gpu_memory_utilization=0.85, |
| 79 | language_model_only=True, |
| 80 | ) |
| 81 | |
| 82 | |
| 83 | @pytest.mark.parametrize( |
| 84 | "config", |
| 85 | [ |
| 86 | pytest.param(QWEN3_DSPARK_DEEPSPEC, id="qwen3-deepspec"), |
| 87 | pytest.param(GEMMA4_DSPARK_DEEPSPEC, id="gemma4-deepspec"), |
| 88 | pytest.param(QWEN3_6_DSPARK_SPECULATORS, id="qwen3.6-speculators"), |
| 89 | ], |
| 90 | ) |
| 91 | def test_dspark_correctness_and_acceptance_rate( |
| 92 | monkeypatch: pytest.MonkeyPatch, |
| 93 | config: DSparkCorrectnessConfig, |
| 94 | vllm_runner, |
| 95 | ): |
| 96 | """Guard GSM8K accuracy and acceptance metrics for DSpark models.""" |
| 97 | if config.disable_flashinfer_sampler: |
| 98 | monkeypatch.setenv("VLLM_USE_FLASHINFER_SAMPLER", "0") |
| 99 | |
| 100 | speculative_config = { |
| 101 | "method": "dspark", |
| 102 | "model": config.draft_model, |
| 103 | "num_speculative_tokens": config.num_speculative_tokens, |
| 104 | "draft_sample_method": "probabilistic", |
| 105 | } |
| 106 | if config.attention_backend is not None and current_platform.is_cuda(): |
| 107 | speculative_config["attention_backend"] = config.attention_backend |
| 108 | |
| 109 | runner_config = { |
| 110 | "block_size": None, |
| 111 | "trust_remote_code": True, |
| 112 | "speculative_config": speculative_config, |
| 113 | "max_model_len": config.max_model_len, |
| 114 | "gpu_memory_utilization": config.gpu_memory_utilization, |
| 115 | "enforce_eager": config.enforce_eager, |
| 116 | "enable_chunked_prefill": None, |
| 117 | "enable_prefix_caching": False, |
| 118 | "disable_log_stats": False, |
| 119 | "compilation_config": CompilationConfig(), |
| 120 | } |
| 121 | if config.max_num_seqs is not None: |
| 122 | runner_config["max_num_seqs"] = config.max_num_seqs |
| 123 | if config.language_model_only: |
| 124 | runner_config["language_model_only"] = True |
| 125 | |
| 126 | with vllm_runner(config.model, **runner_config) as spec_runner: |
| 127 | spec_llm = spec_runner.llm |
| 128 | results = evaluate_gsm8k_offline( |
| 129 | spec_llm, |
| 130 | num_questions=config.num_questions, |
| 131 | max_tokens=config.max_tokens, |
| 132 | temperature=1.0, |
| 133 | use_chat_completions=config.use_chat_completions, |
| 134 | chat_template_kwargs=config.chat_template_kwargs, |
| 135 | ) |
| 136 | accuracy = results["accuracy"] |
| 137 | metrics = spec_llm.get_metrics() |
| 138 | acceptance_rate = compute_acceptance_rate(metrics) |
| 139 | acceptance_len = compute_acceptance_len(metrics) |
| 140 | context = f"DSpark target={config.model}, draft={config.draft_model}" |
| 141 | metrics_summary = ( |
| 142 | f"gsm8k_accuracy={accuracy:.3f}, " |
| 143 | f"acceptance_rate={acceptance_rate:.3f}, " |
| 144 | f"acceptance_len={acceptance_len:.3f}" |
| 145 | ) |
| 146 | print(f"{context}: {metrics_summary}") |
| 147 | |
| 148 | assert accuracy >= config.reference_accuracy * REGRESSION_TOLERANCE, ( |
| 149 | f"{context}: {metrics_summary}" |
| 150 | ) |
| 151 | assert ( |
| 152 | acceptance_rate >= config.reference_acceptance_rate * REGRESSION_TOLERANCE |
| 153 | ), f"{context}: {metrics_summary}" |
| 154 | assert ( |
| 155 | acceptance_len >= config.reference_acceptance_len * REGRESSION_TOLERANCE |
| 156 | ), f"{context}: {metrics_summary}" |