宅中地 - 每日更新
宅中地 - 每日更新

贊助商廣告

X

一份 PDF 拆成兩頁,還是四百二十七頁,系統該怎麼辦

2026年10月07日 首頁 » 熱門科技

假設你在食堂後廚,要處理一批訂單。有的訂單只要一份炒飯,有的訂單要辦一整場婚宴的一百道菜。你手下有幾個廚師,每個廚師一次只能炒一份菜。你會怎麼安排?

如果每張訂單是一個整體任務,交給一個廚師從頭做到尾,那婚宴訂單的廚師累到冒煙,炒飯訂單的廚師卻在旁邊閒著。這就是很多數據處理系統今天面對大規模文檔、影片處理時的真實困境。

一份 PDF 可能只有兩頁,另一份可能有四百二十七頁;一段影片可能切出幾個片段,另一段能切出上百個。這種分布極不均勻,專業說法叫長尾分布,也就是大多數任務量很小,少數任務量巨大,畫出柱狀圖會有一條長長的尾巴拖在後面。而處理這些頁面、片段的 GPU,最喜歡的工作方式是把不同來源的小任務湊在一起打包處理,這樣才能把算力用滿。矛盾就在這裡:細粒度調度需要打散重組,但打散之後,你還得知道每一份炒好的菜該端回哪張訂單的桌子,還得等這桌所有菜都上齊才能算這單完成。

這篇來自北大、港科大等機構團隊的論文,提出了一個叫 RayOrch 的系統,專門解決這個問題。它服務的場景是大模型訓練數據準備:把海量的 PDF 文檔、影片原始素材,加工成結構化的訓練數據。這個加工過程會反覆變換處理粒度,PDF 變成頁面,頁面再拆出區域和表格;影片切成片段,片段再拆出幀和音頻。團隊的問題是,如何讓系統在打包批處理提升效率的同時,依然精確記得每個碎片屬於誰、排第幾、什麼時候才算真正完工。

現有方案為什麼都不夠用

在 RayOrch 出現之前,業界大致有兩條路。

第一條路是把每份文檔、每段影片當成一個不可拆分的整體任務,直接扔給一個進程從頭處理到尾。這樣做的好處是邏輯簡單,父子關係天然清晰,你不需要額外記錄"這一頁屬於哪份文檔",因為整份文檔就是一個任務,從沒被拆開過。但壞處也很直白:頁面、區域、幀這些可以並行處理的細粒度單元被鎖在了任務內部,GPU 沒法把不同文檔的頁面湊在一起批處理,利用率上不去。

第二條路正好反過來,把所有頁面、片段拆散鋪平成一個大列表,交給系統統一調度。這樣細粒度並行是有了,但代價是應用層必須自己記住每一條記錄的父任務是誰、排第幾,還要自己判斷什麼時候某個父任務下面的所有子任務都完工了,最後還要把打散的結果重新分組、排序、拼回原來的文檔結構。論文裡管這套操作叫全局分組、排序、歸約,業界現有的 Ray Data 和 Daft 兩個主流數據處理引擎,走的正是這條路。

問題出在哪?拆散之後的重組,往往需要一次全局的洗牌操作,業內叫 shuffle。

> Shuffle:分布式系統里,把散落在不同節點的數據按某個鍵重新分發聚合的過程,通常伴隨大量網路傳輸和等待,是分布式計算里最耗時的環節之一。

這次洗牌意味著一個隱藏的代價:哪怕只有一份文檔還剩最後一頁沒處理完,其他已經全部處理完的文檔也得等在那兒,因為重組操作是全局性的,必須等所有數據都到位才能開始分組排序。這就好比婚宴訂單的一百道菜,哪怕已經做好九十九道,只要還差一道甜品沒上,服務生就不能把整桌菜端出去,其他早就做完的散台訂單也只能幹看著,因為傳菜的流程是統一批次操作的。如果不這樣設計會怎樣?論文的實測數據給出了答案:在真實的 MinerU 文檔處理流水線里,Ray Data 和 Daft 都在 OCR 階段結束後出現了明顯的處理尾巴,也就是重組、組裝、上傳這幾步只能排隊串行完成,沒法和前面的環節重疊進行。

