Align DreamWaQ PPO rollout and policy dynamics

This commit is contained in:
8x54zj-m
2026-07-22 13:59:16 +08:00
parent 57faf8880b
commit 0555776c11
7 changed files with 26 additions and 15 deletions

View File

@@ -197,7 +197,7 @@ class DreamWaQTask(Go1WalkTask):
def apply_action(self, actions, state): def apply_action(self, actions, state):
"""裁剪动作 + 随机延迟(模拟真实部署延迟,提高 sim-to-real 泛化)。""" """裁剪动作 + 随机延迟(模拟真实部署延迟,提高 sim-to-real 泛化)。"""
actions = np.clip(actions, -4.0, 4.0) actions = np.clip(actions, -100.0, 100.0)
# 随机动作延迟0-3 个控制步0-60ms # 随机动作延迟0-3 个控制步0-60ms
if not hasattr(self, '_action_buffer'): if not hasattr(self, '_action_buffer'):
self._action_buffer = np.zeros((self._num_envs, 3, self._num_action), dtype=np.float32) self._action_buffer = np.zeros((self._num_envs, 3, self._num_action), dtype=np.float32)

View File

@@ -36,7 +36,7 @@ class RslRlActorCfg:
class_name: str = "MLPModel" class_name: str = "MLPModel"
hidden_dims: list[int] = field(default_factory=lambda: [256, 128, 64]) hidden_dims: list[int] = field(default_factory=lambda: [256, 128, 64])
activation: str = "elu" activation: str = "elu"
obs_normalization: bool = True obs_normalization: bool = False
stochastic: bool = True stochastic: bool = True
init_noise_std: float = 1.0 init_noise_std: float = 1.0
noise_std_type: Literal["scalar", "log"] = "scalar" noise_std_type: Literal["scalar", "log"] = "scalar"

View File

@@ -114,8 +114,7 @@ class CENetActorModel(MLPModel):
# 在 nn.Module.__init__ 之后创建 VAE 子模块 # 在 nn.Module.__init__ 之后创建 VAE 子模块
self.vae = CENetVAE(cenet_in_dim, cenet_out_dim, activation) self.vae = CENetVAE(cenet_in_dim, cenet_out_dim, activation)
self.action_clip = 4.0 self.std_floor = 1e-6
self.std_clip = 0.6
self._last_cenet_output = None self._last_cenet_output = None
# AdaBoot: 自适应 bootstrapping论文 Section II-C # AdaBoot: 自适应 bootstrapping论文 Section II-C
self._adaboot_cv_buffer = [] # 速度估计误差的 CV 历史 self._adaboot_cv_buffer = [] # 速度估计误差的 CV 历史
@@ -123,15 +122,16 @@ class CENetActorModel(MLPModel):
def _update_distribution(self, obs: torch.Tensor) -> None: def _update_distribution(self, obs: torch.Tensor) -> None:
"""覆盖父类 — 限制动作分布,避免采样动作进入 PD/关节限位饱和区。""" """覆盖父类 — 限制动作分布,避免采样动作进入 PD/关节限位饱和区。"""
mean = torch.clamp(self.mlp(obs), -self.action_clip, self.action_clip) mean = self.mlp(obs)
if self.stochastic and not self.state_dependent_std: if self.stochastic and not self.state_dependent_std:
with torch.no_grad(): with torch.no_grad():
if self.noise_std_type == "scalar": if self.noise_std_type == "scalar":
self.std.nan_to_num_(nan=0.5, posinf=1.0, neginf=1.0) self.std.nan_to_num_(nan=1.0, posinf=1.0, neginf=1.0)
self.std.clamp_(min=1e-6, max=self.std_clip) self.std.clamp_(min=self.std_floor)
elif self.noise_std_type == "log": elif self.noise_std_type == "log":
self.log_std.nan_to_num_(nan=0.0, posinf=5.0, neginf=-5.0) self.log_std.nan_to_num_(nan=0.0, posinf=5.0, neginf=-5.0)
self.log_std.clamp_(min=-20.0, max=torch.log(torch.tensor(self.std_clip)).item()) self.log_std.nan_to_num_(nan=0.0, posinf=5.0, neginf=-20.0)
self.log_std.clamp_(min=-20.0)
if self.noise_std_type == "scalar": if self.noise_std_type == "scalar":
std = self.std.expand_as(mean) std = self.std.expand_as(mean)
elif self.noise_std_type == "log": elif self.noise_std_type == "log":
@@ -139,7 +139,7 @@ class CENetActorModel(MLPModel):
else: else:
raise ValueError(f"Unknown standard deviation type: {self.noise_std_type}.") raise ValueError(f"Unknown standard deviation type: {self.noise_std_type}.")
else: else:
std = torch.full_like(mean, self.std_clip) std = torch.ones_like(mean)
self.distribution = Normal(mean, std) self.distribution = Normal(mean, std)
def forward( def forward(
@@ -150,7 +150,7 @@ class CENetActorModel(MLPModel):
stochastic_output: bool = False, stochastic_output: bool = False,
) -> torch.Tensor: ) -> torch.Tensor:
actions = super().forward(obs, masks, hidden_state, stochastic_output) actions = super().forward(obs, masks, hidden_state, stochastic_output)
return torch.clamp(actions, -self.action_clip, self.action_clip) return actions
def _get_latent_dim(self) -> int: def _get_latent_dim(self) -> int:
"""Actor 实际输入code(19) + policy(45) = 64。 """Actor 实际输入code(19) + policy(45) = 64。

View File

@@ -85,6 +85,7 @@ class DreamWaQPPO(PPO):
if (not torch.isfinite(obs_batch["policy"]).all() if (not torch.isfinite(obs_batch["policy"]).all()
or not torch.isfinite(obs_batch["obs_history"]).all() or not torch.isfinite(obs_batch["obs_history"]).all()
or ("privileged_obs" in obs_batch and not torch.isfinite(obs_batch["privileged_obs"]).all()) or ("privileged_obs" in obs_batch and not torch.isfinite(obs_batch["privileged_obs"]).all())
or ("prev_privileged_obs" in obs_batch and not torch.isfinite(obs_batch["prev_privileged_obs"]).all())
or not torch.isfinite(actions_batch).all() or not torch.isfinite(actions_batch).all()
or not torch.isfinite(target_values_batch).all() or not torch.isfinite(target_values_batch).all()
or not torch.isfinite(advantages_batch).all() or not torch.isfinite(advantages_batch).all()
@@ -292,7 +293,7 @@ class DreamWaQPPO(PPO):
code, code_vel, decode, mean_vel, logvar_vel, mean_latent, logvar_latent = cenet_out code, code_vel, decode, mean_vel, logvar_vel, mean_latent, logvar_latent = cenet_out
# 速度估计目标privileged_obs 中的 base_vel索引 45:48 # 速度估计目标privileged_obs 中的 base_vel索引 45:48
vel_target = obs_batch["privileged_obs"][:, 45:48] vel_target = obs_batch["prev_privileged_obs"][:, 45:48]
# 观测重建目标:当前 policy obs # 观测重建目标:当前 policy obs
obs_target = obs_batch["policy"] obs_target = obs_batch["policy"]

View File

@@ -83,7 +83,7 @@ class RslrlNpEnvWrap(VecEnv):
"""Return the unwrapped environment (self for this wrapper).""" """Return the unwrapped environment (self for this wrapper)."""
return self return self
def _build_obs_dict(self, state) -> dict[str, torch.Tensor]: def _build_obs_dict(self, state, prev_privileged_obs=None) -> dict[str, torch.Tensor]:
"""将 NpEnvState 中的观测字段组装为 TensorDict 字典。 """将 NpEnvState 中的观测字段组装为 TensorDict 字典。
支持 env 通过 state.info 传递 obs_history 和 privileged_obs。 支持 env 通过 state.info 传递 obs_history 和 privileged_obs。
@@ -96,12 +96,22 @@ class RslrlNpEnvWrap(VecEnv):
if "privileged_obs" in state.info: if "privileged_obs" in state.info:
priv = np.nan_to_num(state.info["privileged_obs"], nan=0.0) priv = np.nan_to_num(state.info["privileged_obs"], nan=0.0)
obs_dict["privileged_obs"] = torch.from_numpy(priv).to(self._device) obs_dict["privileged_obs"] = torch.from_numpy(priv).to(self._device)
if prev_privileged_obs is None:
prev_privileged_obs = np.zeros_like(priv)
obs_dict["prev_privileged_obs"] = torch.from_numpy(
np.nan_to_num(prev_privileged_obs, nan=0.0)
).to(self._device)
return obs_dict return obs_dict
def step(self, actions: torch.Tensor) -> tuple[TensorDict, torch.Tensor, torch.Tensor, dict]: def step(self, actions: torch.Tensor) -> tuple[TensorDict, torch.Tensor, torch.Tensor, dict]:
# Convert torch actions to numpy # Convert torch actions to numpy
actions_np = actions.cpu().numpy() actions_np = actions.cpu().numpy()
# Preserve the privileged observation aligned with the policy history.
previous_privileged_obs = None
if self._state is not None and "privileged_obs" in self._state.info:
previous_privileged_obs = self._state.info["privileged_obs"].copy()
# Step the environment # Step the environment
state = self._env.step(actions_np) state = self._env.step(actions_np)
self._state = state self._state = state
@@ -119,7 +129,7 @@ class RslrlNpEnvWrap(VecEnv):
dones = torch.from_numpy(state.done.astype(np.float32)).to(self._device) dones = torch.from_numpy(state.done.astype(np.float32)).to(self._device)
# 构建多键 TensorDictpolicy + obs_history + privileged_obs # 构建多键 TensorDictpolicy + obs_history + privileged_obs
obs = TensorDict(self._build_obs_dict(state), obs = TensorDict(self._build_obs_dict(state, previous_privileged_obs),
batch_size=[self._num_envs], device=self._device) batch_size=[self._num_envs], device=self._device)
# Build extras dict (RSLRL calls it "extras" not "infos") # Build extras dict (RSLRL calls it "extras" not "infos")

View File

@@ -126,7 +126,7 @@ class rslrl:
runner.algorithm.entropy_coef = 0.01 # 上游 Go1RoughCfgPPO runner.algorithm.entropy_coef = 0.01 # 上游 Go1RoughCfgPPO
runner.algorithm.desired_kl = 0.01 # 上游 0.01 (默认 0.008) runner.algorithm.desired_kl = 0.01 # 上游 0.01 (默认 0.008)
runner.algorithm.clip_param = 0.2 runner.algorithm.clip_param = 0.2
runner.algorithm.schedule = "fixed" # 4096 envs 采样够大,不需要 adaptive 加噪 runner.algorithm.schedule = "adaptive"
runner.algorithm.gamma = 0.99 runner.algorithm.gamma = 0.99
runner.algorithm.lam = 0.95 runner.algorithm.lam = 0.95
runner.algorithm.max_grad_norm = 1.0 runner.algorithm.max_grad_norm = 1.0

View File

@@ -39,7 +39,7 @@ HISTORY_LEN = 5
ACTION_SCALE = 0.25 ACTION_SCALE = 0.25
KP = 28.0 KP = 28.0
KD = 0.7 KD = 0.7
CLIP_ACTIONS = 4.0 CLIP_ACTIONS = 100.0
CLIP_TORQUES = 80.0 CLIP_TORQUES = 80.0
CLIP_OBS = 100.0 CLIP_OBS = 100.0
MAX_VX, MAX_VY, MAX_WZ = 1.0, 1.0, 1.0 MAX_VX, MAX_VY, MAX_WZ = 1.0, 1.0, 1.0