| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Tests for the EAGLE draft ``max_position_embeddings`` override (#48894). |
| 4 | |
| 5 | EAGLE drafts share the target's positional space, but some draft |
| 6 | checkpoints (e.g. ``yuhuili/EAGLE3-LLaMA3.1-Instruct-8B``) ship a |
| 7 | ``max_position_embeddings`` (2048) far smaller than the target's context. |
| 8 | That value sizes the draft's rotary ``cos_sin_cache`` while the proposer |
| 9 | feeds positions up to the target's ``max_model_len``, so the cache gather |
| 10 | goes out of bounds — a device-side assert under torch.compile and silent |
| 11 | garbage reads in eager mode. ``SpeculativeConfig`` must raise the draft's |
| 12 | value to the target's ``max_model_len``, with a log, for the eagle/eagle3 |
| 13 | methods only. |
| 14 | """ |
| 15 | |
| 16 | import logging |
| 17 | from typing import Literal |
| 18 | |
| 19 | import pytest |
| 20 | from transformers import PreTrainedConfig |
| 21 | |
| 22 | from vllm.config.model import ModelConfig |
| 23 | from vllm.config.parallel import ParallelConfig |
| 24 | from vllm.config.speculative import SpeculativeConfig |
| 25 | |
| 26 | # All repos are public; only config/tokenizer-config files are fetched. |
| 27 | EAGLE3_DRAFT = "yuhuili/EAGLE3-LLaMA3.1-Instruct-8B" # max_position_embeddings=2048 |
| 28 | LLAMA3_TARGET = "unsloth/Meta-Llama-3.1-8B-Instruct" # max_position_embeddings=131072 |
| 29 | AR_MODEL = "JackFram/llama-68m" # max_position_embeddings=2048 |
| 30 | |
| 31 | _LOGGER = "vllm.config.speculative" |
| 32 | _OVERRIDE_MSG = "Overriding draft model max_position_embeddings" |
| 33 | |
| 34 | |
| 35 | @pytest.fixture |
| 36 | def vllm_caplog(caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch): |
| 37 | """Make caplog see vLLM logger records (vLLM sets propagate=False).""" |
| 38 | monkeypatch.setattr(logging.getLogger("vllm"), "propagate", True) |
| 39 | with caplog.at_level(logging.INFO, logger=_LOGGER): |
| 40 | yield caplog |
| 41 | |
| 42 | |
| 43 | def _override_logged(caplog: pytest.LogCaptureFixture) -> bool: |
| 44 | return any(_OVERRIDE_MSG in record.getMessage() for record in caplog.records) |
| 45 | |
| 46 | |
| 47 | @pytest.mark.cpu_test |
| 48 | def test_override_raises_smaller_value(vllm_caplog: pytest.LogCaptureFixture): |
| 49 | hf_config = PreTrainedConfig(max_position_embeddings=2048) |
| 50 | SpeculativeConfig._maybe_override_draft_max_position_embeddings( |
| 51 | hf_config, target_max_model_len=8192 |
| 52 | ) |
| 53 | assert hf_config.max_position_embeddings == 8192 |
| 54 | assert _override_logged(vllm_caplog) |
| 55 | |
| 56 | |
| 57 | @pytest.mark.cpu_test |
| 58 | def test_override_keeps_sufficient_value(vllm_caplog: pytest.LogCaptureFixture): |
| 59 | hf_config = PreTrainedConfig(max_position_embeddings=8192) |
| 60 | SpeculativeConfig._maybe_override_draft_max_position_embeddings( |
| 61 | hf_config, target_max_model_len=8192 |
| 62 | ) |
| 63 | assert hf_config.max_position_embeddings == 8192 |
| 64 | assert not _override_logged(vllm_caplog) |
| 65 | |
| 66 | |
| 67 | @pytest.mark.cpu_test |
| 68 | def test_override_ignores_missing_attribute(vllm_caplog: pytest.LogCaptureFixture): |
| 69 | hf_config = PreTrainedConfig() |
| 70 | hf_config.__dict__.pop("max_position_embeddings", None) |
| 71 | SpeculativeConfig._maybe_override_draft_max_position_embeddings( |
| 72 | hf_config, target_max_model_len=8192 |
| 73 | ) |
| 74 | assert not hasattr(hf_config, "max_position_embeddings") |
| 75 | assert not _override_logged(vllm_caplog) |
| 76 | |
| 77 | |
| 78 | @pytest.mark.cpu_test |
| 79 | @pytest.mark.parametrize("method", ["eagle", "eagle3"]) |
| 80 | def test_eagle_draft_inherits_target_max_model_len( |
| 81 | method: Literal["eagle", "eagle3"], vllm_caplog: pytest.LogCaptureFixture |
| 82 | ): |
| 83 | target_model_config = ModelConfig(LLAMA3_TARGET) |
| 84 | assert target_model_config.max_model_len > 2048 |
| 85 | speculative_config = SpeculativeConfig( |
| 86 | target_model_config=target_model_config, |
| 87 | target_parallel_config=ParallelConfig(), |
| 88 | model=EAGLE3_DRAFT, |
| 89 | method=method, |
| 90 | num_speculative_tokens=3, |
| 91 | ) |
| 92 | draft_hf_config = speculative_config.draft_model_config.hf_config |
| 93 | assert draft_hf_config.max_position_embeddings == target_model_config.max_model_len |
| 94 | assert _override_logged(vllm_caplog) |
| 95 | |
| 96 | |
| 97 | @pytest.mark.cpu_test |
| 98 | def test_independent_draft_model_keeps_its_own_limit( |
| 99 | vllm_caplog: pytest.LogCaptureFixture, |
| 100 | ): |
| 101 | """An independent AR draft may genuinely have a smaller context than the |
| 102 | target; its max_position_embeddings must not be resized.""" |
| 103 | target_model_config = ModelConfig( |
| 104 | AR_MODEL, hf_overrides={"max_position_embeddings": 8192} |
| 105 | ) |
| 106 | assert target_model_config.max_model_len == 8192 |
| 107 | speculative_config = SpeculativeConfig( |
| 108 | target_model_config=target_model_config, |
| 109 | target_parallel_config=ParallelConfig(), |
| 110 | model=AR_MODEL, |
| 111 | method="draft_model", |
| 112 | num_speculative_tokens=3, |
| 113 | ) |
| 114 | draft_hf_config = speculative_config.draft_model_config.hf_config |
| 115 | assert draft_hf_config.max_position_embeddings == 2048 |
| 116 | assert not _override_logged(vllm_caplog) |