Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# <<<< BEGIN AUTO-GENERATED by scripts/legacy_utils.py gen-configs >>>>
# Source of truth: legacy-files.txt
# To modify this block: edit legacy-files.txt, then run:
Expand Down Expand Up @@ -1457,10 +1460,8 @@ legacy-files: &legacy_files |
)$
# <<<< END AUTO-GENERATED >>>>

# The set of files subjected to mypy static analysis is single-sourced in
# pyproject.toml under [tool.mypy] `files`. The type-check hook below runs mypy
# with no explicit file arguments (pass_filenames: false) so mypy reads that
# list; the hook's own `files:` pattern only gates *when* the hook triggers.
# Full-project mypy scans use the file set in pyproject.toml. The pre-commit
# hook passes changed paths to the script's default changed-file mode.

# Global exclude: vendored code + trtllm-gen FMHA artifacts (cubin pointers, export headers, cuda_ptx)
exclude: '(^tensorrt_llm/_torch/attention/kernels/blackwell/kimi_k3_kda/kda_chunked_bt16(_small_bh)?\.py$|^cpp/tensorrt_llm/common/sha256/|^triton_kernels/|^tensorrt_llm/_torch/attention/backends/prims_ts/|^tensorrt_llm/_torch/visual_gen/cute_dsl_kernels/blackwell/sol_attn/|trtllmGenKernels/fmha/cubin/kernelMetaInfo\.h$|cubin\.cpp$|cubin\.h$|trtllmGenKernels/fmha/trtllmGen_fmha_export/|trtllmGenKernels/fmha/cuda_ptx/|trtllmGenKernels/batchedGemm/trtllmGen_bmm_export/KernelMetaInfo\.h$|trtllmGenKernels/gemm/trtllmGen_gemm_export/KernelMetaInfo\.h$|\.cubin\.tar\.zst$)'
Expand Down Expand Up @@ -1612,14 +1613,13 @@ repos:
files: >-
^(\.claude/skills/[^/]+/SKILL\.md|\.claude/agents/.*\.md|scripts/check_skill_naming_convention\.py)$
- id: type-check
name: Run static analysis of type correctness using mypy
entry: scripts/run_mypy.sh
name: Record advisory mypy type-check report
entry: scripts/run_mypy.sh --diff-base HEAD
language: script
verbose: true
require_serial: true # because of install_types=true
# Run mypy with no explicit paths so it type-checks the set declared in
# pyproject.toml [tool.mypy] `files` (single source of truth). This
# `files:` pattern only decides when the hook triggers — any change under
# the sampler source/test dirs.
pass_filenames: false
files: ^(tensorrt_llm/_torch/pyexecutor/sampler/|tests/unittest/_torch/sampler/)
# Check changed paths and retain diagnostics only on added diff lines.
# HEAD is the pre-commit base for staged changes.
# Configuration changes trigger a full-project scan without diff filtering.
pass_filenames: true
files: ^(tensorrt_llm/.*\.pyi?$|pyproject\.toml$|scripts/(mypy_exclusion\.json|mypy_baseline\.txt|run_mypy\.sh|mypy_exclusion\.py|diff_line_filter\.py)$|\.pre-commit-config\.yaml$)
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ See [CI overview](docs/source/developer-guide/ci-overview.md) for full details.
| Test waives | `tests/integration/test_lists/waives.txt` | Skip known-failing tests with NVBug links |
| Performance | See [benchmarking guide](docs/source/developer-guide/perf-benchmarking.md) | `trtllm-bench` and `trtllm-serve` benchmarks |

Run changed-file Python static checks with `scripts/run_mypy.sh`, or pass
`--full-project` to scan all product Python files. The `[tool.mypy]` section in
`pyproject.toml` defines the product and test roots and the diagnostic allowlist.
Keep its enabled/disabled code lists aligned with the pinned mypy version in
`requirements-dev.txt`. Missing imports are reported except for PyTorch and the
generated `tensorrt_llm.bindings` modules, which may be absent on development hosts.

