diff --git a/docker/thor/.env.example b/docker/thor/.env.example new file mode 100644 index 000000000..7ae2dcd8d --- /dev/null +++ b/docker/thor/.env.example @@ -0,0 +1,8 @@ +# Thor Docker environment variables. +# +# Copy to .env and edit: +# cp docker/thor/.env.example docker/thor/.env + +# Host path mounted read-only at /models in the container. +# Policy configs that use --task.model-path should reference paths under /models. +MODEL_PATH=${HOME}/models diff --git a/docker/thor/Dockerfile b/docker/thor/Dockerfile new file mode 100644 index 000000000..27b6becce --- /dev/null +++ b/docker/thor/Dockerfile @@ -0,0 +1,284 @@ +# syntax=docker/dockerfile:1.7 +# +# Thor Docker image for holosoma_inference on Jetson Thor (JetPack 7.1). +# +# Platform: aarch64 SBSA, Ubuntu 24.04 (Noble), Python 3.12, CUDA 13. +# Purpose: run policy inference on a real Thor without needing the full +# public Isaac Sim base image (which is x86_64). +# +# Build (default `inference` target — no ROS): +# docker build -t holosoma-thor-inference -f docker/thor/Dockerfile . +# +# Build ROS-enabled target (for Ros2Input cmd_vel bridge): +# docker build --target inference-ros -t holosoma-thor-inference-ros \ +# -f docker/thor/Dockerfile . +# +# Run: +# docker run --rm --runtime nvidia --network=host -it \ +# holosoma-thor-inference inference:g1-29dof-loco --task.interface eth0 +# +# Layer strategy (stable → volatile, so code edits rebuild only the top): +# l4t-cuda — CUDA 13 devel on Ubuntu 24.04 aarch64. Rebuilds ~never. +# python-base — python3.12, build tools, uv. Rebuilds ~never. +# long-deps — NVPL, cuDSS, TensorRT runtime libs. Rebuilds ~never. +# common-deps — Python deps that rarely bump (numpy, scipy, pin, etc.). +# app-deps — holosoma_inference + unitree_sdk2. Rebuilds every commit. +# inference — entrypoint + cmd (terminal, no-ROS target). +# +# The -ros branch mirrors long-deps/common-deps/app-deps on top of a +# ros-jazzy layer, so the Jazzy apt install (~1.5 GB) is also cached stably. + +# ─── Stage: l4t-cuda ────────────────────────────────────────────────────────── +# CUDA 13 devel so nvcc + headers are available for any torch extension compile. +# The SBSA apt repo is already configured in this base image. +FROM nvcr.io/nvidia/cuda:13.0.2-devel-ubuntu24.04 AS l4t-cuda + +ENV LANG=C.UTF-8 +ENV DEBIAN_FRONTEND=noninteractive +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + + +# ─── Stage: python-base ─────────────────────────────────────────────────────── +# System Python + build tools + uv. Output venv lives at /opt/venv so it +# survives across layer boundaries and can be PATH-prepended. +FROM l4t-cuda AS python-base + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + cmake \ + curl \ + git \ + wget \ + python3.12 \ + python3.12-dev \ + python3.12-venv \ + && rm -rf /var/lib/apt/lists/* + +# Install uv from the official image (pinning to :latest — bump explicitly in +# this file if you want a deterministic version). +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +ENV VIRTUAL_ENV=/opt/venv +RUN python3.12 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + + +# ─── Stage: long-deps ───────────────────────────────────────────────────────── +# NVIDIA math + inference libs that rarely change. +FROM python-base AS long-deps + +RUN apt-get update && apt-get install -y --no-install-recommends \ + # NVPL math libs (required by CUDA torch wheel on aarch64 SBSA) + libnvpl-blas0 \ + libnvpl-lapack0 \ + # cuDSS sparse solver (required by torch on CUDA 13) + libcudss0-cuda-13 \ + # TensorRT runtime (policy ONNX runtime may dlopen libnvinfer) + libnvinfer10 \ + libnvinfer-plugin10 \ + libnvonnxparsers10 \ + && rm -rf /var/lib/apt/lists/* + +# cuDSS installs to a versioned path not in the default linker search path. +RUN echo "/usr/lib/aarch64-linux-gnu/libcudss/13" > /etc/ld.so.conf.d/cudss-13.conf \ + && ldconfig + + +# ─── Stage: common-deps ─────────────────────────────────────────────────────── +# Python dependencies of holosoma_inference that rarely change. +# Keep this layer separate from app source so edits to holosoma code don't +# invalidate these installs. +FROM long-deps AS common-deps + +# Core deps from src/holosoma_inference/setup.py install_requires, plus +# pinocchio for WBT policy support (not in setup.py — installed separately, +# matching scripts/setup_inference_via_uv.sh). +# +# DRIFT RISK: this list duplicates setup.py's install_requires because we +# want the layered-cache benefit (deps install ~once, source rebuilds +# invalidate only the final layer). When setup.py deps change, this list +# must be updated too. Source of truth: src/holosoma_inference/setup.py. +RUN uv pip install \ + "pydantic" \ + "loguru" \ + "netifaces" \ + "onnx" \ + "onnxruntime" \ + "scipy" \ + "sshkeyboard" \ + "termcolor" \ + "pyyaml" \ + "tyro>=0.10.0a4" \ + "wandb" \ + "zmq" \ + "defusedxml" \ + "evdev" \ + "pin>=3.8.0" + + +# ─── Stage: app-deps ────────────────────────────────────────────────────────── +# Install amazon-far unitree_sdk2 wheel + copy holosoma_inference source. +# This is the only layer that changes on every commit. +FROM common-deps AS app-deps + +# unitree_sdk2 Python 3.12 aarch64 wheel from amazon-far's GitHub release. +# Bump UNITREE_SDK2_VERSION in one place when a new release is published. +ARG UNITREE_SDK2_VERSION=0.1.3 +ARG UNITREE_SDK2_WHL=unitree_sdk2-${UNITREE_SDK2_VERSION}-cp312-cp312-linux_aarch64.whl +RUN wget -q -O "/tmp/${UNITREE_SDK2_WHL}" \ + "https://github.com/amazon-far/unitree_sdk2/releases/download/${UNITREE_SDK2_VERSION}/${UNITREE_SDK2_WHL}" \ + && uv pip install "/tmp/${UNITREE_SDK2_WHL}" \ + && rm "/tmp/${UNITREE_SDK2_WHL}" + +# holosoma_inference source (editable install, --no-deps since common-deps +# handled the runtime deps above). +COPY src/holosoma_inference /opt/holosoma-src/src/holosoma_inference +WORKDIR /opt/holosoma-src +RUN uv pip install --no-deps -e src/holosoma_inference + + +# ─── Stage: inference ───────────────────────────────────────────────────────── +# Default target: terminal image with policy entrypoint, no ROS. +# Override CMD with the policy config + task flags at `docker run` time. +FROM app-deps AS inference + +WORKDIR /opt/holosoma-src + +ENTRYPOINT ["python3", "src/holosoma_inference/holosoma_inference/run_policy.py"] +CMD ["--help"] + + +# ═════════════════════════════════════════════════════════════════════════════ +# ROS 2 Jazzy branch — mirrors no-ROS branch but layered on ros-jazzy. +# Built with: docker build --target inference-ros ... +# ═════════════════════════════════════════════════════════════════════════════ + +# ─── Stage: ros-jazzy ───────────────────────────────────────────────────────── +# ROS 2 Jazzy from upstream packages.ros.org (not NVIDIA's L4T repo — we want +# the distribution mainline). Adds ~1.5 GB but is stable across code changes. +FROM python-base AS ros-jazzy + +RUN apt-get update && apt-get install -y --no-install-recommends \ + gnupg \ + lsb-release \ + && rm -rf /var/lib/apt/lists/* + +# Add ROS 2 apt repo + key. +RUN curl -fsSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \ + | gpg --dearmor -o /usr/share/keyrings/ros-archive-keyring.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu noble main" \ + > /etc/apt/sources.list.d/ros2.list + +# Install Jazzy base + FastDDS + CycloneDDS. +# +# Why both RMWs: unitree_sdk2's pybind11 binding bundles CycloneDDS 0.10.2 +# (ABI-incompatible with Jazzy's CycloneDDS 0.10.5). Loading both in one +# process crashes. Running rclpy on FastDDS sidesteps the conflict entirely +# — FastDDS and CycloneDDS have disjoint binary symbol spaces, so the two +# middlewares coexist cleanly in one process. unitree_sdk2 keeps its bundled +# CycloneDDS; rclpy uses FastDDS. +# +# Cross-container /cmd_vel interop: FastDDS ↔ CycloneDDS is validated on +# Jazzy for standard message types (TwistStamped tested on aarch64). Pick +# whichever RMW your publisher container uses; no alignment required. +RUN apt-get update && apt-get install -y --no-install-recommends \ + ros-jazzy-ros-base \ + ros-jazzy-rmw-fastrtps-cpp \ + ros-jazzy-rmw-cyclonedds-cpp \ + && rm -rf /var/lib/apt/lists/* + +ENV RMW_IMPLEMENTATION=rmw_fastrtps_cpp + +# Source ROS on every shell so /opt/ros/jazzy bins are on PATH. +RUN echo "source /opt/ros/jazzy/setup.bash" >> /etc/bash.bashrc + + +# ─── Stage: long-deps-ros ───────────────────────────────────────────────────── +# NVIDIA math/inference libs on top of the ROS base. +FROM ros-jazzy AS long-deps-ros + +RUN apt-get update && apt-get install -y --no-install-recommends \ + libnvpl-blas0 \ + libnvpl-lapack0 \ + libcudss0-cuda-13 \ + libnvinfer10 \ + libnvinfer-plugin10 \ + libnvonnxparsers10 \ + && rm -rf /var/lib/apt/lists/* + +RUN echo "/usr/lib/aarch64-linux-gnu/libcudss/13" > /etc/ld.so.conf.d/cudss-13.conf \ + && ldconfig + + +# ─── Stage: common-deps-ros ─────────────────────────────────────────────────── +# Same Python deps as common-deps (kept verbatim so this stage caches +# independently — cross-branch cache sharing is not possible with different +# parent layers). +FROM long-deps-ros AS common-deps-ros + +RUN uv pip install \ + "pydantic" \ + "loguru" \ + "netifaces" \ + "onnx" \ + "onnxruntime" \ + "scipy" \ + "sshkeyboard" \ + "termcolor" \ + "pyyaml" \ + "tyro>=0.10.0a4" \ + "wandb" \ + "zmq" \ + "defusedxml" \ + "evdev" \ + "pin>=3.8.0" + + +# ─── Stage: app-deps-ros ────────────────────────────────────────────────────── +FROM common-deps-ros AS app-deps-ros + +ARG UNITREE_SDK2_VERSION=0.1.3 +ARG UNITREE_SDK2_WHL=unitree_sdk2-${UNITREE_SDK2_VERSION}-cp312-cp312-linux_aarch64.whl +RUN wget -q -O "/tmp/${UNITREE_SDK2_WHL}" \ + "https://github.com/amazon-far/unitree_sdk2/releases/download/${UNITREE_SDK2_VERSION}/${UNITREE_SDK2_WHL}" \ + && uv pip install "/tmp/${UNITREE_SDK2_WHL}" \ + && rm "/tmp/${UNITREE_SDK2_WHL}" + +COPY src/holosoma_inference /opt/holosoma-src/src/holosoma_inference +# demo_scripts/ros2_velocity_publisher.py is used by the run_shuttle_publisher.sh +# helper to drive /cmd_vel for cross-container DDS tests. +COPY demo_scripts /opt/holosoma-src/demo_scripts +WORKDIR /opt/holosoma-src +RUN uv pip install --no-deps -e src/holosoma_inference + + +# ─── Stage: inference-ros ───────────────────────────────────────────────────── +# ROS-enabled terminal target. Entrypoint sources Jazzy then runs the policy +# so Ros2Input can use rclpy + TwistStamped to receive /cmd_vel. +FROM app-deps-ros AS inference-ros + +WORKDIR /opt/holosoma-src + +# Wrap entrypoint to source ROS before execing python. +# +# DDS coexistence: rclpy uses FastDDS (RMW_IMPLEMENTATION=rmw_fastrtps_cpp), +# unitree_sdk2 uses its bundled CycloneDDS 0.10.2. FastDDS and CycloneDDS +# have disjoint symbol spaces, so they coexist in one process — BUT sourcing +# ROS puts Jazzy's libddsc 0.10.5 on LD_LIBRARY_PATH, which would win when +# unitree's bundled libddscxx resolves its libddsc dep, causing an ABI +# mismatch (unitree built against 0.10.2 headers). Prepend unitree's bundle +# dir to LD_LIBRARY_PATH so the bundled libddsc wins for unitree's use. +# rclpy doesn't touch libddsc at all (it's on FastDDS), so no conflict. +RUN printf '%s\n' \ + '#!/bin/bash' \ + 'set -e' \ + 'source /opt/ros/jazzy/setup.bash' \ + 'export LD_LIBRARY_PATH=/opt/venv/lib/python3.12/site-packages/unitree_interface:$LD_LIBRARY_PATH' \ + 'exec python3 src/holosoma_inference/holosoma_inference/run_policy.py "$@"' \ + > /usr/local/bin/run_policy_ros \ + && chmod +x /usr/local/bin/run_policy_ros + +ENTRYPOINT ["/usr/local/bin/run_policy_ros"] +CMD ["--help"] diff --git a/docker/thor/Makefile b/docker/thor/Makefile new file mode 100644 index 000000000..485732d7f --- /dev/null +++ b/docker/thor/Makefile @@ -0,0 +1,43 @@ +# Make targets for the Thor (Jetson AGX, JetPack 7.1) holosoma_inference +# Docker images. Thin wrappers over docker compose so the common commands +# are a single token. +# +# Usage (from docker/thor/): +# make # help +# make inference # build no-ROS image +# make inference-ros # build ROS image +# make both # build both +# make run-inference ARGS='inference:g1-29dof-loco --task.interface eth0' +# make run-inference-ros ARGS='inference:g1-29dof-loco --task.velocity-input ros2 --task.state-input interface --task.interface eth0' +# make clean # remove both images + +.PHONY: help inference inference-ros both run-inference run-inference-ros clean + +COMPOSE := docker compose -f compose.yaml + +help: + @echo "Thor Docker targets (run from docker/thor/):" + @echo " make inference Build holosoma-thor-inference (no ROS)" + @echo " make inference-ros Build holosoma-thor-inference-ros (ROS 2 Jazzy)" + @echo " make both Build both images" + @echo " make run-inference Run no-ROS image (pass ARGS='...')" + @echo " make run-inference-ros Run ROS image (pass ARGS='...')" + @echo " make clean Remove both images" + +inference: + $(COMPOSE) build inference + +inference-ros: + $(COMPOSE) build inference-ros + +both: + $(COMPOSE) build + +run-inference: + $(COMPOSE) run --rm inference $(ARGS) + +run-inference-ros: + $(COMPOSE) run --rm inference-ros $(ARGS) + +clean: + -docker rmi holosoma-thor-inference holosoma-thor-inference-ros diff --git a/docker/thor/README.md b/docker/thor/README.md new file mode 100644 index 000000000..4534bfc8b --- /dev/null +++ b/docker/thor/README.md @@ -0,0 +1,175 @@ +# Thor Docker images — `holosoma_inference` + +Jetson Thor (JetPack 7.1, Ubuntu 24.04, CUDA 13, aarch64 SBSA). + +Two targets ship from a single `Dockerfile`: + +| Target | Image name | Includes | Use case | +|---|---|---|---| +| `inference` | `holosoma-thor-inference` | Policy + unitree_sdk2. No ROS. | Joystick or keyboard input, self-contained inference on Thor. | +| `inference-ros` | `holosoma-thor-inference-ros` | Policy + unitree_sdk2 + ROS 2 Jazzy (FastDDS for rclpy, unitree's bundled CycloneDDS for the SDK). | `Ros2Input` — subscribe to `/cmd_vel` from any ROS publisher. | + +## Build + +From the repo root: + +```bash +# No-ROS variant (smaller image, works with joystick/keyboard input) +docker build --target inference -t holosoma-thor-inference -f docker/thor/Dockerfile . + +# ROS-enabled variant (required for Ros2Input) +docker build --target inference-ros -t holosoma-thor-inference-ros -f docker/thor/Dockerfile . +``` + +Or via Docker Compose (builds the same images, uses `compose.yaml`): + +```bash +docker compose -f docker/thor/compose.yaml build inference +docker compose -f docker/thor/compose.yaml build inference-ros +``` + +Or via the scoped `Makefile` (run from `docker/thor/`): + +```bash +cd docker/thor +make inference # no-ROS +make inference-ros # ROS-enabled +make both # both +``` + +## Run (launch scripts) + +The `scripts/` directory has thin wrappers around `docker compose run` for +the common input-mode combinations (joystick+joystick, ros2+joystick, etc.). +See `scripts/README.md` for the full list. Quick start: + +```bash +cd docker/thor/scripts +./run_joystick.sh # blind-loco via joystick (no ROS) +./run_ros2_joystick.sh # /cmd_vel from ROS + joystick state +./run_shuttle_publisher.sh # (other terminal) drives /cmd_vel +``` + +## Run (Docker Compose — recommended) + +The compose file wires up the runtime flags (`--runtime nvidia`, host +network, host IPC, `--privileged`, GPU env) and mounts a model directory +from the host. Copy `.env.example` to `.env` to override `MODEL_PATH` if +your ONNX models live somewhere other than `$HOME/models`. + +```bash +# Walking demo on a Unitree G1 via joystick (no ROS needed) +docker compose -f docker/thor/compose.yaml run --rm inference \ + inference:g1-29dof-loco --task.interface eth0 + +# Receive /cmd_vel from another ROS container on the same network +docker compose -f docker/thor/compose.yaml run --rm inference-ros \ + inference:g1-29dof-loco \ + --task.velocity-input ros2 \ + --task.state-input interface \ + --task.interface eth0 +``` + +Or via Makefile shortcuts (run from `docker/thor/`): + +```bash +cd docker/thor +make run-inference ARGS='inference:g1-29dof-loco --task.interface eth0' +make run-inference-ros ARGS='inference:g1-29dof-loco --task.velocity-input ros2 --task.state-input interface --task.interface eth0' +``` + +## Run (direct docker, if you don't want compose) + +```bash +docker run --rm --runtime nvidia --network=host --ipc=host --privileged -it \ + -v $HOME/models:/models:ro \ + holosoma-thor-inference \ + inference:g1-29dof-loco --task.interface eth0 + +docker run --rm --runtime nvidia --network=host --ipc=host --privileged -it \ + -v $HOME/models:/models:ro \ + -e RMW_IMPLEMENTATION=rmw_fastrtps_cpp \ + holosoma-thor-inference-ros \ + inference:g1-29dof-loco \ + --task.velocity-input ros2 \ + --task.state-input interface \ + --task.interface eth0 +``` + +`RMW_IMPLEMENTATION=rmw_fastrtps_cpp` is also the image default. Publisher +containers can use either FastDDS or CycloneDDS — cross-vendor DDS interop +for standard message types is validated on Jazzy. + +`--network=host` is required so unitree_sdk2's UDP multicast can reach the +robot. `--runtime nvidia` gives the container access to the Thor GPU. +`--ipc=host` lets the container share POSIX shared memory with sibling +containers (e.g. a ZED depth pipeline). + +## Layer structure + +Ordered stable → volatile so day-to-day code changes only rebuild the top +layer: + +``` +l4t-cuda (CUDA 13 devel, Ubuntu 24.04) ← never + └─ python-base (python3.12, build tools, uv) ← ~never + ├─ long-deps (NVPL, cuDSS, TensorRT libs) ← ~never + │ └─ common-deps (numpy, scipy, pin, …) ← weekly-ish + │ └─ app-deps (unitree_sdk2 + src) ← every commit + │ └─ inference ← terminal target + │ + └─ ros-jazzy (ros-jazzy-ros-base + FastDDS + CycloneDDS) ← ~never + └─ long-deps-ros (same as long-deps) ← ~never + └─ common-deps-ros (same as common-deps) ← weekly-ish + └─ app-deps-ros (unitree_sdk2 + src) ← every commit + └─ inference-ros ← terminal target +``` + +Both branches share `l4t-cuda` and `python-base`. Everything below forks +because the ROS install mutates system apt state and Python deps need to be +installed on top of ROS's system packages to avoid ABI surprises. + +## Version pinning + +- **CUDA**: `nvcr.io/nvidia/cuda:13.0.2-devel-ubuntu24.04` (matches JetPack 7.1 + + CUDA 13 on Thor). +- **ROS 2**: Jazzy (Noble native; compatible with common ROS nav stacks). +- **`unitree_sdk2`**: `0.1.3` (set via `--build-arg UNITREE_SDK2_VERSION=...`). + Bump when a new amazon-far release is published with a cp312 aarch64 wheel. +- **Python deps**: unpinned by design — let `uv` resolve. Source of truth + for the dep list is `src/holosoma_inference/setup.py`. + +## Troubleshooting + +**Build fails at `wget ... unitree_sdk2-0.1.3-cp312-cp312-linux_aarch64.whl`** + +The wheel asset may not yet be uploaded to the release. Check +https://github.com/amazon-far/unitree_sdk2/releases and ping the maintainer. + +**`libcudss.so.0: cannot open shared object file`** + +cuDSS installs to `/usr/lib/aarch64-linux-gnu/libcudss/13/`, which the +Dockerfile adds via `ld.so.conf.d`. If you see this error, something has +overwritten `ld.so.cache`; run `ldconfig` inside the container. + +**Robot doesn't respond to `/cmd_vel` (inference-ros)** + +Most common cause: the policy isn't in Walking state. `--task.state-input +interface` means `START/WALK/STAND` come from the joystick, not ROS — press +the start + walk combo on the Unitree controller before expecting incoming +velocity commands to take effect. + +DDS interop: this image's rclpy runs on FastDDS by default +(`RMW_IMPLEMENTATION=rmw_fastrtps_cpp`). Cross-vendor interop with a +CycloneDDS publisher is hardware-validated for `TwistStamped` on Jazzy-to- +Jazzy (see `scripts/run_shuttle_publisher_cyclonedds.sh`). If you observe +silent data-delivery failures with a mixed setup, first confirm both sides +are on the same ROS 2 distro — our own testing has seen cross-distro +pairings (e.g. Humble ↔ Jazzy) fail silently while same-distro cross-vendor +pairings work. + +Why FastDDS for rclpy (not CycloneDDS): unitree_sdk2's pybind11 binding +bundles CycloneDDS 0.10.2 C++ libraries, ABI-incompatible with Jazzy's +CycloneDDS 0.10.5. Loading both in one process crashes. FastDDS and +CycloneDDS have disjoint binary symbol spaces, so they coexist cleanly — +rclpy on FastDDS, unitree_sdk2 on its bundled CycloneDDS. diff --git a/docker/thor/compose.yaml b/docker/thor/compose.yaml new file mode 100644 index 000000000..938cbd040 --- /dev/null +++ b/docker/thor/compose.yaml @@ -0,0 +1,67 @@ +# Docker Compose for Thor (Jetson Thor, JetPack 7.1) holosoma_inference. +# +# Two services, both built from the same Dockerfile with different targets: +# inference — no-ROS policy; joystick/keyboard/interface input only +# inference-ros — adds ROS 2 Jazzy for Ros2Input (/cmd_vel subscriber) +# +# Only one service should run at a time per robot (both bind to the same +# unitree_sdk2 UDP multicast on the host network). +# +# Usage: +# # Build one or both +# docker compose -f docker/thor/compose.yaml build inference +# docker compose -f docker/thor/compose.yaml build inference-ros +# +# # Run the blind-loco policy with joystick input (no ROS needed) +# docker compose -f docker/thor/compose.yaml run --rm inference \ +# inference:g1-29dof-loco --task.interface eth0 +# +# # Run with /cmd_vel input from a ROS publisher +# docker compose -f docker/thor/compose.yaml run --rm inference-ros \ +# inference:g1-29dof-loco \ +# --task.velocity-input ros2 \ +# --task.state-input interface \ +# --task.interface eth0 +# +# Override the default model directory mount via .env: +# MODEL_PATH=~/models # contains g1_29dof/model_*.onnx + +services: + inference: + image: holosoma-thor-inference + build: + context: ../.. + dockerfile: docker/thor/Dockerfile + target: inference + runtime: nvidia + network_mode: host # unitree_sdk2 uses UDP multicast on the host net + ipc: host # share POSIX shm with other locomotion containers + privileged: true # raw socket access for unitree_sdk2 + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=all + volumes: + # Optional: mount ONNX model directory from host. + # Policy configs reference --task.model-path; set MODEL_PATH in .env + # to override the default of $HOME/models. + - ${MODEL_PATH:-${HOME}/models}:/models:ro + + inference-ros: + image: holosoma-thor-inference-ros + build: + context: ../.. + dockerfile: docker/thor/Dockerfile + target: inference-ros + runtime: nvidia + network_mode: host + ipc: host + privileged: true + environment: + - NVIDIA_VISIBLE_DEVICES=all + - NVIDIA_DRIVER_CAPABILITIES=all + # FastDDS for rclpy — coexists with unitree_sdk2's bundled CycloneDDS + # in the same process. FastDDS ↔ CycloneDDS cross-vendor interop + # works on Jazzy for standard message types (see docker/thor/README.md). + - RMW_IMPLEMENTATION=rmw_fastrtps_cpp + volumes: + - ${MODEL_PATH:-${HOME}/models}:/models:ro diff --git a/docker/thor/scripts/README.md b/docker/thor/scripts/README.md new file mode 100644 index 000000000..df207bffd --- /dev/null +++ b/docker/thor/scripts/README.md @@ -0,0 +1,31 @@ +# Thor launch scripts + +Shell wrappers over `docker compose run --rm ...` for common policy invocations. +Build the image once (`cd docker/thor && make inference` or +`make inference-ros`), then launch with one of these scripts. + +Each script passes through extra arguments to `run_policy.py`, e.g.: + +```bash +./run_joystick.sh --task.model-path /models/my_custom.onnx +``` + +| Script | Target image | velocity_input | state_input | Policy | +|---|---|---|---|---| +| `run_joystick.sh` | `inference` | joystick | joystick | g1-29dof-loco | +| `run_keyboard.sh` | `inference` | keyboard | keyboard | g1-29dof-loco | +| `run_ros2_joystick.sh` | `inference-ros` | ros2 (`/cmd_vel`) | joystick | g1-29dof-loco | +| `run_ros2_keyboard.sh` | `inference-ros` | ros2 (`/cmd_vel`) | keyboard | g1-29dof-loco | +| `run_wbt_joystick.sh` | `inference` | joystick | joystick | g1-29dof-wbt | +| `run_shuttle_publisher.sh` | `inference-ros` | (publisher, not policy) | — | — | +| `run_shuttle_publisher_cyclonedds.sh` | `inference-ros` | (publisher, not policy) | — | — | + +`run_shuttle_publisher.sh` publishes a 3s-forward / 3s-back shuttle pattern +on `/cmd_vel` to validate the Ros2Input cross-container DDS path. Launch +`run_ros2_joystick.sh` in another terminal first; transition the policy +to Walking via the joystick; then run the shuttle publisher. + +`run_shuttle_publisher_cyclonedds.sh` is the same publisher but forces +`RMW_IMPLEMENTATION=rmw_cyclonedds_cpp` — useful for validating cross-vendor +DDS interop when the policy container runs rclpy on FastDDS (the default) +and the publisher uses CycloneDDS. diff --git a/docker/thor/scripts/run_joystick.sh b/docker/thor/scripts/run_joystick.sh new file mode 100755 index 000000000..7b55ae601 --- /dev/null +++ b/docker/thor/scripts/run_joystick.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Launch holosoma-thor-inference with joystick velocity + joystick state commands. +# Policy: g1-29dof-loco (blind locomotion). +# +# Usage: ./run_joystick.sh [extra args for run_policy.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm inference \ + inference:g1-29dof-loco \ + --task.use-joystick \ + --task.interface eth0 \ + "$@" diff --git a/docker/thor/scripts/run_keyboard.sh b/docker/thor/scripts/run_keyboard.sh new file mode 100755 index 000000000..ee1245f09 --- /dev/null +++ b/docker/thor/scripts/run_keyboard.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Launch holosoma-thor-inference with keyboard velocity + keyboard state commands. +# Policy: g1-29dof-loco (blind locomotion). +# +# Usage: ./run_keyboard.sh [extra args for run_policy.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm inference \ + inference:g1-29dof-loco \ + --task.use-keyboard \ + --task.interface eth0 \ + "$@" diff --git a/docker/thor/scripts/run_ros2_joystick.sh b/docker/thor/scripts/run_ros2_joystick.sh new file mode 100755 index 000000000..833ffe98f --- /dev/null +++ b/docker/thor/scripts/run_ros2_joystick.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Launch holosoma-thor-inference-ros with /cmd_vel from ROS + state from joystick. +# Policy: g1-29dof-loco. +# +# Use this when a ROS publisher is driving velocity and the joystick is +# used only for Walking/Standing state transitions. +# +# Usage: ./run_ros2_joystick.sh [extra args for run_policy.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm inference-ros \ + inference:g1-29dof-loco \ + --task.velocity-input ros2 \ + --task.state-input interface \ + --task.interface eth0 \ + "$@" diff --git a/docker/thor/scripts/run_ros2_keyboard.sh b/docker/thor/scripts/run_ros2_keyboard.sh new file mode 100755 index 000000000..6956896e6 --- /dev/null +++ b/docker/thor/scripts/run_ros2_keyboard.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Launch holosoma-thor-inference-ros with /cmd_vel from ROS + state from keyboard. +# Policy: g1-29dof-loco. +# +# Usage: ./run_ros2_keyboard.sh [extra args for run_policy.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm inference-ros \ + inference:g1-29dof-loco \ + --task.velocity-input ros2 \ + --task.state-input keyboard \ + --task.interface eth0 \ + "$@" diff --git a/docker/thor/scripts/run_shuttle_publisher.sh b/docker/thor/scripts/run_shuttle_publisher.sh new file mode 100755 index 000000000..9bb911d6d --- /dev/null +++ b/docker/thor/scripts/run_shuttle_publisher.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Launch the shuttle velocity publisher INSIDE the inference-ros container. +# Publishes /cmd_vel with a 3s-forward, 3s-backward pattern at 0.5 m/s, +# which run_policy (with --task.velocity-input ros2) will subscribe to. +# +# Intended workflow: +# Terminal 1: ./run_ros2_joystick.sh # starts the policy +# Terminal 2: ./run_shuttle_publisher.sh # drives /cmd_vel +# +# Usage: ./run_shuttle_publisher.sh [extra args for ros2_velocity_publisher.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm --entrypoint='' inference-ros \ + bash -c "source /opt/ros/jazzy/setup.bash && \ + python3 -u /opt/holosoma-src/demo_scripts/ros2_velocity_publisher.py \ + --pattern shuttle \ + --other-topic holosoma/state_input \ + $*" \ + "$@" diff --git a/docker/thor/scripts/run_shuttle_publisher_cyclonedds.sh b/docker/thor/scripts/run_shuttle_publisher_cyclonedds.sh new file mode 100755 index 000000000..a3095a8a0 --- /dev/null +++ b/docker/thor/scripts/run_shuttle_publisher_cyclonedds.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Same as run_shuttle_publisher.sh but forces CycloneDDS for the publisher +# while the policy container runs rclpy on FastDDS (image default). This +# validates cross-vendor DDS interop for /cmd_vel TwistStamped delivery — +# useful for anyone bringing a CycloneDDS-based ROS publisher to a policy +# container running on FastDDS. +# +# Usage: ./run_shuttle_publisher_cyclonedds.sh [extra args] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm --entrypoint='' \ + -e RMW_IMPLEMENTATION=rmw_cyclonedds_cpp \ + inference-ros \ + bash -c "source /opt/ros/jazzy/setup.bash && \ + python3 -u /opt/holosoma-src/demo_scripts/ros2_velocity_publisher.py \ + --pattern shuttle \ + --other-topic holosoma/state_input \ + $*" \ + "$@" diff --git a/docker/thor/scripts/run_wbt_joystick.sh b/docker/thor/scripts/run_wbt_joystick.sh new file mode 100755 index 000000000..42a12f14a --- /dev/null +++ b/docker/thor/scripts/run_wbt_joystick.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# Launch holosoma-thor-inference with WBT (whole-body tracking) policy, +# joystick velocity + joystick state. +# +# Policy: g1-29dof-wbt. +# +# Usage: ./run_wbt_joystick.sh [extra args for run_policy.py] +set -e + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + +docker compose -f compose.yaml run --rm inference \ + inference:g1-29dof-wbt \ + --task.use-joystick \ + --task.interface eth0 \ + "$@"