diff --git a/.dockerignore b/.dockerignore index 9354afc..7e9ae1d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,5 +6,9 @@ build/ build-*/ outputs/ third_party/ -eval/sim/ +# Clone targets and venvs (large, runtime-only) +eval/sim/libero/LIBERO/ +eval/sim/libero/libero_uv/ +eval/sim/simpler/SimplerEnv/ +eval/sim/simpler/simpler_uv/ *.gguf diff --git a/docs/DOCKER.md b/docs/DOCKER.md new file mode 100644 index 0000000..80dd488 --- /dev/null +++ b/docs/DOCKER.md @@ -0,0 +1,284 @@ +# Docker evaluation workflow for vla.cpp + +This document describes the Docker Compose evaluation stack, which runs the +vla.cpp inference server and the Python simulation client in separate +containers. The stack supports both **CUDA GPU** and **CPU-only** deployments. + +| Container | Image | Purpose | +|-----------|-------|---------| +| `server` | root `Dockerfile` | C++ `vla-server` daemon (CUDA or CPU) | +| `client` | `eval/Dockerfile.client` | Python simulation environment (MuJoCo, LIBERO / SimplerEnv) | + +> **Source of truth**: For model details, supported architectures, and +> benchmark numbers, refer to the top-level [README.md](../README.md). +> This document covers the Docker-specific evaluation workflow only. + +--- + +## Quick start (CUDA GPU) + +### Prerequisites + +- [Docker Compose](https://docs.docker.com/compose/) v2.24+ +- NVIDIA GPU with proprietary driver ≥ 535. GPU access uses **CDI** + (`devices: - nvidia.com/gpu=all`) — no `nvidia-container-toolkit` needed. + See [CUDA GPU access](#cuda-gpu-access) for details. + +### 1. Download model GGUF files + +```bash +docker compose -f eval/docker-compose.yml build client +docker compose -f eval/docker-compose.yml run --no-deps --rm client \ + hf download vrfai/smolvla-libero-gguf --local-dir /models +``` + +> `--no-deps` skips building the server image, which isn't needed for downloads. +> Each model ships as a single self-contained GGUF — no separate `mmproj` file. + +Models are mounted into both containers at `/models`. + +### 2. Build the images + +```bash +docker compose -f eval/docker-compose.yml build +``` + +Build args accepted by the server `Dockerfile`: + +| Arg | Default | Notes | +|-----|---------|-------| +| `BACKEND` | `cuda` | `cuda` or `cpu` | +| `CUDA_ARCH` | `120` | Blackwell; `89` for RTX40, `87` for Orin, `86` for RTX30 | +| `BASE_IMAGE` | `nvidia/cuda:12.9.1-devel-ubuntu24.04` | Overridden automatically for CPU | +| `JOBS` | `nproc` | Lower if nvcc segfaults on flash-attn kernels | + +Override via e.g. `docker compose -f eval/docker-compose.yml build --build-arg CUDA_ARCH=89 server`. + +### 3. Start the server + +```bash +docker compose -f eval/docker-compose.yml up -d server +docker compose -f eval/docker-compose.yml logs server +# … vla-server: bound to tcp://*:5555. ready. +``` + +The default `command` in `eval/docker-compose.yml` starts SmolVLA for LIBERO. +For other models (e.g. GR00T-N1.7): + +```bash +docker compose -f eval/docker-compose.yml run --rm server \ + --bind tcp://*:5555 /models/gr00tn1d7-libero.gguf +``` + +> **π0 note**: π0 needs a separate `mmproj` vision GGUF. Pass both files: +> `--bind tcp://*:5555 /models/mmproj-....gguf /models/ckpt.gguf`. +> See the [README model table](../README.md#models) for details. + +### 4. Run a LIBERO evaluation episode + +```bash +docker compose -f eval/docker-compose.yml run --rm client \ + python eval/client/run_sim_client_direct.py \ + --task libero_object --task-id 0 --n-episodes 1 \ + --output-dir /tmp/libero_outputs --arch smolvla \ + --vla-addr tcp://server:5555 +``` + +Or drop into an interactive shell: + +```bash +docker compose -f eval/docker-compose.yml run --rm client +root@...:/workspace/vla.cpp# python eval/client/run_sim_client_direct.py \ + --task libero_object --task-id 0 --n-episodes 1 \ + --output-dir /tmp/libero_outputs --arch smolvla \ + --vla-addr tcp://server:5555 +``` + +Results (videos, summary) are written to `/tmp/libero_outputs` on the host. + +**Example output (RTX 5060 Ti, CUDA arch 120):** + +``` +vla-cpp-direct[arch=smolvla]: connected to tcp://server:5555 +- Step 220: reward=1.00, done=True, truncated=False +- Episode finished after 220 steps. Final reward: 1.00 +- Success rate: 100.00% (1/1) +- Average inference time per step: 116.45 ms +``` + +--- + +## Quick start (CPU-only, no GPU) + +On machines without an NVIDIA GPU, +build and run the server image with `BACKEND=cpu`: + +### 1. Build the server image for CPU + +```bash +docker build -t vla-cpp-cpu \ + --build-arg BACKEND=cpu \ + --build-arg BASE_IMAGE=ubuntu:24.04 . +``` + +### 2. Download the model + +```bash +docker build -t vla-cpp-client -f eval/Dockerfile.client . +docker run --rm -v /tmp/smolvla-models:/models vla-cpp-client \ + hf download vrfai/smolvla-libero-gguf --local-dir /models +``` + +### 3. Start the server + +```bash +docker run -d --name vla-cpp-server -p 5555:5555 \ + -v /tmp/smolvla-models:/models:ro \ + vla-cpp-cpu --bind tcp://*:5555 /models/smolvla-libero.gguf +``` + +Verify with `docker logs vla-cpp-server` — look for `vla-server: bound to tcp://*:5555. ready.` + +### 4. Run a LIBERO evaluation episode + +```bash +docker run --rm --network host \ + -v /tmp/smolvla-models:/models \ + -v /tmp/libero_outputs:/tmp/libero_outputs \ + vla-cpp-client \ + python eval/client/run_sim_client_direct.py \ + --task libero_object --task-id 0 --n-episodes 1 \ + --output-dir /tmp/libero_outputs --arch smolvla \ + --vla-addr tcp://localhost:5555 +``` + +> CPU inference is significantly slower than GPU (e.g. ~888 ms/step on Apple M4 +> vs ~113 ms/step on RTX 3090 for SmolVLA). Expect multi-minute episodes. + +--- + +## Supported simulators + +The Docker client image supports both simulators wired through the eval scaffold: + +| Simulator | Supported arches | Setup script | +|-----------|-----------------|-------------| +| **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` | +| **SimplerEnv** | gr00t_n1_6 | `eval/sim/simpler/setup_SimplerEnv.sh` | + +### SimplerEnv example + +```bash +docker compose -f eval/docker-compose.yml run --rm client \ + python eval/client/run_simpler_client_direct.py \ + --arch gr00t_n1_6 \ + --task-id oxe_widowx/widowx_spoon_on_towel --n-episodes 1 \ + --embodiment oxe_widowx --image-size 252 \ + --stats-json /models/dataset_statistics.json +``` + +--- + +## Configuration reference + +### Volumes + +| Host / Volume | Container mount | Purpose | +|---------------|----------------|---------| +| `/tmp/smolvla-models` | `client:/models` (rw), `server:/models:ro` | GGUF model files | +| `/tmp/libero_outputs` | `client:/tmp/libero_outputs` | Eval videos & summaries | +| `hf-cache` (named) | `client:/root/.cache/huggingface` | HuggingFace tokenizer cache | + +### Ports + +| Service | Host | Container | +|---------|------|-----------| +| server | `5555` | `5555` | + +### Network + +Both services share the default Compose network. The client reaches the server +via hostname `server`. + +### CUDA GPU access + +The server uses CDI (`devices: - nvidia.com/gpu=all`). This works without +`nvidia-container-toolkit` as long as: +1. The NVIDIA proprietary driver is installed (≥ 535). +2. A CDI-enabled container runtime is available (containerd ≥ 1.7, + cri-o ≥ 1.29, or Docker with `nvidia-ctk` from `nvidia-container-toolkit` + ≥ 1.15 to generate `/etc/cdi/nvidia.yaml`). + +--- + +## Running without Docker Compose + +### Server only (GPU) + +```bash +docker build -t vla-cpp-server \ + --build-arg BACKEND=cuda --build-arg CUDA_ARCH=120 . + +# CDI +docker run --rm --device nvidia.com/gpu=all -p5555:5555 \ + -v /tmp/smolvla-models:/models:ro \ + vla-cpp-server --bind tcp://*:5555 /models/model.gguf + +# nvidia-container-toolkit +docker run --rm --gpus all -p5555:5555 \ + -v /tmp/smolvla-models:/models:ro \ + vla-cpp-server --bind tcp://*:5555 /models/model.gguf +``` + +### Server only (CPU) + +```bash +docker build -t vla-cpp-cpu \ + --build-arg BACKEND=cpu \ + --build-arg BASE_IMAGE=ubuntu:24.04 . + +docker run --rm -p5555:5555 \ + -v /tmp/smolvla-models:/models:ro \ + vla-cpp-cpu --bind tcp://*:5555 /models/model.gguf +``` + +### Client only + +```bash +docker build -t vla-cpp-client -f eval/Dockerfile.client . +docker run --rm -it --network host \ + -v /tmp/smolvla-models:/models \ + -v /tmp/libero_outputs:/tmp/libero_outputs \ + vla-cpp-client +# Inside: connect to server at localhost:5555 +``` + +--- + +## Known issues + +| Issue | Workaround | +|-------|-----------| +| `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 | +| NumPy 2.x: `module 'numpy' has no attribute 'core'` | `Dockerfile.client` pins `numpy==1.26.4` and patches accelerate | +| `lerobot` pulls GPU torch | `Dockerfile.client` re-pins `torch==2.5.1` (CPU) after installing lerobot | +| LIBERO data files not found | Editable install (`-e`) keeps `bddl_files/` / `init_files/` / `assets/` accessible at runtime | +| LIBERO hangs on first import (dataset path prompt) | `echo "N" \| python3 -c "import libero.libero"` pre-seeds `~/.libero/config.yaml` | +| `pandas` segfaults on import | Pin `pandas==2.0.3` (last NumPy 1.x-compatible release) | +| MuJoCo 3.x: robosuite init fails | Pin `mujoco<3.0` (2.3.7 known-good) | +| `nvidia-container-toolkit` not installed | Use CDI (`devices: - nvidia.com/gpu=all`) instead of `runtime: nvidia` | +| CPU-only: no GPU available | Use `BACKEND=cpu` build arg and `BASE_IMAGE=ubuntu:24.04` instead of CUDA base | + +--- + +## Summary + +The Docker Compose evaluation stack provides a reproducible two-container +workflow for vla.cpp: + +1. **Server** — upstream `Dockerfile`, compiles `vla-server` with GPU or CPU backend. +2. **Client** — `eval/Dockerfile.client`, Python simulation stack with pinned + dependency versions (NumPy 1.x, MuJoCo 2.x, Pandas 2.0.x). +3. **CDI** eliminates the `nvidia-container-toolkit` dependency for GPU access. +4. **CPU-only** mode works without any GPU — use `BACKEND=cpu` build arg. +5. **First-step overhead** (~35 s CUDA graph warmup) occurs once per process (GPU only). \ No newline at end of file diff --git a/eval/Dockerfile.client b/eval/Dockerfile.client new file mode 100644 index 0000000..6f6e86d --- /dev/null +++ b/eval/Dockerfile.client @@ -0,0 +1,127 @@ +# vla.cpp evaluation client container. +# +# Contains the Python simulation environment (MuJoCo, robosuite, lerobot) and +# the eval scripts needed to drive vla-server from a LIBERO / SimplerEnv +# episode. No CUDA, no C++ build — this is purely a Python runtime. +# +# Usage (with the upstream server Dockerfile): +# +# # Terminal 1 — build & start the server: +# docker build -t vla-cpp-server . # upstream Dockerfile +# docker run --gpus all -p5555:5555 -v $PWD/models:/models \ +# vla-cpp-server --bind tcp://*:5555 /models/model.gguf +# +# # Terminal 2 — build & run the client: +# docker build -t vla-cpp-client -f eval/Dockerfile.client . +# docker run --rm -it --network host \ +# -v /tmp/libero_outputs:/tmp/libero_outputs \ +# vla-cpp-client +# root@...:/workspace/vla.cpp# python eval/client/run_sim_client_direct.py \ +# --task libero_object --task-id 0 --n-episodes 1 \ +# --output-dir /tmp/libero_outputs --arch smolvla \ +# --vla-addr tcp://localhost:5555 +# +# Or with docker-compose (see eval/docker-compose.yml). + +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive \ + TZ=UTC \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python \ + VLA_CPP_PROTO=/workspace/vla.cpp/src/serving/vla.proto + +# System dependencies: build tools, protobuf, ZMQ, EGL (for MuJoCo offscreen) +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + cmake \ + git \ + curl \ + ca-certificates \ + libzmq3-dev \ + libprotobuf-dev \ + protobuf-compiler \ + python3.10 \ + python3.10-venv \ + python3.10-dev \ + python3-pip \ + libegl-dev \ + && rm -rf /var/lib/apt/lists/* + +# Make python3.10 the default +RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1 && \ + ln -sf /usr/bin/python3 /usr/local/bin/python +RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.10 + +WORKDIR /workspace/vla.cpp +COPY . . + +# --------------------------------------------------------------------------- +# Python dependencies — order matters to avoid version conflicts +# --------------------------------------------------------------------------- + +# Pin foundational deps first so nothing pulls numpy 2.x or mujoco 3.x +RUN pip3 install numpy==1.26.4 +RUN pip3 install "mujoco<3.0" + +# PyTorch CPU-only (the server handles GPU inference) +# install it now to prevent later deps from pulling in a GPU version +RUN pip3 install torch==2.5.1 --index-url https://download.pytorch.org/whl/cpu +RUN pip3 install torchvision==0.20.1 --index-url https://download.pytorch.org/whl/cpu + +# LIBERO — clone and install. +# We use editable install (following eval/sim/libero/setup_libero.sh) so that +# the source tree with bddl_files/, init_files/, assets/ stays accessible at +# runtime. The first import seeds ~/.libero/config.yaml via the script's +# "echo N" trick, avoiding the interactive dataset-path prompt. +# +# The CMAKE_POLICY_VERSION_MINIMUM env-var is needed by egl-probe (a transitive +# dep of robomimic/robosuite) which requests cmake_minimum_required < 3.5. +RUN CMAKE_POLICY_VERSION_MINIMUM=3.5 pip3 install --upgrade pip setuptools +RUN git clone --depth 1 https://github.com/Lifelong-Robot-Learning/LIBERO.git /tmp/LIBERO +RUN CMAKE_POLICY_VERSION_MINIMUM=3.5 pip3 install -r /tmp/LIBERO/requirements.txt +RUN CMAKE_POLICY_VERSION_MINIMUM=3.5 pip3 install \ + -e /tmp/LIBERO --config-settings editable_mode=compat && \ + rm -rf /root/.libero && \ + echo "N" | python3 -c "import libero.libero" 2>/dev/null || true + +# lerobot — install with dependencies but immediately re-pin torch so it is +# not upgraded from our pinned CPU-only version. +RUN pip3 install lerobot==0.4.3 && \ + pip3 install torch==2.5.1 --index-url https://download.pytorch.org/whl/cpu && \ + pip3 install torchvision==0.20.1 --index-url https://download.pytorch.org/whl/cpu + +# Remaining client dependencies +RUN pip3 install \ + transformers==4.51.3 \ + gymnasium==0.29.1 \ + zmq \ + msgpack==1.1.0 \ + msgpack-numpy==0.4.8 \ + pillow \ + imageio \ + tqdm \ + pydantic \ + av \ + tianshou==0.5.1 \ + tyro \ + pandas==2.0.3 \ + dm_tree \ + einops==0.8.1 \ + albumentations==1.4.18 \ + pyarrow==12.0.1 \ + diffusers==0.30.1 \ + huggingface-hub + +# Re-pin numpy to 1.x — later deps (albumentations, tianshou, etc.) pull in 2.x +RUN pip3 install numpy==1.26.4 + +# Apply the accelerate monkey-patch for numpy compat +RUN sed -i 's/np_core = np._core if is_numpy_available("2.0.0") else np.core/np_core = np.core/' \ + /usr/local/lib/python3.10/dist-packages/accelerate/utils/other.py 2>/dev/null || true + +# Clean up pip cache to keep image size small +RUN rm -rf /root/.cache/pip + +CMD ["bash"] diff --git a/eval/docker-compose.yml b/eval/docker-compose.yml new file mode 100644 index 0000000..cf7d5be --- /dev/null +++ b/eval/docker-compose.yml @@ -0,0 +1,78 @@ +# vla.cpp two-container evaluation stack. +# +# server — vla-server (built with the upstream Dockerfile) +# client — Python simulation environment (eval/Dockerfile.client) +# +# Usage: +# docker compose -f eval/docker-compose.yml build # builds both images +# docker compose -f eval/docker-compose.yml up -d server # start server +# docker compose -f eval/docker-compose.yml run --rm client # interactive shell +# +# Inside the client shell: +# python eval/client/run_sim_client_direct.py \ +# --task libero_object --task-id 0 --n-episodes 1 \ +# --output-dir /tmp/libero_outputs --arch smolvla \ +# --vla-addr tcp://server:5555 +# +# The server is reachable at hostname "server" (Docker Compose DNS). + +services: + + # -------------------------------------------------------------------------- + # vla-server — C++ inference daemon (upstream Dockerfile) + # Build args: + # BACKEND=cuda|cpu (default: cuda) + # CUDA_ARCH (default: 120, use 89 for RTX40, 87 for Orin, etc.) + # -------------------------------------------------------------------------- + server: + build: + context: .. + dockerfile: Dockerfile + args: + BACKEND: cuda + CUDA_ARCH: "120" + image: vla-cpp-server + container_name: vla-cpp-server + devices: + - nvidia.com/gpu=all # CDI + volumes: + # Mount GGUF model files (read-only) + - /tmp/smolvla-models:/models:ro + environment: + - CUDA_VISIBLE_DEVICES=0 + ports: + - "5555:5555" + # For SmolVLA the single GGUF is enough. + # For π0 pass mmproj + model GGUF. + # For baked-in-vision models (BitVLA, GR00T, Evo-1) omit mmproj. + command: + - --bind + - tcp://*:5555 + - /models/smolvla-libero.gguf + + # -------------------------------------------------------------------------- + # Client — Python simulation environment (eval/Dockerfile.client) + # -------------------------------------------------------------------------- + client: + build: + context: .. + dockerfile: eval/Dockerfile.client + image: vla-cpp-client + container_name: vla-cpp-client + stdin_open: true + tty: true + depends_on: + - server + volumes: + # Output directory for eval videos / summaries + - /tmp/libero_outputs:/tmp/libero_outputs + # Model download destination (also used by server) + - /tmp/smolvla-models:/models + # Cache for HuggingFace tokenizers etc. + - hf-cache:/root/.cache/huggingface + environment: + - PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python + - VLA_CPP_PROTO=/workspace/vla.cpp/src/serving/vla.proto + +volumes: + hf-cache: