Skip to content

Commit 949be81

Browse files
tlopextqchengemini-code-assist[bot]
authored
[Docs] Modernize test-gating documentation (#19788)
This pr updates the contributor guide and tvm.testing docstrings/comments to describe the current gating API --------- Co-authored-by: Tianqi Chen <tqchen@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent eb25db1 commit 949be81

5 files changed

Lines changed: 87 additions & 74 deletions

File tree

docs/contribute/code_guide.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,16 @@ If you want your test to run over a variety of targets, use the :py:func:`tvm.te
139139
def test_mytest(target, dev):
140140
...
141141
142-
will run ``test_mytest`` with ``target="llvm"``, ``target="cuda"``, and few others. This also ensures that your test is run on the correct hardware by the CI. If you only want to test against a couple targets use ``@tvm.testing.parametrize_targets("target_1", "target_2")``. If you want to test on a single target, use the associated decorator from :py:func:`tvm.testing`. For example, CUDA tests use the ``@tvm.testing.requires_cuda`` decorator.
142+
will run ``test_mytest`` with ``target="llvm"``, ``target="cuda"``, and few others. This also ensures that your test is run on the correct hardware by the CI. If you only want to test against a couple targets use ``@tvm.testing.parametrize_targets("target_1", "target_2")``. If you want to test on a single target, gate the test on the corresponding capability probe instead of using a per-target decorator. Mark GPU tests with ``@pytest.mark.gpu`` so the CI can select them, and skip when the required feature is unavailable with ``@pytest.mark.skipif``. For example, CUDA tests use:
143+
144+
.. code:: python
145+
146+
@pytest.mark.gpu
147+
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
148+
def test_mycudatest():
149+
...
150+
151+
The ``tvm.testing.env`` module exposes a ``has_*()`` probe for each runtime and hardware feature (e.g. ``has_cuda()``, ``has_rocm()``, ``has_vulkan()``, ``has_llvm()``). To skip a test when an optional Python package is missing, use ``pytest.importorskip("package_name")``.
143152

144153

145154
Network Resources

docs/contribute/testing.rst

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ parameters. For instance, there may be target-specific
111111
implementations that should be tested, where some targets have more
112112
than one implementation. These can be done by explicitly
113113
parametrizing over tuples of arguments, such as shown below. In these
114-
cases, only the explicitly listed targets will run, but they will
115-
still have the appropriate ``@tvm.testing.requires_RUNTIME`` mark
116-
applied to them.
114+
cases, only the explicitly listed targets will run, and each target is
115+
automatically gated on whether it can run on the current machine (a GPU
116+
target gets ``@pytest.mark.gpu`` plus a skip when no device is present).
117117

118118
.. code-block:: python
119119
@@ -134,34 +134,34 @@ marks are as follows.
134134

135135
- ``@pytest.mark.gpu`` - Tags a function as using GPU
136136
capabilities. This has no effect on its own, but can be paired with
137-
command-line arguments ``-m gpu`` or ``-m 'not gpu'`` to restrict
138-
which tests pytest will execute. This should not be called on its
139-
own, but is part of other marks used in unit-tests.
140-
141-
- ``@tvm.testing.uses_gpu`` - Applies ``@pytest.mark.gpu``. This
142-
should be used to mark unit tests that may use the GPU, if one is
143-
present. This decorator is only needed for tests that explicitly
144-
loop over ``tvm.testing.enabled_targets()``, but that is no longer
145-
the preferred style of writing unit tests (see below). When using
146-
``tvm.testing.parametrize_targets()``, this decorator is implicit
147-
for GPU targets, and does not need to be explicitly applied.
148-
149-
- ``@tvm.testing.requires_gpu`` - Applies ``@tvm.testing.uses_gpu``,
150-
and additionally marks that the test should be skipped
151-
(``@pytest.mark.skipif``) entirely if no GPU is present.
152-
153-
- ``@tvm.testing.requires_RUNTIME`` - Several decorators
154-
(e.g. ``@tvm.testing.requires_cuda``), each of which skips a test if
155-
the specified runtime cannot be used. A runtime cannot be used if it
156-
is disabled in the ``config.cmake``, or if a compatible device is
157-
not present. For runtimes that use the GPU, this includes
158-
``@tvm.testing.requires_gpu``.
159-
160-
When using parametrized targets, each test run is decorated with the
161-
``@tvm.testing.requires_RUNTIME`` that corresponds to the target
162-
being used. As a result, if a target is disabled in ``config.cmake``
163-
or does not have appropriate hardware to run, it will be explicitly
164-
listed as skipped.
137+
the command-line arguments ``-m gpu`` or ``-m 'not gpu'`` to restrict
138+
which tests pytest will execute. Apply it to any test that needs a
139+
GPU so that the CI runs it only on GPU nodes.
140+
141+
- ``@pytest.mark.skipif(not tvm.testing.env.has_X(), reason=...)`` -
142+
Skips a test when a required runtime or hardware feature is not
143+
available. The :py:mod:`tvm.testing.env` module exposes one memoized
144+
probe per capability (e.g. ``has_cuda()``, ``has_rocm()``,
145+
``has_vulkan()``, ``has_gpu()``, ``has_llvm()``), each of which
146+
returns ``False`` when the runtime is disabled in ``config.cmake`` or
147+
no compatible device is present. Pair it with ``@pytest.mark.gpu``
148+
for tests that use the GPU::
149+
150+
@pytest.mark.gpu
151+
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
152+
def test_cuda_vectorize_add():
153+
# Test code goes here
154+
155+
- ``pytest.importorskip("package_name")`` - Skips a test (or the whole
156+
module, when called at import time) if an optional Python package is
157+
not installed. Use this instead of a ``skipif`` for package
158+
dependencies.
159+
160+
When using ``tvm.testing.parametrize_targets()``, each parametrized run
161+
is gated automatically on whether its target can run on the current
162+
machine. As a result, if a target is disabled in ``config.cmake`` or
163+
does not have appropriate hardware to run, it will be explicitly listed
164+
as skipped, and GPU targets are tagged with ``@pytest.mark.gpu`` for you.
165165

166166
There also exists a ``tvm.testing.enabled_targets()`` that returns
167167
all targets that are enabled and runnable on the current machine,

python/tvm/testing/env.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,9 @@ def _device_exists(kind: str, index: int = 0) -> bool:
115115
def _build_flag_enabled(flag: str) -> bool:
116116
"""Return whether an optional build flag (e.g. ``USE_CUTLASS``) is on.
117117
118-
Mirrors the historical ``Feature`` check: a flag counts as enabled
119-
unless it is explicitly disabled, so library flags carrying a path
120-
still register as present.
118+
A flag counts as enabled unless it is explicitly disabled, so library
119+
flags carrying a path (rather than a boolean) still register as present.
120+
Callers gate on this via ``@pytest.mark.skipif(not tvm.testing.env.has_cutlass(), ...)``.
121121
"""
122122
try:
123123
value = tvm.support.libinfo().get(flag, "OFF")
@@ -130,8 +130,8 @@ def _build_flag_enabled(flag: str) -> bool:
130130
def _target_enabled(kind: str) -> bool:
131131
"""True if ``kind`` is selected by ``TVM_TEST_TARGETS`` (or the default set).
132132
133-
Restores the historical ``target_kind_enabled`` opt-out, so CI can exclude a
134-
flaky backend (e.g. opencl) via ``TVM_TEST_TARGETS`` and have its tests skip
133+
Honors the ``TVM_TEST_TARGETS`` opt-out, so CI can exclude a flaky
134+
backend (e.g. opencl) via ``TVM_TEST_TARGETS`` and have its tests skip
135135
even when a device is physically present.
136136
"""
137137
try:
@@ -343,8 +343,9 @@ def _nvcc_version() -> tuple:
343343
def has_nvcc_version(major: int, minor: int = 0, release: int = 0) -> bool:
344344
"""True if a CUDA device is present and nvcc is at least ``(major, minor, release)``.
345345
346-
Implies :func:`has_cuda`, matching the historical ``requires_nvcc_version``
347-
decorator which also required the CUDA runtime.
346+
Returns False when no CUDA device is present, so it implies :func:`has_cuda`.
347+
Gate a test with ``@pytest.mark.skipif(not tvm.testing.env.has_nvcc_version(11, 4),
348+
reason="need nvcc >= 11.4")`` (add ``@pytest.mark.gpu`` for GPU selection).
348349
"""
349350
return has_cuda() and _nvcc_version() >= (major, minor, release)
350351

@@ -389,9 +390,10 @@ def has_matrixcore() -> bool:
389390
def has_cudagraph() -> bool:
390391
"""True if a CUDA device is present and the toolkit supports CUDA Graphs.
391392
392-
Implies :func:`has_cuda`, matching the historical ``requires_cudagraph``
393-
decorator (``parent_features="cuda"``): ``nvcc.have_cudagraph()`` only
394-
checks the toolkit version, so the device guard must be explicit.
393+
Implies :func:`has_cuda`: ``nvcc.have_cudagraph()`` only checks the
394+
toolkit version, so the device guard must be explicit. Gate a test with
395+
``@pytest.mark.skipif(not tvm.testing.env.has_cudagraph(), reason=...)``
396+
(add ``@pytest.mark.gpu`` for CI selection).
395397
"""
396398
try:
397399
from tvm.support import nvcc # pylint: disable=import-outside-toplevel

python/tvm/testing/plugin.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,11 @@ def update_parametrize_target_arg(
210210
raise TypeError(msg) from err
211211

212212
if "target" in metafunc.fixturenames:
213-
# Update any explicit use of @pytest.mark.parmaetrize to
214-
# parametrize over targets. This adds the appropriate
215-
# @tvm.testing.requires_* markers for each target.
213+
# Update any explicit use of @pytest.mark.parametrize to
214+
# parametrize over targets. This attaches the appropriate
215+
# per-target gating markers (pytest.mark.gpu for GPU-family
216+
# targets, plus a pytest.mark.skipif guarded by the relevant
217+
# tvm.testing.env.has_*() probe) via _target_to_requirement.
216218
for mark in metafunc.definition.iter_markers("parametrize"):
217219
update_parametrize_target_arg(mark, *mark.args, **mark.kwargs)
218220

python/tvm/testing/utils.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,38 @@
2929
Testing Markers
3030
***************
3131
32-
We use pytest markers to specify the requirements of test functions. Currently
33-
there is a single distinction that matters for our testing environment: does
34-
the test require a gpu. For tests that require just a gpu or just a cpu, we
35-
have the decorator :py:func:`requires_gpu` that enables the test when a gpu is
36-
available. To avoid running tests that don't require a gpu on gpu nodes, this
37-
decorator also sets the pytest marker `gpu` so we can use select the gpu subset
38-
of tests (using `pytest -m gpu`).
39-
40-
Unfortunately, many tests are written like this:
32+
We use pytest markers to specify the requirements of test functions.
33+
Currently there is a single distinction that matters for our testing
34+
environment: does the test require a gpu. Tests that require a gpu are
35+
tagged with the ``gpu`` pytest marker -- the only registered marker (see
36+
the ``markers`` entry in ``pyproject.toml``). This lets us select the
37+
gpu subset of tests with ``pytest -m gpu`` (and exclude them on cpu-only
38+
nodes with ``pytest -m "not gpu"``).
39+
40+
The ``gpu`` marker only controls which testing node a test runs on; it
41+
does not check whether the required hardware or libraries are actually
42+
present. To gate a test on a specific capability, combine the marker
43+
with a ``skipif`` that consults the memoized environment probes in
44+
:py:mod:`tvm.testing.env`:
4145
4246
.. code-block:: python
4347
44-
def test_something():
45-
for target in all_targets():
46-
do_something()
47-
48-
The test uses both gpu and cpu targets, so the test needs to be run on both cpu
49-
and gpu nodes. But we still want to only run the cpu targets on the cpu testing
50-
node. The solution is to mark these tests with the gpu marker so they will be
51-
run on the gpu nodes. But we also modify all_targets (renamed to
52-
enabled_targets) so that it only returns gpu targets on gpu nodes and cpu
53-
targets on cpu nodes (using an environment variable).
54-
55-
Instead of using the all_targets function, future tests that would like to
56-
test against a variety of targets should use the
57-
:py:func:`tvm.testing.parametrize_targets` functionality. This allows us
58-
greater control over which targets are run on which testing nodes.
59-
60-
If in the future we want to add a new type of testing node (for example
61-
fpgas), we need to add a new marker in `tests/python/pytest.ini` and a new
62-
function in this module. Then targets using this node should be added to the
63-
`TVM_TEST_TARGETS` environment variable in the CI.
48+
@pytest.mark.gpu
49+
@pytest.mark.skipif(not tvm.testing.env.has_cuda(), reason="need cuda")
50+
def test_cuda_vectorize_add():
51+
...
52+
53+
There is one ``has_*`` (or ``is_*``) probe per capability -- for example
54+
:py:func:`tvm.testing.env.has_gpu`, :py:func:`tvm.testing.env.has_cuda`,
55+
and :py:func:`tvm.testing.env.has_vulkan`. For optional Python packages,
56+
prefer ``pytest.importorskip("pkg_name")`` instead of a ``skipif``.
57+
58+
To run a test against a variety of targets, use
59+
:py:func:`tvm.testing.parametrize_targets`; it parametrizes the test over
60+
the enabled targets and applies the appropriate ``gpu`` tag and skip
61+
conditions per target automatically. The set of enabled targets is
62+
controlled by the ``TVM_TEST_TARGETS`` environment variable, so the CI
63+
can run different targets on different testing nodes.
6464
6565
"""
6666

0 commit comments

Comments
 (0)