隨著機器學習模型日益複雜,佈署和推論環節的效能最佳化變得至關重要。如何有效地將模型佈署到生產環境,並在滿足延遲和吞吐量要求的同時控制成本,是機器學習工程師面臨的重要挑戰。本文將探討不同應用場景下的最佳化策略,並提供程式碼範例和技術解析,協助讀者更好地應對這些挑戰。考量因素包含硬體平台的選用、模型架構的調整、以及各種推論引擎和框架的特性,才能設計出高效能且經濟的機器學習系統。
模型佈署與推論最佳化:生產環境中的機器學習挑戰
在機器學習(ML)系統的生命週期中,將訓練好的模型佈署到生產環境並有效利用是至關重要的步驟。許多從事機器學習的開發者往往更關注模型的開發和訓練,而忽略了模型佈署和最佳化的重要性。然而,若模型無法有效地佈署和利用,那麼再先進的模型也無法發揮其價值。本章將探討模型佈署和推論過程中所面臨的挑戰,並介紹不同最佳化推論過程的方法。
推論流程的組成
推論流程是指將原始資料作為輸入並產生預測結果的一系列步驟,通常以順序或無環圖的形式存在。除了機器學習模型本身,推論流程還包括特徵計算、資料前處理、輸出後處理等步驟。這些步驟的具體內容會根據系統的需求而有所不同。例如,典型的電腦視覺流程通常從影像縮放和正規化開始,而語言處理流程則從分詞開始。
不同領域對推論最佳化的需求
不同領域對推論最佳化的需求各不相同。高頻交易公司或廣告技術公司需要極低延遲的系統,而行動應用和物聯網開發者則關注能耗效率,以提升使用者經驗。對於使用高負載後端的產品來說,保持系統負載而不增加基礎設施成本至關重要。在某些情況下,模型的預測並不是瓶頸,例如每週批次預測銷售資料並生成報告,此時預測時間的長短並不那麼重要。
本章重點
本章主要關注根據深度學習的系統,因為這些系統在佈署到生產環境前需要更多的工程努力。雖然本章的原理和技術同樣適用於其他型別的機器學習系統。
模型佈署與推論:挑戰
在進行系統設計時,首先需要定義需求。以下是幾個關鍵因素:
延遲
定義了系統對預測請求的回應速度。對於需要即時回應的應用,延遲通常以毫秒為單位進行衡量。
吞吐量
指系統在單位時間內能夠處理的任務或資料量。在機器學習領域,這意味著模型在單位時間內能夠產生的預測數量。
可擴充套件性
需要了解系統可能面臨的預測請求數量,以及這個數量的增減變化。負載模式通常具有季節性,並且可能根據行業的不同而有所不同。
目標平台
模型可以在僅使用CPU或GPU加速的伺服器、無伺服器環境(如AWS Lambda)、桌面、行動裝置或物聯網裝置上執行,甚至可以在瀏覽器中執行。每個平台都有其優勢和限制,需要深入瞭解這些因素後再進行系統設計。
成本
機器學習系統往往是公司中最耗費資源的解決方案,尤其是隨著生成式模型的流行,成本成為了一個前所未有的關鍵因素。瞭解基礎設施的成本及其隨負載增加的變化對於設計系統至關重要。
最佳化推論流程
針對上述挑戰,可以透過多種方法最佳化推論流程,包括但不限於選擇合適的硬體、使用混合裝置會話池、最佳化模型架構等,以滿足不同平台和業務場景的需求。
程式碼範例:使用混合裝置會話池最佳化GPU負載
import numpy as np
def process_request(request, gpu_available):
if gpu_available:
# 使用GPU處理請求
return process_on_gpu(request)
else:
# 使用CPU處理請求
return process_on_cpu(request)
def process_on_gpu(request):
# GPU處理邏輯
return np.random.rand(1)
def process_on_cpu(request):
# CPU處理邏輯
return np.random.rand(1)
# 模擬請求處理
requests = [1, 2, 3, 4, 5]
gpu_available = True
for request in requests:
result = process_request(request, gpu_available)
if result is not None:
print(f"Request {request} processed with result: {result}")
gpu_available = not gpu_available # 模擬GPU可用性切換
內容解密:
- 混合裝置會話池的概念:程式碼展示瞭如何根據GPU的可用性,將請求分配給GPU或CPU處理,以平衡負載。
process_request函式的作用:根據GPU的可用性,決定使用process_on_gpu還是process_on_cpu函式處理請求。process_on_gpu和process_on_cpu函式:這兩個函式模擬了在GPU和CPU上處理請求的邏輯,實際應用中需要替換為具體的處理邏輯。- 負載平衡的重要性:透過動態切換GPU的可用性,程式碼展示瞭如何根據資源狀況調整請求處理策略,以保持系統的穩定性和效率。
本章介紹了模型佈署和推論過程中面臨的挑戰,並提供了最佳化推論流程的方法。無論是對於深度學習系統還是其他型別的機器學習系統,這些原理和技術都是有價值的。透過仔細考慮延遲、吞吐量、可擴充套件性、目標平台和成本等因素,並採用適當的最佳化策略,可以有效地提升模型的生產效率和使用者經驗。
第15章:服務與推論最佳化
在現代人工智慧系統中,模型的服務與推論最佳化是至關重要的。這不僅關乎模型的效能,還涉及到成本、可靠性和安全性等多個方面。本章將探討在服務和推論過程中需要考慮的各種因素,以及如何在這些因素之間找到平衡。
15.1 服務和推論的最佳化考量
在設計和實施模型服務和推論系統時,需要考慮多個關鍵因素,包括成本、可靠性、彈性、安全性和隱私等。
成本
模型的服務成本取決於多個因素,例如硬體選擇、模型複雜度和請求數量。選擇合適的硬體供應商和最佳化模型可以顯著降低成本。然而,過於追求低成本可能會導致效能下降或可靠性問題。
可靠性
可靠性是另一個重要的考量因素。選擇廉價的硬體供應商可能會導致系統在高負載或硬體故障時出現問題。因此,投資於經過驗證的、可靠的解決方案對於確保系統穩定運作至關重要。
彈性
隨著技術的發展和需求的變化,系統需要具備足夠的彈性來適應新的模型、預處理或後處理步驟、新的功能和API等。這意味著系統應該易於修改和擴充套件,而不會影響現有的功能。
安全性和隱私
安全性和隱私是另一個重要的考量領域。根據目標平台的不同,安全需求可能會有所不同。例如,在內部後端系統中執行的模型可能不需要太多的額外安全措施,但如果是移動應用程式,則需要保護模型免受逆向工程的侵害。
15.2 取捨與模式
在最佳化模型服務和推論系統時,各個因素之間往往存在衝突,需要在它們之間找到平衡。
延遲與吞吐量之間的取捨
延遲和吞吐量是兩個常見的最佳化目標。改善其中一個可能會對另一個產生正面或負面的影響。實際生產系統通常會在給定的延遲預算下最佳化吞吐量。
例如,減少深度學習模型的區塊數量可能會同時改善延遲和吞吐量。然而,如果兩個模型具有相同的區塊數量和引數數量,但採用不同的架構,則延遲和吞吐量的表現可能會有所不同。平行性是影響延遲和吞吐量的內部因素之一。
此圖示說明瞭寬模型和深模型在平行計算和順序計算上的差異。
模型準確度與成本之間的取捨
機器學習工程師經常面臨模型準確度和成本之間的取捨。升級到更大的模型可能會提高準確度,但也會增加成本。選擇合適的模型大小可以在準確度和成本之間取得平衡。
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 機器學習模型佈署與推論最佳化
package "關鍵效能指標" {
component [延遲 Latency\n毫秒級回應] as latency
component [吞吐量\nThroughput] as throughput
component [可擴展性\nScalability] as scale
}
package "目標平台" {
component [伺服器\nCPU/GPU] as server
component [無伺服器\nAWS Lambda] as serverless
component [邊緣裝置\nIoT/Mobile] as edge
}
package "最佳化方法" {
component [批次處理\nBatching] as batch
component [快取機制\nCaching] as cache
component [模型路由\nRouting] as route
}
package "推論技術" {
component [ONNX\n跨平台格式] as onnx
component [混合裝置池\nGPU/CPU] as hybrid
component [模型量化\nQuantization] as quant
}
latency --> batch
throughput --> cache
scale --> route
server --> hybrid
serverless --> onnx
edge --> quant
batch --> hybrid
cache --> onnx
note right of latency : 高頻交易\n廣告技術需求
note bottom of hybrid : GPU可用時使用GPU\n否則回退CPU
@enduml
collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型
note right of feature
特徵工程包含:
- 特徵選擇
- 特徵轉換
- 降維處理
end note
note right of eval
評估指標:
- 準確率/召回率
- F1 Score
- AUC-ROC
end note
@enduml
此圖示展示了不同大小的模型在成本和準確度之間的關係。
研究彈性與生產級服務效能之間的取捨
在研究和生產之間取得平衡也很重要。一方面,將模型從實驗環境遷移到生產環境應該盡可能簡單。另一方面,研究團隊需要能夠在不影響生產系統的情況下進行實驗。
模型服務與推論最佳化中的取捨與模式
在最佳化機器學習模型的服務與推論過程中,我們面臨著多種取捨與技術模式的選擇。這些選擇不僅影響系統的效能,也對其可持續性產生深遠影響。本章將探討這些重要的技術考量。
取捨與模式的選擇
當我們決定最佳化模型時,首先需要考慮的是系統的運作環境。無論是選擇線上服務還是離線批處理,都需要對監控和整合測試進行適當的投資,以確保系統的穩定性和可靠性。
模式的選擇
一旦確定了最佳化的方向,就需要選擇合適的實作模式。以下是三種值得關注的模式:
批次處理(Batching)
批次處理是一種提高系統吞吐量的技術,需要在設計階段就予以考慮。動態批次處理是一種常見的做法,後端會暫時緩衝請求,然後對這些請求進行批次推論,以降低延遲並保持系統的回應性。例如,Triton Inference Server 和 Tensorflow Serving 等框架就實作了這種技術。
class DynamicBatcher:
def __init__(self, batch_size, timeout):
self.batch_size = batch_size
self.timeout = timeout
self.requests = []
def add_request(self, request):
self.requests.append(request)
if len(self.requests) >= self.batch_size:
self.process_batch()
def process_batch(self):
# 對self.requests進行批次處理
pass
內容解密:
上述程式碼展示了一個簡單的動態批次處理器。當請求數量達到設定的batch_size時,就會觸發process_batch方法進行批次處理。這種方式可以有效提高系統的吞吐量。
快取(Caching)
快取是一種終極的最佳化手段,其理念是避免重複計算相同的結果。簡單來說,就是將輸入資料與快取值儲存起來,避免重複運算。然而,在實際應用中,由於輸入資料的多樣性,快取的命中率可能會受到影響。
class InMemoryCache:
def __init__(self):
self.cache = {}
def get(self, key, or_else):
v = self.cache.get(key)
if v is None:
v = or_else()
self.cache[key] = v
return v
內容解密:
這段程式碼實作了一個簡單的記憶體快取。當查詢某個鍵值對應的結果時,如果結果已經存在於快取中,就直接傳回快取結果;否則,就計算結果並將其存入快取。這種方式可以有效減少重複計算。
模型路由(Routing between Models)
模型路由是一種新興的最佳化技術,尤其是在大語言模型(LLM)的應用中。透過將不同的請求路由到不同的模型,可以根據請求的複雜度選擇合適的模型,從而達到最佳化的效果。
工具與框架
推論過程的工程化程度很高,幸運的是,有許多工具和框架可供使用。本章重點介紹了一些流行的解決方案,以說明相關原理。
機器學習框架與工具選擇
在機器學習的開發與佈署過程中,選擇適當的框架和工具是至關重要的。一個常見的做法是將訓練和推論(inference)框架分開。使用如Pandas、scikit-learn和Keras等工具進行研究和原型開發是常見的,因為它們提供了靈活性和簡單性。然而,這些工具並不總是最適合推論任務,因為它們需要在靈活性和效能之間進行權衡。因此,將模型在一個框架中訓練後轉換為另一個框架進行推論是一種常見的做法。
訓練與推論框架的分離
將訓練框架與推論框架分離可以帶來多個好處。首先,如果需要更換訓練框架,這種分離可以確保推論流程不受影響。其次,這種分離使得模型的佈署更加靈活,可以根據不同的需求選擇最合適的推論框架。
統一框架與多框架策略
一些研究導向的框架,如Torch,正在縮小研究和生產之間的差距。Torch 2.0引入的編譯功能允許從相同的訓練程式碼生成最佳化的推論流程。因此,無論是使用統一的框架還是結合多個框架來滿足不同的需求,兩種方法都是可行的。
ONNX:機器學習的通用語言
為了在研究靈活性和生產環境中的高效能之間取得平衡,使用跨框架格式(如ONNX)是一個流行的選擇。許多訓練框架都支援將模型轉換為ONNX格式,而推論框架也往往支援ONNX格式或允許將ONNX模型轉換為其自身的格式。ONNX不僅是一種表示格式,也是一個生態系統,包括可用於推論的執行時(runtime)以及用於模型轉換和最佳化的工具集。
ONNX Runtime的優勢
ONNX Runtime非常適合多平台系統,因為它可以在幾乎任何平台上執行,並針對每個平台進行了特定的最佳化。根據我們的經驗,ONNX Runtime在靈活性和效能之間取得了良好的平衡。
其他推論引擎
對於伺服器CPU推論,Intel的OpenVINO是一個流行的選擇。對於CUDA推論,Nvidia的TensorRT被廣泛使用。這兩個引擎都針對其目標硬體進行了最佳化,也可用作ONNX Runtime的後端。此外,TVM和AITemplate也是值得一提的工具,它們能夠為多種硬體目標生成程式碼。
移動裝置上的推論
對於iOS裝置,CoreML是一個常用的引擎,它使用自己的格式。Android開發者通常選擇TensorFlow Lite,儘管由於Android的大碎片化,還有其他更多的選擇。
推論引擎的工作原理
當我們提到某個引擎執行在某個裝置上時,這不一定是完全準確的。例如,即使整體推論是在GPU上進行,一些操作可能仍會被轉發到CPU,因為這樣可以提高效率。GPU擅長於大規模平行計算,但一些與控制流或稀疏資料相關的操作則更適合由CPU處理。
模型最佳化
各種推論引擎通常都帶有最佳化器,可以用來提高模型的效能。例如,ONNX Runtime提供了一系列最佳化器,可以減少模型的尺寸或透過融合操作來減少運算次數。
LLM推論的特殊性
大語言模型(LLM)的推論是一個特定的主題。與傳統模型不同,LLM往往受限於龐大的記憶體佔用和自迴歸正規化,這使得平行化變得困難。目前,有多種推論引擎專門針對LLM,如VLLM、TGI和GGML。
總之,選擇適當的機器學習框架和工具需要考慮多個因素,包括研究靈活性、生產環境中的效能、以及模型的佈署需求。透過瞭解不同框架和工具的優勢和侷限性,可以更好地應對機器學習專案的挑戰。
15.3 工具與框架
在探討推理引擎與推理框架的差異之前,我們建議閱讀 https://vgel.me/posts/faster-inference/ 這篇部落格文章,以及一些真正深入的研究(例如,在撰寫本章節時,我們對 Song 等人提出的「PowerInfer:使用消費級 GPU 的快速大語言模型服務」印象深刻,https://arxiv.org/abs/2312.12456)。
雖然推理引擎和推理框架這兩個術語經常被互動使用,但它們並不完全相同。推理引擎是一種用於推理的執行時環境,而推理框架是一個更通用的術語,可能包括引擎、模型轉換和最佳化工具,以及其他有助於服務各個方面的元件,如批處理、版本控制、模型註冊、記錄等。例如,ONNX Runtime 是一種推理引擎,而 TorchServe(https://pytorch.org/serve/)則是一種推理框架。
對於是否需要一個功能齊全的框架或只是在推理引擎上新增一個簡單的包裝器這個問題,沒有一個統一的答案。根據我們的經驗,一旦你的需求達到一定的確定性,並且你傾向於使用更先進的機器,同時有足夠的人力資源來維護它,那麼框架就是正確的選擇。另一方面,一旦你處於初創公司階段,需要以某種方式交付系統,但知道你的需求只會在接下來的幾個月內被明確化,那麼選擇更精簡的方式是有意義的,即使用推理引擎和一些通訊層(例如,網頁框架)的簡單組合來佈署模型,並將更可靠的解決方案推遲到下一個版本。
15.3.2 無伺服器推理
無伺服器推理是一種從傳統的根據伺服器的模型中脫穎而出的新興方法。由 AWS Lambda 普及的這種無伺服器正規化現在已在多家主要雲端供應商(如 Google Cloud Functions、Azure Functions 和 Cloudflare Workers)以及像 Banana.dev 和 Replicate 這樣的初創公司中得到體現。截至目前,主要供應商主要提供 CPU 推理,GPU 能力有限,儘管隨著初創公司繼續推動該領域的邊界,這種情況可能會改變。
無伺服器推理的優缺點
無伺服器推理通常具有嚴格的正面或負面態度,因為它具有顯著的優缺點。讓我們來強調一些:
無需管理基礎設施或為閒置資源付費:你只需為你使用的資源付費。雖然無伺服器倡導者強調這一點,但實際情況是,可能仍需要一定程度的基礎設施管理,儘管複雜度降低了。
易於擴充套件,尤其是在間歇性負載下:然而,它並不是一個通用的解決方案。雲端供應商可能會對並發請求施加限制,阻止無限且快速的擴充套件。此外,「冷啟動」問題(大型模型初始化需要幾秒鐘)是低延遲應用程式的一個關注點。一些無伺服器供應商,如 Runpod,提供更多的控制權,允許你設定工作者的最小和最大數量,並自定義擴充套件規則。
低負載下的成本效益:然而,對於中等程度但一致且可預測的工作負載,專用機器可能比無伺服器解決方案更具成本效益。價格和延遲的組合也可能令人困惑。例如,如果一個模型在預熱時需要 100 毫秒來處理,而在冷啟動時需要 5,000 毫秒,且價格根據處理時間,那麼冷啟動請求的成本將是 50 倍。最佳化這種場景並不總是直接了當的。
難以在本地測試:開發基礎設施的整體複雜度往往會增加,即使它可能更便宜,如前所述。這不僅僅是「沒有網路連線就無法測試」。一旦無伺服器推理深入我們的系統,它可能會帶來額外的問題(例如,需要為微小的變更重新佈署測試工件,確保測試環境具有適當的許可權等)。
一些無伺服器供應商,如 Replicate,提供廣泛的預訓練基礎模型,可以直接使用。這對於開始新專案,尤其是原型設計或研究目的,特別有利。
我們觀察到無伺服器推理在小型寵物專案和高負載生產環境中都有成功和失敗的案例。毫無疑問,它是一個可行的選擇,但在完全採用之前,仔細考慮和進行徹底的成本效益分析至關重要。我們遵循的基本規則如下:在自動擴充套件是一個顯著優勢(例如,請求數量變化很大),且模型本身不是過大(儘管即使是大語言模型有時也可以佈署在僅 CPU 的無伺服器環境中;https://mng.bz/pxnz)的情況下,考慮使用無伺服器推理。當你對未來的負載不確定時,考慮無伺服器推理也是一個好主意:它可以在一定程度上適應多種負載模式,尤其是在專案開始時。
程式碼例項與解析
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# 載入預訓練模型和分詞器
model_name = "gpt2"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 定義一個簡單的推理函式
def inference(input_text):
inputs = tokenizer(input_text, return_tensors="pt")
outputs = model.generate(**inputs)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
# 測試推理函式
input_text = "Hello, how are you?"
output_text = inference(input_text)
print(output_text)
#### 內容解密:
1. **載入預訓練模型和分詞器**:我們使用 `transformers` 函式庫中的 `AutoModelForCausalLM` 和 `AutoTokenizer` 分別載入預訓練的 GPT-2 模型和對應的分詞器。
2. **定義推理函式**:`inference` 函式接受輸入文字,使用分詞器進行編碼,將編碼後的輸入傳遞給模型進行生成式推理,然後將生成的輸出解碼迴文字。
3. **測試推理函式**:我們提供一個簡單的輸入文字 `"Hello, how are you?"` 來測試 `inference` 函式,並列印生成的輸出文字。