Collapse the non sorted axes to pick the contiguous sort kernel - #4366
Draft
kapellirohith wants to merge 1 commit into
Draft
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.
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: 242 of 9395 enumerated cases are wrong on main, with no error raised and no crash.2461 arrays, 9395 (array, axis) cases, GPU checked against the CPU backend, each variant built and run.
The enumeration builds those arrays from 15 base shapes of rank 1 to 4 by applying every axis permutation,
expand_dims,broadcast_to, forward slices and reversed (negative stride) slices, then sorts each one on every axis. Per case it records which kernel the dispatch selects and compares the GPU result against the CPU backend, which does not use the segment stride. Selection is read fromflags()andstrides(), so it runs as a C++ doctest probe rather than a Python script; I can attach it if that is useful.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). The kernel is selected onin.flags().contiguous(metal/sort.cpp:45,cuda/sort.cu:784), which means dense storage with no gaps and does not imply one ordered run, so a transposed view can take the contiguous kernel and sort the wrong rows.Reproducer
Base
994d9d502Chip M3 Pro
macOS 26.6.1
Before:
FalseAfter:
TrueEach output row is internally sorted, but it holds the wrong input row's data, so a check that only asks whether the output is sorted will pass.
stream=mx.cpuis correct before and after. A 2-D transpose is also correct before and after. The failure is specific to the GPU single-block path on higher-rank transposed views where the non-sorted axes are no longer in row-major order relative to each other.Fix
Collapse the non-sorted axes with
collapse_contiguous_dims. The fast path is selected only when they reduce to a single run, with the segment stride read from that run, and the hand-rolled min-stride loop is removed. After collapsing, at most one non-size-1 dimension may remain, which is exactly the addressing assumption the contiguous kernel makes.Testing
Confirmed the new tests fail on the main branch at
994d9d502: 5/5 red on GPU, both standalone and in-module. They pass on CPU either way; that backend never had the defect.Shapes:
(3, 4, 8),(2, 1, 6),(2, 3, 4, 2),(2, 1, 3, 4).Python: 860 exit 0 on GPU.
test_fft_too_largeis a pre-existing CPU-only failure.C++: 278/278 on both devices.
JIT: 10/10.
Pre-commit: clean.
multi_block_sortis unaffected. All 24 multi-block cases are correct, with multi-block starting when the sorted axis is greater than 2048.Benchmark
(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 blockMedians of 9 repeats x 50 iterations, 3 processes each.
The fast path is not broadly disabled: all 473 layouts that were fast and correct on main stay fast, and all 242 that were fast and wrong are rejected from it.
Build each revision with:
Run both revisions on the same GPU.
(64,64,256) T(1,0,2)is slower because main incorrectly selects the contiguous kernel for it; this change demotes it to the general path. That cost is the price of correctness, not a regression on a case that already worked. The transposed row above it is an input main sorts correctly, and it stays on the fast path.Benchmark script:
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