☰
Datawhale MicroDuck 组队学习 Task 1 笔记:认识小鸭子与篮球平衡
2026/10/15 4:39:33 网站建设 项目流程

本期我自己动手跑通的部分:拿到公开的 b11 篮球策略、导出 ONNX、做 PyTorch/ONNX 逐步一致性、
生成自由球上的平衡视频、复现两种评测协议,并跑了一轮 5 次 PPO 更新的续训 smoke。
所有数字都来自本机实跑,末尾单列"这次没证明什么"。

环境:Ubuntu 22.04 云容器 / RTX 4080 SUPER 32G / Python 3.12.3 / 50 Hz 控制
代码:Vottivott/microduck-playground@aa5bd7909873a807fb9f4045f491519c443142e7(= HF 清单里的training.commit)
模型:公开 checkpointb11_6999,sha25657a322eff092cb71e7cba831791232f0cc4fd45ede0441cad6d87813b0033e41


一、先把"接口"搞清楚,再谈算法

很多人一上来就看 PPO 超参,但这个任务最难也最值钱的是接口——它是一份契约。

观测(actor,61 维)维度训练时的 term
身体角速度(陀螺仪)3base_ang_vel
重力在身体坐标系下的投影3projected_gravity
14 个关节位置14joint_pos
14 个关节速度14joint_vel
上一时刻14 维动作14actions
前向 / 横向 / 偏航速度指令3command(twist)
head_command 零填充4basketball_head_pad
body_command 槽(actor 填 0)6basketball_body_pad

动作是 14 维关节位置残差:下发目标 = 默认站姿 + 1.0 × 动作,再裁剪到关节行程,50 Hz 下发;
部署端没有动作低通滤波(平滑只体现在训练的action_rate_l2奖励里)。

三个容易忽略的细节:

  1. 51:61是占位槽。它原本服务 head/body 姿态任务,篮球任务里 actor 固定填 0——留着不删,是为了让 61 维契约和别的任务完全对齐,从而复用整条部署链路(ONNX 导出、BPU 推理、50 Hz 循环都不用改)。
  2. 动作是相对默认站姿的归一化残差,不是绝对关节角。所以同一个 actor 换个默认站姿就能站到别的东西上。
  3. 导出 ONNX 时,动作上限裁剪和关节行程裁剪都被烘焙进图里(bake_action_clip/bake_action_bounds),部署端拿到的是同一份带裁剪的策略。

关节顺序(ONNX 元数据,和训练一致):
left_hip_yaw, left_hip_roll, left_hip_pitch, left_knee, left_ankle, neck_pitch, head_pitch, head_yaw, head_roll, right_hip_yaw, right_hip_roll, right_hip_pitch, right_knee, right_ankle

二、为什么这个策略必须"带记忆"

actor 的输入里没有球——球在哪、滚多快,它看不见。这叫 blind actor。

但单帧状态不足以判断下一步:球往哪边滚、脚底在球面哪个位置接触,藏在"最近几十个控制步身体姿态怎么变"里。
所以网络结构是:

61D 本体感觉 → LSTM(256) → ELU MLP(512 → 256 → 128) → 14 维动作

训练时critic 是 privileged 的:它输入 76 维,多出来的正是球相对位置/速度(占body_command那个 6 维槽,
位置 ×3.0、速度 ×1.0)、脚部接触高度/时间/力,以及 base 线速度。
导出 ONNX 只保留 actor,所以真机不需要摄像头,也不需要球检测器。

我一开始把 “blind” 理解成"没记忆",这是错的。blind 说的是不看球;LSTM 的h/c就是记忆。

ONNX 契约(我们自己导出的图,opset 18,16 个节点,2.50 MB,sha2568b1c8149…):

inputs : obs[1,61], h_in[1,1,256], c_in[1,1,256] outputs: actions[1,14], h_out[1,1,256], c_out[1,1,256]

h_out/c_out必须传给下一帧;只在启动、策略切换、摔倒恢复、episode reset 时清零。
普通的速度指令变化不能清记忆,否则下一次踩到球面时策略会失去对球滚动趋势的估计。