RayOrch 的核心思路:給每個碎片配一張身份證

RayOrch 的解法,說起來是一個概念上很樸素的東西:結構化血緣。

> 結構化血緣(structural lineage):系統在運行時持續維護的一套控制狀態,記錄每次拆分產生的具體子項集合、每個子項的直接父節點和不可變序號、以及每個結果是否已經"終結"(也就是徹底完成或徹底失敗)。這不是那種事後才生成的溯源日誌,而是實時指導調度和判斷完工與否的活狀態。

翻譯成人話就是:系統不再讓"打包批處理"這件物理層面的事,去決定"誰是誰的孩子、排第幾、什麼時候算齊"這件邏輯層面的事。這兩件事被徹底拆開,各管各的。

這個設計里有幾個關鍵角色。首先是 Domain 和 Entity。

> Domain:編譯時就定好的一個邏輯層級名字,比如"頁面層"或"片段層"。

> Entity:Domain 在運行時具體產生的一個數據單元,比如某份 PDF 的第 0 頁。

然後是 Call 和 Grain。

> Call:程序里配置好的一次函數調用,比如"對頁面做 OCR 識別"這個操作本身。

> Grain:一個 Call 作用在一個具體 Entity 上形成的計算單元,是整個系統調度、重試、提交的最小單位。

程式設計師寫代碼的時候,用兩個專門的操作符來聲明父子關係:一個叫 F.expand,負責把一個父任務拆成一串有序的子項,比如把一份 PDF 拆成按順序編號的頁面;另一個叫 F.reduce,負責在所有子項處理完之後,按照原來的順序把結果重新拼回父任務的結果。這兩個操作符是配對聲明的,編譯器會先檢查這對聲明是否合法,運行時再持續維護這段血緣關係。

這裡最關鍵的一步設計是:調度隊列只管"誰已經準備好可以處理了",完全不管"誰是誰的孩子"。論文裡管這個叫 FIFO Ready Queue,一個先進先出的就緒隊列,每個 Call 獨享一條。不同文檔的頁面,只要都準備好了,就可以按到達順序排進同一條隊列,然後被打包成一批送進 GPU,這批里完全可能混著三份不同文檔的頁面。但每個頁面身上始終帶著自己的身份資訊:我是誰的孩子,我排第幾。GPU 處理完一批混裝的頁面後,返回結果時,系統憑著這些身份資訊,把每個結果準確送回它該去的父任務那裡。

這就好比後廚真的換了一套流程:廚師不再按訂單分組炒菜,而是按"菜好了沒"排隊上灶,誰先備好料就先炒,不同訂單的菜可以同一鍋一起炒。但每份菜出鍋時都掛著一個小標籤,寫著它屬於哪張訂單、是這桌的第幾道菜。傳菜員根本不需要等所有菜都出鍋再統一分發,哪張訂單的標籤都湊齊了,就立刻打包上桌,其他訂單該等就等,互不耽誤。如果沒有這個身份標籤會怎樣?那就退回到了前面說的困境:不是等到全部做完沒法拆開上菜,就是拆開炒了卻分不清該端去哪桌。

論文原文的圖 1 直觀展示了這個對比:左邊是傳統方案里"全局分組排序歸約"的路徑,A 和 C 兩份文檔都處理完了,卻還要傻等 B 文檔的最後幾頁處理完,下游環節全部閒置;右邊是 RayOrch 的路徑,A 和 C 完工的那一刻就立即被送進下一階段,完全不用等 B。

完工判斷:誰說完就是完了,不看隊列誰先誰後

光有身份標籤還不夠,還得有一套清晰的規則來判斷"這個父任務到底算不算完工了"。

RayOrch 給每個邏輯對象都定義了唯一的終結狀態。一個子項的終結狀態可能是"確實存在這個結果"、"被主動丟棄"、"計算失敗"或者"因為上游出問題被抑制"。一整個拆分批次的終結狀態則是"全部子項都成功產出了,按順序排好的列表"、"整體被丟棄"或者"整體失敗"。這些狀態一旦發布就不可撤銷,重複發布是無害的冪等操作,但互相矛盾的發布會被拒絕。

