Research Box 与机器人数据探索:一次把完整具身研发闭环从零搭到可运行的先锋版本

设计原则存续

第五季客户 Demo 交付之后,销售给出了下一个目标——把已经证明"这条路走得通"的一次性能力,做成一个可以本地化部署、面向小型具身团队的完整研发平台。3 个月,4 个人,约 100 万投入,覆盖设备接入 → 机器人对接 → 数据采集 → 数据集/血缘 → 标注 → 训练 SDK → 存储 → K8s 集群资源的整条链路。核心技术闭环完成度到了 90%+,机械臂和机器人接进来能真正跑通"注册 → 唤醒 → 数据生产 → 标注 → 入库 → 训练"这条主路径。同时借着机器人接入这件事,第一次把机器人当成"一台计算机"来拆解,形成了视频与 ROS 数据解耦、边缘算力职责划分这两条后续一直在延续的判断。这个项目最后没有形成商业转化,代码也没有被后续系统直接继承,但设计原则被完整继承下来——采集系统、后训练系统在两个月后的重构里,几乎都是在吸收这套原则的基础上重新长出来的。

项目定位与边界

Research Box 不是"一个大而全的平台",也不是"想做什么就做什么"。它的目标从一开始就非常具体:销售在市场上碰到的是十人以下的小型具身研发团队,这些团队想要一个能本地化一次部署、把自己的机器人接进来、跑通采集到训练的完整闭环的东西。销售判断只要能把这样一套东西做出来,就能卖得掉。

从这个目标出发,我们对一个大型研发平台做了主动的、有边界的简化:小团队没有多供应商,没有配额争抢,数据量在可以手工兜住的量级,机器人数量也少。基于这个前提,把大平台里那些针对规模而存在的复杂度整体砍掉,让剩下的东西轻到十人以下的团队可以自己搭起来、自己用起来。这个简化不是"能少做就少做"的偷懒,而是对目标用户真实场景的准确描述——真正的问题在后面,当规模场景反过来找上门的时候,我们才发现这些被砍掉的东西里,有些补起来的代价是可控的,有些是不可控的。这一节留到章末再讲。

我在这个项目里是核心研发 + 整体技术方案负责人。除了 Kubernetes/底层 Infra(由 Infra 团队接管)和训练任务资源调度(由汇报给我的一名研发同学承担)之外,从产品层到应用层的主体建设——设备管理、GPU 资源管理、机器人对接、数据集、Episode 数据池、数据集派生和血缘、检索、存储管理、采集、遥操录制、标注、人员分配、Pipeline、Pod 数据挂载、训练 SDK、LiRobot 格式转换、页面和接口——基本都是我做的。3 个月里,我的时间大约一半在写代码,五分之二在架构和方案设计,五分之一在带另外的研发一起做(团队里几个人效率都不错,不用花大量时间去教导)。

章一:平台建设

MVP 抽象边界

处境

销售给的目标是"能本地化一次部署、支持用户机器人跑通完整闭环"。要在 3 个月、4 个人的规模里把这么一套东西做出来,就必须先决定:哪些东西现在做到什么程度、哪些东西现在完全不做。

桌上的选项

一条路是按"面向未来所有客户"的规格去搭系统的骨架——从一开始就把多供应商、配额、多级质检、结算、外部数据源采购这些能力都放进抽象里,先把架子拉大,功能一点点填。这条路的好处是未来扩展的时候不用改抽象,坏处是当前 4 个人 3 个月根本填不满,而且填不满的部分会在演示环节暴露出"空壳"的感觉。

另一条路是完全按目标用户的真实场景来定抽象——十人以下小团队,那就假设没有供应商、没有配额、没有多级质检、没有结算、没有三方数据采购,业务实体尽量少,业务关系尽量简单,一条采集要求挂一批人头就行。抽象小到刚好够用户完成一次完整闭环,剩下的能力全部延后。

我的选择

选了后者,并且对每一处砍掉的能力都能说清楚为什么这一刀砍在这里。

