Skip to content

Sparrow Data Platform:从 DDL 到真实具身数据质检

用 MolmoAct2 与 UMI 两份真实数据跑通 DDL、版本化对象路径、Airflow + DockerOperator 质检,并记录被删的列、已知短板和踩过的契约坑。

11 min read

一句话:平台的价值不是“多了一张表”,而是让每条产物都能回答三个问题——哪个工具版本产出的、输入是哪一版、失败了还能不能复现。

前置阅读:机器人视频质检专题的专题首页、帧级检测器、跨模态配对。本文不重复检测器实现,讲怎么把这些检查装进一条可追溯的平台链路。

单条视频的质检脚本和平台之间,差的不是 UI,而是责任边界。脚本可以只输出一个 quality_summary.json;平台必须能回答:这份 JSON 是哪个版本的检测器、在哪台机器、对哪一版输入跑出来的,失败了以后还能不能复现。

下面按搭建顺序拆:DDL 怎么收敛、数据怎么进来、质检怎么跑、两类真实数据暴露了什么差异、踩了哪些坑、下一步缺什么。

1. 为什么需要这个平台

先把问题说具体。没有平台时,常见的状态是:

  • 检测器代码在 git 里,但运行记录只在某次终端的滚动输出里;
  • 三条 episode 的产物都叫 quality_summary.json,散在不同目录,重跑一次就覆盖;
  • 同一个 v1.0.0 可能对应两个不同的镜像,因为镜像被重新构建过;
  • 某个 episode 被判 FAIL,三个月后无法确定是数据坏了,还是当时阈值配错了。

这四件事指向同一个需求:工具身份、输入身份、产物位置、运行结果必须被结构化地记录下来。平台不是把文件换个地方存,而是把“这次判断”变成一条可复查的事实。

具体到机器人视频质检,平台至少要接住三样东西:

  1. 一条 episode 的多路视频与低维数据;
  2. 一次质检运行的配置、工具版本和输出;
  3. 这条 episode 当前是否被接收(accept / reject),以及为什么。

2. DDL 怎么定:同一事实只存一处

最初的设计里,tool_versions 试图把所有表达方式都存下来:version_kind、version_label、image_uri、package_name、package_version、image_digest。这些列看起来“信息丰富”,实际是同一个事实的四种写法。

收敛后的核心形态(示意,省略审计字段):

列 决策 理由
version 保留,v1.0.0 语义 tag,作为唯一身份 人可读、可比较、可排序;评审和排障都靠它
source_uri 保留,完整镜像名;tag 必须等于 version 能解析来源,同时用约束消灭“版本名和 tag 不一致”
image_digest 保留,可空,只用于复现 sha256: 是机器身份,人不可读,不参与业务判断
version_kind 删除 DOCKER / GIT / PKG 可由 source_uri 形态推导
version_label 删除 与 version 重复,只是叫法不同
image_uri 删除 与 source_uri 重复
package_name / package_version 删除 本质是把 source_uri 再拆成两列,换一种冗余

核心判断是:

  • 身份要人可读。docker:sha256:... 适合做校验,不适合做主语。一个 episode 的质检结果应该能被人直接说成”robot_video_qc 的 v1.0.0 跑的”。
  • 冗余会产生两套真相。version_label 和 version 一旦允许不同步,查询时就要决定信谁;image_uri 和 source_uri 同理。删除重复列比写同步逻辑便宜。
  • 推导优于存储。工具类型不是业务事实,是 source_uri 的形态。读取时推导即可,写库时不必固化。
  • digest 只服务复现。它回答“当时到底跑的是哪块镜像”,不回答“这是哪个版本”。因此它可以为空,也不能当主键。

写入侧的契约是:source_uri 的 tag 必须与 version 相等;(tool_id, version) 唯一。这样“版本”只有一个来源, digest 只在需要逐字节复现时出现。

3. 数据怎么进来:路径由平台签发

对象路径固定成:

datasets/{dataset}/{tool}/{version}/{run_id}/{artifact}

实例:

datasets/molmoact2/robot_video_qc/v1.0.0/<run_id>/quality_summary.json

关键不在这个模板本身,而在谁来拼。路径前缀由 API 的 object-layout 签发,工具只拿到一个前缀,再往里写自己的产物。工具不自己拼 datasets/...。

这样做有两个直接收益:

  • 改布局(换 bucket、加租户前缀、调整层级)只改一处,不用在所有工具里搜字符串;
  • 工具无法“发明”一个平台不认识的路径,产物的可发现性由平台保证。

真实取样

本轮用的是 allenai/MolmoAct2-BimanualYAM-Dataset。本机到 huggingface.co 不可达,因此经 hf-mirror.com 拉取同一 revision:

项 实测值
取样文件 14 个
总字节 198,562,402
取样 episode 31673 / 31689 / 31682
LFS 校验 13 个 LFS 文件 sha256 与 HF 元数据 0 mismatch

镜像不是“另一个数据源”,而是同一 revision 的传输通道。sha256 对齐之后,平台记录的是源数据集与 revision,而不是镜像域名。

4. 质检怎么跑:Airflow + DockerOperator

质检任务按 episode 拆成容器任务:Airflow 负责编排、重试与状态,DockerOperator 负责把检测器镜像拉起来跑一条 episode。平台记录的是 run,不是日志。

