[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