main
py 127 lines 3.92 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Config-only DFlash behavior.
4
5 ``dflash_has_any_non_causal`` decides pre-build whether the draft needs a
6 non-causal-capable backend, so its branch table (explicit override, SWA-derived
7 per-layer causality, and the no-``layer_types`` fallback) is worth pinning.
8 """
9
10 from types import SimpleNamespace
11
12 import pytest
13
14 from vllm.model_executor.models.qwen3_dflash import (
15 _dflash_layer_causal,
16 _get_dflash_fc_input_size,
17 dflash_has_any_non_causal,
18 )
19 from vllm.v1.worker.gpu.spec_decode.eagle.eagle3_utils import (
20 get_eagle3_aux_layers_from_config,
21 )
22
23
24 def _config(num_hidden_layers, layer_types=None, causal_override=None, is_causal=None):
25 dflash_config = None if causal_override is None else {"causal": causal_override}
26 return SimpleNamespace(
27 num_hidden_layers=num_hidden_layers,
28 layer_types=layer_types,
29 dflash_config=dflash_config,
30 is_causal=is_causal,
31 )
32
33
34 @pytest.mark.parametrize(
35 "config,expected",
36 [
37 # Override forces causality on every layer, ignoring layer_types.
38 (_config(2, layer_types=["full_attention"] * 2, causal_override=True), False),
39 # Override forces non-causal on every layer.
40 (
41 _config(2, layer_types=["sliding_attention"] * 2, causal_override=False),
42 True,
43 ),
44 # DFlash2 stores the explicit attention semantics at the top level.
45 (
46 _config(
47 2,
48 layer_types=["sliding_attention"] * 2,
49 is_causal=False,
50 ),
51 True,
52 ),
53 (
54 _config(2, layer_types=["full_attention"] * 2, is_causal=True),
55 False,
56 ),
57 # SWA-derived: full-attention layers are non-causal.
58 (_config(2, layer_types=["sliding_attention", "full_attention"]), True),
59 # SWA-derived: all-sliding is fully causal.
60 (_config(2, layer_types=["sliding_attention", "sliding_attention"]), False),
61 # No layer_types -> non-causal fallback.
62 (_config(2, layer_types=None), True),
63 (_config(2, layer_types=[]), True),
64 ],
65 )
66 def test_dflash_has_any_non_causal(config, expected):
67 assert dflash_has_any_non_causal(config) is expected
68
69
70 def test_dflash_layer_causal_is_per_layer():
71 config = _config(2, layer_types=["sliding_attention", "full_attention"])
72 assert _dflash_layer_causal(config, 0) is True
73 assert _dflash_layer_causal(config, 1) is False
74
75
76 def test_dflash_layer_causal_honors_top_level_override():
77 config = _config(
78 2,
79 layer_types=["sliding_attention", "full_attention"],
80 is_causal=False,
81 )
82 assert _dflash_layer_causal(config, 0) is False
83 assert _dflash_layer_causal(config, 1) is False
84
85
86 def _vllm_config(**draft_config):
87 config = SimpleNamespace(**draft_config)
88 return SimpleNamespace(
89 speculative_config=SimpleNamespace(
90 draft_model_config=SimpleNamespace(hf_config=config)
91 )
92 )
93
94
95 def test_dflash_fc_uses_aux_layer_count():
96 vllm_config = _vllm_config(
97 num_hidden_layers=5,
98 hidden_size=4096,
99 target_hidden_size=None,
100 target_layer_ids=[1, 17, 32],
101 )
102
103 assert _get_dflash_fc_input_size(vllm_config) == 3 * 4096
104
105
106 def test_dflash_target_layers_map_to_post_layer_boundaries():
107 vllm_config = _vllm_config(
108 dflash_config={"target_layer_ids": [0, 16, 31]},
109 )
110
111 assert get_eagle3_aux_layers_from_config(vllm_config.speculative_config) == (
112 1,
113 17,
114 32,
115 )
116
117
118 @pytest.mark.parametrize("config_name", ["dflash_config", "eagle_config"])
119 def test_eagle_aux_layers_preserves_legacy_layer_ids(config_name):
120 layer_ids = [1, 17, 32]
121 vllm_config = _vllm_config(
122 **{config_name: {"layer_ids": layer_ids}},
123 )
124
125 assert get_eagle3_aux_layers_from_config(vllm_config.speculative_config) == tuple(
126 layer_ids
127 )