选择 DockerOperator 而不是在 Airflow worker 里直接跑 Python,理由很实际:

  • 检测器依赖 OpenCV / ffmpeg,和调度器的 Python 环境隔离,升级不会互相影响;
  • “这次运行用了哪个工具版本”可以直接落到 tool_versions,镜像 tag 与 version 绑定;
  • 单条 episode 失败只影响一个 task,重跑成本可控。

真实结果

三条 MolmoAct2 episode 与一条 UMI episode 的实际输出:

数据集 Episode 帧数 时长 分辨率 fps 状态
MolmoAct2 31673 1319 43.97 s 640×360 30 PASS
MolmoAct2 31689 1628 54.27 s 640×360 30 PASS
MolmoAct2 31682 1603 53.43 s 640×360 30 PASS
UMI 取样 episode 400 40 s 224×224 10 PASS

三条 MolmoAct2 的 coverage_ratio 落在 0.9992–0.9994;UMI 为 0.9975。这里的 coverage_ratio 衡量的是声明时长与实际时间跨度之比,不是“解码了多少帧”。时间戳在四条数据上均单调。

已知短板

这一轮质检通过了,但不等于覆盖了所有问题。三处必须写明:

  • 只校验名义恒定帧率。当前检查基于 nominal_cfr_frame_clock:按声明的 30 / 10 fps 推时间轴,没有做容器 PTS 校验。可变帧率、PTS 抖动或容器时间戳错误,可能在这套检查下仍然显示 PASS。
  • 不校验 state / action 语义。维度、时间轴、覆盖率能查;“动作是否合理”“夹爪是否真的夹住”不在检查范围。
  • UMI 无标定,无法验证外参。这不是漏做,而是输入里就没有标定制品。

5. 两类数据的差异:同一个工具,不同的结论强度

维度 MolmoAct2 UMI
许可 Apache-2.0 Apache-2.0
规模 32,246 episodes / 76,046,658 frames 1,447 episodes / 699,432 frames
相机 3 路,640×360 1 路,224×224
fps 30 10
observation.state 14 维 7 维
action 有 action 列,14 维 无 action 列;只有 end_pose / start_pos / gripper_width
标定 card 未提及 URDF / 内外参 22 个文件里 URDF / 内参 / 外参关键词命中 0
本轮取样 14 个文件,198,562,402 字节 5 个文件,239,045,003 字节

UMI 的情况值得单独说:它的项目定位就是 calibration-free,用腕部单目相机和手持夹爪采集,不依赖机器人基座标定。因此数据集里没有 URDF、内参、外参,不是“数据质量差”,而是另一类契约。

这对平台的含义是:质检能力必须按输入能力分支,而不是按数据集名字硬编码。

  • 有 action 才做 action 相关检查;没有就跳过并记录“不适用”;
  • 有标定制品才做外参 / 投影校验;没有就输出 UNKNOWN,而不是 FAIL;
  • 同一套 PASS 在两类数据上代表的置信度不同,产物里要能看出“检查了哪些项、跳过了哪些项”。

6. 踩坑:契约比文档更硬

第一次 ingest 时,output_key 写成了带扩展名的 episode_spec.json。DAG 里按逻辑键取产物:

KeyError: episode_spec

原因很简单:DAG 的契约期望 output_key 是逻辑键(episode_spec),扩展名由产物类型决定,不该出现在键里。文档没有把这件事写成硬约束,但代码按硬约束执行了。

第二个坑更贵:Episode / Artifact 是只追加、不可删除的。修好 key 之后,那两条失败记录无法清理,留下:

  • 两条 FAILED Run;
  • 一个废弃 Episode;
  • 只能用 /acceptance 后缀重新发布一条新的验收记录。

这两件事的共同教训是:契约先于便利。

  • 配置里的 key 不是文件名,不能让“看起来像个文件”的写法泄漏进逻辑层;
  • 追加模型下,错误不会被删除,只会被覆盖或追加。失败记录是审计证据,但也是需要治理的债:要有 supersede / 迁移策略,否则 FAILED 会永久混在有效记录里。

如果重来一次,更合理的做法是在 schema 校验阶段就拒绝带扩展名的 output_key,而不是等 DAG 在运行时抛 KeyError。

7. 下一步:URDF → 投影 → YOLO,还缺三样

当前 QC 能判断“画面和时间轴是否可信”,但还判断不了“像素和三维空间是否对得上”。要把链路走到 URDF → UMI 相机投影 → YOLO 训练,缺的不是模型,而是三样基础件:

  1. 标定制品。URDF、相机内参、外参、畸变参数,需要版本化存储,并带 hash 与 provenance。没有版本化的标定,投影结果无法复现。
  2. 工具契约里的 calibration_ref 与投影参数。工具要能声明“我用了哪一版标定”,平台要能据此校验外参、复算投影。当前 schema 里一个相关字段都没有。
  3. 标注侧与 label schema / 血缘绑定。YOLO 的框不仅要绑定到图像,还要绑定到图像对应的 episode、相机、标定版本。否则模型指标下降时,无法区分是数据、标定还是标注的问题。

这三样缺任何一样,投影都只能是一次性的离线脚本,无法进入可追溯的质检链路。UMI 的单目无标定数据会让这件事更明显:没有 calibration_ref,平台只能输出 UNKNOWN,而不是假装完成了外参校验。

参考

Type to search…

↑↓ navigate↵ selectEsc close