RayOrch写入页序血缘,MinerU 64卡15.14倍

RayOrch: Programming and Executing Lineage-Controlled Multi-Grain Dataflows for Foundation-Model Data Preparation

Xiaochen Ma, Zimo Meng, Junzhu Liang, Youhe Jiang, Yue Cheng, Hao Liang, Bohan Zeng, Dengchun Li, Lu Ma, Zhengyang Zhao, Zhen Hao Wong, Runming He, Meiyi Qiang, Jiangtao Guan, Binhang Yuan, Wentao Zhang

cs.DC

2026-09-16

RayOrch在Ray上做带血缘的多粒度数据流:声明expand/reduce,跨父节点拼GPU batch,完成的父节点立刻往下走。H20上MinerU从4卡到64卡15.14倍,端到端比Ray Data少13.1%墙钟。

这篇在解决什么

基础模型的数据准备管线会反复改加工粒度。一份 PDF 拆成页,一页再拆成区域;一条视频拆成 clip、帧、音频段。每个父节点产出多少孩子取决于输入,分布还长尾:论文示意图里,两份 PDF 分别出 2 页和 48 页,一条视频出 120 个 clip。

GPU 阶段想把不同父节点的子节点拼进同一个 batch。结果仍要回到原来的父节点,孩子顺序不能乱,父节点还得知道该等的孩子是不是都到终态了。现有系统走两个极端。粗粒度 job 把整份文档当成一个不透明任务,页级并行露不出来。扁平 flatmap / explode 把每一页摊开,父子关系和页序变成应用自己带的字段;Ray Data、Daft 并不把这些字段当结构来调度。论文实际跑的基线管线,还会在子阶段之后做一次全局 group-by 和 sort,先做完的文档卡在还没做完的文档后面。

方法

RayOrch 把这段父子关系写成程序契约,再由运行时保住。实现是一层 Python,跑在 Ray 上。

逻辑层用一套固定对象。Domain 是编译期的层级,比如 PDF 或 Page;Entity 是这一层上的一个实例;Call 是一次配置好的函数调用;Grain 是某个 Call 作用在某个 Entity 上,也是调度、重试、提交的单位。程序用 F.expand 声明有序、可变基数的父到子展开,用 F.reduce 声明匹配的、按父节点收集的有序归约。编译器检查 Domain 是否兼容、每一对 expand/reduce 是否对齐。运行时记下每次展开的具体孩子集合、每个孩子的直接父节点和从 0 起的不可变序号,以及每个结果的终态。

物理层每个 Call 有一条 FIFO Ready Queue,就绪 Grain 按入队顺序排队。预约策略从队头取最多 B 个组成物理 batch,可以混不同父节点。收集不看 batch 边界,也不看谁先算完,只看声明的成员集合和序号。某个父节点的全部孩子进入终态,它就可以进下一阶段,不用等别的父节点。

失败分两类。类型化的 GroupFailure 只挡住同一个 Call、同一个父节点:队列里还没发出去的兄弟 Grain 直接抑制,飞行中的兄弟即使算完也不能提交,已经提交的不动,无关父节点继续跑。未类型化的 UDF 异常或基础设施故障按物理尝试失败处理,可以提高 generation 再试。提交时核对 Grain 仍在飞行且 generation 匹配,过期或重复报告丢掉。

语义契约只覆盖有限、无环、一对多的层次程序:合法调度可以改 batch 组成、放置、重试时机,最终 Items 和血缘顺序必须一样。UDF 如果还对 batch 形状、输入顺序和随机性不变,保证可以延伸到 payload 字节;否则只保证结构和终态。

结果

硬件全是 NVIDIA H20。MinerU 用 3689 份 PDF、174744 张有效页,每份 1 到 427 页;视频管线 27091 条视频、104952 个 clip,模型是 Qwen2.5-VL-7B;Docling 2000 份 PDF。

强扩展上,MinerU 处理时间从 4 卡 15.26 小时降到 64 卡 1.01 小时,15.14 倍,相当于理想线性的 94.6%。视频从 8 卡到 64 卡 7.82 倍,理想值是 8 倍。

64 卡端到端 MinerU:

系统墙钟(秒)吞吐(页/秒)
RayOrch4295.740.68
Ray Data4945.835.33
Daft6048.528.89
原生 MinerU8874.519.69

相对 Ray Data、Daft、原生 MinerU,墙钟分别少 13.1%、29.0%、51.6%。冷启动时间线里,RayOrch 的 OCR、组装、上传窗口几乎叠在一起,大约 3625 到 3633 秒;父节点本地提交后立刻进上传。Ray Data 和 Daft 在 OCR 之后还有全局 regroup 和组装尾巴。

Docling 在 4 卡上 RayOrch 跑完 9489 秒、0.2107 文档/秒,比 Ray Data 少 16.0% 墙钟,比 Docling Serve 少 22.3%。视频 8 卡时三家几乎打平,3.32、3.30、3.33 小时;64 卡是 0.42、0.43、0.53 小时,RayOrch 相对 Ray Data 只快 0.01 小时。

调度消融固定血缘和提交规则,只叠加 1:M 重分 batch 和 FIFO。文档流式基线 818.0 秒;加上跨父节点重分 batch 降到 634.1 秒;再开 FIFO 降到 579.3 秒,多省 8.6%。

失败实验在 4 卡上对 99 个最大父节点的第 0 页注入失败,这些文档只占 5.25%,页数却占 51.9%,页 UDF 是 50 毫秒固定代价。RayOrch 拦住 6241 个注定失败的兄弟计算进入 UDF,占中毒父节点兄弟的 26.5%;非触发 UDF 调用从 45408 降到 39167,配对墙钟平均少 14.93%。Ray Data 和 Daft 的兄弟抑制是 0,墙钟几乎不动。未受影响的父节点,该有的输出都在。

为什么重要

跑文档解析或视频切 clip 再送 VLM 的人,墙上的时间经常耗在「页已经齐了,还在等一次全局 shuffle」。RayOrch 把完成判定和页序收进运行时,UDF 不用自己带 parent id,也不用事后 group-sort。相对 Ray Data 少 13.1% 墙钟,这是渐进改进。真正换写法的是编程模型和类型化兄弟抑制:页数长尾明显、已经在 Ray Data 上跑 MinerU 一类管线的团队,应该先量 regroup 尾巴占了多少墙钟,再决定值不值得迁。

视频 64 卡上跟 Ray Data 几乎持平。GPU 计算本身占满之后,血缘调度能挤出来的空间很小。

局限与存疑

论文写明:有序收集只发生在每个源 microbatch 内部,不支持通用 join、跨 microbatch 的窗口、反馈环。对象是有限无环的一对多层次,不是一般数据流。

失败抑制依赖类型化 GroupFailure。普通 UDF 异常走重试,不会自动杀掉同一父节点的其余页。失败实验用的是 50 毫秒空 UDF,不是 MinerU 的 1.2B OCR,那 14.93% 不能直接外推到真实模型阶段。

driver 进程握着编译计划、血缘元数据和 pending ObjectRef,payload 放在 Ray object store。论文没测 driver 在更大血缘图上会不会先撑住。语义保证默认只到结构和终态;UDF 对 batch 形状敏感时,字节级可复现要另说。

术语

原文与代码

相关论文

全部论文解读