Skip to content

fix(linalg): support non-contiguous tensor⊗tensor for GPU Mul/Div (#1003, #988)#1096

Open
yingjerkao wants to merge 2 commits into
masterfrom
fix/1003-gpu-mul-div-noncontig
Open

fix(linalg): support non-contiguous tensor⊗tensor for GPU Mul/Div (#1003, #988)#1096
yingjerkao wants to merge 2 commits into
masterfrom
fix/1003-gpu-mul-div-noncontig

Conversation

@yingjerkao

Copy link
Copy Markdown
Collaborator

Part of #1003; closes the GPU non-contiguous gap tracked in #988 for Mul/Div.

Problem

The GPU out-of-place Mul and Div front ends rejected a non-contiguous operand with two tensors must be contiguous. Call Contiguous_() or Contiguous() first — forcing the caller to contiguous-ize first, even though Add/Sub accept it and the CPU path handles it. This was a leftover from the pre-#1013 era when cuMul/cuDiv had no working non-contiguous kernel (#988).

Fix

#1013 unified all four arithmetic ops onto the shared cuArithmeticDispatchGPU kernel, which already applies the layout mappers. Mul.cpp/Div.cpp now pass the layout (shape + inverse mappers) to cuMul_dispatch/cuDiv_dispatch in the non-contiguous branch — exactly as Add.cpp/Sub.cpp already do — instead of erroring. Div's output is already allocated with the true-division (floating) dtype before the branch.

⚠️ Behavior change (flagged): GPU Mul/Div now accept a non-contiguous operand and return the correct result (matching CPU and Add/Sub) rather than throwing. This removes a restriction; it does not change any result that previously succeeded.

Testing

New non-contiguous tensor(op)tensor cases in Mul_test.cpp and Div_test.cpp:

  • GPU-vs-CPU across all dtypes (both operands permuted, so both offsets are gathered through the inverse mappers);
  • independent hand-computed literals with distinct per-element results, so a wrong multi-index decomposition is caught, not just a wrong L/R pairing.

Both new tests fail on the pre-fix code (which threw "must be contiguous"). On an RTX 4070 Ti (CUDA 13, sm_89): gpu_test_main builds clean; the new tests pass; the full Mul/Div GPU suites (148 tests) are green. clang-format-14 clean.

Composes with (and is independent of) #1095 — when both land, this newly-live non-contiguous path uses the by-value layout instead of per-call device allocation.

Note: GPU results are from the local RTX 4070 Ti — there is no GPU CI runner. Cpr still rejects non-contiguous operands (separate Bool-output kernel); left as a follow-up.

🤖 Generated with Claude Code

…1003, #988)

Problem: the GPU out-of-place Mul and Div front ends rejected a
non-contiguous operand with "two tensors must be contiguous", forcing the
caller to contiguous-ize first -- even though Add/Sub accept it and the CPU
path handles it. This was a leftover from the pre-#1013 era when cuMul/cuDiv
had no working non-contiguous kernel (#988).

Fix: #1013 unified all four arithmetic ops onto the shared
cuArithmeticDispatchGPU kernel, which applies the layout mappers. Mul.cpp and
Div.cpp now pass the layout (shape + inverse mappers) to
cuMul_dispatch/cuDiv_dispatch in the non-contiguous branch, exactly as
Add.cpp/Sub.cpp already do, instead of erroring. Div's output is already
allocated with the true-division (floating) dtype before the branch.

Behavior change: GPU Mul/Div now accept a non-contiguous operand and return
the correct result (matching CPU and Add/Sub) rather than throwing.

Testing: new non-contiguous tensor(op)tensor cases in Mul_test.cpp and
Div_test.cpp -- GPU-vs-CPU across all dtypes AND independent hand-computed
literals with distinct per-element results (so a wrong multi-index
decomposition is caught, not just a wrong L/R pairing). Both fail on the
pre-fix code (which threw "must be contiguous"). On an RTX 4070 Ti (CUDA 13,
sm_89): gpu_test_main builds clean, the new tests pass, and the full Mul/Div
GPU suites are green. clang-format-14 clean.

Composes with (and is independent of) #1095: when both land, this
newly-live non-contiguous path uses the by-value layout instead of per-call
device allocation.

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

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

…Mul/Div (#1003)

Review follow-up (#1096): pass out._impl->storage()._impl->size() -- the logical
element count -- as the kernel launch length, not left._impl->storage()._impl->size().
This matches the CPU non-contiguous path (Add.cpp:120) and the contiguous GPU branch.
For a strided view whose storage exceeds its logical size the old value would
over-launch and write out of bounds on `out`; verified this is not currently reachable
(Cytnx compacts slices, so a non-contiguous tensor's storage always equals its logical
size), so the fix is for correctness/consistency, not a live crash. Add/Sub on master
carry the same old pattern (pre-existing).

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

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.71%. Comparing base (86cb96c) to head (303a0a3).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1096   +/-   ##
=======================================
  Coverage   72.71%   72.71%           
=======================================
  Files         226      226           
  Lines       28151    28151           
  Branches       71       71           
=======================================
  Hits        20471    20471           
  Misses       7659     7659           
  Partials       21       21           
Flag Coverage Δ
cpp 72.83% <ø> (ø)
python 64.13% <ø> (ø)

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

Components Coverage Δ
C++ backend 71.93% <ø> (ø)
Python bindings 77.70% <ø> (ø)
Python package 64.13% <ø> (ø)
Files with missing lines Coverage Δ
src/linalg/Div.cpp 86.91% <ø> (ø)
src/linalg/Mul.cpp 76.90% <ø> (ø)

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 86cb96c...303a0a3. 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.

@yingjerkao

Copy link
Copy Markdown
Collaborator Author

Addressed in the latest commit: the cuMul_dispatch/cuDiv_dispatch length argument now uses out._impl->storage()._impl->size() (the logical element count), matching the CPU non-contiguous path and the contiguous GPU branch, rather than left._impl->storage()._impl->size().

This is the same correctness fix as the critical one flagged on #1097 — see that thread for the verification that it isn't currently triggerable in Cytnx (slices compact to contiguous, so a non-contiguous tensor's storage always equals its logical size) but is applied for correctness and future-proofing. The rank-13+ shared-memory guard is handled centrally in #1095.

@yingjerkao
yingjerkao requested review from IvanaGyro and ianmccul July 24, 2026 08:59

@ianmccul ianmccul 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.

OK, but noting that the current implementation is very slow; much slower than making it contiguous (assuming the algorithm for making it contiguous is effiicient -- I didn't check that)

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.

2 participants