dynamo/tests/basic/test_cuda_version_consisten...

159 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Integration test to check CUDA major version consistency across various packages."""
import re
import subprocess
import pytest
# Mark this with every framework to test every container
pytestmark = [
pytest.mark.gpu_0,
pytest.mark.integration,
pytest.mark.parallel,
pytest.mark.post_merge,
pytest.mark.pre_merge,
pytest.mark.sglang,
pytest.mark.trtllm,
pytest.mark.vllm,
]
# Easy to edit later:
IGNORE_PIP_PREFIXES = ("cupy", "nixl")
def sh(cmd: str) -> str:
"""
Run command and return stdout only.
We intentionally drop stderr to avoid noisy tools (pip warnings, etc.).
"""
p = subprocess.run(
["bash", "-lc", f"{cmd} 2>/dev/null"],
stdout=subprocess.PIPE,
text=True,
check=False,
)
return (p.stdout or "").strip()
def major_from_text(text: str) -> int | None:
"""Extract CUDA major (12 or 13) from arbitrary text; otherwise None."""
if not text:
return None
# fmt: off
pats = [
r"\bCUDA_VERSION=(1[23])\.", # CUDA_VERSION=13.0.2
r"\bNV_CUDA_.*?_VERSION=(1[23])\.", # NV_CUDA_CUDART_VERSION=13.0...
r"\+cuda(1[23])\.", # ...+cuda13.0
r"\bcuda\s*>=\s*(1[23])\.", # cuda>=13.0 ...
r"\brelease\s+(1[23])\.", # nvcc: release 13.0
r"-(1[23])-\d\b", # dpkg: ...-13-0
r"\bcuda(1[23])x\b", # cupy-cuda12x (from name)
r"[-+]cu(1[23])", # -cu13 or +cu13 in name
]
# fmt: on
for pat in pats:
m = re.search(pat, text, flags=re.IGNORECASE)
if m:
maj = int(m.group(1))
if maj in (12, 13):
return maj
return None
def pip_cuda_major_from_line(line: str) -> int | None:
"""
Given a pip freeze line like 'torch==2.9.0+cu130', infer CUDA major.
Returns 12/13 or None.
"""
return major_from_text(line)
def keep_pip_line(line: str) -> bool:
"""
Ignore some packages from pip signal (editable allow/ignore policy).
"""
name = line.split("==", 1)[0].strip().lower()
return not name.startswith(IGNORE_PIP_PREFIXES)
def test_cuda_major_consistency() -> None:
"""
Collect CUDA major versions (12/13) from predefined signals and assert consistency.
Prints a readable report with full relevant output when failing.
"""
signals = [
("env:CUDA_VERSION", "env | grep -i '^CUDA_VERSION='"),
("env:NV_CUDA_CUDART_VERSION", "env | grep -i '^NV_CUDA_CUDART_VERSION='"),
("env:NV_CUDA_LIB_VERSION", "env | grep -i '^NV_CUDA_LIB_VERSION='"),
("env:NV_LIBNCCL_PACKAGE", "env | grep -i '^NV_LIBNCCL_PACKAGE='"),
("env:NVIDIA_REQUIRE_CUDA", "env | grep -i '^NVIDIA_REQUIRE_CUDA='"),
("nvcc", "nvcc --version | grep -i 'release' || nvcc --version"),
("dpkg:cuda-*", "dpkg -l | grep -E '^(ii|hi)\\s+cuda-.*-(12|13)-'"),
(
"dpkg:libcublas/libnccl",
"dpkg -l | grep -E '^(ii|hi)\\s+lib(cublas|nccl).*-(12|13)-'",
),
# pip signal: gather a targeted list, then infer majors per line (excluding ignored prefixes)
(
"pip:selected",
"python -m pip list --format=freeze | grep -Ei '(cuda|cudnn|nccl|nvshmem|\\+cu(12|13)[0-9]{2}|-cu(12|13)|^(torch|torchaudio|torchvision)==)'",
),
]
rows: list[tuple[str, int | None, list[str]]] = []
for label, cmd in signals:
out = sh(cmd)
lines = [ln.strip() for ln in out.splitlines() if ln.strip()]
if label.startswith("pip:"):
lines = [ln for ln in lines if keep_pip_line(ln)]
majors = {pip_cuda_major_from_line(ln) for ln in lines}
majors.discard(None)
maj = majors.pop() if len(majors) == 1 else None # None if ambiguous/mixed
# If mixed, well surface it via the global consistency check below.
else:
maj = major_from_text(out)
rows.append((label, maj, lines if lines else ["<no output>"]))
# Compute all detected majors across *all* signals, including per-line pip majors.
detected: list[int] = []
for label, maj, lines in rows:
if label.startswith("pip:"):
for ln in lines:
m = pip_cuda_major_from_line(ln)
if m is not None:
detected.append(m)
else:
if maj is not None:
detected.append(maj)
if not detected:
pytest.skip("No CUDA major (12/13) detected from any signal.")
unique = sorted(set(detected))
# Build a readable multi-line report (no truncation to first line).
report = [
"CUDA major signals (pip ignores prefixes: "
+ ", ".join(IGNORE_PIP_PREFIXES)
+ "):"
]
for label, maj, lines in rows:
maj_s = str(maj) if maj is not None else "-"
report.append(f" {maj_s:>2} {label}")
for ln in lines[:50]: # keep it readable; adjust if you want more
report.append(f" {ln}")
if len(lines) > 50:
report.append(f" ... ({len(lines) - 50} more lines)")
assert len(unique) == 1, (
"\n".join(report) + f"\n\nInconsistent CUDA majors detected: {unique}"
)