Ringkasan Utama
vllm-metal menjalankan continuous batching, paged KV cache, dan server OpenAI-compatible milik vLLM di Apple Silicon lewat MLX, dan keuntungannya terlihat saat banyak request tumpang tindih, bukan pada satu prompt. Pasang lewat Homebrew di macOS 15 atau lebih baru, atur memory budget, dan anggap MTP serta hybrid prefix caching sebagai fitur sempit yang masih eksperimental.
Saya ingin tahu apakah Mac di meja saya bisa melayani tiga sesi coding sekaligus tanpa sesi kedua tertahan di belakang yang pertama. Satu prompt di satu model itu mudah di Apple Silicon. Masalah muncul ketika prompt panjang datang bersama prompt pendek dan setiap request menunggu yang paling lambat.
Saya tidak menjalankan benchmark vllm-metal sendiri. Ini adalah pembacaan teliti atas pengumuman tim vLLM tanggal 22 September 2026, panduan instalasi dan model matrix di repositori, release notes v0.29.0, serta roadmap issue yang masih terbuka. Setiap angka di bawah adalah milik mereka, diukur di hardware mereka, dan saya sebutkan di mana hal itu penting.
Di Mac, masalahnya adalah request yang tumpang tindih, bukan kecepatan mentah
Pengumuman itu membuka dengan diagnosis yang tepat: inferensi lokal itu mudah sampai beberapa request tumpang tindih, lalu time to first token (TTFT), pertumbuhan memori, dan admission control menjadi masalah serving, bukan masalah eksekusi model. vllm-metal menjawabnya dengan bagian vLLM yang sudah menyelesaikan hal itu di GPU: scheduler V1, paged KV block management, chunked prefill, dan frontend OpenAI-compatible. Eksekusinya dikerjakan MLX dan Metal.
Pembagian tugasnya bersih. vLLM menyediakan scheduler, KV block, sampling, dan frontend dengan streaming serta tool-call parsing. mlx_lm menyediakan implementasi model, dan vllm-metal memakai ulang weight loading serta layer token-wise miliknya (RMSNorm, linear, MoE, MLP) tanpa diubah, hanya mengganti attention dengan paged varlen Metal kernel yang di-port dari unified Triton kernel milik vLLM. Dampak praktisnya: sebagian besar kode spesifik model ada di satu layer, dan client apa pun yang bicara OpenAI API tidak perlu diubah.
Pasang dan jalankan model pertama
Anda butuh Apple Silicon dan macOS 15 atau lebih baru. Panduan instalasi menyebut tidak perlu compiler, karena instalasi membawa Python 3.12, vLLM core, dan wheel vllm-metal yang sudah dibangun. Homebrew tap adalah jalur stabil, dan endpoint-nya muncul di http://localhost:8000/v1.
# Apple Silicon and macOS 15 or later. No compiler: it installs prebuilt wheels.
brew tap vllm-project/vllm-metal https://github.com/vllm-project/vllm-metal
brew install vllm-project/vllm-metal/vllm-metal
# Start small, with half of unified memory as the serving budget.
vllm serve Qwen/Qwen3.5-0.8B --gpu-memory-utilization 0.5
# In a second terminal. The endpoint is OpenAI-compatible under /v1.
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "Qwen/Qwen3.5-0.8B",
"messages": [{"role": "user", "content": "Say hi"}]}'
# Upgrade and uninstall are Homebrew's job, not pip's.
brew update && brew upgrade vllm-metal
brew uninstall vllm-metal
# The alternative: the install script. It builds a dev release unless you ask
# for --stable, and keeps its own environment at ~/.venv-vllm-metal.
curl -fsSL https://raw.githubusercontent.com/vllm-project/vllm-metal/main/install.sh \
| bash -s -- --stable
source ~/.venv-vllm-metal/bin/activate # again in every new shell
# Wrong: the installation guide states this is not supported.
# pip install vllm-metalDua detail di blok itu mudah terlewat. Install script secara default memasang build development terbaru dan baru memasang rilis stabil kalau Anda menambahkan --stable. Lalu pip bukan pilihan: panduan itu menyatakan dengan jelas bahwa pip install vllm-metal tidak didukung, jadi tutorial yang menyuruh begitu sudah usang.
Atur memory budget sebelum memilih model
Di Mac, GPU dan aplikasi Anda berbagi satu pool unified memory, jadi vllm-metal memberi satu pengatur budget, --gpu-memory-utilization. Seperti vLLM upstream, ia menjalankan warmup pass saat startup untuk menghitung bobot model, activation, dan temporary buffer, lalu budget yang tersisa menjadi KV cache tetap. Ia juga membatasi reusable buffer cache milik MLX supaya memori tidak merayap naik saat serving. Request yang tidak muat di KV pool menunggu sampai ada page kosong; request itu tidak dibuang.
Contoh di pengumuman memakai 0.5 untuk model 0.8B dan 4B, serta 0.7 untuk hybrid 27B di Mac 64 GB, dan catatan reproduksinya menyebut 0.92 sebagai default vllm-metal. Bacaan saya, yang merupakan inferensi dan bukan aturan terdokumentasi, adalah bahwa angka ini menukar dua kegagalan: terlalu tinggi membuat macOS dan aplikasi lain kekurangan, terlalu rendah mengecilkan KV pool sehingga prompt panjang yang bersamaan harus antre.
Anggap 0.5 sebagai titik awal dari contoh di pengumuman, bukan rekomendasi untuk mesin Anda. Naikkan sedikit demi sedikit sambil memantau memory pressure di Activity Monitor, dan berhenti saat Mac mulai terasa lebih lambat untuk semua hal lain.
Packed query dan paged KV adalah pembeda yang sebenarnya
Di padded batch milik mlx_lm, query attention berbentuk B, H, T_max, D, sehingga setiap request di-pad ke query terpanjang di batch, dan scaled_dot_product_attention milik MLX tidak punya interface varlen. vllm-metal mengemas semua query token yang dijadwalkan ke total_q, H, D dengan cu_seqlens sebagai penanda batas request, menjalankan step prefill dan decode campuran dalam satu forward, dan menyimpan KV di page berukuran tetap yang dialamatkan lewat block table per request. Pengumuman itu membandingkan tujuh engine pada tiga pilihan tersebut.
| Engine | Prefill attention | Decode batching | Penyimpanan KV |
|---|---|---|---|
| Lily | panggilan per prompt | satu request | contiguous |
| Uzu | panggilan per prompt | satu request | contiguous |
| oMLX | panggilan per prompt | batched | contiguous |
| Splash | panggilan per prompt | batched, maksimal 4 | paged |
| mlx_lm | di-pad ke panjang yang sama | batched | contiguous |
| llama.cpp | mask atas slot | batched plus prefill | cell tetap |
| vllm-metal | packed, dengan cu_seqlens | batched plus prefill | paged |
Apakah itu berpengaruh? Tim mengukur Qwen3.6-35B-A3B 4-bit pada batch berisi delapan request dengan total sekitar 6.000 prompt token dan 20 output token per request. Batch A berisi prompt dengan panjang serupa. Batch B berisi satu prompt panjang di antara yang pendek. Tabel menunjukkan wall time batch dalam detik.
| Batch | Prompt token | mlx_lm | oMLX | llama.cpp | vllm-metal |
|---|---|---|---|---|---|
| A | 750 x 8 | 4.52 | 7.32 | 5.29 | 3.87 |
| B | 3,000 + 430 x 7 | 10.99 | 7.22 | 5.33 | 3.64 |
| Perubahan dari A ke B | +143% | -1% | +1% | -6% | |
Temuannya: hanya engine yang memakai padding yang terpukul. Waktu batch mlx_lm naik dari 4,52 detik menjadi 10,99 detik begitu satu prompt 3.000 token bergabung dengan tujuh prompt 430 token, naik 143%, sedangkan vllm-metal turun dari 3,87 detik ke 3,64 detik. oMLX dan llama.cpp juga datar tetapi lebih lambat secara absolut. Bacalah dengan bobot yang tepat: ini satu model, satu batch delapan request, dan 20 output token, dipublikasikan oleh proyek yang menang di tabel itu.
Apa yang ditunjukkan uji agent-load, dan apa yang tidak
Untuk traffic yang realistis, tim memakai agent split dari SiliconBench: 100 prompt multi-turn dengan sekitar 4,6K input token masing-masing, di M5 Pro 64 GB, semuanya 4-bit, dengan server baru di setiap level concurrency. Posting itu mengutip paper SiliconBench yang mengevaluasi sembilan engine Apple Silicon secara lebih luas. Saya tidak membaca paper itu, jadi saya tidak bersandar padanya.
Hasilnya campuran dengan cara yang berguna. Di Qwen3.8-27B, vllm-metal punya TTFT dan latency end-to-end terendah pada concurrency 2 dan 4, tetapi oMLX dengan SSD offload unggul pada concurrency 1. Di Gemma 4 E4B, yang disapu sampai concurrency 16, vllm-metal menjaga TTFT tetap rendah. Di Qwen3.6-35B-A3B pada concurrency 4, vllm-metal dan oMLX dengan RAM cache berdekatan dalam throughput dan latency, dengan selisih TTFT yang lebih besar, dan mlx_lm hanya menyelesaikan sebagian kecil request.
Pelajarannya: baca dulu titik concurrency 1. Kalau Anda satu developer dengan satu agent, hasil 27B di pengumuman itu sendiri menunjukkan engine lain bisa lebih cepat. Alasan memakai vllm-metal adalah dua sesi atau lebih yang tumpang tindih, yang persis bentuk Mac bersama satu tim atau beberapa coding agent paralel.
MTP speculative decoding membantu satu pengguna, hampir tidak membantu keramaian
Multi-token prediction (MTP) memakai model asisten untuk men-draft token yang diverifikasi oleh model target di dalam continuous batch. Fitur ini opt-in lewat --speculative-config. Pengumuman membandingkan satu draft token per step dengan tanpa MTP di Gemma 4 E4B, dan efeknya sangat bergantung pada concurrency.
| Concurrency | Wall time vs tanpa MTP | Output tok/s vs tanpa MTP | Rata-rata TTFT vs tanpa MTP |
|---|---|---|---|
| 1 | -15% | +20% | -1% |
| 8 | -1% | +0% | +4% |
| 16 | -8% | +9% | +20% |
Pada concurrency 1, wall time turun 15% dan output throughput naik 20%. Pada concurrency 8 keuntungannya hilang, dan pada 16 rata-rata TTFT 20% lebih buruk meski wall time 8% lebih baik. Jadi MTP adalah fitur latency untuk satu pengguna, dan cakupannya sempit: hanya Gemma 4, greedy sampling dengan temperature 0, dan synchronous scheduling.
# Right: inside the documented envelope. Gemma 4, synchronous scheduling,
# one draft token per step, and a client that sends temperature 0.
vllm serve google/gemma-4-E4B-it \
--gpu-memory-utilization 0.5 \
--max-model-len 16384 \
--no-async-scheduling \
--speculative-config '{"method":"mtp","model":"mlx-community/gemma-4-E4B-it-assistant-bf16","num_speculative_tokens":1}'
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "google/gemma-4-E4B-it", "temperature": 0,
"messages": [{"role": "user", "content": "Explain paged attention in two sentences."}]}'
# Wrong: each of these steps outside what the announcement says the Metal
# path supports, so do not build on it.
# - a model family other than Gemma 4
# - any sampling other than greedy (temperature above 0)
# - leaving out --no-async-schedulingKalau client Anda mengirim temperature di atas nol, MTP tidak berlaku untuk mereka sesuai dokumentasi. Periksa dulu apa yang sebenarnya dikirim client Anda sebelum merencanakan sesuatu dengan angka 20% itu.
Trade-off yang menentukan apakah Anda bisa mengandalkannya
Saya tidak mengalami kegagalan, karena saya tidak menjalankannya, jadi bagian ini mendaftar apa yang menurut sumbernya sendiri terbatas. Setiap baris tertulis di pengumuman, model matrix, release notes v0.29.0, atau roadmap issue.
| Area | Status menurut sumber | Biayanya bagi Anda |
|---|---|---|
| MTP | Hanya Gemma 4, temperature 0, --no-async-scheduling | Tidak ada percepatan untuk model lain atau output ber-sampling |
| Hybrid prefix caching | Mode align eksperimental untuk model gaya Qwen3.5. Tidak bisa digabung dengan speculative decoding, dan catatan v0.29.0 menyebut output cache-on dan cache-off masih berbeda pada beberapa prompt | Bandingkan output dengan cache dimatikan sebelum memercayainya untuk agent multi-turn |
| Hybrid Nemotron-H dan Granite 4.0 | Prefix caching dimatikan | Seluruh percakapan di-prefill ulang di setiap giliran |
| GGUF | Terverifikasi end to end pada tiga model (Qwen3-0.6B, Llama-3.2-1B-Instruct, Mistral-7B-Instruct-v0.3), hanya qwen2, qwen3, llama, dan mistral yang dense | GGUF favorit Anda mungkin tidak bisa dimuat; cek matrix dulu |
| Vision, embedding, speech | Eksperimental | Anggap sebagai preview, bukan layanan yang Anda janjikan ke orang lain |
| Perubahan upstream | v0.29.0 menolak VLLM_USE_V2_MODEL_RUNNER=1. Prioritas teratas roadmap adalah menjaga backend MLX tetap jalan saat vLLM menghapus runner lamanya di versi 0.32 | Upgrade vLLM adalah sumber kerusakan yang paling mungkin |
| Dependency yang di-pin | MLX di-pin tepat 0.32.1, transformers 5.10.4 atau lebih baru, huggingface_hub 1.28.0 atau lebih baru, Python 3.12 arm64 native | Python environment bersama bisa bentrok; biarkan Homebrew yang mengelolanya |
Release notes mem-pin MLX ke satu versi persis agar cocok dengan native extension yang sudah dibangun, dan roadmap menandai penghapusan runner di vLLM 0.32. Kalau Mac itu melayani sesuatu yang diandalkan orang lain, kunci versinya dengan brew pin vllm-metal dan upgrade dengan sengaja, di hari ketika Anda bisa rollback.
Ada juga catatan struktural. Proyek ini digambarkan sebagai hardware plugin yang dikelola komunitas, dan pengumuman mencantumkan backend PyTorch MPS eksperimental sebagai item roadmap, dengan roadmap issue melaporkan selisih sekitar 30% terhadap MLX. Jalur MLX adalah yang dipakai hari ini.
Yang dikerjakan hari Senin
Jangan mengadopsinya hanya karena benchmark orang lain, termasuk sumber posting ini. Habiskan satu jam untuk mengukur workload Anda sendiri.
- Cek dasarnya: Apple Silicon, macOS 15 atau lebih baru, dan unified memory yang cukup untuk model ditambah KV budget. Pasang dengan Homebrew dan jalankan satu model yang sudah Anda pakai, di 0.5.
- Cari model Anda di supported matrix sebelum mengunduh apa pun. Catat apakah barisnya supported, experimental, atau unverified.
- Jalankan benchmark dari pengumuman di bawah pada concurrency 1 dan sekali lagi pada concurrency yang benar-benar Anda perkirakan, restart server di antara run, dan simpan jumlah request yang berhasil di samping tiap hasil.
- Jalankan dua titik yang sama terhadap apa pun yang Anda pakai sekarang. Kalau concurrency 1 memenangkan yang lama dan traffic Anda satu pengguna, Anda sudah punya jawabannya.
- Baru setelah itu coba MTP, hanya di Gemma 4, hanya dengan temperature 0, dan pin versinya setelah berjalan.
# Terminal 1: the announcement's cross-machine setup. Prefix caching is off so
# every run measures prefill, and the budget is fixed at 0.5.
vllm serve mlx-community/gemma-4-e4b-it-4bit \
--gpu-memory-utilization 0.5 \
--max-model-len 2048 \
--no-enable-prefix-caching \
--host 127.0.0.1 --port 8000
# Terminal 2: 100 Sonnet prompts, about 1,024 tokens in and 128 out.
curl -fsSL https://raw.githubusercontent.com/vllm-project/vllm/main/benchmarks/sonnet.txt \
-o sonnet.txt
BENCH_MACHINE=my-mac-32gb
BENCH_CONCURRENCY=1 # rerun at 8, restarting the server each time
vllm bench serve \
--backend vllm \
--model mlx-community/gemma-4-e4b-it-4bit \
--base-url http://127.0.0.1:8000 \
--dataset-name sonnet --dataset-path sonnet.txt \
--sonnet-input-len 1024 --sonnet-output-len 128 \
--num-prompts 100 --num-warmups 3 \
--request-rate 10 --max-concurrency "$BENCH_CONCURRENCY" \
--temperature 0 --ignore-eos --seed 0 \
--save-result --result-dir benchmark-results \
--result-filename "$BENCH_MACHINE-c$BENCH_CONCURRENCY.json"
# Pointing a coding agent at it follows the vLLM Claude Code page. The model
# name must match --served-model-name and must not contain a slash, so serve
# with --served-model-name my-model rather than the raw Hugging Face id.
# That page documents plain vLLM; I did not check it against vllm-metal.
ANTHROPIC_BASE_URL=http://localhost:8000 \
ANTHROPIC_API_KEY=dummy ANTHROPIC_AUTH_TOKEN=dummy \
ANTHROPIC_DEFAULT_OPUS_MODEL=my-model \
ANTHROPIC_DEFAULT_SONNET_MODEL=my-model \
ANTHROPIC_DEFAULT_HAIKU_MODEL=my-model \
claudeSatu catatan praktis untuk coding agent: halaman Claude Code di dokumentasi vLLM mensyaratkan nama model pada environment variable sama dengan --served-model-name, dan nama itu tidak boleh mengandung garis miring. Jadi id Hugging Face mentah seperti Qwen/Qwen3.5-0.8B perlu alias. Halaman itu mendokumentasikan vLLM biasa dan saya tidak memverifikasinya terhadap vllm-metal.
Mac menjadi kotak inferensi bersama yang kredibel ketika ia menjadwalkan request seperti server, bukan menjalankannya seperti script. vllm-metal membawa scheduler itu ke Apple Silicon. Adopsi untuk sesi yang tumpang tindih, biarkan MTP dan hybrid caching di kolom eksperimen, dan biarkan angka concurrency 1 dan concurrency N milik Anda sendiri yang memutuskan.
