你的名字 commited on
Commit
8a45aed
·
1 Parent(s): d83eabc

Sync Space content from RoboMME_Interactive_Demo_cpu (CPU interactive demo).

Browse files
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04
2
 
3
  ENV DEBIAN_FRONTEND=noninteractive
4
 
@@ -11,10 +11,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
11
  git \
12
  ffmpeg \
13
  libgl1 \
14
- libglvnd-dev \
15
  libglib2.0-0 \
16
  libvulkan1 \
17
- vulkan-tools \
18
  && add-apt-repository ppa:deadsnakes/ppa \
19
  && apt-get update && apt-get install -y --no-install-recommends \
20
  python3.11 \
@@ -32,7 +31,6 @@ RUN useradd -m -u 1000 user
32
 
33
  ENV PYTHONUNBUFFERED=1 \
34
  PIP_NO_CACHE_DIR=1 \
35
- NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics \
36
  HOME=/home/user \
37
  PATH=/home/user/.local/bin:$PATH \
38
  OMP_NUM_THREADS=1 \
 
1
+ FROM ubuntu:22.04
2
 
3
  ENV DEBIAN_FRONTEND=noninteractive
4
 
 
11
  git \
12
  ffmpeg \
13
  libgl1 \
 
14
  libglib2.0-0 \
15
  libvulkan1 \
16
+ mesa-vulkan-drivers \
17
  && add-apt-repository ppa:deadsnakes/ppa \
18
  && apt-get update && apt-get install -y --no-install-recommends \
19
  python3.11 \
 
31
 
32
  ENV PYTHONUNBUFFERED=1 \
33
  PIP_NO_CACHE_DIR=1 \
 
34
  HOME=/home/user \
35
  PATH=/home/user/.local/bin:$PATH \
36
  OMP_NUM_THREADS=1 \
README.md CHANGED
@@ -2,19 +2,21 @@
2
  title: RoboMME Oracle Planner
3
  sdk: docker
4
  app_port: 7860
5
- arxiv: "2603.04639"
6
  ---
7
 
