隨著機器學習模型日益複雜,模型服務已成為機器學習系統的關鍵環節。本文將探討如何使用開源工具,如 Triton 和 KServe,構建高效且可擴充套件的模型服務平台。Triton 是一款高效能的模型服務引擎,支援多種深度學習框架,並提供動態批次處理和 GPU 最佳化等功能。然而,使用 Triton 需要考量其授權成本。KServe 則是一個 Kubernetes 原生的模型服務平台,提供標準化的推理協定和無伺服器佈署能力,簡化了在 Kubernetes 上佈署模型的流程。選擇適合的工具取決於具體需求和基礎設施。在整合模型服務工具時,通常需要考慮與現有系統的相容性,例如 API 介面和模型儲存方式。透過 sidecar 容器等技術,可以將新的模型服務工具整合到現有架構中,而無需大幅修改系統。模型釋出流程也至關重要,需要考慮模型版本控制、自動化佈署和效能評估等環節。

7.4 開源模型服務工具巡覽

模型儲存與轉換

在將模型佈署到Triton之前,需要將模型儲存為特定的格式。以PyTorch模型為例,首先需要將模型轉換為TorchScript格式。以下程式碼展示瞭如何儲存TorchScript模型:

traced_script_module.save("traced_torch_model.pt")

內容解密:

  • traced_script_module.save() 方法用於將已追蹤的PyTorch模型儲存到硬碟。
  • "traced_torch_model.pt" 是儲存模型的檔案名稱,.pt 是PyTorch模型的常見副檔名。
  • 這種格式的模型可以被Triton等服務工具載入並進行推理。