关于供应商:小团队只有自己的采集员,不需要多供应商模型,采集项目里把要求和人头直接挂上就够了,不做供应商这个业务实体。

关于配额和资源争抢:小团队的机器人、GPU、存储都在一个可以互相看见的物理范围内,不存在需要一个软件系统来分配和结算的资源竞争,做出来反而是负担。

关于多级质检:数据量小的时候,人工兜得住;用户团队自己就是生产者,也是质检者,不需要"生产质检 + 平台质检 + 供应商质检"这种多角色多层次的质检模型。

关于结算:不面向三方供应商,就没有结算。

关于外部数据/模型的采购接入:目标场景是用户自己的机器人自己采数据,不是采购别人的数据,所以数据/模型的导入被限定在 Kubernetes 上通过挂载完成,而不是做成一个通用的外部数据源系统。

这不是"做到哪算哪"的口子——是每一处简化都对应目标用户场景里一个明确不存在的需求。

代价

主动砍掉的这些东西,一旦目标用户从"十人以下小团队"变成"多供应商、多配额、多级质检、结算"的生产场景,就得补回来。补回来的时候会碰到两种情况:一种是往里补新概念、和原有抽象能兼容,代价可控;另一种是新概念直接和原有抽象冲突,硬叠上去就是打补丁,最后代价失控。这两种情况的分布,是这个项目留给我最深的一课,也是后面重构决策的直接原因。

结果

当时的 MVP 交付了它该交付的东西:核心技术链路完成度到了 90%+,机器人注册 → 唤醒 → 数据生产 → 遥操录制 → 标注 → 数据入库 → 数据集 → Pod 挂载 → 训练 SDK 读到数据,这条完整主路径在自己的部署环境里能真的跑通。机械臂、机器人这些硬件实际接过;完整部署环境搭过;页面和接口都做完了。

回看

MVP 边界不是"能少做就少做",而是"哪些简化在规模变化时代价可控,哪些不可控"。 当时我们对目标用户场景的描述是准确的,抽象也是自洽的——如果目标用户永远是十人以下小团队,这套系统的抽象就是对的。问题在于,当上层战略从"卖给小团队"扩展到"承接真实生产场景"的时候,抽象跟不上,而跟上的成本超过了重建的成本。

后来采集系统在这套代码上继续叠了大约两个月的功能——供应商、配额、多级质检这些能力一层层往上加,加到某个点上发现继续叠的成本已经高于重构的成本,就决定推倒重来。后训练系统走了同一条路:完整吸收了这一版的设计原则和设计思路之后重新开发了一版。当时(做 Research Box 的时候)AI Coding 的能力还不够强,很多东西是我们一点点搓出来的;后来 AI Coding 成熟起来之后,带着已经清晰的需求做重构反而非常快。

代码没有被直接继承,设计原则被继承了。 这句话是这个项目最诚实的总结。

用户旅程假设验证

处境

Research Box 的主路径是围绕"机器人 → 采集 → 数据 → 数据集 → 训练"设计的。在我们自己的部署环境里,这条路径顺滑、连续、能一次演示到底。但真正把系统交给一个陌生用户的时候,用户的入口未必是这条路径的起点。

桌上的选项

一条路是相信"我们设计的主路径就是用户会走的路径"——把演示做好,让用户看到闭环,剩下的产品化细节(本地数据集导入、本地模型导入、机器人接进来之后代码怎么写、接口怎么对接、有没有标准化的使用模板、用户拿到系统之后应该从哪个按钮开始)都作为后续迭代补齐。

另一条路是在做主路径的同时,主动把"用户可能从哪些其他入口进入系统"也当成一等公民来设计——特别是"我已经有一份数据集/模型,想直接导进来用"这个入口。

我的选择

当时的选择是前者。这个选择并不是拍脑袋——从项目管理和交付节奏的角度,3 个月 4 个人做完整个主路径已经是紧张的,多入口设计是被延后的,而且我们当时确实也向上反映过"目前的产品是演示用的,用户真的要用起来还需要不少调整"。

