解读 122 · mdp.py(一):文件结构与公共设施
解读对象:
microduck-replica/upstream/microduck_rl/src/mjlab_microduck/tasks/mdp.py(7188 行,220 个顶层 def + 10 个 class;含嵌套共 255 个 def)
需要的前置:解读 21(这个文件被谁加载)、解读 25(兄弟篇:观测函数导览)
mdp.py 是全仓库最大的单文件。所有任务配置文件里写的 func=microduck_mdp.xxx,最终都指向这里。本篇不读具体函数,读它的"骨架":函数怎么组织、文件头四个补丁干了什么、公共工具怎么服务全文件。
函数地图:先 grep 再读
grep -n "^def " 一条命令就能拿到全文件 220 个顶层函数和行号。粗看即见组织规律:按任务家族分段,段与段之间用横线注释分隔——507 行起的 standup/velstand 家族(_fallen_mask、upright_progress、recovery_success)、3141 行的 "Domain Randomization Events"、5692 行的 BallKick、5856 行的 SPIN、6145 行的 backlash 观测、6682 行起的 roulade。同族函数共享前缀(spin_*、roulade_*、posture_*、multistage_*),派生变体带后缀(*_phased、*_from_values,后者是"算好中间量再打分"的可测纯函数版)。10 个 class 全部是命令项(4505-6332),下篇细读。还有一个彩蛋:roller 任务段(1035-1293,如 descent_speed_reward、wheel_glide_reward)的 docstring 是法语,另一条任务线的痕迹。
文件头的四个补丁
23-113 行是四个"猴子补丁"(monkeypatch = 运行时替换库函数,import 即对全项目生效):
- 补丁 1(32-40 行):用
_nan_safe_reward_compute替换RewardManager.compute,把 NaN 奖励就地清零——物理爆炸那步的 NaN 会沿"奖励→优势→梯度"传播到崩溃。 - 补丁 2(52-58 行):
_safe_compute_returns包住PPO.compute_returns,课程跳变时先清优势里的 NaN/Inf 再归一化。 - 补丁 3(60-64 行)已移除,注释交代原因:rsl_rl 5.0.1 重构后没有 ActorCritic 类了。
- 补丁 4(79-104 行):
_get_base_metadata_no_passive替换导出元数据函数,把passive_*关节从导出清单滤掉——新模型的下颌连杆是无执行器的被动关节,上游导出器会在这里 KeyError。
每个补丁装上时都 print 一行(66、113 行),启动日志里能确认补丁生效——被替换的是"库的行为",看不见的替换最危险。
公共设施:14 舵机的正典索引
_DEFAULT_ASSET_CFG = SceneEntityCfg("robot")(119 行)是几乎所有函数的默认参数;_NECK_JOINT_PATTERNS(123 行)用四个正则圈定颈/头关节。关键件是 _servo_joint_ids(126-143 行):对带齿隙/轮子/下颌的模型,实体关节表比 14 个舵机宽、且交错插入 passive_*,裸索引会抓错关节;它用 find_joints(r"^(?!passive_).*") 取"非被动"关节并缓存到 env.__dict__(每个 env 的属性字典,当缓存柜用),在普通模型上就是恒等映射。_servo_joint_pos/_servo_joint_vel/_servo_default_joint_pos(146-155 行)三个一行函数全部经它索引——一套奖励代码因此同时服务 5 种模型变体。
函数的统一长相与"挂"在 env 上的状态
绝大多数函数签名是 (env, ..., asset_cfg=_DEFAULT_ASSET_CFG) -> torch.Tensor,返回 (num_envs,)(奖励/惩罚)或 (num_envs, dim)(观测)。跨步记忆不建类,而是挂在 env 的属性上:_prev_leg_actions(动作差分惩罚)、_swing_accum(步态对称累计)、_fallen_tax_armed(滞回闸门)……命名一律下划线开头表示"内部"。有挂就有清:reset_action_history(229-313 行)在回合重置时把这些缓存全部对齐到当前值,否则回合开头会凭空冒出 action_rate 惩罚尖峰。
你带走的收获
- 读超大文件先
grep "^def "建地图:分节注释 + 命名前缀就是这个文件的目录树。 - 猴子补丁集中放文件头、装上即打印,是"改库不改库源码"的可审计做法。
- 跨步状态挂 env 属性、下划线命名、由 reset 事件统一清零——无类风格的状态管理约定。
- 想让一套 MDP 函数吃下多种模型,加一层"正典索引"(
_servo_*)比写五套函数便宜得多。