判斷父任務能不能收尾,只看一件事:這個拆分批次的所有子項,是不是都進入了終結狀態,而且是不是所有該活下來的子項都真的產出了值。至於這些子項是被哪一批物理批處理送進 GPU 的、是先完工還是後完工,這些完全不重要。歸約操作嚴格按照子項被拆分出來時分配的那個不可變序號來排列最終結果,絕不看誰先返回結果。

這裡有個很容易被忽略但很重要的細節,叫代際護欄。

> 代際護欄(generation fence):每次一個計算單元被重試或者被換到另一個執行進程上重新跑,系統就給它分配一個新的"代際編號"。只有編號和當前記錄完全匹配的結果報告才會被系統接受,過期或重複的報告會被直接拒絕。

為什麼需要這個?因為分布式系統里重試是常態,一個進程可能崩潰後被換掉重新跑同一個任務,這時候舊進程如果晚了一步把結果傳回來,系統必須能分辨這是個過期的、該扔掉的答案,不然同一份工作可能被算兩次,甚至用了錯誤的舊結果去覆蓋新結果。這就像餐廳里一個訂單如果被重新劃給了另一個廚師做,原來那個廚師如果稍後端著菜跑回來,服務生得能一眼認出這盤菜已經過期了,不能再端上桌。

出了問題怎麼辦:只攔一家人,不連累鄰桌

數據處理這麼大規模,出錯是必然的,RayOrch 對失敗的處理方式也很講究。

論文區分了兩種失敗。一種是有明確類型標註的父任務級失敗,論文叫 GroupFailure,比如某份文檔本身就損壞了,這一整份文檔下面還沒開始處理的子任務,系統會主動攔下來,不再送進用戶自定義的處理函數裡空跑;已經在處理中的子任務允許跑完,但結果不會被採納;已經提交成功的結果則原封不動保留。另一種是普通的運行時異常,比如某次調用因為網路抖動崩了,這種情況系統的處理方式是重試或者換個執行進程重新跑一次這個計算單元,本身不影響這份文檔的身份資訊。

關鍵在於影響範圍:GroupFailure 只針對"這一個 Call、這一個父任務"的組合生效,完全不會波及其他父任務的處理。這就好比一整箱蘋果里發現一個壞果,你只需要把這一箱裡還沒檢查的蘋果重新過一遍篩,隔壁箱子的蘋果照常上架,不用因為一個壞果把整個倉庫的貨都叫停複查。如果沒有這種精確到"某個父任務"的隔離能力會怎樣?那可能就得像很多簡單系統那樣,一旦發現異常就整批回滾重跑,白白浪費掉大量已經算對的結果。

實際效果:數字說話

RayOrch 團隊用了三類真實工作負載來驗證系統,分別是 MinerU 文檔解析流水線、Docling 文檔轉換工具,以及基於 Qwen2.5-VL-7B 視覺語言模型的影片處理流水線,硬體用的是英偉達 H20 GPU。

在擴展性測試中,MinerU 流水線從 4 塊 GPU 擴到 64 塊 GPU 時,處理時間從 15.26 小時壓縮到 1.01 小時,加速比達到 15.14 倍,相當於理想線性擴展效果的 94.6%。這意味著 GPU 數量翻了 16 倍,處理速度幾乎也跟著翻了 16 倍,擴容基本沒有被內耗吃掉太多性能。影片流水線從 8 塊擴到 64 塊 GPU,加速比達到 7.82 倍。

在固定資源量的橫向對比中,處理 174744 個有效頁面時,RayOrch 用時 4295.7 秒,比 Ray Data 快 13.1%,比 Daft 快 29.0%,比原生 MinerU 快 51.6%。在 Docling 工作負載上,RayOrch 比 Ray Data 快 16.0%,比 Docling Serve 快 22.3%。

論文還專門做了兩個消融實驗,用來單獨驗證某個設計到底帶來了多大貢獻。第一個是調度策略消融:在保持血緣控制和提交機制不變的前提下,逐步疊加"打包重批"和"先進先出調度"這兩個能力,純流式處理不做任何優化耗時 818.0 秒,加上打包重批後降到 634.1 秒,再加上 FIFO 調度進一步降到 579.3 秒,也就是 FIFO 調度這一項單獨就帶來了 8.6% 的提速。

