skip to content
tlj 的工程笔记
← 项目

具身智能黑客松:人形运控 + VLA 操作双赛道

比赛

北京人形机器人创新中心 + 百度智能云「具身智能机器人黑客松」的单人参赛复盘。一条腿是 TienKung-Lab 复杂地形运控,一条腿是 ACT 五任务操作。两赛道都在截止前提交,但都没有官方成绩。真正值得写的是两起工程险情(限时算力到点销毁、1292 字节差点报废整条 VLA 腿)和一次 reward 涨了一倍、机器人其实在原地踏步的假象。

时间
2026-06 ~ 2026-08
角色
需求与赛道取舍、技术路线拍板、质疑与验收(代码由 AI 编写执行)
成果
两赛道均在截止前完成提交:运控自测净前进 0.209 m/s、直立 71.6%;VLA 五任务各约 25 次执行全部 task-ok。无官方成绩,VLA 闭环成功率约 0 且已诊断到底

北京人形机器人创新中心 + 百度智能云办的「具身智能机器人黑客松」。我单人参赛,报了两个赛道:运控·复杂地形(基于开源 TienKung-Lab,天工人形)和 VLA·操作(五个 ACT 任务)。动作模仿子赛道最终没做。

先把结论摆在前面:两条腿都在截止前提交了,但都没有任何官方分数或名次。 运控是邮件提交、无反馈;VLA 的分数只在官方评测平台上,本机网络连不到那个平台,我从头到尾没亲眼看到过数字。自评是名次弱。这页有价值的不是成绩,是过程里的两起险情和一次 reward 假象。

时间线:一份手册推翻了三条继承认知

6-10 开始预研。7-17 17:00 官方 GPU 机开通,7-24 17:00 到点自动释放——这是个硬窗口,不是软 deadline。

7-21 才拿到官方手册,一读推翻了三条一直当前提在用的认知:截止是 07-31 不是 07-24;VLA 赛道只给评测算力、不给训练算力;赛题是 TianYi2.0 + xMimic 而不是 RoboMIND,前期情报押的基线全部作废。7-27 评测平台开放,7-30~7-31 正式评测,7-31 24:00 两赛道均已提交。

两起工程险情

这两件事比任何指标都值得写,因为它们各自差一点就让整条腿归零。

一、限时算力到点销毁,唯一成果靠抢救

现象:官方机是容器,对外网络几乎全封——只有几个镜像域可达,github / pytorch / 微软全部 timeout。进出数据只能靠本机中转 + tar-over-ssh 流式推拉。7-23 起原本走的网盘备份链路又出现账号级 403,等于断了一条腿。而机器 7-24 17:00 按时释放,上面的东西全部消失。

根因:不是网络,是我一直在手动挑文件的低效模式里,根本没有一个在跑的自动备份——没 cron、没进程。用户当场点破的那句是”为什么不设置自动全部传输”。这句话的分量在于:手动挑的前提是”我知道哪些重要”,而限时资源销毁时,你恰恰来不及重新判断什么重要。

解法:7-24 上午把 8 个实验 run(8.4 G)推到网盘,把唯一成果(冠军 run 全量 + 5 个 ckpt + 提交包 + 部署代码)按整目录拉回本机。核心成果 6 件(5 个 agent_best.ckpt + model_21299.pt)逐件 md5 硬验证——不是”看着文件在那儿”就算数:model_21299.pt 22708831、5 个 ckpt 各 336005993,全部对上;运控提交包那 10 件物料另走逐项对照验收(含 onnx 2206520)。17:00 机器按时释放。

判断机器到底有没有被回收,有个反直觉的细节:SSH 和裸 TCP 全不可达、但 ICMP 还通。这不是网络抖动,是实例已经被回收、地址落到了边缘网关上——ping 得通只说明有东西在应答,不说明你的机器还在。

当天没做到的那一半,我当场就标了:抢救完成时,验证强度只到”本机 md5 硬验 + 远端服务端尺寸与 manifest 佐证”,云端那份没有下载回验、没有逐字节硬核对,这一点在当时的记录里如实写了。真正的 6/6 唯一成果各 ≥2 份独立硬验证副本(本机 md5 + 云端逐字节 md5 往返)是赛后 8 月初做备份基建时才补齐的,不是抢救当天的功劳。把这两件事分开记,比合并成一句漂亮结论有用得多。

顺带记下一个静默的洞:某网盘的 CLI 在后台 / cron 环境下会失败(它要 tty)。所以自动备份改用另一家,那家只留作手动首选——否则就会变成”以为在自动备、其实那个 cron 从来没跑成过”。

教训:限时算力是不可再生资源;备份必须是在跑的自动化,不是手动挑;“传完了”和”验过了”是两件事,服务端尺寸对上不等于下载回来逐字节一致。