代价

后来真正出问题的地方,正是这一条。销售把系统拿给美团试用的时候,我们部署了一份云服务,把账号交出去,之后就走销售这一层,我们不再直接和用户接触。从日志看,几乎没有真正的操作行为——很可能是投方案性质的评估,或者是内部先看一眼,而不是真的把它当成一个想用起来的工具。销售侧和老板侧收到的反馈是"没形成结果 / 不好用",但从我们能看到的日志和实际接触过程来看,并不存在一个明确的、来自美团一线用户的"哪一步不好用"的反馈。

这里我要非常谨慎地陈述:目前实际上并没有证据证明"美团体验之后觉得不好用"。已知的是——部署了、给了账号、日志显示几乎没有实际使用、销售/老板层面得到的结论是没形成结果,然后项目被叫停。真实的用户反馈这一层是空的。我不打算把这段包装成"用户明确不满意"来放大戏剧性,也不打算把它归咎到某个具体的人身上。这里真正值得说的,是产品假设本身的问题。

结果

用户不买、项目被叫停之后,团队没有资源再去补产品化的入口,进入了"客户觉得不好用 → 客户不买 → 研发要不要改 → 研发没有资源"的死循环,项目被封存。

回看

这件事真正的教训不是"用户不好用",而是我们对用户旅程的假设和用户真实的旅程之间存在错位。系统被设计成"你有一个机器人,跟着我走一遍就能得到训练数据和结果",但真实的用户很可能已经有数据、已经有模型、已经在自己的路径上走了很久,来到我们这里是为了"把手上的东西装进来看一眼"。这个入口如果不存在,用户就会在系统门口停下来,不管里面的主路径设计得多顺。

需要特别说明一句:这不是老板的错,也不是销售的错。老板要在市场窗口关闭之前拿产品去验证机会,销售要在客户表达兴趣的时候完成交付,这些都是合理的商业动作。真正需要检讨的是我们在做产品设计的时候,没有把"用户从哪里进入系统"当成一个和主路径同等重要的问题去回答。当产品成熟度还没到"陌生用户能自己上手"的状态时,用户被拉进来体验,看到的往往不是我们想让他看到的那条闭环,而是主路径起点之外的空白。

这一段经验直接影响了我后来做设备接入和视频基础设施时的判断:在做主路径之前,先把用户从哪个入口进入这件事想清楚——是扫码进入?是已有数据补 Manifest 进入?是账号切换后历史数据的归属?这些入口问题一旦回答对了,主路径的价值才能真正被用户看见。

章二:向机器人扩展

Research Box 里"接入用户机器人"这件事,最开始只是主路径上的一个节点。但真正开始做的时候才发现,机器人不是一个可以简单地"当作设备接进来"的东西——它是一台完整的 Linux 计算机,上面挂着摄像头、传感器、USB 设备、网络、存储、ROS Topic、控制接口,每一样都有自己的约束。这个认知——机器人首先是一台计算机——不是一个我们要单独实现的抽象组件,而是一个必须先建立起来才能正常分析问题的基础视角。有了这个视角之后,"机器人接入"这件事就变成了几个具体的技术问题:算力怎么分、视频怎么走、ROS 怎么处理、时间怎么对齐。

这一章讲的是这些问题里两个最有代表性的判断。当时手上跑的是我们自己内部买的机械臂(就是奶茶店、咖啡店里常见的那种,具体牌子已经记不清了),硬件在我们自己手里,没有外部客户和硬件厂商的干扰,我和 Leader(负责遥操和控制器程序)一起把整套数据链路在真实机械臂上跑通到了 Demo 级别。这不是生产系统,也没有进入后续的生产数据链路,但它是一次真实设备验证过的架构探索。

视频与 ROS 数据解耦

处境

机器人在运行的时候会同时产生两类数据。一类是 ROS 信号——关节角度、位姿、控制指令、各种传感器读数,量非常小,但对时间和顺序敏感。另一类是视频——一个或多个摄像头以 30 Hz 左右的帧率持续产出图像流,数据量比 ROS 信号大几个数量级。

