From d21603f6aa688cddd0badde263e33b15e6680533 Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Fri, 21 Aug 2026 18:35:14 +0800 Subject: [PATCH] fix(cuda.pathfinder): discover Nsight GUI utilities under their current names find_nvidia_binary_utility() only supported the legacy CUDA-toolkit-bundled Nsight GUI names (nsight-sys/nsight-compute), which current standalone Nsight releases no longer ship. Add the modern GUI executable names nsys-ui and ncu-ui to the supported set, and resolve them on Windows from the standalone install registry (host-windows-*/ and host/windows-desktop-*/ directories, mirroring the existing nsys/ncu CLI discovery). The legacy names are kept for backward compatibility. --- .../_binaries/find_nvidia_binary_utility.py | 12 ++- .../_binaries/supported_nvidia_binaries.py | 6 ++ .../pathfinder/_binaries/windows_nsight.py | 31 +++++++ .../tests/test_find_nvidia_binaries.py | 86 ++++++++++++++++++- cuda_pathfinder/tests/test_windows_nsight.py | 34 ++++++++ 5 files changed, 164 insertions(+), 5 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py index 42dcfda1cfb..4dcb366f995 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py @@ -158,8 +158,9 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: Nsight Compute from their installer registry entries. Select architecture-specific binaries using the native machine architecture, independent of Python. Lookup of the standalone - ``nsys`` and ``ncu`` CLIs is terminal; a miss does not fall - through to CUDA Toolkit locations. + ``nsys``/``ncu`` CLIs and the ``nsys-ui``/``ncu-ui`` GUI + launchers is terminal; a miss does not fall through to CUDA + Toolkit locations. 3.2. **CUDA Toolkit installation**: Use ``CUDA_PATH`` or ``CUDA_HOME`` (in that order), searching ``bin/x64``, ``bin/x86_64``, and @@ -215,11 +216,16 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: return found # 3. Search library-specific standalone installations. - # 3.1. Standalone Nsight CLI lookup is terminal; CTK does not contain nsys/ncu. + # 3.1. Standalone Nsight lookup is terminal; CTK does not contain nsys/ncu + # (nor the nsys-ui/ncu-ui GUI launchers). if IS_WINDOWS and utility_name == "nsys": return _resolve_candidate_paths(windows_nsight.nsys_candidate_paths()) if IS_WINDOWS and utility_name == "ncu": return _resolve_candidate_paths(windows_nsight.ncu_candidate_paths()) + if IS_WINDOWS and utility_name == "nsys-ui": + return _resolve_candidate_paths(windows_nsight.nsys_ui_candidate_paths()) + if IS_WINDOWS and utility_name == "ncu-ui": + return _resolve_candidate_paths(windows_nsight.ncu_ui_candidate_paths()) # 3.2. Search in CUDA Toolkit (CUDA_PATH/CUDA_HOME). if (cuda_path := get_cuda_path_or_home()) is not None: diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py b/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py index ac70378f112..20e485e6b3a 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py @@ -25,9 +25,15 @@ "compute-sanitizer": (_CUDA13_BIN, _CUDA_NVCC_BIN), # Profiling tools "nvprof": (_CUDA_NVCC_BIN,), + # Nsight Systems/Compute ship both a CLI (nsys/ncu) and a GUI (nsys-ui/ncu-ui). + # The GUI executable names differ between the legacy CUDA-toolkit-bundled + # products (nsight-sys/nsight-compute) and the current standalone Nsight + # releases (nsys-ui/ncu-ui), so all four names are kept searchable. "nsys": (_NSIGHT_SYSTEMS_BIN,), + "nsys-ui": (_NSIGHT_SYSTEMS_BIN,), "nsight-sys": (_NSIGHT_SYSTEMS_BIN,), "ncu": (_NSIGHT_COMPUTE_BIN,), + "ncu-ui": (_NSIGHT_COMPUTE_BIN,), "nsight-compute": (_NSIGHT_COMPUTE_BIN,), } diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/windows_nsight.py b/cuda_pathfinder/cuda/pathfinder/_binaries/windows_nsight.py index c5944c39e64..53c5060e5b4 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/windows_nsight.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/windows_nsight.py @@ -20,6 +20,19 @@ "arm64": os.path.join("target", "windows-desktop-win10-t23x-a64"), } +# The standalone Nsight GUI launchers live under the host directories, mirrored +# from the CLI target directories above. The GUI executable names are nsys-ui +# and ncu-ui (see issue #2654). +_NSYS_UI_DIR_BY_ARCH = { + "x64": "host-windows-x64", + "arm64": "host-windows-armv8", +} + +_NCU_UI_DIR_BY_ARCH = { + "x64": os.path.join("host", "windows-desktop-win7-x64"), + "arm64": os.path.join("host", "windows-desktop-win10-t23x-a64"), +} + def _installed_product_root(product: str) -> str | None: """Return the active Nsight product installation recorded by its MSI.""" @@ -72,3 +85,21 @@ def ncu_candidate_paths() -> Iterator[str]: target_dir = _NCU_TARGET_DIR_BY_ARCH[windows_machine_arch()] yield os.path.join(install_root, target_dir, "ncu.exe") + + +def nsys_ui_candidate_paths() -> Iterator[str]: + install_root = _installed_product_root("Systems") + if install_root is None: + return + + host_dir = _NSYS_UI_DIR_BY_ARCH[windows_machine_arch()] + yield os.path.join(install_root, host_dir, "nsys-ui.exe") + + +def ncu_ui_candidate_paths() -> Iterator[str]: + install_root = _installed_product_root("Compute") + if install_root is None: + return + + host_dir = _NCU_UI_DIR_BY_ARCH[windows_machine_arch()] + yield os.path.join(install_root, host_dir, "ncu-ui.exe") diff --git a/cuda_pathfinder/tests/test_find_nvidia_binaries.py b/cuda_pathfinder/tests/test_find_nvidia_binaries.py index 9d08f5f4d5c..fea1a5c3809 100644 --- a/cuda_pathfinder/tests/test_find_nvidia_binaries.py +++ b/cuda_pathfinder/tests/test_find_nvidia_binaries.py @@ -33,6 +33,16 @@ def test_supported_binaries_consistency(): assert set(SITE_PACKAGES_BINDIRS).issubset(SUPPORTED_BINARIES_ALL) +def test_nsight_gui_utility_names_are_supported(): + # Current standalone Nsight releases ship the GUI as nsys-ui/ncu-ui, so + # find_nvidia_binary_utility must not raise UnsupportedBinaryError for them. + assert "nsys-ui" in SUPPORTED_BINARIES + assert "ncu-ui" in SUPPORTED_BINARIES + # The legacy CUDA-toolkit-bundled names remain accepted for compatibility. + assert "nsight-sys" in SUPPORTED_BINARIES + assert "nsight-compute" in SUPPORTED_BINARIES + + @pytest.fixture def clear_find_binary_cache(): find_nvidia_binary_utility.cache_clear() @@ -188,7 +198,9 @@ def test_find_compute_sanitizer_uses_canary_ctk_root(monkeypatch, mocker): ("utility_name", "candidate_names"), ( ("nsys", ("nsys.exe",)), + ("nsys-ui", ("nsys-ui.exe",)), ("ncu", ("ncu.bat", "ncu.exe")), + ("ncu-ui", ("ncu-ui.exe",)), ), ) @pytest.mark.usefixtures("clear_find_binary_cache") @@ -202,7 +214,10 @@ def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) monkeypatch.setenv("CONDA_PREFIX", conda_prefix) - candidate_paths = mocker.patch.object(binary_finder_module.windows_nsight, f"{utility_name}_candidate_paths") + # Hyphenated utility names map to underscored candidate-path helpers + # (e.g. ``nsys-ui`` -> ``nsys_ui_candidate_paths``). + helper_name = f"{utility_name.replace('-', '_')}_candidate_paths" + candidate_paths = mocker.patch.object(binary_finder_module.windows_nsight, helper_name) get_cuda_path = mocker.patch.object(binary_finder_module, "get_cuda_path_or_home") canary = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary") checked = _patch_exec_probe(mocker, existing=[expected]) @@ -222,6 +237,20 @@ def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, ( ("nsys", "Systems", "x64", os.path.join("target-windows-x64", "nsys.exe"), ("nsys.exe",)), ("nsys", "Systems", "arm64", os.path.join("target-windows-armv8", "nsys.exe"), ("nsys.exe",)), + ( + "nsys-ui", + "Systems", + "x64", + os.path.join("host-windows-x64", "nsys-ui.exe"), + ("nsys-ui.exe",), + ), + ( + "nsys-ui", + "Systems", + "arm64", + os.path.join("host-windows-armv8", "nsys-ui.exe"), + ("nsys-ui.exe",), + ), ( "ncu", "Compute", @@ -236,6 +265,20 @@ def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, os.path.join("target", "windows-desktop-win10-t23x-a64", "ncu.exe"), ("ncu.bat", "ncu.exe"), ), + ( + "ncu-ui", + "Compute", + "x64", + os.path.join("host", "windows-desktop-win7-x64", "ncu-ui.exe"), + ("ncu-ui.exe",), + ), + ( + "ncu-ui", + "Compute", + "arm64", + os.path.join("host", "windows-desktop-win10-t23x-a64", "ncu-ui.exe"), + ("ncu-ui.exe",), + ), ), ) @pytest.mark.usefixtures("clear_find_binary_cache") @@ -299,7 +342,15 @@ def test_find_binary_windows_ncu_launcher_hit_does_not_resolve_machine_arch(monk canary.assert_not_called() -@pytest.mark.parametrize(("utility_name", "product"), (("nsys", "Systems"), ("ncu", "Compute"))) +@pytest.mark.parametrize( + ("utility_name", "product"), + ( + ("nsys", "Systems"), + ("nsys-ui", "Systems"), + ("ncu", "Compute"), + ("ncu-ui", "Compute"), + ), +) @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_binary_windows_nsight_registry_miss_is_terminal(monkeypatch, mocker, utility_name, product): @@ -351,6 +402,37 @@ def test_find_windows_nsight_legacy_names_remain_literal_in_early_search(monkeyp ncu_candidates.assert_not_called() +@pytest.mark.parametrize("utility_name", ("nsys-ui", "ncu-ui")) +@pytest.mark.usefixtures("clear_find_binary_cache") +@pytest.mark.agent_authored(model="deepseek-v4-flash") +def test_find_windows_nsight_gui_names_remain_literal_in_early_search(monkeypatch, mocker, utility_name): + site_key = os.path.join("nvidia", utility_name, "bin") + site_dir = os.path.join(os.sep, "site-packages", utility_name, "bin") + conda_prefix = os.path.join(os.sep, "conda") + conda_bin = os.path.join(conda_prefix, "Library", "bin") + expected = os.path.join(conda_bin, f"{utility_name}.exe") + + mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) + mocker.patch.object( + binary_finder_module.supported_nvidia_binaries, + "SITE_PACKAGES_BINDIRS", + {utility_name: (site_key,)}, + ) + find_sub_dirs = mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) + monkeypatch.setenv("CONDA_PREFIX", conda_prefix) + get_cuda_path = mocker.patch.object(binary_finder_module, "get_cuda_path_or_home") + nsys_ui_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "nsys_ui_candidate_paths") + ncu_ui_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "ncu_ui_candidate_paths") + checked = _patch_exec_probe(mocker, existing=[expected]) + + assert find_nvidia_binary_utility(utility_name) == os.path.abspath(expected) + assert checked == [os.path.join(site_dir, f"{utility_name}.exe"), expected] + find_sub_dirs.assert_called_once_with(site_key.split(os.sep)) + get_cuda_path.assert_not_called() + nsys_ui_candidates.assert_not_called() + ncu_ui_candidates.assert_not_called() + + @pytest.mark.parametrize("utility_name", ("nsight-sys", "nsight-compute")) @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") diff --git a/cuda_pathfinder/tests/test_windows_nsight.py b/cuda_pathfinder/tests/test_windows_nsight.py index 78da205988b..58e7fcc9b1c 100644 --- a/cuda_pathfinder/tests/test_windows_nsight.py +++ b/cuda_pathfinder/tests/test_windows_nsight.py @@ -75,6 +75,40 @@ def test_ncu_candidate_paths_fall_back_to_machine_binary(mocker, machine_arch, t assert tuple(windows_nsight.ncu_candidate_paths()) == (launcher, expected) +@pytest.mark.parametrize( + ("machine_arch", "host_dir"), + ( + ("x64", "host-windows-x64"), + ("arm64", "host-windows-armv8"), + ), +) +@pytest.mark.agent_authored(model="deepseek-v4-flash") +def test_nsys_ui_candidate_paths_use_machine_arch(mocker, machine_arch, host_dir): + install_root = os.path.join(os.sep, "Program Files", "Nsight Systems") + expected = os.path.join(install_root, host_dir, "nsys-ui.exe") + mocker.patch.object(windows_nsight, "_installed_product_root", return_value=install_root) + mocker.patch.object(windows_nsight, "windows_machine_arch", return_value=machine_arch) + + assert tuple(windows_nsight.nsys_ui_candidate_paths()) == (expected,) + + +@pytest.mark.parametrize( + ("machine_arch", "host_dir"), + ( + ("x64", os.path.join("host", "windows-desktop-win7-x64")), + ("arm64", os.path.join("host", "windows-desktop-win10-t23x-a64")), + ), +) +@pytest.mark.agent_authored(model="deepseek-v4-flash") +def test_ncu_ui_candidate_paths_use_machine_arch(mocker, machine_arch, host_dir): + install_root = os.path.join(os.sep, "Program Files", "Nsight Compute") + expected = os.path.join(install_root, host_dir, "ncu-ui.exe") + mocker.patch.object(windows_nsight, "_installed_product_root", return_value=install_root) + mocker.patch.object(windows_nsight, "windows_machine_arch", return_value=machine_arch) + + assert tuple(windows_nsight.ncu_ui_candidate_paths()) == (expected,) + + @pytest.mark.agent_authored(model="gpt-5.6") def test_installed_product_root_reads_64_bit_registry(mocker): install_root = os.path.join(os.sep, "Program Files", "Nsight Systems")