二、1292 字节差点报废整条 VLA 腿

现象:本机盘用到 93%,为了腾空间删掉一个已经打包过的归档。删之前我核对了——那个 336 MB 的 agent_best.ckpt 在别处确实有一份,判定冗余,放行。漏掉的是同目录里 1292 字节dataset_stats.pkl(训练时的归一化统计)。ACT 推理要求这个文件与 ckpt 同目录,缺了直接 FileNotFoundError。07-27 正式评测前准备部署包时才发现五个任务的 stats 全丢了。

代价评估:如果找不回来,要么重下 57 G 数据集重算归一化,要么冒归一化漂移的风险硬跑。在评测开始前几天,这等于整条 VLA 腿报废。

解法:4 个从另一个进程 07-24 保命时留在临时目录里的完整副本中捞回,剩下的 lab_task_03 从某网盘 22 G 全量备份里的 lab_task_03.tar流式取出(不落全量、只取那一个成员)。5/5 全部找回并逐件校验。

回头看,救回那 4 个的其实是三天前那个”低效”的动作:抢救时是按整目录拷而不是挑文件拷,所以 1292 字节的 stats 顺带被带了出来。当时嫌整目录笨重,结果它成了三天后的保险——这不是台账里写好的因果,是复盘时才看出来的。

根因:我验错了完整性维度。删 bundle 时只枚举了最大最显眼的文件,没有 diff 全部内容。

教训:冗余 < 错误 < 丢失。盘紧的正确动作是加盘或 offload,不是删成果。同目录的小文件(stats / config)最容易被”只拷大件”漏掉,而它们往往最致命——尺寸和重要性没有相关性。

运控赛道

最终提交 model_21299(变体 vC_fwd,iter 21299,22,708,831 B)。观测 750 维(75 维单帧 × 10 帧 history)→ 动作 20 维(12 腿 + 8 臂,不含腰),actor 是 MLP 750-512-256-128-20,551,316 参数。8192 个并行环境,约 4.56 s/iter。

客观指标不看 reward,用自写的运动学 probe 测(净前进速度 + 直立占比,对 reset 免疫):净前进 0.209 m/s、直立 71.6%。训练日志 iter21299 侧:mean_reward 42.77 / ep_len 817 / terrain_levels 2.09 / track_lin_vel_xy_exp 1.204。

变体谱系(负结果是这张表的主要内容)

变体净前进 m/s直立结论
vA_rough0.085只证明移植跑通,砍
vB_comp6−0.010reward 26→52 但实测原地踏步,否决,留作负结果基线
vC_fwd0.20971.6%采纳,最终提交
vD_push0.210plateau,“加速”这根杠杆到顶
vR_recovery0.22366.6%更快但更爱摔,否决作主策略
vS_stable0.069摔倒惩罚 −200→−350 反而更慢,backfire
vP_clean0.189清理抢梯度项,无改善

配置上用 walk_cfg.py 的 env-gate 开关,一份安装并行跑多路,只加门控不删原配置——这样任何一路失败都不会污染基线。

提交包

10 件:policy.onnx(2,206,520 B 完整格式)、policy.pt、35 秒 xSim_mujoco 演示视频、剥离 ROS2 的 Deploy 3.0 sim2sim 代码、xsim_direct_drive.py、agent.yaml / env.yaml、onnx_verification.txt、eval_model_21299.md、SUBMIT_README.md。

onnx 数值一致性:5 次随机 obs 前向,onnx vs jit .pt 最大绝对误差 2.146e-06(门槛 1e-5)。这一步是在训练刚开始的时候就做掉的,理由很简单:导出这关过不了,后面全是白训。

接口核对上有个挺舒服的发现:官方 Deploy 3.0 的 walk_amp 是 obs 840 / action 23,与我方 750 / 20 的差值恰好是 3 个腰关节(84−75 = 3×3、23−20 = 3),且单帧组成两边同序。所以我方策略是状态机的 drop-in 替换。

VLA 赛道

数据是 X-Humanoid VLA-Challenge-Dataset,56.93 GiB / 1014 条 hdf5 / 5 个子集,state-action 26 维(左臂 7、左手 6、右臂 7、右手 6)。五个任务:齿轮入托盘、开关入篮、传开关到托盘、开电子天平门、关电子天平门。

模型用官方 ACT baseline(纯 torch,不需要 Isaac),ResNet18 backbone,83.92M 参数,chunk 50。五个 agent_best.ckpt 各 336,005,993 B。val_loss 分别是 0.109 / 0.298 / 0.182 / 0.243 / 0.276。评测走 ZMQ 对接组委会仿真,五个任务各约 25 次执行(官方要求 20+),全部 task-ok。