8
- [Arxiv Paper](https://arxiv.org/abs/2603.04639) | [HF Paper](https://huggingface.co/papers/2603.04639) | [Website](https://robomme.github.io/) | [Benchmark Code](https://github.com/RoboMME/robomme_benchmark) | [Policy Learning Code](https://github.com/RoboMME/robomme_policy_learning)
9
-
10
  This Space runs the RoboMME Gradio interface with the Docker SDK.
 
11
  The container entrypoint is defined by the root `Dockerfile` and launches:
 
12
  ```bash
13
  python3 gradio-web/main.py
14
  ```
 
15
  `app_file` is intentionally not set here because this is a Docker Space; the application entrypoint comes from Docker `CMD`, while `app_port: 7860` is the external port published by the Space.
16
- Local GPU Docker run:
 
 
17
  ```bash
18
- docker build -t robomme-gradio:gpu .
19
- docker run --rm --gpus all -p 7860:7860 robomme-gradio:gpu
20
- ```
 
2
  title: RoboMME Oracle Planner
3
  sdk: docker
4
  app_port: 7860
 
5
  ---
6
 
 
 
7
  This Space runs the RoboMME Gradio interface with the Docker SDK.
8
+
9
  The container entrypoint is defined by the root `Dockerfile` and launches:
10
+
11
  ```bash
12
  python3 gradio-web/main.py
13
  ```
14
+
15
  `app_file` is intentionally not set here because this is a Docker Space; the application entrypoint comes from Docker `CMD`, while `app_port: 7860` is the external port published by the Space.
16
+
17
+ Local CPU Docker run:
18
+
19
  ```bash
20
+ docker build -t robomme-gradio:cpu .
21
+ docker run --rm -p 7860:7860 robomme-gradio:cpu
22
+ ```
docker-entrypoint.sh CHANGED
@@ -1,50 +1,25 @@
1
  #!/bin/sh
2
  set -eu
3
 
4
- pick_vulkan_icd() {
5
- for candidate in \
6
- /etc/vulkan/icd.d/nvidia_icd.json \
7
- /etc/vulkan/icd.d/nvidia_icd.x86_64.json \
8
- /usr/share/vulkan/icd.d/nvidia_icd.json \
9
- /usr/share/vulkan/icd.d/nvidia_icd.x86_64.json
10
- do
11
- if [ -f "$candidate" ]; then
12
- printf '%s\n' "$candidate"
13
- return 0
14
- fi
15
- done
16
- return 1
17
- }
18
-
19
- run_diagnostic() {
20
- label="$1"
21
- shift
22
- echo "[entrypoint] $label"
23
- if "$@"; then
24
- return 0
25
- else
26
- status=$?
27
- fi
28
- echo "[entrypoint] $label failed with exit code $status"
29
- return 0
30
- }
31
-
32
  if [ -z "${OMP_NUM_THREADS:-}" ]; then
33
  export OMP_NUM_THREADS=1
34
  fi
35
 
36
- if [ -z "${VK_ICD_FILENAMES:-}" ]; then
37
- if detected_icd="$(pick_vulkan_icd)"; then
38
- export VK_ICD_FILENAMES="$detected_icd"
39
- echo "[entrypoint] Using Vulkan ICD: $VK_ICD_FILENAMES"
40
- else
41
- echo "[entrypoint] Vulkan ICD file not found under /etc or /usr/share"
42
- fi
43
- else
44
- echo "[entrypoint] Respecting preset VK_ICD_FILENAMES: $VK_ICD_FILENAMES"
45
  fi
46
 
 
47
  echo "[entrypoint] OMP_NUM_THREADS=$OMP_NUM_THREADS"
48
- run_diagnostic "nvidia-smi" nvidia-smi
49
- run_diagnostic "vulkaninfo --summary" vulkaninfo --summary
 
 
 
50
  exec "$@"
 
1
  #!/bin/sh
2
  set -eu
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  if [ -z "${OMP_NUM_THREADS:-}" ]; then
5
  export OMP_NUM_THREADS=1
6
  fi
7
 
8
+ export CUDA_VISIBLE_DEVICES=-1
9
+ export NVIDIA_VISIBLE_DEVICES=void
10
+ export ROBOMME_RENDER_BACKEND="${ROBOMME_RENDER_BACKEND:-pci:0}"
11
+ unset NVIDIA_DRIVER_CAPABILITIES
12
+ unset SAPIEN_RENDER_DEVICE
13
+ unset MUJOCO_GL
14
+ if [ -z "${VK_ICD_FILENAMES:-}" ] && [ -f /usr/share/vulkan/icd.d/lvp_icd.x86_64.json ]; then
15
+ export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
 
16
  fi
17
 
18
+ echo "[entrypoint] Starting RoboMME Gradio app in CPU-only mode"
19
  echo "[entrypoint] OMP_NUM_THREADS=$OMP_NUM_THREADS"
20
+ echo "[entrypoint] CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES"
21
+ echo "[entrypoint] NVIDIA_VISIBLE_DEVICES=$NVIDIA_VISIBLE_DEVICES"
22
+ echo "[entrypoint] ROBOMME_RENDER_BACKEND=$ROBOMME_RENDER_BACKEND"
23
+ echo "[entrypoint] SAPIEN_RENDER_DEVICE=${SAPIEN_RENDER_DEVICE:-<unset>}"
24
+ echo "[entrypoint] VK_ICD_FILENAMES=${VK_ICD_FILENAMES:-<unset>}"
25
  exec "$@"
gradio-web/main.py CHANGED
@@ -15,6 +15,18 @@ SRC_DIR = PROJECT_ROOT / "src"
15
  VIDEOS_DIR = APP_DIR / "videos"
16
  TEMP_DEMOS_DIR = PROJECT_ROOT / "temp_demos"
17
  CWD_TEMP_DEMOS_DIR = Path.cwd() / "temp_demos"
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
 
20
 
@@ -27,6 +39,36 @@ if str(SRC_DIR) not in sys.path:
27
  sys.path.insert(0, str(SRC_DIR))
28
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def setup_logging() -> logging.Logger:
31
  """Configure structured logging for Spaces runtime."""
32
  level_name = "DEBUG"
@@ -75,6 +117,7 @@ def log_runtime_graphics_env():
75
  "NVIDIA_DRIVER_CAPABILITIES",
76
  "VK_ICD_FILENAMES",
77
  "OMP_NUM_THREADS",
 
78
  "SAPIEN_RENDER_DEVICE",
79
  "MUJOCO_GL",
80
  ]
@@ -116,6 +159,7 @@ def build_allowed_paths():
116
 
117
 
118
  def main():
 
119
  from ui_layout import CSS, create_ui_blocks
120
 
121
  LOGGER.info("Starting Gradio real environment entrypoint: %s", __file__)
 
15
  VIDEOS_DIR = APP_DIR / "videos"
16
  TEMP_DEMOS_DIR = PROJECT_ROOT / "temp_demos"
17
  CWD_TEMP_DEMOS_DIR = Path.cwd() / "temp_demos"
18
+ DEFAULT_LLVMPipe_ICD = Path("/usr/share/vulkan/icd.d/lvp_icd.x86_64.json")
19
+ DEFAULT_CPU_RENDER_BACKEND = "pci:0"
20
+ CPU_ONLY_ENV_OVERRIDES = {
21
+ "CUDA_VISIBLE_DEVICES": "-1",
22
+ "NVIDIA_VISIBLE_DEVICES": "void",
23
+ "ROBOMME_RENDER_BACKEND": DEFAULT_CPU_RENDER_BACKEND,
24
+ }
25
+ CPU_ONLY_ENV_CLEAR_KEYS = (
26
+ "NVIDIA_DRIVER_CAPABILITIES",
27
+ "SAPIEN_RENDER_DEVICE",
28
+ "MUJOCO_GL",
29
+ )
30
 
31
 
32
 
 
39
  sys.path.insert(0, str(SRC_DIR))
