Uber 基於 Ray 優化乘車業務實踐
介紹
將解決方案擴展到像 Uber 這樣龐大且複雜的市場時,計算效率是一個重大挑戰。 Uber 乘車業務的運行和調優依賴於大量的機器學習模型和優化算法。幸運的是,系統的許多部分可以並行處理。 Ray[1] ® 是 Python® 的通用計算引擎,專爲 ML、AI 和其他算法工作負載而設計。我們描述 Uber 如何採用 Ray 來支持關鍵任務系統。以 Uber 的移動市場分配調整系統爲例,我們發現性能提升高達 40 倍,從而解鎖了新功能。它還通過提高迭代速度、減少事件緩解時間和降低代碼複雜性來提高開發人員的工作效率。
動機和背景
爲了管理移動市場的健康和效率,優步可以調整多種槓桿,例如激勵司機每週完成一定數量的行程,或者爲乘客提供行程折扣的促銷活動。弄清楚如何設置這些槓桿來有效地實現各種目標並最大限度地提高成果,這既是一項技術上具有挑戰性的任務,對公司來說也是一個高價值的機會。
我們想要解決的問題之一如圖 1 所示:
圖 1:市場激勵目標函數。
在這個問題中,我們希望通過改變我們控制的變量 “b” 來最大化對業務有一定價值的目標“f”。由於這些變量是按城市控制的,因此我們將粒度稱爲城市槓桿。我們還受到一些旨在保持市場健康的限制。實際上,這是一個每週解決一次的問題,並且控制變量在一週內有效。
爲了實現這一目標,我們利用大量的觀察和實驗數據來爲這些決策提供信息。這需要構建一個可以擴展的系統,以處理大量數據以進行模型訓練和推理,並容納許多決策變量。
我們設計瞭如圖 2 所示的工作流程來實現我們的目標。一開始有一個特徵存儲系統來處理數據並填充機器學習模型的 ML 模型的特徵。第二個 ML 模型訓練部分進行模型訓練並用於預測預算分配。第三個預算分配部分是根據圖 1 的方程進行優化評估,找到最優的預算分配方案。
圖 2:預算分配系統工作流程。
這個原始工作流程純粹基於 Apache Spark ™進行分佈式計算。 Spark 在第一個特徵存儲系統部分的數據處理方面工作得非常好。然而,對於 Pandas 來說就不那麼好了。我們嘗試過 Pandas UDF(用戶定義函數)或多線程來實現並行,但速度提升並不理想。因此,我們決定嘗試 Ray,因爲它是爲自然的 Python 代碼並行性而設計的。
開發和部署挑戰
原來基於 Spark 的預算分配存在一些主要瓶頸。
Spark 與 Ray 的並行性:分佈式框架選擇瓶頸
正如上節末尾簡要介紹的,Spark 和 Ray 作爲分佈式框架各有優缺點。 Spark 擅長處理給定特定 Spark/PySpark API 的數據處理,並且它負責處理不同 Spark 執行器之間的所有並行性。但是,Spark 無法接受 Pandas 操作或用戶自定義的 Python 代碼並使它們自動並行運行。相反,Ray 可以輕鬆地使 Pandas 代碼或自然 Python 函數並行運行,這對於 Spark 在我們的工作流程中無法加速的應用程序來說是一個完美的用例。不過,Ray 目前還不能支持 Spark 相關的 API,而且 Ray 本身也像 Spark 一樣對數據處理 API 支持有限。Ray Data 可能是未來潛在的解決方案,但到目前爲止它主要用於機器學習而不是一般數據處理目的。所以,僅僅使用一個簡單的分佈式框架來實現我們的目標是很困難的。 Spark 和 Ray 都有自己可以利用的優點。
高併發、輕量級並行:應用速度瓶頸
我們的應用程序中有許多高併發、輕量級並行 Python 函數。比如我們對每個城市有一個優化功能,他們可以在 1-2 秒內快速完成。然而,如果我們要同時做數千個城市的優化功能,那麼安排和分配可能會是一個問題。我們考慮了幾種方法:
-
- Spark :由於 Spark 在不使用 Spark API 的情況下不支持並行 Python 函數,因此所有這些城市優化函數僅在 Spark 的驅動程序節點中串聯運行。
-
- Pandas UDF :我們在 Spark 上嘗試了 Pandas UDF 來加速 Pandas 數據幀的操作速度。然而,速度提升並不理想。此外,Pandas UDF 無法並行化一般的 Python 代碼。
-
- 每個城市的獨立作業:我們需要爲每個城市啓動一個 Docker® 容器來運行,這包含啓動開銷和潛在的計算資源浪費。
Spark 和基於 Pandas 的代碼庫:代碼遷移瓶頸
由於我們在基於 Spark 的集羣上運行遺留代碼,因此編寫了大量 PySpark 代碼來執行數據預處理和後處理。然而,Ray 天然不支持 PySpark 或 Spark 相關代碼。如果我們想要將所有代碼遷移到 Ray 上運行,代碼遷移成本可能會非常巨大,並且需要許多工程師共同努力將所有遺留 PySpark 代碼轉換爲一些等效的 Ray 可理解的代碼。
架構解決方案
爲了解決上一節中提到的瓶頸,我們開發了一種混合模式,圖 3 中提供了 Spark 和 Ray。我們認爲我們應該利用 Spark 和 Ray 的優勢。邏輯簡單明瞭:將數據處理相關的工作放在 Spark 上,將並行的 Python 函數放在 Ray 上。
圖 3:Spark 和 Ray 混合執行模式。
圖 4 顯示了應用程序工作流程。 Spark driver 在這裏扮演了應用程序 master 的角色,應用程序代碼主要運行在 Spark driver 上。當從 Apache HDFS ™加載數據或進行數據預處理時,Spark 驅動程序將工作負載分配給 Spark 執行器並執行任何與數據相關的計算。當 Spark 驅動程序遇到無法在 Spark 上並行計算的內容時,它會將任務發送給 Ray。 Ray 集羣根據請求充當外部計算服務器。接收到 Spark 的函數請求後,並行執行 Spark driver 發送的函數,完成後將結果發送回 Spark driver。 Spark 驅動程序彙總 Ray 的輸出,並與 Spark 執行器一起進行一些數據後處理。最後,Spark 將輸出數據幀寫回 HDFS。
圖 4:Spark 和 Ray 應用程序工作流程。
這種設計可以解決我們應用程序之前的主要障礙。我們不需要擔心 Spark 和 Ray 之間的權衡,因爲我們兩者都有。 Ray 作爲 Spark 的外部服務器,可以接受 Spark 的任何高併發請求,並將計算結果高效地返回給 Spark。由於 Ray 內部有一個控制器來安排發送到 Ray 集羣的任務,因此我們不需要實現一個編排器來監控現有任務或一個消息隊列來安排所有等待的任務。代碼遷移成本也很低,因爲我們仍然可以將大部分 PySpark 代碼保留在 Spark 集羣中執行。只有適合 Ray 的函數纔會被移到 Ray 集羣中並行執行。
跟進這種混合設計,我們還開發了一些功能和工具來幫助進一步提高我們的應用速度,也幫助我們的工程師提高他們的開發迭代速度。
部署和啓動時間優化:迭代速度提升
圖 5 展示了我們如何優化工程師的作業部署和啓動方式,以提高開發迭代速度。我們發現,如果用戶想要進行遠程測試,這可能會非常耗時,因爲他們每次都需要構建新的 Docker 鏡像,即使他們只是對應用程序代碼進行了很小的更改。通常構建 Docker 鏡像大約需要 15-20 分鐘。爲了加速這一過程,我們使用 Amazon S3 ® 等對象存儲作爲應用程序代碼的中間存儲層。每次用戶啓動作業時,他們只需要提供一個僅存儲庫的基本 Docker 鏡像。更改後的應用程序代碼會實時部署,因此不需要每次都構建新的 Docker 映像。因此,我們可以將作業部署和啓動時間控制在 2 分鐘內,這顯着提高了我們的實驗迭代速度。
圖 5:Ray 集羣部署和啓動流程。
數據傳輸速度優化
當引入 Ray 集羣作爲外部集羣時,Spark 集羣和 Ray 集羣之間的通信有時會成爲問題。當數據量較小時這是很好的,這樣我們可以快速發送數據並忽略傳輸開銷。然而,當數據量達到 GB 或 TB 級別時,傳輸時間就不容忽視。如果直接將數據從 Spark 集羣發送到 Ray,有幾個因素會影響傳輸速率:
-
• 互聯網帶寬
-
• 數據序列化和反序列化
-
• Spark driver Pandas 轉換速度
由於 Ray 不支持 Spark 數據幀,Spark 驅動程序需要將來自不同執行器的 Spark 數據幀收集到 Spark 驅動程序中,並將其轉換爲 Pandas 數據幀。此外,如果 Pandas 數據幀太大,可能會導致 Spark 驅動程序出現內存不足問題。
圖 6:Spark 和 Ray 之間的數據傳輸。
爲了加快數據傳輸速度並避免潛在的內存不足問題,我們引入 HDFS 作爲大型數據集傳輸的中間存儲層。與 HDFS 通信時我們獲得更大的帶寬。更重要的是,我們可以直接使用 Spark 將數據作爲 Parquet 文件寫入 HDFS,並使用 Ray 的數據 API 將數據從 HDFS 加載到 Ray。
生產和開發環境一致的 Notebook
爲了幫助開發人員提高速度並減少將暫存代碼轉換爲生產的工作量,我們設計了一款具有與生產環境相同的環境設置和標準的筆記本。我們的開發人員可以在筆記本中測試他們的代碼。當他們讓程序在 Notebook 中運行後,相同的代碼也可以在生產中運行。一個典型的例子是我們的數據科學家更喜歡在 Notebook 中編寫 Pandas 相關的代碼。然而,之前我們沒有一個好的方法來加速 Pandas 在生產中的並行運行。有時後端工程師需要手動將這些操作轉換爲 Pyspark,以使程序更快。採用 Ray 後,數據科學家可以直接在 Notebook 中編寫 Pandas 代碼,後端工程師可以輕鬆地將這些 Pandas 代碼遷移到生產中,而無需進行太多轉換。
圖 7:生產和開發環境一致的筆記本電腦。
Uber 的用例:ADMM 優化器
在本節中,我們將描述 Ray 激勵預算分配系統中一個組件的實現,Ray 是一種優化器,可將總預算劃分爲每個城市級別的分配向量。
由於我們對分配施加了簡單的圓錐約束,並對機器學習模型施加了平滑條件,因此 ADMM(乘數交替方向法)非常適合解決我們的分配問題,因爲它能夠解決非線性、非凸問題,可微問題。
ADMM 算法的另一個優點是它可以很好地並行化,這使得我們的系統可以在添加城市或槓桿時進行擴展。
爲了解決我們的優化問題,我們將其轉換爲圖 8 中略有不同的公式:
圖 8:優化公式。
這導致了更新步驟,我們解決這些步驟來執行圖 9 中的 ADMM 算法:
圖 9:優化求解器。
第一步是使用 Ray 並行求解,並使用 cvxopt 中實現的原對偶內點算法。可以調整 Rho 以確保該問題具有正半定 Hessian 矩陣。第二步可以解析求解,第三步則微不足道。
這是我們的 ADMM 優化算法的工作流程,以及 Ray 如何應用於圖 10 中的該架構。基本上,它是循環預算分配 ADMM 優化的循環。可以分爲以下幾個步驟:
步驟 1:初始化問題。
步驟 2:for 循環中的優化。
-
- 並行解決每週的個別城市問題 [Ray]。
-
- 總結城市優化結果 [Spark]。
-
- 更新跨城市約束變量 [Spark]。
-
- 更新鬆弛變量。 [Spark]。
-
- 檢查收斂標準 [Spark]。
步驟 3:記錄最佳分配值、元數據和收斂變量。
圖 10:ADMM 優化工作流程。
ADMM 的每個城市周優化功能都非常輕量級,大約需要 1-2 秒即可完成。然而,由於高併發優化函數數量龐大,優化速度成爲我們優化器的瓶頸。應用 Ray 進行城市並行優化計算後,我們的預算分配優化器實現了約 40 倍的改進。
Uber Michelangelo 的 KubeRay 後端支持
一個好的 Ray 應用程序應該在應用程序級別具有良好的並行策略和可靠的 Ray 後端來支持所有這些並行計算。在本節中,我們將描述 Michelangelo(Uber 的人工智能平臺)[2] 團隊如何構建可靠的 Ray 後端,供所有客戶團隊在 Uber 中使用。
圖 11 顯示瞭如何在後端配置 Ray 集羣。當提交作業時啓動 Ray 集羣時,Ray 集羣的資源會根據作業的需求(例如節點數、CPU、GPU 和內存規格)動態配置。一旦配置了 Ray 集羣,首先建立 Ray 頭節點,然後它發現並連接 Ray 的所有工作節點。應用程序連接到 Ray 頭節點,該節點協調 Ray 任務在工作節點之間的分佈式執行。 Ray 頭節點的連接詳細信息(IP 地址和端口)會自動發現並提供給應用程序,從而無需手動干預即可無縫執行。作業完成後,應用程序向計算層發送請求,並將分配的資源釋放回池中,確保整個平臺的高效利用。
圖 11:Ray 集羣配置工作流程。
我們還研究並採用了 Ray 核心服務之外的其他模塊。 Ray 與生態系統中的各種開源框架和庫很好地集成,並且可以更輕鬆地試驗來自行業的新技術。我們將 Horovod 與 Ray[3] 集成,採用了 Ray XGBoost[4] 、Ray Data、異構訓練集羣和 Ray Tune,這使得 Ray 成爲 Michelangelo 中用於訓練和微調 XGB、DL 和 Ray 的公共層。LLM 模型。
2023 年,我們將資源集羣從 Peloton[5] (舊的資源調度程序)現代化爲基於 Kubernetes ® 的新 Michelangelo 作業控制器 [6] 服務,並提升了 CPU 和 GPU 訓練作業的資源管理體驗。
儘管存在本地和雲提供商的基礎設施限制,但該服務從用戶那裏抽象了計算集羣和硬件複雜性,並確保動態資源選擇、高可擴展性和靈活性以及更高效的資源調度,這使其非常適合不同的用戶 Uber 的需求。好處包括:
-
• 自動資源分配:設計使用 CRD 來定義資源池,根據組織層次結構、集羣大小和硬件類型要求自動將作業分配到適當的池。這消除了手動資源分配,減少了爭用和超額訂閱。
-
• 動態調度:聯合調度程序會考慮資源可用性、親和性和作業優先級等因素,智能地將作業與集羣匹配。這可確保最佳的資源使用並防止調度失敗。
-
• 集羣健康狀況監控:持續監控集羣健康狀況並維護最新的資源快照,確保作業僅調度在具有可用資源的健康集羣上。
-
• 簡化的用戶體驗:用戶不需要管理底層基礎設施——系統抽象了複雜性,讓他們能夠專注於自己的工作負載,而系統處理資源管理。
-
• 可擴展性和可擴展性:該設計支持新硬件、雲爆發,並與新興技術兼容,確保其能夠擴展並適應未來需求,同時保持高效的資源管理。
後來在 2024 年初,我們成功將所有現有的 XGB 和深度學習訓練作業遷移到 Michelangelo 作業控制器。藉助作業控制器的優勢,我們不斷解鎖更多用例,例如大型語言模型微調和優化應用程序。
結論
Ray 已成爲 Uber 機器學習和一切有潛力並行化的重要工具,例如優化算法、評估算法等。 Ray 現在在 Uber 被廣泛使用,從 Uber Michelangelo 團隊作爲基礎後端開始,應用於不同的應用團隊,如 Marketplace Investment 團隊,爲 Uber 進行預算分配優化。通過在 Uber 應用程序中使用 Ray,我們實現了巨大的性能改進,併爲我們的數據分析師和開發人員提供了更友好的用戶體驗。
引用鏈接
[1]
Ray:https://www.ray.io/
[2]
Michelangelo(Uber 的人工智能平臺):https://www.uber.com/blog/from-predictive-to-generative-ai/
[3]
Horovod 與 Ray:https://www.uber.com/blog/horovod-ray/
[4]
Ray XGBoost:https://www.uber.com/blog/elastic-xgboost-ray/
[5]
Peloton:https://www.uber.com/blog/resource-scheduler-cluster-management-peloton/
[6]
Michelangelo 作業控制器:https://www.uber.com/blog/scaling-ai-ml-infrastructure-at-uber/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/9gLAzxO4SLAxiu-5RHUZ5A