| 1 | name: macOS Apple Silicon Smoke Test |
| 2 | |
| 3 | on: |
| 4 | schedule: |
| 5 | # Daily at 2:30 AM UTC |
| 6 | - cron: '30 2 * * *' |
| 7 | workflow_dispatch: # Manual trigger |
| 8 | |
| 9 | permissions: |
| 10 | contents: read |
| 11 | |
| 12 | jobs: |
| 13 | macos-m1-smoke-test: |
| 14 | if: github.repository_owner == 'vllm-project' |
| 15 | # macos-26 (the supported target) is still a preview runner, so gate on GA |
| 16 | # macos-15 and keep macos-26 non-blocking. |
| 17 | strategy: |
| 18 | fail-fast: false |
| 19 | matrix: |
| 20 | include: |
| 21 | - os: macos-15 |
| 22 | required: true |
| 23 | - os: macos-26 |
| 24 | required: false |
| 25 | name: macos-m1-smoke-test (${{ matrix.os }}) |
| 26 | runs-on: ${{ matrix.os }} |
| 27 | continue-on-error: ${{ !matrix.required }} |
| 28 | timeout-minutes: 30 |
| 29 | |
| 30 | steps: |
| 31 | - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 |
| 32 | |
| 33 | - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 |
| 34 | with: |
| 35 | enable-cache: true |
| 36 | cache-dependency-glob: | |
| 37 | requirements/**/*.txt |
| 38 | pyproject.toml |
| 39 | python-version: '3.12' |
| 40 | |
| 41 | - name: Create virtual environment |
| 42 | run: | |
| 43 | uv venv |
| 44 | echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" |
| 45 | |
| 46 | - name: Install dependencies and build vLLM |
| 47 | run: | |
| 48 | uv pip install -r requirements/build/cpu.txt --index-strategy unsafe-best-match |
| 49 | uv pip install -r requirements/cpu.txt --index-strategy unsafe-best-match |
| 50 | uv pip install -e . --no-build-isolation |
| 51 | env: |
| 52 | CMAKE_BUILD_PARALLEL_LEVEL: 4 |
| 53 | |
| 54 | - name: Verify installation |
| 55 | run: | |
| 56 | python -c "import vllm; print(f'vLLM version: {vllm.__version__}')" |
| 57 | |
| 58 | - name: Smoke test vllm serve |
| 59 | run: | |
| 60 | # Start server in background |
| 61 | VLLM_CPU_KVCACHE_SPACE=1 \ |
| 62 | vllm serve Qwen/Qwen3-0.6B \ |
| 63 | --max-model-len=2K \ |
| 64 | --load-format=dummy \ |
| 65 | --hf-overrides '{"num_hidden_layers": 2}' \ |
| 66 | --enforce-eager \ |
| 67 | --port 8000 & |
| 68 | |
| 69 | SERVER_PID=$! |
| 70 | |
| 71 | # Wait for server to start |
| 72 | for i in {1..30}; do |
| 73 | if curl -s http://localhost:8000/health > /dev/null; then |
| 74 | echo "Server started successfully" |
| 75 | break |
| 76 | fi |
| 77 | if [ "$i" -eq 30 ]; then |
| 78 | echo "Server failed to start" |
| 79 | kill "$SERVER_PID" |
| 80 | exit 1 |
| 81 | fi |
| 82 | sleep 2 |
| 83 | done |
| 84 | |
| 85 | # Test health endpoint |
| 86 | curl -f http://localhost:8000/health |
| 87 | |
| 88 | # Long prompt: hits the split-KV path that short prompts skip (#46769). |
| 89 | 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}))") |
| 90 | curl -f --max-time 120 http://localhost:8000/v1/completions \ |
| 91 | -H "Content-Type: application/json" \ |
| 92 | -d "$PAYLOAD" |
| 93 | |
| 94 | # Cleanup |
| 95 | kill "$SERVER_PID" |