40
 
41
 
42
+ def configure_cpu_only_runtime(logger: logging.Logger | None = None):
43
+ """Force CPU-only execution before importing project modules."""
44
+ cleared = {}
45
+ for key, value in CPU_ONLY_ENV_OVERRIDES.items():
46
+ os.environ[key] = value
47
+ for key in CPU_ONLY_ENV_CLEAR_KEYS:
48
+ previous = os.environ.pop(key, None)
49
+ if previous is not None:
50
+ cleared[key] = previous
51
+ vk_icd_status = "preserved"
52
+ if "VK_ICD_FILENAMES" not in os.environ:
53
+ if DEFAULT_LLVMPipe_ICD.exists():
54
+ os.environ["VK_ICD_FILENAMES"] = str(DEFAULT_LLVMPipe_ICD)
55
+ vk_icd_status = "auto-set"
56
+ else:
57
+ vk_icd_status = "unavailable"
58
+ if logger is not None:
59
+ logger.info(
60
+ "Configured CPU-only runtime overrides=%s cleared=%s vk_icd_status=%s vk_icd=%s",
61
+ CPU_ONLY_ENV_OVERRIDES,
62
+ cleared,
63
+ vk_icd_status,
64
+ os.environ.get("VK_ICD_FILENAMES"),
65
+ )
66
+ return cleared
67
+
68
+
69
+ configure_cpu_only_runtime()
70
+
71
+
72
  def setup_logging() -> logging.Logger:
73
  """Configure structured logging for Spaces runtime."""
74
  level_name = "DEBUG"
 
117
  "NVIDIA_DRIVER_CAPABILITIES",
118
  "VK_ICD_FILENAMES",
119
  "OMP_NUM_THREADS",
120
+ "ROBOMME_RENDER_BACKEND",
121
  "SAPIEN_RENDER_DEVICE",
122
  "MUJOCO_GL",
123
  ]
 
159
 
160
 
161
  def main():
162
+ configure_cpu_only_runtime(LOGGER)
163
  from ui_layout import CSS, create_ui_blocks
164
 
165
  LOGGER.info("Starting Gradio real environment entrypoint: %s", __file__)
gradio-web/minimal_maniskill_cpu_step.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal ManiSkill CPU sim/render sanity check.
2
+
3
+ This uses an official ManiSkill environment instead of RoboMME wrappers so the
4
+ execution path stays as small as possible.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import os
11
+ import sys
12
+ import warnings
13
+ from pathlib import Path
14
+
15
+ import gymnasium as gym
16
+ import mani_skill.envs # noqa: F401 - registers ManiSkill environments
17
+
18
+
19
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
20
+ SRC_DIR = PROJECT_ROOT / "src"
21
+
22
+ if str(PROJECT_ROOT) not in sys.path:
23
+ sys.path.insert(0, str(PROJECT_ROOT))
24
+ if str(SRC_DIR) not in sys.path:
25
+ sys.path.insert(0, str(SRC_DIR))
26
+
27
+ import robomme # noqa: F401,E402 - applies ManiSkill PCI render-backend patch
28
+
29
+
30
+ os.environ.setdefault("ROBOMME_RENDER_BACKEND", "pci:0")
31
+
32
+
33
+ warnings.filterwarnings(
34
+ "ignore",
35
+ message=r"CUDA reports that you have .* fork_rng",
36
+ category=UserWarning,
37
+ )
38
+
39
+
40
+ def main() -> None:
41
+ parser = argparse.ArgumentParser()
42
+ parser.add_argument("--env-id", default="PickCube-v1")
43
+ parser.add_argument("--seed", type=int, default=0)
44
+ args = parser.parse_args()
45
+
46
+ env = gym.make(
47
+ args.env_id,
48
+ obs_mode="rgbd",
49
+ control_mode="pd_joint_pos",
50
+ render_mode="rgb_array",
51
+ sim_backend="physx_cpu",
52
+ render_backend=os.environ["ROBOMME_RENDER_BACKEND"],
53
+ )
54
+
55
+ try:
56
+ obs, info = env.reset(seed=args.seed)
57
+ print(f"reset ok: env_id={args.env_id}")
58
+ print(f"obs keys: {list(obs.keys())}")
59
+ print(f"info keys: {list(info.keys())}")
60
+
61
+ action = env.action_space.sample()
62
+ obs, reward, terminated, truncated, info = env.step(action)
63
+
64
+ rgb = obs["sensor_data"]["base_camera"]["rgb"]
65
+ depth = obs["sensor_data"]["base_camera"]["depth"]
66
+
67
+ print("step ok")
68
+ print(f"reward={reward}")
69
+ print(f"terminated={terminated}")
70
+ print(f"truncated={truncated}")
71
+ print(f"rgb shape={tuple(rgb.shape)} dtype={rgb.dtype}")
72
+ print(f"depth shape={tuple(depth.shape)} dtype={depth.dtype}")
73
+ print(f"info keys after step: {list(info.keys())}")
74
+ finally:
75
+ env.close()
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()
gradio-web/minimal_robomme_env_cpu_step.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal RoboMME custom-env CPU sanity check.
2
+
3
+ This bypasses BenchmarkEnvBuilder/make_env_for_episode and instantiates a
4
+ RoboMME custom environment class directly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import faulthandler
11
+ import os
12
+ import sys
13
+ import warnings
14
+ from pathlib import Path
15
+
16
+
17
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
18
+ SRC_DIR = PROJECT_ROOT / "src"
19
+
20
+ if str(PROJECT_ROOT) not in sys.path:
21
+ sys.path.insert(0, str(PROJECT_ROOT))
22
+ if str(SRC_DIR) not in sys.path:
23
+ sys.path.insert(0, str(SRC_DIR))
24
+
25
+
26
+ def configure_cpu_only_runtime() -> None:
27
+ os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
28
+ os.environ["NVIDIA_VISIBLE_DEVICES"] = "void"
29
+ os.environ.setdefault("ROBOMME_RENDER_BACKEND", "pci:0")
30
+ os.environ.pop("NVIDIA_DRIVER_CAPABILITIES", None)
31
+ os.environ.pop("SAPIEN_RENDER_DEVICE", None)
32
+ os.environ.pop("MUJOCO_GL", None)
33
+ if "VK_ICD_FILENAMES" not in os.environ:
34
+ lvp_icd = Path("/usr/share/vulkan/icd.d/lvp_icd.x86_64.json")
35
+ if lvp_icd.exists():
36
+ os.environ["VK_ICD_FILENAMES"] = str(lvp_icd)
37
+
38
+
39
+ configure_cpu_only_runtime()
40
+ faulthandler.enable(all_threads=True)
41
+
42
+ warnings.filterwarnings(
43
+ "ignore",
44
+ message=r"CUDA reports that you have .* fork_rng",
45
+ category=UserWarning,
46
+ )
47
+
48
+
49
+ def main() -> None:
50
+ parser = argparse.ArgumentParser()
51
+ parser.add_argument("--env-class", default="StopCube")
52
+ parser.add_argument("--seed", type=int, default=0)
53
+ args = parser.parse_args()
54
+
55
+ import robomme.robomme_env as robomme_env
56
+ from robomme.env_record_wrapper.episode_config_resolver import resolve_render_backend
57
+
58
+ env_cls = getattr(robomme_env, args.env_class)
59
+ env = None
60
+
61
+ print(f"instantiate start: env_class={args.env_class}", flush=True)
62
+ try:
63
+ env = env_cls(
64
+ obs_mode="rgb+depth+segmentation",
65
+ control_mode="pd_joint_pos",
66
+ render_mode="rgb_array",
67
+ reward_mode="dense",
68
+ sim_backend="physx_cpu",
69
+ render_backend=resolve_render_backend(),
70
+ seed=args.seed,
71
+ )
72
+ print("instantiate ok", flush=True)
73
+
74
+ obs, info = env.reset(seed=args.seed)
75
+ print(f"reset ok: obs keys={list(obs.keys())}", flush=True)
76
+ print(f"reset info keys={list(info.keys())}", flush=True)
77
+
78
+ action = env.action_space.sample()
79
+ obs, reward, terminated, truncated, info = env.step(action)
80
+ rgb = obs["sensor_data"]["base_camera"]["rgb"]
81
+ depth = obs["sensor_data"]["base_camera"]["depth"]
82
+
83
+ print("step ok", flush=True)
84
+ print(f"reward={reward}", flush=True)
85
+ print(f"terminated={terminated}", flush=True)
86
+ print(f"truncated={truncated}", flush=True)
87
+ print(f"rgb shape={tuple(rgb.shape)} dtype={rgb.dtype}", flush=True)
88
+ print(f"depth shape={tuple(depth.shape)} dtype={depth.dtype}", flush=True)
89
+ print(f"step info keys={list(info.keys())}", flush=True)
90
+ finally:
91
+ if env is not None:
92
+ env.close()
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()
gradio-web/test/test_episode_builder_cpu_backend.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ import types
5
+
6
+
7
+ class _FakeEnv:
8
+ pass
9
+
10
+
11
+ class _FakeDemonstrationWrapper:
12
+ last_env = None
13
+ last_kwargs = None
14
+
15
+ def __init__(self, env, **kwargs):
16
+ type(self).last_env = env
17
+ type(self).last_kwargs = kwargs
18
+ self.env = env
19
+
20
+
21
+ class _FakeFailAwareWrapper:
22
+ last_env = None
23
+
24
+ def __init__(self, env):
25
+ type(self).last_env = env
26
+ self.env = env
27
+
28
+
29
+ def test_builder_make_env_for_episode_forces_cpu_backends(monkeypatch, reload_module):
30
+ resolver = reload_module("robomme.env_record_wrapper.episode_config_resolver")
31
+ captured = {}
32
+
33
+ monkeypatch.setitem(
34
+ sys.modules,
35
+ "robomme.env_record_wrapper.DemonstrationWrapper",
36
+ types.SimpleNamespace(DemonstrationWrapper=_FakeDemonstrationWrapper),
37
+ )
38
+ monkeypatch.setitem(
39
+ sys.modules,
40
+ "robomme.env_record_wrapper.FailAwareWrapper",
41
+ types.SimpleNamespace(FailAwareWrapper=_FakeFailAwareWrapper),
42
+ )
43
+
44
+ def fake_make(env_id, **kwargs):
45
+ captured["env_id"] = env_id
46
+ captured["kwargs"] = kwargs
47
+ return _FakeEnv()
48
+
49
+ monkeypatch.setattr(resolver.gym, "make", fake_make)
50
+
51
+ builder = resolver.BenchmarkEnvBuilder(
52
+ env_id="BinFill",
53
+ dataset="train",
54
+ action_space="joint_angle",
55
+ gui_render=False,
56
+ )
57
+ monkeypatch.setattr(builder, "resolve_episode", lambda episode_idx: (123, "hard"))
58
+
59
+ env = builder.make_env_for_episode(7)
60
+
61
+ assert captured["env_id"] == "BinFill"
62
+ assert captured["kwargs"]["obs_mode"] == "rgb+depth+segmentation"
63
+ assert captured["kwargs"]["control_mode"] == "pd_joint_pos"
64
+ assert captured["kwargs"]["render_mode"] == "rgb_array"
65
+ assert captured["kwargs"]["reward_mode"] == "dense"
66
+ assert captured["kwargs"]["sim_backend"] == "physx_cpu"
67
+ assert captured["kwargs"]["render_backend"] == "pci:0"
68
+ assert captured["kwargs"]["seed"] == 123
69
+ assert captured["kwargs"]["difficulty"] == "hard"
70
+ assert _FakeDemonstrationWrapper.last_kwargs["gui_render"] is False
71
+ assert _FakeFailAwareWrapper.last_env is env.env
72
+
73
+
74
+ def test_builder_make_env_for_episode_honors_render_backend_override(monkeypatch, reload_module):
75
+ resolver = reload_module("robomme.env_record_wrapper.episode_config_resolver")
76
+ captured = {}
77
+
78
+ monkeypatch.setitem(
79
+ sys.modules,
80
+ "robomme.env_record_wrapper.DemonstrationWrapper",
81
+ types.SimpleNamespace(DemonstrationWrapper=_FakeDemonstrationWrapper),
82
+ )
83
+ monkeypatch.setitem(
84
+ sys.modules,
85
+ "robomme.env_record_wrapper.FailAwareWrapper",
86
+ types.SimpleNamespace(FailAwareWrapper=_FakeFailAwareWrapper),
87
+ )
88
+
89
+ def fake_make(env_id, **kwargs):
90
+ captured["env_id"] = env_id
91
+ captured["kwargs"] = kwargs
92
+ return _FakeEnv()
93
+
94
+ monkeypatch.setattr(resolver.gym, "make", fake_make)
95
+ monkeypatch.setenv("ROBOMME_RENDER_BACKEND", "pci:42")
96
+
97
+ builder = resolver.BenchmarkEnvBuilder(
98
+ env_id="BinFill",
99
+ dataset="train",
100
+ action_space="joint_angle",
101
+ gui_render=False,
102
+ )
103
+ monkeypatch.setattr(builder, "resolve_episode", lambda episode_idx: (None, None))
104
+
105
+ builder.make_env_for_episode(1)
106
+
107
+ assert captured["kwargs"]["render_backend"] == "pci:42"
108
+
109
+
110
+ def test_robomme_patches_maniskill_to_preserve_pci_render_backend(reload_module):
111
+ robomme = reload_module("robomme")
112
+ assert robomme is not None
113
+
114
+ from mani_skill.envs.utils.system import backend as ms_backend
115
+
116
+ backend_name, device_id = ms_backend.parse_backend_device_id("pci:0000:00:00.0")
117
+
118
+ assert backend_name == "pci:0000:00:00.0"
119
+ assert device_id is None
gradio-web/test/test_main_launch_config.py CHANGED
@@ -1,7 +1,13 @@
1
  from __future__ import annotations
