Conversation
melonakos
left a comment
There was a problem hiding this comment.
The #631 diagnosis here is correct and the fix is a good one, but half of this PR has already landed on develop via a different route — which is almost certainly why it now conflicts. A rebase should shrink it to something small and easy to approve.
What's already merged
#750, "[DFT][cuFFT] Fix use of invalidated iterator in commit.cpp", merged to develop and did the same thing as your second hunk. develop currently reads:
// erase() invalidates a_min (and any iterator after it).
const auto a_min_pos = a_min - stride_vecs.vec_a.begin();
stride_vecs.vec_a.erase(a_min);
stride_vecs.vec_b.erase(b_min);
...
if (a_min_pos != rank) {
std::swap(n_copy[a_min_pos - 1], n_copy[rank - 1]);
}That's your a_min_idx change under a different name. You were right about the bug — using a_min after erase(a_min) is UB — you just got there in parallel with whoever filed #750. This PR was opened 2026-07-28 and hasn't been rebased since, so it's carrying a now-redundant copy.
What's still genuinely needed
The #631 fix itself. develop still has the plain forward search:
auto a_min = std::min_element(stride_vecs.vec_a.begin() + 1, stride_vecs.vec_a.end());
auto b_min = std::min_element(stride_vecs.vec_b.begin() + 1, stride_vecs.vec_b.end());std::min_element returns the first minimum, so when two dimensions share the smallest stride — which is exactly what happens when a dimension has extent 1 — the outer index wins, and the a_min - begin() != rank check below then performs a spurious dimension swap and rejects a perfectly valid transform. Shape {3,1} is the reproducer, and your comment explaining it is clear and worth keeping.
Your reverse-iterator search resolves ties to the highest index, which is the fix. I checked the iterator arithmetic: min_element over [make_reverse_iterator(end()), make_reverse_iterator(begin()+1)) walks from the last element down to begin()+1, and .base() - 1 maps the result back to the forward iterator for the same element. Correct.
So: please rebase onto develop and drop the duplicated hunk
That leaves just the min_element change plus its comment — a small, focused PR that I'd expect to go through quickly. It should also clear the conflict, and since no CI has ever run on this branch (GitHub can't build a merge commit for a conflicting PR), the rebase is what will finally get it tested.
Two suggestions while you're in there
Factor out the tie-breaking search. The expression is dense and appears twice verbatim. Giving it a name makes the intent legible at the call site:
// Smallest stride, preferring the innermost dimension on ties (see issue #631).
auto innermost_min = [](auto& v) {
return std::min_element(std::make_reverse_iterator(v.end()),
std::make_reverse_iterator(v.begin() + 1)).base() - 1;
};
auto a_min = innermost_min(stride_vecs.vec_a);
auto b_min = innermost_min(stride_vecs.vec_b);Add a {3,1} regression test. You've got a concrete failing shape from the issue — that's the cheapest possible test to write, and it's the thing that stops #631 coming back.
Related
#760 changes the corresponding validity logic on the rocFFT side, and I've approved it. Between these two the DFT backends should end up much closer to each other, which is a good outcome — the rocFFT file still had a "dft/backends/cufft" string in one of its throws, so they'd drifted a fair way apart.
…xlfoundation#631) When several dimensions share the smallest stride, which happens when a dimension has extent 1, std::min_element picked the outer dimension and triggered a spurious dimension swap that rejected valid transforms such as {3,1}. Search in reverse so ties resolve to the innermost dimension. Add {3,1} and {2,3,1} to the DFT compute tests as regression coverage. Signed-off-by: Zheming Jin <zjin-lcf@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
b5dd6dc to
ac03b87
Compare
|
Thanks @melonakos for the careful review. Addressed:
|
Summary
Fixes #631.
oneapi::math::dfton the cuFFT backend throwsdft/backends/cufft/commit: Invalid stridesfor multi-dimensional transforms whose last dimension has extent 1 (e.g.{3,1},{2,1},{5,1},{2,3,1}). The same descriptors work on the Intel (MKL) backends, and the equivalent transform works when calling cuFFT directly.Root cause
In
src/dft/backends/cufft/commit.cpp, the backend usesstd::min_elementto find the dimension with the smallest stride (which becomes the cuFFTistride/ostride, i.e. the innermost dimension).When a dimension has extent 1, its default stride ties with its neighbour's stride.
std::min_elementreturns the first element among ties, so an outer dimension is mis-identified as the innermost one. This triggers the dimension-swap heuristic, producing a dimension/stride ordering that thecheck_stride_validity()pre-check then rejects — computinginner_n (e.g. 3) <= inner_stride (1)as false for both directions and throwingInvalid strides. The underlyingcufftPlanManycall that would have been issued is actually valid.(The related use of
a_minaftervec_a.erase(a_min)invalidated it was fixed separately in #750; this PR is rebased on top of that.)Fix
innermost_minhelper used for both input and output strides.{3,1}and{2,3,1}to the DFT compute tests (test_paramsandtest_params_real_in_place) as regression coverage.No behavioural change for shapes without unit dimensions: for strictly decreasing default strides the reverse search selects the same (last) element as before.
Test plan
Validated end-to-end on an NVIDIA sm_103 device, CUDA 13.2 / cuFFT 12.2, using an open-source DPC++ toolchain (intel/llvm nightly with the CUDA backend). Built oneMath with
-DENABLE_CUFFT_BACKEND=ON -DTARGET_DOMAINS=dft.With the fix, the reproducer from #631 and related shapes all pass:
cufftPlan2d(3,1)and the exactcufftPlanManyparams oneMath generates) succeed on the B300 — the failure is in the pre-check, not cuFFT.{3,1},{2,1},{5,1},{2,3,1}; fixed logic ACCEPTS them; regression shapes unchanged.develop: built with-DENABLE_CUFFT_BACKEND=ON -DTARGET_DOMAINS=dft -DBUILD_FUNCTIONAL_TESTS=ONand rantest_main_dft_ct/test_main_dft_rton a Tesla M40. Without the fix the 16 new non-USM{3,1}/{2,3,1}complex cases fail withInvalid strides; with the fix all non-USM tests pass. (USM tests fail on this particular machine with and without the change, independent of shape.)