Add vLLM v0.18.1 source tree with KV transfer abort fix
third_party/vllm/ now tracked in git for direct patch management.
Based on vLLM v0.18.1 release with one patch applied:
vllm/v1/core/sched/scheduler.py:
Replace fatal assert with graceful skip when KV transfer callback
arrives for an already-aborted request during PD disaggregated serving.
Future vLLM modifications should be made directly in third_party/vllm/
and committed normally. The patches/ directory is kept as documentation
of what changed from upstream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
70
third_party/vllm/tools/pre_commit/check_boolean_context_manager.py
vendored
Normal file
70
third_party/vllm/tools/pre_commit/check_boolean_context_manager.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Lint: detect `with a() and b():` (boolean op in with-statement context).
|
||||
|
||||
Using `and`/`or` to combine context managers is almost always a bug:
|
||||
|
||||
with ctx_a() and ctx_b(): # BUG: only ctx_b is entered
|
||||
with ctx_a() or ctx_b(): # BUG: only ctx_a is entered
|
||||
|
||||
The correct way to combine context managers is:
|
||||
|
||||
with ctx_a(), ctx_b(): # comma-separated
|
||||
with (ctx_a(), ctx_b()): # parenthesized (Python 3.10+)
|
||||
with contextlib.ExitStack() ... # ExitStack
|
||||
"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
|
||||
|
||||
def check_file(filepath: str) -> list[str]:
|
||||
try:
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
source = f.read()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return []
|
||||
|
||||
try:
|
||||
tree = ast.parse(source, filename=filepath)
|
||||
except SyntaxError:
|
||||
return []
|
||||
|
||||
violations = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, (ast.With, ast.AsyncWith)):
|
||||
for item in node.items:
|
||||
if isinstance(item.context_expr, ast.BoolOp):
|
||||
op = "and" if isinstance(item.context_expr.op, ast.And) else "or"
|
||||
violations.append(
|
||||
f"{filepath}:{item.context_expr.lineno}: "
|
||||
f"boolean `{op}` used to combine context managers "
|
||||
f"in `with` statement — use a comma instead"
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: check_boolean_context_manager.py <file> ...", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
all_violations = []
|
||||
for filepath in sys.argv[1:]:
|
||||
all_violations.extend(check_file(filepath))
|
||||
|
||||
if all_violations:
|
||||
print(
|
||||
"❌ Boolean operator used to combine context managers in `with` "
|
||||
"statement.\n"
|
||||
" `with a() and b():` only enters `b()` as a context manager.\n"
|
||||
" Use `with a(), b():` or `with (a(), b()):` instead.\n"
|
||||
)
|
||||
for v in all_violations:
|
||||
print(f" {v}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
144
third_party/vllm/tools/pre_commit/check_forbidden_imports.py
vendored
Normal file
144
third_party/vllm/tools/pre_commit/check_forbidden_imports.py
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
import regex as re
|
||||
|
||||
|
||||
@dataclass
|
||||
class ForbiddenImport:
|
||||
pattern: str
|
||||
tip: str
|
||||
allowed_pattern: re.Pattern = re.compile(r"^$") # matches nothing by default
|
||||
allowed_files: set[str] = field(default_factory=set)
|
||||
|
||||
|
||||
CHECK_IMPORTS = {
|
||||
"pickle/cloudpickle": ForbiddenImport(
|
||||
pattern=(
|
||||
r"^\s*(import\s+(pickle|cloudpickle)(\s|$|\sas)"
|
||||
r"|from\s+(pickle|cloudpickle)\s+import\b)"
|
||||
),
|
||||
tip=(
|
||||
"Avoid using pickle or cloudpickle or add this file to "
|
||||
"tools/pre_commit/check_forbidden_imports.py."
|
||||
),
|
||||
allowed_files={
|
||||
# pickle
|
||||
"vllm/multimodal/hasher.py",
|
||||
"vllm/transformers_utils/config.py",
|
||||
"vllm/model_executor/models/registry.py",
|
||||
"vllm/compilation/caching.py",
|
||||
"vllm/compilation/piecewise_backend.py",
|
||||
"vllm/distributed/utils.py",
|
||||
"vllm/distributed/parallel_state.py",
|
||||
"vllm/distributed/device_communicators/all_reduce_utils.py",
|
||||
"vllm/distributed/device_communicators/shm_broadcast.py",
|
||||
"vllm/distributed/device_communicators/shm_object_storage.py",
|
||||
"vllm/distributed/weight_transfer/ipc_engine.py",
|
||||
"tests/distributed/test_weight_transfer.py",
|
||||
"vllm/utils/hashing.py",
|
||||
"tests/multimodal/media/test_base.py",
|
||||
"tests/tokenizers_/test_hf.py",
|
||||
"tests/utils_/test_hashing.py",
|
||||
"tests/compile/test_aot_compile.py",
|
||||
"benchmarks/kernels/graph_machete_bench.py",
|
||||
"benchmarks/kernels/benchmark_lora.py",
|
||||
"benchmarks/kernels/benchmark_machete.py",
|
||||
"benchmarks/fused_kernels/layernorm_rms_benchmarks.py",
|
||||
"benchmarks/cutlass_benchmarks/w8a8_benchmarks.py",
|
||||
"benchmarks/cutlass_benchmarks/sparse_benchmarks.py",
|
||||
# cloudpickle
|
||||
"vllm/v1/executor/multiproc_executor.py",
|
||||
"vllm/v1/executor/ray_executor.py",
|
||||
"vllm/entrypoints/llm.py",
|
||||
"tests/utils.py",
|
||||
# pickle and cloudpickle
|
||||
"vllm/v1/serial_utils.py",
|
||||
},
|
||||
),
|
||||
"re": ForbiddenImport(
|
||||
pattern=r"^\s*(?:import\s+re(?:$|\s|,)|from\s+re\s+import)",
|
||||
tip="Replace 'import re' with 'import regex as re' or 'import regex'.",
|
||||
allowed_pattern=re.compile(r"^\s*import\s+regex(\s*|\s+as\s+re\s*)$"),
|
||||
allowed_files={"setup.py"},
|
||||
),
|
||||
"triton": ForbiddenImport(
|
||||
pattern=r"^(from|import)\s+triton(\s|\.|$)",
|
||||
tip="Use 'from vllm.triton_utils import triton' instead.",
|
||||
allowed_pattern=re.compile(
|
||||
"from vllm.triton_utils import (triton|tl|tl, triton)"
|
||||
),
|
||||
allowed_files={"vllm/triton_utils/importing.py"},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def check_file(path: str) -> int:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
return_code = 0
|
||||
# Check all patterns in the whole file
|
||||
for import_name, forbidden_import in CHECK_IMPORTS.items():
|
||||
# Skip files that are allowed for this import
|
||||
if path in forbidden_import.allowed_files:
|
||||
continue
|
||||
# Search for forbidden imports
|
||||
for match in re.finditer(forbidden_import.pattern, content, re.MULTILINE):
|
||||
# Check if it's allowed
|
||||
if forbidden_import.allowed_pattern.match(match.group()):
|
||||
continue
|
||||
# Calculate line number from match position
|
||||
line_num = content[: match.start() + 1].count("\n") + 1
|
||||
print(
|
||||
f"{path}:{line_num}: "
|
||||
"\033[91merror:\033[0m " # red color
|
||||
f"Found forbidden import: {import_name}. {forbidden_import.tip}"
|
||||
)
|
||||
return_code = 1
|
||||
return return_code
|
||||
|
||||
|
||||
def main():
|
||||
returncode = 0
|
||||
for path in sys.argv[1:]:
|
||||
returncode |= check_file(path)
|
||||
return returncode
|
||||
|
||||
|
||||
def test_regex():
|
||||
test_cases = [
|
||||
# Should match
|
||||
("import pickle", True),
|
||||
("import cloudpickle", True),
|
||||
("import pickle as pkl", True),
|
||||
("import cloudpickle as cpkl", True),
|
||||
("from pickle import *", True),
|
||||
("from cloudpickle import dumps", True),
|
||||
("from pickle import dumps, loads", True),
|
||||
("from cloudpickle import (dumps, loads)", True),
|
||||
(" import pickle", True),
|
||||
("\timport cloudpickle", True),
|
||||
("from pickle import loads", True),
|
||||
# Should not match
|
||||
("import somethingelse", False),
|
||||
("from somethingelse import pickle", False),
|
||||
("# import pickle", False),
|
||||
("print('import pickle')", False),
|
||||
("import pickleas as asdf", False),
|
||||
]
|
||||
for i, (line, should_match) in enumerate(test_cases):
|
||||
result = bool(CHECK_IMPORTS["pickle/cloudpickle"].pattern.match(line))
|
||||
assert result == should_match, (
|
||||
f"Test case {i} failed: '{line}' (expected {should_match}, got {result})"
|
||||
)
|
||||
print("All regex tests passed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--test-regex" in sys.argv:
|
||||
test_regex()
|
||||
else:
|
||||
sys.exit(main())
|
||||
111
third_party/vllm/tools/pre_commit/check_init_lazy_imports.py
vendored
Normal file
111
third_party/vllm/tools/pre_commit/check_init_lazy_imports.py
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Ensure we perform lazy loading in vllm/__init__.py.
|
||||
i.e: appears only within the `if typing.TYPE_CHECKING:` guard,
|
||||
**except** for a short whitelist.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
INIT_PATH: Final = Path("vllm/__init__.py")
|
||||
|
||||
# If you need to add items to whitelist, do it here.
|
||||
ALLOWED_IMPORTS: Final[frozenset[str]] = frozenset(
|
||||
{
|
||||
"vllm.env_override",
|
||||
}
|
||||
)
|
||||
ALLOWED_FROM_MODULES: Final[frozenset[str]] = frozenset(
|
||||
{
|
||||
".version",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _is_internal(name: str | None, *, level: int = 0) -> bool:
|
||||
if level > 0:
|
||||
return True
|
||||
if name is None:
|
||||
return False
|
||||
return name.startswith("vllm.") or name == "vllm"
|
||||
|
||||
|
||||
def _fail(violations: Iterable[tuple[int, str]]) -> None:
|
||||
print("ERROR: Disallowed eager imports in vllm/__init__.py:\n", file=sys.stderr)
|
||||
for lineno, msg in violations:
|
||||
print(f" Line {lineno}: {msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
source = INIT_PATH.read_text(encoding="utf-8")
|
||||
tree = ast.parse(source, filename=str(INIT_PATH))
|
||||
|
||||
violations: list[tuple[int, str]] = []
|
||||
|
||||
class Visitor(ast.NodeVisitor):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._in_type_checking = False
|
||||
|
||||
def visit_If(self, node: ast.If) -> None:
|
||||
guard_is_type_checking = False
|
||||
test = node.test
|
||||
if isinstance(test, ast.Attribute) and isinstance(test.value, ast.Name):
|
||||
guard_is_type_checking = (
|
||||
test.value.id == "typing" and test.attr == "TYPE_CHECKING"
|
||||
)
|
||||
elif isinstance(test, ast.Name):
|
||||
guard_is_type_checking = test.id == "TYPE_CHECKING"
|
||||
|
||||
if guard_is_type_checking:
|
||||
prev = self._in_type_checking
|
||||
self._in_type_checking = True
|
||||
for child in node.body:
|
||||
self.visit(child)
|
||||
self._in_type_checking = prev
|
||||
for child in node.orelse:
|
||||
self.visit(child)
|
||||
else:
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
if self._in_type_checking:
|
||||
return
|
||||
for alias in node.names:
|
||||
module_name = alias.name
|
||||
if _is_internal(module_name) and module_name not in ALLOWED_IMPORTS:
|
||||
violations.append(
|
||||
(
|
||||
node.lineno,
|
||||
f"import '{module_name}' must be inside typing.TYPE_CHECKING", # noqa: E501
|
||||
)
|
||||
)
|
||||
|
||||
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
||||
if self._in_type_checking:
|
||||
return
|
||||
module_as_written = ("." * node.level) + (node.module or "")
|
||||
if (
|
||||
_is_internal(node.module, level=node.level)
|
||||
and module_as_written not in ALLOWED_FROM_MODULES
|
||||
):
|
||||
violations.append(
|
||||
(
|
||||
node.lineno,
|
||||
f"from '{module_as_written}' import ... must be inside typing.TYPE_CHECKING", # noqa: E501
|
||||
)
|
||||
)
|
||||
|
||||
Visitor().visit(tree)
|
||||
|
||||
if violations:
|
||||
_fail(violations)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
151
third_party/vllm/tools/pre_commit/check_spdx_header.py
vendored
Normal file
151
third_party/vllm/tools/pre_commit/check_spdx_header.py
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
import sys
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class SPDXStatus(Enum):
|
||||
"""SPDX header status enumeration"""
|
||||
|
||||
EMPTY = "empty" # empty __init__.py
|
||||
COMPLETE = "complete"
|
||||
MISSING_LICENSE = "missing_license" # Only has copyright line
|
||||
MISSING_COPYRIGHT = "missing_copyright" # Only has license line
|
||||
MISSING_BOTH = "missing_both" # Completely missing
|
||||
|
||||
|
||||
FULL_SPDX_HEADER = (
|
||||
"# SPDX-License-Identifier: Apache-2.0\n"
|
||||
"# SPDX-FileCopyrightText: Copyright contributors to the vLLM project"
|
||||
)
|
||||
|
||||
LICENSE_LINE = "# SPDX-License-Identifier: Apache-2.0"
|
||||
COPYRIGHT_LINE = "# SPDX-FileCopyrightText: Copyright contributors to the vLLM project" # noqa: E501
|
||||
|
||||
|
||||
def check_spdx_header_status(file_path):
|
||||
"""Check SPDX header status of the file"""
|
||||
with open(file_path, encoding="UTF-8") as file:
|
||||
lines = file.readlines()
|
||||
if not lines:
|
||||
# Empty file
|
||||
return SPDXStatus.EMPTY
|
||||
|
||||
# Skip shebang line
|
||||
start_idx = 0
|
||||
if lines and lines[0].startswith("#!"):
|
||||
start_idx = 1
|
||||
|
||||
has_license = False
|
||||
has_copyright = False
|
||||
|
||||
# Check all lines for SPDX headers (not just the first two)
|
||||
for i in range(start_idx, len(lines)):
|
||||
line = lines[i].strip()
|
||||
if line == LICENSE_LINE:
|
||||
has_license = True
|
||||
elif line == COPYRIGHT_LINE:
|
||||
has_copyright = True
|
||||
|
||||
# Determine status based on what we found
|
||||
if has_license and has_copyright:
|
||||
return SPDXStatus.COMPLETE
|
||||
elif has_license and not has_copyright:
|
||||
# Only has license line
|
||||
return SPDXStatus.MISSING_COPYRIGHT
|
||||
# Only has copyright line
|
||||
elif not has_license and has_copyright:
|
||||
return SPDXStatus.MISSING_LICENSE
|
||||
else:
|
||||
# Completely missing both lines
|
||||
return SPDXStatus.MISSING_BOTH
|
||||
|
||||
|
||||
def add_header(file_path, status):
|
||||
"""Add or supplement SPDX header based on status"""
|
||||
with open(file_path, "r+", encoding="UTF-8") as file:
|
||||
lines = file.readlines()
|
||||
file.seek(0, 0)
|
||||
file.truncate()
|
||||
|
||||
if status == SPDXStatus.MISSING_BOTH:
|
||||
# Completely missing, add complete header
|
||||
if lines and lines[0].startswith("#!"):
|
||||
# Preserve shebang line
|
||||
file.write(lines[0])
|
||||
file.write(FULL_SPDX_HEADER + "\n")
|
||||
file.writelines(lines[1:])
|
||||
else:
|
||||
# Add header directly
|
||||
file.write(FULL_SPDX_HEADER + "\n")
|
||||
file.writelines(lines)
|
||||
|
||||
elif status == SPDXStatus.MISSING_COPYRIGHT:
|
||||
# Only has license line, need to add copyright line
|
||||
# Find the license line and add copyright line after it
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == LICENSE_LINE:
|
||||
# Insert copyright line after license line
|
||||
lines.insert(
|
||||
i + 1,
|
||||
f"{COPYRIGHT_LINE}\n",
|
||||
)
|
||||
break
|
||||
|
||||
file.writelines(lines)
|
||||
|
||||
elif status == SPDXStatus.MISSING_LICENSE:
|
||||
# Only has copyright line, need to add license line
|
||||
# Find the copyright line and add license line before it
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == COPYRIGHT_LINE:
|
||||
# Insert license line before copyright line
|
||||
lines.insert(i, f"{LICENSE_LINE}\n")
|
||||
break
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function"""
|
||||
files_missing_both = []
|
||||
files_missing_copyright = []
|
||||
files_missing_license = []
|
||||
|
||||
for file_path in sys.argv[1:]:
|
||||
status = check_spdx_header_status(file_path)
|
||||
|
||||
if status == SPDXStatus.MISSING_BOTH:
|
||||
files_missing_both.append(file_path)
|
||||
elif status == SPDXStatus.MISSING_COPYRIGHT:
|
||||
files_missing_copyright.append(file_path)
|
||||
elif status == SPDXStatus.MISSING_LICENSE:
|
||||
files_missing_license.append(file_path)
|
||||
else:
|
||||
continue
|
||||
|
||||
# Collect all files that need fixing
|
||||
all_files_to_fix = (
|
||||
files_missing_both + files_missing_copyright + files_missing_license
|
||||
)
|
||||
if all_files_to_fix:
|
||||
print("The following files are missing the SPDX header:")
|
||||
if files_missing_both:
|
||||
for file_path in files_missing_both:
|
||||
print(f" {file_path}")
|
||||
add_header(file_path, SPDXStatus.MISSING_BOTH)
|
||||
|
||||
if files_missing_copyright:
|
||||
for file_path in files_missing_copyright:
|
||||
print(f" {file_path}")
|
||||
add_header(file_path, SPDXStatus.MISSING_COPYRIGHT)
|
||||
if files_missing_license:
|
||||
for file_path in files_missing_license:
|
||||
print(f" {file_path}")
|
||||
add_header(file_path, SPDXStatus.MISSING_LICENSE)
|
||||
|
||||
sys.exit(1 if all_files_to_fix else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
46
third_party/vllm/tools/pre_commit/check_torch_cuda.py
vendored
Normal file
46
third_party/vllm/tools/pre_commit/check_torch_cuda.py
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
import sys
|
||||
|
||||
import regex as re
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Regex: match `torch.cuda.xxx` but allow `torch.accelerator.xxx`
|
||||
# --------------------------------------------------------------------------- #
|
||||
_TORCH_CUDA_PATTERNS = [
|
||||
r"\btorch\.cuda\.(empty_cache|synchronize|device_count|current_device|memory_reserved|memory_allocated|max_memory_allocated|max_memory_reserved|reset_peak_memory_stats|memory_stats|set_device|device\()\b",
|
||||
r"\bwith\storch\.cuda\.device\b",
|
||||
]
|
||||
|
||||
ALLOWED_FILES = {"vllm/platforms/", "vllm/device_allocator/"}
|
||||
|
||||
|
||||
def scan_file(path: str) -> int:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
for pattern in _TORCH_CUDA_PATTERNS:
|
||||
for match in re.finditer(pattern, content, re.MULTILINE):
|
||||
# Calculate line number from match position
|
||||
line_num = content[: match.start() + 1].count("\n") + 1
|
||||
print(
|
||||
f"{path}:{line_num}: "
|
||||
"\033[91merror:\033[0m " # red color
|
||||
"Found torch.cuda API call. Please refer RFC "
|
||||
"https://github.com/vllm-project/vllm/issues/30679, use "
|
||||
"torch.accelerator API instead."
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
returncode = 0
|
||||
for filename in sys.argv[1:]:
|
||||
if any(filename.startswith(prefix) for prefix in ALLOWED_FILES):
|
||||
continue
|
||||
returncode |= scan_file(filename)
|
||||
return returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
1524
third_party/vllm/tools/pre_commit/generate_attention_backend_docs.py
vendored
Normal file
1524
third_party/vllm/tools/pre_commit/generate_attention_backend_docs.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
34
third_party/vllm/tools/pre_commit/generate_nightly_torch_test.py
vendored
Normal file
34
third_party/vllm/tools/pre_commit/generate_nightly_torch_test.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Generates specialized requirements files for nightly PyTorch testing.
|
||||
|
||||
This script reads the main test requirements input file (`requirements/test.in`)
|
||||
and splits its content into two files:
|
||||
1. `requirements/nightly_torch_test.txt`: Contains dependencies
|
||||
except PyTorch-related.
|
||||
2. `torch_nightly_test.txt`: Contains only PyTorch-related packages.
|
||||
"""
|
||||
|
||||
input_file = "requirements/test.in"
|
||||
output_file = "requirements/nightly_torch_test.txt"
|
||||
|
||||
# white list of packages that are not compatible with PyTorch nightly directly
|
||||
# with pip install. Please add your package to this list if it is not compatible
|
||||
# or make the dependency test fails.
|
||||
white_list = ["torch", "torchaudio", "torchvision", "mamba_ssm"]
|
||||
|
||||
with open(input_file) as f:
|
||||
lines = f.readlines()
|
||||
|
||||
skip_next = False
|
||||
|
||||
for line in lines:
|
||||
if skip_next:
|
||||
if line.startswith((" ", "\t")) or line.strip() == "":
|
||||
continue
|
||||
skip_next = False
|
||||
|
||||
if any(k in line.lower() for k in white_list):
|
||||
skip_next = True
|
||||
continue
|
||||
123
third_party/vllm/tools/pre_commit/mypy.py
vendored
Executable file
123
third_party/vllm/tools/pre_commit/mypy.py
vendored
Executable file
@@ -0,0 +1,123 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Run mypy on changed files.
|
||||
|
||||
This script is designed to be used as a pre-commit hook. It runs mypy
|
||||
on files that have been changed. It groups files into different mypy calls
|
||||
based on their directory to avoid import following issues.
|
||||
|
||||
Usage:
|
||||
python tools/pre_commit/mypy.py <ci> <python_version> <changed_files...>
|
||||
|
||||
Args:
|
||||
ci: "1" if running in CI, "0" otherwise. In CI, follow_imports is set to
|
||||
"silent" for the main group of files.
|
||||
python_version: Python version to use (e.g., "3.10") or "local" to use
|
||||
the local Python version.
|
||||
changed_files: List of changed files to check.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import regex as re
|
||||
|
||||
# After fixing errors resulting from changing follow_imports
|
||||
# from "skip" to "silent", remove its directory from SEPARATE_GROUPS.
|
||||
SEPARATE_GROUPS = [
|
||||
"tests",
|
||||
# v0 related
|
||||
"vllm/lora",
|
||||
"vllm/model_executor",
|
||||
]
|
||||
|
||||
# TODO(woosuk): Include the code from Megatron and HuggingFace.
|
||||
EXCLUDE = [
|
||||
"vllm/model_executor/models",
|
||||
"vllm/model_executor/layers/fla/ops",
|
||||
# Ignore triton kernels in ops.
|
||||
"vllm/v1/attention/ops",
|
||||
# TODO: Remove these entries after fixing mypy errors.
|
||||
"vllm/benchmarks",
|
||||
"vllm/config",
|
||||
]
|
||||
|
||||
|
||||
def group_files(changed_files: list[str]) -> dict[str, list[str]]:
|
||||
"""
|
||||
Group changed files into different mypy calls.
|
||||
|
||||
Args:
|
||||
changed_files: List of changed files.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping file group names to lists of changed files.
|
||||
"""
|
||||
exclude_pattern = re.compile(f"^{'|'.join(EXCLUDE)}.*")
|
||||
file_groups = {"": []}
|
||||
file_groups.update({k: [] for k in SEPARATE_GROUPS})
|
||||
for changed_file in changed_files:
|
||||
# Skip files which should be ignored completely
|
||||
if exclude_pattern.match(changed_file):
|
||||
continue
|
||||
# Group files by mypy call
|
||||
for directory in SEPARATE_GROUPS:
|
||||
if re.match(f"^{directory}.*", changed_file):
|
||||
file_groups[directory].append(changed_file)
|
||||
break
|
||||
else:
|
||||
if changed_file.startswith("vllm/"):
|
||||
file_groups[""].append(changed_file)
|
||||
return file_groups
|
||||
|
||||
|
||||
def mypy(
|
||||
targets: list[str],
|
||||
python_version: str | None,
|
||||
follow_imports: str | None,
|
||||
file_group: str,
|
||||
) -> int:
|
||||
"""
|
||||
Run mypy on the given targets.
|
||||
|
||||
Args:
|
||||
targets: List of files or directories to check.
|
||||
python_version: Python version to use (e.g., "3.10") or None to use
|
||||
the default mypy version.
|
||||
follow_imports: Value for the --follow-imports option or None to use
|
||||
the default mypy behavior.
|
||||
file_group: The file group name for logging purposes.
|
||||
|
||||
Returns:
|
||||
The return code from mypy.
|
||||
"""
|
||||
args = ["mypy"]
|
||||
if python_version is not None:
|
||||
args += ["--python-version", python_version]
|
||||
if follow_imports is not None:
|
||||
args += ["--follow-imports", follow_imports]
|
||||
print(f"$ {' '.join(args)} {file_group}")
|
||||
return subprocess.run(args + targets, check=False).returncode
|
||||
|
||||
|
||||
def main():
|
||||
ci = sys.argv[1] == "1"
|
||||
python_version = sys.argv[2]
|
||||
file_groups = group_files(sys.argv[3:])
|
||||
|
||||
if python_version == "local":
|
||||
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
returncode = 0
|
||||
for file_group, changed_files in file_groups.items():
|
||||
follow_imports = None if ci and file_group == "" else "skip"
|
||||
if changed_files:
|
||||
returncode |= mypy(
|
||||
changed_files, python_version, follow_imports, file_group
|
||||
)
|
||||
return returncode
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
15
third_party/vllm/tools/pre_commit/png-lint.sh
vendored
Executable file
15
third_party/vllm/tools/pre_commit/png-lint.sh
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Ensure that *.excalidraw.png files have the excalidraw metadata
|
||||
# embedded in them. This ensures they can be loaded back into
|
||||
# the tool and edited in the future.
|
||||
|
||||
find . -iname '*.excalidraw.png' | while read -r file; do
|
||||
if git check-ignore -q "$file"; then
|
||||
continue
|
||||
fi
|
||||
if ! grep -q "excalidraw+json" "$file"; then
|
||||
echo "$file was not exported from excalidraw with 'Embed Scene' enabled."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
24
third_party/vllm/tools/pre_commit/shellcheck.sh
vendored
Executable file
24
third_party/vllm/tools/pre_commit/shellcheck.sh
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
scversion="stable"
|
||||
|
||||
if [ -d "shellcheck-${scversion}" ]; then
|
||||
export PATH="$PATH:$(pwd)/shellcheck-${scversion}"
|
||||
fi
|
||||
|
||||
if ! [ -x "$(command -v shellcheck)" ]; then
|
||||
if [ "$(uname -s)" != "Linux" ] || [ "$(uname -m)" != "x86_64" ]; then
|
||||
echo "Please install shellcheck: https://github.com/koalaman/shellcheck?tab=readme-ov-file#installing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# automatic local install if linux x86_64
|
||||
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${scversion?}/shellcheck-${scversion?}.linux.x86_64.tar.xz" | tar -xJv
|
||||
export PATH="$PATH:$(pwd)/shellcheck-${scversion}"
|
||||
fi
|
||||
|
||||
# TODO - fix warnings in .buildkite/scripts/hardware_ci/run-amd-test.sh
|
||||
find . -path ./.git -prune -o -name "*.sh" \
|
||||
-not -path "./.buildkite/scripts/hardware_ci/run-amd-test.sh" -print0 | \
|
||||
xargs -0 sh -c "for f in \"\$@\"; do git check-ignore -q \"\$f\" || shellcheck -s bash \"\$f\"; done" --
|
||||
81
third_party/vllm/tools/pre_commit/update-dockerfile-graph.sh
vendored
Executable file
81
third_party/vllm/tools/pre_commit/update-dockerfile-graph.sh
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# Update Dockerfile dependency graph when docker/Dockerfile changes.
|
||||
# This script is designed to be used as a pre-commit hook.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Accept file paths as arguments
|
||||
FILES=("$@")
|
||||
|
||||
# Check if docker/Dockerfile is among the provided files
|
||||
if printf '%s\n' "${FILES[@]}" | grep -q "^docker/Dockerfile$"; then
|
||||
echo "docker/Dockerfile has changed, attempting to update dependency graph..."
|
||||
|
||||
# Check if Docker is installed and running
|
||||
if ! command -v docker &> /dev/null; then
|
||||
echo "Warning: Docker command not found. Skipping Dockerfile graph update."
|
||||
echo "Please install Docker to automatically update the graph: https://docs.docker.com/get-docker/"
|
||||
exit 0
|
||||
fi
|
||||
if ! docker info &> /dev/null; then
|
||||
echo "Warning: Docker daemon is not running. Skipping Dockerfile graph update."
|
||||
echo "Please start Docker to automatically update the graph."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Define the target file path
|
||||
TARGET_GRAPH_FILE="docs/assets/contributing/dockerfile-stages-dependency.png"
|
||||
|
||||
# Ensure target directory exists
|
||||
mkdir -p "$(dirname "$TARGET_GRAPH_FILE")"
|
||||
|
||||
# Store old image hash in a variable if the file exists
|
||||
OLD_HASH=""
|
||||
if [ -f "$TARGET_GRAPH_FILE" ]; then
|
||||
OLD_HASH=$(sha256sum "$TARGET_GRAPH_FILE")
|
||||
fi
|
||||
|
||||
# Generate Dockerfile graph
|
||||
echo "Running dockerfilegraph tool..."
|
||||
docker run \
|
||||
--rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
--workdir /workspace \
|
||||
--volume "$(pwd -P)":/workspace \
|
||||
ghcr.io/patrickhoefler/dockerfilegraph:alpine \
|
||||
--output png \
|
||||
--dpi 200 \
|
||||
--max-label-length 50 \
|
||||
--filename docker/Dockerfile \
|
||||
--legend
|
||||
|
||||
echo "Finding generated PNG file..."
|
||||
# Check for Dockerfile.png in the root directory (most likely location)
|
||||
if [ -f "./Dockerfile.png" ]; then
|
||||
echo "Found generated file at: ./Dockerfile.png"
|
||||
mv "./Dockerfile.png" "$TARGET_GRAPH_FILE"
|
||||
else
|
||||
# Try to find it elsewhere
|
||||
DOCKERFILE_PNG=$(find . -name "Dockerfile.png" -type f | head -1)
|
||||
|
||||
if [ -n "$DOCKERFILE_PNG" ]; then
|
||||
echo "Found generated file at: $DOCKERFILE_PNG"
|
||||
mv "$DOCKERFILE_PNG" "$TARGET_GRAPH_FILE"
|
||||
else
|
||||
echo "Error: Could not find the generated PNG file"
|
||||
find . -name "*.png" -type f -mmin -5
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if the graph has changed
|
||||
NEW_HASH=$(sha256sum "$TARGET_GRAPH_FILE")
|
||||
if [ "$NEW_HASH" != "$OLD_HASH" ]; then
|
||||
echo "Graph has changed. Please stage the updated file: $TARGET_GRAPH_FILE"
|
||||
exit 1
|
||||
else
|
||||
echo "No changes in graph detected."
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
165
third_party/vllm/tools/pre_commit/validate_config.py
vendored
Normal file
165
third_party/vllm/tools/pre_commit/validate_config.py
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Ensures all fields in a config dataclass have default values
|
||||
and that each field has a docstring.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import sys
|
||||
from itertools import pairwise
|
||||
|
||||
import regex as re
|
||||
|
||||
|
||||
def get_attr_docs(cls_node: ast.ClassDef) -> dict[str, str]:
|
||||
"""
|
||||
Get any docstrings placed after attribute assignments in a class body.
|
||||
|
||||
Adapted from https://davidism.com/attribute-docstrings/
|
||||
https://davidism.com/mit-license/
|
||||
"""
|
||||
|
||||
out = {}
|
||||
|
||||
# Consider each pair of nodes.
|
||||
for a, b in pairwise(cls_node.body):
|
||||
# Must be an assignment then a constant string.
|
||||
if (
|
||||
not isinstance(a, (ast.Assign, ast.AnnAssign))
|
||||
or not isinstance(b, ast.Expr)
|
||||
or not isinstance(b.value, ast.Constant)
|
||||
or not isinstance(b.value.value, str)
|
||||
):
|
||||
continue
|
||||
|
||||
doc = inspect.cleandoc(b.value.value)
|
||||
|
||||
# An assignment can have multiple targets (a = b = v), but an
|
||||
# annotated assignment only has one target.
|
||||
targets = a.targets if isinstance(a, ast.Assign) else [a.target]
|
||||
|
||||
for target in targets:
|
||||
# Must be assigning to a plain name.
|
||||
if not isinstance(target, ast.Name):
|
||||
continue
|
||||
|
||||
out[target.id] = doc
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ConfigValidator(ast.NodeVisitor):
|
||||
def __init__(self): ...
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
# Validate classes with a @config decorator
|
||||
decorators = set()
|
||||
for decorator in node.decorator_list:
|
||||
if isinstance(decorator, ast.Call):
|
||||
decorator = decorator.func
|
||||
if isinstance(decorator, ast.Name) and decorator.id == "config":
|
||||
decorators.add(decorator.id)
|
||||
|
||||
if decorators == {"config"}:
|
||||
validate_class(node)
|
||||
elif "config" in decorators:
|
||||
fail(f"config decorator for {node.name} should be used alone", node)
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def validate_class(class_node: ast.ClassDef):
|
||||
attr_docs = get_attr_docs(class_node)
|
||||
|
||||
for stmt in class_node.body:
|
||||
# A field is defined as a class variable that has a type annotation.
|
||||
if isinstance(stmt, ast.AnnAssign):
|
||||
# Skip ClassVar and InitVar
|
||||
# see https://docs.python.org/3/library/dataclasses.html#class-variables
|
||||
# and https://docs.python.org/3/library/dataclasses.html#init-only-variables
|
||||
if (
|
||||
isinstance(stmt.annotation, ast.Subscript)
|
||||
and isinstance(stmt.annotation.value, ast.Name)
|
||||
and stmt.annotation.value.id in {"ClassVar", "InitVar"}
|
||||
):
|
||||
continue
|
||||
|
||||
if isinstance(stmt.target, ast.Name):
|
||||
field_name = stmt.target.id
|
||||
if stmt.value is None:
|
||||
fail(
|
||||
f"Field '{field_name}' in {class_node.name} must have "
|
||||
"a default value.",
|
||||
stmt,
|
||||
)
|
||||
|
||||
if field_name not in attr_docs:
|
||||
fail(
|
||||
f"Field '{field_name}' in {class_node.name} must have "
|
||||
"a docstring.",
|
||||
stmt,
|
||||
)
|
||||
|
||||
if (
|
||||
isinstance(stmt.annotation, ast.Subscript)
|
||||
and isinstance(stmt.annotation.value, ast.Name)
|
||||
and stmt.annotation.value.id == "Union"
|
||||
and isinstance(stmt.annotation.slice, ast.Tuple)
|
||||
):
|
||||
args = stmt.annotation.slice.elts
|
||||
literal_args = [
|
||||
arg
|
||||
for arg in args
|
||||
if isinstance(arg, ast.Subscript)
|
||||
and isinstance(arg.value, ast.Name)
|
||||
and arg.value.id == "Literal"
|
||||
]
|
||||
if len(literal_args) > 1:
|
||||
fail(
|
||||
f"Field '{field_name}' in {class_node.name} must "
|
||||
"use a single "
|
||||
"Literal type. Please use 'Literal[Literal1, "
|
||||
"Literal2]' instead of 'Union[Literal1, Literal2]'"
|
||||
".",
|
||||
stmt,
|
||||
)
|
||||
|
||||
|
||||
def validate_ast(tree: ast.stmt):
|
||||
ConfigValidator().visit(tree)
|
||||
|
||||
|
||||
def validate_file(file_path: str):
|
||||
try:
|
||||
print(f"Validating {file_path} config dataclasses ", end="")
|
||||
with open(file_path, encoding="utf-8") as f:
|
||||
source = f.read()
|
||||
|
||||
tree = ast.parse(source, filename=file_path)
|
||||
validate_ast(tree)
|
||||
except ValueError as e:
|
||||
print(e)
|
||||
raise SystemExit(1) from e
|
||||
else:
|
||||
print("✅")
|
||||
|
||||
|
||||
def fail(message: str, node: ast.stmt):
|
||||
raise ValueError(f"❌ line({node.lineno}): {message}")
|
||||
|
||||
|
||||
def main():
|
||||
for filename in sys.argv[1:]:
|
||||
# Only run for Python files in vllm/ or tests/
|
||||
if not re.match(r"^(vllm|tests)/.*\.py$", filename):
|
||||
continue
|
||||
# Only run if the file contains @config
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
if "@config" in f.read():
|
||||
validate_file(filename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user