2
 
 
3
  import sys
4
  import types
 
 
 
 
 
5
 
6
 
7
  class _FakeDemo:
@@ -15,7 +21,14 @@ class _FakeDemo:
15
  return None
16
 
17
 
18
- def test_main_launch_passes_ui_css(monkeypatch, reload_module):
 
 
 
 
 
 
 
19
  main = reload_module("main")
20
  fake_demo = _FakeDemo()
21
  fake_ui_layout = types.SimpleNamespace(
@@ -25,6 +38,12 @@ def test_main_launch_passes_ui_css(monkeypatch, reload_module):
25
 
26
  monkeypatch.setitem(sys.modules, "ui_layout", fake_ui_layout)
27
  monkeypatch.setenv("PORT", "7861")
 
 
 
 
 
 
28
 
29
  main.main()
30
 
@@ -34,3 +53,85 @@ def test_main_launch_passes_ui_css(monkeypatch, reload_module):
34
  assert fake_demo.launch_kwargs["theme"] == fake_demo.theme
35
  assert fake_demo.launch_kwargs["css"] == fake_ui_layout.CSS
36
  assert fake_demo.launch_kwargs["head"] == fake_demo.head
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
+ import os
4
  import sys
5
  import types
6
+ from pathlib import Path
7
+
8
+
9
+ DEFAULT_LLVMPipe_ICD = "/usr/share/vulkan/icd.d/lvp_icd.x86_64.json"
10
+ DEFAULT_CPU_RENDER_BACKEND = "pci:0"
11
 
12
 
13
  class _FakeDemo:
 
21
  return None
22
 
23
 
24
+ def test_main_launch_passes_ui_css_and_forces_cpu_runtime(monkeypatch, reload_module):
25
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "0")
26
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
27
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda")
28
+ monkeypatch.setenv("NVIDIA_DRIVER_CAPABILITIES", "compute,utility,graphics")
29
+ monkeypatch.setenv("VK_ICD_FILENAMES", "/tmp/nvidia_icd.json")
30
+ monkeypatch.setenv("MUJOCO_GL", "egl")
31
+
32
  main = reload_module("main")
