Repository navigation
Support running CLM on Apple Silicon (MPS device + llama.cpp embedder) - #18
Open
anirbanbasu wants to merge 4 commits into
Open
anirbanbasu wants to merge 4 commits into
anirbanbasu wants to merge 4 commits into
Conversation
…path default_device() now tries MPS after CUDA before falling back to CPU, and vllm is gated to sys_platform == 'linux' in pyproject.toml so it no longer blocks pip install on macOS (vLLM ships no macOS wheel). Documents running the embedder with llama.cpp's llama-server as a drop-in replacement for vLLM's OpenAI-compatible /v1/embeddings endpoint. Fixes Contrastive-LM#17 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Anirban Basu <anirbanbasu@users.noreply.github.com>
Author
|
Note that |
…th too Mirrors the vLLM instructions with -c 8192 on llama-server and clm-serve --max-tokens 8192. Signed-off-by: Anirban Basu <anirbanbasu@users.noreply.github.com>
…ript train/finetune.py and evaluation/bon_eval.py hardcoded cuda-or-cpu device selection, so training/eval silently ran on CPU on Apple Silicon even though torch sees MPS; both now reuse clm.heads.default_device() via a shared _device(gpu) helper, same as clm-serve. serve_qwen3_8b.sh was vLLM-only. It now picks an ENGINE (vllm or llama.cpp), auto-detected from the OS (Darwin -> llama.cpp) and overridable, so the same script launches llama-server on Apple Silicon with the flags documented in the README. Signed-off-by: Anirban Basu <anirbanbasu@users.noreply.github.com>
VectorArena only asked the driver for free/total device memory on CUDA; on MPS it silently fell back to a hardcoded 8 GiB guess, making the --action-cache fraction budget meaningless on Apple Silicon machines with much less (8 GiB) or much more (36-128 GiB) unified memory. Use torch.mps.recommended_max_memory()/driver_allocated_memory() (torch 2.3+) when available, keeping the old guess only as a last resort. Signed-off-by: Anirban Basu <anirbanbasu@users.noreply.github.com>
Open
This branch has not been deployed
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.
Fixes #17 and #16.
Summary
default_device()(src/clm/heads.py) now tries MPS after CUDA before falling back to CPU, soclm-serveruns the projection heads on Apple Silicon's GPU without any flags.--device/CLM_DEVICEstill override it.vllmis now gated tosys_platform == 'linux'inpyproject.toml, since vLLM has never published a macOS wheel — this was silently breakingpip install -e ./pip install contrastive-lmon macOS (see installation on mac #16), with pip backtracking through vllm sdists that need a Linux/CUDA build toolchain.llama.cpp'sllama-server --embedding --pooling lastas a drop-in replacement forvllm serveon Apple Silicon —Embedder(src/clm/embedder.py) already speaks plain OpenAI-compatible/v1/embeddings, so no code changes were needed there.train/finetune.pyandevaluation/bon_eval.pyhardcodedcuda-or-cpudevice selection, so fine-tuning and best-of-N evaluation silently ran on CPU on Apple Silicon even though torch sees MPS. Both now resolve their--gpuflag through a small_device(gpu)helper that falls back todefault_device()(so MPS is picked automatically) instead ofcpudirectly.serve_qwen3_8b.shwas vLLM-only. It now takes anENGINEvariable (vllmorllama.cpp), auto-detected from the OS (Darwin→llama.cpp) and overridable, so the same script launchesllama-serverwith the flags above on Apple Silicon instead ofvllm serve.VectorArena(src/clm/cache.py), which backsclm-serve's--action-cache, only queried real device memory for its budget on CUDA (torch.cuda.mem_get_info); onmpsit silently assumed a fixed 8 GiB total/free, making the0.02-style fraction budget meaningless on Apple Silicon machines with much less (8 GiB) or much more (36-128 GiB) unified memory. It now readstorch.mps.recommended_max_memory()/driver_allocated_memory()(torch 2.3+) when onmps, falling back to the old fixed guess only if that API isn't available.Test plan
Verified end-to-end on Apple Silicon (M-series, 64GB unified memory):
llama-server -hf unsloth/Qwen3-8B-GGUF:BF16 --embedding --pooling last -ngl 1024 -fa on -c 2048 --port 8090serves the embedder correctly (checkedusage.prompt_tokensfor single and batched inputs).pip install -e .succeeds in a clean venv on macOS with novllminstalled.clm-servelogsmodels [...] on mpsand reserves the vector cache onmps, with no--deviceflag passed.POST /v1/systemoneandEngine.rank(...)return correct, sensible answers (e.g. ranks "The Moon's gravitational pull." highest for "What causes tides on Earth?").GET /healthreportsembedder: trueand devicemps.serve_qwen3_8b.shresolvesENGINE=llama.cppautomatically on Darwin andENGINE=vllmwhen explicitly overridden.VectorArena('mps', budget=0.02)reserves memory scaled to this machine's actual unified memory viatorch.mps.recommended_max_memory()(not the old fixed 8 GiB guess), andarena.get(...)correctly fills, hits and returnsmps-device tensors.Related PRs
Three other Apple Silicon efforts are already open/closed against this repo; this PR takes a smaller, dependency-light approach:
Embedder(already generic OpenAI-compatible HTTP) atllama-server, an existing external binary, and uses an off-the-shelf GGUF instead of converting anything.clm-mps-embedcommand that loads Qwen3-8B safetensors in-process via PyTorch/Transformers on MPS (src/clm/mps_embed.py, a newmacosextra, plus changes tocache.py,engine.py,server.py,evaluation/bon_eval.py, and the playground JS). This PR only touchesdefault_device()inheads.py(and now the same pattern infinetune.py/bon_eval.py), plus a small MPS memory-query fix incache.py's existingVectorArena— the encoder stays a separatellama-serverprocess, so there's no new in-repo embedding implementation to maintain.mlxdevice backend: MLX-based heads (mlx_heads.py), an MLX embedding server (mlx_embed.py), and unified-memory preflight logic (memory.py) — a new backend alongsidecpu/cuda. This PR doesn't introduce a new device backend; it only extends PyTorch's existingmpssupport indefault_device(), so the heads stay on stock PyTorch.vllmbehind asys_platformmarker inpyproject.tomlso it doesn't blockpip installon macOS — same idea, arrived at independently.Net effect: no new source files, no new dependencies, and no new device string — just teaching
default_device()(and the two training/eval scripts that duplicated its logic) about MPS, and treatingllama-serveras an already-installed external tool that speaks the API CLM already expects. Trade-off: unlike #7/#9, there's no bundled one-command setup script for fine-tuning from scratch on Apple Silicon;serve_qwen3_8b.shcovers the encoder, buttrain/finetune.py's from-scratch embedding path still needs a reachable--embed-url(llama-server) since there is no in-process llama.cpp Python backend equivalent to vLLM's offlineLLM(...).