[Lint]Add lint hooks for clang-format, shellcheck, forbidden imports, and boolean context manager checks (#7511)

### What this PR does / why we need it?
This PR introduces several upstream `vllm`-aligned lint hooks into
`vllm-ascend` and makes them part of the actual `pre-commit` flow.

Main changes in this PR:
- add `check-boolean-context-manager` to catch boolean expressions in
`with` statements
- add `check-forbidden-imports` to forbid direct `re` imports and
disallowed direct `triton` imports
- enable shell script linting through `tools/shellcheck.sh`
- add root `.clang-format` aligned with upstream `vllm`, enable
`clang-format` in `pre-commit`, temporarily **exclude all `csrc/**`**
from `clang-format` to avoid bringing a large native code reformat into
this PR

This PR focuses on landing the smaller and immediately useful lint
alignment first, without mixing in the larger requirements-management
migration.

### Does this PR introduce _any_ user-facing change?
No.

This PR only updates repository lint configuration, static checks, and
internal import/style enforcement. It does not change runtime behavior
or public interfaces.

### How was this patch tested?
Tested locally in the project virtual environment.

Commands used:
```bash
bash format.sh
```
Verified checks passed:
``` bash
ruff check...............................................................Passed
ruff format..............................................................Passed
codespell................................................................Passed
typos....................................................................Passed
clang-format.............................................................Passed
Lint GitHub Actions workflow files.......................................Passed
Lint shell scripts.......................................................Passed
Lint PNG exports from excalidraw.........................................Passed
Check for spaces in all filenames........................................Passed
Enforce __init__.py in Python packages...................................Passed
Check for forbidden imports..............................................Passed
Check for boolean ops in with-statements.................................Passed
Suggestion...............................................................Passed
- hook id: suggestion
- duration: 0s

To bypass pre-commit hooks, add --no-verify to git commit.
```
**note:**
clang-format is enabled but currently excludes all csrc/**


- vLLM version: v0.17.0
- vLLM main:
8b6325758c

---------

Signed-off-by: MrZ20 <2609716663@qq.com>
This commit is contained in:
SILONG ZENG
2026-03-24 20:03:01 +08:00
committed by GitHub
parent d1a83a72f7
commit 1e3c1e76bf
24 changed files with 262 additions and 134 deletions

26
.clang-format Normal file
View File

@@ -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

View File

@@ -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/*

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -7,6 +7,7 @@ packaging
pip
pybind11
pyyaml
regex
scipy
pandas
psutil

View File

@@ -1,6 +1,6 @@
import logging
import os
import re
import regex as re
from dataclasses import dataclass, field
from typing import Any

View File

@@ -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"))

View File

@@ -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> ...", 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())

View File

@@ -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())

View File

@@ -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())

View File

@@ -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<user>[A-Za-z0-9-_]+)[^\`]*\`(?P<sha>[0-9a-fA-F]+)\`\s*[-–—]\s*(?P<date>.+)$")

View File

@@ -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" --

View File

@@ -1,9 +1,9 @@
# Standard
import json
import os
import re
from dataclasses import dataclass
import regex as re
import torch
# Third Party

View File

@@ -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

View File

@@ -15,9 +15,9 @@
#
import os
import re
import socket
import regex as re
from vllm.logger import logger

View File

@@ -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

View File

@@ -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

View File

@@ -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),)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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