第 1 章 初识 Microduck

本章代码走读:Cargo.toml(workspace 根)、AGENTS.md(RL 仓库)、duck-control/src/obs.rs 开篇。

1.1 它是什么

Microduck 是 Pollen Robotics 推出的小型双足机器人,2026 年 8 月 27 日开启预售,定价 399 美元(税前),目标 2026 年圣诞节前在北美、欧洲和英国首发。四款配色:Cream、Graphite、Lavender、Sky。

硬件规格:

一个容易搞混的数字就此澄清:媒体报道的"15 电机"与 RL 仓库的"14 XL330"都对——策略世界是 14 维的,嘴是策略世界之外的第 15 个执行器。

1.2 软件全景与两个仓库的分工

软件以 Apache-2.0 开源在 GitHub 的 pollen-robotics 组织下(3D 模型文件为 CC BY-SA-NC):

pollen-robotics/microduck       # 机器人软件 + SDK:Rust workspace,22 个成员 crate
pollen-robotics/microduck_rl    # 强化学习训练:Python/mjlab(MuJoCo Warp)+ rsl_rl PPO

两者的接口契约只有两个文件大小:一个 61 维观测向量进,14 维动作出 的 ONNX 图,外加一份 manifest.json。RL 仓库的 AGENTS.md 第一段就把这条链路讲清楚了:

RL training environments for Microduck — a ~800 g, ~25 cm tall bipedal
robot with 14 Dynamixel XL330 servos — built on mjlab
(MuJoCo Warp) with PPO (rsl_rl). Policies are trained here at 50 Hz, exported to
ONNX, and deployed by the runtime in the `pollen-robotics/microduck` repo on
the real robot. Sim2real transfer is the whole point: every convention below
exists because breaking it produced a policy that worked in the viewer and
failed on hardware.

最后一句值得抄在工位上:"每一条约定之所以存在,都是因为打破它曾产出一个在查看器里工作、上机就翻车的策略。" 这句话是全书的题眼——后面所有章节,无论是 Rust 侧的观测组装还是 Python 侧的奖励调参,讲的都是这类"看不见的契约"。

1.3 代码走读:workspace 根 Cargo.toml

主仓库根目录的 Cargo.toml 是全书的第一个"架构文档"。它不只列成员,还把三个外部依赖的版本钉死策略写在 [workspace.metadata] 里,每一段注释都是一个真实事故的墓志铭:

# Workspace root for the robot daemon.
#
# One crate per service or tool, matching the service split in
# docs/design/architecture.md §1. `robotd`, `btd` and `mediad` are all siblings now.
[workspace]
resolver = "3"
members = ["btd", "configd", "duck-control", "duck-detect", "duck-ether", "duck-ipc-proto",
  "duckctl", "kinematics", "mediad", "odometry", "pad-imu", "padd", "pet-detect", "sounds",
  "tof", "updater", "robotctl", "robotd", "robotd-params", "test-support", "uyvy", "xtask"]

三段元数据(节选):

# ONNX Runtime, which robotd dlopens to run a policy. One source of truth: `xtask package`
# bakes these into the release's preinstall hook, and a test asserts scripts/setup-board.sh
# agrees with them. Two copies drifting apart is exactly how a board ended up with 1.20.1
# against an `ort` that panics below 1.23.
[workspace.metadata.onnxruntime]
floor  = "1.23"
target = "1.28.0"

# The official policy set, which `robotd` runs and which lives on the Hugging Face Hub rather
# than in this repository — a gait retrain should not need a daemon release, and a daemon fix
# should not re-ship six megabytes of unchanged weights.
[workspace.metadata.policies]
repo    = "pollen-robotics/microduck-policies"
version = "v5"

三个设计决策藏在这里:

  1. ONNX Runtime 是运行时 dlopen 的,不是编译期链接——所以它的版本约束必须有人守着,守的方式是"元数据 + 安装脚本 + 一个断言两者一致的测试"三方互锁;
  2. 官方策略集住在 Hugging Face Hub(microduck-policies,最低版本 v5),不进代码仓库——"步态重训不应该要求守护进程发版,守护进程修复也不应该重新分发六兆没变的权重"。这句注释把"策略即资产、与代码解耦"的部署哲学一句话讲完;
  3. Rockchip NPU 运行时(rknn v2.3.2)同样钉死——"比模型旧的运行时会在 rknn_init 处带着一个数字失败",这种注释只有踩过坑才写得出来。

1.4 代码走读:duck-control/src/obs.rs 开篇——"最高风险代码"

主仓库里 duck-control crate 的 obs.rs,模块注释开头一句就给全仓库的风险排了序:

//! The observation vector the policy sees.
//!
//! **This is the highest-risk code in the crate.** It is a flat array of 61 floats whose
//! every index must match what the policy was trained against. A wrong offset does not fail
//! loudly — it produces a plausible-looking robot that falls over, and the symptom looks
//! like a tuning or timing problem rather than an indexing one.

61 维观测的完整布局(这段表在部署侧和训练侧各有一份,必须逐位一致):

index   width  contents
0..3        3  gyro, trunk frame, rad/s
3..6        3  projected gravity, trunk frame, unit vector
6..20      14  joint position minus home pose, mouth excluded
20..34     14  joint velocity, mouth excluded
34..48     14  previous action, mouth excluded
48..61     13  command (below)

命令块 13 维:速度指令 3(vx、vy、vyaw)+ 头部姿态 4 + 身体姿态 6。第 8 章会从训练侧看到同一张表的镜像,第 4 章会看到 robotd 如何每个 tick 把它组装出来。

为什么一个 61 个浮点数的数组是"最高风险代码"? 因为它失败的形态不是报错,而是"一只看起来合理但站不住的鸭子"——症状像调参问题,病因却是索引错位。这是所有跨进程、跨语言 ML 系统的通病,Microduck 用文档加注释加双侧常量断言来防御(duck_ipc_proto::POLICY_OBS_LEN 与本 crate 的 OBS_LEN 有编译期一致性断言)。

1.5 为什么它值得研究

  1. 小而完整,且注释即历史。两个仓库的代码注释里大量记录了"哪次实验、哪个 run、什么数字、为什么改"——不是教学示例代码的整洁,而是生产系统的诚实。
  2. sim2real 的全链路样板。从 Onshape CAD 导出 MJCF、BAM 执行器建模、齿隙注入、域随机化、ONNX 导出烘焙归一化器、Hub 分发、守护进程加载——每一环都有真实代码。
  3. 消费级产品工程。配网、蓝牙配对、WebRTC、签名更新、日志预算——研究原型最欠的债这里都还了。

1.6 本章小结

Microduck = 14 个策略舵机 + 1 个嘴 + 一颗 RK3566 + 两个开源仓库。两个仓库之间只隔着一张 [1,61] → [1,14] 的 ONNX 图,而这个接口的每一侧都把"对齐"当作最高风险事项对待。下一章进入 Rust workspace 的内部结构。