粗略估计一份 10 MB 的机器人采集数据里,视频大约占 9.8 MB,ROS 信号大约占 0.2 MB。这个数字不是精确的实测结果,是基于常识的估算:机器人的关节数据每秒最多 100 帧,每帧只有几十个 float,量非常小;而视频是 30 Hz 的连续图像,每一秒都是几百 KB 级别,二者的量级差距是显然的,不需要严格实验就能得出结论。

这个量级差距直接决定了:这两类数据不能被放进同一个容器

桌上的选项

一条路是让视频跟着 ROS 走——用 ROS Bag 作为唯一的数据容器,视频作为 ROS Topic 的一种消息类型,ROS Bag 记录所有东西。这是 ROS 生态里最标准、最省事的做法,理论上可以把所有数据一次性回放,时间戳天然对齐。

另一条路是让视频走独立链路——ROS 只管 ROS 擅长的低数据量信号和控制数据,视频作为一条独立的数据流,从摄像头设备直接产生、独立编码、独立落盘、独立上传,最后通过时间戳和 ROS 数据在数据体系里建立关联。

我的选择

选了后者。判断的依据是数据量级:一份 10 MB 数据里 9.8 MB 是视频,如果把视频塞进 ROS Bag,ROS 数据的所有组织形式、传输策略、存储策略、下游处理链路都会围绕这 9.8 MB 视频来展开——ROS Bag 会变得非常大,传输效率下降,存储膨胀,对视频的独立处理(切片、脱敏、质检、Chunk 化上传)全部要绕开 ROS 生态重来一遍。ROS 的强项在实时信号和低延迟消息传递,不在承载大体量媒体流。

具体的做法是:机器人本体上用 Python 脚本从 ROS Topic 里把信号数据导出来,同时视频从摄像头设备直接读走独立编码路径,两条链路各自跑;在机器人本地按 LiRobot 的格式组织数据的时候,ROS 数据带着自身的时间戳、视频帧也带着采集时间戳,脚本内存里把两条时间轴对齐,最终在 LiRobot 的 Parquet 文件里建立一个时间索引,让视频和 ROS 数据可以按时间关联回去。

代价

需要一层显式的时间对齐工作——脚本内存里对齐、Parquet 里写入时间索引。这多了一份工程量,也需要保证机器人端的时钟基准是稳定的。ROS Bag 那种"打开就能回放全部"的便利性也放弃了。

结果

这套解耦在真实机械臂上跑通了。ROS 数据 → Python 导出 → 本地文件;视频 → 独立流 → 落盘/遥操分流;两条链路各自完成之后,通过 Parquet 时间索引在 LiRobot 数据里关联起来;数据通过挂载在机器人上的远程 S3 FS 直接写到远端存储,触发上传。这一整套流程作为 Demo 跑通,机器人 SDK 里沉淀了机器人端数据采集、上传、视频分流、同步落盘、遥操 WebView 触发等能力。

回看

这个决策的本质不是"ROS Bag 好不好用",而是当两种数据的量级差距超过一个数量级的时候,把它们放进同一个容器就是错的。ROS 是信号系统,把大体量媒体流塞进信号系统会让整个系统的重心被拖偏。视频独立走一条路,让视频基础设施可以按视频自己的逻辑演进(后来在双目设备的项目里,视频链路的 Chunk 切片、Manifest、Closed GOP、Remux 全部是围绕独立视频链路展开的,如果视频当初跟着 ROS 走,这些能力全部要重来一遍)。

这条判断后来一直延续下去——设备接入项目里的视频独立上传、Chunk 化处理、Manifest 数据自描述,思路都是同一个:让数据按自己的性质走自己最合适的链路,然后在一个更高的抽象层(时间索引、Manifest)上重新关联起来

边缘算力职责划分

处境