33
  fake_demo = _FakeDemo()
34
  fake_ui_layout = types.SimpleNamespace(
 
38
 
39
  monkeypatch.setitem(sys.modules, "ui_layout", fake_ui_layout)
40
  monkeypatch.setenv("PORT", "7861")
41
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "2")
42
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
43
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda")
44
+ monkeypatch.setenv("NVIDIA_DRIVER_CAPABILITIES", "graphics")
45
+ monkeypatch.setenv("VK_ICD_FILENAMES", "/tmp/another_nvidia_icd.json")
46
+ monkeypatch.setenv("MUJOCO_GL", "egl")
47
 
48
  main.main()
49
 
 
53
  assert fake_demo.launch_kwargs["theme"] == fake_demo.theme
54
  assert fake_demo.launch_kwargs["css"] == fake_ui_layout.CSS
55
  assert fake_demo.launch_kwargs["head"] == fake_demo.head
56
+ assert os.environ["CUDA_VISIBLE_DEVICES"] == "-1"
57
+ assert os.environ["NVIDIA_VISIBLE_DEVICES"] == "void"
58
+ assert os.environ["ROBOMME_RENDER_BACKEND"] == DEFAULT_CPU_RENDER_BACKEND
59
+ assert os.environ["VK_ICD_FILENAMES"] == "/tmp/another_nvidia_icd.json"
60
+ assert "NVIDIA_DRIVER_CAPABILITIES" not in os.environ
61
+ assert "SAPIEN_RENDER_DEVICE" not in os.environ
62
+ assert "MUJOCO_GL" not in os.environ
63
+
64
+
65
+ def test_configure_cpu_only_runtime_autosets_llvmpipe_icd(monkeypatch, reload_module):
66
+ original_exists = Path.exists
67
+
68
+ def fake_exists(self):
69
+ if str(self) == DEFAULT_LLVMPipe_ICD:
70
+ return True
71
+ return original_exists(self)
72
+
73
+ monkeypatch.setattr(Path, "exists", fake_exists)
74
+ monkeypatch.delenv("VK_ICD_FILENAMES", raising=False)
75
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "3")
76
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
77
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda")
78
+ monkeypatch.setenv("NVIDIA_DRIVER_CAPABILITIES", "graphics")
79
+ monkeypatch.setenv("MUJOCO_GL", "egl")
80
+
81
+ main = reload_module("main")
82
+ monkeypatch.delenv("VK_ICD_FILENAMES", raising=False)
83
+
84
+ main.configure_cpu_only_runtime()
85
+
86
+ assert os.environ["CUDA_VISIBLE_DEVICES"] == "-1"
87
+ assert os.environ["NVIDIA_VISIBLE_DEVICES"] == "void"
88
+ assert os.environ["ROBOMME_RENDER_BACKEND"] == DEFAULT_CPU_RENDER_BACKEND
89
+ assert os.environ["VK_ICD_FILENAMES"] == DEFAULT_LLVMPipe_ICD
90
+ assert "NVIDIA_DRIVER_CAPABILITIES" not in os.environ
91
+ assert "SAPIEN_RENDER_DEVICE" not in os.environ
92
+ assert "MUJOCO_GL" not in os.environ
93
+
94
+
95
+ def test_configure_cpu_only_runtime_preserves_existing_vk_icd(monkeypatch, reload_module):
96
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "4")
97
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
98
+ monkeypatch.setenv("ROBOMME_RENDER_BACKEND", "pci:9")
99
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda")
100
+ monkeypatch.setenv("NVIDIA_DRIVER_CAPABILITIES", "graphics")
101
+ monkeypatch.setenv("VK_ICD_FILENAMES", "/tmp/custom_icd.json")
102
+ monkeypatch.setenv("MUJOCO_GL", "egl")
103
+
104
+ main = reload_module("main")
105
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "5")
106
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
107
+ monkeypatch.setenv("ROBOMME_RENDER_BACKEND", "pci:7")
108
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda")
109
+ monkeypatch.setenv("NVIDIA_DRIVER_CAPABILITIES", "graphics")
110
+ monkeypatch.setenv("VK_ICD_FILENAMES", "/tmp/preserved_icd.json")
111
+ monkeypatch.setenv("MUJOCO_GL", "egl")
112
+
113
+ main.configure_cpu_only_runtime()
114
+
115
+ assert os.environ["CUDA_VISIBLE_DEVICES"] == "-1"
116
+ assert os.environ["NVIDIA_VISIBLE_DEVICES"] == "void"
117
+ assert os.environ["ROBOMME_RENDER_BACKEND"] == "pci:0"
118
+ assert os.environ["VK_ICD_FILENAMES"] == "/tmp/preserved_icd.json"
119
+ assert "NVIDIA_DRIVER_CAPABILITIES" not in os.environ
120
+ assert "SAPIEN_RENDER_DEVICE" not in os.environ
121
+ assert "MUJOCO_GL" not in os.environ
122
+
123
+
124
+ def test_configure_cpu_only_runtime_clears_stale_sapien_render_device(monkeypatch, reload_module):
125
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cpu")
126
+ monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "7")
127
+ monkeypatch.setenv("NVIDIA_VISIBLE_DEVICES", "all")
128
+
129
+ main = reload_module("main")
130
+ monkeypatch.setenv("SAPIEN_RENDER_DEVICE", "cuda:0")
131
+
132
+ main.configure_cpu_only_runtime()
133
+
134
+ assert os.environ["CUDA_VISIBLE_DEVICES"] == "-1"
135
+ assert os.environ["NVIDIA_VISIBLE_DEVICES"] == "void"
136
+ assert os.environ["ROBOMME_RENDER_BACKEND"] == DEFAULT_CPU_RENDER_BACKEND
137
+ assert "SAPIEN_RENDER_DEVICE" not in os.environ
human_readme.md CHANGED
@@ -15,7 +15,7 @@ uv sync
15
  uv pip install -e .
