| 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 tests.utils import single_gpu_only |
| 10 | from vllm.config import CompilationConfig |
| 11 | |
| 12 | from ...utils import compute_acceptance_len |
| 13 | from ..utils import run_acceptance_length_eval |
| 14 | |
| 15 | |
| 16 | @dataclass(frozen=True) |
| 17 | class DFlashCorrectnessConfig: |
| 18 | model: str |
| 19 | draft_model: str |
| 20 | expected_accuracy: float |
| 21 | expected_acceptance_len: float |
| 22 | num_speculative_tokens: int = 16 |
| 23 | max_model_len: int = 4096 |
| 24 | max_num_seqs: int = 128 |
| 25 | num_questions: int = 1319 |
| 26 | use_chat_completions: bool = False |
| 27 | enforce_eager: bool = False |
| 28 | disable_flashinfer_sampler: bool = False |
| 29 | language_model_only: bool = False |
| 30 | chat_template_kwargs: dict[str, object] | None = None |
| 31 | |
| 32 | |
| 33 | QWEN3_DFLASH = DFlashCorrectnessConfig( |
| 34 | model="Qwen/Qwen3-8B", |
| 35 | draft_model="z-lab/Qwen3-8B-DFlash-b16", |
| 36 | expected_accuracy=0.8, |
| 37 | expected_acceptance_len=3.5, |
| 38 | ) |
| 39 | |
| 40 | QWEN3_8_DFLASH2_NVFP4 = DFlashCorrectnessConfig( |
| 41 | model="nvidia/Qwen3.8-27B-NVFP4", |
| 42 | draft_model="z-lab/Qwen3.8-27B-DFlash2", |
| 43 | expected_accuracy=0.90, |
| 44 | expected_acceptance_len=6.14 * 0.95, |
| 45 | num_speculative_tokens=7, |
| 46 | max_num_seqs=32, |
| 47 | use_chat_completions=True, |
| 48 | language_model_only=True, |
| 49 | chat_template_kwargs={"enable_thinking": False}, |
| 50 | ) |
| 51 | |
| 52 | LAGUNA_DFLASH_NVFP4 = DFlashCorrectnessConfig( |
| 53 | model="poolside/Laguna-XS-2.1-NVFP4", |
| 54 | draft_model="poolside/Laguna-XS-2.1-DFlash-NVFP4", |
| 55 | expected_accuracy=0.7, # Standard GSM8K sanity floor. |
| 56 | expected_acceptance_len=3.05 * 0.9, |
| 57 | num_speculative_tokens=15, |
| 58 | max_model_len=8192, |
| 59 | max_num_seqs=32, |
| 60 | num_questions=200, |
| 61 | use_chat_completions=True, |
| 62 | enforce_eager=True, |
| 63 | disable_flashinfer_sampler=True, |
| 64 | ) |
| 65 | |
| 66 | |
| 67 | @pytest.mark.parametrize("use_mrv2", [False, True]) |
| 68 | def test_dflash_reference_acceptance_lengths( |
| 69 | monkeypatch: pytest.MonkeyPatch, |
| 70 | use_mrv2: bool, |
| 71 | vllm_runner, |
| 72 | ): |
| 73 | run_acceptance_length_eval( |
| 74 | monkeypatch, |
| 75 | vllm_runner, |
| 76 | spec_config={ |
| 77 | "model": QWEN3_DFLASH.model, |
| 78 | "trust_remote_code": True, |
| 79 | "speculative_config": { |
| 80 | "method": "dflash", |
| 81 | "model": QWEN3_DFLASH.draft_model, |
| 82 | "num_speculative_tokens": QWEN3_DFLASH.num_speculative_tokens, |
| 83 | "max_model_len": 32768, |
| 84 | }, |
| 85 | "max_model_len": 32768, |
| 86 | "max_num_seqs": 128, |
| 87 | "gpu_memory_utilization": 0.85, |
| 88 | "enforce_eager": False, |
| 89 | "disable_log_stats": False, |
| 90 | }, |
| 91 | # Table 1 in https://arxiv.org/pdf/2602.06036. |
| 92 | expected_acceptance_lengths={ |
| 93 | "mt-bench": 4.24, |
| 94 | "humaneval": 6.50, |
| 95 | "gsm8k": 6.54 * 0.975, |
| 96 | }, |
| 97 | chat_template_kwargs={"enable_thinking": False}, |
| 98 | use_mrv2=use_mrv2, |
| 99 | ) |
| 100 | |
| 101 | |
| 102 | @single_gpu_only |
| 103 | @pytest.mark.parametrize( |
| 104 | ("config", "use_mrv2"), |
| 105 | [ |
| 106 | pytest.param( |
| 107 | QWEN3_DFLASH, |
| 108 | False, |
| 109 | id="qwen3-mrv1", |
| 110 | ), |
| 111 | pytest.param( |
| 112 | QWEN3_DFLASH, |
| 113 | True, |
| 114 | id="qwen3-mrv2", |
| 115 | ), |
| 116 | pytest.param( |
| 117 | LAGUNA_DFLASH_NVFP4, |
| 118 | True, |
| 119 | id="laguna-nvfp4-mrv2", |
| 120 | ), |
| 121 | pytest.param( |
| 122 | QWEN3_8_DFLASH2_NVFP4, |
| 123 | True, |
| 124 | id="qwen3.8-dflash2-nvfp4-mrv2", |
| 125 | ), |
| 126 | ], |
| 127 | ) |
| 128 | def test_dflash_correctness( |
| 129 | monkeypatch: pytest.MonkeyPatch, |
| 130 | config: DFlashCorrectnessConfig, |
| 131 | use_mrv2: bool, |
| 132 | vllm_runner, |
| 133 | ): |
| 134 | """Guard GSM8K accuracy and batched acceptance length for DFlash models.""" |
| 135 | monkeypatch.setenv("VLLM_USE_V2_MODEL_RUNNER", "1" if use_mrv2 else "0") |
| 136 | if config.disable_flashinfer_sampler: |
| 137 | monkeypatch.setenv("VLLM_USE_FLASHINFER_SAMPLER", "0") |
| 138 | |
| 139 | with vllm_runner( |
| 140 | config.model, |
| 141 | block_size=None, |
| 142 | trust_remote_code=True, |
| 143 | speculative_config={ |
| 144 | "method": "dflash", |
| 145 | "model": config.draft_model, |
| 146 | "num_speculative_tokens": config.num_speculative_tokens, |
| 147 | "max_model_len": config.max_model_len, |
| 148 | }, |
| 149 | max_model_len=config.max_model_len, |
| 150 | max_num_seqs=config.max_num_seqs, |
| 151 | gpu_memory_utilization=0.85, |
| 152 | enforce_eager=config.enforce_eager, |
| 153 | disable_log_stats=False, |
| 154 | enable_chunked_prefill=None, |
| 155 | compilation_config=CompilationConfig(), |
| 156 | language_model_only=config.language_model_only, |
| 157 | ) as spec_runner: |
| 158 | spec_llm = spec_runner.llm |
| 159 | results = evaluate_gsm8k_offline( |
| 160 | spec_llm, |
| 161 | num_questions=config.num_questions, |
| 162 | use_chat_completions=config.use_chat_completions, |
| 163 | chat_template_kwargs=config.chat_template_kwargs, |
| 164 | ) |
| 165 | accuracy = results["accuracy"] |
| 166 | acceptance_len = compute_acceptance_len(spec_llm.get_metrics()) |
| 167 | context = ( |
| 168 | f"DFlash target={config.model}, draft={config.draft_model}, MRV2={use_mrv2}" |
| 169 | ) |
| 170 | print( |
| 171 | f"{context}: GSM8K accuracy={accuracy:.3f}, " |
| 172 | f"acceptance_len={acceptance_len:.2f}" |
| 173 | ) |
| 174 | |
| 175 | assert accuracy >= config.expected_accuracy, ( |
| 176 | f"{context}: GSM8K accuracy {accuracy:.3f} is below " |
| 177 | f"{config.expected_accuracy:.3f}; acceptance_len={acceptance_len:.3f}" |
| 178 | ) |
| 179 | assert acceptance_len >= config.expected_acceptance_len, ( |
| 180 | f"{context}: acceptance_len {acceptance_len:.3f} is below " |
| 181 | f"{config.expected_acceptance_len:.3f}; accuracy={accuracy:.3f}" |
| 182 | ) |