部署一個SOTA模型,讓它服務數百萬用戶,處理TB級別的數據,并且7x24小時可靠運行是件非常有挑戰性的工作。我們將探討構建一個能夠創建LLM、多模態模型以及各種其他AI產品的大規模AI系統所需的每個開發階段。每個開發階段如何相互關聯,以及它們各自的職責。
第一階段:AI 的系統與硬件
構建大規模 AI 系統的第一步是選擇正確的硬件。它影響你的模型運行速度、花費多少錢以及所有設備使用的能量。
在本節中,我們將討論可用的不同硬件系統,以及如何使它們具有成本和能源效益。
AI 的計算硬件
用于訓練或其他 AI 任務的三種最常見的硬件類型是:
AI 硬件可用性
- CPU(中央處理單元) 它們擅長處理許多不同的任務,但它們的核心不多,所以對于需要大量并行處理的深度學習或大型 AI 作業來說可能會比較慢。
- GPU(圖形處理單元) 最初是為處理視頻和圖形而構建的,但現在它們是 AI 的寵兒。因為它們比 CPU 擁有更多的核心,這意味著它們可以同時處理很多事情,非常適合訓練和運行 AI 模型。
- TPU(張量處理單元) 是谷歌專門為深度學習制造的特殊芯片。它們速度非常快,效率超高,并且使用的能源更少。這使它們非常適合大型、復雜的 AI 任務。
您可以在這里了解更多:https://cloud.google.com/tpu
但最近,由于對 AI 的需求不斷增長,也引入了一些新型硬件。
現代硬件
- 一個很好的例子是 FPGA(現場可編程門陣列)。這些芯片很特別,因為它們可以被重新編程以適應不同的 AI 任務。它們為您提供了根據模型需求微調性能的靈活性,這在快速變化的 AI 項目中非常有用。
- 還有 ASIC(專用集成電路)。這些不像 CPU 甚至 FPGA 那樣是通用的。相反,它們是為一件事而設計的:盡可能快且高效地運行 AI 模型。因為它們是為特定工作(如驅動神經網絡)而構建的,所以它們可以節省能源并且運行速度非常快。
您可以閱讀這個故事了解更多細節:
在選擇硬件時,直接選擇 GPU 并不總是正確的方法,因為我們常常假設它們會自動提高性能,無論是數據預處理、微調還是 LLM 推理。
然而,性能很大程度上取決于…
模型架構 + 基礎設施選擇
- 在 AI 架構方面,一個有用的技術是模型量化,許多現代開源模型 API 提供商(如 Together AI 或 Nebius AI)已經在使用了。這意味著減少你的 AI 模型在進行計算時使用的細節量,比如使用更小的數字(例如,8位而不是32位)。
- 在基礎設施方面,云服務和虛擬化通常是最佳解決方案。與其購買昂貴的硬件,不如從 AWS、Google Cloud 或 Azure 等提供商那里租用強大的機器。這為您提供了根據項目需求擴展或縮減的靈活性,從而節省資金并避免浪費。
看一下谷歌的比較圖表,它顯示了不同模型架構在各種 GPU 上的表現。
性能比較 — 谷歌比較文章)
谷歌在 MLPerf 3.1 基準測試(主要用于測試系統處理輸入的速度)上進行了測試。
- 對于棘手的 AI 作業,使用強大 H100 GPU 的 A3 VM 比舊的 A2 VM 快 1.7 到 3.9 倍。
- 如果您希望在獲得可靠 AI 性能的同時節省資金,使用 L4 GPU 的 G2 VM 是一個不錯的選擇。
- 測試表明,L4 GPU 每花費一美元所能提供的性能比類似云服務高出 1.8 倍。
像 Bending Spoons 這樣的公司已經在使用 G2 VM 來高效地為用戶帶來新的 AI 功能。
AI 的分布式系統
一旦根據您的需求選擇了優化的硬件和模型架構,我們就會進入下一階段,這涉及到您如何規劃 AI 的分布式系統。
分布式系統的主要原則是…
將一個大任務分成更小的部分,讓多臺計算機同時處理它們。
在 AI 中,這通過分擔工作負載來加速數據處理和模型訓練。
因此,要創建一個分布式系統,我們需要牢記一些重要因素。讓我們先將其可視化,然后理解其流程。
分布式 AI 系統(來自 Sina Torfi — 開源)
在將分布式邏輯應用于我們的 AI 系統時,我們需要考慮多種因素。讓我們看看流程是怎樣的:
- 首先,我們需要了解規模。 我們處理的是數百、數千還是數百萬個數據點?盡早了解這一點有助于我們規劃系統以實現平穩增長。
- 接下來,我們選擇合適的工具。 根據項目的規模和類型,我們需要正確的處理能力、內存和通信方法的組合。云平臺使管理變得更加容易。
- 然后,我們確保一切協同工作。 系統的不同部分可能需要并行運行或在不同的機器上運行。我們的目標是避免減速并保持系統平穩運行。
- 之后,我們保持靈活性。 我們自動化資源調整,而不是手動調整。像 Kubernetes 這樣的工具有助于系統根據負載變化自動調整。
- 我們還需要監控性能。 密切關注系統有助于我們及早發現問題,無論是數據分布不均還是網絡瓶頸。
- 最后,我們確保一切保持同步。 隨著系統的擴展,數據和模型在所有部分保持一致至關重要。
優化網絡
一旦您決定了 AI 系統的分布式部分,您需要確保所有組件都正確連接。
它們必須能夠順暢地相互通信,沒有任何障礙
如果分布式組件無法有效通信,可能會破壞您的訓練或生產代碼。
讓我們看看如何讓對話流暢而不中斷:
分布式系統的網絡優化
讓我們分解一下:
- 首先,我們尋找潛在的減速點。 延遲、有限容量或數據丟失會嚴重影響性能,因此盡早識別這些風險非常重要。
- 然后,我們減少延遲。 為了加快速度,我們使用更快的連接,將機器放在一起,甚至將一些處理轉移到邊緣。
- 接下來,我們增加帶寬。 狹窄的網絡路徑會導致交通擁堵。我們通過壓縮數據、優先處理重要信息或升級網絡來解決這個問題。
- 之后,我們選擇正確的通信方法。 一些協議比其他協議更能處理大負載。選擇正確的協議可確保您的系統運行快速高效。
- 我們還為未來的增長做計劃。 隨著系統的擴展,網絡必須跟上。使用可以根據需要增長的靈活設置是關鍵。
- 最后,我們監控網絡。 定期檢查有助于我們及早發現問題。監控工具可以在問題導致系統減速之前向我們發出警報。
AI 的存儲解決方案
因此,在您決定了用于訓練或推理的硬件及其背后的分布式邏輯之后,您需要的下一件事是存儲,以保存您訓練好的模型以及用戶與您的 AI 模型對話產生的數據。
我們存儲數據的方式必須既滿足當前需求,又能為未來的更多數據做好準備
AI 存儲需求的增長(來自 Market US)
我們有三種類型的數據存儲系統:
數據存儲選項
- 對象存儲最適合大數據。 在這里,您可以不斷添加文件而無需擔心結構。當您的數據來自許多來源并需要稍后整合時,它是完美的。
- 文件系統更適合小型、有組織的設置。 它們有點像您計算機上的文件夾。它們有助于保持整潔,并且在您的數據有限且結構良好時是理想的選擇。
而第三種類型是數據庫,在您的數據具有結構時很有用。以下是如何選擇正確的類型:
- SQL 數據庫非常適合有組織、相互關聯的數據。 當您的數據具有明確的關系時(例如用戶、訂單和產品),請使用它們。它們非常適合需要準確性和一致性的復雜任務。
- NoSQL 數據庫適用于靈活或不斷變化的數據。 如果您的數據不能整齊地放入表格或增長迅速,像 MongoDB 或 Cassandra 這樣的 NoSQL 選項提供了您所需的自由度和可擴展性。
盡管工具不是唯一重要的東西,但如何使用它們也很重要:
- 數據湖以原始形式保存所有內容。 它是一個巨大的容器,用于存放各種數據,您可以稍后進行排序和處理。
- 數據倉庫存儲干凈、可隨時使用的數據。 這就像一個組織良好的圖書館,您可以快速找到所需的確切內容。
- 數據版本控制跟蹤更改。 在更新模型或處理隨時間變化的數據時,這很重要。它有助于保持組織性并防止錯誤。
- 混合存儲結合了速度和成本節約。 您使用快速存儲來存放經常需要的數據,使用更便宜的存儲來存放其余數據。這樣,您可以在必要時節省資金并仍然獲得快速訪問。
快速的數據訪問是 AI 性能的關鍵。
使用像 Redis 這樣的內存存儲進行快速檢索,并應用數據分片來分散負載并防止減速。
在某個時候,您需要決定哪種存儲設置效果最好:云、本地或兩者兼有。
云 vs 本地
- 混合存儲為您提供靈活性。 您可以將敏感數據保留在自己的服務器上,同時使用云來處理其他所有內容。這有助于平衡安全性和可擴展性。
- 多云策略提供更多選擇。 通過使用多個云提供商,您可以避免被鎖定在一家。這就像有不同的菜單可供選擇,具體取決于您的需求。
第二階段:高級模型訓練技術
到目前為止,我們已經介紹了硬件、存儲以及如何充分利用它們。現在是時候看看訓練技術是如何工作的,以及我們如何優化它們。
優化神經網絡訓練的策略
AI 模型通常建立在神經網絡之上,雖然許多模型從基本的梯度下降開始,但在實際場景中,有更高級的選項表現更好。
優化神經網絡
Adam 優化是一個明智的選擇。 它結合了 AdaGrad 和 RMSprop 的優點。它能很好地處理噪聲數據和稀疏梯度,使其成為流行的默認選擇。
# 使用 Adam 優化器,學習率為 0.001
optimizer = optim.Adam(model.parameters(), lr=0.001)
RMSprop 有助于提高學習穩定性。 它根據最近的梯度行為調整學習率,并且對于非平穩問題效果很好。
# 使用 RMSprop 優化器,學習率為 0.001,alpha(平滑常數)為 0.99
optimizer = optim.RMSprop(model.parameters(), lr=0.001, alpha=0.99)
Adagrad 能適應你的數據。 它為每個參數更改學習率,這對于稀疏數據非常有用,但可能導致學習率隨著時間的推移縮得太小。
# 使用 Adagrad 優化器,學習率為 0.01
optimizer = optim.Adagrad(model.parameters(), lr=0.01)
讓我們用一個簡單的表格來概覽我們有哪些優化器以及它們各自的適用場景。
因此,這種比較可以幫助機器學習工程師決定選擇哪種優化器。
我們可以放心地從 Adam 開始。雖然優化器之間存在差異,但從實用的角度出發并獲得一些初步見解是很重要的。
大規模訓練的框架和工具
接下來是正則化技術,這對于防止過擬合和確保模型能夠很好地泛化到新數據至關重要。以下是一些幫助您的模型很好地泛化到新數據的常用方法。
帶有權重衰減的 L2 正則化通過抑制大的權重來幫助保持模型更簡單。
# 使用 Adam 優化器,學習率為 0.001,權重衰減(L2懲罰)為 1e-5
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)
模型中的 Dropout 層在訓練期間隨機丟棄神經元,使模型不太可能過擬合。
import torch.nn as nnclass MyModel(nn.Module):def __init__(self):super(MyModel, self).__init__()# 第一個線性層:輸入大小 784(例如 28x28 圖像)到 256 個神經元self.layer1 = nn.Linear(784, 256)# Dropout 層,概率為 50%,以減少過擬合self.dropout = nn.Dropout(0.5)# 第二個線性層:256 到 10 個輸出類別(例如數字 0-9)self.layer2 = nn.Linear(256, 10)def forward(self, x):# 應用第一個線性變換x = self.layer1(x)# 應用 dropout 進行正則化x = self.dropout(x)# 應用第二個線性變換以產生輸出 logitsx = self.layer2(x)return x
基于驗證損失的早停法。 如果驗證損失停止改善,就沒有必要繼續訓練了。
best_loss = float('inf') # 將最佳損失初始化為無窮大
patience = 10 # 如果沒有改善,則在停止前等待的 epoch 數
trigger_times = 0 # 記錄沒有改善的 epoch 次數for epoch in range(max_epochs):val_loss = validate(model, val_loader) # 在驗證集上評估if val_loss < best_loss:best_loss = val_loss # 更新最佳損失trigger_times = 0 # 如果有改善,則重置計數器else:trigger_times += 1 # 沒有改善,增加計數器if trigger_times >= patience:print('早停') # 如果連續 'patience' 個 epoch 沒有改善,則停止訓練break
處理非常大的模型會帶來新的挑戰。以下是一些使其易于管理的方法。
模型并行將模型分布到多個 GPU 上。 模型的不同部分在不同的設備上處理。
# 定義一個順序塊并將其移動到 GPU 0
self.seq1 = nn.Sequential(# 層放在這里,例如 nn.Linear(...), nn.ReLU() 等
).to('cuda:0')# 定義另一個順序塊并將其移動到 GPU 1
self.seq2 = nn.Sequential(# 層放在這里
).to('cuda:1')# 定義一個全連接(或其他)層并將其移動到 GPU 1
self.fc = nn.Linear(...).to('cuda:1')
數據并行將數據分布到多個 GPU 上。 PyTorch DataParallel
自動管理此過程。
# 使用 DataParallel 包裝模型
model = DataParallel(MyModel())
# 將模型移動到 CUDA 設備(默認是所有可用 GPU)
model.to('cuda')
梯度累積允許使用更大的批次。 當內存有限時,它通過在更新前累積梯度來提供幫助。
# 在開始累積之前重置梯度
optimizer.zero_grad()for i, (inputs, labels) in enumerate(training_set):# 前向傳播outputs = model(inputs)# 計算損失loss = loss_function(outputs, labels)# 反向傳播(累積梯度)loss.backward()# 每 'accumulation_steps' 次迭代執行一次優化器步驟if (i + 1) % accumulation_steps == 0:optimizer.step() # 更新模型參數optimizer.zero_grad() # 為下一次累積重置梯度
聯邦學習將數據保留在本地設備上。 模型在設備上分別訓練,只共享更新。
for round in range(num_rounds):model_updates = [] # 用于收集所有設備權重更新的列表# 每個設備在其本地數據上進行訓練for device in devices:updated_model = train_on_device(model, device.data) # 本地訓練model_updates.append(updated_model.get_weights()) # 收集權重# 聚合更新(例如,取平均值)并更新全局模型model.set_weights(aggregate(model_updates))
為了在不損失太多性能的情況下使大型模型更高效,知識蒸餾是一種很好的方法。
使用大型教師模型訓練小型學生模型。 這有助于減小模型大小,同時保持良好的準確性。
import torch.nn.functional as Fdef knowledge_distillation_loss(outputs, labels, teacher_outputs, temp=2.0, alpha=0.5):# 硬損失:學生預測和真實標簽之間的標準交叉熵hard_loss = F.cross_entropy(outputs, labels)# 軟損失:軟化的學生預測和教師預測之間的 KL 散度# 應用溫度來軟化概率分布soft_loss = F.kl_div(F.log_softmax(outputs / temp, dim=1), # 學生 logits(軟化)F.softmax(teacher_outputs / temp, dim=1), # 教師 logits(軟化)reduction='batchmean' # 在批次上取平均)# 最終損失:硬損失和軟損失的加權和# 按照原始 KD 論文的建議,將軟損失乘以 temp^2return alpha * hard_loss + (1 - alpha) * soft_loss * (temp ** 2)
通過結合正確的優化器、正則化方法和訓練策略,我們可以構建既強大又高效的模型,即使在大規模下也是如此。
讓我們用一個比較表來更清晰地理解這一點。
使用 TensorFlow 和 PyTorch 進行擴展
框架在規模化處理 AI 時也扮演著重要角色。以下是一些流行的選項:
最重要的框架
- TensorFlow 提供 TensorFlow Distributed Strategies 來幫助在 GPU 和 TPU 上高效擴展訓練。
- PyTorch 以 PyTorch Distributed 聞名,支持跨多個 GPU 和多臺機器進行擴展。
- Horovod 與 TensorFlow、PyTorch 和 Keras 配合使用,以提高在 GPU 和 CPU 上的可擴展性。
- Kubernetes 有助于在規模化運行時平穩部署和管理 AI 工作負載。
- CUDA 和 cuDNN 加速 GPU 計算和深度學習性能。
- NeMo 專注于構建語音和自然語言處理模型。
模型擴展和高效處理
模型擴展是處理大數據集和復雜任務的關鍵。讓我們探索一些簡單的方法來并行化模型和數據,智能地處理批次,并應對訓練挑戰。
模型并行, 當模型對于單個 GPU 來說太大時,我們可以將模型分布到多個設備上。您可以按層(垂直)或層的部分(水平)進行劃分。目標是減少設備之間的數據移動。
import torch
import torch.nn as nn# 定義一個簡單模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.layer1 = nn.Linear(10, 20)self.relu = nn.ReLU()self.layer2 = nn.Linear(20, 10)self.layer3 = nn.Linear(10, 5)def forward(self, x):# 手動按設備拆分的前向傳播x = self.layer1(x)x = self.relu(x)# 在繼續之前將張量移動到第二個設備x = x.to(device2)x = self.layer2(x)x = self.relu(x)x = self.layer3(x)return x# 實例化模型
model = SimpleModel()# 定義設備
device1 = torch.device('cuda:0')
device2 = torch.device('cuda:1')# 將模型部分移動到各自的設備
model.layer1.to(device1)
model.relu.to(device1) # 可選,因為 ReLU 是無狀態且輕量級的
model.layer2.to(device2)
model.layer3.to(device2)# device1 上的示例輸入張量
x = torch.randn(1, 10).to(device1)# 前向傳播(在模型的 forward 方法內部處理)
output = model(x)# output 現在在 device2 上
我們可以使用像 NCCL 這樣的快速通信庫來減少移動數據時的延遲,并使用 torch.cuda.synchronize()
來確保設備按順序完成任務。
import torch
import torch.distributed as dist############ NCL ############
def init_process(rank, size, backend='nccl'):# 初始化分布式進程組dist.init_process_group(backend, rank=rank, world_size=size)world_size = 4
for i in range(world_size):init_process(rank=i, size=world_size, backend='nccl') # 使用 NCCL 后端初始化進程############ 同步 ############
def synchronize_devices(devices):# 同步指定的 CUDA 設備for device in devices:if 'cuda' in str(device):torch.cuda.synchronize(device)device1 = torch.device('cuda:0')
device2 = torch.device('cuda:2')
synchronize_devices([device1, device2]) # 同步 device1 和 device2
數據并行, 我們可以在多個設備上運行相同的模型處理不同的數據塊。當模型適合單個 GPU,但我們希望并行處理更多數據時,這很有用。
import torch
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader, Dataset, DistributedSampler# 虛擬數據集
class CustomDataset(Dataset):def __init__(self, data):self.data = datadef __len__(self):return len(self.data)def __getitem__(self, idx):return self.data[idx]# 初始化分布式訓練
def setup(rank, world_size):# 使用 NCCL 后端初始化進程組dist.init_process_group(backend='nccl', rank=rank, world_size=world_size)# 設置當前 CUDA 設備torch.cuda.set_device(rank)# 創建帶有分布式采樣器的數據加載器
def get_dataloader(dataset, batch_size, rank, world_size):# 創建分布式采樣器sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank)# 返回使用此采樣器的數據加載器return DataLoader(dataset, batch_size=batch_size, sampler=sampler)# 示例用法
rank = 0
world_size = 2
setup(rank, world_size) # 設置分布式環境dataset = CustomDataset(torch.arange(1000)) # 創建數據集
dataloader = get_dataloader(dataset, batch_size=32, rank=rank, world_size=world_size) # 獲取數據加載器model = SimpleModel().to(rank) # 將模型移動到指定設備
model = DDP(model, device_ids=[rank]) # 使用 DistributedDataParallel 包裝模型
在反向傳播之后,DDP 會在設備之間同步梯度,因此模型權重保持一致。
我們還可以通過梯度壓縮來減少通信負載。這是一個使用 8 位量化的簡單版本:
def quantize_gradients(model, bits=8):# 量化梯度到指定位數q_level = 2**bits - 1 # 量化級別數 (例如 8位 是 255)for param in model.parameters():if param.grad is not None:grad = param.grad.data # 訪問梯度張量# 計算最小值和最大值用于歸一化min_val, max_val = grad.min(), grad.max()# 歸一化到 [0, 1] 并縮放到 [0, q_level]grad_norm = (grad - min_val) / (max_val - min_val + 1e-8) * q_level# 量化:四舍五入到最近的級別grad_quant = torch.round(grad_norm)# 反量化:映射回原始尺度grad_dequant = grad_quant / q_level * (max_val - min_val) + min_val# 用量化-反量化后的版本替換原始梯度param.grad.data = grad_dequant
高效批處理, 我們可以通過調整批次處理方式來提高速度和內存使用率。
- 混合精度訓練 使用半精度(float16)進行更快的計算:
from torch.cuda.amp import GradScaler, autocastscaler = GradScaler() # 處理縮放以防止 float16 梯度下溢for data, target in dataloader:optimizer.zero_grad() # 清除之前的梯度with autocast(): # 啟用混合精度 — 在安全的地方使用 float16,否則使用 float32output = model(data) # 前向傳播(一些操作在 float16 中)loss = loss_fn(output, target) # 計算損失(仍然在 float32 中)scaler.scale(loss).backward() # 縮放損失以避免梯度下溢,然后反向傳播scaler.step(optimizer) # 如果沒有溢出,則取消縮放梯度并調用 optimizer.step()scaler.update() # 調整下一次迭代的縮放比例
- 梯度累積 在 GPU 無法處理大批次時有所幫助:
optimizer.zero_grad() # 清零梯度
accum_steps = 4 # 梯度累積步數for i, (data, target) in enumerate(dataloader):output = model(data) # 前向傳播# 縮放損失以在累積步驟中取平均loss = loss_fn(output, target) / accum_stepsloss.backward() # 反向傳播,累積梯度# 每 accum_steps 個批次更新一次權重并重置梯度if (i + 1) % accum_steps == 0:optimizer.step()optimizer.zero_grad()# 處理剩余的梯度,如果總批次數不能被 accum_steps 整除
if (i + 1) % accum_steps != 0:optimizer.step()optimizer.zero_grad()
讓我們了解同步和異步訓練之間的基本區別:
同步訓練, 所有工作節點在更新權重之前等待交換梯度。確保模型一致,但最慢的工作節點會拖慢所有人。
- 梯度平均
- 自適應批次大小
- 預測性等待時間調度
異步訓練 工作節點無需等待即可更新權重。加快了速度,但梯度可能是過時的。
- 使用過時梯度校正
- 動態調整學習率
- 維護模型版本以跟蹤更新
那么,到目前為止我們學到了什么,讓我們在一個表格中總結一下:
(注意:原文在此處暗示有一個總結表格,但未提供。因此翻譯中省略該表格。)
第三階段:高級模型推理技術
當我們部署 ML 模型并有數百萬人使用它們時,絕對需要一種高效的推理方法,使其易于所有用戶訪問。
我們經常遇到資源不像我們希望的那樣容易獲得的情況。在本節中,我們將探討各種可以幫助我們使推理更加優化和有效的技術和策略。
大規模高效推理
模型量化 通過降低其數字的精度(例如從 32 位浮點數到 8 位整數)來幫助縮小模型并加快推理速度。這意味著更小的模型和更快的計算,但要注意可能會有一些準確性下降。
主要有兩種類型:
量化類型
- 靜態量化:在運行模型之前將權重轉換為低精度。
- 動態量化:在推理期間動態轉換權重和激活值,平衡速度和靈活性。
以下是如何使用 PyTorch 對 ResNet18 模型進行動態量化:
# ResNet18 動態模型量化示例
import torch
from torchvision.models import resnet18# 加載預訓練的 ResNet18 模型
model = resnet18(pretrained=True)
model.eval() # 設置為評估模式進行推理# 對線性層應用動態量化,以實現更快的推理和更小的模型尺寸
quantized_model = torch.quantization.quantize_dynamic(model, {torch.nn.Linear, torch.nn.Conv2d}, dtype=torch.qint8 # 指定對線性和卷積層量化,目標類型為 int8
)# 打印量化后的模型架構
print(quantized_model)
有兩種最常用的訓練技術在節省磁盤空間、內存以及最重要的成本方面發揮著重要作用。
- 訓練后量化(Post-Training Quantization, PTQ): 在訓練之后量化模型。這種方法很快,但可能導致更多的準確性損失,因為模型在訓練時不知道它將使用較低的精度。
- 量化感知訓練(Quantization-Aware Training, QAT): 模型在訓練期間就考慮到量化進行學習,因此它能夠適應,并且通常在量化后保持更好的準確性。
以下是如何使用 PyTorch 對 ResNet18 模型進行 QAT:
import torch
from torchvision.models import resnet18
import torch.quantizationmodel = resnet18(pretrained=True)
model.train() # 設置為訓練模式# 融合 Conv、BatchNorm 和 ReLU 層以獲得更好的量化效果
model = torch.quantization.fuse_modules(model, [['conv1', 'bn1', 'relu']]) # 融合第一個卷積塊# 設置 QAT 配置
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm') # 使用 fbgemm 后端的默認 QAT 配置# 準備模型進行 QAT(原地修改)
torch.quantization.prepare_qat(model, inplace=True)# ... 在此處添加您的訓練循環以微調模型 ...# 訓練后轉換為量化版本
torch.quantization.convert(model, inplace=True) # 將模型轉換為量化版本print(model) # 打印最終的量化模型
模型剪枝 通過移除不太重要的部分來幫助縮小模型——可以通過將單個權重置零或剪掉整個神經元或通道來實現。
- 非結構化剪枝: 將單個權重置零(模型中存在大量微小的“空洞”)。它可以使模型變得稀疏,但通常需要特殊的硬件/軟件才能獲得速度優勢。
- 結構化剪枝: 移除整個通道或濾波器,這在常規硬件上更容易加速,但通常需要一些重新訓練。
您也可以在訓練期間動態地進行剪枝。
讓我們看一個非結構化和結構化剪枝的例子。
# 非結構化模型剪枝示例
import torch
import torch.nn.utils.prune as prune
import torch.nn as nn# 定義一個簡單的順序模型
model = nn.Sequential(nn.Linear(10, 100),nn.ReLU(),nn.Linear(100, 2)
)# 指定要剪枝的參數:第一個和最后一個線性層的權重
parameters_to_prune = ((model[0], 'weight'),(model[2], 'weight')
)# 應用全局非結構化剪枝:
# 在所有指定參數中,剪枝掉 L1 范數最小的 20% 的權重
prune.global_unstructured(parameters_to_prune,pruning_method=prune.L1Unstructured, # 使用 L1 非結構化方法amount=0.2, # 剪枝比例為 20%
)print(model) # 打印模型以查看附加的剪枝掩碼# 結構化模型剪枝示例
import torch
import torch.nn.utils.prune as prune
import torch.nn as nn# 定義相同的模型
model = nn.Sequential(nn.Linear(10, 100),nn.ReLU(),nn.Linear(100, 2)
)# 對第一個線性層應用結構化剪枝:
# 基于維度 0(輸出通道)上的 L2 范數,移除 50% 的整個通道(行)
prune.ln_structured(model[0], # 要剪枝的模塊name='weight', # 要剪枝的參數amount=0.5, # 要剪枝的通道比例n=2, # 用于重要性評分的 L2 范數 (n=2)dim=0 # 要剪枝的維度(輸出通道)
)print(model) # 查看結構化剪枝的剪枝掩碼
大規模高效推理 (續)
(注意:原文此處標題與上一節部分重復,但內容不同,推測為編輯錯誤,這里按內容翻譯為“平臺部署與優化”)
在不同平臺(如手機、物聯網設備或云服務器)上部署 ML 模型時,您需要針對每種環境調整方法:
- 模型簡化: 刪減那些對準確率貢獻不大但消耗資源的層或操作。這有助于使模型更精簡、更快,尤其是在資源受限的設備上。
- 硬件感知優化: 使您的模型適應可用的硬件能力,例如使用 GPU 優化或利用某些智能手機上的 NPU(神經處理單元)。
- 軟件框架和工具: 使用為您的平臺設計的部署工具。例如:TensorFlow Lite: 非常適合移動和邊緣設備。ONNX Runtime: 跨多個平臺工作,以實現一致的性能。
專用硬件可以帶來巨大的速度提升:
- GPU: 非常適合并行任務,如矩陣乘法,這在深度學習中很常見。
- TPU: 谷歌的定制芯片,針對張量數學進行了優化,對于訓練和推理都非常高效。
- FPGA: 靈活的硬件,可以為特定工作負載編程,提供出色的性能和能源效率。
假設優化 ML 系統推理的時間表為一年,計劃可能如下:
(注意:原文在此處暗示有一個時間表示例,但未提供。因此翻譯中省略。)
讓我們看一個 LLM 推理優化示例:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import timedef setup_model(optimized=True):# 加載 "facebook/opt-350m" 模型的 tokenizertokenizer = AutoTokenizer.from_pretrained("facebook/opt-350m")if optimized:# 設置 4 位加載的量化配置,計算精度為 float16quantization_config = BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_compute_dtype=torch.float16)# 加載啟用了 4 位量化的模型model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m",quantization_config=quantization_config)# 將模型轉換為 BetterTransformer 后端以實現更快的推理model = model.to_bettertransformer()else:# 加載標準的、未經優化的全精度模型model = AutoModelForCausalLM.from_pretrained("facebook/opt-350m")# 將模型移動到 GPU 以進行更快的計算return tokenizer, model.to("cuda")def generate_text(model, tokenizer, input_text, use_flash_attention=False):# 對輸入文本進行分詞并將張量移動到 GPUinputs = tokenizer(input_text, return_tensors="pt").to("cuda")if use_flash_attention:# 在支持的 GPU 上啟用 FlashAttention 內核以進行高效的注意力計算with torch.backends.cuda.sdp_kernel(enable_flash=True, enable_math=False, enable_mem_efficient=False):outputs = model.generate(**inputs)else:# 不使用 FlashAttention 優化的常規生成outputs = model.generate(**inputs)# 將生成的 token ID 解碼為字符串,跳過特殊 tokenreturn tokenizer.decode(outputs[0], skip_special_tokens=True)def measure_performance(input_text, optimized=True, use_flash_attention=False):# 設置模型和 tokenizer(優化版或標準版)tokenizer, model = setup_model(optimized)# 記錄生成開始時間start_time = time.time()# 使用給定的模型和 tokenizer 生成文本result_text = generate_text(model, tokenizer, input_text, use_flash_attention)# 記錄結束時間并計算耗時end_time = time.time()print(f"生成的文本: {result_text}")print(f"耗時: {end_time - start_time:.2f} 秒")# 示例輸入提示
input_text = "Hello my dog is cute and"# 運行優化版本(4位量化 + BetterTransformer + FlashAttention)
print("運行優化版本:")
measure_performance(input_text, optimized=True, use_flash_attention=True)# 運行標準模型且不使用 FlashAttention
print("\\n運行未優化版本:")
measure_performance(input_text, optimized=False, use_flash_attention=False)
當您的模型訓練完成并準備就緒時,下一個重要步驟是確保它在現實世界中運行良好。
管理實時應用的延遲和吞吐量
這意味著它應該處理大量請求,快速響應,并且無論如何都要保持可靠。讓我們看看如何在生產環境中高效地擴展您的模型推理。
在生產環境中,請求可能會快速涌入。您希望將這些請求均勻地分配到您的服務器上,這樣它們就不會過載。這就是負載均衡的作用——它使一切平穩運行。但如果做得不對,您的系統仍然會遇到困難。
- 自適應負載均衡: 這意味著根據服務器當前的繁忙程度將請求發送給它們,就像交通管制員將汽車引導到最不擁擠的道路一樣。
- 資源分配: 巧妙地使用硬件非常重要。GPU 對于繁重任務很強大,但對于簡單請求可能有點大材小用。根據請求的要求將工作分配給 GPU 或 CPU 有助于節省資源并加快速度。
import torchdef allocate_inference(request):"""根據請求復雜性將推理分配給 GPU 或 CPU 的簡單函數。"""# 假設 'request' 有一個 'complexity' 屬性(整數)if request.complexity > 5:device = torch.device("cuda:0") # 對復雜請求使用 GPUelse:device = torch.device("cpu") # 對簡單請求使用 CPU# 在選定的設備上加載您的模型model = your_model.to(device) # your_model 應替換為你的實際模型變量# 繼續進行推理...return model
當您的機器學習模型上線時,尤其是在實時應用中,您需要平衡兩個重要因素:延遲(latency) 和 吞吐量(throughput)。
- 延遲是您獲得響應的速度。
- 吞吐量是您在給定時間內可以處理的響應數量。
延遲和吞吐量(來自 Sina Torfi — 開源)
兩者都非常重要,但有時改進一個可能會拖慢另一個。讓我們用簡單的方式來分析如何管理這兩者。
在實時應用中,用戶或系統不希望等待太久。您希望您的模型快速給出答案。
- 精簡您的模型: 使用像量化和剪枝(前面已介紹)這樣的技術來縮小模型并使其更快,而不會損失太多準確性。
- 更智能地服務: 您提供模型服務的方式很重要!在需要額外算力時使用 GPU,或使用像 TorchServe 這樣的工具來有效管理請求,可以產生巨大的差異。
吞吐量關系到您的模型一次能處理多少請求,這在流量高峰期非常重要。
- 批處理(Batch Processing): 將多個傳入的請求組合在一起并一起處理,就像用一輛滿載乘客的公共汽車代替為每個人單獨派車一樣。總體來說更快,但在批次移動之前需要一點時間來填滿。
- 異步處理: 讓您的系統在處理先前請求的同時處理新請求,就像您在做飯時還能接新訂單一樣。這可以保持一切順暢流動。
平衡延遲和吞吐量意味著有時您需要根據實時情況改變一次處理的請求數量——即您的批次大小。
以下是使用 PyTorch 實現此目的的一種簡單方法:
import torch
from queue import Queue
from threading import Thread# 定義一個簡單的線性模型用于推理
model = torch.nn.Linear(10, 2)
model.eval() # 將模型設置為評估模式def inference_worker(input_queue):while True:# 等待隊列中的一批輸入batch = input_queue.get()if batch is None: # 檢查退出信號breakwith torch.no_grad(): # 推理時禁用梯度計算output = model(batch) # 對輸入批次運行推理# (可選) 在此處根據需要處理輸出input_queue.task_done() # 標記任務已完成# 創建一個最大容量為 10 的隊列來存放輸入批次
input_queue = Queue(maxsize=10)# 啟動一個工作線程,處理來自隊列的輸入批次
worker = Thread(target=inference_worker, args=(input_queue,))
worker.start()# 動態地將批次送入隊列
for _ in range(100):# 創建一個包含 5 個樣本,每個樣本有 10 個特征的批次input_batch = torch.randn(5, 10)input_queue.put(input_batch) # 將批次添加到隊列等待處理input_queue.put(None) # 向工作線程發送退出信號
worker.join() # 等待工作線程完成
到目前為止,我們學到了……
這不僅僅是關于速度或容量——而是關于平衡快速響應和對高負載的平穩處理,以保持用戶滿意
邊緣 AI 和移動部署
在智能手機和物聯網小工具等邊緣設備上部署 AI 模型意味著在數據產生的地方直接運行 AI。
這種設置減少了延遲,節省了網絡帶寬,并使您的數據更具隱私性,因為它不必離開設備。
為了讓 AI 在這些資源有限的設備上良好工作,您需要專注于幾個智能策略:
- 模型優化: 使用量化、剪枝和知識蒸餾等技術來縮小模型。更小的模型運行更快,更適合性能較弱的硬件。
- 邊緣友好框架: 像 TensorFlow Lite 和 PyTorch Mobile 這樣的工具就是為此而構建的。它們有助于轉換和優化您的模型,使其在邊緣設備上高效運行。
import tensorflow as tf# 將您的 TensorFlow 模型轉換為 TensorFlow Lite 格式以進行邊緣部署
converter = tf.lite.TFLiteConverter.from_keras_model(model) # model 應為你的 Keras 模型對象
tflite_model = converter.convert() # 執行轉換# 將 TFLite 模型寫入文件
with open('model.tflite', 'wb') as f:f.write(tflite_model)
邊緣設備通常在處理能力、內存和電池壽命方面有嚴格的限制。因此,您的 AI 模型需要精簡高效:
- 模型壓縮: 使用剪枝或量化來縮小模型,以節省空間并加快推理速度。
- 節能算法: 選擇或設計不會耗盡電池或使處理器過載的算法。
- 邊緣優化架構: 使用像 MobileNet 或 EfficientNet 這樣專門為快速、輕量且仍保持準確性而構建的網絡。
第四階段:性能分析與優化
在優化 AI 系統時,關鍵是找出導致速度變慢的地方——即瓶頸——這樣您就可以修復它們并提升性能。
診斷系統瓶頸
這里有兩個主要工具可以提供幫助:分析(profiling) 和 基準測試(benchmarking)。
分析 是深入挖掘,查看您的系統如何使用 CPU、GPU 和內存等資源,以及代碼的不同部分運行需要多長時間。它就像一張性能地圖,突出顯示了您想要改進的緩慢或耗費資源的地方。
- Python 的 cProfile: 一個方便的內置工具,用于衡量您的 Python 代碼大部分時間花在了哪里。
- NVIDIA Nsight Systems: 如果您使用 NVIDIA GPU,此工具可跟蹤 GPU 性能并幫助查找 CUDA 代碼中的瓶頸。
基準測試著眼于更大的圖景:您的整個系統與標準或其他版本相比有多快和多高效。它設定了一個基線,這樣您就知道自己從哪里開始,并可以衡量您的更改帶來了多少幫助。
- 建立基線: 在進行任何更改之前,對您當前的系統進行基準測試。
- 比較: 檢查您的系統與其他系統或行業基準的對比情況。
- 衡量影響: 優化后,再次進行基準測試,看看您的改進是否真的產生了效果。
瓶頸有不同的形式,如計算、內存或網絡瓶頸,每種都需要不同的修復方法。
計算瓶頸 發生在您的處理器(CPU/GPU)跟不上工作量時。
修復方法:
- 使用并行計算:將工作分散到多個核心或 GPU 上以加快速度。
- 優化算法:簡化計算或切換到更有效的方法以減輕負載。
內存瓶頸 發生在您的系統無法足夠快地移動數據或內存不足時。
修復方法:
- 緩存常用數據以避免緩慢的內存讀取。
- 使用剪枝、量化或更輕量級的數據結構來減少內存占用。
- 示例: 如果您的模型對于 GPU 內存來說太大,您可能需要這些技巧,因為您不能簡單地添加更多 RAM。
網絡瓶頸 出現在分布式系統中,數據需要在機器之間傳輸。
修復方法:
- 使用更好的數據序列化來縮小正在發送的數據的大小。
- 切換到更高效的通信協議,以降低延遲并加快數據傳輸速度。
AI 模型的操作化
密切關注系統的健康狀況和 AI 模型的性能對于平穩、可靠的運營至關重要。良好的監控有助于及早發現問題,在它們變成大問題之前。以下是建立有效監控策略的直接方法:
您可以使用的工具:
(圖片可能描述監控工具,如 Prometheus, Grafana 等)
- Prometheus: 一個開源工具,用于收集和存儲指標,如 CPU 使用率、內存消耗、磁盤 I/O 和網絡流量。它非常適合跟蹤 AI 基礎設施的整體健康狀況。
- Grafana: 一個強大的可視化工具,與 Prometheus 配合良好,可以創建直觀的儀表板。它有助于輕松發現系統數據中的異常和趨勢。
模型性能監控 流行的選項包括:
- TensorBoard: 專為 TensorFlow 和 PyTorch 構建,TensorBoard 讓您可以可視化訓練和評估指標,如損失、準確率、權重分布,甚至模型的架構。定期檢查這些有助于您了解模型的學習和表現情況。
- 自定義日志記錄: 有時您需要跟蹤特定于應用程序的指標或事件,而 TensorBoard 無法涵蓋。實現自己的日志記錄系統可以讓您捕獲預測、錯誤或任何自定義 KPI 以進行更深入的分析。
因此,一些最佳的監控技術是:
最佳監控技術
- 設置有意義的警報: 收集數據固然很好,但為關鍵指標定義閾值至關重要,這樣一旦出現問題,您就會立即收到通知。警報可以幫助您在問題影響用戶之前迅速采取行動。
- 監控數據質量: 您的模型的好壞取決于它獲得的數據。注意數據漂移(輸入數據隨時間的變化)和可能降低性能的異常情況。例如,記錄樣本圖像或數據批次可以幫助您及早發現變化。
- 持續評估: 定期使用新數據評估您的模型,以發現性能下降。當準確性或其他指標低于設定閾值時,自動觸發警報或重新訓練,確保您的模型保持有效。
- 指標異常檢測: 使用基于 ML 的異常檢測技術自動標記模型性能中的異常模式,這樣您就可以在無需手動檢查的情況下始終掌握潛在問題。
- 檢測數據和概念漂移: 定期檢查您的數據性質或問題本身是否正在發生變化。專門的漂移檢測工具可以提醒您這些變化,促使模型更新或重新訓練。
- 自動化重新訓練流水線: 構建工作流,可以在新數據到達或性能下降時自動重新訓練和重新部署模型。但要明智——設置嚴格的標準,以避免在實踐中無關緊要的微小改進上浪費資源。
調試 AI 系統:工具和方法論
由于復雜的數據流和模型,調試 AI 很棘手。使用這些工具和方法:
- PyTorch Autograd Profiler: 檢查 PyTorch 模型中的時間和內存使用情況。
- TensorFlow Debugger (tfdbg): 檢查張量值以發現諸如 NaN 或錯誤形狀之類的錯誤。
- 交互式調試: 使用 Jupyter notebook 進行實時數據和模型檢查。
- 高級分析: 像 NVIDIA Nsight 和 PyTorch Profiler 這樣的工具可以分析 GPU 使用情況和硬件瓶頸以優化性能。
機器學習的 CI/CD 流水線
快速、可靠的模型更新是 AI 項目的關鍵。CI/CD 自動化了測試、集成和部署,以最小的人工干預使模型平穩運行。
能夠快速測試和改進 ML 模型是構建成功 AI 系統的關鍵。通過使用 CI/CD(持續集成和持續部署),我們可以以最小的人工干預自動化測試、模型更新和部署。這使一切運行順暢可靠。
ML 中的持續集成 (CI)
CI 意味著自動檢查代碼更改以及早發現問題。在 ML 中,它還包括檢查數據、訓練腳本和模型本身。
- 自動化測試:設置測試以檢查數據質量、模型訓練和預測。對小部分使用單元測試,對完整流水線使用集成測試。
- 版本控制:使用像 DVC 這樣的工具來跟蹤數據和模型的更改,就像對待代碼一樣。這有助于保持一切一致,并在需要時輕松回滾。
ML 中的持續部署 (CD)
CD 意味著自動將新模型投入生產,以便用戶快速獲得最新的改進。
- 模型服務:像 TensorFlow Serving 或 TorchServe 這樣的工具有助于高效地提供模型服務并管理版本。
- Docker:使用 Docker 將您的模型及其所有依賴項打包在一起。這使得在任何地方運行模型變得容易。
- Jenkins + Kubernetes:使用 Jenkins 自動化諸如測試和部署模型之類的任務。與 Kubernetes 結合使用以在生產中擴展和管理模型。
使其更好地工作的額外工具
- 實驗跟蹤:像 MLflow 或 Weights & Biases 這樣的工具有助于跟蹤實驗、模型指標和結果。
- 環境管理:使用像 Conda 或 Pipenv 這樣的工具來管理 Python 包,并與 Docker 配對以實現開發和生產之間的一致性。
- 模型驗證:設置自動化檢查,以確保每個模型在部署前都滿足性能標準(例如,準確性或精確度)。
結論
感謝閱讀!無論您是開始新項目還是改進現有項目,我希望本指南對您的 AI 工作有所幫助。