本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
在 Kubernetes 上使用 PaddlePaddle 執行深度學習
什麼是 PaddlePaddle
PaddlePaddle 是一個易用、高效、靈活、可擴充套件的深度學習平臺,最初由百度於2014年開發,用於將深度學習應用於百度產品。
使用 PaddlePaddle 已經建立了超過50項創新,支援15個百度產品,範圍從搜尋引擎、線上廣告到問答和系統安全。
2016年9月,百度開源了 PaddlePaddle,它很快吸引了許多百度之外的貢獻者。
為什麼要在 Kubernetes 上執行 PaddlePaddle
PaddlePaddle 的設計旨在精簡且獨立於計算基礎設施。使用者可以在 Hadoop、Spark、Mesos、Kubernetes 等之上執行它。我們對 Kubernetes 興趣濃厚,因為它具有靈活性、高效性和豐富的功能。
在我們將 PaddlePaddle 應用於各種百度產品時,我們注意到 PaddlePaddle 的兩種主要使用方式——研究和產品。研究資料不常變動,重點是快速實驗以達到預期的科學測量。產品資料經常變動。它通常來自網路服務生成的日誌訊息。
一個成功的深度學習專案包括研究和資料處理管道。有許多引數需要調整。許多工程師同時在專案的不同部分工作。
為了確保專案易於管理並有效利用硬體資源,我們希望在同一基礎設施平臺上執行專案的所有部分。
該平臺應提供:
容錯能力。它應該將管道的每個階段抽象為一個服務,該服務由許多程序組成,透過冗餘提供高吞吐量和魯棒性。
自動伸縮。白天,通常有許多活躍使用者,平臺應擴充套件線上服務。而在夜間,平臺應為深度學習實驗釋放一些資源。
作業打包和隔離。它應該能夠將需要 GPU 的 PaddlePaddle 訓練器程序、需要大記憶體的 Web 後端服務以及需要磁碟 IO 的 CephFS 程序分配到同一個節點上,以充分利用其硬體。
我們想要的是一個平臺,它在同一個叢集上執行深度學習系統、Web 伺服器(例如 Nginx)、日誌收集器(例如 fluentd)、分散式佇列服務(例如 Kafka)、日誌合併器以及使用 Storm、Spark 和 Hadoop MapReduce 編寫的其他資料處理器。我們希望在同一個叢集上執行所有作業——線上和離線、生產和實驗——這樣我們就可以充分利用叢集,因為不同型別的作業需要不同的硬體資源。
我們選擇基於容器的解決方案,因為虛擬機器引入的開銷與我們追求效率和利用率的目標相悖。
根據我們對不同基於容器解決方案的研究,Kubernetes 最符合我們的要求。
Kubernetes 上的分散式訓練
PaddlePaddle 原生支援分散式訓練。PaddlePaddle 叢集中有兩個角色:引數伺服器和訓練器。每個引數伺服器程序維護全域性模型的一個分片。每個訓練器擁有模型的一個本地副本,並使用其本地資料更新模型。在訓練過程中,訓練器將模型更新發送給引數伺服器,引數伺服器負責聚合這些更新,以便訓練器可以將其本地副本與全域性模型同步。
| | | 圖1:模型被分成兩個分片。分別由兩個引數伺服器管理。 |
其他一些方法使用一組引數伺服器來共同在多個主機的 CPU 記憶體空間中儲存一個非常大的模型。但在實踐中,我們很少有如此大的模型,因為由於 GPU 記憶體的限制,處理非常大的模型效率會很低。在我們的配置中,多個引數伺服器主要是為了實現快速通訊。假設只有一個引數伺服器程序與所有訓練器一起工作,引數伺服器將不得不聚合所有訓練器傳來的梯度,併成為瓶頸。根據我們的經驗,一個實驗上高效的配置是訓練器和引數伺服器數量相同。我們通常在同一個節點上執行一對訓練器和引數伺服器。在下面的 Kubernetes 作業配置中,我們啟動一個執行 N 個 Pod 的作業,每個 Pod 中包含一個引數伺服器程序和一個訓練器程序。
yaml
apiVersion: batch/v1
kind: Job
metadata:
name: PaddlePaddle-cluster-job
spec:
parallelism: 3
completions: 3
template:
metadata:
name: PaddlePaddle-cluster-job
spec:
volumes:
- name: jobpath
hostPath:
path: /home/admin/efs
containers:
- name: trainer
image: your\_repo/paddle:mypaddle
command: ["bin/bash", "-c", "/root/start.sh"]
env:
- name: JOB\_NAME
value: paddle-cluster-job
- name: JOB\_PATH
value: /home/jobpath
- name: JOB\_NAMESPACE
value: default
volumeMounts:
- name: jobpath
mountPath: /home/jobpath
restartPolicy: Never
從配置中可以看出,並行度(parallelism)和完成數(completions)都設定為 3。因此,該作業將同時啟動 3 個 PaddlePaddle Pod,並在所有 3 個 Pod 完成後結束。
圖2:作業A包含三個 Pod,作業B包含一個 Pod,在兩個節點上執行。
每個 Pod 的入口點是 start.sh。它從儲存服務下載資料,以便訓練器可以快速從 Pod 本地磁碟空間讀取。下載完成後,它會執行一個 Python 指令碼 start_paddle.py,該指令碼啟動一個引數伺服器,等待所有 Pod 的引數伺服器準備好服務,然後啟動 Pod 中的訓練器程序。
這種等待是必要的,因為每個訓練器都需要與所有引數伺服器通訊,如圖1所示。Kubernetes API 使訓練器能夠檢查 Pod 的狀態,因此 Python 指令碼可以在所有引數伺服器的狀態變為“執行中”之前等待,然後才觸發訓練過程。
目前,資料分片到 Pod/訓練器的對映是靜態的。如果要執行 N 個訓練器,我們需要將資料分成 N 個分片,並將每個分片靜態地分配給一個訓練器。我們再次依賴 Kubernetes API 來列出作業中的 Pod,這樣我們就可以將 Pod/訓練器從 1 索引到 N。第 i 個訓練器將讀取第 i 個數據分片。
訓練資料通常儲存在分散式檔案系統上。在我們的本地叢集中,我們使用 CephFS;在 AWS 上,我們使用 Amazon Elastic File System。如果您有興趣構建一個 Kubernetes 叢集來執行分散式 PaddlePaddle 訓練作業,請遵循此教程。
下一步
我們正在努力讓 PaddlePaddle 與 Kubernetes 更加流暢地執行。
您可能已經注意到,當前的訓練器排程完全依賴於基於靜態分割槽對映的 Kubernetes。這種方法啟動簡單,但可能會導致一些效率問題。
首先,緩慢或宕機的訓練器會阻塞整個作業。初始部署後沒有受控的搶佔或重新排程。其次,資源分配是靜態的。因此,如果 Kubernetes 擁有比我們預期更多的可用資源,我們必須手動更改資源需求。這是一項繁瑣的工作,與我們的效率和利用率目標不符。
為了解決上述問題,我們將增加一個理解 Kubernetes API 的 PaddlePaddle Master,它可以動態地新增/刪除資源容量,並以更動態的方式將分片排程到訓練器。PaddlePaddle Master 使用 etcd 作為分片到訓練器動態對映的容錯儲存。因此,即使 Master 崩潰,對映也不會丟失。Kubernetes 可以重啟 Master,作業將繼續執行。
另一個潛在的改進是更好的 PaddlePaddle 作業配置。我們關於訓練器和引數伺服器數量相同的經驗主要來自於使用專用叢集。這種策略在僅執行 PaddlePaddle 作業的客戶端叢集上表現良好。然而,在執行多種作業的通用叢集上,這種策略可能不是最優的。
PaddlePaddle 訓練器可以利用多個 GPU 來加速計算。GPU 在 Kubernetes 中尚未成為一級資源。我們必須半手動管理 GPU。我們很樂意與 Kubernetes 社群合作,改進 GPU 支援,以確保 PaddlePaddle 在 Kubernetes 上執行得最好。
- 下載 Kubernetes
- 在 GitHub 上參與 Kubernetes 專案
- 在 Stack Overflow 上提問(或回答問題)
- 在 Slack 上與社群聯絡
- 在 Twitter 上關注我們 @Kubernetesio 獲取最新更新