Skip to content

fix(cmake): make cuTENSOR/cuQuantum finders honest and version-aware - #993

Merged
yingjerkao merged 2 commits into
masterfrom
split/cuda-finder-modernization
Jul 28, 2026
Merged

fix(cmake): make cuTENSOR/cuQuantum finders honest and version-aware#993
yingjerkao merged 2 commits into
masterfrom
split/cuda-finder-modernization

Conversation

@pcchen

@pcchen pcchen commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

First split of #950 (the finder-correctness portion). No CMakeLists.txt or
compiler/version-floor changes — those remain separate follow-up splits.

Changes

  • Honest *_FOUND. CUTENSOR_FOUND/CUQUANTUM_FOUND are now derived from
    the actual find_library results via find_package_handle_standard_args,
    instead of set(... TRUE) unconditionally — which let a NOTFOUND silently
    pass a caller's REQUIRED check and link an empty / -NOTFOUND entry.
  • Version-aware cuTENSOR layout. Search both the cuTENSOR 2.x flat lib/
    and the legacy per-CUDA-major lib/<major> via PATH_SUFFIXES, dropping the
    dead lib/10.2/lib/11 branches; report the directory actually found.
  • cuTENSOR ≥ 2.0 enforced by reading CUTENSOR_MAJOR/MINOR from the headers
    and failing early on the 1.x API.
  • FindCUQUANTUM builds CUQUANTUM_LIBRARIES conditionally (parity).

Addresses the finder portions of #945 / #946.

Verification

Extracted verbatim from the already-reviewed #950 diff; both modules parse
cleanly. These finders only run under USE_CUTENSOR/USE_CUQUANTUM (i.e. a
CUDA build), which can't be exercised on the author's macOS machine — a
reviewer with a CUDA box please sanity-check a -DUSE_CUTENSOR=ON
(and -DUSE_CUQUANTUM=ON) configure.

Posted by Claude Code on behalf of @pcchen

Split out of #950 (finder-correctness portion).