Orin 是当时机器人上最常见的边缘算力平台。它的算力很实用,但不是取之不尽的资源——摆在机器人上、装在有限的功耗预算里、和推理模型共享内存。而机器人在运行时至少要同时做几件事:模型推理(决定下一步动作)、数据采集(把 ROS 信号和视频抓下来)、把采集的数据转成 LiRobot 格式、把 LiRobot 数据上传到远端存储。这几件事同时进行的时候,Orin 会不会顶得住?

桌上的选项

一条路是把重活尽量放在机器人上——反正边缘算力也有一些,采集之后就地做 LiRobot 转换、就地做数据打包、就地做部分数据处理,然后上传成品。这条路的好处是数据流简单、上传的东西是"处理好的"、云端的负担轻。

另一条路是把机器人上的计算严格限定在"只有在机器人上做才有意义"的那几件事——推理必须在机器人上(延迟不能忍受),采集必须在机器人上(数据源在这里),推理数据落盘必须在机器人上(数据得先存下来),其他所有重计算全部搬出机器人,包括 LiRobot 格式转换、数据打包、复杂的数据处理。

我的选择

选了后者,并且形成了一条明确的原则:机器人本体上只做实时运行必须的事,其他重计算尽量放到机器人之外

具体的判断依据:在 Orin 上同时做数据采集 + LiRobot 格式转换 + 上传,算力基本已经到顶——这一点从既有的车端经验里就能推得清楚,车端我们做过比较细致的算力和内存测试,当时车端的内存只有 16GB,还要以内存和显存共用的方式去跑大模型和应用程序,算力紧张是常态。机器人的 Orin 面对的是同类问题:算力预算已经很紧,如果再叠加模型推理、状态上报、其他辅助服务,就完全没有余量了。

需要诚实说明一点:Orin 上"同时采集 + 转换 + 上传"这个具体场景的性能压力,不是我亲自实测出来的,是硬件团队和用户端在实际跑的时候给我的反馈结论。软件端没有为了证明这个结论再做一次重复的 benchmark——一方面是交付节奏不允许,另一方面是即便性能没到瓶颈,做算力职责划分也是一个必须做的架构决策,实测数据不改变结论方向。

代价

这条原则意味着云端要承担更多的处理链路——原始数据(ROS + 视频)从机器人上传上来之后,LiRobot 格式转换、数据聚合、后续处理这些活都在云端完成。云端要接住这一份计算量,链路要能承载原始数据的规模。作为交换,机器人端保持了实时性和稳定性。

结果

Demo 里机器人端的角色被压得非常薄:推理 + 采集 + 视频分流(用现成的 T 型分流工具在 SDK 侧接入)+ 数据落盘 + 通过远程 S3 FS 写数据。就这些。重计算全部在机器人之外发生。整套链路在机械臂上稳定跑通了 Demo 阶段。

回看

这条原则和双目设备项目里"把 MP4 编码从设备端搬到云端"的判断是同一个:边缘算力是硬约束,云端算力是弹性资源,任何不是必须在边缘完成的计算,都应该搬到云端。这句话在做 Research Box 的时候还只是一个隐约的判断,到后来做双目设备项目的时候被明确成一条完整的架构原则,再往后做视频基础设施、数据流水线的时候一直被反复引用。

同时也要诚实标注这条链路的边界:视频分流用的是现有的 T 型分流工具,不是我们自己实现的;实际验证的是遥操 + 落盘两路,推理这一路在架构上考虑了但不是这次验证的重点;只跑过一类机械臂,多品牌机器人的统一接入并没有真正解决——LiRobot 的宽表结构本身就降低了消息异构的复杂度,我们没有碰到那个问题不代表这个问题不存在。遥操部分停留在调研阶段,宇树开放的遥操工具链作为基础,真正做到生产级还需要不小的改造。这些边界必须写清楚,不能因为讲得完整就假装它是一个成熟的生产系统。

技术架构:Research Box 系统边界与设计原则迁移

