1. 先把问题摆清楚仿真里能跑的策略一上真机就废在哪做过机器人强化学习真机部署的人都经历过同一个瞬间仿真曲线漂亮得像教科书奖励收敛、步态稳定、抗扰测试全绿结果策略一烧进真机机器站起来三秒就开始原地抽搐或者干脆一个趔趄趴下。这不是策略没学好而是从仿真到现实sim-to-real之间的差距从来不是单点误差而是一整套系统性偏移的叠加。我前后带过几个不同形态的足式、轮足和机械臂项目每一次真机部署的第一周几乎都在做同一件事把仿真里那些被默认成立的假设一条条拆掉看看哪一条在真机上不成立。这篇文章想讲的就是这件事——五大关键策略系统辨识、执行器建模、分层域随机化、观测与动作空间对齐、课程学习与安全约束下的真机微调。它们不是并列的五个技巧而是有明确先后顺序的一套工程流水线。适合谁看已经能在仿真里训出会走的策略、但真机上一直不敢放手或者一放手就摔的同行也适合做机械臂抓取、灵巧手操作、移动底盘导航这类同样面临 sim-to-real 问题的朋友方法论是通用的只是参数和关注点不同。全文我尽量按我当时怎么想、为什么这么选、踩了什么坑的顺序写参数和代码都给到能直接抄的程度。1.1 仿真和真机之间到底差在哪几层很多人一上来就怀疑是不是随机化不够然后疯狂加大噪声范围结果策略变得极度保守走路像踩鸡蛋真机上反而更差。我的经验是先把差距分层再逐层处理效率高得多。我一般分成六层来看层级典型差距表现处理优先级结构层连杆长度、关节轴向、质心位置、装配公差姿态系统性偏斜、单侧受力不均高先测准动力学层质量、转动惯量、摩擦、阻尼、齿隙、死区力矩响应偏软或偏硬、低速爬行高执行器层力矩上限、速度上限、电流环带宽、指令延迟高频抖动、饱和后失稳最高传感层编码器噪声、IMU零偏与温漂、滤波群延迟低频漂移、高频毛刺高控制层控制频率、零阶保持、时间戳抖动、插值方式相位滞后、极限环振荡高环境层地面材质、坡度、负载变化、气流泛化失效、特定场景崩中靠随机化关键在于前四层是确定性差距能用测量和建模消掉后两层是不确定性差距只能靠随机化和在线适配兜住。顺序反了就会很难受——你先做随机化等于在一堆错误的基础上撒噪声最后连哪部分误差是噪声、哪部分是模型错都分不清。1.2 五大策略的框架以及我为什么这样排优先级我把这五件事排成一条链理由和调音很像一把吉他跑音了你先把弦拧准系统辨识再考虑演出场地温度变化带来的微调域随机化如果你先按温度可能变化去随机补偿那叫缘木求鱼。系统辨识把质量、惯量、摩擦、死区这些能测的量测出来写回仿真模型里。目标是让仿真和真机在慢动作、低动态工况下先对上。执行器建模电机不是理想力矩源。这一层通常是最大的单点误差源也是高频抖动的元凶。加一阶惯性、纯延迟、饱和与死区之后很多玄学抖动会直接消失。分层域随机化在已经尽量准的模型上只对确实测不准、确实会变的维度做随机并且分层设定范围。观测与动作空间对齐这一层最容易被忽略因为它看起来不是算法问题。单位、坐标系、顺序、归一化统计量、滤波器群延迟任何一项错位策略的表现都会断崖式下跌。课程学习与安全约束下的真机微调最后 10% 的性能提升在真机上完成但必须有支架、限幅、急停这些护栏。我自己的项目里第 4 条造成的问题次数最多第 2 条造成的损失最大摔机基本都是它第 3 条最容易做过头。这个排序是先做减法再做加法先把确定性误差减掉再往上加不确定性。1.3 先立一条规矩把能不能上真机当成独立指标这条经验我觉得比任何参数都值钱。训练的时候不要只看平均奖励要单独维护一组部署前基准动作比如固定站立 30 秒、原地踏步 60 步、直线行走 5 米、侧向抗扰推一下、坡道上走 3 米。每个动作在仿真里跑出量化指标躯干姿态角速度均方根、关节跟踪误差、跌倒率、能耗真机上重复同样动作采集同样指标两者相除或相减得到sim-to-real 差距。这个指标不降就别加新功能。我吃过一次亏一个策略在仿真里抗扰测试做得很好我直接上真机做动态跳跃结果落地瞬间膝关节力矩打满驱动器保护机器直接跪了。事后回看那个策略的力矩饱和占比在仿真里只有 2%真机上跑到 20% 以上而这个指标我根本没单独记。从那以后我的部署门槛里必看三项力矩饱和占比、关节速度上限触发次数、动作二阶差分抖动程度。2. 策略一系统辨识打底先把确定性的差距补上系统辨识听起来学术实操上就是拿尺子和秤把机器量一遍再用几组简单实验把摩擦和死区摸出来。它不性感但性价比最高。我一般花半天到一天做完一轮换来的是后面所有调参时间的大幅压缩。2.1 需要辨识哪些量从质量惯量到摩擦死区不要想着全辨识工程上够用就行。我通常分三批第一批是几何与惯性参数用 CAD 模型加称重核对。重点核对总质量、各连杆质量、质心位置、关节轴向。CAD 模型和实物偏差个 10% 很常见尤其是加了外壳、线束、散热件之后。总质量差 10%支撑腿的力矩需求就差不多差 10%这不是小数字。第二批是关节摩擦与阻尼。做法是把机器吊起来或者放在低摩擦支架上让单个关节以一系列恒定低速正反转动记录指令力矩—实际速度曲线。曲线在零速附近会有一段明显的死区力矩加了但不动斜率变化点附近有 Stribeck 效应的痕迹。工程上不用拟合复杂模型我一般用Coulomb 摩擦 黏性阻尼 死区三件套tau_friction fc * sign(qdot) fv * qdot |qdot| v_eps tau_deadzone dz * sign(tau_cmd) |tau_cmd| dz 时输出为 0fc、fv、dz三个参数每个关节一组写进配置文件。别小看dz它是低速爬行和静止抖动的直接来源。第三批是关节限位与实际零位。这一步必须实机逐关节做因为装配误差会让机械零位和编码器零位差个零点几度到一两度。零点偏 1 度策略学到的腿伸直在真机上就是腿略微弯曲闭环里会被放大成躯干高度偏差。提示辨识实验一定要在驱动器电流限幅调低的状态下做靠机械限位和低限流兜底别用训练时的满功率参数跑扫频很容易撞限位。2.2 一套能落地的辨识流程我用的流程大致是这样全程一个人一台机器一天能跑完静态称重与几何核对整机称重、单腿称重把其他腿架空的差值法、量关键连杆长度。误差超过 3% 就修 CAD 参数。吊装扫频机器吊起脚离地。对每个关节给一组正弦力矩指令频率从 0.1 Hz 到 2 Hz 扫幅值从小到大记录位置、速度、指令力矩、实际电流。目的是拿到指令—响应的幅频和相频特性顺便看出延迟。恒速摩擦测试给慢速斜坡速度目标记录维持该速度所需的力矩正反各做一遍取平均消掉重力分量吊装状态下重力分量接近零或用对称位置抵消。死区测试从零力矩开始缓慢增加记录关节开始转动的力矩阈值。延迟测试给一个阶跃力矩指令看实际力矩或电流响应上升的 10%—90% 时间以及从指令下发到响应的纯延迟。这个数直接进执行器模型。回写与验证把参数写进仿真模型跑一遍和真机相同的基准动作对比关节跟踪误差。一般能把误差压掉 40%—60%。延迟测量这一步很多人跳过但它极其关键。我测过不同驱动器纯延迟从 2 ms 到 20 ms 都有控制频率 100 Hz 时一个控制周期就是 10 ms延迟超一个周期闭环稳定性立刻变差。2.3 辨识结果的落库与版本管理这一点是血泪教训。辨识参数一定要和策略 checkpoint 绑定版本。我最早的目录结构是sim_config.yaml一路覆盖后来发现某次策略表现异常回头根本不知道当时用的是哪版摩擦参数白查了两天。现在我的做法是# identify/robot_v3_2024_11.yaml meta: robot: quadruped_v3 date: 2024-11-08 operator: dev_a note: 更换小腿外壳后重新辨识总质量 0.42kg inertial: total_mass: 12.85 com_offset: [0.003, -0.001, 0.012] joints: FL_hip: fc: 0.42 fv: 0.031 dz: 0.18 zero_offset: 0.0087 torque_limit: 24.0 velocity_limit: 21.0策略训练完成后checkpoint 目录里放一份identify_ref.txt指向这个文件的哈希。这样任何时候都能复现这个策略是在哪个物理模型上训出来的。3. 策略二执行器建模别把电机当成理想力矩源如果只能做一件事我会选执行器建模。原因很简单策略输出的动作最终要靠电机去执行而电机是整个链路里非线性最强、带宽最低、最容易被理想化的一环。仿真里默认给多少力矩出多少力矩、立刻生效真机上完全不是。3.1 电机加减速器加驱动器非理想在哪我一般关注这几个特性力矩饱和不是缓慢饱和是硬削顶。仿真里如果不做削顶策略会学出需要 40 N·m的动作真机只能给 24 N·m行为直接崩。速度相关的能力衰减高速时反电动势上升可用力矩下降形成一条力矩—速度包络线。仿真里通常只做常数限幅忽略了这条包络。一阶惯性指令力矩到实际力矩之间有一个时间常数通常几毫秒到几十毫秒。这个时间常数和控制周期同量级时会引入显著相位滞后。纯延迟通信、驱动内部处理、电流环采样叠加出来的死时间。齿隙与死区减速器背隙让正反向切换时有一个空行程表现为过零时的迟滞。温漂连续运行后绕组温度上升同样电流出力矩下降。长时间跑的策略会越跑越软。最后一条特别隐蔽。我有一次做连续 20 分钟的行走测试前 5 分钟指标正常10 分钟后躯干高度缓慢下降15 分钟后开始拖地。查了半天是电机热了力矩系数下降约 8%策略的补偿能力被吃掉了。3.2 一阶惯性加延迟加死区搭一个够用的执行器模型不需要上完整的电机电磁模型工程上一个三段式模型就能吃掉 80% 的误差。我在仿真里这样实现class ActuatorModel: def __init__(self, dt, tau_motor0.012, delay_steps2, t_limit24.0, v_limit21.0, deadzone0.18): self.dt dt self.alpha dt / (tau_motor dt) # 一阶惯性离散化系数 self.delay_steps delay_steps self.t_limit t_limit self.v_limit v_limit self.deadzone deadzone self.buf deque([0.0] * (delay_steps 1), maxlendelay_steps 1) self.tau_actual 0.0 def step(self, tau_cmd, qdot): # 1) 死区 if abs(tau_cmd) self.deadzone: tau_cmd 0.0 # 2) 力矩-速度包络约束 envelope self.t_limit * max(0.0, 1.0 - min(abs(qdot) / self.v_limit, 1.0) ** 2) tau_cmd max(-envelope, min(envelope, tau_cmd)) # 3) 纯延迟 self.buf.append(tau_cmd) tau_delayed self.buf[0] # 4) 一阶惯性 self.tau_actual self.alpha * (tau_delayed - self.tau_actual) return self.tau_actualtau_motor和delay_steps直接来自 2.2 节的阶跃测试。dt是控制周期注意这里的模型要和策略的控制频率一致不然离散化误差会重新引入相位问题。注意这个模型在仿真里是训练时也启用的不是只在测试时加。很多人只在评估时加执行器模型训练时还是理想力矩那策略学出来的动作依然会依赖理想响应。训练和评估用同一套执行器模型是 sim-to-real 的基本纪律。3.3 控制频率与时延预算怎么算控制频率不是越高越好但太低一定不行。我一般按总延迟不超过 1.5 个控制周期来定总延迟 传感采样延迟 通信延迟 推理时间 执行器纯延迟 控制周期 T 1 / f 经验约束总延迟 1.5 * T举个实际例子IMU 采样加滤波群延迟约 3 ms编码器总线读取 1 ms中间件通信 1 ms策略推理 0.4 ms三层 MLP256 隐藏单元单核 CPU执行器纯延迟 6 ms合计约 11.4 ms。按 1.5 倍约束T 7.6 ms也就是频率不高于 130 Hz。我最后取 100 Hz留出余量应对偶发的调度抖动。控制频率周期总延迟占比实测表现50 Hz20 ms57%站立稳行走发飘抗扰差100 Hz10 ms114%综合最优足式常用200 Hz5 ms228%抖动明显需要额外滤波500 Hz2 ms570%延迟主导闭环容易极限环这个表是我在四足平台上多次实测的经验值不同硬件差别很大但趋势是一致的频率超过某个点之后延迟不变而周期变短相位裕度反而被吃掉。别盲目追高频率。4. 策略三域随机化但要随机在该随机的地方域随机化是最容易被滥用的一条。我见过把摩擦系数随机到 0.01—2.0、质量随机 ±50%、观测噪声随机到信号本身的量级结果策略学得极其保守动作幅度小、响应慢真机表现还不如不做随机化。正确的做法是分层、有依据、可验证。4.1 随机化维度分三层来管我按确定程度把维度分三层第一层不随机硬编码几何尺寸、关节轴向、总质量这些测得很准的量。随机它们只会引入无意义的难度还会让策略对正确信息不信任。有人会说随机化能提升鲁棒性但结构参数是真的准随机它属于给自己加戏。第二层小范围随机±5% 以内负载质量、质心偏移、连杆质量。这些会随挂载变化但不会变太多。第三层大范围随机±30% 甚至更多摩擦系数、阻尼、执行器增益、纯延迟、观测噪声、地面摩擦与刚度。这些确实测不准、确实会变而且变化对稳定性影响大。4.2 随机范围怎么定用实测区间不要拍脑袋我的定法是三步走先测同一台机器在不同温度、不同地面、不同电量下各测一组参数得到实测分布。再扩以实测区间为基础外扩 30%—50% 作为训练随机范围。外扩是为了覆盖没测到但可能发生的工况比如换地面材质。再收训练两三轮后看策略在仿真里的性能下降幅度。如果平均奖励下降超过 20%说明随机化范围太宽往回收。具体的配置我一般这样写randomization: # 第一层锁定不随机 locked: [link_length, joint_axis, total_mass] # 第二层小范围 payload_mass: { range: [0.0, 1.5], mode: uniform } # 单位 kg com_offset: { range: [-0.02, 0.02], mode: uniform } # 第三层大范围 joint_friction: { scale: [0.6, 1.6], mode: multiplicative } # 相对辨识值 joint_damping: { scale: [0.5, 2.0], mode: multiplicative } actuator_gain: { scale: [0.85, 1.15], mode: multiplicative } actuator_delay: { range: [0.004, 0.014], mode: uniform } # 秒 obs_noise_std: { range: [0.0, 0.02], mode: gaussian } ground_friction: { range: [0.4, 1.2], mode: uniform } ground_stiffness: { range: [0.7, 1.3], mode: multiplicative }注意joint_friction用的是乘性随机即以辨识值为基准按倍数缩放。这是我强烈推荐的摩擦这类量本身有量级差异大关节 0.8小关节 0.15加性随机会让小关节被过度扰动。4.3 随机化过头的副作用与收敛判断随机化过头有三个典型症状我列出来方便对照动作幅度变小策略学会了少动少错步幅明显小于仿真最优。响应变慢对外部扰动的恢复时间拉长姿态回调拖沓。性能方差变大不同随机种子训出来的策略表现差异巨大说明优化景观被噪声搅乱了。判断是否过头我会看两个曲线训练奖励的方差以及评估时在无随机化环境下的性能。理想情况是——训练时带随机化评估时也带随机化同时单独跑一组干净环境的评估。如果干净环境性能掉得厉害说明策略在靠随机化蒙混如果干净环境性能好但真机差说明随机化范围不够或者维度选错。还有一个技巧分阶段打开随机化。前 20% 训练步只开小范围随机中间 50% 逐步加宽到目标范围最后 30% 固定在最宽范围。这个调度比一开始就全开收敛快得多我实测训练步数能少 25% 左右。5. 策略四观测与动作空间对齐把感知错位消掉这一节是我认为最容易翻车、也最容易被跳过的部分。因为它不属于算法看起来只是接口对齐但实际上一半的真机性能断崖都出在这里。5.1 观测侧噪声、延迟、滤波、单位一个都不能错我做观测对齐时按一张固定清单逐项核对顺序与维度仿真里观测向量拼接顺序必须和真机拼装代码完全一致。这个听起来很低级但我见过不止一次因为关节顺序写成[FL, FR, RL, RR]还是[FL, RL, FR, RR]不一致导致策略输出完全错乱。单位弧度还是角度、米还是毫米、弧度每秒还是度每秒。仿真里用弧度真机驱动回的是角度中间少乘一个pi/180策略就直接废了。符号约定关节正方向、IMU 的 roll/pitch/yaw 符号、角速度正方向。四足平台上左右腿的符号约定不一致特别常见。坐标系世界系、机体系、IMU 系。姿态角是从哪个系转到哪个系重力投影用的是哪个系下的量。噪声与滤波真机数据一定有噪声仿真里要加对应量级的噪声。但更重要的是滤波带来的群延迟——低通滤波器降噪的同时会引入相位滞后这个滞后等效于观测延迟必须在仿真里补上。归一化统计量训练时算的 mean/std 必须固化并原样用于真机绝对不能在真机上重新统计。我踩过这个坑真机首帧数据分布和训练集不同在线归一化直接把观测推到了分布外。观测延迟的补偿有两种做法我一般都用一点一是在仿真里直接给观测加k步延迟让策略学会用历史信息推断当前状态二是在部署侧做简单的状态预测用上一帧速度外推但预测步长不超过 1 个周期多了反而引入振荡。5.2 动作侧限幅、平滑、缩放与安全投影动作侧的对齐同样重要而且直接影响安全缩放系数策略输出通常是 [-1, 1]要乘上动作尺度比如关节位置增量 ±0.25 rad或力矩 ±24 N·m。这个尺度训练和部署必须一致。限幅一定要在部署侧再限一次不能信任策略输出。我习惯在动作发出前做三重限幅幅值限幅、变化率限幅、以及基于当前关节位置的软限位投影。平滑策略在 100 Hz 输出直接下发会有高频抖。我一般加一个一阶低通时间常数 10—20 ms但要注意这会引入额外延迟得算进 3.3 节的延迟预算里。动作类型是输出关节位置目标还是力矩。位置目标模式下底层还有一个 PD 环PD 增益本身也是 sim-to-real 的差距来源必须在仿真里建模同样的 PD。def project_action(raw_action, last_action, cfg): a raw_action * cfg.action_scale # 缩放 a np.clip(a, -cfg.hard_limit, cfg.hard_limit) # 幅值限幅 delta a - last_action delta np.clip(delta, -cfg.rate_limit, cfg.rate_limit) # 变化率限幅 a last_action delta a lowpass(a, alphacfg.lpf_alpha) # 平滑 return a注意rate_limit这个参数在仿真里也要用同样的值否则策略会学出依赖快速切换的动作真机上被限速之后行为完全变形。5.3 一份观测对齐检查清单我把这张表贴在工位上每次新平台部署前逐项打勾检查项检查方法常见错误观测维度与顺序打印首帧向量逐位对照关节顺序错位单位一致性手动转一个关节看数值变化量角度/弧度混用符号约定正方向推一下看符号左右腿符号相反IMU 安装偏差静置读 roll/pitch应为 0安装倾斜未补偿归一化统计量对比训练时的 mean/std真机在线重算观测延迟打标记信号测端到端延迟滤波器群延迟漏算控制频率打时间戳统计周期抖动实际频率低于设定动作尺度单步小动作看实际位移缩放系数不一致这张表里我最想强调的是IMU 安装偏差。IMU 装在机身上很难做到完全水平静置时读数可能已经有 1—2 度的滚转。如果不在部署侧补偿掉策略会一直以为机器在斜坡上持续做姿态修正表现为缓慢漂移。补偿方法很简单静置采 200 帧取平均作为零偏写进配置。6. 策略五课程学习加安全约束让真机训练不至于摔机前四条做完策略在真机上大概率已经能站着、能慢走了。剩下的是敢不敢放手和怎么把最后那点性能榨出来。这一步的核心是用课程学习控制风险用安全约束兜住底线。6.1 从扶着走到放手跑的课程设计仿真里的课程学习是为了加速收敛真机上的课程学习是为了控制试错成本。我一般按这个梯度推进固定基座机器吊在支架上脚悬空只验证关节动作方向和幅度是否正确。这一步能抓出 5.1、5.2 节的对齐错误成本为零。部分承重支架承担 60%—70% 体重验证站立和重心转移。这时即使策略崩了也不会摔。全重静止站立支架撤掉但旁边有人扶先站 30 秒。看躯干姿态角速度的均方根正常应在 0.05 rad/s 以内。低速直线行走给一个保守的速度指令正常速度的 30%走 2 米。标准工况正常速度、转向、抗扰推。边界工况坡道、不平地面、负载变化。每一步设一个明确的通过判据不通过就回退到上一步。我用的判据大概是这样阶段通过判据失败处理固定基座关节跟踪误差 0.05 rad查对齐部分承重站立 60 s 不触发保护查力矩饱和全重站立姿态角速度 RMS 0.05 rad/s查执行器模型低速行走走 2 m 无跌倒、无急停查延迟预算标准工况连续 5 min 无异常查温漂这个表的价值在于每一步失败都能定位到前面某一节的问题。固定基座失败基本是对齐问题部分承重失败基本是执行器模型问题全重站立失败往往是观测延迟或 PD 增益长时间运行失败通常是温漂。有了这个映射排查效率会高很多。6.2 三层防护缺一层都不要上真机真机强化学习最怕的不是性能差是策略输出一个离谱动作机器直接把减速器打坏。我的防护分三层任何一层都不能省第一层软件限位在动作投影里做关节软限位接近机械限位时按比例压缩动作幅度越近压缩越狠。这层在 5.2 节的project_action里实现。第二层力矩与速度限幅在驱动层做和策略无关。我一般把训练时的力矩上限再打个 0.8 的折扣作为部署限幅留出余量给瞬态。第三层硬件与外部急停按钮、吊装带松弛状态只在跌落瞬间绷紧、四周防护垫。这三样看起来土但救过我至少三次。提示吊装带一定要调成正常行走时不承重、跌倒时在躯干倾斜 30 度左右绷紧的长度。太紧会影响策略表现太松则没有保护作用。6.3 真机微调小学习率、少步数、强监控要不要在真机上继续训练我的答案是只在仿真已经调到瓶颈、且真机数据采集安全可控时才做而且要做得很克制。我通常的做法是学习率降到仿真最后的 1/10 到 1/20比如 1e-5 量级。每次只采 30—60 秒数据一个 batch 就更新一次更新完立刻回仿真环境做一次回归测试确认没跑偏。用保守的更新约束比如 PPO 的 clip 收紧到 0.1或者加一个 KL 早停阈值比如 0.01。只微调策略网络的最后一层或最后两层前面特征层冻结。这样能大幅降低一次更新毁掉全部能力的风险。这套做法的收益没有想象中大通常能把真机指标提升 5%—15%主要是补上那些仿真里没法建模的细节比如特定地面的打滑特性、某个关节的异常摩擦。如果仿真侧还有明显没做好的地方先回去改仿真别指望真机微调能救。7. 部署链路的工程实现从 checkpoint 到实时推理算法层面对齐了工程链路没搭好一样白搭。这一节讲我实际用的部署结构。7.1 从训练 checkpoint 到实时推理的落地路径我的链路是训练框架导出 → 通用模型格式 → 推理侧加载 → 数值一致性校验 → 实时循环调用。# 1. 导出训练侧 python export_policy.py \ --ckpt runs/quad_v3/ckpt_12000.pt \ --obs_dim 48 --act_dim 12 \ --out deploy/policy_v3.onnx # 2. 数值一致性校验离线 python verify_export.py \ --onnx deploy/policy_v3.onnx \ --ckpt runs/quad_v3/ckpt_12000.pt \ --samples 2000 --tol 1e-5数值一致性校验这一步绝对不能省。我遇到过一次导出后输出偏差 1e-3 量级的情况原因是有个自定义激活函数在导出时被近似了。1e-3 在策略输出上看起来很小但经过 PD 环放大后真机上表现为持续的小幅抖动。校验方法是同一批随机输入分别跑原模型和导出模型取最大绝对偏差要求小于 1e-5。推理侧我一般不用重型框架直接用一个轻量的推理运行时单线程绑定固定 CPU 核避免调度抖动。实测三层 MLP48→256→256→12单次推理在 0.2—0.4 ms完全够用。7.2 实时控制循环的时间预算实时循环我按固定周期跑结构大致是读—算—写三段每段都打时间戳while running: t0 now() obs read_sensors() # 编码器 IMU含滤波 t1 now() state build_observation(obs) action policy.infer(state) # 推理 t2 now() cmd project_action(action, last_action, cfg) write_command(cmd) # 下发到驱动 t3 now() log_cycle(t0, t1, t2, t3) # 环形缓冲用于事后分析 sleep_until(t0 period) # 固定周期不用 sleep(period)两个细节一是用sleep_until而不是sleep(period)避免累积漂移二是把每段的耗时都记下来我曾经靠这个日志发现某次通信总线偶发 8 ms 阻塞正是抖动的根因。时间预算我一般这样分配100 Hz周期 10 ms环节预算实测传感器读取与滤波2.0 ms1.4 ms观测构建0.5 ms0.2 ms策略推理1.0 ms0.35 ms动作投影0.3 ms0.1 ms指令下发1.5 ms0.9 ms余量4.7 ms—余量留到接近一半是有意的因为实时系统的抖动是长尾分布平均值好看不代表最坏情况好看。我要求 P99 的循环耗时不超过周期的 80%。7.3 日志与回放出问题能复现真机调试最痛苦的是偶发。我现在的日志系统会记录每帧的原始传感器值、构建后的观测、策略原始输出、投影后动作、下发指令、各段耗时、时间戳。用二进制格式写环形缓冲保留最近 5 分钟出问题时一键落盘。有了这份日志回放就很简单把观测序列离线喂给策略看输出是否和当时一致。如果一致说明问题在策略外部执行器、机械、环境如果不一致说明观测构建或推理链路有随机性比如归一化统计量被改了。这个二分法能省掉大量猜测时间。8. 常见问题与排查实录下面这些都是我真机上实际遇到过的按现象归类附排查顺序。8.1 高频抖动与低频漂移高频抖动肉眼可见的快速颤动频率和控制频率同量级或更低通常有三个来源按概率排序延迟不匹配仿真里执行器延迟设 2 ms真机 8 ms。排查方法给一个方波力矩指令测实际响应的相位滞后反推延迟。修正后重新训练通常一次就好。PD 增益过高位置目标模式下底层 PD 的刚度设得比仿真高。排查方法把刚度降到仿真值的 70%看抖动是否消失。观测噪声被放大速度估计用差分算出噪声大策略对噪声敏感。排查方法加低通或换用带滤波的速度估计同时在仿真里加对应噪声。低频漂移几十秒尺度的缓慢偏移多数是这几件事IMU 零偏未补偿或温漂导致零偏变化。静置 5 分钟看 roll/pitch 是否单调漂移。归一化统计量在真机上被重新计算。检查部署代码有没有偷偷调用update_stats。足端打滑导致的位姿估计累积误差。这在没有外部定位时几乎无解只能靠降低步速或引入视觉。8.2 性能断崖与一上真机就趴下仿真 100 分、真机 0 分这种断崖我遇到过三次原因各不相同第一次是关节顺序错位。仿真里我按[FL, FR, RL, RR]拼部署代码按[FL, RL, FR, RR]拼。策略看到的是乱序的关节角输出自然离谱。排查方法就是把首帧观测向量打印出来和仿真同姿态下的观测逐位对比一眼就能看出。第二次是动作尺度不一致。训练时位置增量尺度是 ±0.25 rad部署代码里写成了 ±0.5 rad。策略输出的动作被放大一倍膝关节直接怼到限位。这个错误的隐蔽性在于——机器还能站只是动作夸张、容易过冲很容易被误判成策略泛化不好。第三次是执行器力矩上限设错。仿真里是 24 N·m真机驱动配置里写的是 33 N·m驱动器额定值差 37%。策略学的是在 24 N·m 约束下的动作真机给到 33 N·m动态特性完全变了落地时冲击大得多。这三次的共同点是都不是算法问题都是配置问题。所以我现在的第一条排查原则是——先怀疑配置再怀疑策略。8.3 问题速查表现象最可能原因快速验证修正站立时高频颤动执行器延迟低估方波响应测相位重设延迟参数重训走两步就前倾摔倒质心或零位偏差静态称重、零位核对修模型参数越走越慢、躯干下沉电机温漂、电池压降连续 15 min 记录力矩加增益随机化或降速转向时单侧异常左右符号或摩擦不对称左右对称动作对比修符号、分别辨识抗扰后长时间振荡观测延迟 增益过高加延迟重训、降增益双管齐下起步瞬间触发保护动作变化率过大看首帧动作幅值加变化率限幅特定地面打滑地面摩擦未随机化换地面复现加地面参数随机长时运行性能衰减累积误差、温漂分段记录指标降时长、加在线适配9. 评测与迭代把感觉变好了变成指标变好了最后想聊一下迭代方法。真机调试最容易陷入的陷阱是凭感觉——今天改个参数觉得好一点明天改回去又觉得差不多一周下来没有任何积累。我的做法是维护一张部署指标卡每次改动前后各跑一遍固定流程记录同一组数字指标采集方式目标站立姿态角速度 RMS静立 30 s 0.05 rad/s直线行走横向偏差走 5 m测最大横向偏移 0.15 m关节跟踪误差 RMS全程记录 0.06 rad力矩饱和占比统计τ动作二阶差分均值反映抖动越低越好与基线比连续运行 10 min 性能衰减首末两分钟指标比 0.9这张卡的好处是改动效果一目了然而且能防止改 A 好了、改 B 又坏了的反复。我有一次为了降抖动加了较强的低通滤波抖动指标确实好了但延迟预算被吃掉行走横向偏差从 0.11 m 涨到 0.19 m。如果只看抖动我会以为这次改动是成功的。还有一点每次只改一个变量。这条听起来是常识但在真机调试的现场压力下特别容易破功——机器老摔人会本能地一次改三四个参数然后问题解决了也不知道是哪个起的作用下次再遇到还是不会。我现在强制自己写改动日志一次一条改完必须跑完整指标卡。我个人在几个项目里反复验证下来这套五大策略的流水线大概能把真机部署周期从三四周反复试错压缩到一周左右而且摔机次数明显下降。真正花时间的从来不是训练是把仿真和真机之间那些看不见的缝一条条补上——补完之后你会发现强化学习本身反而是整条链路上最省心的那一环。