New C++ unit tests should use `add_tllm_gtest` with explicit `SOURCES`,
`LINK_LIBRARIES`, `OWNER`, and scheduling `LABELS`. Request `FULL_STACK` explicitly
for integration tests and include `gpu` in `LABELS` for tests that need a GPU.
Expand Down
2 changes: 1 addition & 1 deletion jenkins/Build.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ def runLLMBuild(
withEnv(["MYPY_REQUIRE_BINDINGS=1"]) {
trtllm_utils.llmExecStepWithRetry(
pipeline,
script: "cd ${LLM_ROOT} && bash scripts/run_mypy.sh",
script: "cd ${LLM_ROOT} && bash scripts/run_mypy.sh --full-project && cat mypy_final_report.txt",
shortCommondRunTimeMax: 240 // always retry on failure if the cmd runs less than 240s
)
}
Expand Down
153 changes: 93 additions & 60 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

####################################################################################################
############################### BUILD CONFIGURATION ##############################################
####################################################################################################
Expand Down Expand Up @@ -844,71 +847,101 @@ follow_imports = "silent"
explicit_package_bases = true
namespace_packages = true
strict = true
check_untyped_defs = true
show_error_codes = true
# Files checked when mypy runs with no explicit paths. This is the single source
# of truth for the type-check file set. Both the CI build stage (which invokes
# scripts/run_mypy.sh directly) and the local pre-commit hook run mypy without
# explicit paths (the hook sets `pass_filenames: false`), so both read this list.
files = [
"tensorrt_llm/_torch/pyexecutor/sampler/sampler.py",
"tensorrt_llm/_torch/pyexecutor/sampler/sampler_common.py",
"tensorrt_llm/_torch/pyexecutor/sampler/sampling_resolution.py",
"tensorrt_llm/_torch/pyexecutor/sampler/logprobs.py",
"tensorrt_llm/_torch/pyexecutor/sampler/sampler_features.py",
"tensorrt_llm/_torch/pyexecutor/sampler/seed_manager.py",
"tensorrt_llm/_torch/pyexecutor/sampler/finish_reasons.py",
"tensorrt_llm/_torch/pyexecutor/sampler/top_p_decay.py",
"tensorrt_llm/_torch/pyexecutor/sampler/penalties.py",
"tensorrt_llm/_torch/pyexecutor/sampler/token_ban.py",
"tensorrt_llm/_torch/pyexecutor/sampler/ops/flashinfer.py",
"tensorrt_llm/_torch/pyexecutor/sampler/ops/vanilla.py",
"tests/unittest/_torch/sampler/test_torch_sampler.py",
"tests/unittest/_torch/sampler/test_token_ban.py",
"tests/unittest/_torch/sampler/test_beam_search.py",
"tests/unittest/_torch/sampler/test_beam_search_util.py",
]
# NB:
# . -> 'tensorrt_llm'
# tests/unittest -> 'utils'
# tests/unittest/_torch/sampler -> for 'test_beam_search_util'
mypy_path = ".:tests/unittest:tests/unittest/_torch/sampler"

[[tool.mypy.overrides]]
module = ["tensorrt_llm.bindings.*"]
ignore_errors = true

# Generated pybind11 stub; DeepGEMM returns a bare py::dict from
# get_bf16_mega_gate_config, which stubgen renders without type parameters.
[[tool.mypy.overrides]]
module = ["tensorrt_llm.deep_gemm_cpp_tllm", "deep_gemm_cpp_tllm"]
disallow_any_generics = false

[[tool.mypy.overrides]]
module = [
"tensorrt_llm._torch.pyexecutor.sampler",
"tensorrt_llm._torch.pyexecutor.sampler.*",
# Diagnostic allowlist for product code. Keep the complementary
# disabled list in sync with the mypy version pinned in requirements-dev.txt.
enable_error_code = [
"syntax",
"import-not-found",
"name-defined",
"used-before-def",
"call-arg",
]
disable_error_code = [
"abstract",
"annotation-unchecked",
"arg-type",
"assert-type",
"assignment",
"attr-defined",
"await-not-async",
"call-overload",
"comparison-overlap",
"deprecated",
"dict-item",
"empty-body",
"exhaustive-match",
"exit-return",
"explicit-any",
"explicit-override",
"func-returns-value",
"has-type",
"ignore-without-code",
"import",
"import-untyped",
"index",
"list-item",
"literal-required",
"maybe-unrecognized-str-typeform",
"metaclass",
"method-assign",
"misc",
"mutable-override",
"name-match",
"narrowed-type-not-subtype",
"no-any-return",
"no-any-unimported",
"no-overload-impl",
"no-redef",
"no-untyped-call",
# TODO(TRTLLM-15310): strict mode's warn_redundant_casts flags several cast(...) calls
# in the sampler package (introduced by #16620). Whether a cast is "redundant" is
# binding-dependent -- the full build check (compiled bindings + stubs) flags 2,
# the bindings-less local/pre-commit check flags ~20 -- so removing casts is a
# fragile WAR. Suppress redundant-cast for the sampler module until the casts are
# audited and the annotations tightened, rather than churn the source.
"no-untyped-def",
"operator",
"overload-cannot-match",
"overload-overlap",
"override",
"possibly-undefined",
"prop-decorator",
"redundant-cast",
"redundant-expr",
"redundant-self",
"return",
"return-value",
"safe-super",
"str-bytes-safe",
"str-format",
"truthy-bool",
"truthy-function",
"truthy-iterable",
"top-level-await",
"type-abstract",
"type-arg",
"type-var",
"typeddict-item",
"typeddict-readonly-mutated",
"typeddict-unknown-key",
"unreachable",
"untyped-decorator",
"unused-awaitable",
"unused-coroutine",
"unused-ignore",
"unimported-reveal",
"union-attr",
"valid-newtype",
"valid-type",
"var-annotated",
]
# Files checked by scripts/run_mypy.sh --full-project. Pre-commit passes changed
# product Python files explicitly to the script's default changed-file mode.
files = ["tensorrt_llm"]
# scripts/run_mypy.sh reads path and symbol exclusions from scripts/mypy_exclusion.json.
mypy_path = "."

[[tool.mypy.overrides]]
module = [
"tests.*",
"_torch.*", # tests/unittest/_torch/*, cf. mypy_path
"test_torch_sampler", # tests/unittest/_torch/sampler/..., cf. mypy_path
"test_token_ban", # tests/unittest/_torch/sampler/..., cf. mypy_path
"test_beam_search", # tests/unittest/_torch/sampler/..., cf. mypy_path
"test_beam_search_util", # tests/unittest/_torch/sampler/..., cf. mypy_path
]
disable_error_code = [
"no-untyped-def",
"no-untyped-call",
]
# TensorRT-LLM bindings are generated by the C++ build and may be absent in a source checkout.
module = ["torch.*", "tensorrt_llm.bindings", "tensorrt_llm.bindings.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["tensorrt_llm.llmapi.tracer"]
disable_error_code = ["import-not-found"]
100 changes: 100 additions & 0 deletions scripts/diff_line_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Match source locations to added lines in a Git diff."""

import re
import subprocess
from pathlib import Path

DIFF_HUNK_PATTERN = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")


class AddedLineFilter:
"""Check whether source locations fall on lines added relative to a Git ref."""

def __init__(self, project_root: Path, diff_base: str) -> None:
self.project_root = project_root.resolve()
self.added_lines_by_path: dict[str, set[int]] = {}

git_root = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
cwd=self.project_root,
capture_output=True,
text=True,
check=False,
)
if git_root.returncode != 0:
raise ValueError(f"cannot read diff lines: {git_root.stderr.strip()}")

base = subprocess.run(
["git", "rev-parse", "--verify", f"{diff_base}^{{commit}}"],
cwd=self.project_root,
capture_output=True,
text=True,
check=False,
)
if base.returncode != 0:
raise ValueError(f"invalid diff base {diff_base!r}: {base.stderr.strip()}")
self.base_commit = base.stdout.strip()

def contains(self, source_path: str, line_number: int) -> bool:
"""Return whether a path and line identify added or replaced source code."""
path = Path(source_path)
resolved_path = (
path.resolve() if path.is_absolute() else (self.project_root / path).resolve()
)
try:
relative_path = resolved_path.relative_to(self.project_root)
except ValueError:
return False

path_key = relative_path.as_posix()
if path_key not in self.added_lines_by_path:
self.added_lines_by_path[path_key] = self._load_added_lines(relative_path)
return line_number in self.added_lines_by_path[path_key]

def _load_added_lines(self, relative_path: Path) -> set[int]:
path = relative_path.as_posix()
tracked = subprocess.run(
["git", "ls-files", "--error-unmatch", "--", path],
cwd=self.project_root,
capture_output=True,
text=True,
check=False,
)
source_path = self.project_root / relative_path
if tracked.returncode == 1 and source_path.is_file():
return set(range(1, len(source_path.read_text().splitlines()) + 1))
if tracked.returncode != 0:
raise ValueError(
f"cannot check Git tracking state for {path}: {tracked.stderr.strip()}"
)

diff = subprocess.run(
[
"git",
"diff",
"--no-ext-diff",
"--no-renames",
"--unified=0",
self.base_commit,
"--",
path,
],
cwd=self.project_root,
capture_output=True,
text=True,
check=False,
)
if diff.returncode != 0:
raise ValueError(f"cannot read diff for {path}: {diff.stderr.strip()}")

lines: set[int] = set()
for diff_line in diff.stdout.splitlines():
match = DIFF_HUNK_PATTERN.match(diff_line)
if match is None:
continue
start = int(match[1])
count = int(match[2] or 1)
lines.update(range(start, start + count))
return lines
Loading
Loading