第二個是失敗注入實驗:團隊故意在 99 份最大的文檔的第 0 頁植入失敗,這 99 份文檔只占全部文檔數量的 5.25%,但頁數占比高達 51.9%。結果顯示,RayOrch 成功攔截了 6241 個本該白白浪費的兄弟子任務,讓它們根本沒進入實際計算函數,未受影響的父任務照常拿到完整正確的輸出。相比之下,Ray Data 和 Daft 由於是先整體拆分鋪平再事後過濾的架構,完全沒有實時攔截能力,多餘計算一次不少地全跑了一遍。平均下來,RayOrch 在這種失敗場景下反而比無失敗的正常運行還快了 14.93%,因為省下了那些註定要被丟棄的無效計算。

| 系統 | 處理 174744 頁耗時 | 吞吐量(頁/秒) |

|---|---|---|

| **RayOrch** | **4295.7 秒** | **40.6788** |

| Ray Data | 4945.8 秒 | 35.3318 |

| Daft | 6048.5 秒 | 28.8903 |

| 原生 MinerU | 8874.47 秒 | 19.6907 |

| 系統 | Docling 2000 份文檔耗時 | 吞吐量(份/秒) |

|---|---|---|

| **RayOrch** | **9489.07 秒** | **0.2107** |

| Ray Data | 11298.75 秒 | 0.1769 |

| Docling Serve | 12214.70 秒 | 0.1637 |

寫在後面

讀完這篇論文,最觸動我的其實是一個很反直覺的設計取捨:大家一般會覺得,想讓系統更靈活、更能並行處理,就得把數據結構拍扁、變簡單。但 RayOrch 走的是相反的路,它反而給系統加了一層更複雜的狀態管理,專門用來記住那些"看似多餘"的父子關係和序號資訊。結果這層額外的複雜度,換來的是應用層代碼的大幅簡化,用戶寫 Torch 風格的幾行 Python 代碼就能聲明拆分和歸約,剩下的調度、批處理、失敗恢復全部交給運行時去操心。這提醒我一個更普遍的道理:有時候把複雜度往下沉到系統層,而不是硬塞給每個使用者自己實現一遍分組排序邏輯,才是真正的省力氣。

另外一個讓我意外的細節是失敗注入實驗的結果,注入失敗之後系統反而比正常運行更快。乍一看有點反常,細想卻很合理,因為那些提前被攔下來的計算本來就是白費功夫,攔掉之後節省下來的時間比失敗處理本身增加的開銷還多。這個數據背後藏著一個樸素的常識:識別出"不該做的事"並立刻停手,有時候比把所有事情都做完再收拾爛攤子划算得多。

那麼下一個問題是,如果未來的訓練數據流水線里加入了跨批次的窗口操作,或者需要在多個數據源之間做真正的聯表操作,這套只在單個微批次內維護血緣的設計,還能不能扛得住?

Q&A

Q1:RayOrch是什麼?

A:RayOrch是一個用於大模型訓練數據準備的編程模型和分布式執行引擎,專門解決文檔、影片等數據在處理粒度反覆變化時如何保持父子關係、順序和完工判斷的問題。

Q2:RayOrch相比Ray Data和Daft有什麼優勢?

A:RayOrch在MinerU文檔處理任務上端到端時間比Ray Data快13.1%,比Daft快29.0%,還能在失敗發生時精確攔截無效計算,而Ray Data和Daft缺乏這種實時攔截能力。

Q3:RayOrch的擴展性表現如何?

A:在MinerU流水線上從4塊GPU擴到64塊GPU時,處理時間從15.26小時降到1.01小時,加速比達到15.14倍,接近理想線性擴展效果的94.6%。

宅中地 - Facebook 分享 宅中地 - Twitter 分享 宅中地 - Whatsapp 分享 宅中地 - Line 分享
相關內容
Copyright ©2026 | 服務條款 | DMCA | 聯絡我們
宅中地 - 每日更新