解读 26 · mdp.py(下):reward 与事件函数库
解读对象:
microduck-replica/upstream/microduck_rl/src/mjlab_microduck/tasks/mdp.py(7188 行),抽读 reward / event / curriculum 相关
需要的前置:解读 24(这些函数在配置里被谁引用);event term = reset/interval 时机执行的事件项
模块级补丁:给 RewardManager 打 NaN 疫苗
mdp.py 开头就干了件大事:32–40 行把 RewardManager.compute 整体替换为
_nan_safe_reward_compute——mjlab 在重置之前算奖励,物理爆炸那一步的奖励是 NaN,
会沿"奖励→优势→损失→梯度→std 变负→torch.normal 崩溃"传播;补丁对结果和 _episode_sums
就地 nan_to_num_。50 行起对 PPO.compute_returns 做第二层补丁:课程跳变时优势归一化
可能爆掉,先清 NaN/Inf 再归一化。猴子补丁(monkeypatch = 运行时替换库函数)写在模块顶部,
import 即生效,所有任务共享。
终止与重置事件
robot_state_is_nan(963–1014 行):检查 joint_pos/joint_vel 加根体四个状态量是否有限,
再按 sensor_names 检查接触力——注释里写明这是 2026-08-21 一次真实崩溃的修复(接触力先于
关节状态爆炸,而 critic 的 foot_contact_forces 观测吃到了它)。reset_action_history
(229–313 行)是 reset 模式事件,把 _prev_leg_actions、_prev_neck_actions 及加速度用的
两级缓存重置为当前动作——不重置的话,回合开头的 action_rate 惩罚会凭空出现尖峰。
面向 BAM 执行器的域随机化
randomize_delayed_actuator_gains(3146–3194 行)不碰 MuJoCo 模型,而是调执行器自己的
reset_gains/set_gains,每关节采样后按环境平均成单标量(执行器只接受全局缩放);
randomize_bam_friction(3212–3240 行)对 FrictionDRBamActuator 先复位再采样
friction_scale——两者都遵守"先恢复标称、再采新值"防累积。expand_bam_friction_fields
(3197–3209 行)是个带装饰器的空函数:@requires_model_fields("dof_frictionloss",
"dof_damping") 的唯一作用是让 mjlab 把这两个模型字段按世界展开,BAM 每步要写它们。
两个明星奖励函数
head_pose_tracking(5128–5192 行):mean(exp(-(err/std)²)) 对 4 个颈/头关节取均值——
均值而非平方和,单关节误差大时其余关节仍有梯度;可选 fine_std 混入窄高斯定价小误差;
测量角在齿隙模型上读输出侧(+ qpos[backlash]),与编码器观测一致。它还会把关节 id 缓存到
env._head_pose_neck_ids 等属性,与 head_pose_bias_penalty(5229–5317 行)共享。
后者是 2026-08-20"头部下垂修复":对 1 s EMA 后的误差取 L1(-mean(|EMA(err)|)),
让走路不可避免的振荡在平均中抵消,只罚可逃逸的直流偏垂;可选的 smoothstep 门控
(gate_height_low/gate_tilt_full_deg=20.0/gate_tilt_zero_deg=45.0)让摔倒/起身阶段
不预充误差,5309–5310 行还在回合头两步清零 EMA。
课程函数:改"活着的"cfg,不是 env.cfg
reward_weight(3442–3461 行)按 weight_stages 分档改奖励权重;com_range_curriculum
(3464–3501 行)改事件范围;standing_envs_curriculum(3295–3332 行)改站立环境比例。
三者的注释是同一课:EventManager/RewardManager 初始化时 deepcopy 了配置,改
env.cfg.events 是无效操作,必须通过 get_term_cfg 拿经理持有的活配置。
VelocityCommandCommandOnly._resample_command(4508–4533 行)则展示命令类怎么当奖励
基础设施用:按 rel_turn_in_place_envs 抽签,把这些环境的线速度清零、偏航强制
[0.4·max, max],并从站立集合里摘除。
你带走的收获
- NaN 防护要成体系:奖励补丁 + 终止检查 + 观测消毒三层,单点防护挡不住传播链。
- 随机化函数统一"先复位再采样",累积污染是 DR 最常见的暗坑。
- 好的奖励塑形常是"换基"而非"加码":瞬时误差改 EMA 误差,问题从"不可逃逸税"变成"可逃逸偏置"。
- 课程函数写法固定:分档 dict +
common_step_counter比较 + 改活配置,五分钟就能照抄一门新课。