# Installing sd-cli (stable-diffusion.cpp)
The [`sdcpp`](../engines/sdcpp.md) engine subprocess-invokes
`sd-cli` per image job.
## You usually don't need this page
The worker **auto-provisions** `sd-cli` on the first image job: if no
binary is resolvable it downloads the platform's prebuilt
stable-diffusion.cpp **Vulkan** build (~37 MB, universal across
NVIDIA / AMD / Intel) into `<models_root>/bin/` and runs it from
there. A fresh worker - Linux, macOS arm64, or Windows - serves real
image jobs out of the box with no manual step. See
[auto-provisioning](../engines/sdcpp.md#auto-provisioning).
Every release target auto-provisions out of the box — Windows x64,
Linux x64, macOS arm64 and **Intel** (the upstream Darwin binary is
universal2), and Linux **arm64** (we build + host that one ourselves;
see [auto-provisioning](../engines/sdcpp.md#auto-provisioning)).
Use this playbook only when you want to **override** the auto-
provisioned binary: a CUDA build for maximum NVIDIA throughput, a
from-source build, an air-gapped mirror, or a target with no prebuilt
at all (e.g. Windows arm64).
The binary is not bundled into our release artefacts on purpose:
sd-cpp's pre-built matrix (CUDA / Vulkan / ROCm / Metal / CPU)
already covers the platforms we care about, bundling would either
ship a 30+ MB binary in every release or fragment the matrix, and the
worker fetches the right one on demand anyway.
## What to install
Upstream releases live at
<https://github.com/leejet/stable-diffusion.cpp/releases/latest>.
There is **no Linux CUDA pre-build** (only Vulkan / ROCm / CPU /
Windows-CUDA). On NVIDIA the Vulkan build works perfectly — we
verified Z-Image-Turbo at 1024×1024 / 8 steps in ~5-9 s on an
RTX 4090. If you need every last drop of perf you can compile sd.cpp
from source against CUDA, but Vulkan is the unattended default.
| Linux x86_64 + NVIDIA / AMD / Intel | `sd-master-<sha>-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip` |
| Linux x86_64 + AMD ROCm 7.x | `sd-master-<sha>-bin-Linux-Ubuntu-24.04-x86_64-rocm-7.13.0.zip` |
| Linux x86_64 + CPU only | `sd-master-<sha>-bin-Linux-Ubuntu-24.04-x86_64.zip` |
| macOS arm64 | `sd-master-<sha>-bin-Darwin-macOS-26.6.2-arm64.zip` |
| Windows x64 + CUDA | `sd-master-<sha>-bin-win-cuda12-x64.zip` |
The zip ships three files:
- `sd-cli` — the CLI we invoke per job
- `sd-server` — long-running HTTP server (we don't use it today,
but it's a likely future optimisation)
- `libstable-diffusion.so` — shared library both binaries dynlink
## Install layout
We expect:
```
~/.local/lib/stable-diffusion/
sd-cli
sd-server
libstable-diffusion.so
~/.local/bin/
sd-cli → wrapper script that exports LD_LIBRARY_PATH
sd-server → same
```
The wrapper script keeps the actual binaries next to their .so and
sets `LD_LIBRARY_PATH` at launch time so the dynamic linker finds
the .so.
## One-shot installer
```bash
#!/usr/bin/env bash
set -euo pipefail
URL='https://github.com/leejet/stable-diffusion.cpp/releases/download/master-920-2f88688/sd-master-2f88688-bin-Linux-Ubuntu-24.04-x86_64-vulkan.zip'
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
curl -L --fail -o "$TMPDIR/sd.zip" "$URL"
unzip -o "$TMPDIR/sd.zip" -d "$TMPDIR"
install -d "$HOME/.local/lib/stable-diffusion" "$HOME/.local/bin"
install -m 755 "$TMPDIR/sd-cli" "$TMPDIR/sd-server" "$HOME/.local/lib/stable-diffusion/"
install -m 644 "$TMPDIR/libstable-diffusion.so" "$HOME/.local/lib/stable-diffusion/"
for bin in sd-cli sd-server; do
cat > "$HOME/.local/bin/$bin" <<EOF
#!/usr/bin/env bash
export LD_LIBRARY_PATH="\$HOME/.local/lib/stable-diffusion:\${LD_LIBRARY_PATH:-}"
exec "\$HOME/.local/lib/stable-diffusion/$bin" "\$@"
EOF
chmod +x "$HOME/.local/bin/$bin"
done
The URL is pinned to the same `master-N-<sha>` build the worker
auto-provisions - the `DEFAULT_RELEASE_TAG` const in
[`src/engine/sd_provision.rs`](../../src/engine/sd_provision.rs)
(`master-920-2f88688`) and the `sdcpp-prebuilt.yml` workflow default.
Keeping them identical means a manual install and the auto-provisioned
binary share one known-good sd.cpp commit, so there's no flag drift
between them. When you bump `DEFAULT_RELEASE_TAG`, update this URL in
the same change. The latest-release URL is fine for a one-off first
install.
## GPU runtime (Vulkan loader)
The Vulkan builds need the Vulkan **loader** present: `libvulkan.so.1`
(Linux) or `vulkan-1.dll` (Windows). We can't auto-provision it — it
ships with the GPU driver (Windows) or a system package + driver
(Linux). The engine preflights it and, when missing, fails the job
with the remedy: on Debian/Ubuntu `sudo apt install libvulkan1
mesa-vulkan-drivers` (plus the NVIDIA/AMD vendor driver), then verify
with `vulkaninfo --summary`. macOS uses Metal — no Vulkan loader
needed.
## Resolution order (worker side)
On the first image job the engine resolves `sd-cli` in this order:
1. `$STUDIO_WORKER_SD_CLI` env var (absolute path; the operator override,
including a PATH-free one)
2. `<models_root>/bin/sd-cli` - the auto-provisioner's slot (default
`~/models/bin/`); it records the release it installed in
`.sd-cli-release` and re-provisions when the pin changes, so do not
drop your own binary here
3. `~/.local/bin/sd-cli` (matches the playbook above)
4. `sd-cli` on `$PATH`
Steps 3 and 4 only count when `sd-cli --version` reports the pinned commit
(`DEFAULT_RELEASE_TAG`'s sha): build your own CUDA binary at that commit to keep using
it. An install at another commit is passed over and the pinned build is provisioned.
If none resolve, the engine **auto-provisions** into
`<models_root>/bin/` (download + extract the platform Vulkan build),
then runs it. Provisioning can be steered with:
- `STUDIO_WORKER_SDCPP_RELEASE` - a `master-<n>-<sha>` upstream tag to
fetch instead of the pinned default
- `STUDIO_WORKER_SDCPP_URL` - a full zip URL (air-gapped mirror) that
skips tag + asset resolution
Provisioning only fails on a target with no prebuilt Vulkan asset
(Linux arm64, Intel macOS) or with no network; the error names the
missing asset and points back here (on Windows the binary is
`sd-cli.exe`).
## Verifying the install
```bash
# Usage: /home/.../sd-cli [options]
# CLI Options:
# Smoke-test a real generation if you've staged Z-Image-Turbo files
cd ~/models
sd-cli --diffusion-model z_image_turbo-Q4_K.gguf \
--vae ae.safetensors --llm Qwen3-4B-Instruct-2507-Q4_K_M.gguf \
-p "a stone golem" --cfg-scale 1.0 --steps 8 \
-W 512 -H 512 --diffusion-fa \
-o /tmp/smoke.webp
file /tmp/smoke.webp
# /tmp/smoke.webp: RIFF (little-endian) data, Web/P image
```
If `sd-cli` panics with a Vulkan symbol error, your distro's Vulkan
loader is missing. `apt install vulkan-tools libvulkan1` on Ubuntu;
`vulkaninfo --summary` is the smoke test.
## See also
- [`docs/engines/sdcpp.md`](../engines/sdcpp.md) — the worker engine that drives this binary
- [`docs/runtime/model-source.md`](../runtime/model-source.md) — where the model files come from
- Z-Image-Turbo author's `4GB VRAM` guide: <https://github.com/leejet/stable-diffusion.cpp/wiki/How-to-Use-Z-Image-on-a-GPU-with-Only-4GB-VRAM>