上一篇文章說明了如何利用英特爾(Intel)所研發的新產品:IPEX (Intel-Extension-for-PyTorch )軟體搭配GPU硬體,並透過 PyTorch的<xpu>裝置來發揮GPU潛能,大幅加速AI模型的訓練和推論;同時也以<ResNet + LoRA>微調訓練的範例,來說明其使用方法和流程。而與上述兩項產品息息相關的重要技術是:
- BF16半精度浮點數。2721
- 將影像資料設為 Channel_last資料格式(即NHWC格式)。
本文就要來介紹以上這兩項技術,並延續上一篇的<ResNet50 + LoRA>微調模型訓練,以完整程式碼示範如何發揮這兩項新技術的用法和魅力。
這兩項技術常常互相搭配,以提供進階的最佳化效益。例如在大語言模型(LLM)中,常常採取自動混合精度(Automatic mixed precision)訓練模式,此時會在模型訓練過程中,針對各層(Layer)而選擇不同的資料精度(如FP32或BF16等),來搭配不同的影像資料格式(如NCHW或NHWC),從而實現節省記憶體,並加快訓練速度之目的。
使用BF16半精度浮點數格式
簡介BF16單精度浮點數格式
首先複習一下,在電腦裡的數值常分為整數(integer)與浮點數(floating point)兩種。例如,一隻獵狗的年紀(如5歲),即是一個整數。而它的體重是11.753公斤,這即是浮點數。其中,浮點數的電腦儲存格式又可分為:單精度浮點數(float)與雙精度浮點數(double)兩種。而單精度浮點數(全名:Single-precision floating-point)又分為三種,如圖1所示:
圖1:單精度與半精度浮點數格式
在圖1,FP32是IEEE 754-2008 方案的單精度浮點數格式,為32位元長度。而FP16(又稱為float16),它的全名稱是:半精度浮點數(Half-precision floating-point)。至於BF16,又稱為:bfloat16 (全名是:Brain float point)浮點格式。它使用16位元(2位元組)來儲存一個浮點數。如圖1所示,分為三部分:
- 符號(Sign):佔1位元,0代表正值,1代表負值。
- 指數(Exponent):佔8位元。
- 尾數(Mantissa):佔7位元。
例如,32767這個數值,以科學計數法可以寫成3.2767*10^(4)。其中3.2767即是尾數,而4即為指數。
從圖1可以看出,FP32格式擁有比較長的尾數(23位元),能表達很高精確度的數值。它的指數部分(8位元),是用來表達其數值大小的有效範圍,又稱:動態範圍(dynamic range)。
接下來比較看看FP16與BF16的差別。FP16 較 BF16 擁有更長的尾數,但指數部分較短。因此 FP16 的精(確)度高於BF16。採用BF16格式的用意是藉由降低精度,來換取較大的有效範圍。
OpenVINO更多的新功能陸續問世!新版本將新增對新硬體以及更多生成式AI模型的支援,在2024年的新開始,讓我們一起透過免費線上講座「掌握OpenVINO在生成式AI最新功能, 並實現Model Server創新應用」來探索這套越來越強大的工具,讓您的專案開發更如虎添翼,名額有限快來報名!
從機器學習(ML)的角度看BF16的特性
為什麼要特別提到這BF16格式呢? 因為它是專為人工智慧(AI) / 深度學習(DL)的張量(Tensor)運算最佳化發展而來。它原來是由Google Brain團隊發明,並使用於其第三代Tensor Processing Unit (TPU),用來降低記憶體(Memory)存儲需求。如今已被許多公司(如Intel、Arm等)的AI加速器廣泛採用。
採用BF16格式的用意是藉由降低數值的精確度,來減少讓張量相乘所需的運算資源和功耗。目前有許多ML運算使用FP32格式,雖然可以達到非常準確的運算,但需要強大的硬體而且極其耗電。而BF16雖然較低精度,但在相同硬體上的傳輸效率,因而更省電,只是預測的準確性較低一些。
例如,在大語言模型(LLM)的訓練任務中是更重要的,由於注意力(Attention)機制中使用了大量的exp運算,此時更大的有效範圍比精確度更為重要。因為在訓練時梯度很容易超過FP16的表示範圍,導致訓練loss值爆掉,而BF16表示範圍跟FP32一致,訓練模型時就穩定多了。
使用<IPEX + GPU + BF16>環境訓練
在上一篇文章裡,曾經建置了<IPEX + GPU>訓練環境。在本篇裡,就來加以擴大,採用BF16半精度浮點數格式。並延續上一篇的<ResNet50 + LoRA>模型訓練範例,來觀察BF16對性能優化的貢獻。
Step-c1:準備訓練資料(Training data)
本篇範例使用的訓練資料集,與上一篇(第2篇)的訓練資料集是相同的。請您參考上一篇文章的說明。
Step-c2: 定義<ResNet50+LoRA>模型,並展開訓練
本範例的<ResNet50 + LoRA>整合模型,與上一篇文章的模型是一致的(訓練資料集也是一致的),請您參閱其中的說明。在本範例裡,只是將原來的<IPEX + GPU>訓練環境,擴大為<IPEX + GPU + BF16>而已。此範例的程式碼如下:
# lora_RESNET_basic_003_train_xpu_bf16_cl.py
import numpy as np
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import Dataset, DataLoader
from torchvision.models import resnet50, ResNet50_Weights
import time
data_path = '/home/eapet/resnet_LoRA/m_data/train/'
base_path = '/home/eapet/resnet_LoRA/m_data/'
# Make torch deterministic
# 設定:每次訓練的W&B初始化都是一樣的
_ = torch.manual_seed(0)
#-------------------------------------
# 載入ResNet50預訓練模型
# Step 1: Initialize model with the best available weights
weights = ResNet50_Weights.IMAGENET1K_V1
resnet_model = resnet50(weights=weights)
# 遷移學習不需要梯度(不更改權重)
for param in resnet_model.parameters():
param.requires_grad = False
#-------------------------------
resnet_model.eval()
#------------------------------------------
# 準備Training data
# 把圖片轉換成Tensor
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
data_set = ImageFolder(data_path, transform=transform)
length = len(data_set)
print("\n")
print(length, "張圖片\n")
bz = 60
test_loader = DataLoader(data_set, batch_size=bz, shuffle=True)
#--------------------------------------
def process_lx(labels):
lx = labels.clone()
for i in range(bz):
if(labels==0):
lx=340
elif(labels==1):
lx=24
elif(labels==2):
lx=947
return lx
for idx, (images, la) in enumerate(test_loader):
break
labels = process_lx(la)
print(labels)
#-----------------------------------
def test():
correct = 0
total = 0
wrong_counts =
with torch.no_grad():
for idx, (images, la) in enumerate(test_loader):
labels = process_lx(la)
prediction = resnet_model(images)
for idx, zv in enumerate(prediction):
if torch.argmax(zv) == labels:
correct +=1
else:
#print(idx)
wrong_counts] +=1
total +=1
#print(correct)
print('\n', f'Accuracy: {round(correct/total, 3)}')
for i in range(len(wrong_counts)):
print(f'wrong counts for the digit {i}: {wrong_counts}')
#------------------------------------------
print('\n------ 原模型測試: ------')
test()
#==========================================
class Lora(nn.Module):
def __init__(self, m, n, rank=10):
super().__init__()
self.m = m
self.A = nn.Parameter(torch.randn(m, rank))
self.B = nn.Parameter(torch.zeros(rank, n))
def forward(self, inputs):
#print(inputs.shape)
#inputs = inputs.view(-1, self.m)
inputs = torch.reshape(inputs, )
#print(self.m)
return torch.mm(torch.mm(inputs, self.A), self.B)
lora = Lora(224 * 224 * 3, 1000)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(lora.parameters(), lr=1e-4)
#========================================================
print('\n------ 外掛LoRA模型,協同訓練: ------')
import intel_extension_for_pytorch as ipex
resnet_model = resnet_model.to(memory_format=torch.channels_last)
#lora = lora.to(memory_format=torch.channels_last)
loss_fn = loss_fn.to("xpu")
lora = lora.to("xpu")
lora, optimizer = ipex.optimize(lora, optimizer=optimizer, dtype=torch.bfloat16)
resnet_model = resnet_model.to("xpu")
resnet_model = ipex.optimize(resnet_model, dtype=torch.bfloat16)
base = 0
epochs = 100
begin = time.time()
for ep in range(epochs+1):
total_loss = 0
for idx, (images, la) in enumerate(test_loader):
labels = process_lx(la)
images = images.to("xpu")
labels = labels.to("xpu")
images =images.to(memory_format=torch.channels_last)
with torch.xpu.amp.autocast(enabled=True, dtype=torch.bfloat16):
pred = resnet_model(images) + lora(images)
loss = loss_fn(pred, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item() * bz
if((base+ep)%5 == 0):
loss_np = total_loss / 120
print('ep=', base+ep,'/',base+epochs, ', loss=', loss_np)
end = time.time()
print("TTT =", end-begin)
#------ Saved to *.CKPT ---------------------------
FILE = base_path + 'LORA_for_RESNET_ep50.ckpt'
torch.save(lora.state_dict(), FILE)
print('\nsaved to ' + FILE)
#-------------------------------------
def test22():
correct = 0
total = 0
wrong_counts =
with torch.no_grad():
for idx, (images, la) in enumerate(test_loader):
labels = process_lx(la)
images = images.to("xpu")
labels = labels.to("xpu")
with torch.xpu.amp.autocast(enabled=True, dtype=torch.bfloat16):
prediction = resnet_model(images) + lora(images)
for idx, zv in enumerate(prediction):
if torch.argmax(zv) == labels:
correct +=1
else:
#print(idx)
wrong_counts] +=1
total +=1
#print(correct)
print('\n', f'Accuracy: {round(correct/total, 3)}')
for i in range(len(wrong_counts)):
print(f'wrong counts for the digit {i}: {wrong_counts}')
#------------------------------------------
print('\n------ 原模型測試: ------')
test22()
#------------------------
#END
此範例使用Pytorch的指令:
lora = lora.to("xpu")
lora, optimizer = ipex.optimize(lora, optimizer=optimizer, dtype=torch.bfloat16)
resnet_model = resnet_model.to("xpu")
resnet_model = ipex.optimize(resnet_model, dtype=torch.bfloat16)
其設定由GPU來執行這lora模型和resnet_model模型。並且設定兩個模型的優化器(Optimizer)都採取BF16浮點數,也就是反向傳播的計算時採取BF16浮點數。繼續使用指令:
with torch.xpu.amp.autocast(enabled=True, dtype=torch.bfloat16):
prediction = resnet_model(images) + lora(images)
其設定兩個模型的正向推演計算時,也採取BF16浮點數。如下圖所示:
只需不到短短一分鐘...
輸入您的信箱與ID註冊即可享有一切福利!
會員福利
免費電子報
會員搶先看
主題訂閱
好文收藏