對於其他訓練框架的模型格式要求,可以參考Triton的GitHub倉函式庫(http://mng.bz/NmOn)中的說明。

模型服務流程

在Triton中進行模型服務主要涉及三個步驟:

  1. 將模型檔案複製到模型倉函式庫中。
  2. 呼叫管理API(POST v2/repository/models/${MODEL_NAME}/load)來註冊模型。
  3. 傳送推理請求(POST v2/models/${MODEL_NAME}/versions/${MODEL_VERSION})。

更多關於Triton管理API的資訊,可以參考Triton的HTTP/REST和gRPC協設定檔案(http://mng.bz/DZvR)。對於推理API,可以參考KServe社群標準推理協設定檔案(https://kserve.github.io/website/0.10/modelserving/data_plane/v2_protocol/)。

為何選擇Triton

撰寫本文時,我們認為Triton是最佳的模型服務方案,主要根據以下三點理由:

  1. 框架無關性:Triton支援多種訓練框架,提供了良好的後端框架,使得幾乎任何訓練框架建立的模型都能被執行。
  2. 高效能:Triton具備多種機制來提升服務效能,例如動態批次處理、GPU最佳化以及模型分析工具,從而提供了更好的模型服務效能,如服務吞吐量。
  3. 支援複雜場景:Triton支援諸如模型整合和音訊串流等高階模型服務使用案例。

注意事項

使用Triton時需要注意其授權和成本問題。雖然Triton採用BSD 3-Clause授權,可以免費修改和分發,但如果需要官方支援和除錯,可能需要購買NVIDIA AI-enterprise授權,這將帶來不菲的成本。

KServe及其他工具

除了Triton之外,還有多種開源的模型服務工具,如KServe(https://www.kubeflow.org/docs/external-add-ons/kserve/)、Seldon Core(https://www.seldon.io/solutions/open-source-projects/core)和BentoML(https://github.com/bentoml/BentoML)。這些工具各有其優勢,有些輕量且易於使用,如BentoML;有些則使得在Kubernetes上佈署模型變得簡單快捷,如Seldon Core和KServe。

KServe的優勢

KServe是一個由多家高科技公司合作開發的開源專案,旨在建立一個標準化的模型服務解決方案。它提供了一個抽象的模型服務介面,支援多種主流的機器學習框架。KServe的主要貢獻在於建立了一個標準化的推理協定,這使得我們可以使用一套統一的API來查詢不同服務工具上的模型。

KServe還原生支援Kubernetes上的無伺服器推理解決方案,利用Knative來處理網路路由、模型工作者自動擴縮減以及模型版本追蹤。透過簡單的組態,就可以在Kubernetes叢集上佈署模型並使用標準化的API進行查詢。

以下是一個KServe的範例組態:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: "torchserve"
spec:
  predictor:
    pytorch:
      storageUri: gs://kfserving-examples/models/torchserve/image_classifier

內容解密:

  • apiVersionkind 定義了KServe的資源型別和版本。
  • metadata.name 指定了InferenceService的名稱。
  • spec.predictor 部分定義了預測器的型別和組態,在此例中使用了PyTorch預測器。
  • storageUri 指定了模型檔案的位置。

KServe使用不同的服務工具來執行推理,如TensorFlow Serving和Triton,並透過簡單的Kubernetes CRD組態隱藏了背後的複雜性。雖然KServe的新版本(v2)仍在測試階段,但其標準化的推理協定和無伺服器佈署能力使其在眾多方案中脫穎而出。如果需要在Kubernetes上建立一個支援多種主流訓練框架的大型服務平台,KServe值得關注。

將服務工具整合到現有的服務系統中

在許多情況下,用新的服務後端完全取代現有的預測服務並不是一個可行的選擇。每個服務工具都有其對模型儲存、模型註冊和推斷請求格式的特定要求。這些要求有時會與現有的系統預測介面以及內部的模型後設資料和檔案系統相衝突。為了在不破壞業務的情況下引入新技術,我們通常採用整合的方法,而不是完全替換。

以Triton伺服器為例進行整合

這裡,我們以Triton伺服器為例,展示如何將一個服務工具整合到現有的預測服務中。在這個例子中,我們假設三件事情:首先,現有的預測服務執行在Kubernetes上;其次,現有的預測服務的Web推斷介面不允許改變;第三,有一個模型儲存系統,將模型檔案儲存在雲端儲存中,例如Amazon S3。圖7.11展示了這個過程。

圖7.11(A)展示了系統概覽。在現有的預測API後面增加了一系列Triton伺服器的Kubernetes Pod。透過Kubernetes的負載平衡器,一個預測請求可以落在任何Triton Pod上。我們還增加了一個所有Triton Pod都可以存取的分享卷;這個分享卷充當所有Triton例項的分享Triton模型倉函式庫。

圖7.11(B)展示了一個Triton伺服器Kubernetes Pod內部的結構。每個Triton Pod都有兩個Docker容器:一個Triton伺服器容器和一個sidecar容器。Triton伺服器容器是我們在7.4.3節中討論過的Triton推斷伺服器。模型預測發生在這個容器中,我們可以簡單地將這個容器視為一個黑盒。sidecar容器充當介面卡/代理,在將預測請求轉發給Triton容器之前準備Triton所需的一切。這個sidecar容器從雲端儲存下載模型到Triton本地模型倉函式庫(分享卷),呼叫Triton註冊模型,並將預測請求轉換為Triton API呼叫。

內容解密:

  1. 整合方法:透過在現有的預測服務後面新增Triton伺服器的Kubernetes Pod,並使用sidecar容器作為介面卡/代理來準備Triton所需的模型和請求格式,從而實作與現有系統的整合。
  2. 分享卷的作用:作為所有Triton Pod的分享模型倉函式庫,確保每個Pod都能存取相同的模型檔案。
  3. Sidecar容器的功能:下載模型、註冊模型到Triton,並轉換預測請求格式,以適應Triton API的要求。
  4. 優點:這種整合方法允許在不改變現有預測服務API和外部模型儲存系統的情況下,使用新的Triton後端,提高了系統的靈活性和可擴充套件性。

發布模型

發布模型是指將新訓練好的模型佈署到預測服務中,並將其暴露給使用者。自動化模型佈署和支援模型評估是構建生產環境中模型服務系統時需要解決的兩個主要問題。

模型的發布流程

  1. 註冊模型:資料科學家或訓練服務將新產生的模型(包括模型的檔案和後設資料)註冊到後設資料儲存系統中。
  2. 評估模型:資料科學家透過向預測服務傳送帶有特定模型版本的預測請求來測試新註冊模型的效能。
  3. 發布最佳模型:資料科學家在後設資料儲存系統中將效能最佳的模型版本設定為發布版本,客戶端應用程式將自動開始使用新的發布版本的模型。

內容解密:

  1. 後設資料儲存系統:用於管理由深度學習系統產生的工件(如模型)的後設資料,提供模型的查詢和管理功能。
  2. 自動化佈署:透過將訓練好的模型自動註冊到後設資料儲存系統,並由預測服務載入和使用,實作模型的自動化佈署。
  3. 多版本支援:預測服務支援載入任意版本的模型,使得評估和比較不同版本的模型成為可能。

將Triton伺服器例項整合到現有的服務系統中的架構圖

圖示說明:

此圖示展示瞭如何將一系列Triton伺服器例項整合到現有的服務系統中。其中,預測Web API透過Kubernetes負載平衡器將請求分發到不同的Triton伺服器Pod。每個Triton伺服器Pod包含一個Triton伺服器容器和一個sidecar容器,sidecar容器負責下載模型、註冊模型和轉換請求格式。所有Triton Pod分享一個用於存放模型檔案的分享卷。

模型的發布流程圖

圖示說明:

此圖示展示了模型的發布流程。首先,資料科學家將新訓練的模型註冊到後設資料儲存系統。然後,預測服務根據需要載入特定版本的模型。最後,將最佳效能的模型版本發布給客戶端應用程式使用。同時,資料科學家可以評估不同版本的模型的效能,以決定哪個版本最優。

7.5 釋出模型

在圖7.13中,我們可以看到後設資料儲存區(metadata store)有兩個部分:模型查詢表(model lookup table)和模型後設資料列表(model metadata list)。模型後設資料列表純粹用於儲存後設資料;所有模型後設資料物件都儲存在此列表中。模型查詢表用作快速搜尋的索引表。查詢表中的每個記錄都指向後設資料列表中的實際後設資料物件。

訓練服務可以在訓練完成後自動將模型註冊到後設資料儲存區。資料科學家也可以手動註冊模型,這通常發生在資料科學家希望佈署他們在本地建立的模型(而不使用深度學習系統)時。

當後設資料儲存區收到模型註冊請求時,首先,它為該模型建立一個後設資料物件。其次,它透過新增新的搜尋記錄來更新模型查詢表;該記錄使我們能夠透過使用模型名稱和版本來找到該模型後設資料物件。除了使用模型名稱和版本搜尋查詢表之外,後設資料儲存區還允許使用模型ID搜尋模型後設資料。

實際的模型檔案儲存在工件儲存區(artifact store)——一個雲端物件儲存區,例如Amazon S3。模型在工件儲存區中的儲存位置作為指標儲存在模型的後設資料物件中。

圖7.13顯示了模型查詢表中針對模型A的兩個搜尋記錄:版本1.0.0和1.1.0。每個搜尋記錄對映到不同的模型後設資料物件(分別為ID = 12345和ID = 12346)。透過這種儲存結構,我們可以使用模型名稱和模型版本找到任何模型後設資料;例如,我們可以透過搜尋“模型A”和版本“1.1.0”找到模型後設資料物件ID = 12346。

使用模型的標準名稱和版本來找到實際的後設資料和模型檔案,是預測服務能夠同時提供不同模型版本的基礎。讓我們看看如何在下一節中預測服務中使用後設資料儲存區。

7.5.2 即時載入任意版本的模型

為了決定在生產環境中使用哪個模型版本,我們希望在相同的環境中公平地評估每個模型版本的效能,並且使用相同的API輕鬆地進行評估。為此,我們可以呼叫預測服務來執行具有不同模型版本的預測請求。

在我們的提案中,預測服務在收到預測請求時,從後設資料儲存區即時載入模型。資料科學家可以透過在預測請求中定義模型名稱和版本,允許預測服務使用任何模型版本執行預測。圖7.14說明瞭這個過程。

圖7.14顯示了預測服務即時載入服務請求中指定的模型。當接收到預測請求時,路由層首先在後設資料儲存區中找到請求的模型,下載模型檔案,然後將請求傳遞給後端預測器。以下是執行時模型載入和服務過程的七個步驟的詳細說明:

  1. 使用者向預測服務傳送預測請求。在請求中,他們可以透過提供模型名稱和版本(/predict/{model_name}/{version})或模型ID(/predict/{model_id})來指定要使用的模型。
  2. 預測服務內的路由層搜尋後設資料儲存區並找到模型後設資料物件。
  3. 路由層然後將模型檔案下載到所有預測器都可以存取的分享磁碟。
  4. 透過檢查模型的後設資料,例如演算法型別,路由層將預測請求路由到正確的後端預測器。
  5. 預測器從分享磁碟載入模型。
  6. 預測器處理資料前處理,執行模型,執行後處理,並將結果傳回給路由層。
  7. 路由層將預測結果傳回給呼叫者。

7.5.3 透過更新預設模型版本釋出模型

在模型評估之後,模型釋出的最後一步是讓客戶在預測服務中使用新驗證的模型版本。我們希望模型的釋出過程對客戶來說是無感知的,因此客戶不會意識到底層模型版本的變化。

在上一節(7.5.2)的步驟1中,使用者可以使用/predict/{model_name}/{version} API請求任何指定的模型版本進行模型服務。此功能對於評估同一模型的多個版本至關重要,因此我們可以防止模型的效能迴歸。

但是在生產場景中,我們不希望客戶跟蹤模型版本和模型ID。或者,我們可以定義一些靜態版本字串作為變數,以代表新發布的模型,並讓客戶在預測請求中使用它們,而不是使用實際的模型版本。

例如,我們可以定義兩個特殊的靜態模型版本或標籤,例如STGPROD,分別代表預生產和生產環境。如果與PROD標籤相關聯的模型A的版本是1.0.0,則使用者可以呼叫/predict/model_A/PROD,並且預測服務將載入模型A和版本1.0.0來執行模型服務。當我們將新發布的模型版本升級到1.2.0——透過將PROD標籤與版本1.2.0相關聯——/predict/model_A/PROD請求將落在模型版本1.2.0上。

使用特殊的靜態版本/標籤字串,預測使用者不需要記住模型ID或版本;他們只需使用/predict/{model_name}/PROD傳送預測請求,以使用新發布的模型。在背後,我們(資料科學家或工程師)在後設資料儲存區的查詢表中維護這些特殊字串與實際版本之間的對映,因此預測服務知道要為/STG/PROD請求下載哪個模型版本。

在我們的提案中,我們將把特定的模型版本對映到靜態模型版本的操稱為**#### 內容解密:** 此段落主要講述如何透過更新預設的靜態版本來釋出新的已驗證可用的正式版模型的流程與原理。

  • 藉由定義兩個靜態字串作為變數,分別代表預生產以及生產環境
  • 藉由改變靜態字串所對應的對映關係,實作無感知地釋出新模型的目標
  • 在Metadata Store中維護靜態與真實版本的對映關係,以確保 Prediction Service 可以正確下載所需版本的 Model Files
  • 藉由這種方式,可以實作 Model Release 的自動化以及透明化
  • 藉由這種方式,可以實作 Model Serving 的靈活性以及可擴充套件性
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 開源模型服務工具與整合策略

package "Kubernetes Cluster" {
    package "Control Plane" {
        component [API Server] as api
        component [Controller Manager] as cm
        component [Scheduler] as sched
        database [etcd] as etcd
    }

    package "Worker Nodes" {
        component [Kubelet] as kubelet
        component [Kube-proxy] as proxy
        package "Pods" {
            component [Container 1] as c1
            component [Container 2] as c2
        }
    }
}

api --> etcd : 儲存狀態
api --> cm : 控制迴圈
api --> sched : 調度決策
api --> kubelet : 指令下達
kubelet --> c1
kubelet --> c2
proxy --> c1 : 網路代理
proxy --> c2

note right of api
  核心 API 入口
  所有操作經由此處
end note

@enduml

此圖示說明瞭資料科學家註冊新 Model 後,Metadata Store 更新 Model Lookup Table,Prediction Service 根據 STG 或 PROD 標籤下載對應 Model,並傳回 Prediction 結果給客戶應用程式的流程。

  • 資料科學家負責註冊新 Model 到 Metadata Store
  • Metadata Store 更新 Model Lookup Table 以維護靜態與真實版本的對映關係
  • Prediction Service 根據 STG 或 PROD 請求下載對應版本的 Model Files
  • 藉由這種流程,可以實作 Model Release 的自動化以及透明化