Collapse the non sorted axes to pick the contiguous sort kernel - #4366
Open
kapellirohith wants to merge 1 commit into
Open
Collapse the non sorted axes to pick the contiguous sort kernel#4366kapellirohith wants to merge 1 commit into
kapellirohith wants to merge 1 commit into
Conversation
single_block_sort walks the rows of the contiguous kernel with a single segment stride, so the axes that are not sorted have to be one contiguous run. flags().contiguous only means dense with no gaps, so a transposed view took that kernel and sorted wrong. Use collapse_contiguous_dims on those axes to decide, and read the segment stride off the collapsed run, which also removes the hand rolled loop that recomputed it as a min.
kapellirohith
force-pushed
the
sort-segment-order
branch
from
September 11, 2026 16:36
005f933 to
69ab55a
Compare
kapellirohith
marked this pull request as ready for review
September 11, 2026 16:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #4137.
mx.sortandmx.argsortreturn silently wrong results on some transposed views on the GPU, with no error raised and no crash.Reproducer
Falsebefore,Trueafter.Each output row is internally sorted but holds the wrong input row's data, so a check that only asks whether the output is sorted still passes.
stream=mx.cpuis correct either way, as is any 2-D transpose. The failure needs a rank 3 or higher view whose non-sorted axes are no longer in row major order relative to each other.Cause
The contiguous kernel addresses row
ratr * in_stride_segment_axis(kernels/sort.h:283,cuda/sort.cu:305). The nc kernel decodes the same row withelem_to_loc(kernels/sort.h:393,cuda/sort.cu:398). Selection is onin.flags().contiguous(metal/sort.cpp:45,cuda/sort.cu:784), which only means dense storage with no gaps. That does not imply the non-sorted axes form a single ordered run, so a transposed view can take the contiguous kernel and sort the wrong rows.Fix
Collapse the non-sorted axes with
collapse_contiguous_dimsand take the fast path only when they reduce to a single run, with the segment stride read from that run. The hand-rolled min-stride loop goes away.This cannot demote an input that already works. The contiguous kernel is correct for a layout exactly when
elem_to_loc(r) == r * strideholds for everyr, and that is the arithmetic progressioncollapse_contiguous_dimsmerges, so every layout main gets right is still accepted.Over 1058 (array, axis) cases, built from 15 base shapes of rank 1 to 4 by applying every axis permutation,
expand_dims,broadcast_to, and forward, reversed and strided slices:row_contiguousThe 122 layouts the selection predicate marks wrong on main are exactly the 122 whose GPU result differs from the CPU backend, with nothing in either set that is not in the other.
in.flags().contiguousstays in the condition. It is what keeps negative strides out of the contiguous kernel, whose row index is unsigned.Testing
The new test covers
(3, 4, 8),(2, 1, 6),(2, 3, 4, 2)and(2, 1, 3, 4)under every axis permutation and every axis. It fails on main and passes here; CPU is correct either way.multi_block_sortis unaffected; multi block starts when the sorted axis exceeds 2048.Benchmark
M3 Pro. Each entry is the median of 3 runs, each itself the median of 9 repeats x 50 iterations, in ms.
(1024,1024)axis 1, contiguous(1024,1024)T(1,0) axis 0, transposed(256,64,64)axis 2, contiguous(64,64,256)T(1,0,2) axis 2(512,512)axis 1, multi blockThe first, second, third and fifth rows are unchanged within run to run spread; main alone varied from 0.4265 to 0.4587 on the first row across runs.
(64,64,256) T(1,0,2)is one of the layouts main sorts incorrectly, so this change moves it to the general path. That is the cost of correctness, not a regression on an input that worked. The transposed row above it is an input main sorts correctly, and it stays on the fast path.CUDA validation is source level only. No NVIDIA hardware was used.
Checklist
pre-commit run --all-filesto format my code / installed pre-commit prior to committing this change