One API. Any GGUF Model. Seamless local LLM switching.
A lightweight OpenAI and Anthropic-compatible API server that loads, manages, and switches between GGUF models on a single GPU. Point any OpenAI or Anthropic SDK or tool at it (Python, Node, Cursor, Cline, Continue) — no manual process or port juggling.
A llama-swap alternative in Rust with system memory-pressure eviction, OOM-only context fallback, Swagger UI, Hugging Face metadata enrichment, and built-in usage tracking.
Requires llama.cpp llama-server and GGUF model files — on Linux NVIDIA hosts install with ./scripts/update-llama-cpp.sh (see Quick Start).
Status: Experimental — single-GPU home labs and development machines on a trusted LAN. One model loaded at a time. System RAM is monitored for pressure eviction;
vram_gbsizes context heuristically. Opt-inauto_nglcan pick GPU layers from free VRAM (nvidia-smi orvram_gbfallback) — still a heuristic, not live layer telemetry. See docs/COMPATIBILITY.md.
- Fast & Lightweight — Minimal overhead, maximum performance
- Hot-Swap Models — Switch between GGUF models on the fly
- Open & Extensible — Modular, easy to extend, community-driven
- 100% Local — Your models. Your data. Your machine.
Also included:
- OpenAI-compatible API —
/v1/chat/completions,/v1/completions,/v1/embeddings,/v1/responses,/v1/models,/v1/models/registry.json,/v1/audio/* - Anthropic Messages API —
POST /v1/messages(stream + non-stream); translated onto the loadedllama-serverOpenAI backend - Tool calling — Chat Completions forwards
tools/tool_choice/tool_calls; the Responses API translates function tools, function calls, and strict streaming events to/fromllama-server. Actual model behavior depends on the model and llama.cpp build (see COMPATIBILITY) - Swagger UI — Try-it-out at
http://localhost:9090/swagger-ui/(live model dropdown, Rescan Models, hides chat vs embedding endpoints by selected model kind) - Auto-discovery — Scans GGUF dirs with a cheap validation ladder (filename → header → metadata); sidecars skipped
- Live model rescan —
POST /v1/models/refreshplus a configurable daily watcher (models_rescan_interval_secs); merges new GGUFs without a full redeploy - HF metadata enrichment — fills empty
description/ context / VRAM /capabilities/hf_repofrom Hugging Face on launch and rescan (sync-hf-metadataCLI also available) - Model management —
models search,models files, andmodels pullfor one-command GGUF discovery, download, validation, and registry from Hugging Face - Kind-aware routing — chat / completions / messages / responses require chat-like kinds; embeddings require
embedding(and pass--embeddingstollama-server) - Single-slot hot-swap — One resident model; switches drain in-flight requests; failed switches roll back
- Memory-pressure eviction — Unloads when system RAM crosses the critical threshold
- Auto GPU layers (
auto_ngl) — Opt-in: at load, pick-nglfrom free VRAM + GGUF size (manualngl/extra_argsstill win) - Idle priority model — Preferred model auto-loads after a configurable idle timeout
- llama.cpp backend — Spawns and manages
llama-serverchild processes - SSE streaming, Prometheus (
/metrics), usage history (/v1/usage), portablemodels.json
Models (GGUF) API endpoints
Mistral / Llama / Phi / … → /v1/chat/completions
↓ /v1/completions
gguf-switchboard ─────────▶ /v1/embeddings
(single-slot swap) /v1/responses, /v1/audio/*
/v1/messages (Anthropic)
Request for model B while A is loaded → drain → unload A → load B → forward. After idle_timeout, the priority model warms back up. Details in Architecture.
When running local LLMs you usually juggle llama-server processes, ports, and GPU memory by hand. gguf-switchboard is a llama-swap-style swap proxy in Rust for constrained GPUs: memory-pressure eviction, OOM context fallback, idle priority model, HF-enriched registry metadata, and usage tracking — one OpenAI/Anthropic endpoint, llama.cpp only.
Full landscape table and vs llama-swap feature matrix: docs/COMPARISON.md.
gguf-switchboard is a swap proxy — it does not run inference itself. You need a working llama.cpp llama-server and GGUF models on disk before the systemd service will stay enabled.
| Requirement | Notes |
|---|---|
llama-server (required) |
From llama.cpp. On Linux NVIDIA hosts prefer ./scripts/update-llama-cpp.sh → /usr/local/bin/llama-server. Otherwise put it on PATH or set defaults.llama_server in models.toml. |
| GGUF model files | Directory of .gguf weights (system default: /var/lib/gguf-switchboard/models). |
| Linux (recommended) | Ubuntu/Debian for deploy.sh (apt). Other distros: install build deps yourself. |
| macOS | Build from source only — no systemd. See macOS. |
| Rust | Installed automatically by deploy.sh if missing; otherwise rustup. |
| GPU stack | NVIDIA + CUDA toolkit on Linux, or Apple Metal on macOS (CPU-only llama.cpp works but is slow). |
Clone the repo, install a CUDA llama-server into /usr/local, then deploy the switchboard service:
git clone --branch main https://github.com/pradeepgudipati/gguf-switchboard.git
cd gguf-switchboard
# 1) Build + install llama.cpp with CUDA (idempotent upgrade path)
# Run as your user; the script sudo's only for install / service steps.
./scripts/update-llama-cpp.sh
# 2) Build + install gguf-switchboard and enable the systemd unit
./deploy.sh./scripts/update-llama-cpp.sh does: check CUDA → clone/pull ~/llama.cpp → CUDA Release build → verify GPU → stop gguf-switchboard if present → cmake --install to /usr/local → strip stale RUNPATH → ldconfig → assert libs are not resolved from the source build tree → restart the unit only if it exists.
Overrides: LLAMA_DIR (default ~/llama.cpp), PREFIX (default /usr/local), SERVICE (default gguf-switchboard), SKIP_PULL=1, SKIP_SERVICE=1.
deploy.sh does not install llama.cpp. It requires /usr/local/bin/llama-server (--version must succeed). If missing or broken, deploy exits after installing the switchboard binary and prints ./scripts/update-llama-cpp.sh help. If no GGUF models are registered yet, the unit is installed but left stopped.
What deploy.sh does when ready:
- Pulls latest
main(stashes dirty working tree first — see Updating) - Creates system user
ggsand directories under/opt/gguf-switchboard+/var/lib/gguf-switchboard - Installs build deps + Rust if needed
- Builds the release binary →
/usr/local/bin/gguf-switchboard(root-owned) - Syncs the project into
/opt/gguf-switchboard(skips rsync if already running from there) - Writes
config.toml/ generatesmodels.tomlwith absolute system paths - Installs the systemd unit as
User=ggs/Group=ggs, thendaemon-reload+enable --now - Validates
ggscan read configs/models and execute both binaries; checks/health
# Optional: copy legacy ~/models into the system models dir (never deletes the source)
./deploy.sh --migrate-models
# Discover from an alternate directory while still registering the canonical models_dir
MODELS_DIR=/path/to/gguf-files ./deploy.sh --refresh-modelsThen open http://localhost:9090/swagger-ui/.
If the binary is not on PATH, point the registry at it:
# models.toml
[defaults]
llama_server = "/path/to/llama-server"Prefer ./scripts/update-llama-cpp.sh on CUDA hosts — it installs shared libs correctly and strips RUNPATH. Manual one-shot (binary copy only):
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j"$(nproc)"
sudo cp build/bin/llama-server /usr/local/bin/
llama-server --versionmacOS (Metal) — no systemd; build llama-server yourself, then use Build without systemd:
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_METAL=ON
cmake --build build --config Release -j"$(sysctl -n hw.ncpu)"
sudo cp build/bin/llama-server /usr/local/bin/
llama-server --versionStill needs a working llama-server first (./scripts/update-llama-cpp.sh on CUDA hosts).
# amd64 (see Releases for arm64 + checksums)
curl -fsSL -o gguf-switchboard \
https://github.com/pradeepgudipati/gguf-switchboard/releases/latest/download/gguf-switchboard-linux-amd64
chmod +x gguf-switchboard
sudo mv gguf-switchboard /usr/local/bin/
# Copy the tracked examples to system runtime paths (or run ./deploy.sh)
git clone --branch main --depth 1 https://github.com/pradeepgudipati/gguf-switchboard.git
cd gguf-switchboard
sudo mkdir -p /opt/gguf-switchboard /var/lib/gguf-switchboard/models
sudo cp config.example.toml /opt/gguf-switchboard/config.toml
gguf-switchboard discover-models /var/lib/gguf-switchboard/models -o /opt/gguf-switchboard/models.toml
gguf-switchboard /opt/gguf-switchboard/config.tomlLocal binary only — no sudo, no systemd (Linux or macOS):
git clone --branch main https://github.com/pradeepgudipati/gguf-switchboard.git
cd gguf-switchboard
cargo build --release
cp config.example.toml config.toml
cp models.example.toml models.toml
./target/release/gguf-switchboard discover-models ~/models -o models.toml
./target/release/gguf-switchboard config.tomldeploy.sh is Linux-only (systemd). On a Mac, use Build without systemd.
| Step | Linux (deploy.sh) |
macOS |
|---|---|---|
Clone + cargo build |
Yes | Yes |
| Model discovery | Yes | Yes |
| systemd auto-start | Yes | No — terminal or your own launchd plist |
| Auto-install build deps | Yes (apt) |
Xcode CLI tools; jq via Homebrew if needed |
Use a Metal build of llama-server. Create runtime files from config.example.toml / models.example.toml; keep user-owned config.toml / models.toml in the checkout.
With gguf-switchboard installed, search, browse, and download GGUF models from Hugging Face:
sudo mkdir -p /var/lib/gguf-switchboard/models
# Search for models
gguf-switchboard models search "Qwen3.5 9B"
# Browse available files in a repo
gguf-switchboard models files lmstudio-community/Qwen3.5-9B-GGUF
# Download, validate, and register a model (runs a quick speed test if the server is up)
gguf-switchboard models pull lmstudio-community/Qwen3.5-9B-GGUF --quant Q4_K_M --dir /var/lib/gguf-switchboard/models
# Optional: tune parallel aria2 connections (default 8, maximum 16)
gguf-switchboard models pull lmstudio-community/Qwen3.5-9B-GGUF --quant Q4_K_M --connections 8
# Skip the post-pull speed test
gguf-switchboard models pull lmstudio-community/Qwen3.5-9B-GGUF --quant Q4_K_M --no-benchPublic downloads automatically use aria2c when available, then verify the expected size, Hugging Face LFS checksum, and GGUF metadata before registration. If Hugging Face rejects parallel range requests, the native downloader resumes the partial file. Authenticated downloads using HF_TOKEN, or systems without aria2c, use the native downloader directly. A successful pull refreshes a running gguf-switchboard server automatically and (unless --no-bench) runs a short chat completion to print prompt and generation tok/s.
Or download manually — any .gguf file in /var/lib/gguf-switchboard/models works:
# Example layout after manual download:
# /var/lib/gguf-switchboard/models/Qwen3.5-9B-Q4_K_M.gguf
# /var/lib/gguf-switchboard/models/gemma-4-E4B-it-Q4_K_M.ggufIf you downloaded models manually, run ./deploy.sh --refresh-models so discovery registers them.
curl -s http://localhost:9090/health
curl -s http://localhost:9090/status | jq .
curl -s http://localhost:9090/v1/models | jq '.data[].id'Supported upgrade path from an existing checkout:
cd ~/gguf-switchboard # or wherever you cloned
# Refresh CUDA llama-server first when the backend changed upstream
./scripts/update-llama-cpp.sh
# Then rebuild / reinstall the switchboard service
./deploy.sh| Goal | Command |
|---|---|
| Pull + rebuild + restart switchboard | ./deploy.sh |
Rebuild only (no git pull) |
./deploy.sh --skip-pull |
Install / refresh CUDA llama-server in /usr/local |
./scripts/update-llama-cpp.sh |
| Pick up new GGUF files (merge registry) | ./deploy.sh --refresh-models |
Copy legacy ~/models into system models dir |
./deploy.sh --migrate-models |
| Live rescan while running | curl -X POST http://localhost:9090/v1/models/refresh (or Swagger Rescan Models) |
| Restart without rebuild | sudo systemctl restart gguf-switchboard |
Important:
- Deploy stashes uncommitted changes (including untracked files) before
git pull. Recover withgit stash list/git stash pop. - Live config lives under
/opt/gguf-switchboard/(config.toml,models.toml); models andusage.dbunder/var/lib/gguf-switchboard/. Tracked defaults live inconfig.example.tomlandmodels.example.toml. - After editing aliases /
priority/extra_args, restart:sudo systemctl restart gguf-switchboard. deploy.shwill not start the unit if/usr/local/bin/llama-serveris missing/broken or no GGUF models are registered — fix with./scripts/update-llama-cpp.shand/or model pull, then re-run deploy.
# Logs
sudo systemctl status gguf-switchboard
sudo journalctl -u gguf-switchboard -fAdd a short ggs alias so you can type ggs instead of gguf-switchboard. deploy.sh offers to add this automatically on Linux without conflicting with the common gs='git status' alias.
Linux (bash):
echo "alias ggs='gguf-switchboard'" >> ~/.bashrc
source ~/.bashrcLinux / macOS (zsh):
echo "alias ggs='gguf-switchboard'" >> ~/.zshrc
source ~/.zshrcWindows (PowerShell):
# Add to your PowerShell profile
if (!(Test-Path $PROFILE)) { New-Item -Path $PROFILE -Force }
Add-Content $PROFILE "Set-Alias -Name ggs -Value gguf-switchboard"
. $PROFILEAfter that:
ggs models search "Qwen3.5"
ggs models pull lmstudio-community/Qwen3.5-9B-GGUF --quant Q4_K_M
ggs config.toml| Symptom | Likely fix |
|---|---|
Deploy exits; prints ./scripts/update-llama-cpp.sh |
Install or refresh CUDA llama-server into /usr/local/bin: ./scripts/update-llama-cpp.sh, then ./deploy.sh |
llama-server: not found / models fail to load |
Same as above |
| Service unhealthy / no models | Put GGUFs in /var/lib/gguf-switchboard/models and run ./deploy.sh --refresh-models |
| First install has no GGUF models | Not fatal. Installer leaves the service stopped and prints ggs models search, ggs models pull, and ./deploy.sh --refresh-models |
Empty /v1/models |
Check models_dir in /opt/gguf-switchboard/models.toml; enable auto_discover = true; restart |
| Deploy "lost" my edits | git stash list — deploy stashes dirty trees before pull |
| Port 9090 in use | Change bind in /opt/gguf-switchboard/config.toml and restart |
| Doc | Contents |
|---|---|
| docs/CONFIGURATION.md | config.toml, models.toml, discovery, context sizing, CLI |
| docs/USAGE.md | API examples, SDKs, IDE setup, monitoring, local run |
| docs/ARCHITECTURE.md | Scheduler/backend overview and project layout |
| docs/COMPARISON.md | Landscape vs Ollama / llama-swap / others |
| docs/BENCHMARKS.md | Throughput, swap latency, bench script |
| docs/COMPATIBILITY.md | OpenAI endpoint coverage |
Two runtime files under /opt/gguf-switchboard/: config.toml (bind, idle timeout, vram_gb) and models.toml (aliases → GGUF paths). Models live in /var/lib/gguf-switchboard/models/. Full reference: docs/CONFIGURATION.md.
# After install, tweak models then restart
sudo systemctl restart gguf-switchboardcurl http://localhost:9090/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"YOUR_ALIAS","messages":[{"role":"user","content":"Hello"}],"max_tokens":64}'Swagger UI: http://localhost:9090/swagger-ui/ — more examples in docs/USAGE.md.
MIT

