Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

421 lines
14 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
"""Thin capability probes for test gating.
This module exposes small ``has_*`` predicates that report whether the
current environment can run a given feature. They are meant to be used
with plain pytest markers and ``skipif``::
import pytest
import tvm.testing
@pytest.mark.gpu
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
def test_my_cuda_kernel():
...
Each probe's expensive query (device lookup, ``nvcc`` subprocess, libinfo)
is memoized with :func:`functools.cache`, so it runs at most once per
process even though ``skipif`` evaluates the predicate at import time for
every decorated test. Probes never raise: when support is absent they
return ``False`` (or a zero version tuple) rather than propagating an
error out of collection.
Three kinds of probe live here:
* **runtime device** probes (``has_cuda``, ``has_gpu`` …) ask whether a
usable device of a given kind is present;
* **build-support** probes (``has_cudnn`` …, ``build_flag_enabled`` …) ask whether
an optional library was compiled into the runtime;
* **version / capability** probes (``has_cuda_compute``,
``has_nvcc_version`` …) ask about a finer capability of a present device
or toolchain.
"""
import functools
import os
import tvm
__all__ = [
"build_flag_enabled",
"has_adreno_opencl",
# cpu features
"has_cpu_feature",
"has_cublas",
# runtime device
"has_cuda",
# version / capability
"has_cuda_compute",
"has_cudagraph",
# build support
"has_cudnn",
"has_gpu",
# toolchain / environment
"has_hexagon",
"has_hexagon_toolchain",
"has_hipblas",
"has_llvm",
"has_llvm_min_version",
"has_matrixcore",
"has_metal",
"has_multi_gpu",
"has_nccl",
"has_nvcc_version",
"has_nvptx",
"has_nvshmem",
"has_opencl",
"has_rocm",
"has_vulkan",
"has_x86_avx512",
"has_x86_vnni",
]
@functools.cache
def _device_exists(kind: str, index: int = 0) -> bool:
"""Return whether ``tvm.device(kind, index)`` is present and usable."""
try:
return bool(tvm.device(kind, index).exist)
except Exception: # pylint: disable=broad-except
# A missing backend / driver must skip the test, not crash collection.
return False
@functools.cache
def build_flag_enabled(flag: str) -> bool:
"""Return whether an optional build flag (e.g. ``USE_CUTLASS``) is on.
A flag counts as enabled unless it is explicitly disabled, so library
flags carrying a path (rather than a boolean) still register as present.
Callers gate via ``@pytest.mark.skipif(not env.build_flag_enabled("USE_X"), ...)``.
"""
try:
value = tvm.support.libinfo().get(flag, "OFF")
return str(value).lower() not in ("off", "false", "0")
except Exception: # pylint: disable=broad-except
return False
@functools.cache
def _target_enabled(kind: str) -> bool:
"""True if ``kind`` is selected by ``TVM_TEST_TARGETS`` (or the default set).
Honors the ``TVM_TEST_TARGETS`` opt-out, so CI can exclude a flaky
backend (e.g. opencl) via ``TVM_TEST_TARGETS`` and have its tests skip
even when a device is physically present.
"""
try:
from tvm.testing.utils import _tvm_test_targets # pylint: disable=import-outside-toplevel
for target in _tvm_test_targets():
k = target["kind"] if isinstance(target, dict) else str(target).split()[0]
if k == kind:
return True
return False
except Exception: # pylint: disable=broad-except
return True # fail open: the device check still gates
@functools.cache
def _runtime_enabled(kind: str) -> bool:
"""True if the runtime was built with support for target ``kind``.
Used for kinds whose device existence does not imply the backend was
compiled in -- notably ``llvm``, which maps to the always-present CPU
device, so ``tvm.device("llvm").exist`` is True even on ``USE_LLVM=OFF``.
"""
try:
return bool(tvm.runtime.enabled(kind))
except Exception: # pylint: disable=broad-except
return False
def _device_usable(kind: str) -> bool:
"""True if ``kind`` is enabled for this run and a ``kind`` device exists.
The TVM_TEST_TARGETS opt-out is checked first so that an excluded backend
never probes a (possibly crashy) device.
"""
return _target_enabled(kind) and _device_exists(kind)
# --- runtime device probes -------------------------------------------------
def has_cuda() -> bool:
"""True if a CUDA device is present and enabled in TVM_TEST_TARGETS."""
return _device_usable("cuda")
def has_rocm() -> bool:
"""True if a ROCm device is present and enabled in TVM_TEST_TARGETS."""
return _device_usable("rocm")
def has_vulkan() -> bool:
"""True if a Vulkan device is present and enabled in TVM_TEST_TARGETS."""
return _device_usable("vulkan")
def has_metal() -> bool:
"""True if a Metal device is present and enabled in TVM_TEST_TARGETS."""
return _device_usable("metal")
def has_opencl() -> bool:
"""True if an OpenCL device is present and enabled in TVM_TEST_TARGETS."""
return _device_usable("opencl")
def has_nvptx() -> bool:
"""True if NVPTX is usable: a (CUDA) device, plus the LLVM backend it needs."""
return _device_usable("nvptx") and has_llvm()
def has_llvm() -> bool:
"""True if the LLVM backend was built in and enabled in TVM_TEST_TARGETS.
Uses ``tvm.runtime.enabled`` rather than device existence: ``llvm`` maps to
the CPU device, which exists even on a ``USE_LLVM=OFF`` build.
"""
return _target_enabled("llvm") and _runtime_enabled("llvm")
def has_gpu() -> bool:
"""True if any GPU backend (cuda/rocm/opencl/metal/vulkan) is present."""
return (
_device_exists("cuda")
or _device_exists("rocm")
or _device_exists("opencl")
or _device_exists("metal")
or _device_exists("vulkan")
)
@functools.cache
def has_multi_gpu(count: int = 2) -> bool:
"""True if at least ``count`` devices of a single GPU backend exist."""
for kind in ("cuda", "rocm", "opencl", "metal", "vulkan"):
if all(_device_exists(kind, index) for index in range(count)):
return True
return False
# --- build-support probes --------------------------------------------------
#
# These wrap the optional-library build flags. Features that extend CUDA /
# ROCm additionally require the parent device to be present.
def has_cudnn() -> bool:
"""True if cuDNN was built in and a CUDA device is present."""
return has_cuda() and build_flag_enabled("USE_CUDNN")
def has_cublas() -> bool:
"""True if cuBLAS was built in and a CUDA device is present."""
return has_cuda() and build_flag_enabled("USE_CUBLAS")
def has_nccl() -> bool:
"""True if NCCL was built in and a CUDA device is present."""
return has_cuda() and build_flag_enabled("USE_NCCL")
def has_hipblas() -> bool:
"""True if hipBLAS was built in and a ROCm device is present."""
return has_rocm() and build_flag_enabled("USE_HIPBLAS")
@functools.cache
def has_nvshmem() -> bool:
"""True if the disco NVSHMEM runtime is available (requires CUDA).
Probes the runtime global function rather than the ``USE_NVSHMEM`` build
flag, since the flag can be set in builds that do not ship the runtime.
"""
try:
return has_cuda() and (
tvm.get_global_func("runtime.disco.nvshmem.init_nvshmem_uid", allow_missing=True)
is not None
)
except Exception: # pylint: disable=broad-except
return False
# --- version / capability probes -------------------------------------------
@functools.cache
def _cuda_compute_version() -> tuple:
"""Return the (major, minor) CUDA compute version, or (0, 0) if unknown."""
try:
from tvm.support import nvcc # pylint: disable=import-outside-toplevel
arch = nvcc.get_target_compute_version()
return nvcc.parse_compute_version(arch)
except Exception: # pylint: disable=broad-except
return (0, 0)
def has_cuda_compute(major: int, minor: int = 0, exact: bool = False) -> bool:
"""True if the CUDA compute capability satisfies ``(major, minor)``.
When ``exact`` is False (default) the check is ``compute >= (major,
minor)``; when True it requires an exact match. Returns False when no
CUDA device is present, so it implies :func:`has_cuda`.
"""
if not has_cuda():
return False
compute = _cuda_compute_version()
want = (major, minor)
if exact:
return compute == want
return compute >= want
@functools.cache
def _nvcc_version() -> tuple:
"""Return the (major, minor, release) nvcc version, or (0, 0, 0)."""
try:
from tvm.support import nvcc # pylint: disable=import-outside-toplevel
return nvcc.get_cuda_version()
except Exception: # pylint: disable=broad-except
return (0, 0, 0)
def has_nvcc_version(major: int, minor: int = 0, release: int = 0) -> bool:
"""True if a CUDA device is present and nvcc is at least ``(major, minor, release)``.
Returns False when no CUDA device is present, so it implies :func:`has_cuda`.
Gate a test with ``@pytest.mark.skipif(not tvm.testing.env.has_nvcc_version(11, 4),
reason="need nvcc >= 11.4")`` (add ``@pytest.mark.gpu`` for GPU selection).
"""
return has_cuda() and _nvcc_version() >= (major, minor, release)
@functools.cache
def _llvm_version_major() -> int:
"""Return the major LLVM version, or 0 if LLVM is unavailable."""
try:
return int(tvm.target.codegen.llvm_version_major())
except Exception: # pylint: disable=broad-except
return 0
def has_llvm_min_version(major: int) -> bool:
"""True if LLVM is available and its major version is at least ``major``."""
return has_llvm() and _llvm_version_major() >= major
@functools.cache
def has_matrixcore() -> bool:
"""True if a ROCm device with Matrix Core support (compute >= 8) exists."""
try:
from tvm.support import rocm # pylint: disable=import-outside-toplevel
return has_rocm() and bool(rocm.have_matrixcore(tvm.rocm().compute_version))
except Exception: # pylint: disable=broad-except
return False
@functools.cache
def has_cudagraph() -> bool:
"""True if a CUDA device is present and the toolkit supports CUDA Graphs.
Implies :func:`has_cuda`: ``nvcc.have_cudagraph()`` only checks the
toolkit version, so the device guard must be explicit. Gate a test with
``@pytest.mark.skipif(not tvm.testing.env.has_cudagraph(), reason=...)``
(add ``@pytest.mark.gpu`` for CI selection).
"""
try:
from tvm.support import nvcc # pylint: disable=import-outside-toplevel
return has_cuda() and bool(nvcc.have_cudagraph())
except Exception: # pylint: disable=broad-except
return False
# --- toolchain / environment probes ----------------------------------------
@functools.cache
def has_hexagon_toolchain() -> bool:
"""True if the Hexagon toolchain is available for compilation."""
try:
from tvm.contrib.hexagon import ( # pylint: disable=import-outside-toplevel
_ci_env_check,
)
return build_flag_enabled("USE_HEXAGON") and _ci_env_check._compile_time_check() is True
except Exception: # pylint: disable=broad-except
return False
@functools.cache
def has_hexagon() -> bool:
"""True if Hexagon can both compile and run (toolchain + attached device)."""
try:
from tvm.contrib.hexagon import ( # pylint: disable=import-outside-toplevel
_ci_env_check,
)
return has_hexagon_toolchain() and _ci_env_check._run_time_check() is True
except Exception: # pylint: disable=broad-except
return False
@functools.cache
def has_adreno_opencl() -> bool:
"""True if remote Adreno OpenCL testing is configured (RPC_TARGET set)."""
return build_flag_enabled("USE_OPENCL") and os.environ.get("RPC_TARGET") is not None
# --- cpu feature probes ----------------------------------------------------
@functools.cache
def _has_cpu_feature(features) -> bool:
"""True if the host CPU advertises the given LLVM target ``features``."""
try:
codegen = tvm.target.codegen
cpu = codegen.llvm_get_system_cpu()
triple = codegen.llvm_get_system_triple()
target = tvm.target.Target({"kind": "llvm", "mtriple": triple, "mcpu": cpu})
return bool(codegen.target_has_features(features, target))
except Exception: # pylint: disable=broad-except
return False
def has_cpu_feature(features) -> bool:
"""True if the host CPU supports ``features`` (a name or list of names)."""
if isinstance(features, list):
features = tuple(features)
return _has_cpu_feature(features)
def has_x86_vnni() -> bool:
"""True if the host CPU supports x86 VNNI (AVX512-VNNI or AVX-VNNI)."""
return has_cpu_feature("avx512vnni") or has_cpu_feature("avxvnni")
def has_x86_avx512() -> bool:
"""True if the host CPU supports the x86 AVX512 extensions."""
return has_cpu_feature(["avx512bw", "avx512cd", "avx512dq", "avx512vl", "avx512f"])