Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions benchmarks/python/masked_scatter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import math
import os
import platform
import subprocess
import time
from copy import copy
Expand All @@ -17,21 +18,43 @@
if not os.path.isdir(RESULTS_DIR):
os.mkdir(RESULTS_DIR)

DEVICE_NAME = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"])
DEVICE_NAME = DEVICE_NAME.decode("utf-8").strip("\n")

TORCH_DEVICE = torch.device(
"mps"
if torch.backends.mps.is_available()
else ("cuda" if torch.cuda.is_available() else "cpu")
)


def get_device_name():
if TORCH_DEVICE.type == "cuda":
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
stderr=subprocess.DEVNULL,
)
return out.decode("utf-8").splitlines()[0].strip()
except Exception:
return "CUDA_GPU"
if TORCH_DEVICE.type == "mps":
try:
out = subprocess.check_output(
["sysctl", "-n", "machdep.cpu.brand_string"],
stderr=subprocess.DEVNULL,
)
return out.decode("utf-8").strip()
except Exception:
return "Apple_Silicon"
return platform.processor() or platform.machine() or "CPU"


DEVICE_NAME = get_device_name()


N_WARMUP = 5
N_ITER_BENCH = 50
N_ITER_FUNC = 20

VECTOR_LENGTHS = [4096 * (2**i) for i in range(10)]
VECTOR_LENGTHS = [4096 * (2**i) for i in range(12)]
MASK_DENSITIES = [0.01, 0.1, 0.25, 0.5]
D_TYPES = ("float32", "float16")

Expand Down Expand Up @@ -202,9 +225,10 @@ def main():
)
output_path = os.path.join(
RESULTS_DIR,
f"{DEVICE_NAME.replace(' ', '_')}_masked_scatter_{dtype}.pdf",
f"{DEVICE_NAME.replace(' ', '_')}_masked_scatter_{dtype}.png",
)
fig.savefig(output_path)
print(f"Saved benchmark image: {output_path}")
plt.close(fig)


Expand Down
87 changes: 87 additions & 0 deletions mlx/backend/cuda/device/scatter.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,91 @@ __global__ void scatter(
Op{}(out + out_idx, upd[upd_loc]);
}

template <typename T, bool SrcContiguous, bool DstContiguous, typename IdxT>
__global__ void masked_scatter(
const T* dst,
const bool* mask,
const int32_t* scatter_offsets,
const T* src,
T* out,
IdxT size,
IdxT src_batch_size,
IdxT mask_batch_size,
const __grid_constant__ Shape dst_shape,
const __grid_constant__ Strides dst_strides,
int32_t dst_ndim,
const __grid_constant__ Shape src_shape,
const __grid_constant__ Strides src_strides,
int32_t src_ndim) {
IdxT index = cg::this_grid().thread_rank();
if (index >= size) {
return;
}

T dst_val;
if constexpr (DstContiguous) {
dst_val = dst[index];
} else {
IdxT dst_loc =
elem_to_loc(index, dst_shape.data(), dst_strides.data(), dst_ndim);
dst_val = dst[dst_loc];
}

if (mask[index]) {
IdxT src_index = static_cast<IdxT>(scatter_offsets[index]);
if (src_index < src_batch_size) {
IdxT batch_idx = index / mask_batch_size;
if constexpr (SrcContiguous) {
out[index] = src[batch_idx * src_batch_size + src_index];
} else {
IdxT src_elem = batch_idx * src_batch_size + src_index;
IdxT src_loc = elem_to_loc(
src_elem, src_shape.data(), src_strides.data(), src_ndim);
out[index] = src[src_loc];
}
return;
}
}

out[index] = dst_val;
}

template <typename T, typename IdxT, int N_READS>
__global__ void masked_scatter_vec_contiguous(
const T* dst,
const bool* mask,
const int32_t* scatter_offsets,
const T* src,
T* out,
IdxT size,
IdxT src_batch_size,
IdxT mask_batch_size) {
IdxT vec_index = cg::this_grid().thread_rank();
IdxT base = vec_index * N_READS;
if (base >= size) {
return;
}

auto out_vec = load_vector<N_READS>(dst, vec_index, size, static_cast<T>(0));
auto mask_vec = load_vector<N_READS>(mask, vec_index, size, false);
auto offset_vec = load_vector<N_READS>(scatter_offsets, vec_index, size, 0);

#pragma unroll
for (int i = 0; i < N_READS; ++i) {
IdxT index = base + i;
if (index >= size) {
break;
}
if (mask_vec[i]) {
IdxT src_index = static_cast<IdxT>(offset_vec[i]);
if (src_index < src_batch_size) {
IdxT batch_idx = index / mask_batch_size;
out_vec[i] = src[batch_idx * src_batch_size + src_index];
}
}
}

store_vector<N_READS>(out, vec_index, out_vec, size);
}

} // namespace mlx::core::cu
127 changes: 127 additions & 0 deletions mlx/backend/cuda/indexing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "mlx/backend/cuda/jit_module.h"
#include "mlx/backend/cuda/kernel_utils.cuh"
#include "mlx/backend/gpu/copy.h"
#include "mlx/backend/gpu/scan.h"
#include "mlx/dtype_utils.h"
#include "mlx/primitives.h"

Expand Down Expand Up @@ -435,4 +436,130 @@ void ScatterAxis::eval_gpu(const std::vector<array>& inputs, array& out) {
kernel, num_blocks, block_dims, {}, 0, args.args());
}

void MaskedScatter::eval_gpu(const std::vector<array>& inputs, array& out) {
nvtx3::scoped_range r("MaskedScatter::eval_gpu");
assert(inputs.size() == 3);

const array& dst = inputs[0];
const array& mask = inputs[1];
const array& src = inputs[2];

auto& s = stream();
auto& encoder = cu::get_command_encoder(s);

const size_t total = mask.size();
out.set_data(cu::malloc_async(out.nbytes(), encoder));
if (total == 0) {
return;
}

array mask_flat = flatten_in_eval(mask, 1, -1, s);
if (mask_flat.data<void>() != mask.data<void>()) {
encoder.add_temporary(mask_flat);
}
if (!mask_flat.flags().row_contiguous) {
mask_flat = contiguous_copy_gpu(mask_flat, s);
encoder.add_temporary(mask_flat);
}

array scatter_offsets(mask_flat.shape(), int32, nullptr, {});
scatter_offsets.set_data(cu::malloc_async(scatter_offsets.nbytes(), encoder));
encoder.add_temporary(scatter_offsets);

scan_gpu_inplace(
mask_flat,
scatter_offsets,
Scan::Sum,
/* axis= */ 1,
/* reverse= */ false,
/* inclusive= */ false,
s);

const size_t batch_count = mask.shape(0);
const size_t mask_batch_size = mask_flat.size() / batch_count;
const size_t src_batch_size = src.size() / src.shape(0);
bool large = total > INT32_MAX || src.size() > INT32_MAX;
bool vectorized = src.flags().row_contiguous && dst.flags().row_contiguous;
constexpr int kMaskedScatterVecSize = 16;
constexpr int kMaskedScatterVecBlockDim = 256;

std::string module_name =
fmt::format("masked_scatter_{}", dtype_to_string(out.dtype()));
cu::JitModule& mod = cu::get_jit_module(s.device, module_name, [&]() {
std::vector<std::string> kernel_names;
for (int src_contiguous = 0; src_contiguous <= 1; ++src_contiguous) {
for (int dst_contiguous = 0; dst_contiguous <= 1; ++dst_contiguous) {
for (int use_large = 0; use_large <= 1; ++use_large) {
kernel_names.push_back(
fmt::format(
"mlx::core::cu::masked_scatter<{}, {}, {}, {}>",
dtype_to_cuda_type(out.dtype()),
src_contiguous ? "true" : "false",
dst_contiguous ? "true" : "false",
use_large ? "int64_t" : "int32_t"));
}
}
}
for (int use_large = 0; use_large <= 1; ++use_large) {
kernel_names.push_back(
fmt::format(
"mlx::core::cu::masked_scatter_vec_contiguous<{}, {}, {}>",
dtype_to_cuda_type(out.dtype()),
use_large ? "int64_t" : "int32_t",
kMaskedScatterVecSize));
}
return std::make_tuple(false, jit_source_scatter, std::move(kernel_names));
});

cu::KernelArgs args;
args.append(dst);
args.append(mask_flat);
args.append(scatter_offsets);
args.append(src);
args.append(out);
if (large) {
args.append<int64_t>(mask_flat.size());
args.append<int64_t>(src_batch_size);
args.append<int64_t>(mask_batch_size);
} else {
args.append<int32_t>(mask_flat.size());
args.append<int32_t>(src_batch_size);
args.append<int32_t>(mask_batch_size);
}
if (!vectorized) {
args.append_ndim(dst.shape());
args.append_ndim(dst.strides());
args.append<int32_t>(dst.ndim());
args.append_ndim(src.shape());
args.append_ndim(src.strides());
args.append<int32_t>(src.ndim());
}

encoder.set_input_array(dst);
encoder.set_input_array(mask_flat);
encoder.set_input_array(scatter_offsets);
encoder.set_input_array(src);
encoder.set_output_array(out);

std::string kernel_name = vectorized
? fmt::format(
"mlx::core::cu::masked_scatter_vec_contiguous<{}, {}, {}>",
dtype_to_cuda_type(out.dtype()),
large ? "int64_t" : "int32_t",
kMaskedScatterVecSize)
: fmt::format(
"mlx::core::cu::masked_scatter<{}, {}, {}, {}>",
dtype_to_cuda_type(out.dtype()),
src.flags().row_contiguous ? "true" : "false",
dst.flags().row_contiguous ? "true" : "false",
large ? "int64_t" : "int32_t");
auto kernel = mod.get_kernel(kernel_name);
auto [num_blocks, block_dims] = vectorized
? get_launch_args(
mask_flat, large, kMaskedScatterVecSize, kMaskedScatterVecBlockDim)
: get_launch_args(mask_flat, large);
encoder.add_kernel_node_raw(
kernel, num_blocks, block_dims, {}, 0, args.args());
}

} // namespace mlx::core
1 change: 0 additions & 1 deletion mlx/backend/cuda/primitives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ NO_GPU(Inverse)
NO_GPU(Cholesky)
NO_GPU_MULTI(Eig)
NO_GPU_MULTI(Eigh)
NO_GPU(MaskedScatter)

namespace distributed {
NO_GPU_MULTI(Send)
Expand Down
Loading
Loading