| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import math |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from tests.utils import multi_gpu_marks |
| 9 | from vllm import SamplingParams |
| 10 | from vllm.config import CompilationConfig |
| 11 | from vllm.config.compilation import CompilationMode, CUDAGraphMode |
| 12 | from vllm.platforms import current_platform |
| 13 | from vllm.v1.metrics.reader import Counter |
| 14 | |
| 15 | pytestmark = pytest.mark.skipif( |
| 16 | not current_platform.is_rocm(), reason="ROCm-specific tests" |
| 17 | ) |
| 18 | |
| 19 | |
| 20 | def _small_hyv4_config(config): |
| 21 | # Apply to both target and MTP draft while retaining MLA/indexer dimensions. |
| 22 | config.update( |
| 23 | { |
| 24 | "hidden_size": 512, |
| 25 | "num_hidden_layers": 2, |
| 26 | "num_attention_heads": 64, |
| 27 | "q_lora_rank": 128, |
| 28 | "intermediate_size": 512, |
| 29 | "moe_intermediate_size": 256, |
| 30 | "n_routed_experts": 4, |
| 31 | "num_experts_per_tok": 2, |
| 32 | "n_shared_experts": 1, |
| 33 | "enable_ihc": True, |
| 34 | "index_n_heads": 32, |
| 35 | "index_topk": 2048, |
| 36 | "layer_types": ["deepseek_sparse_attention"] * 2, |
| 37 | "indexer_types": ["full", "shared"], |
| 38 | "num_nextn_predict_layers": 1, |
| 39 | "vocab_size": 256, |
| 40 | "bos_token_id": 1, |
| 41 | "eos_token_id": 2, |
| 42 | "pad_token_id": 0, |
| 43 | } |
| 44 | ) |
| 45 | return config |
| 46 | |
| 47 | |
| 48 | def _num_captured_graphs(_worker): |
| 49 | from vllm.compilation.counter import compilation_counter |
| 50 | |
| 51 | return compilation_counter.num_cudagraph_captured |
| 52 | |
| 53 | |
| 54 | def _piecewise_compile_state(worker): |
| 55 | from vllm.compilation.counter import compilation_counter |
| 56 | |
| 57 | # `cudagraph_mode` is returned by name: the enum does not survive the |
| 58 | # collective_rpc round trip, it arrives as its bare value. |
| 59 | return ( |
| 60 | compilation_counter.num_models_seen, |
| 61 | compilation_counter.num_cudagraph_captured, |
| 62 | worker.vllm_config.compilation_config.cudagraph_mode.name, |
| 63 | ) |
| 64 | |
| 65 | |
| 66 | @pytest.mark.parametrize( |
| 67 | "tp,enforce_eager,mtp,dtype,kv_cache_dtype", |
| 68 | [ |
| 69 | pytest.param(1, True, False, "bfloat16", "auto", id="eager-tp1"), |
| 70 | pytest.param(1, True, False, "float16", "auto", id="eager-fp16"), |
| 71 | pytest.param(1, True, False, "bfloat16", "fp8", id="eager-fp8-kv"), |
| 72 | pytest.param( |
| 73 | 2, |
| 74 | False, |
| 75 | False, |
| 76 | "bfloat16", |
| 77 | "auto", |
| 78 | id="graphs-tp2", |
| 79 | marks=multi_gpu_marks(num_gpus=2), |
| 80 | ), |
| 81 | pytest.param(1, True, True, "bfloat16", "auto", id="eager-mtp"), |
| 82 | ], |
| 83 | ) |
| 84 | def test_hyv4_dummy_generation( |
| 85 | vllm_runner, |
| 86 | enable_pickle, |
| 87 | tp: int, |
| 88 | enforce_eager: bool, |
| 89 | mtp: bool, |
| 90 | dtype: str, |
| 91 | kv_cache_dtype: str, |
| 92 | ) -> None: |
| 93 | """Exercise prefill, decode, and batch consistency with dummy HY weights.""" |
| 94 | with vllm_runner( |
| 95 | "tencent/Hy4-preview", |
| 96 | trust_remote_code=True, |
| 97 | skip_tokenizer_init=True, |
| 98 | load_format="dummy", |
| 99 | disable_log_stats=not mtp, |
| 100 | dtype=dtype, |
| 101 | kv_cache_dtype=kv_cache_dtype, |
| 102 | hf_overrides=_small_hyv4_config, |
| 103 | max_model_len=256, |
| 104 | max_num_batched_tokens=128, |
| 105 | max_num_seqs=8, |
| 106 | enable_chunked_prefill=True, |
| 107 | enable_prefix_caching=False, |
| 108 | kv_cache_memory_bytes=128 * 1024 * 1024, |
| 109 | enforce_eager=enforce_eager, |
| 110 | tensor_parallel_size=tp, |
| 111 | speculative_config={"method": "mtp", "num_speculative_tokens": 2} |
| 112 | if mtp |
| 113 | else None, |
| 114 | seed=17, |
| 115 | # Exercise supported decode graphs independently of default graph policy. |
| 116 | compilation_config=CompilationConfig( |
| 117 | mode=CompilationMode.NONE, |
| 118 | cudagraph_mode=CUDAGraphMode.NONE |
| 119 | if enforce_eager |
| 120 | else CUDAGraphMode.FULL_DECODE_ONLY, |
| 121 | cudagraph_capture_sizes=[] if enforce_eager else [1, 2, 4], |
| 122 | ), |
| 123 | ) as model: |
| 124 | if not enforce_eager: |
| 125 | assert model.collective_rpc(_num_captured_graphs) == [3] * tp |
| 126 | prompts = [ |
| 127 | {"prompt_token_ids": [11, 12, 13, 14]}, |
| 128 | {"prompt_token_ids": list(range(20, 84))}, |
| 129 | ] |
| 130 | params = SamplingParams( |
| 131 | temperature=0, max_tokens=4, ignore_eos=True, logprobs=1 |
| 132 | ) |
| 133 | outputs = model.llm.generate(prompts, params) |
| 134 | assert len(outputs) == len(prompts) |
| 135 | for prompt, output in zip(prompts, outputs): |
| 136 | completion = output.outputs[0] |
| 137 | assert len(completion.token_ids) == 4 |
| 138 | assert completion.cumulative_logprob is not None |
| 139 | assert math.isfinite(completion.cumulative_logprob) |
| 140 | single = model.llm.generate([prompt], params)[0].outputs[0] |
| 141 | assert single.token_ids == completion.token_ids |
| 142 | |
| 143 | if mtp: |
| 144 | assert any( |
| 145 | isinstance(metric, Counter) |
| 146 | and metric.name == "vllm:spec_decode_num_drafts" |
| 147 | and metric.value > 0 |
| 148 | for metric in model.llm.get_metrics() |
| 149 | ), "MTP drafter did not run" |
| 150 | |
| 151 | |
| 152 | def test_hyv4_backbone_reaches_the_compiler(vllm_runner, enable_pickle) -> None: |
| 153 | """Guard against the backbone losing `@support_torch_compile`. |
| 154 | |
| 155 | An undecorated backbone is not a hard failure: vLLM downgrades |
| 156 | `cudagraph_mode` to NONE and serves the whole model eager, which on TP8 |
| 157 | gfx942 cost ~4x decode latency. |
| 158 | """ |
| 159 | with vllm_runner( |
| 160 | "tencent/Hy4-preview", |
| 161 | trust_remote_code=True, |
| 162 | skip_tokenizer_init=True, |
| 163 | load_format="dummy", |
| 164 | dtype="bfloat16", |
| 165 | hf_overrides=_small_hyv4_config, |
| 166 | max_model_len=256, |
| 167 | max_num_batched_tokens=128, |
| 168 | max_num_seqs=8, |
| 169 | enable_chunked_prefill=True, |
| 170 | enable_prefix_caching=False, |
| 171 | kv_cache_memory_bytes=128 * 1024 * 1024, |
| 172 | enforce_eager=False, |
| 173 | seed=17, |
| 174 | compilation_config=CompilationConfig( |
| 175 | mode=CompilationMode.VLLM_COMPILE, |
| 176 | cudagraph_mode=CUDAGraphMode.PIECEWISE, |
| 177 | cudagraph_capture_sizes=[1, 2, 4], |
| 178 | ), |
| 179 | ) as model: |
| 180 | for models_seen, captured, mode in model.collective_rpc( |
| 181 | _piecewise_compile_state |
| 182 | ): |
| 183 | assert models_seen > 0, "backbone was never handed to the compiler" |
| 184 | assert mode == CUDAGraphMode.PIECEWISE.name |
| 185 | assert captured > 0 |