Skip to content

Commit b18873d

Browse files
add Docker Compose evaluation stack (client image + orchestration + docs) (#15)
1 parent a399931 commit b18873d

5 files changed

Lines changed: 547 additions & 1 deletion

File tree

.dockerignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ build/
66
build-*/
77
outputs/
88
third_party/
9-
eval/sim/
9+
# Clone targets and venvs (large, runtime-only)
10+
eval/sim/libero/LIBERO/
11+
eval/sim/libero/libero_uv/
12+
eval/sim/simpler/SimplerEnv/
13+
eval/sim/simpler/simpler_uv/
1014
*.gguf

docs/DOCKER.md

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# Docker evaluation workflow for vla.cpp
2+
3+
This document describes the Docker Compose evaluation stack, which runs the
4+
vla.cpp inference server and the Python simulation client in separate
5+
containers. The current `eval/docker-compose.yml` is a **CUDA GPU** stack that
6+
requests an NVIDIA CDI device; CPU-only Docker commands are provided separately
7+
below.
8+
9+
| Container | Image | Purpose |
10+
|-----------|-------|---------|
11+
| `server` | root `Dockerfile` | C++ `vla-server` daemon (CUDA or CPU) |
12+
| `client` | `eval/Dockerfile.client` | Python simulation environment (MuJoCo, LIBERO / SimplerEnv) |
13+
14+
> **Source of truth**: For model details, supported architectures, and
15+
> benchmark numbers, refer to the top-level [README.md](../README.md).
16+
> This document covers the Docker-specific evaluation workflow only.
17+
18+
---
19+
20+
## Quick start (CUDA GPU)
21+
22+
### Prerequisites
23+
24+
- [Docker Compose](https://docs.docker.com/compose/) v2.24+
25+
- NVIDIA GPU with proprietary driver ≥ 535.
26+
- CDI GPU access for Docker (`devices: - nvidia.com/gpu=all`). See
27+
[CUDA GPU access](#cuda-gpu-access) for runtime setup details.
28+
29+
### 1. Download model GGUF files
30+
31+
```bash
32+
docker compose -f eval/docker-compose.yml build client
33+
docker compose -f eval/docker-compose.yml run --no-deps --rm client \
34+
hf download vrfai/smolvla-libero-gguf --local-dir /models
35+
```
36+
37+
> `--no-deps` skips building the server image, which isn't needed for downloads.
38+
> The Compose file mounts the host directory `/tmp/smolvla-models` into both
39+
> containers as `/models`; the default server command expects
40+
> `/models/smolvla-libero.gguf`.
41+
42+
Models are mounted into both containers at `/models`.
43+
44+
### 2. Build the images
45+
46+
```bash
47+
docker compose -f eval/docker-compose.yml build
48+
```
49+
50+
Build args accepted by the server `Dockerfile`:
51+
52+
| Arg | Default | Notes |
53+
|-----|---------|-------|
54+
| `BACKEND` | `cuda` | `cuda` or `cpu` |
55+
| `CUDA_ARCH` | `120` | Blackwell; `89` for RTX40, `87` for Orin, `86` for RTX30 |
56+
| `BASE_IMAGE` | `nvidia/cuda:12.9.1-devel-ubuntu24.04` | Set to `ubuntu:24.04` when building a CPU image |
57+
| `JOBS` | `nproc` | Lower if nvcc segfaults on flash-attn kernels |
58+
59+
Override via e.g. `docker compose -f eval/docker-compose.yml build --build-arg CUDA_ARCH=89 server`.
60+
61+
### 3. Start the server
62+
63+
```bash
64+
docker compose -f eval/docker-compose.yml up -d server
65+
docker compose -f eval/docker-compose.yml logs server
66+
# … vla-server: bound to tcp://*:5555. ready.
67+
```
68+
69+
The default `command` in `eval/docker-compose.yml` starts SmolVLA for LIBERO:
70+
`--bind tcp://*:5555 /models/smolvla-libero.gguf`. To serve another model,
71+
create a Compose override that replaces only `server.command`; for example:
72+
73+
```bash
74+
cat >/tmp/vla-compose.override.yml <<'YAML'
75+
services:
76+
server:
77+
command:
78+
- --bind
79+
- tcp://*:5555
80+
- /models/gr00tn1d7-libero.gguf
81+
YAML
82+
83+
docker compose -f eval/docker-compose.yml -f /tmp/vla-compose.override.yml up -d server
84+
```
85+
86+
> **π0 note**: π0 needs a separate `mmproj` vision GGUF. Pass both files:
87+
> `--bind tcp://*:5555 /models/mmproj-....gguf /models/ckpt.gguf`.
88+
> See the [README model table](../README.md#models) for details.
89+
90+
### 4. Run a LIBERO evaluation episode
91+
92+
```bash
93+
docker compose -f eval/docker-compose.yml run --rm client \
94+
python eval/client/run_sim_client_direct.py \
95+
--task libero_object --task-id 0 --n-episodes 1 \
96+
--output-dir /tmp/libero_outputs --arch smolvla \
97+
--vla-addr tcp://server:5555
98+
```
99+
100+
Or drop into an interactive shell:
101+
102+
```bash
103+
docker compose -f eval/docker-compose.yml run --rm client
104+
root@...:/workspace/vla.cpp# python eval/client/run_sim_client_direct.py \
105+
--task libero_object --task-id 0 --n-episodes 1 \
106+
--output-dir /tmp/libero_outputs --arch smolvla \
107+
--vla-addr tcp://server:5555
108+
```
109+
110+
Results (videos, summary) are written to `/tmp/libero_outputs` on the host.
111+
112+
**Example output (RTX 5060 Ti, CUDA arch 120):**
113+
114+
```
115+
vla-cpp-direct[arch=smolvla]: connected to tcp://server:5555
116+
- Step 220: reward=1.00, done=True, truncated=False
117+
- Episode finished after 220 steps. Final reward: 1.00
118+
- Success rate: 100.00% (1/1)
119+
- Average inference time per step: 116.45 ms
120+
```
121+
122+
---
123+
124+
## Quick start (CPU-only, no GPU)
125+
126+
The checked-in Compose file requests `devices: - nvidia.com/gpu=all`, so use
127+
plain `docker build` / `docker run` for a CPU-only host unless you also maintain
128+
a local Compose override that removes the GPU device request. Build and run the
129+
server image with `BACKEND=cpu`:
130+
131+
### 1. Build the server image for CPU
132+
133+
```bash
134+
docker build -t vla-cpp-cpu \
135+
--build-arg BACKEND=cpu \
136+
--build-arg BASE_IMAGE=ubuntu:24.04 .
137+
```
138+
139+
### 2. Download the model
140+
141+
```bash
142+
docker build -t vla-cpp-client -f eval/Dockerfile.client .
143+
docker run --rm -v /tmp/smolvla-models:/models vla-cpp-client \
144+
hf download vrfai/smolvla-libero-gguf --local-dir /models
145+
```
146+
147+
### 3. Start the server
148+
149+
```bash
150+
docker run -d --name vla-cpp-server -p 5555:5555 \
151+
-v /tmp/smolvla-models:/models:ro \
152+
vla-cpp-cpu --bind tcp://*:5555 /models/smolvla-libero.gguf
153+
```
154+
155+
Verify with `docker logs vla-cpp-server` — look for `vla-server: bound to tcp://*:5555. ready.`
156+
157+
### 4. Run a LIBERO evaluation episode
158+
159+
```bash
160+
docker run --rm --network host \
161+
-v /tmp/smolvla-models:/models \
162+
-v /tmp/libero_outputs:/tmp/libero_outputs \
163+
vla-cpp-client \
164+
python eval/client/run_sim_client_direct.py \
165+
--task libero_object --task-id 0 --n-episodes 1 \
166+
--output-dir /tmp/libero_outputs --arch smolvla \
167+
--vla-addr tcp://localhost:5555
168+
```
169+
170+
> CPU inference is significantly slower than GPU (e.g. ~888 ms/step on Apple M4
171+
> vs ~113 ms/step on RTX 3090 for SmolVLA). Expect multi-minute episodes.
172+
173+
---
174+
175+
## Supported simulators
176+
177+
The Docker client image supports both simulators wired through the eval scaffold:
178+
179+
| Simulator | Supported arches | Setup script |
180+
|-----------|-----------------|-------------|
181+
| **LIBERO** | smolvla, pi0, pi05, gr00t_n1_5, gr00t_n1_6, gr00t_n1_7, bitvla, evo1, openvla_oft, vla_adapter, vla_jepa | `eval/sim/libero/setup_libero.sh` |
182+
| **SimplerEnv** | gr00t_n1_6 | `eval/sim/simpler/setup_SimplerEnv.sh` |
183+
184+
### SimplerEnv example
185+
186+
```bash
187+
docker compose -f eval/docker-compose.yml run --rm client \
188+
python eval/client/run_simpler_client_direct.py \
189+
--arch gr00t_n1_6 \
190+
--task-id oxe_widowx/widowx_spoon_on_towel --n-episodes 1 \
191+
--embodiment oxe_widowx --image-size 252 \
192+
--stats-json /models/dataset_statistics.json
193+
```
194+
195+
---
196+
197+
## Configuration reference
198+
199+
### Volumes
200+
201+
| Host / Volume | Container mount | Purpose |
202+
|---------------|----------------|---------|
203+
| `/tmp/smolvla-models` | `client:/models` (rw), `server:/models:ro` | GGUF model files |
204+
| `/tmp/libero_outputs` | `client:/tmp/libero_outputs` | Eval videos & summaries |
205+
| `hf-cache` (named) | `client:/root/.cache/huggingface` | HuggingFace tokenizer cache |
206+
207+
### Ports
208+
209+
| Service | Host | Container |
210+
|---------|------|-----------|
211+
| server | `5555` | `5555` |
212+
213+
### Network
214+
215+
Both services share the default Compose network. The client reaches the server
216+
via hostname `server`.
217+
218+
### CUDA GPU access
219+
220+
The server service in `eval/docker-compose.yml` uses CDI
221+
(`devices: - nvidia.com/gpu=all`). This works when:
222+
1. The NVIDIA proprietary driver is installed (≥ 535).
223+
2. A CDI-enabled container runtime is available (containerd ≥ 1.7,
224+
cri-o ≥ 1.29, or Docker with `nvidia-ctk` from `nvidia-container-toolkit`
225+
≥ 1.15 to generate `/etc/cdi/nvidia.yaml`).
226+
227+
---
228+
229+
## Running without Docker Compose
230+
231+
### Server only (GPU)
232+
233+
```bash
234+
docker build -t vla-cpp-server \
235+
--build-arg BACKEND=cuda --build-arg CUDA_ARCH=120 .
236+
237+
# CDI
238+
docker run --rm --device nvidia.com/gpu=all -p5555:5555 \
239+
-v /tmp/smolvla-models:/models:ro \
240+
vla-cpp-server --bind tcp://*:5555 /models/model.gguf
241+
242+
# nvidia-container-toolkit
243+
docker run --rm --gpus all -p5555:5555 \
244+
-v /tmp/smolvla-models:/models:ro \
245+
vla-cpp-server --bind tcp://*:5555 /models/model.gguf
246+
```
247+
248+
### Server only (CPU)
249+
250+
```bash
251+
docker build -t vla-cpp-cpu \
252+
--build-arg BACKEND=cpu \
253+
--build-arg BASE_IMAGE=ubuntu:24.04 .
254+
255+
docker run --rm -p5555:5555 \
256+
-v /tmp/smolvla-models:/models:ro \
257+
vla-cpp-cpu --bind tcp://*:5555 /models/model.gguf
258+
```
259+
260+
### Client only
261+
262+
```bash
263+
docker build -t vla-cpp-client -f eval/Dockerfile.client .
264+
docker run --rm -it --network host \
265+
-v /tmp/smolvla-models:/models \
266+
-v /tmp/libero_outputs:/tmp/libero_outputs \
267+
vla-cpp-client
268+
# Inside: connect to server at localhost:5555
269+
```
270+
271+
---
272+
273+
## Known issues
274+
275+
| Issue | Workaround |
276+
|-------|-----------|
277+
| `Unsupported gpu architecture 'compute_120'` with CUDA < 12.8 | Use CUDA 12.8+ for `sm_120`, or set `CUDA_ARCH=89` for RTX40-series compatibility |
278+
| NumPy 2.x: `module 'numpy' has no attribute 'core'` | `Dockerfile.client` pins `numpy==1.26.4` and patches accelerate |
279+
| `lerobot` pulls GPU torch | `Dockerfile.client` re-pins `torch==2.5.1` (CPU) after installing lerobot |
280+
| LIBERO data files not found | Editable install (`-e`) keeps `bddl_files/` / `init_files/` / `assets/` accessible at runtime |
281+
| LIBERO hangs on first import (dataset path prompt) | `echo "N" \| python3 -c "import libero.libero"` pre-seeds `~/.libero/config.yaml` |
282+
| `pandas` segfaults on import | Pin `pandas==2.0.3` (last NumPy 1.x-compatible release) |
283+
| MuJoCo 3.x: robosuite init fails | Pin `mujoco<3.0` (2.3.7 known-good) |
284+
| `nvidia-container-toolkit` not installed | Use CDI (`devices: - nvidia.com/gpu=all`) instead of `runtime: nvidia` |
285+
| CPU-only: no GPU available | Use the CPU-only `docker build` / `docker run` flow above, or maintain a Compose override that removes `devices: - nvidia.com/gpu=all` and builds with `BACKEND=cpu` plus `BASE_IMAGE=ubuntu:24.04` |
286+
287+
---
288+
289+
## Summary
290+
291+
The Docker evaluation stack provides a reproducible two-container workflow for
292+
vla.cpp:
293+
294+
1. **Server** — upstream `Dockerfile`, compiles `vla-server` with GPU by default
295+
in Compose or with a CPU backend in the standalone CPU flow.
296+
2. **Client**`eval/Dockerfile.client`, Python simulation stack with pinned
297+
dependency versions (NumPy 1.x, MuJoCo 2.x, Pandas 2.0.x).
298+
3. **CDI** is the GPU access path used by the checked-in Compose file.
299+
4. **CPU-only** mode works without any GPU through the standalone Docker commands
300+
above.
301+
5. **First-step overhead** (~35 s CUDA graph warmup) occurs once per process (GPU only).

0 commit comments

Comments
 (0)