16
  ```
17
 
18
- ## 🐳 Gradio Docker Deployment (HF Space + GPU)
19
 
20
  This repository also supports Docker deployment for the Gradio app entrypoint:
21
 
@@ -26,23 +26,23 @@ python3 gradio-web/main.py
26
  Build image:
27
 
28
  ```bash
29
- docker build -t robomme-gradio:gpu .
30
  ```
31
 
32
- Run container (GPU + Vulkan for ManiSkill/SAPIEN):
33
 
34
  ```bash
35
- docker run --rm --gpus all -p 7860:7860 robomme-gradio:gpu
36
  ```
37
 
38
- The image sets `NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics` so the NVIDIA container runtime exposes Vulkan/graphics driver files inside the container. Without graphics capability, ManiSkill/SAPIEN may fail with `vk::createInstanceUnique: ErrorIncompatibleDriver`.
39
 
40
  Optional metadata override:
41
 
42
  ```bash
43
- docker run --rm --gpus all -p 7860:7860 \
44
  -e ROBOMME_METADATA_ROOT=/home/user/app/src/robomme/env_metadata/train \
45
- robomme-gradio:gpu
46
  ```
47
 
48
  Notes:
@@ -148,13 +148,18 @@ Want to add your model? Download the [dataset](https://huggingface.co/datasets/Y
148
 
149
  A1: Use a physical display or set up a virtual display for GUI rendering (e.g. install a VNC server and set the `DISPLAY` variable correctly).
150
 
151
- **Q2: Failure related to Vulkan installation.**
152
 
153
- A2: ManiSkill/SAPIEN requires both Vulkan userspace packages inside the container and NVIDIA graphics capability exposed by the container runtime. This image installs `libvulkan1`, `vulkan-tools`, and `libglvnd-dev`, and sets `NVIDIA_DRIVER_CAPABILITIES=compute,utility,graphics`. If it still does not work, first verify the host machine itself supports Vulkan (`vulkaninfo` on the host), then switch to CPU rendering:
154
 
155
  ```python
156
- os.environ['SAPIEN_RENDER_DEVICE'] = 'cpu'
157
- os.environ['MUJOCO_GL'] = 'osmesa'
 
 
 
 
 
158
  ```
159
 
160
 
 
15
  uv pip install -e .
16
  ```
17
 
18
+ ## 🐳 Gradio Docker Deployment (HF Space CPU-only)
19
 
20
  This repository also supports Docker deployment for the Gradio app entrypoint:
21
 
 
26
  Build image:
27
 
28
  ```bash
29
+ docker build -t robomme-gradio:cpu .
30
  ```
31
 
32
+ Run container:
33
 
34
  ```bash
35
+ docker run --rm -p 7860:7860 robomme-gradio:cpu
36
  ```
37
 
38
+ The container forces CPU-only ManiSkill/SAPIEN backends and does not require NVIDIA runtime or `--gpus all`, which keeps it aligned with Hugging Face Docker Spaces CPU deployments.
39
 
40
  Optional metadata override:
41
 
42
  ```bash
43
+ docker run --rm -p 7860:7860 \
44
  -e ROBOMME_METADATA_ROOT=/home/user/app/src/robomme/env_metadata/train \
45
+ robomme-gradio:cpu
46
  ```
47
 
48
  Notes:
 
148
 
149
  A1: Use a physical display or set up a virtual display for GUI rendering (e.g. install a VNC server and set the `DISPLAY` variable correctly).
150
 
151
+ **Q2: Failure related to ManiSkill/SAPIEN rendering initialization.**
152
 
153
+ A2: This Docker image is configured for CPU-only execution and should not rely on NVIDIA runtime settings. If rendering still fails, first check that no external environment variables are forcing GPU paths, then keep the container on the CPU-only defaults:
154
 
155
  ```python
156
+ os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
157
+ os.environ['NVIDIA_VISIBLE_DEVICES'] = 'void'
158
+ os.environ.setdefault('ROBOMME_RENDER_BACKEND', 'pci:0') # llvmpipe software Vulkan on CPU
159
+ os.environ.pop('SAPIEN_RENDER_DEVICE', None)
160
+ os.environ.pop('NVIDIA_DRIVER_CAPABILITIES', None)
161
+ os.environ.pop('MUJOCO_GL', None)
162
+ os.environ.setdefault('VK_ICD_FILENAMES', '/usr/share/vulkan/icd.d/lvp_icd.x86_64.json')
163
  ```
164
 
165
 
sapien_offscreen.png ADDED
src/robomme/__init__.py CHANGED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RoboMME package initialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ def _patch_maniskill_pci_render_backend() -> None:
7
+ """Allow ManiSkill to pass PCI-style Vulkan device strings through intact."""
8
+ try:
9
+ from mani_skill.envs.utils.system import backend as ms_backend
10
+ except Exception:
11
+ return
12
+
13
+ if getattr(ms_backend, "_robomme_pci_backend_patch", False):
14
+ return
15
+
16
+ original = ms_backend.parse_backend_device_id
17
+
18
+ def patched_parse_backend_device_id(backend: str):
19
+ if isinstance(backend, str) and backend.startswith("pci:"):
20
+ return backend, None
21
+ return original(backend)
22
+
23
+ ms_backend.parse_backend_device_id = patched_parse_backend_device_id
24
+ ms_backend._robomme_pci_backend_patch = True
25
+
26
+
27
+ _patch_maniskill_pci_render_backend()
28
+
src/robomme/env_record_wrapper/episode_config_resolver.py CHANGED
@@ -14,6 +14,7 @@ DATASET_ROOT = Path(__file__).resolve().parents[1] / "env_metadata"
14
 
15
  _ALLOWED_DATASETS = {"train", "test"}
16
  _ALLOWED_ACTION_SPACES = {"joint_angle", "ee_pose", "waypoint", "multi_choice"}
 
17
  _DEFAULT_TASK_LIST = [
18
  "PickXtimes",
19
  "StopCube",
@@ -87,6 +88,16 @@ def get_episode_metadata(
87
  return metadata_index.get((task, episode))
88
 
89
 
 
 
 
 
 
 
 
 
 
 
90
  class BenchmarkEnvBuilder:
91
  """
92
  Episode environment builder.
@@ -195,6 +206,8 @@ class BenchmarkEnvBuilder:
195
  control_mode="pd_joint_pos",
196
  render_mode=self.render_mode,
197
  reward_mode="dense",
 
 
198
  )
199
  if seed is not None:
200
  env_kwargs["seed"] = seed
 
14
 
15
  _ALLOWED_DATASETS = {"train", "test"}
16
  _ALLOWED_ACTION_SPACES = {"joint_angle", "ee_pose", "waypoint", "multi_choice"}
17
+ _DEFAULT_CPU_RENDER_BACKEND = "pci:0"
18
  _DEFAULT_TASK_LIST = [
19
  "PickXtimes",
20
  "StopCube",
 
88
  return metadata_index.get((task, episode))
89
 
90
 
91
+ def resolve_render_backend(default: str = _DEFAULT_CPU_RENDER_BACKEND) -> str:
92
+ """Resolve the render backend for CPU-only execution.
93
+
94
+ Docker CPU mode uses llvmpipe, which SAPIEN exposes as a Vulkan PCI device
95
+ rather than the string "cpu".
96
+ """
97
+ value = str(os.environ.get("ROBOMME_RENDER_BACKEND", default)).strip()
98
+ return value or default
99
+
100
+
101
  class BenchmarkEnvBuilder:
102
  """
103
  Episode environment builder.
 
206
  control_mode="pd_joint_pos",
207
  render_mode=self.render_mode,
208
  reward_mode="dense",
209
+ sim_backend="physx_cpu",
210
+ render_backend=resolve_render_backend(),
211
  )
212
  if seed is not None:
213
  env_kwargs["seed"] = seed