问题
多 GPU data-parallel 训练时,isaacgym / isaacsim / genesis 三个 GPU 后端的 env(collector / worker 进程)全部落在可见设备 0,rank 到 GPU 的分配机制(training.devices → resolve_dp_rank_device)只作用于 learner / replay buffer,从不传给这三个后端的 env。对比:mjwarp 通过 bind_backend_process_device(src/unilab/base/process_device.py)强制 collector 与 learner 同卡,行为正确。
各后端证据
isaacgym(Python 3.8 子进程 worker + shm 协议)
- owner YAML 写死
env.isaacgym_device_id: 0:src/unilab/conf/ppo/task/g1_walk_flat/isaacgym.yaml:31、src/unilab/conf/sac/task/g1_walk_flat/isaacgym.yaml:29;EnvCfg.isaacgym_device_id 默认 None(src/unilab/base/base.py:57)。
- 默认值链路:
subprocess_ipc/backend.py:302 device_id = 0 if device_id is None → INIT payload 携带 device_id(:408)→ worker gym.create_sim(device_id, graphics_device_id, ...)(isaacgym/worker.py:96-120)。
- DP 下
env_cfg_override 只追加 cpu_ids(uni_rl/offpolicy/double_buffer_runner.py:528-537),从不重写 isaacgym_device_id;worker 子进程只继承父进程 env,无人设 CUDA_VISIBLE_DEVICES。
isaacsim(Kit 子进程 worker)
- 链路与 isaacgym 同构:owner YAML 写死
env.isaacsim_device_id: 0(src/unilab/conf/ppo/task/g1_walk_flat/isaacsim.yaml:27 等)→ 同一 payload 字段 → worker self.device = f"cuda:{device_id}"(isaacsim/worker.py:152-158),传给 AppLauncher({"device": ..., "multi_gpu": False}) 与 SimulationCfg。
- 注:
isaacsim/worker.py:115 的 self.device = "cuda:0" 只是 _WorkerContext.__init__ 占位初值,init_sim 第一行即被 payload 覆盖,不是根因;根因是 payload 的 device_id 永远来自写死 0 的 YAML(或 None→0 默认)。multi_gpu: False 表明 worker 设计上单卡单进程,需要外层按 rank 分配,外层没做。
genesis(进程内后端)
- 最糟:
GenesisBackend.__init__(unisim/backend/genesis/backend.py:78-94)不接受 device 入参,UniLab 侧 EnvCfg / genesis.yaml 也没有任何 device 字段。
gs.init(backend=gs.gpu) 会强制 torch.set_default_device("cuda:0")(genesis/materialization.py:116 注释明确记录);backend 跟随 torch.cuda.current_device()(genesis/backend.py:124-127),而 UniLab/uni_rl 全链路只有 off-policy learner 在 dp_sync.py:90 调 torch.cuda.set_device,collector / PPO worker 无人 set。
resolve_backend_process_device 只对 mjwarp 返回非 None,genesis collector 无绑定;one-session-per-process 守卫意味着进程内无二次选择机会。
影响
任何 training.devices=[0,1,...] 的多卡训练,这三个后端的所有 rank env 物理仿真都挤在 devices[0] 一张卡上:该卡显存/算力成为瓶颈,其余卡只跑 learner;且用户无报错、无告警,表象是多卡 scaling 无效。
修复方向(供 maintainer 判断)
- isaacgym / isaacsim:构建 env 时把 rank device index 写进
env_cfg_override["isaacgym_device_id"/"isaacsim_device_id"]。注意 off-policy 路径 rank device 是 host-global index,PPO torchrun 路径是 LOCAL_RANK + 全量 CUDA_VISIBLE_DEVICES,两者 index 语义不同,需要各自换算。
- genesis:需要 unisim-core 侧为
GenesisBackend / init_genesis_session 新增 device 入参,并在 gs.init / current_device 之前 torch.cuda.set_device;UniLab 侧补 EnvCfg 字段与 rank device 透传。
- 过渡期防护:检测到
training.devices 多卡 + 这三个后端 + env device 全为 0 时输出 warning,避免静默挤卡。
仅为问题记录与方向建议,不在本 issue 内实施。
问题
多 GPU data-parallel 训练时,isaacgym / isaacsim / genesis 三个 GPU 后端的 env(collector / worker 进程)全部落在可见设备 0,rank 到 GPU 的分配机制(
training.devices→resolve_dp_rank_device)只作用于 learner / replay buffer,从不传给这三个后端的 env。对比:mjwarp 通过bind_backend_process_device(src/unilab/base/process_device.py)强制 collector 与 learner 同卡,行为正确。各后端证据
isaacgym(Python 3.8 子进程 worker + shm 协议)
env.isaacgym_device_id: 0:src/unilab/conf/ppo/task/g1_walk_flat/isaacgym.yaml:31、src/unilab/conf/sac/task/g1_walk_flat/isaacgym.yaml:29;EnvCfg.isaacgym_device_id默认 None(src/unilab/base/base.py:57)。subprocess_ipc/backend.py:302device_id = 0 if device_id is None→ INIT payload 携带device_id(:408)→ workergym.create_sim(device_id, graphics_device_id, ...)(isaacgym/worker.py:96-120)。env_cfg_override只追加cpu_ids(uni_rl/offpolicy/double_buffer_runner.py:528-537),从不重写isaacgym_device_id;worker 子进程只继承父进程 env,无人设CUDA_VISIBLE_DEVICES。isaacsim(Kit 子进程 worker)
env.isaacsim_device_id: 0(src/unilab/conf/ppo/task/g1_walk_flat/isaacsim.yaml:27等)→ 同一 payload 字段 → workerself.device = f"cuda:{device_id}"(isaacsim/worker.py:152-158),传给AppLauncher({"device": ..., "multi_gpu": False})与SimulationCfg。isaacsim/worker.py:115的self.device = "cuda:0"只是_WorkerContext.__init__占位初值,init_sim第一行即被 payload 覆盖,不是根因;根因是 payload 的device_id永远来自写死 0 的 YAML(或 None→0 默认)。multi_gpu: False表明 worker 设计上单卡单进程,需要外层按 rank 分配,外层没做。genesis(进程内后端)
GenesisBackend.__init__(unisim/backend/genesis/backend.py:78-94)不接受 device 入参,UniLab 侧EnvCfg/genesis.yaml也没有任何 device 字段。gs.init(backend=gs.gpu)会强制torch.set_default_device("cuda:0")(genesis/materialization.py:116注释明确记录);backend 跟随torch.cuda.current_device()(genesis/backend.py:124-127),而 UniLab/uni_rl 全链路只有 off-policy learner 在dp_sync.py:90调torch.cuda.set_device,collector / PPO worker 无人 set。resolve_backend_process_device只对 mjwarp 返回非 None,genesis collector 无绑定;one-session-per-process 守卫意味着进程内无二次选择机会。影响
任何
training.devices=[0,1,...]的多卡训练,这三个后端的所有 rank env 物理仿真都挤在devices[0]一张卡上:该卡显存/算力成为瓶颈,其余卡只跑 learner;且用户无报错、无告警,表象是多卡 scaling 无效。修复方向(供 maintainer 判断)
env_cfg_override["isaacgym_device_id"/"isaacsim_device_id"]。注意 off-policy 路径 rank device 是 host-global index,PPO torchrun 路径是LOCAL_RANK+ 全量CUDA_VISIBLE_DEVICES,两者 index 语义不同,需要各自换算。GenesisBackend/init_genesis_session新增 device 入参,并在gs.init/current_device之前torch.cuda.set_device;UniLab 侧补EnvCfg字段与 rank device 透传。training.devices多卡 + 这三个后端 + env device 全为 0 时输出 warning,避免静默挤卡。仅为问题记录与方向建议,不在本 issue 内实施。