Research Box 覆盖范围(面向 <10 人团队,刻意简化)
┌─────────────────────────────────────┐
│  设备管理(GPU / 机器人 / 机械臂)    │
│  数据采集(遥操 / 录制 / 标注)       │
│  数据集(Episode / 派生 / 血缘 / 检索)│
│  Pipeline(Pod 挂载数据 + 自定义处理) │
│  Training SDK + 训练任务              │
│  存储管理                            │
│  K3s 集群                            │
│  ──────────────────────────          │
│  刻意砍掉:                           │
│  × 多供应商 / 配额 / 多级质检 / 结算   │
│  × 复杂存储挂载 / 数据版本控制         │
│  × 用户已有数据/模型导入入口           │
└─────────────────────────────────────┘

设计原则迁移(代码未继承,原则继承)
  Research Box ──────────────────────►  后续系统
  数据解耦(视频/信号分流)         →  Case 4 设备基础设施
  边缘算力边界(实时留设备端)      →  Case 5 训练系统
  MVP 边界意识                     →  Case 3 数据生产系统
  时间轴关联(Parquet 索引)        →  Case 4 Manifest

延续下去的东西

Research Box 没有形成商业转化,代码在后续的采集系统和后训练系统里也没有被直接继承——项目管理、存储、SDK、数据集这些能力在早期被复用过一段时间,但很快因为"叠功能的代价高于重构的代价"而被推倒重来。真正延续下去的,是设计原则。这些原则在后来的项目里几乎每一个都反复出现过:

MVP 边界要靠"简化的代价在规模变化时是否可控"来判断,而不是"能少做就少做"——这条判断直接影响了后来采集系统的抽象怎么建(供应商、配额、多级质检、结算这些概念要不要从一开始就在抽象里留位置)、后训练系统怎么组织资源模型。

用户旅程的入口和主路径同等重要——这条判断在设备接入项目里被彻底贯彻了:二维码作为唯一入口、Manifest 让历史数据自然进入新体系不需要迁移、身份绑定跟着数据走不跟着设备走。每一个都是在回答"用户/数据从哪里进入系统"这个问题。

让数据按自己的性质走自己的链路,然后在更高的抽象层重新关联——ROS 与视频解耦、Parquet 时间索引在这里第一次成型,后来演进成 Manifest 数据抽象层、Chunk 化视频存储、生产格式与交付格式分离。

边缘算力是硬约束,重计算搬到云端——这条原则从机器人 Orin 场景一路延续到双目设备的 MP4 编码重构,再到后续设备接入的整体职责划分。

诚实标注能力边界——机器人数据链路里,遥操只到调研阶段、只跑过一类机械臂、Orin 性能压力不是自己实测的、视频分流用的是现成工具,这些都被明确写出来。这个习惯后来延续到每一份设计文档、每一次能力评估、每一场对上汇报里。

回看整个项目

这个项目不是失败。3 个月 4 个人做出了一个覆盖具身研发全链路、机械臂和机器人能真跑通的先锋版本,核心技术闭环完成度 90%+,部署环境是完整的,主路径是通的。它没有形成商业转化,但商业转化不是这个项目该完成的目标——它该完成的目标是把"完整闭环长什么样"这件事一次性想清楚、一次性搭起来,让公司在这个方向上有一个可以看到全貌的东西。这件事它完成了。

代码没有被继承,设计原则被继承了。这句话本身就是这个项目最大的价值——先锋版本的意义不是让下一代直接复用,而是让下一代知道哪些路走得通、哪些抽象撑不住规模、哪些边界必须重新划。当我后来做采集系统重构、做双目设备接入、做视频基础设施、做后训练系统的时候,每一次我都在用 Research Box 里学到的东西,而不是在用 Research Box 里写的代码。

这个项目也让我第一次看清楚:把技术链路跑通,和让一个陌生用户真正能够独立使用,是两个完全不同的问题。这两个问题都值得单独做一遍,不能假设"跑通了自然就好用了"。这条认知后来在设备接入项目里被彻底贯彻——每一步都在回答"用户从哪里进入、系统怎么保证他走下去",而不是"技术链路能不能在自己的环境里跑通"。