-
Notifications
You must be signed in to change notification settings - Fork 2k
[CUDA][Improvement] Fast path for gather #3794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Copyright © 2025 Apple Inc. | ||
|
|
||
| #include "mlx/backend/cuda/device/indexing.cuh" | ||
| #include "mlx/backend/cuda/device/utils.cuh" | ||
|
|
||
| namespace mlx::core::cu { | ||
|
|
||
| template <typename T, typename IdxT, typename LocT, int N> | ||
| __global__ void gather_front( | ||
| const T* src, | ||
| const IdxT* indices, | ||
| T* out, | ||
| int64_t stride, | ||
| int32_t size) { | ||
| LocT row = blockIdx.x; | ||
| int n_vec = (stride + N - 1) / N; | ||
| int vec = blockIdx.y * blockDim.x + threadIdx.x; | ||
| if (vec >= n_vec) { | ||
| return; | ||
| } | ||
|
|
||
| auto idx = absolute_index(indices[row], size); | ||
| const T* src_row = src + static_cast<LocT>(idx) * static_cast<LocT>(stride); | ||
| T* out_row = out + static_cast<LocT>(row) * static_cast<LocT>(stride); | ||
|
|
||
| auto v = load_vector<N>(src_row, vec, stride, static_cast<T>(0)); | ||
| store_vector<N>(out_row, vec, v, stride); | ||
| } | ||
|
|
||
| } // namespace mlx::core::cu |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -81,6 +81,67 @@ void Gather::eval_gpu(const std::vector<array>& inputs, array& out) { | |||||||||||||||||||||
| uint32_t slice_size = std::accumulate( | ||||||||||||||||||||||
| slice_sizes_.begin(), slice_sizes_.end(), 1, std::multiplies<uint32_t>()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (nidx == 1 && axes_[0] == 0 && src.flags().row_contiguous && | ||||||||||||||||||||||
| inputs[1].flags().row_contiguous && slice_size == src.strides()[0] && | ||||||||||||||||||||||
| inputs[1].size() <= static_cast<size_t>(INT32_MAX)) { | ||||||||||||||||||||||
| constexpr int vec_bytes = 16; | ||||||||||||||||||||||
| int vec_size = vec_bytes / out.dtype().size(); | ||||||||||||||||||||||
| if (vec_size < 1) { | ||||||||||||||||||||||
| vec_size = 1; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::string module_name = fmt::format( | ||||||||||||||||||||||
| "gather_front_{}_{}", | ||||||||||||||||||||||
| dtype_to_string(out.dtype()), | ||||||||||||||||||||||
| dtype_to_string(idx_dtype)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| cu::JitModule& mod = | ||||||||||||||||||||||
| cu::get_jit_module(encoder.device(), module_name, [&]() { | ||||||||||||||||||||||
| std::vector<std::string> kernel_names; | ||||||||||||||||||||||
| for (int l = 0; l <= 1; ++l) { | ||||||||||||||||||||||
| kernel_names.push_back( | ||||||||||||||||||||||
| fmt::format( | ||||||||||||||||||||||
| "mlx::core::cu::gather_front<{}, {}, {}, {}>", | ||||||||||||||||||||||
| dtype_to_cuda_type(out.dtype()), | ||||||||||||||||||||||
| dtype_to_cuda_type(idx_dtype), | ||||||||||||||||||||||
| l ? "int64_t" : "int32_t", | ||||||||||||||||||||||
| vec_size)); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return std::make_tuple( | ||||||||||||||||||||||
| false, jit_source_gather_front, std::move(kernel_names)); | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| cu::KernelArgs args; | ||||||||||||||||||||||
| args.append(src); | ||||||||||||||||||||||
| args.append(inputs[1]); | ||||||||||||||||||||||
| args.append(out); | ||||||||||||||||||||||
| args.append<int64_t>(slice_size); | ||||||||||||||||||||||
| args.append<int32_t>(src.shape(0)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| encoder.set_input_array(src); | ||||||||||||||||||||||
| encoder.set_input_array(inputs[1]); | ||||||||||||||||||||||
| encoder.set_output_array(out); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::string kernel_name = fmt::format( | ||||||||||||||||||||||
| "mlx::core::cu::gather_front<{}, {}, {}, {}>", | ||||||||||||||||||||||
| dtype_to_cuda_type(out.dtype()), | ||||||||||||||||||||||
| dtype_to_cuda_type(idx_dtype), | ||||||||||||||||||||||
| large ? "int64_t" : "int32_t", | ||||||||||||||||||||||
| vec_size); | ||||||||||||||||||||||
| auto kernel = mod.get_kernel(kernel_name); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| uint32_t n_vec = (slice_size + vec_size - 1) / vec_size; | ||||||||||||||||||||||
| uint32_t max_block_dim = 256; | ||||||||||||||||||||||
| uint32_t block_dim = n_vec < max_block_dim ? n_vec : max_block_dim; | ||||||||||||||||||||||
| dim3 block(block_dim, 1, 1); | ||||||||||||||||||||||
| dim3 grid( | ||||||||||||||||||||||
|
Comment on lines
+133
to
+137
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some nitpickings:
Suggested change
We don't have standards on what names to call block and grid dims but we should use the same names in the same file. (We did not use the more common |
||||||||||||||||||||||
| static_cast<uint32_t>(inputs[1].size()), | ||||||||||||||||||||||
| (n_vec + block_dim - 1) / block_dim, | ||||||||||||||||||||||
|
Comment on lines
+138
to
+139
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried swapping we might get better coalesce by swapping: For benchmarking you can use env var |
||||||||||||||||||||||
| 1); | ||||||||||||||||||||||
| encoder.add_kernel_node_raw(kernel, grid, block, {}, 0, args.args()); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::string module_name = fmt::format( | ||||||||||||||||||||||
| "gather_{}_{}_{}", | ||||||||||||||||||||||
| dtype_to_string(out.dtype()), | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the kernel is capable of handling the large size?