Skip to content
Open
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
3 changes: 2 additions & 1 deletion mlx/backend/cuda/event.cu
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ void Event::wait() {
} else {
event.atomic->wait(value());
}
CHECK_CUDA_ERROR(cudaPeekAtLastError());
// Check for errors during kernel execution and reset the error state.
CHECK_CUDA_ERROR(cudaGetLastError());
check_error();
}

Expand Down
2 changes: 2 additions & 0 deletions mlx/backend/cuda/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace mlx::core {

void check_cuda_error(const char* name, cudaError_t err) {
if (err != cudaSuccess) {
// Clear the error so it does not resurface in later checks.
cudaGetLastError();
throw std::runtime_error(
fmt::format("{} failed: {}", name, cudaGetErrorString(err)));
}
Expand Down
6 changes: 5 additions & 1 deletion python/src/buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,13 @@ extern "C" inline int getbuffer(PyObject* obj, Py_buffer* view, int flags) {
std::memset(view, 0, sizeof(Py_buffer));
auto a = nb::cast<mx::array>(nb::handle(obj));

{
// Exceptions can not propagate through the buffer protocol.
try {
nb::gil_scoped_release nogil;
a.eval();
} catch (const std::exception& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
return -1;
}

std::vector<Py_ssize_t> shape(a.shape().begin(), a.shape().end());
Expand Down
6 changes: 6 additions & 0 deletions python/tests/test_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,12 @@ def test_buffer_protocol_ref_counting(self):
mv = None
self.assertIsNone(wr())

def test_buffer_protocol_eval_error(self):
# Errors from evaluating the array are raised instead of aborting
a = mx.linalg.inv(mx.zeros((2, 2)), stream=mx.cpu)
with self.assertRaises(RuntimeError):
memoryview(a)

def test_array_view_ref_counting(self):
a = mx.arange(3)
wr = weakref.ref(a)
Expand Down
10 changes: 10 additions & 0 deletions tests/cuda_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,13 @@ TEST_CASE("test clear cache trims CUDA pool") {
cudaSuccess);
CHECK_LT(final_reserved, allocated_reserved);
}

TEST_CASE("test eval after cuda error") {
auto s = default_stream(Device::gpu);
// A failed allocation sets a CUDA error on the calling thread.
CHECK_THROWS(eval(zeros({1 << 20, 1 << 20}, float32, s)));
// The error must not resurface in later evaluations.
auto a = ones({4}, float32, s);
CHECK_NOTHROW(eval(a));
CHECK(array_equal(a, ones({4})).item<bool>());
}