三、奖励设计的关键:别把目标写成惩罚

训练日志里打印出的 13 项奖励,是理解这个任务行为的钥匙:

奖励项权重作用
track_linear_velocity+1.0速度跟踪(“在球上走”)
track_angular_velocity+0.5偏航跟踪
pose+1.0保持站姿
body_ang_vel−0.05抑制身体乱转
angular_momentum−0.02抑制角动量堆积
dof_pos_limits−1.0关节别顶限位
action_rate_l2−0.2动作变化惩罚
self_collisions−1.0自碰撞
upright+1.0身体直立
centered+2.0根部留在球心上方(权重最高)
feet_on_ball+1.0双脚在球面合理接触高度
height+1.0站在球顶,别蹲下也别陷进去
ball_speed−0.05球速正则,小权重

最反直觉的一条:球速只做很小的负项(−0.05),"滚动"交给速度跟踪去驱动。
如果反过来把球速当主要负项,策略最容易学到的行为是"僵在原地不动"——它确实不滚了,但也就不走了。

课程与随机化也值得记:训练用BallHoldAction从1 → 0.5 → 0.25 → 0.1 → 0.03 → 0逐步松开球
(1 相当于焊死,0 是完全自由),play/eval 默认 0;interval 事件push_robot每 1.5–3.0 s 推一次机器人
——这正是 b11 相对 b10 的唯一改动,却把 60 秒生存率从 92.19% 提到 97.85%。

四、实操:把环境搭干净比跑模型难

uv sync --locked在这台机器上只有 ~1 MB/s,而且卡在全平台索引解析上久久不动。最后用的办法是绕开实时解析:

# 1) 从提交的 uv.lock 离线导出精确版本清单(1119 行,带 sha256)uvexport--frozen--no-dev --no-emit-project--formatrequirements-txt-opins.txt# 2) 按清单直连国内镜像下载轮子(140 个 / 4.2 GB / 45 秒 / 逐个校验 sha256)# 关键:--frozen 会按 lock 里的 files.pythonhosted.org 地址下载(~0.4 MB/s),# 必须改成直连镜像,否则会以为"uv 很慢",其实是源不对# 3) 离线安装,再 editable 装项目uv pipinstall--no-index --find-links<wheels>-rpins.txt uv pipinstall--no-deps-e.

两个坑值得单独记:

  • 大文件限速:镜像对单连接的大文件(torch 858 MB)会压到 ~0.25 MB/s,但它支持 Range 请求——
    分 8 块并行下载再按序拼接、最后校验 sha256,几分钟就完了。
  • 代理是把双刃剑:学术代理能过 GitHub / HuggingFace,但会让国内镜像从 14 MB/s 掉到 0.2 MB/s。
    所以"下载镜像用直连、拉 GitHub 用代理"要分开设置(git 可单独git config --global http.proxy ...)。

还有一条更重要的教训:别把"我这边能跑"当成"可复现"。我一度在 main HEAD 上跑出结果,
后来发现发布清单里写的是training.commit,于是重新钉到aa5bd79重跑全链路,
并确认该提交的uv.lock与专题代码包的uv.lock字节一致(sha256affaa39d…),才敢说环境与发布口径一致。

五、结果:复核到了官方量级

HF 仓库里只有checkpoint.pt,没有policy.onnx,所以 ONNX 是我们自己导出的——这反而让链路更完整。

检查项结果
ONNX 导出opset 18、16 节点、obs[1,61]+h/c → actions[1,14]+h/c、2.50 MB、sha2568b1c8149…
PyTorch ↔ ONNX 逐步一致性(40 步,第 20 步含一次 reset)最大绝对误差1.430511474609375e-06
官方validation.json记录的同项检查1.430511474609375e-06(完全一致)
本地 ONNX Runtime(CPU)全 0 观测通过,输出形状正确且全部有限
256 环境 × 30 s(seed 101,command_scale 1)10 s100%· 20 s99.61%· 30 s98.83%
1024 环境 × 60 s × 3 seed(官方压力协议)2975/3072 = 96.84%(官方同协议 97.01%,差 5 个环境)
续训 smoke(64 环境 × 5 迭代)迭代 6999→7004,无 NaN,吞吐 1224–1743 env-step/s
官方仓库测试套件332 passed, 2 skipped

