This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 86c45a54 [TEST] Run the Python test suite in parallel with a shared
GPU lock (#654)
86c45a54 is described below
commit 86c45a54ab72aa1d81b01ae86822fae4b959bd86
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Jul 5 23:57:11 2026 +0800
[TEST] Run the Python test suite in parallel with a shared GPU lock (#654)
---
pyproject.toml | 6 +-
python/tvm_ffi/testing/__init__.py | 1 +
python/tvm_ffi/testing/_locking.py | 121 +++++++++++++++++++++++++++
tests/python/test_cubin_launcher.py | 95 ++++++++++++---------
tests/python/test_current_work_stream_gpu.py | 17 ++--
tests/python/test_dlpack_exchange_api.py | 23 +++--
tests/python/test_load_inline.py | 26 ++++--
tests/python/test_stream.py | 94 ++++++++++++---------
tests/python/test_tensor.py | 13 ++-
9 files changed, 287 insertions(+), 109 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index cb3c4592..8cae0f0f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,7 +49,7 @@ torch = [
"numpy",
"ml_dtypes",
]
-test = [{ include-group = "torch" }, "pytest"]
+test = [{ include-group = "torch" }, "pytest", "pytest-xdist"]
dev = [
{ include-group = "test" },
"pre-commit",
@@ -176,6 +176,10 @@ sdist.exclude = [
[tool.pytest.ini_options]
testpaths = ["tests"]
+# Run the suite in parallel across all available cores. GPU-touching tests
+# serialize on a shared machine-local lock via
tvm_ffi.testing.run_with_gpu_lock,
+# so parallel workers do not contend for the device. Pass "-n0" to disable.
+addopts = ["-n", "auto"]
[tool.ruff]
include = ["python/**/*.py", "tests/**/*.py"]
diff --git a/python/tvm_ffi/testing/__init__.py
b/python/tvm_ffi/testing/__init__.py
index 4061bd63..22bcbb3b 100644
--- a/python/tvm_ffi/testing/__init__.py
+++ b/python/tvm_ffi/testing/__init__.py
@@ -17,6 +17,7 @@
"""Testing utilities."""
from ._ffi_api import * # noqa: F403
+from ._locking import run_with_gpu_lock
from .testing import (
TestCompare,
TestCustomCompare,
diff --git a/python/tvm_ffi/testing/_locking.py
b/python/tvm_ffi/testing/_locking.py
new file mode 100644
index 00000000..19a3f7e5
--- /dev/null
+++ b/python/tvm_ffi/testing/_locking.py
@@ -0,0 +1,121 @@
+# 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.
+"""Helpers for tests that use exclusive machine-local resources.
+
+Parallel test runners (for example ``pytest -n auto``) spread tests across
+several worker processes. Tests that reach for a shared, machine-local
+resource such as a single GPU must serialize with each other so that
+concurrent access does not exhaust device memory or corrupt device state.
+:func:`run_with_gpu_lock` provides that serialization through an advisory
+file lock shared by every cooperating worker on the machine.
+"""
+
+from __future__ import annotations
+
+import getpass
+import os
+import tempfile
+from collections.abc import Callable
+from pathlib import Path
+from typing import Any, TypeVar
+
+from ..utils import FileLock
+
+_LOCK_DIR_ENV_VAR = "TVM_FFI_TEST_LOCK_DIR"
+_LOCK_DIR_PREFIX = "tvm-ffi-test-locks"
+_LOCK_FILENAME = "gpu.lock"
+_R = TypeVar("_R")
+
+# Resolved GPU lock path, cached on the first ``run_with_gpu_lock`` call.
+#
+# Init is lazy (not at import) because importing does not imply a GPU test will
+# run, and resolving the path creates a directory. Concurrent first-callers may
+# race to set this global, but the race is benign: every worker derives the
same
+# path.
+_GPU_LOCK_PATH: Path | None = None
+
+
+def _ensure_gpu_lock_path() -> Path:
+ """Return the path to the machine-local GPU lock file, creating its
directory.
+
+ Returns
+ -------
+ path
+ The full path to the ``gpu.lock`` file. The parent directory is created
+ if it does not yet exist.
+
+ Notes
+ -----
+ The lock directory defaults to a per-user directory under the system
+ temporary directory, ``<tempdir>/tvm-ffi-test-locks-<user>``. Scoping the
+ default to the current user avoids ownership and permission conflicts when
+ several users share one host. It can be redirected with the
+ ``TVM_FFI_TEST_LOCK_DIR`` environment variable when all cooperating
+ processes need an explicitly shared machine-local path.
+
+ """
+ lock_dir_override = os.environ.get(_LOCK_DIR_ENV_VAR)
+ if lock_dir_override:
+ lock_dir = Path(lock_dir_override).expanduser()
+ else:
+ # Tag the default directory with the current user, falling back to the
+ # numeric uid then ``unknown`` when a login name cannot be resolved.
+ try:
+ user_tag = getpass.getuser()
+ except Exception:
+ uid = getattr(os, "getuid", None)
+ user_tag = str(uid()) if uid is not None else "unknown"
+ lock_dir = Path(tempfile.gettempdir()) /
f"{_LOCK_DIR_PREFIX}-{user_tag}"
+
+ lock_dir.mkdir(parents=True, exist_ok=True)
+ return lock_dir / _LOCK_FILENAME
+
+
+def run_with_gpu_lock(func: Callable[..., _R], /, *args: Any, **kwargs: Any)
-> _R:
+ """Run a callable while holding the machine-local GPU lock.
+
+ The lock serializes GPU access across parallel test workers so that
+ concurrent device use does not break GPU-related tests. Pass a callable
+ that contains the complete live-device lifetime (device creation,
+ allocation, execution, synchronization, and result checks); keep work that
+ does not touch the device, such as source compilation, outside the callable
+ so it can still run in parallel.
+
+ Parameters
+ ----------
+ func
+ Callable containing the complete live local-GPU lifetime.
+ args
+ Positional arguments forwarded to ``func``.
+ kwargs
+ Keyword arguments forwarded to ``func``.
+
+ Returns
+ -------
+ result
+ The return value of ``func``.
+
+ """
+ # Resolve and cache the lock path on the first call (see ``_GPU_LOCK_PATH``
+ # above for why this is lazy and why the concurrent-init race is benign).
+ global _GPU_LOCK_PATH # noqa: PLW0603 -- intentional first-call
memoization cache
+ lock_path = _GPU_LOCK_PATH
+ if lock_path is None:
+ lock_path = _ensure_gpu_lock_path()
+ _GPU_LOCK_PATH = lock_path
+ with FileLock(str(lock_path)):
+ return func(*args, **kwargs)
diff --git a/tests/python/test_cubin_launcher.py
b/tests/python/test_cubin_launcher.py
index d6f4e35b..bc038d09 100644
--- a/tests/python/test_cubin_launcher.py
+++ b/tests/python/test_cubin_launcher.py
@@ -32,6 +32,7 @@ except ImportError:
torch = None # ty: ignore[invalid-assignment]
import tvm_ffi.cpp
+from tvm_ffi.testing import run_with_gpu_lock
# Check if CUDA is available
@@ -201,28 +202,32 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(launch_mul_two,
cubin_test::LaunchMulTwo);
extra_ldflags=["-lcudart"],
)
- # Load CUBIN from bytes
- load_fn = mod["load_cubin_data"]
- load_fn(cubin_bytes)
+ def run_and_check() -> None:
+ assert torch is not None
+ # Load CUBIN from bytes
+ load_fn = mod["load_cubin_data"]
+ load_fn(cubin_bytes)
- # Test add_one kernel
- launch_add_one = mod["launch_add_one"]
- n = 256
- x = torch.arange(n, dtype=torch.float32, device="cuda")
- y = torch.empty(n, dtype=torch.float32, device="cuda")
+ # Test add_one kernel
+ launch_add_one = mod["launch_add_one"]
+ n = 256
+ x = torch.arange(n, dtype=torch.float32, device="cuda")
+ y = torch.empty(n, dtype=torch.float32, device="cuda")
- launch_add_one(x, y)
- expected = x + 1
- torch.testing.assert_close(y, expected)
+ launch_add_one(x, y)
+ expected = x + 1
+ torch.testing.assert_close(y, expected)
- # Test mul_two kernel
- launch_mul_two = mod["launch_mul_two"]
- x = torch.arange(n, dtype=torch.float32, device="cuda") * 0.5
- y = torch.empty(n, dtype=torch.float32, device="cuda")
+ # Test mul_two kernel
+ launch_mul_two = mod["launch_mul_two"]
+ x = torch.arange(n, dtype=torch.float32, device="cuda") * 0.5
+ y = torch.empty(n, dtype=torch.float32, device="cuda")
- launch_mul_two(x, y)
- expected = x * 2
- torch.testing.assert_close(y, expected)
+ launch_mul_two(x, y)
+ expected = x * 2
+ torch.testing.assert_close(y, expected)
+
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(sys.platform != "linux", reason="CUBIN launcher only
supported on Linux")
@@ -299,17 +304,21 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(launch_add_one_ex,
cubin_test_launch_ex::LaunchAdd
extra_ldflags=["-lcudart"],
)
- load_fn = mod["load_cubin_data"]
- load_fn(cubin_bytes)
+ def run_and_check() -> None:
+ assert torch is not None
+ load_fn = mod["load_cubin_data"]
+ load_fn(cubin_bytes)
+
+ launch_add_one_ex = mod["launch_add_one_ex"]
+ n = 256
+ x = torch.arange(n, dtype=torch.float32, device="cuda")
+ y = torch.empty(n, dtype=torch.float32, device="cuda")
- launch_add_one_ex = mod["launch_add_one_ex"]
- n = 256
- x = torch.arange(n, dtype=torch.float32, device="cuda")
- y = torch.empty(n, dtype=torch.float32, device="cuda")
+ launch_add_one_ex(x, y)
+ expected = x + 1
+ torch.testing.assert_close(y, expected)
- launch_add_one_ex(x, y)
- expected = x + 1
- torch.testing.assert_close(y, expected)
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(sys.platform != "linux", reason="CUBIN launcher only
supported on Linux")
@@ -387,21 +396,25 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(launch_mul_two,
cubin_test_chain::LaunchMulTwo);
mod = tvm_ffi.cpp.load_inline("cubin_test_chain", cuda_sources=cpp_code)
- # Load CUBIN from bytes
- load_fn = mod["load_cubin_data"]
- load_fn(cubin_bytes)
+ def run_and_check() -> None:
+ assert torch is not None
+ # Load CUBIN from bytes
+ load_fn = mod["load_cubin_data"]
+ load_fn(cubin_bytes)
+
+ launch_add_one = mod["launch_add_one"]
+ launch_mul_two = mod["launch_mul_two"]
- launch_add_one = mod["launch_add_one"]
- launch_mul_two = mod["launch_mul_two"]
+ # Test chained execution: (x + 1) * 2
+ n = 128
+ x = torch.full((n,), 5.0, dtype=torch.float32, device="cuda")
+ temp = torch.empty(n, dtype=torch.float32, device="cuda")
+ y = torch.empty(n, dtype=torch.float32, device="cuda")
- # Test chained execution: (x + 1) * 2
- n = 128
- x = torch.full((n,), 5.0, dtype=torch.float32, device="cuda")
- temp = torch.empty(n, dtype=torch.float32, device="cuda")
- y = torch.empty(n, dtype=torch.float32, device="cuda")
+ launch_add_one(x, temp) # temp = x + 1 = 6
+ launch_mul_two(temp, y) # y = temp * 2 = 12
- launch_add_one(x, temp) # temp = x + 1 = 6
- launch_mul_two(temp, y) # y = temp * 2 = 12
+ expected = torch.full((n,), 12.0, dtype=torch.float32, device="cuda")
+ torch.testing.assert_close(y, expected)
- expected = torch.full((n,), 12.0, dtype=torch.float32, device="cuda")
- torch.testing.assert_close(y, expected)
+ run_with_gpu_lock(run_and_check)
diff --git a/tests/python/test_current_work_stream_gpu.py
b/tests/python/test_current_work_stream_gpu.py
index 910962e0..aed771a3 100644
--- a/tests/python/test_current_work_stream_gpu.py
+++ b/tests/python/test_current_work_stream_gpu.py
@@ -20,6 +20,7 @@ from __future__ import annotations
import ctypes
import pytest
+from tvm_ffi.testing import run_with_gpu_lock
try:
import torch
@@ -88,9 +89,13 @@ def test_current_work_stream_matches_torch_stream() -> None:
extra_include_paths=include_paths,
)
- device_id = torch.cuda.current_device()
- is_hip = torch.version.hip is not None
- stream = torch.cuda.Stream(device=device_id)
- with torch.cuda.stream(stream):
- expected_stream = int(stream.cuda_stream)
- mod.assert_current_work_stream(api_ptr, is_hip, expected_stream)
+ def run_and_check() -> None:
+ assert torch is not None
+ device_id = torch.cuda.current_device()
+ is_hip = torch.version.hip is not None
+ stream = torch.cuda.Stream(device=device_id)
+ with torch.cuda.stream(stream):
+ expected_stream = int(stream.cuda_stream)
+ mod.assert_current_work_stream(api_ptr, is_hip, expected_stream)
+
+ run_with_gpu_lock(run_and_check)
diff --git a/tests/python/test_dlpack_exchange_api.py
b/tests/python/test_dlpack_exchange_api.py
index 0938a251..f37e3b98 100644
--- a/tests/python/test_dlpack_exchange_api.py
+++ b/tests/python/test_dlpack_exchange_api.py
@@ -31,6 +31,7 @@ try:
import tvm_ffi
from torch.utils import cpp_extension
from tvm_ffi import libinfo
+ from tvm_ffi.testing import run_with_gpu_lock
except ImportError:
torch = None # ty: ignore[invalid-assignment]
@@ -223,17 +224,21 @@ def test_dlpack_exchange_api_gpu_tensor_metadata() ->
None:
assert torch is not None
echo = tvm_ffi.get_global_func("testing.echo")
- for shape in [(512,), (512, 512), (2, 3, 4)]:
- source = torch.empty(shape, device="cuda", dtype=torch.float16)
+ def run_and_check() -> None:
+ assert torch is not None
+ for shape in [(512,), (512, 512), (2, 3, 4)]:
+ source = torch.empty(shape, device="cuda", dtype=torch.float16)
- tvm_tensor = tvm_ffi.from_dlpack(source)
- assert tvm_tensor.shape == shape
- assert tvm_tensor.dtype == tvm_ffi.dtype("float16")
+ tvm_tensor = tvm_ffi.from_dlpack(source)
+ assert tvm_tensor.shape == shape
+ assert tvm_tensor.dtype == tvm_ffi.dtype("float16")
- echoed = echo(source)
- assert tuple(echoed.shape) == shape
- assert echoed.dtype == source.dtype
- assert echoed.device == source.device
+ echoed = echo(source)
+ assert tuple(echoed.shape) == shape
+ assert echoed.dtype == source.dtype
+ assert echoed.device == source.device
+
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(not _has_dlpack_api, reason="PyTorch DLPack Exchange API
not available")
diff --git a/tests/python/test_load_inline.py b/tests/python/test_load_inline.py
index 672a264c..3cc62da5 100644
--- a/tests/python/test_load_inline.py
+++ b/tests/python/test_load_inline.py
@@ -27,6 +27,7 @@ except ImportError:
import tvm_ffi.cpp
from tvm_ffi.module import Module
+from tvm_ffi.testing import run_with_gpu_lock
def test_load_inline_cpp() -> None:
@@ -191,7 +192,8 @@ def test_load_inline_cuda() -> None:
functions=["add_one_cuda"],
)
- if torch is not None:
+ def run_and_check() -> None:
+ assert torch is not None
# test with raw stream
x_cuda = torch.asarray([1, 2, 3, 4, 5], dtype=torch.float32,
device="cuda")
y_cuda = torch.empty_like(x_cuda)
@@ -206,6 +208,8 @@ def test_load_inline_cuda() -> None:
stream.synchronize()
torch.testing.assert_close(x_cuda + 1, y_cuda)
+ run_with_gpu_lock(run_and_check)
+
@pytest.mark.skipif(torch is None, reason="Requires torch")
def test_load_inline_with_env_tensor_allocator() -> None:
@@ -242,7 +246,7 @@ def test_load_inline_with_env_tensor_allocator() -> None:
)
assert torch is not None
- def run_check() -> None:
+ def run_and_check() -> None:
"""Must run in a separate function to ensure deletion happens before
mod unloads.
When a module returns an object, the object deleter address is part of
the
@@ -257,7 +261,7 @@ def test_load_inline_with_env_tensor_allocator() -> None:
assert y_cpu.dtype == torch.float32
torch.testing.assert_close(x_cpu + 1, y_cpu)
- run_check()
+ run_and_check()
@pytest.mark.skipif(
@@ -321,10 +325,14 @@ def test_load_inline_both() -> None:
mod.add_one_cpu(x, y)
numpy.testing.assert_equal(x + 1, y)
- x_cuda = torch.asarray([1, 2, 3, 4, 5], dtype=torch.float32, device="cuda")
- y_cuda = torch.empty_like(x_cuda)
- mod.add_one_cuda(x_cuda, y_cuda)
- torch.testing.assert_close(x_cuda + 1, y_cuda)
+ def run_and_check() -> None:
+ assert torch is not None
+ x_cuda = torch.asarray([1, 2, 3, 4, 5], dtype=torch.float32,
device="cuda")
+ y_cuda = torch.empty_like(x_cuda)
+ mod.add_one_cuda(x_cuda, y_cuda)
+ torch.testing.assert_close(x_cuda + 1, y_cuda)
+
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(
@@ -349,7 +357,7 @@ def test_cuda_memory_alloc_noleak() -> None:
functions=["return_tensor"],
)
- def run_check() -> None:
+ def run_and_check() -> None:
"""Must run in a separate function to ensure deletion happens before
mod unloads."""
assert torch is not None
x = torch.arange(1024 * 1024, dtype=torch.float32, device="cuda")
@@ -361,4 +369,4 @@ def test_cuda_memory_alloc_noleak() -> None:
# memory should not grow as we loop over
assert diff <= 1024**2 * 8
- run_check()
+ run_with_gpu_lock(run_and_check)
diff --git a/tests/python/test_stream.py b/tests/python/test_stream.py
index afc9139b..ffdd122e 100644
--- a/tests/python/test_stream.py
+++ b/tests/python/test_stream.py
@@ -22,6 +22,7 @@ import ctypes
import pytest
import tvm_ffi
import tvm_ffi.cpp
+from tvm_ffi.testing import run_with_gpu_lock
try:
import torch
@@ -83,21 +84,26 @@ def test_raw_stream() -> None:
def test_torch_stream() -> None:
assert torch is not None
mod = gen_check_stream_mod()
- device_id = torch.cuda.current_device()
- device = tvm_ffi.device("cuda", device_id)
- device_type = device.dlpack_device_type()
- stream_1 = torch.cuda.Stream(device_id)
- stream_2 = torch.cuda.Stream(device_id)
- with tvm_ffi.use_torch_stream(torch.cuda.stream(stream_1)):
- assert torch.cuda.current_stream() == stream_1
- mod.check_stream(device_type, device_id, stream_1.cuda_stream)
- with tvm_ffi.use_torch_stream(torch.cuda.stream(stream_2)):
- assert torch.cuda.current_stream() == stream_2
- mod.check_stream(device_type, device_id, stream_2.cuda_stream)
+ def run_and_check() -> None:
+ assert torch is not None
+ device_id = torch.cuda.current_device()
+ device = tvm_ffi.device("cuda", device_id)
+ device_type = device.dlpack_device_type()
+ stream_1 = torch.cuda.Stream(device_id)
+ stream_2 = torch.cuda.Stream(device_id)
+ with tvm_ffi.use_torch_stream(torch.cuda.stream(stream_1)):
+ assert torch.cuda.current_stream() == stream_1
+ mod.check_stream(device_type, device_id, stream_1.cuda_stream)
+
+ with tvm_ffi.use_torch_stream(torch.cuda.stream(stream_2)):
+ assert torch.cuda.current_stream() == stream_2
+ mod.check_stream(device_type, device_id, stream_2.cuda_stream)
- assert torch.cuda.current_stream() == stream_1
- mod.check_stream(device_type, device_id, stream_1.cuda_stream)
+ assert torch.cuda.current_stream() == stream_1
+ mod.check_stream(device_type, device_id, stream_1.cuda_stream)
+
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(
@@ -106,24 +112,29 @@ def test_torch_stream() -> None:
def test_torch_current_stream() -> None:
assert torch is not None
mod = gen_check_stream_mod()
- device_id = torch.cuda.current_device()
- device = tvm_ffi.device("cuda", device_id)
- device_type = device.dlpack_device_type()
- stream_1 = torch.cuda.Stream(device_id)
- stream_2 = torch.cuda.Stream(device_id)
- with torch.cuda.stream(stream_1):
- assert torch.cuda.current_stream() == stream_1
- with tvm_ffi.use_torch_stream():
- mod.check_stream(device_type, device_id, stream_1.cuda_stream)
- with torch.cuda.stream(stream_2):
- assert torch.cuda.current_stream() == stream_2
+ def run_and_check() -> None:
+ assert torch is not None
+ device_id = torch.cuda.current_device()
+ device = tvm_ffi.device("cuda", device_id)
+ device_type = device.dlpack_device_type()
+ stream_1 = torch.cuda.Stream(device_id)
+ stream_2 = torch.cuda.Stream(device_id)
+ with torch.cuda.stream(stream_1):
+ assert torch.cuda.current_stream() == stream_1
with tvm_ffi.use_torch_stream():
- mod.check_stream(device_type, device_id, stream_2.cuda_stream)
+ mod.check_stream(device_type, device_id, stream_1.cuda_stream)
- assert torch.cuda.current_stream() == stream_1
- with tvm_ffi.use_torch_stream():
- mod.check_stream(device_type, device_id, stream_1.cuda_stream)
+ with torch.cuda.stream(stream_2):
+ assert torch.cuda.current_stream() == stream_2
+ with tvm_ffi.use_torch_stream():
+ mod.check_stream(device_type, device_id,
stream_2.cuda_stream)
+
+ assert torch.cuda.current_stream() == stream_1
+ with tvm_ffi.use_torch_stream():
+ mod.check_stream(device_type, device_id, stream_1.cuda_stream)
+
+ run_with_gpu_lock(run_and_check)
@pytest.mark.skipif(
@@ -132,14 +143,19 @@ def test_torch_current_stream() -> None:
def test_torch_graph() -> None:
assert torch is not None
mod = gen_check_stream_mod()
- device_id = torch.cuda.current_device()
- device = tvm_ffi.device("cuda", device_id)
- device_type = device.dlpack_device_type()
- graph = torch.cuda.CUDAGraph()
- stream = torch.cuda.Stream(device_id)
- x = torch.zeros(1, device="cuda")
- with tvm_ffi.use_torch_stream(torch.cuda.graph(graph, stream=stream)):
- assert torch.cuda.current_stream() == stream
- mod.check_stream(device_type, device_id, stream.cuda_stream)
- # avoid cuda graph no capture warning
- x = x + 1
+
+ def run_and_check() -> None:
+ assert torch is not None
+ device_id = torch.cuda.current_device()
+ device = tvm_ffi.device("cuda", device_id)
+ device_type = device.dlpack_device_type()
+ graph = torch.cuda.CUDAGraph()
+ stream = torch.cuda.Stream(device_id)
+ x = torch.zeros(1, device="cuda")
+ with tvm_ffi.use_torch_stream(torch.cuda.graph(graph, stream=stream)):
+ assert torch.cuda.current_stream() == stream
+ mod.check_stream(device_type, device_id, stream.cuda_stream)
+ # avoid cuda graph no capture warning
+ x = x + 1
+
+ run_with_gpu_lock(run_and_check)
diff --git a/tests/python/test_tensor.py b/tests/python/test_tensor.py
index b89b236a..e21de826 100644
--- a/tests/python/test_tensor.py
+++ b/tests/python/test_tensor.py
@@ -30,6 +30,7 @@ except ImportError:
import numpy as np
import tvm_ffi
+from tvm_ffi.testing import run_with_gpu_lock
def test_tensor_attributes() -> None:
@@ -194,10 +195,14 @@ def test_tensor_from_pytorch_rocm() -> None:
def _check_device(x: tvm_ffi.Tensor) -> str:
return x.device.type
- # PyTorch uses device name "cuda" to represent ROCm device
- x = torch.randn(128, device="cuda")
- device_type = tvm_ffi.get_global_func("testing.check_device")(x)
- assert device_type == "rocm"
+ def run_and_check() -> None:
+ assert torch is not None
+ # PyTorch uses device name "cuda" to represent ROCm device
+ x = torch.randn(128, device="cuda")
+ device_type = tvm_ffi.get_global_func("testing.check_device")(x)
+ assert device_type == "rocm"
+
+ run_with_gpu_lock(run_and_check)
def test_optional_tensor_view() -> None: