【啟動AI Maker世代 】2024 MAI 開發者社群大會(5/16-17)
|

運用BF16與NHWC技術實現進階版LLM微調訓練最佳化

   
作者:高煥堂、鄭仲平

上一篇文章說明了如何利用英特爾(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:單精度與半精度浮點數格式

在圖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註冊即可享有一切福利!

會員福利
1

免費電子報

2

會員搶先看

3

主題訂閱

4

好文收藏

高煥堂

Author: 高煥堂

擁有40多年軟硬體整合設計經驗,專精Android終端平台、AI、Docker容器、VR、AI(人工智慧)、IC軟硬整合技術與大數據應用。 近5年來,從事於AI普及化教育工作,目前擔任銘傳大學AI課程、長庚智慧醫療研究所AI課程授課老師。也擔任永春國小、東園國小、立志中學、君毅中學、永春高中等學校的AI師資培育工作。

Share This Post On
468 ad

Submit a Comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *