feat(vllm): macOS/Metal support via vllm-metal (MLX) - #10489
Merged
Conversation
Add an additive Apple-Silicon path to the existing vllm Python backend so vLLM runs on macOS via vllm-metal (github.com/vllm-project/vllm-metal). Spike outcome (proven on a real M4 / macOS 26.5, Qwen3-0.6B): - vllm-metal registers through vLLM's platform-plugin entry point (metal -> vllm_metal:register); MetalPlatform activates and runs on the GPU through MLX. - LocalAI's backend.py is UNCHANGED: AsyncEngineArgs(...) -> AsyncLLMEngine.from_engine_args transparently resolves to vLLM 0.23's v1 AsyncLLM MLX engine, and async generate produced correct output. - backend.py is NOT touched: its only empty_cache() call is CUDA-only (guarded by torch.cuda.is_available()), so the benign shutdown-only "Allocator for mps is not a DeviceAllocator" noise comes from vLLM's internal EngineCore teardown, not from our code. Changes (all gated behind a darwin condition; Linux/CUDA/ROCm/Intel paths are byte-for-byte unchanged): - install.sh: darwin branch forces PYTHON_VERSION=3.12 (vllm-metal requirement), creates/activates LocalAI's managed venv via ensureVenv, then reproduces vllm-metal's installer INTO that venv (build vLLM 0.23.0 from the release source tarball against requirements/cpu.txt, then install the prebuilt vllm-metal wheel from its latest GitHub release), and runs runProtogen. installRequirements is skipped on darwin. - backend-matrix.yml: add a vllm includeDarwin entry (mps, python). - index.yaml: add metal capability + concrete metal-vllm / metal-vllm-development child entries mirroring the metal-kitten-tts template. Version coupling: vllm-metal pins vLLM 0.23.0, equal to LocalAI's current vllm pin. Bumping vllm must be coordinated with a supporting vllm-metal release; documented in install.sh and requirements-cublas13-after.txt. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
The Apple Silicon build pinned vLLM 0.23.0 as a hidden string in install.sh while floating the vllm-metal wheel on releases/latest - the two could drift apart silently. Make both a tracked, reproducible pair (VLLM_METAL_VERSION + VLLM_VERSION), fetch the wheel by tag, and add .github/bump_vllm_metal.sh wired into bump_deps.yaml. It tracks vllm-project/vllm-metal (not vllm/vllm latest), reading the coupled vLLM source version from vllm-metal's own installer, and opens a bump PR - mirroring the existing bump_vllm_wheel.sh for the cu130 wheel. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
Follow-up: VLLM_VERSION was still a hardcoded string duplicating what VLLM_METAL_VERSION already determines. Derive it at install time from vllm-metal's own installer (vllm_v=) at the pinned tag - one source of truth, no second value to drift. The bumper now touches only VLLM_METAL_VERSION; the derivation is immutable per tag, so builds stay reproducible. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
The darwin build resolved the wheel URL via api.github.com, whose unauthenticated rate limit (60/hr per IP) 403s on shared macOS runners (observed after the 9-min vLLM source build). Construct the release-asset download URL deterministically from the pinned tag and the cp312/arm64 wheel name instead - no API call, no rate limit. Verified the URL resolves (200). Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
Audit of the Score path against vllm-metal (MLX on macOS): the engine accepts SamplingParams(prompt_logprobs=1) but returns an all-None prompt_logprobs list rather than computing it, so scoring is not supported there. The old guard treated the truthy [None] list as valid and silently scored every candidate as 0. Detect the all-None case and return UNIMPLEMENTED instead. No-op on Linux/CUDA, which populate real entries. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
localai-bot
marked this pull request as ready for review
June 24, 2026 21:32
Resolve includeDarwin conflict in backend-matrix.yml: keep both the vllm and the newly-merged liquid-audio darwin entries (additive). Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
Resolve includeDarwin conflict: keep the vllm and merged trl darwin entries. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:opus-4.8 [Claude Code]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds macOS (Apple Silicon / Metal) support to the
vllmbackend via vllm-metal (MLX). On darwin, vLLM runs through vllm-metal's platform-plugin; LocalAI'sbackend.pygeneration path is unchanged. Linux/CUDA/ROCm/Intel paths are untouched (all darwin logic is additive, gated behinduname -s = Darwin).What changed
backend/python/vllm/install.sh(darwin branch): forcesPYTHON_VERSION=3.12, uses LocalAI's managed venv (ensureVenv), builds vLLM from the release source tarball, then installs thevllm-metalwheel;installRequirementsis skipped on darwin.VLLM_METAL_VERSION. The coupled vLLM source version is derived at build time from vllm-metal's own installer (vllm_v=) at the pinned tag (no second hardcoded value to drift; reproducible since the tag is immutable)..github/bump_vllm_metal.sh+bump_deps.yaml: a new autobumper job that tracks vllm-project/vllm-metal (not vllm/vllm latest — darwin can only use the vLLM vllm-metal supports) and opens a bump PR forVLLM_METAL_VERSION. Mirrors the existingbump_vllm_wheel.sh..github/backend-matrix.yml:vllmadded underincludeDarwin:.backend/index.yaml:metal:capability + concretemetal-vllm/metal-vllm-developmententries.backend.py(one defensive guard):Scorenow returnsUNIMPLEMENTEDwhen the engine yields no usableprompt_logprobs, instead of silently scoring every candidate as 0 (see audit). No-op on Linux/CUDA.Validated
metal -> vllm_metal:register,MetalPlatform, MLX GPU);AsyncLLMEngine.from_engine_argstransparently resolves to vLLM 0.23's v1 MLX engine; asyncgenerateproduced correct output onQwen/Qwen3-0.6B.VLLM_METAL_VERSIONto the latest release and left the derived line untouched.backend.py audit (Score / Embeddings vs the v1 MLX engine)
prompt_logprobs): not supported on Metal. Live test: vllm-metal acceptsSamplingParams(prompt_logprobs=1)but returns an all-Nonelist rather than computing it. The previous guard treated the truthy[None]as valid → silent all-zero scores. Now it returnsUNIMPLEMENTED. Generation/chat is unaffected.Embedding()callsself.model.encode(), butself.modelis never assigned anywhere inbackend.py(identical onmaster) — it raisesAttributeErroron every platform, CUDA included. This is not a darwin regression; the vllm backend is generation-only and embeddings are served by other backends. Out of scope here.Version coupling
vllm-metal builds against a specific vLLM release; darwin follows vllm-metal and can lag the Linux
vllmpin (requirements-cublas13-after.txt, bumped independently) until vllm-metal supports a newer vLLM. The singleVLLM_METAL_VERSIONpin + autobumper keep this tracked and reproducible.Assisted-by: Claude:opus-4.8 [Claude Code]