#!/bin/bash
# Pinned by full URL because no index serves it: PyPI's vllm has no macOS
# wheel yet. To bump, raise VLLM_VERSION to a release that attaches one —
# not every release does; v0.26.0 was the first.
VLLM_VERSION="0.27.0"
VLLM_WHEEL_URL="https://github.com/vllm-project/vllm/releases/download/v${VLLM_VERSION}/vllm-${VLLM_VERSION}%2Bcpu-cp312-cp312-macosx_11_0_arm64.whl"
_cleanup_dirs=()
register_cleanup_dir() {
_cleanup_dirs+=("$1")
}
cleanup_tmp_dirs() {
local dir
if [[ ${#_cleanup_dirs[@]} -eq 0 ]]; then
return
fi
for dir in "${_cleanup_dirs[@]}"; do
rm -rf "$dir"
done
}
fetch_latest_release() {
local repo_owner="$1"
local repo_name="$2"
echo "Fetching latest release..." >&2
local latest_release_url="https://api.github.com/repos/${repo_owner}/${repo_name}/releases/latest"
local release_data
if ! release_data=$(curl -fsSL "$latest_release_url" 2>&1); then
error "Failed to fetch release information."
echo "Please check your internet connection and try again." >&2
exit 1
fi
if [[ -z "$release_data" ]] || [[ "$release_data" == *"Not Found"* ]]; then
error "No releases found for this repository."
echo "Please visit https://github.com/${repo_owner}/${repo_name}/releases" >&2
exit 1
fi
echo "$release_data"
}
extract_wheel_url() {
local release_data="$1"
python3 -c "
import sys
import json
try:
data = json.loads('''$release_data''', strict=False)
assets = data.get('assets', [])
for asset in assets:
name = asset.get('name', '')
if name.endswith('.whl'):
print(asset.get('browser_download_url', ''))
break
except Exception as e:
print('', file=sys.stderr)
"
}
install_vllm() {
echo ""
section "Installing vLLM core"
echo "Wheel: vLLM ${VLLM_VERSION} (prebuilt macOS arm64)"
# The wheel's own metadata pulls torch et al. from PyPI.
if ! uv pip install "$VLLM_WHEEL_URL"; then
error "Failed to install vLLM core from ${VLLM_WHEEL_URL}"
echo "Please check your internet connection and try again." >&2
exit 1
fi
success "Installed vLLM core"
}
download_and_install_wheel() {
local wheel_url="$1"
local package_name="$2"
local wheel_name
wheel_name=$(basename "$wheel_url")
echo "Latest release: $wheel_name"
success "Found latest release"
local tmp_dir
tmp_dir=$(mktemp -d)
register_cleanup_dir "$tmp_dir"
echo ""
echo "Downloading wheel..."
local wheel_path="$tmp_dir/$wheel_name"
if ! curl -fsSL "$wheel_url" -o "$wheel_path"; then
error "Failed to download wheel."
exit 1
fi
success "Downloaded wheel"
# Install vllm-metal package
if ! uv pip install "$wheel_path"; then
error "Failed to install ${package_name}."
exit 1
fi
success "Installed ${package_name}"
}
main() {
set -eu -o pipefail
trap cleanup_tmp_dirs EXIT
local repo_owner="vllm-project"
local repo_name="vllm-metal"
local package_name="vllm-metal"
for arg in "$@"; do
case "$arg" in
-h|--help)
cat <<'EOF'
Usage: install.sh
Options:
-h, --help Show this help.
EOF
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
echo "Run with --help for usage." >&2
exit 1
;;
esac
done
# Source shared library functions
# Try local lib.sh first (when running ./install.sh), fall back to remote (when piped from curl)
local local_lib=""
if [[ -n "${BASH_SOURCE[0]:-}" ]]; then
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-}")" && pwd)"
local_lib="$script_dir/scripts/lib.sh"
fi
if [[ -n "$local_lib" && -f "$local_lib" ]]; then
# shellcheck source=/dev/null
source "$local_lib"
else
# Fetch from remote (curl | bash case)
local lib_url="https://raw.githubusercontent.com/$repo_owner/$repo_name/main/scripts/lib.sh"
local lib_tmp
lib_tmp=$(mktemp)
if ! curl -fsSL "$lib_url" -o "$lib_tmp"; then
echo "Error: Failed to fetch lib.sh from $lib_url" >&2
rm -f "$lib_tmp"
exit 1
fi
# shellcheck source=/dev/null
source "$lib_tmp"
rm -f "$lib_tmp"
fi
if ! is_apple_silicon; then
error "vllm-metal requires Apple Silicon arm64. Detected: $(uname -m)."
exit 1
fi
if ! ensure_uv; then
exit 1
fi
local venv="$HOME/.venv-vllm-metal"
if [[ -n "$local_lib" && -f "$local_lib" ]]; then
venv="$PWD/.venv-vllm-metal"
fi
ensure_venv "$venv"
if ! require_arm64_python python; then
exit 1
fi
install_vllm
if [[ -n "$local_lib" && -f "$local_lib" ]]; then
# Local source install (running ./install.sh from a checkout). Prebuild the
# native paged-attention artifacts from this tree — the _paged_ops .so and
# the precompiled .metallib shaders — so the kernels load with no runtime
# compile, exactly like a release wheel; otherwise get_ops() fails loud
# ("Prebuilt native extension not found") the first time paged attention is
# used. build_native_artifacts needs the build deps (mlx, nanobind)
# importable, so the editable install pulls them in first and points the
# install at this tree, where the artifacts land. The remote (curl | bash)
# branch below installs a prebuilt release wheel instead and needs no
# toolchain. Mirrors scripts/release.sh / scripts/test.sh.
uv pip install -e .
ensure_metal_toolchain
build_native_artifacts
else
local release_data
release_data=$(fetch_latest_release "$repo_owner" "$repo_name")
local wheel_url
wheel_url=$(extract_wheel_url "$release_data")
if [[ -z "$wheel_url" ]]; then
error "No wheel file found in the latest release."
exit 1
fi
download_and_install_wheel "$wheel_url" "$package_name"
fi
echo ""
success "Installation complete!"
echo ""
echo "To use vllm, activate the virtual environment:"
echo " source $venv/bin/activate"
echo ""
echo "Or add the venv to your PATH:"
echo " export PATH=\"$venv/bin:\$PATH\""
}
main "$@"