Skip to content

feat: G1 per-joint kp/kd domain randomization (issue #129) - #259

Merged
TATP-233 merged 1 commit into
mainfrom
g1_deploy
Apr 16, 2026
Merged

TATP-233 merged 1 commit into
mainfrom
g1_deploy

Conversation

@lemon-star608

@lemon-star608 lemon-star608 commented Apr 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • 为 G1 joystick(PPO / SAC)引入 per-joint kp/kd 域随机化,与 Go2 架构对齐,解决 issue Refactor: G1 joystick 统一采用 MuJoCo kp/kd 域随机化方案,移除 backend_overrides 临时方案 #129
  • 在 SimBackend 上新增 get_actuator_gains() 作为正式可选能力合约(NotImplementedError 默认值,与 get_physics_state() 同一模式),MuJoCoBackend 实现该方法从模型状态读取 per-joint kp/kd 数组
  • 在 LocomotionDRProvider 上新增 _get_base_actuator_gains() 钩子方法(与 _get_qvel_limit / _build_extra_info_updates 同一模式),默认返回 (None, None) 回退到标量 ControlConfig.Kp/Kd,Go1/Go2 行为不变
  • G1JoystickDomainRandomizationProvider 重写该钩子,在 env init 冷路径通过正式合约 backend.get_actuator_gains() 读取并缓存 per-joint 基准 gains;DR 关闭时不调用 get_actuator_gains(),Motrix backend 若被误配置为开启则在初始化阶段显式抛出 NotImplementedError
  • dr_utils.build_common_reset_randomization 和 validate_common_reset_randomization 新增 base_kp/base_kd 关键字参数,移除原有 getattr(env, "_backend") / hasattr(backend, "get_actuator_gains") 隐式 backend 探测代码;新参数均为可选默认 None,所有现有调用方完全向后兼容
  • G1 Motrix 配置通过 randomize_kp: false / randomize_kd: false 显式禁用 kp/kd DR

Linked Work

Validation

  • make check
  • uv run pytest -m "not slow"
  • MuJoCo backend:kp/kd DR 开启,per-joint gains 正确缓存并随机化(29 关节,范围 [14.3, 99.1])
  • Motrix backend:randomize_kp/kd: false,provider 无缓存 gains,payload 不含 kp/kd
  • Motrix backend 误配置为开启:初始化阶段显式抛出 NotImplementedError

Commands actually run:

uv run pyright src/unilab/base/backend/base.py \
               src/unilab/base/backend/mujoco_backend.py \
               src/unilab/dr/dr_utils.py \
               src/unilab/envs/locomotion/common/dr_provider.py \
               src/unilab/envs/locomotion/g1/joystick.py \
               src/unilab/envs/locomotion/g1/joystick_sac.py
uv run pytest -m "not slow"

Impact

  • Backend impact: both(MuJoCo 新增 get_actuator_gains() 实现;Motrix 继承 NotImplementedError 默认值,行为不变)
  • Platform impact: both
  • Training effect expected: yes(MuJoCo backend 下 kp/kd DR 生效;Motrix 行为不变)

Checklist

  • Added or updated tests where needed
  • Updated docs if behavior or workflow changed
  • Linked the driving issue
  • Noted any follow-up work explicitly

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns G1 joystick (PPO/SAC) with the Go2-style kp/kd domain randomization flow by introducing per-joint actuator gain randomization via a formal backend capability (get_actuator_gains()), and wiring that through the locomotion DR provider/reset payload generation.

Changes:

  • Add an optional SimBackend.get_actuator_gains() contract and implement it for MuJoCoBackend to read per-actuator kp/kd from the model.
  • Introduce G1DomainRandConfig + G1 DR provider gain caching, and pass cached base kp/kd into common reset randomization utilities.
  • Update Motrix task configs to explicitly disable kp/kd randomization (avoiding unsupported-backend initialization errors).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/unilab/envs/locomotion/g1/joystick.py Adds G1-specific DR config/provider hook; conditionally caches per-joint gains from backend during env init.
src/unilab/envs/locomotion/g1/joystick_sac.py Switches SAC config to G1 DR config and mirrors the gain-caching DR initialization path.
src/unilab/envs/locomotion/common/dr_provider.py Adds _get_base_actuator_gains() hook and threads base kp/kd into validate/reset-plan generation.
src/unilab/dr/dr_utils.py Extends common reset randomization builder/validator with optional base_kp/base_kd and removes implicit backend probing.
src/unilab/base/backend/base.py Defines optional backend API get_actuator_gains() with a default NotImplementedError.
src/unilab/base/backend/mujoco_backend.py Implements get_actuator_gains() by reading MuJoCo actuator gain/bias parameters.
conf/ppo/task/g1_joystick/motrix.yaml Disables kp/kd DR under Motrix for PPO.
conf/offpolicy/task/sac/g1_sac/motrix.yaml Disables kp/kd DR under Motrix for SAC.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +192 to 198
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
dr_provider = G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
else:
dr_provider = G1JoystickDomainRandomizationProvider()
self._init_domain_randomization(dr_provider)

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The domain-randomization provider selection logic is duplicated here and in g1/joystick_sac.py. To reduce the chance of future drift (e.g., adding a new DR term in one env but not the other), consider factoring this into a shared helper (or a small method on the base env) that returns the correctly-configured provider.

Suggested change
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
dr_provider = G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
else:
dr_provider = G1JoystickDomainRandomizationProvider()
self._init_domain_randomization(dr_provider)
dr_provider = self._create_domain_randomization_provider(backend)
self._init_domain_randomization(dr_provider)
def _create_domain_randomization_provider(
self, backend: Any
) -> G1JoystickDomainRandomizationProvider:
if self._cfg.domain_rand.randomize_kp or self._cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
return G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
return G1JoystickDomainRandomizationProvider()

Copilot uses AI. Check for mistakes.
self._init_reward_functions()
self._init_domain_randomization(G1JoystickDomainRandomizationProvider())
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity and to avoid relying on a local variable after initialization, consider calling self._backend.get_actuator_gains() instead of backend.get_actuator_gains() here. It makes it explicit that the gains are being read from the env’s active backend instance.

Suggested change
base_kp, base_kd = backend.get_actuator_gains()
base_kp, base_kd = self._backend.get_actuator_gains()

Copilot uses AI. Check for mistakes.
Comment on lines +99 to 105
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
dr_provider = G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
else:
dr_provider = G1JoystickDomainRandomizationProvider()
self._init_domain_randomization(dr_provider)

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The domain-randomization provider selection logic is duplicated with g1/joystick.py. Consider extracting a shared helper so PPO/SAC variants stay in sync when DR behavior changes (e.g., additional reset terms or different gain-caching rules).

Suggested change
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
dr_provider = G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
else:
dr_provider = G1JoystickDomainRandomizationProvider()
self._init_domain_randomization(dr_provider)
dr_provider = self._create_domain_randomization_provider(cfg, backend)
self._init_domain_randomization(dr_provider)
def _create_domain_randomization_provider(self, cfg: G1JoystickSACCfg, backend):
"""Create the DR provider while staying aligned with PPO/shared logic when available."""
parent_factory = getattr(super(), "_create_domain_randomization_provider", None)
if callable(parent_factory):
return parent_factory(cfg, backend)
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()
return G1JoystickDomainRandomizationProvider(base_kp=base_kp, base_kd=base_kd)
return G1JoystickDomainRandomizationProvider()

Copilot uses AI. Check for mistakes.
self._init_reward_functions()
self._init_domain_randomization(G1JoystickDomainRandomizationProvider())
if cfg.domain_rand.randomize_kp or cfg.domain_rand.randomize_kd:
base_kp, base_kd = backend.get_actuator_gains()

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability, prefer self._backend.get_actuator_gains() over the local backend variable here, so it’s clear the gains are read from the env’s configured backend instance.

Suggested change
base_kp, base_kd = backend.get_actuator_gains()
base_kp, base_kd = self._backend.get_actuator_gains()

Copilot uses AI. Check for mistakes.
@TATP-233

Copy link
Copy Markdown
Collaborator

再补充一下修改前后的训练对比曲线

@TATP-233
TATP-233 merged commit df6942f into main Apr 16, 2026
9 checks passed
@TATP-233
TATP-233 deleted the g1_deploy branch April 16, 2026 06:32
@lemon-star608

lemon-star608 commented Apr 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

紫色为仅开启kdkpDR后的曲线
Eplen
Reward

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mujoco_uni.batch_env
pool.get_field(env_id, name) -> np.ndarray
提供了批量获取DR参数属性的功能,替换成这个接口

TATP-233 added a commit that referenced this pull request May 4, 2026
feat: G1 per-joint kp/kd domain randomization (issue #129)
TATP-233 added a commit that referenced this pull request May 30, 2026
feat: G1 per-joint kp/kd domain randomization (issue #129)
TATP-233 added a commit that referenced this pull request Sep 4, 2026
feat: G1 per-joint kp/kd domain randomization (issue #129)
EtherealTide pushed a commit to EtherealTide/UniLab that referenced this pull request Sep 24, 2026
Closes Motphys#1617.

UniSim 1.7.x accepts a bounded, readback-validated PhysX solver override
set on the IsaacSim backend (unilabsim/unisim#251, Motphys#259), but the standard
UniLab path (registry.make -> make_manager_based_rl_env -> create_backend)
had no channel for those knobs: task owners could not declare the solver
table and the backend silently ran PhysX defaults.

- EnvCfg gains the six solver fields, following the per-backend knob
  convention (Optional, None keeps the PhysX scene defaults):
  isaacsim_solver_position_iteration_count (int >= 1),
  isaacsim_solver_velocity_iteration_count (int >= 0),
  isaacsim_bounce_threshold_velocity / isaacsim_contact_offset /
  isaacsim_rest_offset / isaacsim_max_depenetration_velocity
  (non-negative floats).  EnvCfg.validate mirrors the PhysxSolverConfig
  bounds so owner configs fail fast.
- env_backend_kwargs forwards all six to the UniSim factory under the
  same names.
- Tests: field->kwargs mapping assertions (values forwarded verbatim,
  defaults stay None) and validation rejection coverage.
- Changelog entry (shared page, Added).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants