diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..0ebde21b --- /dev/null +++ b/.clang-format @@ -0,0 +1,26 @@ +BasedOnStyle: Google +UseTab: Never +IndentWidth: 2 +ColumnLimit: 120 + +# Force pointers to the type for C++. +DerivePointerAlignment: false +PointerAlignment: Left + +# Reordering #include statements can (and currently will) introduce errors +SortIncludes: false + +# Style choices +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +IndentPPDirectives: BeforeHash + +IncludeCategories: + - Regex: '^<' + Priority: 4 + - Regex: '^"(llvm|llvm-c|clang|clang-c|mlir|mlir-c)/' + Priority: 3 + - Regex: '^"(qoda|\.\.)/' + Priority: 2 + - Regex: '.*' + Priority: 1 diff --git a/.github/workflows/dockerfiles/Dockerfile.lint b/.github/workflows/dockerfiles/Dockerfile.lint index ffd1e7b8..26e4631c 100644 --- a/.github/workflows/dockerfiles/Dockerfile.lint +++ b/.github/workflows/dockerfiles/Dockerfile.lint @@ -20,7 +20,7 @@ FROM ascendai/python:3.11-ubuntu22.04 ARG TARGETARCH RUN apt-get update -y && \ - apt-get install -y curl git gcc g++ cmake libnuma-dev jq && \ + apt-get install -y curl git gcc g++ cmake libnuma-dev jq wget xz-utils shellcheck && \ rm -rf /var/cache/apt/* && \ rm -rf /var/lib/apt/lists/* diff --git a/.github/workflows/scripts/ci_log_summary.py b/.github/workflows/scripts/ci_log_summary.py index da36a54f..38d7a13e 100644 --- a/.github/workflows/scripts/ci_log_summary.py +++ b/.github/workflows/scripts/ci_log_summary.py @@ -3,7 +3,6 @@ from __future__ import annotations import argparse import copy import json -import re import shutil import subprocess import sys @@ -11,6 +10,8 @@ from collections import defaultdict from pathlib import Path from typing import Any +import regex as re + """ Generate CI failure summaries from a local pytest log or a GitHub Actions run. Examples: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6470c435..000c6aed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ default_install_hook_types: default_stages: - pre-commit # Run locally - manual # Run in CI +exclude: '^(\.agents|\.claude|\.gemini)/.*' repos: - repo: https://github.com/astral-sh/ruff-pre-commit @@ -35,13 +36,13 @@ repos: "--exclude", "csrc/**" ] -# - repo: https://github.com/pre-commit/mirrors-clang-format -# rev: v20.1.3 -# hooks: -# - id: clang-format -# files: ^csrc/.*\.(cpp|hpp|cc|hh|cxx|hxx)$ -# types_or: [c++] -# args: [--style=google, --verbose] +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v21.1.2 + hooks: + - id: clang-format + exclude: ^csrc/.* + types_or: [c++] + args: [--style=file, --verbose] - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.45.0 @@ -59,6 +60,11 @@ repos: - repo: local hooks: + - id: shellcheck + name: Lint shell scripts + entry: tools/shellcheck.sh + language: script + types: [shell] - id: png-lint name: Lint PNG exports from excalidraw entry: tools/png-lint.sh @@ -97,6 +103,17 @@ repos: language: script types: [python] pass_filenames: false + - id: check-forbidden-imports + name: Check for forbidden imports + entry: python tools/check_forbidden_imports.py + language: python + types: [python] + additional_dependencies: [regex] + - id: check-boolean-context-manager + name: Check for boolean ops in with-statements + entry: python tools/check_boolean_context_manager.py + language: python + types: [python] # Keep `suggestion` last - id: suggestion name: Suggestion diff --git a/collect_env.py b/collect_env.py index 7b8c2b65..f45d08e7 100644 --- a/collect_env.py +++ b/collect_env.py @@ -18,11 +18,11 @@ import datetime import locale import os -import re import subprocess import sys from collections import namedtuple +import regex as re from vllm.envs import environment_variables try: diff --git a/requirements.txt b/requirements.txt index b307c62e..f816566e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ packaging pip pybind11 pyyaml +regex scipy pandas psutil diff --git a/tests/e2e/nightly/single_node/models/scripts/single_node_config.py b/tests/e2e/nightly/single_node/models/scripts/single_node_config.py index 599987eb..586f62ef 100644 --- a/tests/e2e/nightly/single_node/models/scripts/single_node_config.py +++ b/tests/e2e/nightly/single_node/models/scripts/single_node_config.py @@ -1,6 +1,6 @@ import logging import os -import re +import regex as re from dataclasses import dataclass, field from typing import Any diff --git a/tools/aisbench.py b/tools/aisbench.py index b04d731e..b5d96412 100644 --- a/tools/aisbench.py +++ b/tools/aisbench.py @@ -18,7 +18,6 @@ import hashlib import json import logging import os -import re import subprocess import tempfile from pathlib import Path @@ -26,6 +25,7 @@ from pathlib import Path import filelock import huggingface_hub import pandas as pd +import regex as re from modelscope import snapshot_download # type: ignore BENCHMARK_HOME = os.getenv("BENCHMARK_HOME", os.path.abspath("./benchmark")) diff --git a/tools/check_boolean_context_manager.py b/tools/check_boolean_context_manager.py new file mode 100644 index 00000000..ea684097 --- /dev/null +++ b/tools/check_boolean_context_manager.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. +# Copyright 2023 The vLLM team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# This file is a part of the vllm-ascend project. +# Adapted from https://github.com/vllm-project/vllm/tree/main/tools +# +"""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 " + "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=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 a `with` " + "statement.\n" + "Use `with a(), b():` or `with (a(), b()):` instead.\n" + ) + for violation in all_violations: + print(f" {violation}") + return 1 + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/check_forbidden_imports.py b/tools/check_forbidden_imports.py new file mode 100644 index 00000000..14571789 --- /dev/null +++ b/tools/check_forbidden_imports.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. +# Copyright 2023 The vLLM team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# This file is a part of the vllm-ascend project. +# Adapted from https://github.com/vllm-project/vllm/tree/main/tools +# + +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"^$") + 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/check_forbidden_imports.py."), + allowed_files={ + "vllm_ascend/distributed/kv_transfer/kv_pool/cpu_offload/metadata.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*)$"), + ), + "triton": ForbiddenImport( + pattern=r"^(from|import)\s+triton(\s|\.|$)", + tip=("Use 'from vllm.triton_utils import triton'/'tl'."), + allowed_pattern=re.compile( + r"^\s*import\s+triton\.language\.extra\.cann\.extension\s+as\s+_extension_module(\s+#.*)?$" + ), + ), +} + + +def check_file(path: str) -> int: + try: + with open(path, encoding="utf-8") as f: + content = f.read() + except (OSError, UnicodeDecodeError): + return [] + + return_code = 0 + for import_name, forbidden_import in CHECK_IMPORTS.items(): + if path in forbidden_import.allowed_files: + continue + + for match in re.finditer(forbidden_import.pattern, content, re.MULTILINE): + if forbidden_import.allowed_pattern.match(match.group()): + continue + + line_num = content[: match.start() + 1].count("\n") + 1 + print( + f"{path}:{line_num}: " + "\033[91merror:\033[0m " + f"Found forbidden import: {import_name}. {forbidden_import.tip}" + ) + return_code = 1 + + return return_code + + +def main() -> int: + return_code = 0 + for path in sys.argv[1:]: + return_code |= check_file(path) + return return_code + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/enforce_regex_import.py b/tools/enforce_regex_import.py deleted file mode 100644 index 896e1ad0..00000000 --- a/tools/enforce_regex_import.py +++ /dev/null @@ -1,96 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# Copyright 2023 The vLLM team. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# Adapted from https://github.com/vllm-project/vllm/tree/main/tools -# - -from __future__ import annotations - -import subprocess -from pathlib import Path - -import regex as re - -FORBIDDEN_PATTERNS = re.compile(r"^\s*(?:import\s+re(?:$|\s|,)|from\s+re\s+import)") -ALLOWED_PATTERNS = [ - re.compile(r"^\s*import\s+regex\s+as\s+re\s*$"), - re.compile(r"^\s*import\s+regex\s*$"), -] - - -def get_staged_python_files() -> list[str]: - try: - result = subprocess.run( - ["git", "diff", "--cached", "--name-only", "--diff-filter=AM"], capture_output=True, text=True, check=True - ) - files = result.stdout.strip().split("\n") if result.stdout.strip() else [] - return [f for f in files if f.endswith(".py")] - except subprocess.CalledProcessError: - return [] - - -def is_forbidden_import(line: str) -> bool: - line = line.strip() - return bool(FORBIDDEN_PATTERNS.match(line) and not any(pattern.match(line) for pattern in ALLOWED_PATTERNS)) - - -def check_file(filepath: str) -> list[tuple[int, str]]: - violations = [] - try: - with open(filepath, encoding="utf-8") as f: - for line_num, line in enumerate(f, 1): - if is_forbidden_import(line): - violations.append((line_num, line.strip())) - except (OSError, UnicodeDecodeError): - pass - return violations - - -def main() -> int: - files = get_staged_python_files() - if not files: - return 0 - - total_violations = 0 - - for filepath in files: - if not Path(filepath).exists(): - continue - - if filepath == "setup.py": - continue - - violations = check_file(filepath) - if violations: - print(f"\nāŒ {filepath}:") - for line_num, line in violations: - print(f" Line {line_num}: {line}") - total_violations += 1 - - if total_violations > 0: - print(f"\nšŸ’” Found {total_violations} violation(s).") - print("āŒ Please replace 'import re' with 'import regex as re'") - print(" Also replace 'from re import ...' with 'from regex import ...'") # noqa: E501 - print("āœ… Allowed imports:") - print(" - import regex as re") - print(" - import regex") # noqa: E501 - return 1 - - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/format_contributors.py b/tools/format_contributors.py index 93d5d9e1..903f00ec 100644 --- a/tools/format_contributors.py +++ b/tools/format_contributors.py @@ -16,10 +16,11 @@ # Adapted from https://github.com/vllm-project/vllm/tree/main/tools # import argparse -import re import sys from datetime import datetime +import regex as re + p = re.compile(r"@(?P[A-Za-z0-9-_]+)[^\`]*\`(?P[0-9a-fA-F]+)\`\s*[-–—]\s*(?P.+)$") diff --git a/tools/shellcheck.sh b/tools/shellcheck.sh index f3900708..112383d8 100755 --- a/tools/shellcheck.sh +++ b/tools/shellcheck.sh @@ -19,13 +19,12 @@ # Adapted from https://github.com/vllm-project/vllm/tree/main/tools # -set -e +set -euo pipefail scversion="stable" if [ -d "shellcheck-${scversion}" ]; then - PATH="$PATH:$(pwd)/shellcheck-${scversion}" - export PATH + export PATH="$PATH:$(pwd)/shellcheck-${scversion}" fi if ! [ -x "$(command -v shellcheck)" ]; then @@ -34,12 +33,9 @@ if ! [ -x "$(command -v shellcheck)" ]; then 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 - PATH="$PATH:$(pwd)/shellcheck-${scversion}" - export PATH + export PATH="$PATH:$(pwd)/shellcheck-${scversion}" fi -# should enable this -# find . -path ./.git -prune -o -name "*.sh" -print0 \ -# | xargs -0 -I {} sh -c 'git check-ignore -q "{}" || shellcheck -s bash "{}"' +find . -path ./.git -prune -o -name "*.sh" -print0 | \ + xargs -0 sh -c "for f in \"\$@\"; do git check-ignore -q \"\$f\" || shellcheck -s bash \"\$f\"; done" -- diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py index a3136760..078590f6 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py @@ -1,9 +1,9 @@ # Standard import json import os -import re from dataclasses import dataclass +import regex as re import torch # Third Party diff --git a/vllm_ascend/model_loader/netloader/interaction/elastic.py b/vllm_ascend/model_loader/netloader/interaction/elastic.py index 7c1c7f8d..e98087c7 100644 --- a/vllm_ascend/model_loader/netloader/interaction/elastic.py +++ b/vllm_ascend/model_loader/netloader/interaction/elastic.py @@ -15,11 +15,11 @@ # import json -import re import socket import threading from contextlib import suppress +import regex as re import torch from vllm.logger import logger diff --git a/vllm_ascend/model_loader/netloader/utils.py b/vllm_ascend/model_loader/netloader/utils.py index 1481ed16..1efa9e49 100644 --- a/vllm_ascend/model_loader/netloader/utils.py +++ b/vllm_ascend/model_loader/netloader/utils.py @@ -15,9 +15,9 @@ # import os -import re import socket +import regex as re from vllm.logger import logger diff --git a/vllm_ascend/ops/linear_op.py b/vllm_ascend/ops/linear_op.py index c8b12615..6ba9c462 100644 --- a/vllm_ascend/ops/linear_op.py +++ b/vllm_ascend/ops/linear_op.py @@ -40,10 +40,10 @@ Row parallel op follows a similar approach - inherit from RowColumnParallelOp an get_row_parallel_op. """ -import re from functools import lru_cache from types import SimpleNamespace +import regex as re import torch import torch.distributed as dist import torch.nn.functional as F diff --git a/vllm_ascend/ops/triton/batch_invariant/matmul.py b/vllm_ascend/ops/triton/batch_invariant/matmul.py index e5606a27..10cda68b 100644 --- a/vllm_ascend/ops/triton/batch_invariant/matmul.py +++ b/vllm_ascend/ops/triton/batch_invariant/matmul.py @@ -18,7 +18,6 @@ # import torch -from triton.runtime import driver # type: ignore from vllm.triton_utils import tl, triton @@ -269,7 +268,9 @@ def linear_persistent(x, y): # Allocate output tensor (same data type as x) output = torch.zeros((M, N), dtype=x.dtype, device=x.device) - grid_size = driver.active.utils.get_device_properties(torch.npu.current_device())["num_vectorcore"] // 2 + grid_size = ( + triton.runtime.driver.active.utils.get_device_properties(torch.npu.current_device())["num_vectorcore"] // 2 + ) # Define block sizes (can be adjusted based on hardware) BLOCK_K = 256 diff --git a/vllm_ascend/ops/triton/batch_invariant/rmsnorm.py b/vllm_ascend/ops/triton/batch_invariant/rmsnorm.py index 767854c2..5044c233 100644 --- a/vllm_ascend/ops/triton/batch_invariant/rmsnorm.py +++ b/vllm_ascend/ops/triton/batch_invariant/rmsnorm.py @@ -18,7 +18,6 @@ # import torch -from triton.runtime import driver # type: ignore from vllm.triton_utils import tl, triton @@ -113,7 +112,9 @@ def rms_norm( output = torch.empty_like(input_2d, dtype=input_.dtype) BLOCK_SIZE = 1024 - max_grid_size = driver.active.utils.get_device_properties(torch.npu.current_device())["num_vectorcore"] + max_grid_size = triton.runtime.driver.active.utils.get_device_properties(torch.npu.current_device())[ + "num_vectorcore" + ] grid = (min(n_rows, max_grid_size),) diff --git a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_mrope.py b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_mrope.py index 6fd5b879..ba920c25 100644 --- a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_mrope.py +++ b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_mrope.py @@ -17,8 +17,7 @@ import torch -import triton # type: ignore -import triton.language as tl # type: ignore +from vllm.triton_utils import tl, triton from vllm.utils.torch_utils import direct_register_custom_op from vllm_ascend.ops.triton.triton_utils import get_vectorcore_num diff --git a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope.py b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope.py index f7e59a28..9e581566 100644 --- a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope.py +++ b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope.py @@ -16,8 +16,7 @@ # import torch -import triton # type: ignore -import triton.language as tl # type: ignore +from vllm.triton_utils import tl, triton from vllm.utils.torch_utils import direct_register_custom_op from vllm_ascend.ops.triton.triton_utils import extract_slice, get_element, get_vectorcore_num, insert_slice diff --git a/vllm_ascend/ops/triton/mamba/causal_conv1d.py b/vllm_ascend/ops/triton/mamba/causal_conv1d.py index 4e3680ef..a06320e0 100644 --- a/vllm_ascend/ops/triton/mamba/causal_conv1d.py +++ b/vllm_ascend/ops/triton/mamba/causal_conv1d.py @@ -11,10 +11,9 @@ from typing import Any import torch import torch.nn.functional as F -import triton -import triton.language as tl from vllm.distributed import get_pcp_group from vllm.forward_context import get_forward_context +from vllm.triton_utils import tl, triton from vllm.v1.attention.backends.utils import PAD_SLOT_ID # type: ignore diff --git a/vllm_ascend/quantization/modelslim_config.py b/vllm_ascend/quantization/modelslim_config.py index 151109c5..120f2191 100644 --- a/vllm_ascend/quantization/modelslim_config.py +++ b/vllm_ascend/quantization/modelslim_config.py @@ -24,11 +24,11 @@ configs generated by the ModelSlim tool, along with model-specific mappings. import glob import json import os -import re from collections.abc import Mapping from types import MappingProxyType from typing import Any, Optional +import regex as re import torch from vllm.config import get_current_vllm_config from vllm.logger import logger diff --git a/vllm_ascend/utils.py b/vllm_ascend/utils.py index a9f72ed6..4db163c7 100644 --- a/vllm_ascend/utils.py +++ b/vllm_ascend/utils.py @@ -23,13 +23,13 @@ import atexit import functools import math import os -import re from contextlib import nullcontext from enum import Enum from functools import lru_cache from threading import Lock from typing import TYPE_CHECKING, Any +import regex as re import torch import torch_npu # noqa: F401 from packaging.version import InvalidVersion, Version