[CPU] Fix macOS/Apple Silicon hang by enabling OpenMP in the build (#46769)
Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Michael Goin committed
Jun 26, 2026 at 12:32 UTC
c6554f321ce4c7563290d02eec323f262fc43fef
10 files changed
+49
-15
.github/actionlint.yaml
+2
@@ -3,3 +3,5 @@
3
self-hosted-runner:
4
labels:
5
- vllm-runners
6
+ # Not yet in actionlint's known-label set.
7
+ - macos-26
.github/workflows/macos-smoke-test.yml
+17
-8
@@ -11,7 +11,19 @@ permissions:
11
12
jobs:
13
macos-m1-smoke-test:
14
- runs-on: macos-latest
14
+ # macos-26 (the supported target) is still a preview runner, so gate on GA
15
+ # macos-15 and keep macos-26 non-blocking.
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ include:
20
+ - os: macos-15
21
+ required: true
22
+ - os: macos-26
23
+ required: false
24
+ name: macos-m1-smoke-test (${{ matrix.os }})
25
+ runs-on: ${{ matrix.os }}
26
+ continue-on-error: ${{ !matrix.required }}
27
timeout-minutes: 30
28
29
steps:
@@ -72,14 +84,11 @@ jobs:
84
# Test health endpoint
85
curl -f http://localhost:8000/health
86
75
- # Test completion
76
- curl -f http://localhost:8000/v1/completions \
87
+ # Long prompt: hits the split-KV path that short prompts skip (#46769).
88
+ PAYLOAD=$(python -c "import json; print(json.dumps({'model': 'Qwen/Qwen3-0.6B', 'prompt': 'The quick brown fox jumps over the lazy dog. ' * 24, 'max_tokens': 16}))")
89
+ curl -f --max-time 120 http://localhost:8000/v1/completions \
90
-H "Content-Type: application/json" \
78
- -d '{
79
- "model": "Qwen/Qwen3-0.6B",
80
- "prompt": "Hello",
81
- "max_tokens": 5
82
- }'
91
+ -d "$PAYLOAD"
92
93
# Cleanup
94
kill "$SERVER_PID"
cmake/cpu_extension.cmake
+3
@@ -24,7 +24,10 @@ set (ENABLE_NUMA TRUE)
24
# Check the compile flags
25
#
26
if(MACOSX_FOUND)
27
+ # Apple clang needs -Xpreprocessor to enable OpenMP. No runtime link is
28
+ # needed: _C is a dynamic_lookup bundle and resolves libomp from torch.
29
list(APPEND CXX_COMPILE_FLAGS
30
+ "-Xpreprocessor" "-fopenmp"
31
"-DVLLM_CPU_EXTENSION")
32
else()
33
list(APPEND CXX_COMPILE_FLAGS
csrc/cpu/cpu_attn_impl.hpp
+3
-3
@@ -124,7 +124,7 @@ struct AttentionMetadata {
124
workitem_group_num(workitem_group_num),
125
reduction_item_num(reduction_item_num),
126
reduction_split_num(reduction_split_num),
127
- thread_num(omp_get_max_threads()),
127
+ thread_num(cpu_utils::get_max_threads()),
128
effective_thread_num(thread_num),
129
split_kv_q_token_num_threshold(split_kv_q_token_num_threshold),
130
attention_scratchpad_size_per_thread(0),
@@ -405,7 +405,7 @@ class AttentionScheduler {
405
torch::Tensor schedule(const ScheduleInput& input) const {
406
const bool causal = input.causal;
407
const bool is_dynamic_causal = input.dynamic_causal != nullptr;
408
- const int32_t thread_num = omp_get_max_threads();
408
+ const int32_t thread_num = cpu_utils::get_max_threads();
409
const int64_t cache_size = cpu_utils::get_available_l2_size();
410
const int32_t max_num_q_per_iter = input.max_num_q_per_iter;
411
const int32_t kv_len_alignment = input.kv_block_alignment;
@@ -1423,7 +1423,7 @@ class AttentionMainLoop {
1423
1424
public:
1425
void operator()(const AttentionInput* input) {
1426
- const int thread_num = omp_get_max_threads();
1426
+ const int thread_num = cpu_utils::get_max_threads();
1427
TORCH_CHECK_EQ(input->metadata->thread_num, thread_num);
1428
std::atomic<int32_t> guard_counter(0);
1429
std::atomic<int32_t>* guard_counter_ptr = &guard_counter;
csrc/cpu/cpu_fused_moe.cpp
+1
-1
@@ -267,7 +267,7 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
267
TORCH_CHECK_EQ(output_size_2 % gemm_n_tile_size, 0);
268
TORCH_CHECK_EQ(output_size_13 / 2, input_size_2);
269
270
- const int32_t thread_num = omp_get_max_threads();
270
+ const int32_t thread_num = cpu_utils::get_max_threads();
271
272
const int32_t w13_input_buffer_size = cpu_utils::round_up<64>(
273
gemm_m_tile_size * input_size_13 * sizeof(scalar_t));
csrc/cpu/cpu_types.hpp
+16
@@ -25,4 +25,20 @@
25
#include <omp.h>
26
#endif
27
28
+#include <c10/util/Exception.h>
29
+
30
+namespace cpu_utils {
31
+// Without OpenMP the omp pragmas compile to serial loops, so report 1: kernels
32
+// that barrier on the thread count would otherwise deadlock.
33
+inline int get_max_threads() {
34
+#ifdef _OPENMP
35
+ return omp_get_max_threads();
36
+#else
37
+ TORCH_WARN_ONCE(
38
+ "vLLM CPU was built without OpenMP; running single-threaded.");
39
+ return 1;
40
+#endif
41
+}
42
+} // namespace cpu_utils
43
+
44
#endif
\ No newline at end of file
csrc/cpu/cpu_wna16.cpp
+1
-1
@@ -155,7 +155,7 @@ void cpu_gemm_wna16_impl(
155
constexpr int32_t gemm_m_tile_size = gemm_t::MaxMSize;
156
constexpr int32_t n_block_size = 16;
157
static_assert(gemm_n_tile_size % n_block_size == 0);
158
- const int32_t thread_num = omp_get_max_threads();
158
+ const int32_t thread_num = cpu_utils::get_max_threads();
159
160
// a simple schedule policy, just to hold more B tiles in L2 and make sure
161
// each thread has tasks
csrc/cpu/dnnl_kernels.cpp
+1
-1
@@ -202,7 +202,7 @@ void dynamic_quant_epilogue(const float* input, scalar_t* output,
202
using cvt_vec_t = typename KernelVecType<scalar_t>::cvt_vec_type;
203
constexpr int vec_elem_num = load_vec_t::VEC_ELEM_NUM;
204
205
- const int64_t thread_num = omp_get_max_threads();
205
+ const int64_t thread_num = cpu_utils::get_max_threads();
206
if (num_tokens > thread_num) {
207
#pragma omp parallel for
208
for (int64_t i = 0; i < num_tokens; ++i) {
csrc/cpu/mla_decode.cpp
+1
-1
@@ -251,7 +251,7 @@ void mla_decode_kvcache_cpu_impl(
251
constexpr int QK_NUM_ELEM = qk_vec_type::VEC_ELEM_NUM;
252
253
// shared across threads
254
- const int max_threads = omp_get_max_threads();
254
+ const int max_threads = cpu_utils::get_max_threads();
255
const int acc_out_nbytes =
256
max_threads * num_heads * V_HEAD_DIM * sizeof(float);
257
float* acc_out = static_cast<float*>(std::aligned_alloc(64, acc_out_nbytes));
docs/getting_started/installation/cpu.apple.inc.md
+4
@@ -15,6 +15,10 @@ Currently the CPU implementation for macOS supports FP32 and FP16 datatypes.
15
- SDK: `XCode 15.4` or later with Command Line Tools
16
- Compiler: `Apple Clang >= 15.0.0`
17
18
+!!! note
19
+ The macOS CPU build is smoke-tested in CI on the latest GA Apple Silicon
20
+ runner; other macOS or Apple Clang versions are best-effort.
21
+
22
--8<-- [end:requirements]
23
--8<-- [start:set-up-using-python]
24