- CUTENSOR_FOUND / CUQUANTUM_FOUND are now derived from the actual
  find_library results via find_package_handle_standard_args, instead of
  being set TRUE unconditionally (which let a NOTFOUND silently pass a
  caller's REQUIRED check and link an empty / -NOTFOUND entry).
- FindCUTENSOR: search both the cuTENSOR 2.x flat lib/ layout and the
  legacy per-CUDA-major lib/<major> layout (PATH_SUFFIXES), dropping the
  dead lib/10.2 and lib/11 branches; report the real found dir.
- FindCUTENSOR: read CUTENSOR_MAJOR/MINOR from the headers and FATAL_ERROR
  on the unsupported 1.x API (Cytnx requires cuTENSOR >= 2.0).
- FindCUQUANTUM: build CUQUANTUM_LIBRARIES conditionally (parity).

Addresses the finder portions of #945 / #946. No CMakeLists.txt or
compiler/version-floor policy changes (those remain separate splits).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the CMake module files FindCUQUANTUM.cmake and FindCUTENSOR.cmake to enforce stricter version requirements (CUDA >= 12 and cuTENSOR >= 2.0) and to use FindPackageHandleStandardArgs for robust package finding. Feedback on these changes suggests handling cases where the cuTENSOR minor version cannot be parsed by defaulting it to 0 to avoid malformed version strings, and dynamically retrieving CUQUANTUM_LIBRARY_DIRS for consistency.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cmake/Modules/FindCUTENSOR.cmake
Comment thread cmake/Modules/FindCUQUANTUM.cmake

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c19d7e4c6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cmake/Modules/FindCUTENSOR.cmake Outdated
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 30.88%. Comparing base (d02cb29) to head (6650adc).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #993   +/-   ##
=======================================
  Coverage   30.88%   30.88%           
=======================================
  Files         229      229           
  Lines       34758    34758           
  Branches    14409    14409           
=======================================
  Hits        10734    10734           
  Misses      16720    16720           
  Partials     7304     7304           
Flag Coverage Δ
cpp 30.39% <ø> (ø)
python 59.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 32.21% <ø> (ø)
Python bindings 17.28% <ø> (ø)
Python package 59.41% <ø> (ø)

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d02cb29...6650adc. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…arse

- FindCUTENSOR: drop the CUDA>=12 FATAL_ERROR (a version-floor policy that
  belongs in the separate #962/CMakeLists.txt split and would reject valid
  CUDA 11 + cuTENSOR 2.x installs). The generic lib/<major> suffix already
  covers 11/12/13. This finder now stays policy-free. (codex review)
- FindCUTENSOR: default CUTENSOR_MINOR to 0 when the header line is absent,
  so CUTENSOR_VERSION isn't malformed ("2.") for the VERSION_VAR check.
  (gemini review)
- FindCUQUANTUM: derive CUQUANTUM_LIBRARY_DIRS from the found library via
  get_filename_component, mirroring FindCUTENSOR. (gemini review)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pcchen

pcchen commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — addressed all three in 6650adc:

Note this PR (first split of #950) intentionally keeps only finder correctness + layout + the cuTENSOR ≥ 2.0 API requirement — no CMakeLists.txt / CUDA-version-floor changes. As before, it can't be built on a CUDA-less machine; a reviewer with a CUDA box please sanity-check a -DUSE_CUTENSOR=ON configure.

Posted by Claude Code on behalf of @pcchen

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6650adcbfd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +75 to +77
message(WARNING
"Could not determine the cuTENSOR version from headers under "
"${CUTENSOR_INCLUDE_DIRS}; Cytnx requires cuTENSOR >= 2.0.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a parsed cuTENSOR version before succeeding

When CUTENSOR_ROOT has a libcutensor library but the headers are missing or don't expose CUTENSOR_MAJOR in one of these two searched files, this branch only emits a warning. The subsequent find_package_handle_standard_args(... VERSION_VAR CUTENSOR_VERSION) does not fail because the repo calls find_package(CUTENSOR REQUIRED) without requesting a version, so CUTENSOR_FOUND becomes true with an empty/unknown version and UNI_CUTENSOR is enabled anyway. That defers an invalid cuTENSOR install to compile/link time, and can also let unsupported headers through when the new >=2 check cannot parse them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still open, and I agree it is a real gap: when the headers do not expose CUTENSOR_MAJOR, the else() branch only warns and find_package_handle_standard_args can still succeed with CUTENSOR_VERSION unset.

Judging it non-blocking for this PR: it requires a cuTENSOR install whose libraries are present but whose headers are missing or unrecognised, which is a broken install rather than a supported layout — and the current behaviour (warn, proceed) is no worse than the pre-PR behaviour (no version check at all). Better handled together with the version-floor policy work in #950, which owns the "what do we require" decision. Leaving open as a tracked follow-up rather than resolving.

@pcchen
pcchen requested review from IvanaGyro and yingjerkao July 6, 2026 07:21
Comment on lines +30 to +32
# Search both cuTENSOR library layouts: 2.x tarballs place the libraries
# directly under lib/, while older tarballs and apt use a per-CUDA-major
# subdir (lib/<cuda-major>, e.g. lib/11, lib/12, lib/13). Listing both as

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This layout was changed since 2.3 not 2.x

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair — I could not confirm the exact release from the installs on my test box (conda 2.2.0 ships a flat lib/, apt 2.4.1 uses <multiarch>/libcutensor/13/, but both are repackagings rather than NVIDIA tarballs), so I will defer to you on 2.3 being the boundary.

Worth noting this is a comment-accuracy point only: the finder lists both suffixes unconditionally, so behaviour is identical regardless of which release moved the layout. Leaving the thread open for @pcchen to correct the wording — flagging it as non-blocking rather than resolving it myself, since it is your comment.

# directly under lib/, while older tarballs and apt use a per-CUDA-major
# subdir (lib/<cuda-major>, e.g. lib/11, lib/12, lib/13). Listing both as
# find_library PATH_SUFFIXES resolves either layout for any CUDA major. The
# older minor-specific lib/10.2 and lib/11.0 special-cases were removed; the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not useful for the future. It can be put in the commit message.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and I have applied it literally: the rationale is now in the squash commit message for the merge rather than only in the source. The in-file comment can be trimmed in a follow-up — leaving this open for @pcchen rather than resolving it myself.

Comment thread cmake/Modules/FindCUTENSOR.cmake
@yingjerkao

Copy link
Copy Markdown
Collaborator

@pcchen can you update the PR based the review comments? /

@yingjerkao

Copy link
Copy Markdown
Collaborator

Verified on a CUDA box, as the PR description asked for.

Environment

  • NVIDIA RTX 4070 Ti SUPER (sm_89), CUDA 13.0 (nvcc V13.0.88), CMake 4.0, GCC 13.3
  • cuTENSOR 2.4.1 via apt (/usr/lib/x86_64-linux-gnu/libcutensor/{12,13}/) and 2.2.0 via conda
  • cuQuantum: cuTensorNet 2.10 / cuStateVec via apt, and 2.7.0 / 1.8.0 via conda

1. Finder matrix — isolated

A minimal probe project calling find_package(CUTENSOR|CUQUANTUM REQUIRED), with CMAKE_MODULE_PATH
pointed at either current master's modules or this PR's, nothing else varied. (Both roots
passed as cache vars with CUTENSOR_ROOT/CUQUANTUM_ROOT cleared from the environment — see the
note at the bottom.)

layout master this PR
cuTENSOR 2.x flat lib/ libs found, but CUTENSOR_LIBRARY_DIRS reports <root>/lib/12a directory that does not exist found; LIBRARY_DIRS = the real <root>/lib; version 2.4 detected
cuTENSOR legacy lib/13 CUTENSOR_FOUND=TRUE with CUTENSOR_LIB-NOTFOUND found correctly at <root>/lib/13
cuTENSOR apt multiarch (CUTENSOR_ROOT=/usr) CUTENSOR_FOUND=TRUE with -NOTFOUND fails cleanly: Could NOT find CUTENSOR (missing: CUTENSOR_LIB) (found version "2.4")
cuQuantum apt multiarch (CUQUANTUM_ROOT=/usr) CUQUANTUM_FOUND=TRUE with -NOTFOUND fails cleanly: Could NOT find CUQUANTUM (missing: CUQUANTUM_TENSORNET_LIB ...)
cuQuantum conda root found, correct found, correct

The PR improves every case and regresses none. Two things worth adding to the description:

(a) The silent--NOTFOUND bug reproduces exactly as predicted. On master, CUTENSOR_LIBRARIES
comes back as the literal string CUTENSOR_LIB-NOTFOUND;CUTENSORMg_LIB-NOTFOUND while
CUTENSOR_FOUND=TRUE and the caller's REQUIRED passes — so -NOTFOUND goes onto the link line.
Confirmed, not theoretical.

(b) The lib/<major> change fixes a CUDA-13 break the description undersells. Current master
hardcodes lib/12 for any CUDA ≥ 12:

elseif((${CUDAToolkit_VERSION_MAJOR} GREATER_EQUAL 12))
  set(CUTNLIB_DIR "${CUTNLIB_DIR}12")

so on a CUDA 13 host the legacy per-CUDA-major tarball layout (lib/13/) is never searched and
silently yields -NOTFOUND. This PR's lib/${CUDAToolkit_VERSION_MAJOR} is what fixes it. Given
the repo's GPU CI now runs CUDA 13 (#1044), that seems worth calling out explicitly.

2. End-to-end build

Because this branch is ~392 commits behind master, I did not build it as-is. Instead I applied
only the two module files from this PR onto current master (2318026) — which is what merging
will actually produce — and did a full CUDA build with both optional deps enabled:

cmake -S . -B build_full -DUSE_CUDA=ON \
  -DUSE_CUTENSOR=ON  -DCUTENSOR_ROOT=$CONDA_PREFIX \
  -DUSE_CUQUANTUM=ON -DCUQUANTUM_ROOT=$CONDA_PREFIX \
  -DCMAKE_CUDA_ARCHITECTURES=89 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DRUN_TESTS=ON

Configure output:

-- cuTENSOR version: 2.2 (from .../include/cutensor.h)
-- Found CUTENSOR: .../lib/libcutensor.so (found version "2.2")
-- Build with CuTensor: YES
-- CuTensor: libdir:.../lib incdir:.../include libs:.../libcutensor.so;.../libcutensorMg.so
-- Found CUQUANTUM: .../lib/libcutensornet.so
-- Build with CuQuantum: YES

Both CUTENSOR_LIBRARY_DIRS and CUQUANTUM_LIBRARY_DIRS now name the directory the libraries were
actually found in, and the version gate reports a real number instead of being silent.

The compile + gpu_test_main run on that configuration is still in progress as I post this; I will
follow up in this thread with the result. Nothing about it affects section 1, which is where the
finder behaviour is actually pinned.

3. Unrelated pre-existing bug I hit while testing (not a blocker for this PR)

In both master and this PR, the finders prefer the environment variable over an explicit cache
variable:

if(NOT DEFINED ENV{CUTENSOR_ROOT} AND NOT DEFINED CUTENSOR_ROOT)
  message(FATAL_ERROR "CUTENSOR_ROOT not set!")
else()
  if(DEFINED ENV{CUTENSOR_ROOT})
    set(CUTENSOR_ROOT "$ENV{CUTENSOR_ROOT}")   # <-- clobbers -DCUTENSOR_ROOT=...
  endif()

So -DCUQUANTUM_ROOT=/some/path is silently ignored whenever CUQUANTUM_ROOT happens to be
exported — which conda/miniforge environments do by default. This cost me a while to spot (I
concluded cuQuantum was unconfigurable on this box when in fact my own -D was being discarded).
CMake convention is the reverse: the cache variable should win, with the environment as fallback.
Happy to open a separate issue if you'd like it tracked rather than folded in here.


Net: LGTM on the finder behavior from a real CUDA 13 host. Suggest adding (b) to the PR
description since it turns this from a tidy-up into a CUDA-13 correctness fix.

@yingjerkao

yingjerkao commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Follow-up: the end-to-end build finished. Green.

Recall the setup — this PR's two module files applied onto current master (2318026), since the branch itself is ~392 commits behind, with -DUSE_CUTENSOR=ON -DUSE_CUQUANTUM=ON against a conda cuTENSOR 2.2.0 / cuTensorNet 2.7.0 on a CUDA 13.0 host (sm_89).

  • libcytnx.a, test_main and gpu_test_main all build and link cleanly.
  • GPU suite, linalg_Test.* + Rsvd.*: 25 passed, 1 skipped, 0 failed (the skip is the unrelated pre-existing GpuBkUtExpM, which skips because Eig has no CUDA backend).

So the finder changes hold up in a real cuTENSOR + cuQuantum build, not just in isolation.

Two incidental observations from that run, neither caused by this PR:

1. A cuQuantum build does not execute the #1102 failure path at all. Rsvd.GpuFlagCombinations and GesvdTruncate.GpuFlagCombinations abort with CUSOLVER_STATUS_EXECUTION_FAILED from cusolverDnDgesvdj on a non-cuQuantum CUDA 13 build (that is #1102), but both pass here. The reason is that src/linalg/Gesvd_truncate.cpp has a #ifdef UNI_CUQUANTUM branch (lines 19 and 59) routing the GPU path through cuQuantumGeSvd_internal (cuTensorNet), so cuGeSvd_internal.cu:244 is never reached — cuQuantum bypasses the bug rather than fixing anything. Full diagnosis, including the alternative explanations I ruled out (it is not a cuSOLVER version difference — LD_DEBUG=bindings shows both builds binding to the same CUDA 13 libcusolver.so.12), is in #1102.

(Amended: this item originally read "cuQuantum flips two GPU failures to passes", which wrongly implied cuQuantum fixes something. It bypasses the call site. Corrected after tracing the mechanism.)

2. A conda cuQuantum drags in mismatched CUDA majors. The link emits:

/usr/bin/ld: warning: libcublas.so.12, needed by .../libcutensornet.so, may conflict with libcublas.so.13
/usr/bin/ld: warning: libcusolver.so.11, needed by .../libcutensornet.so, may conflict with libcusolver.so.12

i.e. the conda-packaged cuTensorNet was built against CUDA 12 while the toolkit here is 13. It links and the tests pass, but it is exactly the class of runtime-library mixing #948 is about. Worth a version compatibility check somewhere, though clearly out of scope for this PR.

I have also split the environment-variable precedence bug I mentioned earlier into its own issue, #1129, rather than expand this PR's scope.

Verification summary unchanged: LGTM.

@yingjerkao yingjerkao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving on the strength of the hardware verification above (RTX 4070 Ti SUPER, CUDA 13.0, cuTENSOR 2.4.1 apt / 2.2.0 conda, cuTensorNet 2.7.0):

  • Finder matrix across five install layouts, current master vs this PR: improves every case, regresses none.
  • End-to-end build of this PR's two modules applied onto current master with -DUSE_CUTENSOR=ON -DUSE_CUQUANTUM=ON: library, test_main and gpu_test_main all build; GPU suite 25 passed / 1 skipped / 0 failed.
  • The silent--NOTFOUND bug this PR fixes is confirmed real on master, not theoretical: CUTENSOR_FOUND=TRUE alongside CUTENSOR_LIB-NOTFOUND, passing a caller's REQUIRED check.
  • The lib/${CUDAToolkit_VERSION_MAJOR} change additionally fixes a CUDA-13 break on master, which hardcodes lib/12 for any CUDA >= 12 — relevant now that GPU CI runs CUDA 13 (#1044).

Three threads are left open deliberately rather than resolved:

  • @IvanaGyro's two notes on the block comment (2.3 vs 2.x, and moving rationale to the commit message). Comment-accuracy only — the finder searches both suffixes unconditionally, so behaviour is unaffected. Left for @pcchen to trim; I have put the rationale in the squash commit message as requested.
  • @chatgpt-codex-connector on a missing/unrecognised version header only warning. Real but narrow (a broken install), no worse than the pre-PR behaviour of no version check at all; belongs with the version-floor policy in #950.

Separately filed #1129 for the $ENV{*_ROOT}-clobbers--D precedence bug in these same two finders, to keep it out of this PR's scope.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@yingjerkao
yingjerkao merged commit 1820911 into master Jul 28, 2026
19 checks passed
@yingjerkao
yingjerkao deleted the split/cuda-finder-modernization branch July 28, 2026 15:29
yingjerkao added a commit that referenced this pull request Jul 28, 2026
… host's (#1131)

GPU CI was red on master for every PR, failing at configure:

    CMake Error at FindPackageHandleStandardArgs.cmake:227 (message):
      Could NOT find CUTENSOR (missing: CUTENSOR_LIB) (found version "2.4")

This is a regression from #993. The self-hosted runner's cuTENSOR is a CUDA-12
build (`.../cutensor-cu13/lib/12/`) used from a CUDA 13 toolkit. #993 replaced the
hardcoded `lib/12` suffix with `lib/${CUDAToolkit_VERSION_MAJOR}` -> `lib/13`, which
does not exist there, and simultaneously made `CUTENSOR_FOUND` honest -- so a miss
that old master hid behind an unconditional `set(CUTENSOR_FOUND TRUE)` became a hard
configure failure. Old master matched `lib/12` only by luck.

The per-major directory records which CUDA major the libraries were *built* for,
which need not equal the host toolkit's major: a cuTENSOR built for CUDA 12 ships
lib/12 and is usable from a CUDA 13 host. Restricting the search to
lib/${CUDAToolkit_VERSION_MAJOR} therefore misses valid installs.

Glob the per-major directories the install actually has and append them to the
search suffixes, highest first, keeping lib/ and the host major at the front so an
exact match still wins. Also log the resolved suffix list, which was not previously
visible in the configure output.

Everything #993 added is preserved -- honest *_FOUND, the >= 2.0 version gate, and
flat-lib/ support. This only widens where libraries may be found.

Verified on the GPU runner host (CUDA 13.0, sm_89):

- The exact ci-gpu_tests.yml configure line with the runner roots now succeeds and
  resolves lib/12/libcutensor.so (suffixes: lib;lib/13;lib/12); gpu_test_main builds
  from it.
- Finder probe across four layouts -- runner lib/12 on CUDA 13, flat lib/ 2.x
  tarball, lib/13, and conda -- all resolve. Only the first changes behaviour.
- Negative control: a root with headers but no libraries still fails to configure,
  so #993's honest-failure behaviour is intact.
- Full CI green (20/20), including the GPU job on the self-hosted runner and the
  CUDA wheel build, which exercises the pip cutensor-cu13 layout.

Fixes the GPU CI breakage introduced by #993. Related: #946, #1129.
@IvanaGyro IvanaGyro mentioned this pull request Aug 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants