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
55 changes: 55 additions & 0 deletions benchmarks/python/synchronize_bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import time

import mlx.core as mx

rank = mx.distributed.init().rank()


def timeit(fn, a):

# warmup
for _ in range(5):
mx.eval(fn(a))

its = 10
tic = time.perf_counter()
for _ in range(its):
mx.eval(fn(a))
toc = time.perf_counter()
ms = 1000 * (toc - tic) / its
return ms


def all_reduce_benchmark():
a = mx.ones((5, 5), mx.int32)

its_per_eval = 100

def fn(x):
for _ in range(its_per_eval):
x = mx.distributed.all_sum(x)
x = x - 1
return x

ms = timeit(fn, a) / its_per_eval
if rank == 0:
print(f"All Reduce: time per iteration {ms:.6f} (ms)")


def all_gather_benchmark():
a = mx.ones((5, 5), mx.int32)
its_per_eval = 100

def fn(x):
for _ in range(its_per_eval):
x = mx.distributed.all_gather(x)[0]
return x

ms = timeit(fn, a) / its_per_eval
if rank == 0:
print(f"All gather: time per iteration {ms:.6f} (ms)")


if __name__ == "__main__":
all_reduce_benchmark()
all_gather_benchmark()
1 change: 1 addition & 0 deletions mlx/backend/metal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/distributed.cpp
${CMAKE_CURRENT_SOURCE_DIR}/device.cpp
${CMAKE_CURRENT_SOURCE_DIR}/event.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fence.cpp
${CMAKE_CURRENT_SOURCE_DIR}/fft.cpp
${CMAKE_CURRENT_SOURCE_DIR}/hadamard.cpp
${CMAKE_CURRENT_SOURCE_DIR}/indexing.cpp
Expand Down
15 changes: 15 additions & 0 deletions mlx/backend/metal/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ CommandEncoder::~CommandEncoder() {
enc_->release();
}

void CommandEncoder::set_buffer(
const MTL::Buffer* buf,
int idx,
int64_t offset /* = 0 */) {
enc_->setBuffer(buf, offset, idx);
}

void CommandEncoder::set_input_array(
const array& a,
int idx,
Expand All @@ -155,6 +162,10 @@ void CommandEncoder::set_output_array(
int64_t offset /* = 0 */) {
// Add barriers before adding the output to the output set
set_input_array(a, idx, offset);
register_output_array(a);
}

void CommandEncoder::register_output_array(array& a) {
all_outputs_.insert(a.buffer().ptr());
auto buf = static_cast<MTL::Resource*>(a.buffer().ptr());
if (concurrent_) {
Expand Down Expand Up @@ -189,6 +200,10 @@ void CommandEncoder::dispatch_threads(
enc_->dispatchThreads(grid_dims, group_dims);
}

void CommandEncoder::barrier() {
enc_->memoryBarrier(MTL::BarrierScopeBuffers);
}

Device::Device() {
auto pool = new_scoped_memory_pool();
device_ = load_device();
Expand Down
4 changes: 4 additions & 0 deletions mlx/backend/metal/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ struct CommandEncoder {

void set_input_array(const array& a, int idx, int64_t offset = 0);
void set_output_array(array& a, int idx, int64_t offset = 0);
void register_output_array(array& a);
void dispatch_threadgroups(MTL::Size grid_dims, MTL::Size group_dims);
void dispatch_threads(MTL::Size grid_dims, MTL::Size group_dims);
void maybeInsertBarrier();
void set_buffer(const MTL::Buffer* buf, int idx, int64_t offset = 0);

void set_compute_pipeline_state(MTL::ComputePipelineState* kernel) {
enc_->setComputePipelineState(kernel);
Expand Down Expand Up @@ -110,6 +112,8 @@ struct CommandEncoder {
return all_outputs_;
};

void barrier();

private:
MTL::ComputeCommandEncoder* enc_;
bool needs_barrier_{false};
Expand Down
63 changes: 38 additions & 25 deletions mlx/backend/metal/distributed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "mlx/backend/common/utils.h"
#include "mlx/backend/metal/device.h"
#include "mlx/backend/metal/event.h"
#include "mlx/backend/metal/fence.h"
#include "mlx/distributed/ops.h"
#include "mlx/distributed/primitives.h"
#include "mlx/scheduler.h"
Expand All @@ -26,23 +27,28 @@ void AllReduce::eval_gpu(
assert(outputs.size() == 1);

auto& in = inputs[0];

Fence f{stream()};

if (in.event().valid()) {
f.update_gpu(in);
}

auto& out = outputs[0];
if (in.is_donatable()) {
out.move_shared_buffer(in);
} else {
out.set_data(allocator::malloc_or_wait(out.nbytes()));
}
f.wait_gpu(out);

auto e = Event(stream());
e.set_value(1);
signal_and_wait(in.event(), e);
auto task = [in = in,
out = out,
e = std::move(e),
f = std::move(f),
reduce_type = reduce_type_,
group = group()]() mutable {
if (in.event().valid()) {
in.event().wait();
f.wait();
}
switch (reduce_type) {
case Sum:
Expand All @@ -52,7 +58,7 @@ void AllReduce::eval_gpu(
default:
throw std::runtime_error("Only all reduce sum is supported for now");
}
e.signal();
f.update();
};
scheduler::enqueue(detail::communication_stream(), std::move(task));
}
Expand All @@ -67,17 +73,20 @@ void AllGather::eval_gpu(

out.set_data(allocator::malloc_or_wait(out.nbytes()));

auto e = Event(stream());
e.set_value(1);
signal_and_wait(in.event(), e);
Fence f{stream()};

if (in.event().valid()) {
f.update_gpu(in);
}
f.wait_gpu(out);

auto task =
[in = in, out = out, e = std::move(e), group = group()]() mutable {
[in = in, out = out, f = std::move(f), group = group()]() mutable {
if (in.event().valid()) {
in.event().wait();
f.wait();
}
distributed::detail::all_gather(group, in, out);
e.signal();
f.update();
};
scheduler::enqueue(detail::communication_stream(), std::move(task));
}
Expand All @@ -89,22 +98,28 @@ void Send::eval_gpu(
assert(outputs.size() == 1);

auto& in = inputs[0];

// Encode a signal event for the input
Fence f{stream()};
if (in.event().valid()) {
f.update_gpu(in);
}

auto& out = outputs[0];
move_or_copy(in, out);

// Schedule an async send on the comm stream
auto task = [in = in, out = out, group = group(), dst = dst_]() mutable {
auto task = [in = in,
out = out,
f = std::move(f),
group = group(),
dst = dst_]() mutable {
if (in.event().valid()) {
in.event().wait();
f.wait();
}
distributed::detail::send(group, out, dst);
};
scheduler::enqueue(detail::communication_stream(), std::move(task));

// Encode a signal event for the input
if (in.event().valid()) {
encode_signal(in.event());
}
}

void Recv::eval_gpu(
Expand All @@ -117,16 +132,14 @@ void Recv::eval_gpu(

out.set_data(allocator::malloc_or_wait(out.nbytes()));

auto e = Event(stream());
e.set_value(1);

encode_wait(e);
Fence f{stream()};
f.wait_gpu(out);

// Schedule an async recv on the comm stream
auto task =
[out = out, e = std::move(e), group = group(), src = src_]() mutable {
[out = out, f = std::move(f), group = group(), src = src_]() mutable {
distributed::detail::recv(group, out, src);
e.signal();
f.update();
};
scheduler::enqueue(detail::communication_stream(), std::move(task));
}
Expand Down
Loading