Skip to content

Commit f628a7d

Browse files
committed
feat: add GPU and CPU variants
1 parent 3cd1ca6 commit f628a7d

16 files changed

Lines changed: 223 additions & 52 deletions

‎.dockerignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*
22
!Dockerfile
33
!avatar.png
4+
!entrypoint.sh
45
!manager.mjs
56
!qvac.config.mjs
67
!ui/

‎.github/workflows/auto_check.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ on:
1111
jobs:
1212
bump-upstream:
1313
uses: dappnode/workflows/.github/workflows/bump-upstream.yml@master
14+
with:
15+
use_variants: true
16+
variants_dir: package_variants
1417
secrets: inherit

‎.github/workflows/build.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ on:
1111
jobs:
1212
build:
1313
uses: dappnode/workflows/.github/workflows/dappnode-build-hash.yml@master
14+
with:
15+
all_variants: true
1416
secrets: inherit

‎.github/workflows/main.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Log in to GitHub Container Registry
2222
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
2323
- name: Publish
24-
run: npx @dappnode/dappnodesdk publish patch --github_release --timeout 1h --content_provider=http://10.200.200.7:5001 --eth_provider=https://web3.dappnode.net
24+
run: npx @dappnode/dappnodesdk publish patch --all-variants --github_release --timeout 1h --content_provider=http://10.200.200.7:5001 --eth_provider=https://web3.dappnode.net
2525
env:
2626
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2727
DEVELOPER_ADDRESS: "0xf35960302a07022aba880dffaec2fdd64d5bf1c1"

‎Dockerfile‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
FROM node:22-bookworm-slim
1+
FROM node:22-trixie-slim
22

33
ARG UPSTREAM_VERSION
44
ARG TARGETARCH
55

66
RUN apt-get update \
77
&& apt-get install -y --no-install-recommends \
8-
ca-certificates ffmpeg libatomic1 libvulkan1 mesa-vulkan-drivers tini \
8+
ca-certificates ffmpeg libatomic1 libvulkan1 mesa-vulkan-drivers tini vulkan-tools \
99
&& rm -rf /var/lib/apt/lists/* \
1010
&& npm install --global --no-audit --no-fund "@qvac/cli@${UPSTREAM_VERSION}" \
1111
&& chmod 0755 /usr/local/lib/node_modules/@qvac/cli/node_modules/bare-runtime-*/bin/bare \
@@ -26,18 +26,19 @@ WORKDIR /app
2626

2727
COPY qvac.config.mjs /app/qvac.config.mjs
2828
COPY manager.mjs /app/manager.mjs
29+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
2930
COPY ui /app/ui
3031
COPY avatar.png /app/ui/avatar.png
3132

3233
RUN mkdir -p /data/config /data/models /data/runtime \
33-
&& chown -R node:node /app /data
34+
&& chown -R node:node /app /data \
35+
&& chmod 0755 /usr/local/bin/entrypoint.sh
3436

3537
ENV HOME=/data \
3638
NODE_ENV=production \
3739
QVAC_VERSION=${UPSTREAM_VERSION}
3840

39-
USER node
40-
4141
EXPOSE 8080 11434
4242

43-
ENTRYPOINT ["/usr/bin/tini", "--", "node", "/app/manager.mjs"]
43+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
44+
CMD ["node", "/app/manager.mjs"]

