本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

在 Kubernetes 上使用 PaddlePaddle 執行深度學習

什麼是 PaddlePaddle

PaddlePaddle 是一個易於使用、高效、彈性且可擴展的深度學習平台,最初由百度開發,自 2014 年以來將深度學習應用於百度產品。

已使用 PaddlePaddle 創造超過 50 項創新,支援 15 項百度產品,範圍從搜尋引擎、線上廣告到問答和系統安全。

2016 年 9 月,百度開源了 PaddlePaddle,並很快吸引了許多來自百度以外的貢獻者。

為何在 Kubernetes 上執行 PaddlePaddle

PaddlePaddle 的設計宗旨是精簡且獨立於運算基礎架構。使用者可以在 Hadoop、Spark、Mesos、Kubernetes 等之上執行它。我們對 Kubernetes 有著濃厚的興趣,因為它的彈性、效率和豐富的功能。

當我們在各種百度產品中應用 PaddlePaddle 時,我們注意到兩種主要的 PaddlePaddle 用途 - 研究和產品。研究數據通常不會經常變更,重點是快速實驗以達到預期的科學測量。產品數據經常變更。它通常來自 Web 服務產生的日誌訊息。

一個成功的深度學習專案包含研究和數據處理管線。有許多參數需要調整。許多工程師同時處理專案的不同部分。

為了確保專案易於管理並有效利用硬體資源,我們希望在同一個基礎架構平台上執行專案的所有部分。

該平台應提供

  • 容錯能力。它應將管線的每個階段抽象化為服務,該服務由許多程序組成,這些程序透過冗餘提供高吞吐量和穩健性。

  • 自動擴展。在白天,通常有許多活躍使用者,平台應擴展線上服務。而在夜間,平台應釋放一些資源用於深度學習實驗。

  • 工作包裝和隔離。它應能夠將需要 GPU 的 PaddlePaddle 訓練器程序、需要大量記憶體的 Web 後端服務以及需要磁碟 IO 的 CephFS 程序指派到同一個節點,以充分利用其硬體。

我們想要的是一個平台,它可以在同一個叢集上執行深度學習系統、Web 伺服器 (例如,Nginx)、日誌收集器 (例如,fluentd)、分散式佇列服務 (例如,Kafka)、日誌聯結器以及使用 Storm、Spark 和 Hadoop MapReduce 編寫的其他數據處理器。我們希望在同一個叢集上執行所有工作 - 線上和離線、生產和實驗 - 這樣我們就可以充分利用叢集,因為不同種類的工作需要不同的硬體資源。

我們選擇基於容器的解決方案,因為 VM 引入的額外負擔與我們追求效率和利用率的目標相矛盾。

根據我們對不同基於容器的解決方案的研究,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:在兩個節點上運行的三個 Pod 的工作 A 和一個 Pod 的工作 B。|

每個 Pod 的進入點是 start.sh。它從儲存服務下載數據,以便訓練器可以從 Pod 本地磁碟空間快速讀取。下載完成後,它執行一個 Python 腳本 start_paddle.py,該腳本啟動一個參數伺服器,等待直到所有 Pod 的參數伺服器都準備好提供服務,然後啟動 Pod 中的訓練器程序。

這種等待是必要的,因為如圖 1 所示,每個訓練器都需要與所有參數伺服器對話。Kubernetes API 使訓練器能夠檢查 Pod 的狀態,因此 Python 腳本可以等待直到所有參數伺服器的狀態變更為 "running",然後再觸發訓練程序。

目前,從數據碎片到 Pod/訓練器的映射是靜態的。如果我們要執行 N 個訓練器,我們需要將數據分割成 N 個碎片,並靜態地將每個碎片指派給一個訓練器。我們再次依賴 Kubernetes API 來列出工作中的 Pod,以便我們可以將 Pod/訓練器從 1 編號到 N。第 i 個訓練器將讀取第 i 個數據碎片。

訓練數據通常在分散式檔案系統上提供。在實務上,我們在內部部署叢集上使用 CephFS,在 AWS 上使用 Amazon Elastic File System。如果您有興趣建構一個 Kubernetes 叢集來執行分散式 PaddlePaddle 訓練工作,請遵循 本教學課程

下一步

我們正在努力更順暢地使用 Kubernetes 執行 PaddlePaddle。

您可能會注意到,目前的訓練器排程完全依賴於基於靜態分割地圖的 Kubernetes。這種方法很容易上手,但可能會導致一些效率問題。

首先,緩慢或停止運作的訓練器會阻止整個工作。在初始部署後,沒有受控的搶佔或重新排程。其次,資源分配是靜態的。因此,如果 Kubernetes 擁有的可用資源超出我們的預期,我們必須手動變更資源需求。這是繁瑣的工作,並且不符合我們的效率和利用率目標。

為了解決上述問題,我們將新增一個 PaddlePaddle master,它可以理解 Kubernetes API,可以動態新增/移除資源容量,並以更動態的方式將碎片分派給訓練器。PaddlePaddle master 使用 etcd 作為從碎片到訓練器的動態映射的容錯儲存。因此,即使 master 崩潰,映射也不會遺失。Kubernetes 可以重新啟動 master,工作將繼續運行。

另一個潛在的改進是更好的 PaddlePaddle 工作組態。我們擁有相同數量的訓練器和參數伺服器的經驗主要來自使用特殊用途叢集。觀察到該策略在僅運行 PaddlePaddle 工作的客戶叢集上表現良好。但是,此策略在運行多種工作的一般用途叢集上可能不是最佳的。

PaddlePaddle 訓練器可以利用多個 GPU 來加速運算。GPU 在 Kubernetes 中還不是一級資源。我們必須半手動地管理 GPU。我們很樂意與 Kubernetes 社群合作改進 GPU 支援,以確保 PaddlePaddle 在 Kubernetes 上運行最佳。