实测闭环成功率约 0。

把低分诊断做到底

val_loss 0.1 和成功率 0 放在一起,第一反应一定是”哪里有 bug”。我要求把外因一条条排干净,最后拿到三条决定性证据:

  1. 对齐数据与原始关节数据逐字节相同(仅采样率 2 倍差异)——排除系统性关节偏移。
  2. 离线 replay:模型预测的 action 与 hdf5 里记录的 action 吻合,整段 RMS 0.1~0.23 rad,正好对应 val_loss 的量级——说明推理链路忠实,没有隐藏 bug。
  3. 亲眼比对训练帧与仿真评测帧:同为 Isaac 渲染、同视觉域、同场景(训练数据本身就是仿真遥操作采集的)——排除 sim-to-sim 域差。

结论:val_loss 0.1~0.3 与成功率约 0 并不矛盾。 这是行为克隆开环训练、闭环 rollout 时误差累积走出分布(OOD)的结构性天花板,不是 bug、不是域差。开环每一步都对着专家轨迹上的状态预测,闭环里第一步的小偏差把机器人带到训练分布外,之后每步都在没见过的状态上预测,误差自乘。这个诊断的价值在于它划清了边界:想提成功率不是去调 lr,是要上 chunk 聚合 / 更强 backbone / 交互式数据(DAgger 类)或者干脆换范式。

其他工程坑

  1. reward 假象(最值钱的一条):vB 的 reward 从 26 涨到 52,看起来一路向好。probe 一测,净前进速度六千迭代死卡在 0——机器人找到了”原地踏步刷步态奖励 + 存活奖励”的局部最优。识破它靠的是自写运动学 probe,不是看 reward 曲线;而触发我去写 probe 的是用户一句”这么顺利?你确定吗”。locomotion 必须量净前进速度,训练指标不是比赛分数。 补一句不太光彩但必须写的:这个 probe 自己也先错过一次。早期它报”70% 摔倒”,查下去是工具里的 sticky-flag——姿态标志一旦置位就不再清除,于是一次瞬时倾斜被算成了后面全程摔倒。修掉之后实测 vA 直立 97.3%、vB 98.9%。所以”别信 reward、信我的 probe”这句话本身也不成立,正确的是每一层测量都要被质疑一次,包括你为了质疑别人而造的那个工具。
  2. 杠杆到顶就要换杠杆:vC 靠加大前进奖励破了原地最优(0 → 0.209),vD 继续加只到 0.210。换的两条新杠杆(降摔倒惩罚 vS、清理抢梯度项 vP)也都失败——三条路一起证明 vC 就是当前配置下的天花板,再调是浪费窗口。
  3. torch.cuda.is_available() 是 True 但跑不了任何 kernel:预装的 torch 只编到 sm_90,卡是 sm_120,一律 no kernel image。要装对 CUDA 组合、并且实测一次真卷积跑通才算数,is_available() 什么都不证明。
  4. 离线机上唯一的真 blocker:某可视化配置的 debug_vis=True 会去拉远端资产服务器上的 USD,离线环境下无限等待。通用死法:凡 debug_vis / marker / GroundPlane 这类东西,先查它是否走远端资产源。
  5. 日志有结果但进程是死的:训到 max 之后进程不退出、空转了 8 小时,而我读的是 stale 日志、当成实时进度。判断”还在跑”要验新鲜度(日志 mtime、两次读 iter 是否变、GPU util、进程 etimes),后来上了 watchdog。另一个连带坑:warm-start 的 iter 是全局累加的(1300 + 20000 → 21300),拿”相对 max”判完成会把卡死误判成训完。
  6. 两个日志源打架该信谁:查进程自己打开的文件描述符(/proc/<pid>/fd),找到真正在被写的那个 log 来裁决。这比看时间戳可靠。
  7. 评测会话一次性、掉线不可重连:握手只在会话起点发一次,驱动端中途死掉的话,仿真会带着一个冻住的机器人跑完剩余全部场景、全判失败,整场报废。我曾经因为给 runner 套了一个超时上限,把一场正在计分的活会话杀掉。之后的铁律:脱离 SSH 挂着跑、绝不套超时上限、一口气驱动到自然结束。
  8. 配置错被过度理论化:评测第一次卡死,根因只是端口用了默认值而实际不同——一个参数。但我从那个现象推断出一整套”计分必须由平台侧发起”的架构理论,越走越远。用户一句”别瞎测赶紧看权威文档”把我拉回来,官方群里一条消息秒解。教训:握手不通,先穷尽最笨的端口 / host / 默认值,再上升到协议层假设。

被否决的方案

诚实的边界