‎README.md‎

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,30 @@ This package runs the official [QVAC](https://qvac.tether.io/) CLI as a private,
44

55
The image includes QVAC's upstream worker and native Linux addons. The built-in configuration UI provides a searchable model library and manages QVAC settings without shell or File Manager access.
66

7-
## Endpoints
7+
## Variants
88

9-
- Configuration UI: `http://qvac.dappnode:8080`
10-
- OpenAI-compatible API: `http://qvac.dappnode:11434/v1`
11-
- Interactive API documentation: `http://qvac.dappnode:11434/docs`
9+
The repository publishes two packages from the same image and configuration:
10+
11+
| Variant | Package | Hostname | Hardware behavior |
12+
| --- | --- | --- | --- |
13+
| CPU | `qvac.dnp.dappnode.eth` | `qvac.dappnode` | Does not request a GPU and runs on every DAppNode |
14+
| GPU | `qvac-gpu.dnp.dappnode.eth` | `qvac-gpu.dappnode` | Reserves every GPU exposed by Docker's GPU/CDI runtime |
15+
16+
On either hostname, the configuration UI uses port `8080`, the OpenAI-compatible API uses `/v1` on port `11434`, and the interactive API documentation is at `/docs` on port `11434`.
1217

1318
## Defaults
1419

15-
- QVAC CLI: `0.11.0`
20+
- QVAC CLI: `0.12.0`
1621
- Model: `QWEN3_600M_INST_Q4`
1722
- API alias: `qwen3-600m`
1823
- Model cache: `/data/models`
1924
- Configuration: `/data/config/qvac.config.json`
2025

21-
CPU inference is supported on Linux. The package does not request host GPU devices, so larger models and image generation can be slow. Model downloads, configuration, and backups persist in the `data` volume.
26+
Model downloads, configuration, and backups persist in the `data` volume.
27+
28+
## CPU and GPU behavior
29+
30+
Docker treats a Compose GPU reservation as a hard requirement and fails before starting the container when no GPU vendor is available. The CPU package therefore omits that reservation. The GPU package requests every GPU through Docker's vendor-neutral `gpu` capability and requires a working AMD or NVIDIA container toolkit/CDI configuration. Both variants use the same image, which includes the Vulkan loader, Mesa Vulkan drivers, and `vulkaninfo`.
2231

2332
## Configuration ownership
2433

@@ -28,17 +37,27 @@ The installation wizard is limited to settings that must exist before the packag
2837
- Optional host mapping for the API port
2938
- Data-volume location
3039

31-
After installation, use `http://qvac.dappnode:8080` for model selection, aliases, preload/default behavior, engine configuration, logging, download behavior, browser origins, JSON import/export, validation, and backup restore.
40+
After installation, open port `8080` on your variant's hostname for model selection, aliases, preload/default behavior, engine configuration, logging, download behavior, browser origins, JSON import/export, validation, and backup restore.
3241

3342
## Development
3443

35-
Build and run locally without IPFS:
44+
Build and run the CPU variant locally without IPFS:
3645

3746
```bash
38-
docker compose build
39-
docker compose up -d
47+
docker compose -f docker-compose.yml -f package_variants/cpu/docker-compose.yml build
48+
docker compose -f docker-compose.yml -f package_variants/cpu/docker-compose.yml up -d
4049
```
4150

51+
Confirm that the CPU-safe service is running:
52+
53+
```bash
54+
docker compose -f docker-compose.yml -f package_variants/cpu/docker-compose.yml ps
55+
API_PORT=$(docker compose -f docker-compose.yml -f package_variants/cpu/docker-compose.yml port qvac 11434 | sed 's/.*://')
56+
curl --fail "http://127.0.0.1:${API_PORT}/v1/models"
57+
```
58+
59+
Use `package_variants/gpu/docker-compose.yml` instead to test the GPU variant on a host where `docker run --rm --gpus all ubuntu:24.04 true` succeeds. CI and the release workflow build and publish both variants with the SDK's `--all-variants` option.
60+
4261
Use `make dev-tar` only when a Docker image tarball is needed for a DAppNode development upload.
4362

4463
## Upstream

‎dappnode_package.json‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
{
2-
"name": "qvac.dnp.dappnode.eth",
3-
"version": "0.1.0",
42
"upstream": [
53
{
64
"repo": "tetherto/qvac",
7-
"version": "0.11.0",
5+
"version": "0.12.0",
86
"arg": "UPSTREAM_VERSION"
97
}
108
],
119
"shortDescription": "Private multimodal AI with OpenAI-compatible APIs",
12-
"description": "Run the complete [QVAC](https://qvac.tether.io/) CLI locally on DAppNode as an OpenAI-compatible AI provider. QVAC keeps prompts and inference on your own hardware. Its HTTP server supports text generation, embeddings, RAG, image and video generation, transcription, text-to-speech, and audio translation when the corresponding models are configured.\n\nA built-in configuration UI includes a searchable model library, validates and backs up settings, and applies changes without shell access. The QVAC worker reloads independently, so the UI remains available while models download or start. The default configuration serves the compact `QWEN3_600M_INST_Q4` text-generation model as `qwen3-600m`. Downloaded models, custom configuration, and backups are stored in a persistent volume.\n\nThis image includes QVAC's upstream worker and all native Linux addons shipped by the CLI.\n\n- Configuration UI: `http://qvac.dappnode:8080`\n- API: `http://qvac.dappnode:11434/v1`\n- API documentation: `http://qvac.dappnode:11434/docs`\n- CPU inference works on Linux; 4 GB RAM and 5 GB free disk are the upstream minimum recommendations, while larger models require more resources.\n- Set an API key during installation if clients outside your trusted DAppNode network can reach the service.",
10+
"description": "Run the complete [QVAC](https://qvac.tether.io/) CLI locally on DAppNode as an OpenAI-compatible AI provider. QVAC keeps prompts and inference on your own hardware. Its HTTP server supports text generation, embeddings, RAG, image and video generation, transcription, text-to-speech, and audio translation when the corresponding models are configured.\n\nA built-in configuration UI includes a searchable model library, validates and backs up settings, and applies changes without shell access. The QVAC worker reloads independently, so the UI remains available while models download or start. The default configuration serves the compact `QWEN3_600M_INST_Q4` text-generation model as `qwen3-600m`. Downloaded models, custom configuration, and backups are stored in a persistent volume.\n\nThis image includes QVAC's upstream worker and all native Linux addons shipped by the CLI. The default package does not reserve a GPU, so it installs and runs on CPU-only DAppNodes.\n\n- Configuration UI: `http://qvac.dappnode:8080`\n- API: `http://qvac.dappnode:11434/v1`\n- API documentation: `http://qvac.dappnode:11434/docs`\n- At least 4 GB RAM and 5 GB free disk are required for the default model; larger models require substantially more RAM and storage.\n- Set an API key during installation if clients outside your trusted DAppNode network can reach the service.",
1311
"type": "service",
12+
"mainService": "qvac",
1413
"author": "DAppNode Association <admin@dappnode.io> (https://github.com/dappnode)",
1514
"license": "Apache-2.0",
1615
"categories": ["AI"],
@@ -40,6 +39,6 @@
4039
}
4140
],
4241
"warnings": {
43-
"onInstall": "Without an API key, QVAC is available without authentication to clients that can reach the DAppNode network. The configuration UI on port 8080 is administrative and should not be exposed publicly. The complete runtime image and configured model downloads can use many GB of storage. Large models may also require substantial RAM and take several minutes to load."
42+
"onInstall": "This package runs on CPU-only DAppNodes. CPU inference can be slow, so start with the default compact model before selecting larger models. Without an API key, QVAC is available without authentication to clients that can reach the DAppNode network. The configuration UI on port 8080 is administrative and should not be exposed publicly. The complete runtime image and configured model downloads can use many GB of storage. Large models may also require substantial RAM and take several minutes to load."
4443
}
4544
}

