llama.cpp Studio is a local control plane for downloading, configuring, and serving LLMs from a single machine.
The project combines:
- a Vue 3 frontend
- a FastAPI backend
- YAML-backed state under
data/ - a unified
llama-swapOpenAI-compatible endpoint on port2000
Today, the app manages five runtime families:
llama.cppfor GGUF modelsik_llama.cppfor GGUF modelsLMDeployfor safetensors models1Cat-vLLMfor vLLM-backed modelsaudio.cppfor prepared audio model bundles (TTS, ASR, VAD, and related tasks)
This README has been rebuilt to match the current repository layout and runtime behavior.
- Search Hugging Face and the audio.cpp package catalog for compatible models
- Download and refresh GGUF quantizations, optional
mmproj/ MTP / DFlash companion files, safetensors bundles, and prepared audio.cpp packages - Store model and engine state in YAML instead of SQLite
- Build
llama.cpp,ik_llama.cpp, andaudio.cppfrom source and manage multiple installed versions - Install LMDeploy and 1Cat-vLLM from releases or source into dedicated virtual environments
- Install CUDA Toolkit versions into the persistent app data directory
- Configure models per engine using a parameter catalog parsed from the active runtime binary
- Serve models through one OpenAI-compatible endpoint exposed by
llama-swap - Stream progress and notifications over Server-Sent Events
| Purpose | Docker / container | Local dev |
|---|---|---|
| Web UI + FastAPI API | http://localhost:8080 |
frontend: http://localhost:5173, backend API: http://localhost:8081 |
| OpenAI-compatible model endpoint | http://localhost:2000 |
http://localhost:2000 |
| OpenAPI docs | http://localhost:8080/docs |
http://localhost:8081/docs |
| Raw schema | http://localhost:8080/openapi.json |
http://localhost:8081/openapi.json |
In local dev, Vite proxies /api from 5173 to 127.0.0.1:8081.
Browser UI (Vue 3)
-> FastAPI backend
-> YAML config in data/config/
-> Hugging Face downloads in data/models/ and data/hf-cache/
-> engine installs in data/llama-cpp/, data/lmdeploy/, data/1cat-vllm/, and data/audio-cpp/
-> CUDA installs in data/cuda/
-> llama-swap config in data/llama-swap-config.yaml
-> llama-swap on :2000
-> llama.cpp / ik_llama.cpp / LMDeploy / 1Cat-vLLM / audio.cpp runtimes
The backend starts llama-swap automatically when there is at least one active runtime binary available from any registered engine (including audio-only installs).
- Start the app.
- Open
Engines. - Build and activate a
llama.cpporik_llama.cppversion for GGUF models. - If you want safetensors support, install and activate LMDeploy or 1Cat-vLLM.
- If you want audio tasks (TTS, ASR, VAD, and related), build and activate
audio.cppfrom source. - If you need gated Hugging Face access, set
HUGGINGFACE_API_KEYor enter a token in the UI. - Open
Search, find a model, and download or install it. - Open
Models, configure the model, and choose its engine. - If the UI says the
llama-swapconfig is stale, apply the pending config. - Start the model from the library.
- Call it through
http://localhost:2000/v1/....
Important:
- Saving model config updates the YAML store immediately.
- Applying pending
llama-swapconfig rewritesdata/llama-swap-config.yamland unloads models before regenerating proxy state. - GGUF models require an active
llama.cpporik_llama.cppbuild. - safetensors models require an active LMDeploy or 1Cat-vLLM install.
- audio.cpp models require a prepared bundle installed or imported locally, plus an active
audio.cppbuild.
- Docker
- Docker Compose
- For NVIDIA GPU use: NVIDIA drivers on the host plus the NVIDIA Container Toolkit
git clone <repo-url>
cd llama-cpp-studio
docker compose -f docker-compose.cpu.yml up --buildThis mode:
- exposes
8080for the UI/API - exposes
2000forllama-swap - mounts
./datato/app/data - mounts
./backendto/app/backend - enables backend reload with
RELOAD=true
This is the best Docker option for backend-focused development. The frontend is still the built bundle from the image, not a live Vite dev server.
docker compose -f docker-compose.cuda.yml up --build -dThis mode:
- exposes the same ports:
8080and2000 - mounts
./datato/app/data - reserves NVIDIA GPUs for the container
- disables backend reload
docker build -t llama-cpp-studio .
docker run -d \
--name llama-cpp-studio \
-p 8080:8080 \
-p 2000:2000 \
-v "$(pwd)/data:/app/data" \
llama-cpp-studioFor NVIDIA GPUs, add --gpus all.
Open:
- UI:
http://localhost:8080 - OpenAPI docs:
http://localhost:8080/docs - model endpoint:
http://localhost:2000/v1/models
- Node.js 20+
- Python 3
- a virtual environment tool such as
venv - a
pythonexecutable onPATHif you want to use the providednpmscripts as-is
If you want to build runtimes on the host instead of inside Docker, you will also need native build tooling such as:
cmakebuild-essentialgitpkg-configlibopenblas-devfor OpenBLAS-backed CPU builds
The repository scripts use python, so if your system only provides python3 you should either add a python alias or translate the commands below accordingly.
npm install
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txtnpm run dev:allThat starts:
- frontend Vite dev server on
5173 - backend on
8081
You can also run them separately:
npm run dev:frontend
npm run dev:backendBuild the frontend, then let FastAPI serve the built assets:
npm run build
python backend/main.pyWhen running outside Docker, the app stores persistent state under ./data.
npm run testOr run suites individually:
npm run test:frontend
python -m pytest backend/tests -qThe app is built around a persistent writable data directory. In Docker that is /app/data. Outside Docker it is ./data.
Typical layout:
data/
config/
models.yaml
engines.yaml
settings.yaml
engine_params_catalog.yaml
audio-cpp/
servers/
models/
gguf/
safetensors/
audio-cpp/
hf-cache/
llama-cpp/
lmdeploy/
1cat-vllm/
audio-cpp/
builds/
tools/
cuda/
logs/
temp/
llama-swap-config.yaml
What these are used for:
config/models.yaml: downloaded models, their file ledgers, and per-model configurationconfig/engines.yaml: installed engine versions, active versions, and build settingsconfig/settings.yaml: app settings such as Hugging Face token and proxy portconfig/llama_swap_routing.yaml: Studio-managed llama-swapprofilesandselectorsconfig/engine_params_catalog.yaml: parsed CLI parameter catalog used by the model config UIconfig/audio-cpp/servers/: generated one-modelaudiocpp_serverJSON sidecars forllama-swapmodels/: downloaded GGUF, safetensors, and prepared audio.cpp bundlesmodels/audio-cpp/: versioned prepared audio model packageshf-cache/: Hugging Face cachellama-cpp/: source checkouts and build artifacts forllama.cppandik_llama.cpplmdeploy/: LMDeploy virtual environments and source installs1cat-vllm/: 1Cat-vLLM virtual environments and source installsaudio-cpp/builds/: source checkouts and build artifacts foraudio.cppaudio-cpp/tools/: isolated Python virtual environment for the upstream model managercuda/: CUDA Toolkit installs managed by the applogs/: installer and background task logsllama-swap-config.yaml: generated proxy configuration
GGUF models are managed as quantized entries grouped by Hugging Face repo. They run through either:
llama.cppik_llama.cpp
Current engine management behavior:
- multiple versions can be installed and retained
- versions can be activated or deleted
- updates build the latest source ref with the saved build settings
- custom source repositories are labeled as forks while retaining source-sync behavior
- parameter support is discovered by scanning the active binary's
--helpoutput
For Hugging Face-backed GGUF models, the model config page can attach or replace
mmproj, MTP, and DFlash companions without repeating the weight download. MTP and
DFlash drafts are mutually exclusive. The Search and Model Config pages can also check
the recorded weights and selected companions against Hugging Face and redownload only
files whose remote metadata changed.
safetensors repos are managed as logical model bundles and run through LMDeploy or 1Cat-vLLM.
Current Python-engine flows:
- install the latest or a specific LMDeploy version from PyPI
- install the latest or a specific 1Cat-vLLM release
- install either engine from a source repository and branch
- save default release/PyPI versions and source repo/branch settings without installing
- keep multiple installs in the engine registry
- label custom source repositories as forks while keeping them syncable
- activate or remove installs from the UI
CUDA is managed as a persistent install under data/cuda.
The Docker image is prepared to use:
CUDA_HOMECUDA_PATHLD_LIBRARY_PATH- NCCL-related include and library paths
The app can install multiple CUDA versions and keeps a current symlink for the active one.
audio.cpp is a native engine for prepared audio model bundles. It is gated by AUDIO_CPP_ENABLED (enabled by default). Maturity is per surface, not a single blanket label:
| Surface | Maturity |
|---|---|
Speech (/v1/audio/speech) and ASR (/v1/audio/transcriptions) via llama-swap |
Primary Studio path; treat as stable once smoke-tested against your pin |
Generic tasks via /upstream/{model}/v1/tasks/run |
Supported but second-class until llama-swap routes /v1/tasks/run |
Catalog discovery from upstream JSON (--list-loaders --json, package family / standalone, --inspect --json) |
Stable on modern audio.cpp tips that advertise those contracts |
| Heuristic discovery fallback (fuzzy package→family matching) | Experimental; logged via discovery_source; controlled by AUDIO_CPP_HEURISTIC_DISCOVERY (default on) |
Supported tasks include TTS, ASR, VAD, diarization, separation, generation, voice conversion, speech-to-speech, and alignment. Build backends include CPU, CUDA, and Vulkan on Linux; Metal is exposed only when the host supports it.
Current audio.cpp flows:
- build/update from a user-chosen tracking ref (branch/tag) persisted in engine settings; Update syncs in place when possible
- on activate/sync, rescan
--help/--list-loaders(JSON preferred) and rediscover packages from the active checkout (no Studio package YAML mirror); persist acapability_deltafor operator review - after contract drift, review affected model configs
- discover verified-compatible packages through the normalized model catalog (
/api/model-catalog/search), preferring authoritative package JSON fields over fuzzy matching - install packages via
tools/model_manager_v2.pywhen the active audio.cpp checkout has it (spec-backed GGUF downloads frommodel_specs/*.json); fall back to Studio HF snapshot download or the legacymodel_manager.py/model_manager_deprecated.pyfor composite/converter packages - install direct HF snapshots, composite/converter packages via the upstream model manager, or import a local prepared directory
- configure family, task, mode, backend, device, load options, and session options from scanned model profiles (generic request UI for unknown families); instructions policy prefers upstream inspect JSON
- run one
audiocpp_serverprocess per Studio model behindllama-swapusing a generated JSON sidecar
Model manager behavior:
- Prefer
tools/model_manager_v2.pyfor catalog listing and ready snapshot installs (no Torch required) - Keep
tools/model_manager_deprecated.py(or oldertools/model_manager.py) for assemble/convert packages not supported by specs - Python/Torch are required only by the legacy model manager helper path
- Studio creates an isolated helper venv under
data/audio-cpp/tools/on first use of legacy assemble/convert installs - installs are validated with
audiocpp_cli --inspectbefore promotion intodata/models/audio-cpp/
Pinned upstream versions:
| Component | Pin |
|---|---|
| audio.cpp repository | https://github.com/0xShug0/audio.cpp.git |
| Tracking ref | User-configurable (bootstraps from GitHub latest release / default branch) |
| llama-swap | v249 |
Studio proxies OpenAI audio under /v1/audio on :8080 (with WAV conversion for ASR). llama-swap v249 still does not route /v1/tasks/run; generic non-OpenAI audio tasks use the direct upstream fallback at /upstream/{model}/v1/tasks/run until that lands. When it does, Studio will bump the LLAMA_SWAP_VERSION pin and point examples at /v1/tasks/run through the proxy.
Studio generates llama-swap selectors (virtual model IDs) and profiles (runtime pin maps) into llama-swap-config.yaml. Edit them under Engines → Virtual models & profiles (deep link: /engines#ev-section-routing). Per-model API aliases remain on the model config page and are valid selector/profile targets.
| Concept | Purpose |
|---|---|
Selector (warm) |
Prefer a target that is already loaded; otherwise start the first available |
Selector (pin) |
Always route to the first target |
Selector (spillover) |
Fill each target up to the spillover limit, then overflow to the next |
| Profile | Named map of client model ids → model / alias / selector (or empty to disable) |
Workflow:
- Edit selectors/profiles in the Engines panel and Save (writes
config/llama_swap_routing.yaml, marks config stale). - Apply llama-swap config (panel button or header notice). This regenerates
llama-swap-config.yamland reloads the proxy; all loaded models stop. - Optionally switch the active profile live (panel dropdown or API) — only profiles already present in the applied YAML can be activated.
Example: coding selector with warm routing + a day/night profile:
curl -s -X PUT http://localhost:8080/api/llama-swap/routing \
-H 'Content-Type: application/json' \
-d '{
"selectors": {
"coding-model": {
"strategy": "warm",
"targets": ["my-fast-alias", "my-heavy-alias"],
"name": "Coding"
}
},
"profiles": {
"day": { "description": "Daytime pins", "pins": { "llm": "coding-model" } },
"night": { "description": "Night pins", "pins": { "llm": "my-fast-alias" } }
}
}'
curl -s -X POST http://localhost:8080/api/llama-swap/apply-config
curl -s -X PUT http://localhost:8080/api/llama-swap/profiles/active \
-H 'Content-Type: application/json' \
-d '{"name":"day"}'Each model stores configuration per engine. In practice that means:
- switching a model from
llama.cpptoik_llama.cpporLMDeploydoes not destroy the other engine sections - the UI shows parameters based on the scanned catalog for the active engine
- unsupported flags can be hidden in the config view
- raw custom CLI args can be appended
- the saved
llama-swapcommand can be previewed from the UI and API - Hugging Face-backed GGUF and safetensors models keep a file ledger containing each recorded weight, shard, or selected companion plus available size and remote identity metadata
- checking for updates compares that ledger with Hugging Face and starts a background download only for changed files; files removed upstream are reported but not silently detached from the local model
Useful model-related routes:
GET /api/modelsPOST /api/models/searchPOST /api/models/downloadGET /api/models/{id}/companionsPOST /api/models/{id}/refreshPOST /api/models/{id}/projectorPOST /api/models/{id}/mtpPOST /api/models/{id}/dflashGET /api/model-catalog/searchPOST /api/model-catalog/installPOST /api/model-catalog/importGET /api/models/{id}/configPUT /api/models/{id}/configGET /api/models/{id}/saved-llama-swap-cmdPOST /api/models/{id}/preview-llama-swap-cmdPOST /api/models/{id}/startPOST /api/models/{id}/stop
The user-facing inference endpoint is the llama-swap proxy on port 2000.
Useful requests:
curl http://localhost:2000/v1/modelscurl http://localhost:2000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "replace-with-a-model-id-from-v1-models",
"messages": [
{"role": "user", "content": "Say hello in one sentence."}
]
}'The model value should come from GET /v1/models. It may be a sanitized Hugging Face repo plus quantization or a custom alias if you set one in model config.
Studio’s Audio page (/audio) is a first-class workspace for audio.cpp models—same navigation weight as Models / Search / Engines. It reuses the model library, saved Model Config (family/task/defaults/voice presets), and reference-audio Assets. Deep links:
- Models row → Audio (audio.cpp packages)
- Model Config → Open Audio
- Query params:
/audio?model=<id>&tab=speech|transcribe|…
Speech and Transcribe call Studio’s OpenAI-compatible proxy (below). Other task panels use llama-swap’s upstream /v1/tasks/run fallback when needed.
llama-swap still serves OpenAI audio routes on port 2000. Studio also mounts the same paths under http://localhost:8080/v1/audio so clients can stay on the Studio origin. On POST /v1/audio/transcriptions, Studio converts non-WAV uploads (OGG/Opus, MP3, WebM, …) to WAV via ffmpeg, then forwards to llama-swap. Speech and other /v1/audio/* routes are passed through unchanged. Generic task calls use POST /v1/audio/tasks/run (Studio → llama-swap /upstream/{model}/v1/tasks/run) so the browser never needs cross-origin access to :2000. Dev (vite on :5173) proxies /api and /v1 to the backend.
Prefer Studio for ASR clients (including Hermes voice messaging):
# Hermes / OpenAI-compatible STT base URL
STT_OPENAI_BASE_URL=http://localhost:8080/v1curl http://localhost:8080/v1/audio/speech \
-H "Content-Type: application/json" \
-d '{
"model": "replace-with-an-audio-model-id",
"input": "Hello from audio.cpp",
"voice": "default"
}' \
--output speech.wav# Non-WAV (e.g. OGG) is converted before audio.cpp sees it
curl http://localhost:8080/v1/audio/transcriptions \
-H "Authorization: Bearer local" \
-F model="replace-with-an-asr-model-id" \
-F file="@memo.ogg"Direct llama-swap (WAV or already-compatible files):
curl http://localhost:2000/v1/audio/voices \
-H "Authorization: Bearer local"curl http://localhost:2000/v1/audio/transcriptions \
-H "Authorization: Bearer local" \
-F model="replace-with-an-asr-model-id" \
-F file="@sample.wav"For generic audio tasks exposed by /v1/tasks/run, use the direct upstream path until llama-swap adds unified routing:
curl http://localhost:2000/upstream/replace-with-model-id/v1/tasks/run \
-H "Content-Type: application/json" \
-d '{"task":"vad","input":{"audio_path":"/path/in/container.wav"}}'The FastAPI app exposes a small number of main route groups:
/api/models: model library, downloads, config, start/stop, metadata/api/model-catalog: normalized search, install, import, and task status for verified packages/api/engines: engine capability descriptors used by the UI/api/llama-versions: engine versions, build settings, source builds, CUDA actions/api/audio-cpp: audio.cpp build, activation, status, and update checks/api/lmdeploy: LMDeploy install/remove/status/update checks and saved install defaults/api/1cat-vllm: 1Cat-vLLM install/remove/status/update checks and saved install defaults/api/status: system status and proxy health/api/gpu-info: GPU and CPU capability information/api/events: Server-Sent Events for progress and notifications/api/llama-swap: stale/apply/pending proxy configuration endpoints/api/llama-swap/routing: GET/PUT Studio-managed profiles and selectors/api/llama-swap/profiles: live profile listing;PUT .../activeswitches the active profile on the running proxy/v1/audio: OpenAI-compatible speech / transcriptions / voices proxy to llama-swap (ASR multipart may be ffmpeg-converted to WAV)
OpenAPI docs are available at /docs.
Most users only need a few environment variables:
| Variable | Purpose |
|---|---|
HUGGINGFACE_API_KEY |
Access gated Hugging Face models and authenticated downloads |
HF_HUB_ENABLE_HF_TRANSFER=1 |
Enable faster Hugging Face transfer support when available |
HF_HOME |
Base Hugging Face cache directory |
HUGGINGFACE_HUB_CACHE |
Hugging Face hub cache directory |
CUDA_VISIBLE_DEVICES |
Limit visible GPUs to comma-separated indices/UUIDs (e.g. 0,1). Do not set all — leave unset for all devices |
RELOAD |
Enable or disable backend auto-reload |
BACKEND_CORS_ORIGINS |
Comma-separated allowed origins |
BACKEND_CORS_ALLOW_CREDENTIALS |
Toggle credentialed CORS requests |
CPU_ONLY_MODE |
Force GPU detection into CPU-only mode |
AUDIO_CPP_ENABLED |
Enable or disable the audio.cpp integration (1 default, set 0/false to disable) |
AUDIO_CPP_HEURISTIC_DISCOVERY |
Allow fuzzy package→family discovery when upstream JSON omits fields (1 default; set 0 to require package JSON contracts) |
Advanced / less common:
| Variable | Purpose |
|---|---|
LMDEPLOY_BIN |
Override the LMDeploy executable path used by the backend |
CMAKE or CMAKE_EXECUTABLE |
Override the CMake executable used for source builds |
The app needs write access to the mounted data directory. If the container logs complain about /app/data permissions, fix ownership on the host volume before continuing.
Check these in order:
- an engine version is installed and active
- the model was downloaded successfully
- pending
llama-swapchanges were applied http://localhost:2000/healthis reachable
Saving model config updates the database state, but the generated proxy config may still be stale. Use the UI's apply flow or call:
curl -X POST http://localhost:8080/api/llama-swap/apply-configUse the Engines page action to rescan CLI parameters for the active engine. The backend builds the parameter registry from the runtime binary's --help output.