视频两段,同一 actor、同一个 ONNX,只改速度指令:

  • (0, 0, 0):250 步 = 5.0 s,0 次摔倒,单步推理 1.19 ms(控制预算 20 ms,余量充足);
  • 前向0.15 m/s:500 步 = 10.0 s,0 次摔倒,单步推理 1.15 ms——这段才真正体现"一边抬脚换重心、一边把球滚出去"。

续训那一轮还能看到"从恢复态重新站稳"的过程:Mean episode length从 16.00 步(0.32 s)一路涨到 68.33 步(1.37 s),
平均奖励 1.58 → 6.52。

六、顺手搞清的一件事:控制步 / 训练迭代 / episode

三个尺度混起来最容易读错,实际换算是:

概念尺度本次取值
物理步2.5 ms0.005 s(MuJoCo-Warp)
控制步20 ms0.02 s = 4 个物理步,一次观测 → 一次 14 维动作
episode10 s500 控制步
训练迭代0.48 s 仿真64 环境 × 24 控制步 =1536 个转移

所以教程里那句提醒要按字面理解:"10 个 iteration"是 10 轮采样+优化,不是每个动作只训练 10 步——
按本次配置是 64 × 24 × 10 = 15360 条转移。

七、一条从数据里看出来的观察

34 次摔倒里有33 次触发条件是low_height(根部相对球心太低),不是倾角、也不是偏移。再看数值:

状态根部高于球心球速倾角动作变化 RMS
正常(30 s 复核)0.2345 m0.115 m/s~0.04 rad0.233
失败瞬间0.16 m 左右0.46–0.49 m/s0.20–0.26 rad—

也就是说它不是"被推倒",更像"球被带快了 → 自己陷下去 → 失去球顶支撑"。
这条观察直接给出一个可验证的改动:把height权重(当前 1.0)加大,或把ball_speed正则(当前 −0.05)调强一点,
看low_height型失败是否下降、同时速度跟踪误差是否变差。这是我下一步最想做的对照实验。

八、常用的两条命令

# 导出 + 逐步一致性(含一次 reset 检查记忆清零)MICRODUCK_BB_BLIND=1MICRODUCK_BB_HISTORY=1uv run python scripts/export.py Mjlab-Basketball-MicroDuck\--checkpoint-file artifacts/basketball/checkpoint.pt --onnx-file outputs/basketball_b11.onnx uv run python scripts/verify_basketball_onnx_parity.py artifacts/basketball/checkpoint.pt outputs/basketball_b11.onnx# 续训 smoke / 官方三 seed 评测WANDB_MODE=disabled uv run python scripts/finetune_basketball.py artifacts/basketball/checkpoint.pt\--run-name smoke --num-envs64--iterations5--learning-rate 2e-5 --push-interval-s1.53--save-interval5uv run python scripts/eval_basketball_long.py artifacts/basketball/checkpoint.pt eval.json\--seed101--seconds60--num-envs1024--command-scale2--blind

九、这次没证明什么(避免误读)

  1. 这是公开 checkpoint 的本地复核,不是我们从随机初始化训练出来的策略。98.83%、96.84%都是复核数字,不能当自研成果。
  2. 全部来自 MuJoCo-Warp 仿真。官方明确说明真机时序与真实行为未验证,这些数字不能写成真实机器人成功率。
  3. smoke 不等于变好。5 次 PPO 更新、7680 个转移,只能证明采样/更新/保存/导出/一致性链路可用。

十、下一步想做的

  1. 奖励权重对照实验:加大height权重 / 调强ball_speed正则,在同一 60 秒协议下比较改动前后的生存率与速度跟踪误差。
  2. 记忆的边际贡献:按h/c清零频率或输入历史长度做消融(教程里 b4/b5 近似 0、b7 44.6%、b6 84.4% 已经给了线索,我还没亲手复现这条链)。
  3. 把速度跟踪做细:前向误差 0.108–0.154 m/s 相对指令幅度并不小,值得单独出一组曲线。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询