‎docker-compose.yml‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
version: "3.5"
22

33
services:
4-
qvac.dnp.dappnode.eth:
5-
image: "qvac.dnp.dappnode.eth:0.1.0"
4+
qvac:
65
build:
76
context: .
87
args:
9-
UPSTREAM_VERSION: "0.11.0"
8+
UPSTREAM_VERSION: "0.12.0"
109
restart: unless-stopped
1110
volumes:
1211
- "data:/data"

‎entrypoint.sh‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
node_uid=$(id -u node)
5+
node_gid=$(id -g node)
6+
gpu_group_ids=""
7+
8+
for gpu_device in /dev/dri/card* /dev/dri/renderD*; do
9+
[ -e "${gpu_device}" ] || continue
10+
11+
gpu_gid=$(stat -c '%g' "${gpu_device}")
12+
[ "${gpu_gid}" != "0" ] || continue
13+
14+
case ",${gpu_group_ids}," in
15+
*,"${gpu_gid}",*) ;;
16+
*) gpu_group_ids="${gpu_group_ids:+${gpu_group_ids},}${gpu_gid}" ;;
17+
esac
18+
done
19+
20+
set -- /usr/bin/tini -- "$@"
21+
22+
if [ -n "${gpu_group_ids}" ]; then
23+
echo "[entrypoint] Granting QVAC access to GPU group IDs: ${gpu_group_ids}"
24+
exec setpriv \
25+
--reuid "${node_uid}" \
26+
--regid "${node_gid}" \
27+
--groups "${gpu_group_ids}" \
28+
-- "$@"
29+
fi
30+
31+
exec setpriv \
32+
--reuid "${node_uid}" \
33+
--regid "${node_gid}" \
34+
--clear-groups \
35+
-- "$@"

‎getting-started.md‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22

33
Open the configuration UI after installation:
44

5-
- Configuration UI: `http://qvac.dappnode:8080`
6-
- OpenAI-compatible API: `http://qvac.dappnode:11434/v1`
7-
- Interactive API documentation: `http://qvac.dappnode:11434/docs`
5+
- CPU package: `qvac.dappnode`
6+
- GPU package: `qvac-gpu.dappnode`
7+
8+
On the hostname for your installed package, the configuration UI uses port `8080`, the OpenAI-compatible API uses `/v1` on port `11434`, and the interactive API documentation is at `/docs` on port `11434`.
89

910
QVAC starts with the compact `qwen3-600m` model. Its first download can take several minutes; later starts reuse the persistent model cache.
1011

12+
## CPU and GPU variants
13+
14+
The `qvac.dnp.dappnode.eth` package does not reserve a GPU and therefore starts on CPU-only DAppNodes. CPU inference is slower and needs enough system RAM for the selected model; begin with `qwen3-600m` before loading larger models.
15+
16+
The `qvac-gpu.dnp.dappnode.eth` package reserves every GPU exposed by Docker. Before installing it, confirm that `docker run --rm --gpus all ubuntu:24.04 true` succeeds on the DAppNode host. Use the CPU package if that check fails.
17+
1118
## Choose models
1219

1320
1. Open **Models** in the configuration UI.
@@ -23,7 +30,8 @@ Models that do not preload are loaded when first requested. This helps avoid fil
2330
Use the API key chosen during installation as a Bearer token. Omit the `Authorization` header if no key was configured.
2431

2532
```bash
26-
curl http://qvac.dappnode:11434/v1/chat/completions \
33+
QVAC_HOST=qvac.dappnode # Use qvac-gpu.dappnode for the GPU package
34+
curl "http://${QVAC_HOST}:11434/v1/chat/completions" \
2735
-H 'Content-Type: application/json' \
2836
-H 'Authorization: Bearer YOUR_API_KEY' \
2937
-d '{

0 commit comments

Comments
 (0)