Ray 作為新興的分散式計算框架,其資源管理機制是其效能優異的關鍵。Ray 不僅能靈活管理 CPU、記憶體等標準資源,也支援自定義資源的組態,讓開發者能更精細地控制任務執行環境。記憶體管理方面,Ray 以需求為導向進行排程,並區分系統記憶體和應用程式記憶體,有效控制資源消耗。Placement Group 則提供更進階的資源預分配和任務組織能力,讓相關任務在同一節點執行,減少資料傳輸成本。自動擴充套件器功能則能根據負載動態調整工作節點數量,確保資源的有效利用。Ray 的排程器採用雙層架構,全域排程器負責跨節點的資源分配,而本地排程器則管理節點內的任務排程,結合心跳機制和任務依賴關係,實作高效的任務分配和執行。此外,Ray 也支援名稱空間和執行時環境管理,方便開發者管理和分享資源,並確保應用程式在分散式環境中的一致性。
資源管理與自動排程:Ray的探討
雲端計算的資源管理
在雲端計算的環境中,資源管理是保證任務高效執行的關鍵。Ray是一個分散式計算框架,能夠靈活地管理CPU、記憶體以及自定義資源。這些資源的管理不僅影響任務的排程,還會影響整個系統的效能和穩定性。以下將探討Ray如何管理這些資源,並如何透過自動排程來最佳化任務執行。
記憶體需求與排程
在Ray中,指定記憶體需求並不會限制實際的記憶體使用量。這些需求主要用於排程時的準入控制,類別似於CPU排程的方式。任務本身需要確保不超過其申請的記憶體量。例如,@ray.remote(memory=500 * 1024 * 1024) 會為該任務請求500 MiB的記憶體。
記憶體使用細節
Ray的記憶體使用分為兩大類別:Ray系統記憶體和應用程式記憶體。
-
Ray系統記憶體:包括Redis和Raylet兩部分。
- Redis:用於儲存叢集中的節點和Actor清單。這部分記憶體使用量通常較小。
- Raylet:每個節點上執行的C++ Raylet程式所使用的記憶體,這部分也通常較小且無法控制。
-
Ray應用程式記憶體:包括Worker堆積積和Object store兩部分。
- Worker堆積積:使用者應用程式所使用的記憶體,最好測量為應用程式的居住集合大小(RSS)減去其分享記憶體使用量(SHR)。
- Object store記憶體:應用程式在物件儲存中建立物件時使用的記憶體。物件在超出作用域時會被驅逐。當物件儲存滿時,物件會被寫入磁碟。
- Object store分享記憶體:應用程式透過
ray.get讀取物件時使用的記憶體。
自定義資源管理
除了標準的CPU和記憶體資源外,Ray還支援自定義資源管理。這些資源可以是硬體資源,也可以是特定節點上的軟體資源或資料集。
例如,在混合架構叢集中,可以為x86節點新增--resources={"x86": "1"},為ARM節點新增--resources={"arm64":"1"}。這樣可以確保不同型別的節點能夠根據其特性分配不同的任務。
自動擴充套件與垂直擴充套件
自動擴充套件器(Autoscaler)
自動擴充套件器是Ray的一個重要元件,負責管理工作節點。它具備以下功能:
- 啟動新工作節點:根據需求上傳使用者定義的檔案或目錄,並在啟動後執行初始化命令。
- 終止工作節點:當節點閒置、初始化失敗或組態變更時終止工作節點。
- 重啟工作節點:當Raylet當機或工作節點設定變更時重啟工作節點。
垂直擴充套件
垂直擴充套件是指透過增加每個程式的資源來提升效能。Ray支援為任務和Actor請求不同數量的記憶體、CPU核心或GPU。這樣可以根據實際需求靈活組態資源,提升任務執行效率。
Placement Groups
Placement Groups是Ray中用來組織任務和預分配資源的一種機制。它們有助於重複利用資源並提高資料區域性,從而改善整體執行效能。
例如,在同一個節點上執行多個函式進行大規模資料交換,可以利用資料區域性來提升執行效能。
資料圖表
此圖示展示了Ray中的資源管理流程:
graph TD;
C[C]
H[H]
A[任務提出] --> B[檢查資源需求];
B --> C{叢集有足夠資源?};
C -- 是 --> D[分配資源並執行任務];
C -- 否 --> E[啟動新工作節點];
E --> F[等待新節點初始化];
F --> D;
D --> G[監控任務執行];
G --> H{任務完成?};
H -- 是 --> I[釋放資源];
H -- 否 --> G;
內容解密:
- A到B:當任務提出時,首先會檢查其資源需求。
- B到C:檢查叢集是否有足夠的資源來滿足該需求。
- C到D:如果有足夠資源,則直接分配並執行任務。
- C到E:如果沒有足夠資源,則啟動新工作節點。
- E到F:等待新工作節點初始化完成。
- F到D:新工作節點初始化完成後,分配資源並執行任務。
- D到G:監控任務執行過程。
- G到H:檢查任務是否完成。
- H到I:如果任務完成則釋放資源;如果未完成則繼續監控。
資源分配策略:Ray 專屬的置放群組技術
在分散式計算架構中,資源分配策略是影響系統效能和可靠性的重要因素。Ray 透過置放群組(Placement Groups)技術,提供了靈活且高效的資源管理機制,能夠顯著提升任務排程的效率和資源利用率。
資源分配策略的重要性
資源分配策略主要解決兩個核心問題:資料本地性(Data Locality)和故障還原。資料本地性意味著將計算任務盡量分配到與資料較接近的節點上,以減少資料傳輸量,從而提高計算速度。此外,將工作負載分散到多個節點上,可以降低單一節點故障對整體系統的影響。
Ray 的置放群組
置放群組是 Ray 提供的一種高階資源管理機制,允許使用者定義一組資源束(Resource Bundles),並根據特定策略將這些資源束分配到不同的節點上。置放群組的主要功能包括:
- 預先組態資源:確保在任務開始前已經分配好所需的資源,避免因為資源不足而延遲任務啟動。
- 叢集排程(Gang Scheduling):確保所有任務和演員(Actors)同時啟動,從而提高平行度。
- 組織任務和演員:根據需求將任務和演員分配到不同的節點上,以實作最大化資料本地性或負載平衡。
資源束與置放策略
每個置放群組由多個資源束組成,每個資源束包含一組特定的資源(如 CPU、GPU 等)。使用者可以根據需要定義多個資源束,並指定這些資源束應該如何分配到節點上。Ray 支援多種置放策略:
- STRICT_PACK:所有資源束必須放置在單一節點上。
- PACK:嘗試將所有資源束封裝到單一節點上,如果無法完全封裝則允許分散到其他節點。
- STRICT_SPREAD:每個資源束必須放置在不同的節點上。
- SPREAD:嘗試將每個資源束分散到不同的節點上,如果無法完全分散則允許部分封裝。
置放群組的生命週期
置放群組的生命週期包括建立、組態和清理三個階段:
- 建立:當建立置放群組時,Ray 會計算如何分配這些資源束,並向所有節點傳送資源保留請求。Ray 保證這些操作是原子性的。
- 組態:如果現有節點無法滿足要求,Ray 會嘗試透過自動擴充套件來增加新節點。如果無法擴充套件則會傳回錯誤。
- 清理:當任務完成後,Ray 會自動移除置放群組。如果需要持久化某些資源組態,可以透過設定
lifetime="detached"或明確呼叫remove_placement_group來保留。
實際應用範例
以下是如何在 Ray 中建立和使用置放群組的具體步驟:
建立置放群組
首先需要引入必要的模組:
from ray.util.placement_group import (
placement_group,
placement_group_table,
remove_placement_group
)
然後定義所需的資源束並建立置放群組:
cpu_bundle = {"CPU": 3}
mini_cpu_bundle = {"CPU": 1}
pg = placement_group([cpu_bundle, mini_cpu_bundle])
ray.get(pg.ready())
print(placement_group_table(pg))
print(ray.available_resources())
在指定位置執行遠端函式
使用 options 引數指定遠端函式執行在特定的資源束上:
handle = remote_fun.options(placement_group=pg, placement_group_bundle_index=0).remote(1)
擴充套件應用:複雜資源組
如果在叢集中執行 Ray ,可以建立更複雜的資源組。例如 ,若叢集中包含 GPU 節點 ,則可以建立更復雜的資源組以滿足特定需求。
gpu_bundle = {"CPU": 2, "GPU": 1}
cpu_gpu_bundle = {"CPU": 4, "GPU": 2}
complex_pg = placement_group([gpu_bundle, cpu_gpu_bundle])
ray.get(complex_pg.ready())
內容解密:
from ray.util.placement_group import:這段程式碼引入了 Ray 中管理置放群組所需的模組。placement_group([cpu_bundle, mini_cpu_bundle]):這行程式碼建立了一個包含兩個資源束的置放群組。ray.get(pg.ready()):這行程式碼等待直到該置放群組準備就緒。print(placement_group_table(pg)):這行程式碼列印出該置放群組中的所有已組態資源束。print(ray.available_resources()):這行程式碼列印出叢集中目前可用的所有資源。handle = remote_fun.options(placement_group=pg, placement_group_bundle_index=0).remote(1):這行程式碼將遠端函式remote_fun的執行位置限定為指定pg中索引為0的資源束。
透過這些步驟,玄貓能夠有效地利用 Ray 的置放群組技術來最佳化分散式計算任務中的資源管理與排程。
分散式計算平台 Ray 的詳細設計與應用
自動調整放置群組
在 Ray 中,我們可以使用放置群組來確保特定的任務或 Actor 在相同的節點上執行。這對於需要低延遲通訊或分享狀態的應用程式特別有用。例如,我們可以建立一個混合 CPU 和 GPU 的放置群組,確保某些任務能夠使用 GPU 資源。
# 建立一個混合 CPU 和 GPU 的放置群組
cpu_bundle = {"CPU": 1}
gpu_bundle = {"GPU": 1}
pg = placement_group([cpu_bundle, gpu_bundle])
ray.get(pg.ready())
print(placement_group_table(pg))
print(ray.available_resources())
內容解密:
以上程式碼展示瞭如何在 Ray 中建立一個混合 CPU 和 GPU 的放置群組。首先,我們定義了兩個資源束(bundle),分別代表 CPU 和 GPU 資源。接著,我們使用 placement_group 函式建立了一個包含這兩個束的放置群組。ray.get(pg.ready()) 會等待直到放置群組中的所有資源都可用。最後,我們列印預出放置群組的表格和當前可用的資源。
當我們在測試叢集上執行這段程式碼時,Ray 的自動調整器會分配一個具有 GPU 的節點來滿足這些資源需求。完成後,我們可以使用 remove_placement_group(pg) 來刪除這個放置群組。
Ray 排程器
Ray 使用一種自下而上的分散式排程器,由全域排程器和每個節點的本地排程器組成。任務建立時,會先提交到節點的本地排程器,這樣可以鼓勵任務的本地性。如果本地節點過載或無法滿足任務需求(例如缺少 GPU),本地排程器會呼叫全域排程器接管。
全域排程器會先識別出能夠滿足任務資源需求的節點集合,然後選擇估計等待時間最短的節點來執行任務。估計等待時間包括任務在該節點的排隊時間和遠端輸入的傳輸時間。
每個工作者會定期向全域排程器傳送心跳訊息,包括資源可用性和佇列深度。全域排程器也會根據 GCS 中的任務輸入位置和大小來決定排程位置。一旦全域排程器選擇了節點,它會呼叫該節點的本地排程器來安排任務。
放置群組命名
我們可以為放置群組指定名稱,這樣可以在 Ray 叢集中的任何工作中透過名稱來檢索和使用放置群組,而不需要傳遞放置群組處理程式。
# 建立一個命名的放置群組
pg = placement_group([cpu_bundle, gpu_bundle], name="my_placement_group")
內容解密:
這段程式碼展示瞭如何在建立放置群組時指定名稱。透過新增 name="my_placement_group" 引數,我們可以為放置群組指定一個名稱,這樣在需要時可以透過名稱來檢索和使用它。
名稱空間
名稱空間是作業和 Actor 的邏輯分組,提供有限的隔離性。預設情況下,每個 Ray 程式都在自己的匿名名稱空間中執行,無法從其他 Ray 程式存取。要在多個 Ray 應用程式之間分享 Actor,必須將它們放在同一個名稱空間中。
# 初始化 Ray 並指定名稱空間
ray.init(namespace="shared_namespace")
內容解密:
這段程式碼展示瞭如何在初始化 Ray 時指定名稱空間。透過 namespace="shared_namespace" 引數,我們可以將不同的 Ray 應用程式放在同一個名稱空間中,以便它們可以分享 Actor 和資源。
執行時環境管理
Ray 支援使用 Conda 和 Virtualenv 來管理依賴關係。Ray 會根據需要在更大的容器內動態建立這些虛擬環境,並使用比對的環境啟動工作者。
以下是如何為 Python 應用程式指定依賴關係並建立執行時環境:
# 指定需要的 PyPI 包
runtime_env = {"pip": ["bs4"]}
內容解密:
這段程式碼展示瞭如何在 Ray 中指定需要的 PyPI 包來建立執行時環境。透過 runtime_env = {"pip": ["bs4"]},我們可以確保 Beautiful Soup 函式庫在分散式環境中可用。
如果有更複雜的依賴關係設定(例如使用 Conda),可以透過指定 Conda 環境檔案或包列表來建立執行時環境:
# 指定 Conda 環境檔案路徑
runtime_env = {"conda": "path/to/conda/env/file"}
內容解密:
這段程式碼展示瞭如何在 Ray 中指定 Conda 環境檔案路徑來建立執行時環境。透過 runtime_env = {"conda": "path/to/conda/env/file"},我們可以確保所有必要的 Conda 包都可用於分散式執行。
雙重架構及雙重圖示
此圖示顯示了 Ray 的雙重架構設計:
graph LR A[Global Scheduler] --> B[Local Scheduler] B --> C[Worker] C --> D[Task] D --> E[Remote Input] E --> F[Object Store] B --> G[Heartbeat] G --> A
說明圖示:
- Global Scheduler:負責全域資源分配。
- Local Scheduler:每個節點上的本地排程器。
- Worker:執行任務的工作者。
- Task:待執行的任務。
- Remote Input:遠端輸入。
- Object Store:物件儲存區。
- Heartbeat:工作者向全域排程器傳送心跳訊息。
此圖示展示了 Ray 的雙重架構設計:全域排程器負責全域資源分配,而每個節點上的本地排程器則負責本地資源管理和心跳訊息傳送。
動態虛擬環境及其影響
動態建立虛擬環境可能會導致啟動和擴充套件速度變慢,特別是涉及大型原生程式碼編譯或沒有預先存在輪子(wheel)的情況下(例如 TensorFlow 在 ARM 上)。為瞭解決這些問題,我們可以在叢集或容器中預先建立好 Conda 環境。
# 在叢集中建立 Conda 環境
conda create -n myenv python=3.8 tensorflow
內容解密:
此圖示說明如何在叢集或容器中預先設定好Conda環境以加速啟動及擴充套件速度:由手動設定Conda虛擬環境以滿足需要大型原生程式碼編譯及沒有預先存在輪子(wheel)等情況下之專案需求。
執行佈署模式
Ray 提供了一種輕量級機制來提交作業而不必擔心函式庫不比對問題和避免遠端叢集與主節點之間網路不穩定問題:
# 提交作業到 Ray 叢集
job = ray.job.submit(remote_function, runtime_env=runtime_env)
內容解密:
此圖示說明如何使用Ray作業API提交作業以避免函式庫不比對問題及網路不穩定問題:透過ray.job.submit()函式提交作業至Ray叢集並搭配所需之執行時環境引數進行實際佈署操作。