Skip to content

Commit 1ea55d9

Browse files
authored
Merge branch 'main' into include-nvml-in-docs
2 parents a536e0b + bffbeec commit 1ea55d9

12 files changed

Lines changed: 233 additions & 214 deletions

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ guide for package-specific conventions and workflows.
2323
(search), `read_file`, `list_dir`, `glob_file_search`, `apply_patch`,
2424
`todo_write/update_plan`. Use `cmd`/`run_terminal_cmd` only when no listed
2525
tool can perform the action.
26+
- If `pixi` is available for this repo, prefer `pixi run ...` or the matching
27+
`pixi` task over invoking raw `python`, `pytest`, `pip`, or similar tools
28+
directly so commands run in the repository-managed environment.
29+
- When extracting or transforming JSON in shell workflows, prefer `jq` over
30+
one-off Python parsing. For `gh` commands that return JSON, prefer the
31+
built-in `--jq` flag instead of piping the output into `python`.
2632
- When multiple tool calls can be parallelized (e.g., todo updates with other
2733
actions, file searches, reading files), make these tool calls in parallel
2834
instead of sequential. Avoid single calls that might not yield a useful

cuda_bindings/cuda/bindings/_internal/utils.pyx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,14 @@ cdef int get_nested_resource_ptr(nested_resource[ResT] &in_out_ptr, object obj,
120120
nested_ptr.reset(nested_vec, True)
121121
for i, obj_i in enumerate(obj):
122122
if ResT is char:
123-
obj_i_bytes = (<str?>(obj_i)).encode()
123+
obj_i_type = type(obj_i)
124+
if obj_i_type is str:
125+
obj_i_bytes = obj_i.encode("utf-8")
126+
elif obj_i_type is bytes:
127+
obj_i_bytes = obj_i
128+
else:
129+
raise TypeError(
130+
f"Expected str or bytes, got {obj_i_type.__name__}")
124131
str_len = <size_t>(len(obj_i_bytes)) + 1 # including null termination
125132
deref(nested_res_vec)[i].resize(str_len)
126133
obj_i_ptr = <char*>(obj_i_bytes)

cuda_bindings/tests/test_nvjitlink.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ def test_create_and_destroy(option):
100100
nvjitlink.destroy(handle)
101101

102102

103+
def test_create_and_destroy_bytes_options():
104+
handle = nvjitlink.create(1, [b"-arch=sm_80"])
105+
assert handle != 0
106+
nvjitlink.destroy(handle)
107+
108+
103109
@pytest.mark.parametrize("option", ARCHITECTURES)
104110
def test_complete_empty(option):
105111
handle = nvjitlink.create(1, [f"-arch={option}"])

cuda_bindings/tests/test_nvvm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def test_get_buffer_empty(get_size, get_buffer):
126126
assert buffer == b"\x00"
127127

128128

129-
@pytest.mark.parametrize("options", [[], ["-opt=0"], ["-opt=3", "-g"]])
129+
@pytest.mark.parametrize("options", [[], ["-opt=0"], ["-opt=3", "-g"], [b"-opt=0"]])
130130
def test_compile_program_with_minimal_nvvm_ir(minimal_nvvmir, options):
131131
with nvvm_program() as prog:
132132
nvvm.add_module_to_program(prog, minimal_nvvmir, len(minimal_nvvmir), "FileNameHere.ll")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from __future__ import annotations
6+
7+
import json
8+
import os
9+
import sys
10+
import traceback
11+
from collections.abc import Sequence
12+
13+
DYNAMIC_LIB_NOT_FOUND_MARKER = "CHILD_LOAD_NVIDIA_DYNAMIC_LIB_HELPER_DYNAMIC_LIB_NOT_FOUND_ERROR:"
14+
15+
16+
def _validate_abs_path(abs_path: str) -> None:
17+
assert abs_path, f"empty path: {abs_path=!r}"
18+
assert os.path.isabs(abs_path), f"not absolute: {abs_path=!r}"
19+
assert os.path.isfile(abs_path), f"not a file: {abs_path=!r}"
20+
21+
22+
def _load_nvidia_dynamic_lib_for_test(libname: str) -> str:
23+
# Keep imports inside the subprocess body so startup stays focused on the
24+
# code under test rather than the parent test module.
25+
from cuda.pathfinder import load_nvidia_dynamic_lib
26+
from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL
27+
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import _load_lib_no_cache
28+
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import (
29+
SUPPORTED_LINUX_SONAMES,
30+
SUPPORTED_WINDOWS_DLLS,
31+
)
32+
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS
33+
34+
def require_abs_path(loaded_dl: LoadedDL) -> str:
35+
abs_path = loaded_dl.abs_path
36+
if not isinstance(abs_path, str):
37+
raise RuntimeError(f"loaded dynamic library is missing abs_path: {loaded_dl!r}")
38+
_validate_abs_path(abs_path)
39+
return abs_path
40+
41+
loaded_dl_fresh: LoadedDL = load_nvidia_dynamic_lib(libname)
42+
if loaded_dl_fresh.was_already_loaded_from_elsewhere:
43+
raise RuntimeError("loaded_dl_fresh.was_already_loaded_from_elsewhere")
44+
45+
fresh_abs_path = require_abs_path(loaded_dl_fresh)
46+
assert loaded_dl_fresh.found_via is not None
47+
48+
loaded_dl_from_cache: LoadedDL = load_nvidia_dynamic_lib(libname)
49+
if loaded_dl_from_cache is not loaded_dl_fresh:
50+
raise RuntimeError("loaded_dl_from_cache is not loaded_dl_fresh")
51+
52+
loaded_dl_no_cache = _load_lib_no_cache(libname)
53+
no_cache_abs_path = require_abs_path(loaded_dl_no_cache)
54+
supported_libs = SUPPORTED_WINDOWS_DLLS if IS_WINDOWS else SUPPORTED_LINUX_SONAMES
55+
if not loaded_dl_no_cache.was_already_loaded_from_elsewhere and libname in supported_libs:
56+
raise RuntimeError("not loaded_dl_no_cache.was_already_loaded_from_elsewhere")
57+
if not os.path.samefile(no_cache_abs_path, fresh_abs_path):
58+
raise RuntimeError(f"not os.path.samefile({no_cache_abs_path=!r}, {fresh_abs_path=!r})")
59+
return fresh_abs_path
60+
61+
62+
def probe_load_nvidia_dynamic_lib_and_print_json(libname: str) -> None:
63+
from cuda.pathfinder import DynamicLibNotFoundError
64+
65+
try:
66+
abs_path = _load_nvidia_dynamic_lib_for_test(libname)
67+
except DynamicLibNotFoundError:
68+
sys.stdout.write(f"{DYNAMIC_LIB_NOT_FOUND_MARKER}\n")
69+
traceback.print_exc(file=sys.stdout)
70+
return
71+
sys.stdout.write(f"{json.dumps(abs_path)}\n")
72+
73+
74+
def main(argv: Sequence[str] | None = None) -> int:
75+
args = list(sys.argv[1:] if argv is None else argv)
76+
if len(args) != 1:
77+
raise SystemExit("Usage: python -m cuda.pathfinder._testing.load_nvidia_dynamic_lib_subprocess <libname>")
78+
probe_load_nvidia_dynamic_lib_and_print_json(args[0])
79+
return 0
80+
81+
82+
if __name__ == "__main__":
83+
raise SystemExit(main())

cuda_pathfinder/cuda/pathfinder/_utils/spawned_process_runner.py

Lines changed: 0 additions & 131 deletions
This file was deleted.
Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,54 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
# This helper is factored out so spawned child processes only import this
5-
# lightweight module. That avoids re-importing the test module (and
6-
# repeating its potentially expensive setup) in every child process.
4+
from __future__ import annotations
75

8-
import json
9-
import os
6+
import subprocess
107
import sys
11-
import traceback
8+
import tempfile
9+
from pathlib import Path
1210

11+
from cuda.pathfinder._testing.load_nvidia_dynamic_lib_subprocess import DYNAMIC_LIB_NOT_FOUND_MARKER
1312

14-
def build_child_process_failed_for_libname_message(libname, result):
13+
LOAD_NVIDIA_DYNAMIC_LIB_SUBPROCESS_MODULE = "cuda.pathfinder._testing.load_nvidia_dynamic_lib_subprocess"
14+
# Launch the child from a neutral directory so `python -m cuda.pathfinder...`
15+
# resolves the installed package instead of the source checkout. In CI the
16+
# checkout does not contain the generated `_version.py` file.
17+
LOAD_NVIDIA_DYNAMIC_LIB_SUBPROCESS_CWD = Path(tempfile.gettempdir())
18+
PROCESS_TIMED_OUT = -9
19+
20+
21+
def build_child_process_failed_for_libname_message(libname: str, result: subprocess.CompletedProcess[str]) -> str:
1522
return (
1623
f"Child process failed for {libname=!r} with exit code {result.returncode}\n"
1724
f"--- stdout-from-child-process ---\n{result.stdout}<end-of-stdout-from-child-process>\n"
1825
f"--- stderr-from-child-process ---\n{result.stderr}<end-of-stderr-from-child-process>\n"
1926
)
2027

2128

22-
def validate_abs_path(abs_path):
23-
assert abs_path, f"empty path: {abs_path=!r}"
24-
assert os.path.isabs(abs_path), f"not absolute: {abs_path=!r}"
25-
assert os.path.isfile(abs_path), f"not a file: {abs_path=!r}"
29+
def child_process_reported_dynamic_lib_not_found(result: subprocess.CompletedProcess[str]) -> bool:
30+
return result.stdout.startswith(DYNAMIC_LIB_NOT_FOUND_MARKER)
2631

2732

28-
def child_process_func(libname):
29-
from cuda.pathfinder import DynamicLibNotFoundError, load_nvidia_dynamic_lib
30-
from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import _load_lib_no_cache
31-
from cuda.pathfinder._dynamic_libs.supported_nvidia_libs import (
32-
SUPPORTED_LINUX_SONAMES,
33-
SUPPORTED_WINDOWS_DLLS,
34-
)
35-
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS
36-
33+
def run_load_nvidia_dynamic_lib_in_subprocess(
34+
libname: str,
35+
*,
36+
timeout: float,
37+
) -> subprocess.CompletedProcess[str]:
38+
command = [sys.executable, "-m", LOAD_NVIDIA_DYNAMIC_LIB_SUBPROCESS_MODULE, libname]
3739
try:
38-
loaded_dl_fresh = load_nvidia_dynamic_lib(libname)
39-
except DynamicLibNotFoundError:
40-
print("CHILD_LOAD_NVIDIA_DYNAMIC_LIB_HELPER_DYNAMIC_LIB_NOT_FOUND_ERROR:")
41-
traceback.print_exc(file=sys.stdout)
42-
return
43-
if loaded_dl_fresh.was_already_loaded_from_elsewhere:
44-
raise RuntimeError("loaded_dl_fresh.was_already_loaded_from_elsewhere")
45-
validate_abs_path(loaded_dl_fresh.abs_path)
46-
assert loaded_dl_fresh.found_via is not None
47-
48-
loaded_dl_from_cache = load_nvidia_dynamic_lib(libname)
49-
if loaded_dl_from_cache is not loaded_dl_fresh:
50-
raise RuntimeError("loaded_dl_from_cache is not loaded_dl_fresh")
51-
52-
loaded_dl_no_cache = _load_lib_no_cache(libname)
53-
# check_if_already_loaded_from_elsewhere relies on these:
54-
supported_libs = SUPPORTED_WINDOWS_DLLS if IS_WINDOWS else SUPPORTED_LINUX_SONAMES
55-
if not loaded_dl_no_cache.was_already_loaded_from_elsewhere and libname in supported_libs:
56-
raise RuntimeError("not loaded_dl_no_cache.was_already_loaded_from_elsewhere")
57-
if not os.path.samefile(loaded_dl_no_cache.abs_path, loaded_dl_fresh.abs_path):
58-
raise RuntimeError(f"not os.path.samefile({loaded_dl_no_cache.abs_path=!r}, {loaded_dl_fresh.abs_path=!r})")
59-
validate_abs_path(loaded_dl_no_cache.abs_path)
60-
61-
print(json.dumps(loaded_dl_fresh.abs_path))
40+
return subprocess.run( # noqa: S603 - trusted argv: current interpreter + internal test helper module
41+
command,
42+
capture_output=True,
43+
text=True,
44+
timeout=timeout,
45+
check=False,
46+
cwd=LOAD_NVIDIA_DYNAMIC_LIB_SUBPROCESS_CWD,
47+
)
48+
except subprocess.TimeoutExpired:
49+
return subprocess.CompletedProcess(
50+
args=command,
51+
returncode=PROCESS_TIMED_OUT,
52+
stdout="",
53+
stderr=f"Process timed out after {timeout} seconds and was terminated.",
54+
)

0 commit comments

Comments
 (0)