OpenPi训练标准化流程Docker封装

官方代码拉取

拉取官方代码

git clone --recurse-submodules git@github.com:Physical-Intelligence/openpi.git

# Or if you already cloned the repo:
git submodule update --init --recursive

新建docker文件夹,放置以下内容

Dockerfile

Dockerfile主要训练环境按照官方要求封装,使得不同机器、集群分发的训练环境一致。

# syntax=docker/dockerfile:1.7

FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04

ARG DEBIAN_FRONTEND=noninteractive

ARG INSTALL_RLDS=0


# -----------------------------
# apt 源
# -----------------------------

RUN sed -i \
    -e 's@archive.ubuntu.com@mirrors.tuna.tsinghua.edu.cn@g' \
    -e 's@security.ubuntu.com@mirrors.tuna.tsinghua.edu.cn@g' \
    /etc/apt/sources.list


RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        git \
        git-lfs \
        ca-certificates \
        curl \
        build-essential \
        clang \
        linux-headers-generic \
        ffmpeg \
        libgl1 \
        libglib2.0-0 \
        libsm6 \
        libxext6 \
        libxrender1 && \
    git lfs install --system && \
    rm -rf /var/lib/apt/lists/*


# -----------------------------
# uv
# -----------------------------

ENV UV_INSTALL_DIR=/usr/local/bin
ENV UV_PYTHON_INSTALL_MIRROR=https://mirrors.ustc.edu.cn/github-release/astral-sh/python-build-standalone/
ENV UV_DOWNLOAD_URL=https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/
ENV UV_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple

RUN curl -sL \
    https://mirrors.ustc.edu.cn/github-release/astral-sh/uv/LatestRelease/uv-installer.sh \
    | sh


# -----------------------------
# Python 环境
# -----------------------------

ENV UV_LINK_MODE=copy
ENV VIRTUAL_ENV=/workspace/openpi/.venv
ENV PATH="${VIRTUAL_ENV}/bin:${PATH}"

ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1


WORKDIR /workspace/openpi


COPY pyproject.toml uv.lock ./


RUN uv venv --python 3.11
RUN uv pip install setuptools wheel

# -----------------------------
# 安装依赖
# -----------------------------

COPY packages/openpi-client/pyproject.toml \
     packages/openpi-client/pyproject.toml

COPY packages/openpi-client/src \
     packages/openpi-client/src


RUN --mount=type=cache,target=/root/.cache/uv \
    if [ "${INSTALL_RLDS}" = "1" ]; then \
        GIT_LFS_SKIP_SMUDGE=1 \
        uv sync \
          --no-install-project \
          --no-dev \
          --group rlds; \
    else \
        GIT_LFS_SKIP_SMUDGE=1 \
        uv sync \
          --no-install-project \
          --no-dev; \
    fi


# -----------------------------
# openpi源码
# -----------------------------

COPY . .

RUN chmod +x docker/train_pipeline.sh


RUN --mount=type=cache,target=/root/.cache/uv \
    GIT_LFS_SKIP_SMUDGE=1 \
    uv pip install \
      -e .


# -----------------------------
# transformers patch
# -----------------------------

RUN python - <<'PY'
from pathlib import Path
import shutil
import transformers

source = Path(
    "/workspace/openpi/src/openpi/models_pytorch/transformers_replace"
)

target = Path(transformers.__file__).parent

if source.exists():
    for item in source.iterdir():
        destination = target / item.name

        if item.is_dir():
            shutil.copytree(
                item,
                destination,
                dirs_exist_ok=True,
            )
        else:
            shutil.copy2(item, destination)

    print(f"Patched transformers at: {target}")
PY


# -----------------------------
# 数据目录
# -----------------------------

ENV OPENPI_DATA_HOME=/openpi_assets
ENV HF_HOME=/root/.cache/huggingface


RUN mkdir -p \
    /data \
    /openpi_assets \
    /workspace/openpi/checkpoints \
    /workspace/openpi/assets \
    /workspace/openpi/wandb


ENTRYPOINT ["/workspace/openpi/docker/train_pipeline.sh"]

Docker Compose

Docker Compose主要指定Docker容器的网络通讯,映射目录、镜像、环境变量等标准化部署参数

name: openpi-pi05

services:
  train:
    image: openpi-train:cuda12.2

    build:
      context: ..
      dockerfile: docker/train.Dockerfile
      args:
        INSTALL_RLDS: ${INSTALL_RLDS:-0}

    init: true
    stdin_open: true
    tty: true

    # 训练程序、数据加载器会使用共享内存
    ipc: host

    # W&B、Hugging Face、策略服务等访问方便
    network_mode: host

    working_dir: /workspace/openpi

    env_file:
      - path: ../.env
        required: false

    volumes:

      # 数据集
      - ${DATASET_DIR}:/data:ro

      # checkpoint
      - ${CHECKPOINT_DIR}:/workspace/openpi/checkpoints

      # assets / norm stats
      - ${ASSETS_DIR}:/workspace/openpi/assets

      # pi0/pi05 权重缓存
      - ${OPENPI_CACHE_DIR}:/openpi_assets

      # HuggingFace缓存
      - ${HF_CACHE_DIR}:/root/.cache/huggingface

      # W&B日志
      - ${WANDB_DIR}:/workspace/openpi/wandb


    environment:

      OPENPI_DATA_HOME: /openpi_assets

      # hf缓存地址
      HF_HOME: /root/.cache/huggingface

      # lerobot数据父路径
      HF_LEROBOT_HOME: /data

      CUDA_VISIBLE_DEVICES: ${CUDA_VISIBLE_DEVICES:-0}

      # JAX显存比例
      XLA_PYTHON_CLIENT_MEM_FRACTION: ${XLA_MEMORY_FRACTION:-0.90}


      WANDB_API_KEY: ${WANDB_API_KEY:-}
      WANDB_MODE: ${WANDB_MODE:-online}


      NVIDIA_VISIBLE_DEVICES: all
      NVIDIA_DRIVER_CAPABILITIES: compute,utility


      PYTHONUNBUFFERED: "1"


    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

.env

训练环境变量导入Docker,模型输入输出产物存放地址在该文件中进行改动

USER_ID和GROUP_ID主要考虑模型产物权限问题

可根据宿主机查看:

id -u
id -g
# -----------------------------
# 自动归一化 + 训练 Pipeline
# -----------------------------
TRAIN_CONFIG_NAME=pi05_r2v2_custom
DATASET_REPO_ID=dual_leg_pick_bottle_new_01
R2V2_VIEW=dual
EXP_NAME=dual_leg_pick_bottle_new_01

MODEL_PI05=1
ACTION_DIM=32
OUTPUT_ACTION_DIM=16
ACTION_HORIZON=50
DISCRETE_STATE_INPUT=0
PROMPT_FROM_TASK=1
BASE_CHECKPOINT=gs://openpi-assets/checkpoints/pi05_base/params

TRAIN_BACKEND=jax
FSDP_DEVICES=1
BATCH_SIZE=32
NUM_WORKERS=4
NUM_TRAIN_STEPS=20000
SEED=42

PEAK_LR=2.5e-5
DECAY_LR=2.5e-6
WARMUP_STEPS=1000
LR_DECAY_STEPS=20000
WEIGHT_DECAY=1e-10
CLIP_GRADIENT_NORM=1.0

LOG_INTERVAL=100
SAVE_INTERVAL=1000
KEEP_PERIOD=5000
OVERWRITE=0
RESUME=0

SKIP_NORM_STATS=0
NORM_MAX_FRAMES=

WANDB_ENABLED=1
WANDB_PROJECT=openpi
PYTORCH_GPU_COUNT=1
PYTORCH_TRAINING_PRECISION=bfloat16


# 你的 LeRobot / RLDS 数据目录
DATASET_DIR=/media/eft/F6918E841F1A5101

# 训练结果
CHECKPOINT_DIR=/mnt/data/pi05_outputs/checkpoints

# norm stats 和训练 assets
ASSETS_DIR=/mnt/data/pi05_outputs/assets

# openpi 下载的 pi05_base 等模型缓存
OPENPI_CACHE_DIR=/mnt/data/model_cache/openpi

# Hugging Face 缓存
HF_CACHE_DIR=/mnt/data/model_cache/huggingface

# W&B 日志
WANDB_DIR=/mnt/data/pi05_outputs/wandb

# 单卡一般设置为0
CUDA_VISIBLE_DEVICES=0

XLA_MEMORY_FRACTION=0.90

# 普通 LeRobot 数据不需要RLDS
INSTALL_RLDS=0

WANDB_MODE=online
WANDB_API_KEY=

# proxy
HTTP_PROXY=http://127.0.0.1:7890
HTTPS_PROXY=http://127.0.0.1:7890
NO_PROXY=localhost,127.0.0.1

# 如果是 Clash Meta / v2ray 等
ALL_PROXY=http://127.0.0.1:7890

config.py注入

根据构建好的docker导入的环境变量自动注入对应训练配置文件

def _env_bool(name: str, default: bool) -> bool:
    value = os.getenv(name)
    if value is None:
        return default
    normalized = value.strip().lower()
    if normalized in {"1", "true", "yes", "on"}:
        return True
    if normalized in {"0", "false", "no", "off"}:
        return False
    raise ValueError(f"{name} must be a boolean, got {value!r}")


def _env_config() -> TrainConfig | None:
    """Build an opt-in TrainConfig for the Docker training pipeline."""
    name = os.getenv("TRAIN_CONFIG_NAME")
    if not name:
        return None

    repo_id = os.getenv("DATASET_REPO_ID")
    if not repo_id:
        raise ValueError("DATASET_REPO_ID is required when TRAIN_CONFIG_NAME is set")

    view = os.getenv("R2V2_VIEW", "dual").strip().lower()
    data_factory = {
        "dual": LeRobotR2v2DataConfig,
        "right": LeRobotRightR2v2DataConfig,
        "front": LeRobotR2v2FrontDataConfig,
    }.get(view)
    if data_factory is None:
        raise ValueError("R2V2_VIEW must be one of: dual, right, front")

    train_steps = int(os.getenv("NUM_TRAIN_STEPS", "50000"))
    peak_lr = float(os.getenv("PEAK_LR", "2.5e-5"))
    checkpoint = os.getenv("BASE_CHECKPOINT", "gs://openpi-assets/checkpoints/pi05_base/params")
    precision = os.getenv("PYTORCH_TRAINING_PRECISION", "bfloat16")
    if precision not in {"bfloat16", "float32"}:
        raise ValueError("PYTORCH_TRAINING_PRECISION must be bfloat16 or float32")

    return TrainConfig(
        name=name,
        project_name=os.getenv("WANDB_PROJECT", "openpi"),
        exp_name=os.getenv("EXP_NAME", name),
        model=pi0_config.Pi0Config(
            pi05=_env_bool("MODEL_PI05", True),
            action_dim=int(os.getenv("ACTION_DIM", "32")),
            output_action_dim=int(os.getenv("OUTPUT_ACTION_DIM", "16")),
            action_horizon=int(os.getenv("ACTION_HORIZON", "50")),
            discrete_state_input=_env_bool("DISCRETE_STATE_INPUT", False),
        ),
        data=data_factory(
            repo_id=repo_id,
            base_config=DataConfig(prompt_from_task=_env_bool("PROMPT_FROM_TASK", True)),
        ),
        weight_loader=weight_loaders.CheckpointWeightLoader(checkpoint),
        lr_schedule=_optimizer.CosineDecaySchedule(
            warmup_steps=int(os.getenv("WARMUP_STEPS", "1000")),
            peak_lr=peak_lr,
            decay_steps=int(os.getenv("LR_DECAY_STEPS", str(train_steps))),
            decay_lr=float(os.getenv("DECAY_LR", str(peak_lr / 10))),
        ),
        optimizer=_optimizer.AdamW(
            weight_decay=float(os.getenv("WEIGHT_DECAY", "1e-10")),
            clip_gradient_norm=float(os.getenv("CLIP_GRADIENT_NORM", "1.0")),
        ),
        assets_base_dir=os.getenv("ASSETS_BASE_DIR", "/workspace/openpi/assets"),
        checkpoint_base_dir=os.getenv("CHECKPOINT_BASE_DIR", "/workspace/openpi/checkpoints"),
        seed=int(os.getenv("SEED", "42")),
        batch_size=int(os.getenv("BATCH_SIZE", "32")),
        num_workers=int(os.getenv("NUM_WORKERS", "2")),
        num_train_steps=train_steps,
        log_interval=int(os.getenv("LOG_INTERVAL", "100")),
        save_interval=int(os.getenv("SAVE_INTERVAL", "1000")),
        keep_period=int(os.getenv("KEEP_PERIOD", "5000")),
        overwrite=_env_bool("OVERWRITE", False),
        resume=_env_bool("RESUME", False),
        wandb_enabled=_env_bool("WANDB_ENABLED", True),
        fsdp_devices=int(os.getenv("FSDP_DEVICES", "1")),
        pytorch_training_precision=precision,  # type: ignore[arg-type]
    )


_PIPELINE_CONFIG = _env_config()
if _PIPELINE_CONFIG is not None:
    _CONFIGS.append(_PIPELINE_CONFIG)

训练脚本

自动归一化数据+训练 在容器构建完成之后:

#!/usr/bin/env bash
set -euo pipefail

for name in TRAIN_CONFIG_NAME DATASET_REPO_ID; do
  if [[ -z "${!name:-}" ]]; then
    echo "ERROR: ${name} is required" >&2
    exit 2
  fi
done

echo "Config: ${TRAIN_CONFIG_NAME}"
echo "Dataset: ${HF_LEROBOT_HOME:-/data}/${DATASET_REPO_ID}"
echo "Experiment: ${EXP_NAME:-${TRAIN_CONFIG_NAME}}"

if [[ "${SKIP_NORM_STATS:-0}" != "1" ]]; then
  echo "[1/2] Computing normalization statistics"
  norm_args=(--config-name "${TRAIN_CONFIG_NAME}")
  if [[ -n "${NORM_MAX_FRAMES:-}" ]]; then
    norm_args+=(--max-frames "${NORM_MAX_FRAMES}")
  fi
  uv run python scripts/compute_norm_stats.py "${norm_args[@]}"
else
  echo "[1/2] Skipping normalization statistics"
fi

echo "[2/2] Starting ${TRAIN_BACKEND:-jax} training"
case "${TRAIN_BACKEND:-jax}" in
  jax)
    uv run python scripts/train.py "${TRAIN_CONFIG_NAME}"
    ;;
  pytorch)
    gpu_count="${PYTORCH_GPU_COUNT:-1}"
    if (( gpu_count > 1 )); then
      uv run torchrun --standalone --nnodes=1 --nproc-per-node="${gpu_count}" \
        scripts/train_pytorch.py "${TRAIN_CONFIG_NAME}"
    else
      uv run python scripts/train_pytorch.py "${TRAIN_CONFIG_NAME}"
    fi
    ;;
  *)
    echo "ERROR: TRAIN_BACKEND must be jax or pytorch" >&2
    exit 2
    ;;
esac

构建流程

最终文件树如图所示

2026-07-21_18-56

  1. 构建镜像
docker compose -f docker/compose.train.yml --env-file .env build 

2026-07-22_09-14

  1. 查看构建的镜像
docker images | grep openpi-pi05
  1. 后台运行镜像自动运行entrypoint
(base) eft@eft-ThinkStation-P3-Tower:~/桌面/openpi$ docker compose -f docker/compose.train.yml --env-file .env up -d train
[+] Running 1/1
 ✔ Container openpi-pi05-train-1  Started