From e52e7e644a5e9930f8bfc52f7c6ebff30da40e86 Mon Sep 17 00:00:00 2001 From: Pepijn Kooijmans Date: Mon, 23 Mar 2026 18:31:57 +0100 Subject: [PATCH] fix(docker): add libero_plus install workaround to generic Dockerfile.benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generic Dockerfile.benchmark was using a plain `uv pip install ".[libero_plus]"` which silently fails to make `libero` importable due to an upstream LIBERO-plus packaging bug. Port the dedicated clone + .pth workaround from Dockerfile.eval-libero-plus so `docker build --build-arg BENCHMARK=libero_plus` produces working containers. Also fix eval worker using nonexistent `parser.parse()` — use `draccus.parse()`. Made-with: Cursor --- docker/Dockerfile.benchmark | 28 ++++++++++++++++-- src/lerobot/scripts/lerobot_eval_worker.py | 4 +-- tests/scripts/test_eval_worker.py | 34 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 tests/scripts/test_eval_worker.py diff --git a/docker/Dockerfile.benchmark b/docker/Dockerfile.benchmark index 63e2e56d9..d1feb356d 100644 --- a/docker/Dockerfile.benchmark +++ b/docker/Dockerfile.benchmark @@ -39,6 +39,7 @@ ARG BENCHMARK=libero ENV DEBIAN_FRONTEND=noninteractive \ MUJOCO_GL=egl \ PATH=/lerobot/.venv/bin:$PATH \ + CMAKE_POLICY_VERSION_MINIMUM=3.5 \ CUDA_VISIBLE_DEVICES=0 \ DEVICE=cuda \ BENCHMARK=${BENCHMARK} @@ -82,7 +83,7 @@ ENV HOME=/home/user_lerobot \ TORCH_HOME=/home/user_lerobot/.cache/torch \ TRITON_CACHE_DIR=/home/user_lerobot/.cache/triton -RUN uv venv --python python${PYTHON_VERSION} +RUN uv venv --seed --python python${PYTHON_VERSION} # Copy only the dependency manifests first so Docker can cache this layer # independently of source-code changes. @@ -96,8 +97,29 @@ RUN if [ "$UNBOUND_DEPS" = "true" ]; then \ fi # Install lerobot core + the selected benchmark extra. -# Git-based deps (libero_plus, robomme) require network access at build time. -RUN uv pip install --no-cache ".[${BENCHMARK}]" +# LIBERO-plus needs a dedicated install path because the upstream package is +# import-broken when installed via the extras chain alone. +RUN case "${BENCHMARK}" in \ + libero_plus) \ + PATH=/usr/bin:/bin:/lerobot/.venv/bin:$PATH /lerobot/.venv/bin/python -m pip install --no-cache-dir \ + "hf-libero>=0.1.3,<0.2.0" \ + "hf-egl-probe>=1.0.1" \ + "transformers>=5.3.0,<6.0.0" \ + "scipy>=1.14.0,<2.0.0" \ + "bddl>=1.0.1,<2.0.0" \ + "future" \ + "easydict>=1.9" \ + "wand" \ + "scikit-image>=0.20.0" \ + "gym>=0.25.0,<0.27.0" \ + && git clone --depth 1 https://github.com/sylvestf/LIBERO-plus.git /tmp/LIBERO-plus \ + && PATH=/usr/bin:/bin:/lerobot/.venv/bin:$PATH /lerobot/.venv/bin/python -m pip install --no-cache-dir --no-deps /tmp/LIBERO-plus \ + && /lerobot/.venv/bin/python -c "import pathlib, site; pathlib.Path(site.getsitepackages()[0], 'libero_plus_repo.pth').write_text('/tmp/LIBERO-plus\n')" \ + && /lerobot/.venv/bin/python -m pip install --no-cache-dir . \ + && /lerobot/.venv/bin/python -c "import libero, robosuite, bddl" ;; \ + *) \ + uv pip install --no-cache ".[${BENCHMARK}]" ;; \ + esac # Triton requires its ptxas binary to be executable (NVIDIA-specific). RUN if [ -f /lerobot/.venv/lib/python${PYTHON_VERSION}/site-packages/triton/backends/nvidia/bin/ptxas ]; then \ diff --git a/src/lerobot/scripts/lerobot_eval_worker.py b/src/lerobot/scripts/lerobot_eval_worker.py index a8998b7ef..321795237 100644 --- a/src/lerobot/scripts/lerobot_eval_worker.py +++ b/src/lerobot/scripts/lerobot_eval_worker.py @@ -39,10 +39,10 @@ import urllib.request from dataclasses import dataclass, field from pathlib import Path +import draccus import numpy as np from lerobot import envs # noqa: F401 — registers all env subclasses -from lerobot.configs import parser from lerobot.envs.configs import EnvConfig from lerobot.envs.factory import make_env from lerobot.envs.utils import add_envs_task, preprocess_observation @@ -184,7 +184,7 @@ def worker_main(cfg: EvalWorkerConfig) -> None: def main() -> None: init_logging() - cfg = parser.parse(EvalWorkerConfig) + cfg = draccus.parse(config_class=EvalWorkerConfig) worker_main(cfg) diff --git a/tests/scripts/test_eval_worker.py b/tests/scripts/test_eval_worker.py new file mode 100644 index 000000000..518f29a6f --- /dev/null +++ b/tests/scripts/test_eval_worker.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# Copyright 2025 The HuggingFace Inc. team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +from lerobot.scripts import lerobot_eval_worker + + +def test_worker_main_writes_results(monkeypatch, tmp_path: Path): + cfg = lerobot_eval_worker.EvalWorkerConfig( + env="pusht", # type: ignore[arg-type] + instance_id=3, + output_path=tmp_path / "worker.json", + ) + + monkeypatch.setattr(lerobot_eval_worker, "run_worker", lambda _cfg: {"per_task": []}) + + lerobot_eval_worker.worker_main(cfg) + + assert cfg.output_path.exists() + assert cfg.output_path.read_text().strip() == '{\n "per_task": []\n}'