diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 940c2885809..082e8a9eb90 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -3,3 +3,5 @@ self-hosted-runner: labels: - vllm-runners + # Not yet in actionlint's known-label set. + - macos-26 diff --git a/.github/workflows/macos-smoke-test.yml b/.github/workflows/macos-smoke-test.yml index 9068ec281b2..eb502578ea4 100644 --- a/.github/workflows/macos-smoke-test.yml +++ b/.github/workflows/macos-smoke-test.yml @@ -11,7 +11,19 @@ permissions: jobs: macos-m1-smoke-test: - runs-on: macos-latest + # macos-26 (the supported target) is still a preview runner, so gate on GA + # macos-15 and keep macos-26 non-blocking. + strategy: + fail-fast: false + matrix: + include: + - os: macos-15 + required: true + - os: macos-26 + required: false + name: macos-m1-smoke-test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + continue-on-error: ${{ !matrix.required }} timeout-minutes: 30 steps: @@ -72,14 +84,11 @@ jobs: # Test health endpoint curl -f http://localhost:8000/health - # Test completion - curl -f http://localhost:8000/v1/completions \ + # Long prompt: hits the split-KV path that short prompts skip (#46769). + 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}))") + curl -f --max-time 120 http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ - -d '{ - "model": "Qwen/Qwen3-0.6B", - "prompt": "Hello", - "max_tokens": 5 - }' + -d "$PAYLOAD" # Cleanup kill "$SERVER_PID" diff --git a/cmake/cpu_extension.cmake b/cmake/cpu_extension.cmake index 5c19446601e..9d8796c0d7a 100644 --- a/cmake/cpu_extension.cmake +++ b/cmake/cpu_extension.cmake @@ -24,7 +24,10 @@ set (ENABLE_NUMA TRUE) # Check the compile flags # if(MACOSX_FOUND) + # Apple clang needs -Xpreprocessor to enable OpenMP. No runtime link is + # needed: _C is a dynamic_lookup bundle and resolves libomp from torch. list(APPEND CXX_COMPILE_FLAGS + "-Xpreprocessor" "-fopenmp" "-DVLLM_CPU_EXTENSION") else() list(APPEND CXX_COMPILE_FLAGS diff --git a/csrc/cpu/cpu_attn_impl.hpp b/csrc/cpu/cpu_attn_impl.hpp index 7b3757b313d..260ed7cd417 100644 --- a/csrc/cpu/cpu_attn_impl.hpp +++ b/csrc/cpu/cpu_attn_impl.hpp @@ -124,7 +124,7 @@ struct AttentionMetadata { workitem_group_num(workitem_group_num), reduction_item_num(reduction_item_num), reduction_split_num(reduction_split_num), - thread_num(omp_get_max_threads()), + thread_num(cpu_utils::get_max_threads()), effective_thread_num(thread_num), split_kv_q_token_num_threshold(split_kv_q_token_num_threshold), attention_scratchpad_size_per_thread(0), @@ -405,7 +405,7 @@ class AttentionScheduler { torch::Tensor schedule(const ScheduleInput& input) const { const bool causal = input.causal; const bool is_dynamic_causal = input.dynamic_causal != nullptr; - const int32_t thread_num = omp_get_max_threads(); + const int32_t thread_num = cpu_utils::get_max_threads(); const int64_t cache_size = cpu_utils::get_available_l2_size(); const int32_t max_num_q_per_iter = input.max_num_q_per_iter; const int32_t kv_len_alignment = input.kv_block_alignment; @@ -1423,7 +1423,7 @@ class AttentionMainLoop { public: void operator()(const AttentionInput* input) { - const int thread_num = omp_get_max_threads(); + const int thread_num = cpu_utils::get_max_threads(); TORCH_CHECK_EQ(input->metadata->thread_num, thread_num); std::atomic guard_counter(0); std::atomic* guard_counter_ptr = &guard_counter; diff --git a/csrc/cpu/cpu_fused_moe.cpp b/csrc/cpu/cpu_fused_moe.cpp index 35c23df97be..07b0aaf8688 100644 --- a/csrc/cpu/cpu_fused_moe.cpp +++ b/csrc/cpu/cpu_fused_moe.cpp @@ -267,7 +267,7 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input, TORCH_CHECK_EQ(output_size_2 % gemm_n_tile_size, 0); TORCH_CHECK_EQ(output_size_13 / 2, input_size_2); - const int32_t thread_num = omp_get_max_threads(); + const int32_t thread_num = cpu_utils::get_max_threads(); const int32_t w13_input_buffer_size = cpu_utils::round_up<64>( gemm_m_tile_size * input_size_13 * sizeof(scalar_t)); diff --git a/csrc/cpu/cpu_types.hpp b/csrc/cpu/cpu_types.hpp index 744c80c8f53..7b2c3d3b74c 100644 --- a/csrc/cpu/cpu_types.hpp +++ b/csrc/cpu/cpu_types.hpp @@ -25,4 +25,20 @@ #include #endif +#include + +namespace cpu_utils { +// Without OpenMP the omp pragmas compile to serial loops, so report 1: kernels +// that barrier on the thread count would otherwise deadlock. +inline int get_max_threads() { +#ifdef _OPENMP + return omp_get_max_threads(); +#else + TORCH_WARN_ONCE( + "vLLM CPU was built without OpenMP; running single-threaded."); + return 1; +#endif +} +} // namespace cpu_utils + #endif \ No newline at end of file diff --git a/csrc/cpu/cpu_wna16.cpp b/csrc/cpu/cpu_wna16.cpp index 5c6d1ce48a7..ae7aef74c44 100644 --- a/csrc/cpu/cpu_wna16.cpp +++ b/csrc/cpu/cpu_wna16.cpp @@ -155,7 +155,7 @@ void cpu_gemm_wna16_impl( constexpr int32_t gemm_m_tile_size = gemm_t::MaxMSize; constexpr int32_t n_block_size = 16; static_assert(gemm_n_tile_size % n_block_size == 0); - const int32_t thread_num = omp_get_max_threads(); + const int32_t thread_num = cpu_utils::get_max_threads(); // a simple schedule policy, just to hold more B tiles in L2 and make sure // each thread has tasks diff --git a/csrc/cpu/dnnl_kernels.cpp b/csrc/cpu/dnnl_kernels.cpp index 058fe25b0e2..6dda0929616 100644 --- a/csrc/cpu/dnnl_kernels.cpp +++ b/csrc/cpu/dnnl_kernels.cpp @@ -202,7 +202,7 @@ void dynamic_quant_epilogue(const float* input, scalar_t* output, using cvt_vec_t = typename KernelVecType::cvt_vec_type; constexpr int vec_elem_num = load_vec_t::VEC_ELEM_NUM; - const int64_t thread_num = omp_get_max_threads(); + const int64_t thread_num = cpu_utils::get_max_threads(); if (num_tokens > thread_num) { #pragma omp parallel for for (int64_t i = 0; i < num_tokens; ++i) { diff --git a/csrc/cpu/mla_decode.cpp b/csrc/cpu/mla_decode.cpp index 3bd0d2e688f..702912a5bcc 100644 --- a/csrc/cpu/mla_decode.cpp +++ b/csrc/cpu/mla_decode.cpp @@ -251,7 +251,7 @@ void mla_decode_kvcache_cpu_impl( constexpr int QK_NUM_ELEM = qk_vec_type::VEC_ELEM_NUM; // shared across threads - const int max_threads = omp_get_max_threads(); + const int max_threads = cpu_utils::get_max_threads(); const int acc_out_nbytes = max_threads * num_heads * V_HEAD_DIM * sizeof(float); float* acc_out = static_cast(std::aligned_alloc(64, acc_out_nbytes)); diff --git a/docs/getting_started/installation/cpu.apple.inc.md b/docs/getting_started/installation/cpu.apple.inc.md index e54afc49384..e312964ec8a 100644 --- a/docs/getting_started/installation/cpu.apple.inc.md +++ b/docs/getting_started/installation/cpu.apple.inc.md @@ -15,6 +15,10 @@ Currently the CPU implementation for macOS supports FP32 and FP16 datatypes. - SDK: `XCode 15.4` or later with Command Line Tools - Compiler: `Apple Clang >= 15.0.0` +!!! note + The macOS CPU build is smoke-tested in CI on the latest GA Apple Silicon + runner; other macOS or Apple Clang versions are best-effort. + --8<-- [end:requirements] --8<-- [start:set-up-using-python]