main
py 135 lines 4.81 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import os
4 import random
5 from typing import NamedTuple
6
7 import pytest
8 import torch
9
10 from vllm.platforms import current_platform
11 from vllm.transformers_utils.config import get_config
12 from vllm.transformers_utils.model_arch_config_convertor import (
13 ModelArchConfigConvertorBase,
14 )
15 from vllm.triton_utils import HAS_TRITON
16 from vllm.v1.attention.backends.fa_utils import flash_attn_supports_mla
17
18
19 class DeviceConfig(NamedTuple):
20 available: bool
21 backends: list[str]
22
23
24 # Maps each device to its availability and supported backends.
25 DEVICE_BACKENDS: dict[str, DeviceConfig] = {
26 "cuda": DeviceConfig(
27 available=current_platform.is_cuda()
28 and current_platform.has_device_capability(80),
29 # FlashInfer backend temporarily disabled due to invariant CTA sizes.
30 # See FlashInfer issue #2424
31 backends=["FLASH_ATTN", "TRITON_ATTN", "FLEX_ATTENTION"],
32 ),
33 "xpu": DeviceConfig(
34 available=current_platform.is_xpu() and HAS_TRITON,
35 backends=["TRITON_ATTN"],
36 ),
37 }
38
39 DEFAULT_MODEL = "Qwen/Qwen3-1.7B"
40 TEST_MODEL = os.getenv("VLLM_TEST_MODEL", DEFAULT_MODEL)
41
42 # Override backends for MLA models (MLA only supported on CUDA).
43 if os.getenv("VLLM_TEST_MODEL"):
44 config = get_config(TEST_MODEL, trust_remote_code=False)
45 if ModelArchConfigConvertorBase(config, config.get_text_config()).is_deepseek_mla():
46 DEVICE_BACKENDS["cuda"] = DeviceConfig(
47 available=DEVICE_BACKENDS["cuda"].available,
48 backends=["TRITON_MLA"]
49 + (["FLASH_ATTN_MLA"] if flash_attn_supports_mla() else []),
50 )
51 DEVICE_BACKENDS["xpu"] = DeviceConfig(
52 available=DEVICE_BACKENDS["xpu"].available,
53 backends=[],
54 )
55
56 # Only include backends for devices that are actually available.
57 BACKENDS: list[str] = sorted(
58 {b for cfg in DEVICE_BACKENDS.values() if cfg.available for b in cfg.backends}
59 )
60
61 skip_unsupported = pytest.mark.skipif(
62 not any(cfg.available for cfg in DEVICE_BACKENDS.values()),
63 reason="Requires CUDA >= Ampere (SM80) or Intel XPU with Triton",
64 )
65
66 skip_if_not_cuda = pytest.mark.skipif(
67 not DEVICE_BACKENDS["cuda"].available,
68 reason="Requires CUDA >= Ampere (SM80)",
69 )
70
71
72 def _random_prompt(min_words: int = 1024, max_words: int = 1024 * 2) -> str:
73 # Generate more realistic prompts that will actually produce varied tokens
74 # Use a mix of common English text patterns
75
76 prompt_templates = [
77 # Question-answer style
78 "Question: What is the capital of France?\nAnswer: The capital of France is",
79 "Q: How does photosynthesis work?\nA: Photosynthesis is the process by which",
80 "User: Can you explain quantum mechanics?\nAssistant: Quantum mechanics is",
81 # Story/narrative style
82 "Once upon a time in a distant galaxy, there lived",
83 "The old man walked slowly down the street, remembering",
84 "In the year 2157, humanity finally discovered",
85 # Technical/code style
86 "To implement a binary search tree in Python, first we need to",
87 "The algorithm works by iterating through the array and",
88 "Here's how to optimize database queries using indexing:",
89 # Factual/informative style
90 "The Renaissance was a period in European history that",
91 "Climate change is caused by several factors including",
92 "The human brain contains approximately 86 billion neurons which",
93 # Conversational style
94 "I've been thinking about getting a new laptop because",
95 "Yesterday I went to the store and bought",
96 "My favorite thing about summer is definitely",
97 ]
98
99 # Pick a random template
100 base_prompt = random.choice(prompt_templates)
101
102 if max_words < min_words:
103 max_words = min_words
104 target_words = random.randint(min_words, max_words)
105
106 if target_words > 50:
107 # For longer prompts, repeat context
108 padding_text = (
109 " This is an interesting topic that deserves more explanation. "
110 # TODO: Update to * (target_words // 10) to better align with word ratio
111 * (target_words // 50)
112 )
113 base_prompt = padding_text + base_prompt
114
115 return base_prompt
116
117
118 def _extract_step_logprobs(request_output):
119 if getattr(request_output, "outputs", None):
120 inner = request_output.outputs[0]
121 if hasattr(inner, "logprobs") and inner.logprobs is not None:
122 t = torch.tensor(
123 [
124 inner.logprobs[i][tid].logprob
125 for i, tid in enumerate(inner.token_ids)
126 ],
127 dtype=torch.float32,
128 )
129 return t, inner.token_ids
130
131 return None, None
132
133
134 def is_device_capability_below_90() -> bool:
135 return not current_platform.has_device_capability(90)