Skip to content

Commit 3ab748b

Browse files
authored
Apply the same thread-local approach to the CPU (#3537)
1 parent 933a4a7 commit 3ab748b

7 files changed

Lines changed: 42 additions & 10 deletions

File tree

mlx/backend/cpu/encoder.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22

33
#include "mlx/backend/cpu/encoder.h"
44

5+
#include <fmt/format.h>
6+
57
namespace mlx::core::cpu {
68

9+
std::unordered_map<int, CommandEncoder>& get_command_encoders() {
10+
static thread_local std::unordered_map<int, CommandEncoder> encoders;
11+
return encoders;
12+
}
13+
714
CommandEncoder& get_command_encoder(Stream stream) {
8-
static std::unordered_map<int, CommandEncoder> encoder_map;
9-
auto it = encoder_map.find(stream.index);
10-
if (it == encoder_map.end()) {
11-
it = encoder_map.emplace(stream.index, stream).first;
15+
auto& encoders = get_command_encoders();
16+
auto it = encoders.find(stream.index);
17+
if (it == encoders.end()) {
18+
throw std::runtime_error(
19+
fmt::format(
20+
"There is no Stream(cpu, {}) in current thread.", stream.index));
1221
}
1322
return it->second;
1423
}

mlx/backend/cpu/encoder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,6 @@ struct MLX_API CommandEncoder {
6363
};
6464

6565
MLX_API CommandEncoder& get_command_encoder(Stream stream);
66+
std::unordered_map<int, CommandEncoder>& get_command_encoders();
6667

6768
} // namespace mlx::core::cpu

mlx/backend/cpu/eval.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77

88
namespace mlx::core::cpu {
99

10+
void new_stream(Stream s) {
11+
auto& encoders = get_command_encoders();
12+
encoders.try_emplace(s.index, s);
13+
}
14+
15+
void clear_streams() {
16+
get_command_encoders().clear();
17+
}
18+
1019
void eval(array& arr) {
1120
auto s = arr.primitive().stream();
1221

mlx/backend/cpu/eval.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace mlx::core::cpu {
99

10+
void new_stream(Stream s);
1011
void eval(array& arr);
12+
void clear_streams();
1113

1214
} // namespace mlx::core::cpu

mlx/scheduler.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright © 2023 Apple Inc.
22

33
#include "mlx/scheduler.h"
4+
#include "mlx/backend/cpu/eval.h"
45
#include "mlx/backend/gpu/eval.h"
56

67
namespace mlx::core {
@@ -25,6 +26,7 @@ void synchronize() {
2526
}
2627

2728
void clear_streams() {
29+
cpu::clear_streams();
2830
gpu::clear_streams();
2931
}
3032

mlx/stream.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "mlx/stream.h"
44
#include "mlx/backend/cpu/device_info.h"
5+
#include "mlx/backend/cpu/eval.h"
56
#include "mlx/backend/gpu/device_info.h"
67
#include "mlx/backend/gpu/eval.h"
78

@@ -70,6 +71,8 @@ Stream new_stream(Device d) {
7071
auto& s = streams.emplace_back(index, d);
7172
if (d == Device::gpu) {
7273
gpu::new_stream(s);
74+
} else {
75+
cpu::new_stream(s);
7376
}
7477
return s;
7578
}

python/tests/test_threads.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,43 @@
99

1010
class TestThreads(mlx_tests.MLXTestCase):
1111
def test_threadlocal_stream(self):
12+
raised = [False, False]
1213
test_stream = mx.new_stream(mx.default_device())
1314

14-
def test_failure():
15+
def test_failure(i):
1516
with self.assertRaises(RuntimeError):
1617
with mx.stream(test_stream):
1718
x = mx.arange(10)
1819
mx.eval(2 * x)
1920
mx.clear_streams()
21+
raised[i] = True
2022

21-
t1 = threading.Thread(target=test_failure)
22-
t2 = threading.Thread(target=test_failure)
23+
t1 = threading.Thread(target=test_failure, args=(0,))
24+
t2 = threading.Thread(target=test_failure, args=(1,))
2325
t1.start()
2426
t2.start()
2527
t1.join()
2628
t2.join()
29+
self.assertTrue(all(raised))
2730

31+
raised = [True, True]
2832
test_stream = mx.new_thread_local_stream(mx.default_device())
2933

30-
def test_success():
34+
def test_success(i):
3135
with mx.stream(test_stream):
3236
x = mx.arange(10)
3337
mx.eval(2 * x)
3438
self.assertEqual(x.tolist(), list(range(10)))
3539
mx.clear_streams()
40+
raised[i] = False
3641

37-
t1 = threading.Thread(target=test_success)
38-
t2 = threading.Thread(target=test_success)
42+
t1 = threading.Thread(target=test_success, args=(0,))
43+
t2 = threading.Thread(target=test_success, args=(1,))
3944
t1.start()
4045
t2.start()
4146
t1.join()
4247
t2.join()
48+
self.assertFalse(any(raised))
4349

4450

4551
if __name__ == "__main__":

0 commit comments

Comments
 (0)