Skip to content

fix(backend): detect failed CUDA kernel launches instead of returning zeros - #1175

Open
yingjerkao wants to merge 1 commit into
masterfrom
fix/1171-check-kernel-launch
Open

fix(backend): detect failed CUDA kernel launches instead of returning zeros#1175
yingjerkao wants to merge 1 commit into
masterfrom
fix/1171-check-kernel-launch

Conversation

@yingjerkao

@yingjerkao yingjerkao commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

A CUDA kernel launch reports its status only through cudaGetLastError() — the
launch expression itself cannot fail visibly. Nothing in the GPU backend read
that status, so a launch that never ran left the output buffer holding whatever
it already contained, usually zeros from a fresh allocation. The failure was
indistinguishable from a numerical bug.

This is part 2 of #1171: on an sm_80 card, a default-architecture build returned
zeros across the entire GPU surface — including tests unrelated to any local
change — with no error raised. As that issue puts it, a launch-status check
turns "your kernel is wrong" into "no kernel image for this device".

Fix

Add CYTNX_CHECK_CUDA_LAUNCH (include/cytnx_error.hpp, inside the UNI_GPU
guard) and call it after every kernel launch in the built GPU sources — 213
sites across 21 files.

Design notes:

  • It reads the status without synchronizing, so it stays cheap enough to sit
    after every launch. That scopes it to launch-time failures (no image for the
    running device, bad launch configuration) rather than errors raised during
    execution, which surface asynchronously at the next synchronizing call.
  • It raises through cytnx_error_msg, so the failure is catchable and names the
    kernel plus the launch site — unlike checkCudaErrors, which exit()s the
    process.
  • The pre-existing checkCudaErrors(cudaGetLastError()) in
    cuTrace_internal.cu becomes the same macro rather than a second read that
    could only ever see success.
  • cuTNPerm_gpu.cu is deliberately left alone: it is not referenced by any build
    file (Delete cuTNPerm_gpu.{cu,hpp}: orphaned source, never compiled and would not compile #1142).

Some if/else branches gained braces because the check becomes a second
statement; that is the only other change in the diff.

Trade-off worth a reviewer's attention

30 of the 213 checked launch sites are followed by cudaFree of temporary
device buffers (114 free calls in all), which a throw now skips — so a failed
launch leaks device memory where it previously continued silently. An exception
plus a leak beats silently wrong numbers, but scoping those buffers with RAII is
a genuine follow-up rather than something to mix into this change.

Tracked as #1176, with the affected sites enumerated. The DeviceBuffer<T> type
that fixes it already exists (added by #1146 for the same failure mode in the
cuSOLVER wrappers), so the follow-up is reuse rather than new machinery.

Testing

  • GPU suite: ctest -L gpu on 2x RTX 4070 Ti SUPER (sm_89, arch 89 build) —
    841/841 passed, 0 failed, 1726 s. The 4 non-running tests are pre-existing
    source-level GTEST_SKIP()s (GPU ExpM unimplemented, cuQuantum-dependent QR,
    zero-heavy SVD). This is the no-false-positives evidence: the guard stays quiet
    across the whole GPU surface when launches genuinely succeed.

  • Guard fires correctly: a standalone probe against the real header confirms
    a valid launch does not throw, while a launch rejected by the driver (2048
    threads/block, over the 1024 limit — the same launch-rejected class as "no
    kernel image for this device") throws a catchable cytnx::error:
    CUDA kernel launch failed for 'probe_kernel': invalid argument.

  • End-to-end arch mismatch, from the session that wrote the patch: same tree,
    library built for 75-real (SASS with no JIT-able PTX), run on sm_89.

    before: linalg::Add -> values = 0 0 0 0, no error
    after:  linalg::Add -> CytnxError "CUDA kernel launch failed for 'tn_kernel':
            named symbol not found" at cuArithmeticDispatch.cuh:122
    
  • Formatting: pre-commit run --files <changed> clean (clang-format v14).

  • CPU presets not run, by design: the only non-.cu/.cuh change is inside
    #if defined(UNI_GPU), so this commit cannot affect a CPU build. CI's
    ci-cmake_tests covers that gate.

No automated regression test accompanies this. Reproducing the bug requires
building the whole library for an architecture the running device cannot execute,
which is not something the test suite can set up — hence the manual
demonstration above.

Scope

Relates to #1171; does not close it. Part 1 of that issue — the multi-arch
CMAKE_CUDA_ARCHITECTURES default emitting LTO IR that is never device-linked —
is untouched and still wants a fix.

🤖 Generated with Claude Code

… zeros

A kernel launch reports its status only through cudaGetLastError(); the
launch expression itself cannot fail visibly. Nothing in the GPU backend
read that status, so a launch that never ran left the output buffer holding
whatever it already contained -- usually zeros from a fresh allocation --
and the failure was indistinguishable from a numerical bug.

Demonstrated on an sm_89 GPU with a library built for 75-real (SASS with no
JIT-able PTX), the same tree with and without this change:

  before: linalg::Add -> values = 0 0 0 0, no error
  after:  linalg::Add -> CytnxError "CUDA kernel launch failed for
          'tn_kernel': named symbol not found" at cuArithmeticDispatch.cuh:122

Add CYTNX_CHECK_CUDA_LAUNCH and call it after every kernel launch -- 213
sites across 21 files. It reads the status without synchronizing, so it
stays cheap enough to sit after each launch, and it catches launch-time
failures (no image for the running device, bad launch configuration) rather
than errors raised during execution, which surface asynchronously. Raising
through cytnx_error_msg keeps the failure catchable and reports the launch
site, unlike checkCudaErrors, which exit()s the process.

The pre-existing checkCudaErrors(cudaGetLastError()) in cuTrace_internal.cu
becomes the same macro rather than a second read that could only ever see
success. cuTNPerm_gpu.cu is left alone: it is not referenced by any build
file (#1142).

Note one trade-off: 30 of the 213 checked launch sites are followed by
cudaFree of temporary device buffers (114 free calls in all), which a throw
now skips, so a failed launch leaks device memory where it previously
continued silently. An exception plus a leak is preferable to silently wrong
numbers, and scoping those buffers with RAII is left as a follow-up rather
than mixed into this change.

Relates to #1171.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.92%. Comparing base (ba5eb90) to head (e1647fd).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1175   +/-   ##
=======================================
  Coverage   67.92%   67.92%           
=======================================
  Files         208      208           
  Lines       22354    22354           
  Branches       72       72           
=======================================
  Hits        15184    15184           
  Misses       7148     7148           
  Partials       22       22           
Flag Coverage Δ
cpp 67.99% <ø> (ø)
python 63.96% <ø> (ø)

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

Components Coverage Δ
C++ backend 67.99% <ø> (ø)
Python bindings ∅ <ø> (∅)
Python package 63.96% <ø> (ø)
Files with missing lines Coverage Δ
include/cytnx_error.hpp 0.00% <ø> (ø)

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 ba5eb90...e1647fd. 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.

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.

1 participant