[ad_1]
Khi sự hiện diện của các ứng dụng dựa trên AI ngày càng trở nên phổ biến trong cuộc sống hàng ngày của chúng ta, thách thức tối ưu hóa hiệu suất thời gian chạy của chúng ngày càng tăng. Giảm số lượng bit được sử dụng để biểu diễn các loại dấu phẩy động là một kỹ thuật phổ biến có thể tăng tốc các ứng dụng AI và giảm dung lượng bộ nhớ của chúng. Và thực tế, nhiều bộ tăng tốc phần cứng AI hiện đại bao gồm hỗ trợ chuyên dụng cho các biểu diễn dấu phẩy động 8 bit. trong một bài trướcchúng tôi đã thảo luận về tiềm năng (và rủi ro) của việc đào tạo với FP8 và chứng minh nó trong thực tế trên phiên bản đào tạo dựa trên H100 bằng cách sử dụng PyTorch và Động cơ biến áp (TE), một thư viện chuyên dụng để tăng tốc các mẫu Transformer trên GPU NVIDIA. Đương nhiên, việc PyTorch giới thiệu hỗ trợ riêng cho các kiểu dữ liệu FP8 chỉ còn là vấn đề thời gian. Trong bài đăng này, chúng tôi sẽ xem xét các khả năng hiện tại và chứng minh việc sử dụng chúng trên một chip AI hỗ trợ FP8 khác, GPU NVIDIA L4. Cụ thể hơn, chúng tôi sẽ chạy thử nghiệm trên Google Cloud g2-tiêu chuẩn-16 VM (với một GPU L4), một máy ảo chuyên dụng hình ảnh VM học sâuvà PyTorch 2.3.0.
Điều quan trọng là, tính đến thời điểm viết bài này, hỗ trợ FP8 gốc của PyTorch là đánh giá cao thực nghiệm. Công dụng của nó là không khuyến khích cho người yếu tim hoặc không chịu được lỗi. Bài đăng này chủ yếu dành cho những người mới áp dụng – bất kỳ ai (như chúng tôi) bị ám ảnh bởi việc tối ưu hóa hiệu suất của mô hình AI và tiềm năng tốt của công nghệ mới này. Hãy nhớ rằng các API mà chúng tôi giới thiệu có thể được sửa đổi vào thời điểm bạn đọc bài đăng này.
Trọng tâm của chúng tôi sẽ là tác động tiềm tàng mà việc sử dụng FP8 có thể gây ra đối với hiệu suất thời gian chạy của các ứng dụng AI. Để tìm hiểu về ý nghĩa thuật toán, chúng tôi giới thiệu người đọc tới các hướng dẫn chuyên dụng về chủ đề này (chẳng hạn như đây Và đây).
Cảm ơn rất nhiều Yitzhak Levi cho những đóng góp của ông cho bài viết này.
Kể từ phiên bản 2.2, PyTorch bao gồm “hỗ trợ hạn chế” cho torch.float8_e4m3fn
Và torch.float8_e5m2
các kiểu dữ liệu (tương ứng với 3 và 2 bit mantissa) cả hai đều là sự triển khai của các kiểu được chỉ định trong Định dạng FP8 cho học sâu giấy. Trong đoạn mã bên dưới, chúng tôi hiển thị các thuộc tính và phạm vi động của các loại mới so với các loại bit động cũ:
import torch
from tabulate import tabulatef32_type = torch.float32
bf16_type = torch.bfloat16
e4m3_type = torch.float8_e4m3fn
e5m2_type = torch.float8_e5m2
# gather finfo for every kind
desk = ()
for dtype in (f32_type, bf16_type, e4m3_type, e5m2_type):
numbits = 32 if dtype == f32_type else 16 if dtype == bf16_type else 8
data = torch.finfo(dtype)
desk.append((data.dtype, numbits, data.max,
data.min, data.smallest_normal, data.eps))
headers = ('knowledge kind', 'bits', 'max', 'min', 'smallest regular', 'eps')
print(tabulate(desk, headers=headers))
'''
Output:
knowledge kind bits max min smallest regular eps
------------- ---- ----------- ------------ --------------- -----------
float32 32 3.40282e+38 -3.40282e+38 1.17549e-38 1.19209e-07
bfloat16 16 3.38953e+38 -3.38953e+38 1.17549e-38 0.0078125
float8_e4m3fn 8 448 -448 0.015625 0.125
float8_e5m2 8 57344 -57344 6.10352e-05 0.25
'''
Chúng ta có thể tạo các tensor FP8 bằng cách chỉ định dtype trong hàm khởi tạo tensor như được trình bày dưới đây:
system="cuda"
e4m3 = torch.tensor(1., system=system, dtype=e4m3_type)
e5m2 = torch.tensor(1., system=system, dtype=e5m2_type)
Chúng tôi cũng có thể chuyển các kiểu cũ sang FP8. Trong khối mã bên dưới, chúng tôi tạo ra một tenxơ ngẫu nhiên của các số float và so sánh kết quả của việc chuyển chúng thành bốn loại dấu phẩy động khác nhau:
x = torch.randn(2, 2, system=system, dtype=f32_type)
x_bf16 = x.to(bf16_type)
x_e4m3 = x.to(e4m3_type)
x_e5m2 = x.to(e5m2_type)print(tabulate(((‘float32’, *x.cpu().flatten().tolist()),
(‘bfloat16’, *x_bf16.cpu().flatten().tolist()),
(‘float8_e4m3fn’, *x_e4m3.cpu().flatten().tolist()),
(‘float8_e5m2’, *x_e5m2.cpu().flatten().tolist())),
headers=(‘knowledge kind’, ‘x1’, ‘x2’, ‘x3’, ‘x4’)))
'''
The pattern output demonstrates the dynamic vary of the different sorts:
knowledge kind x1 x2 x3 x4
------------- -------------- -------------- -------------- --------------
float32 2.073093891143 -0.78251332044 -0.47084918620 -1.32557279110
bfloat16 2.078125 -0.78125 -0.4707031 -1.328125
float8_e4m3fn 2.0 -0.8125 -0.46875 -1.375
float8_e5m2 2.0 -0.75 -0.5 -1.25
------------- -------------- -------------- -------------- --------------
'''
Mặc dù việc tạo tensor FP8 khá dễ dàng nhưng bạn có thể nhanh chóng nhận thấy rằng việc thực hiện một số phép tính số học cơ bản trên tensor FP8 không được hỗ trợ (trong PyTorch 2.3.0, tính đến thời điểm viết bài này). Ngoại lệ duy nhất (được cho là quan trọng nhất) là phép nhân ma trận FP8, được hỗ trợ thông qua hàm torch._scaled_mm chuyên dụng. Được minh họa trong khối mã bên dưới, hàm này nhận được hai tensor FP8 (cùng loại) và các hệ số tỷ lệ liên quan của chúng, cũng như một tensor sai lệch tùy chọn:
output, output_amax = torch._scaled_mm(
torch.randn(16,16, system=system).to(e4m3_type),
torch.randn(16,16, system=system).to(e4m3_type).t(),
bias=torch.randn(16, system=system).to(bf16_type),
out_dtype=e4m3_type,
scale_a=torch.tensor(1.0, system=system),
scale_b=torch.tensor(1.0, system=system)
)
Để hiểu rõ hơn về các khả năng và chế độ sử dụng API hiện tại, bạn có thể xem qua Tập lệnh kiểm tra API trong kho lưu trữ PyTorch.
Ngược lại với sự hỗ trợ của FP8 trong Động cơ biến áp thư viện mà chúng tôi đã trình diễn trong bài trước, bản gốc PyTorch cho phép định nghĩa và sử dụng rõ ràng các kiểu dữ liệu FP8. Điều này cung cấp cho các nhà phát triển nâng cao sự linh hoạt hơn nhiều trong việc thiết kế và triển khai các thuật toán FP8 tùy chỉnh. Tuy nhiên, như đã thảo luận trong bài đăng trước của chúng tôi, việc đào tạo mô hình FP8 ML thành công thường đòi hỏi một số màn nhào lộn sáng tạo; nhiều người dùng sẽ mong muốn một API cấp cao tự động áp dụng các sơ đồ chuyển đổi loại và chia tỷ lệ đã được thử nghiệm trong trận chiến cho các thuật toán đào tạo mô hình AI hiện có của họ. Mặc dù (tính đến thời điểm viết bài này) không phải là một phần của thư viện PyTorch chính thức, nhưng chức năng đó được cung cấp thông qua thư viện float8_experimental.
Trong phần này, chúng tôi sẽ trình bày việc sử dụng thư viện float8_experimental một cách đơn giản Máy biến áp tầm nhìn (ViT-Big) mô hình phân loại được hỗ trợ với 632 triệu tham số (sử dụng phiên bản 1.0.3 của mô hình phổ biến timm gói Python). Vui lòng xem tài liệu để hướng dẫn về việc cài đặt thư viện float8_experimental. Chúng tôi thiết lập đường trục ViT để sử dụng tổng hợp trung bình toàn cầu để tránh một số khúc mắc trong sản phẩm hiện tại (ví dụ: xem đây). Trong khối mã bên dưới, chúng tôi minh hoạ việc huấn luyện FP8 với chiến lược mở rộng quy mô bị trì hoãn trên tập dữ liệu được tạo ngẫu nhiên. Chúng tôi bao gồm các điều khiển để chuyển đổi loại dấu phẩy động, sử dụng ngọn đuốc.compile chế độ và thiết lập kích thước lô.
import torch
from timm.fashions.vision_transformer import VisionTransformer
from torch.utils.knowledge import Dataset, DataLoader
import os
import time#float8 imports
from float8_experimental import config
from float8_experimental.float8_linear import Float8Linear
from float8_experimental.float8_linear_utils import (
swap_linear_with_float8_linear,
sync_float8_amax_and_scale_history
)
#float8 configuration (see documentation)
config.enable_amax_init = False
config.enable_pre_and_post_forward = False
# mannequin configuration controls:
fp8_type = True # toggle to alter floating-point precision
compile_model = True # toggle to allow mannequin compilation
batch_size = 32 if fp8_type else 16 # management batch dimension
system = torch.system('cuda')
# use random knowledge
class FakeDataset(Dataset):
def __len__(self):
return 1000000
def __getitem__(self, index):
rand_image = torch.randn((3, 256, 256), dtype=torch.float32)
label = torch.tensor(knowledge=(index % 1024), dtype=torch.int64)
return rand_image, label
# get knowledge loader
def get_data(batch_size):
ds = FakeDataset()
return DataLoader(
ds,
batch_size=batch_size,
num_workers=os.cpu_count(),
pin_memory=True
)
# outline the timm mannequin
def get_model():
mannequin = VisionTransformer(
class_token=False,
global_pool="avg",
img_size=256,
embed_dim=1280,
num_classes=1024,
depth=32,
num_heads=16
)
if fp8_type:
swap_linear_with_float8_linear(mannequin, Float8Linear)
return mannequin
# outline the coaching step
def train_step(inputs, label, mannequin, optimizer, criterion):
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
outputs = mannequin(inputs)
loss = criterion(outputs, label)
optimizer.zero_grad(set_to_none=True)
loss.backward()
if fp8_type:
sync_float8_amax_and_scale_history(mannequin)
optimizer.step()
mannequin = get_model()
optimizer = torch.optim.Adam(mannequin.parameters())
criterion = torch.nn.CrossEntropyLoss()
train_loader = get_data(batch_size)
# copy the mannequin to the GPU
mannequin = mannequin.to(system)
if compile_model:
# compile mannequin
mannequin = torch.compile(mannequin)
mannequin.practice()
t0 = time.perf_counter()
summ = 0
rely = 0
for step, knowledge in enumerate(train_loader):
# copy knowledge to GPU
inputs = knowledge(0).to(system=system, non_blocking=True)
label = knowledge(1).squeeze(-1).to(system=system, non_blocking=True)
# practice step
train_step(inputs, label, mannequin, optimizer, criterion)
# seize step time
batch_time = time.perf_counter() - t0
if step > 10: # skip first steps
summ += batch_time
rely += 1
t0 = time.perf_counter()
if step > 50:
break
print(f'common step time: {summ / rely}')
Điều đầu tiên chúng tôi lưu ý là việc sử dụng loại dữ liệu có độ chính xác thấp hơn sẽ giải phóng bộ nhớ GPU, cho phép chúng tôi tăng gấp đôi kích thước lô. Bảng dưới đây tóm tắt kết quả hiệu suất (được đo bằng thời gian bước trung bình) khi luyện tập với nhiều cài đặt cấu hình khác nhau. Như được đề xuất trong tài liệu, ngọn đuốc.compile Thử nghiệm FP8 được chạy bằng phiên bản hàng đêm của PyTorch (cụ thể là phiên bản torch-2.4.0.dev20240520+cu121).
Như kết quả đã chứng minh, việc sử dụng các lớp tuyến tính FP8 làm tăng hiệu suất của mô hình đồ chơi của chúng tôi lên 47%(!!) so với thử nghiệm cơ bản của chúng tôi, nhưng chỉ một khi nó được kết hợp với việc sử dụng ngọn đuốc.compile. Đương nhiên, kết quả sẽ thay đổi tùy theo định nghĩa và kích thước của mô hình.
Để so sánh, chúng tôi thực hiện trình tự huấn luyện tương tự bằng cách sử dụng Động cơ biến áp (TE) thư viện (phiên bản 1.6). Mặc dù TE bao gồm tính năng tối ưu hóa của riêng nó Lớp biến áp (như đã được chứng minh trong phần của chúng tôi bài trước), chúng tôi ghi đè thủ công ngọn đuốc.nn.Tuyến tính lớp với TE tuyến tính để giới hạn đánh giá so sánh của chúng tôi chỉ ở mức hỗ trợ tuyến tính FP8. Trong khối mã bên dưới, chúng tôi triển khai một tiện ích hoán đổi lớp tuyến tính đơn giản (bạn tự chịu rủi ro khi sử dụng!!) và áp dụng nó cho mô hình ViT của chúng tôi. Chúng tôi cũng bao gồm chức năng bước đào tạo cần thiết cho đào tạo FP8 bằng TE:
import transformer_engine.pytorch as te# swap all linear layers with te.Linear
def simple_swap(mannequin):
for submodule_name, submodule in mannequin.named_modules():
if isinstance(submodule, torch.nn.Linear):
print(submodule_name)
path_in_state_dict = submodule_name.cut up('.')
current_module = mannequin
# traverse to leaf module
leaf_path = path_in_state_dict(:-1)
leaf_name = path_in_state_dict(-1)
for child_name in leaf_path:
current_module = getattr(current_module, child_name)
# carry out a swap
old_leaf = getattr(current_module, leaf_name)
new_leaf = te.Linear(old_leaf.in_features,
old_leaf.out_features,
old_leaf.bias will not be None)
setattr(current_module, leaf_name, new_leaf)
def get_model():
mannequin = VisionTransformer(
class_token=False,
global_pool="avg",
img_size=256,
embed_dim=1280,
num_classes=1024,
depth=32,
num_heads=16
)
simple_swap(mannequin)
return mannequin
def train_step(inputs, label, mannequin, optimizer, criterion):
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
with te.fp8_autocast(enabled=True):
outputs = mannequin(inputs)
loss = criterion(outputs, label)
optimizer.zero_grad(set_to_none=True)
loss.backward()
optimizer.step()
Kết quả thí nghiệm TE được ghi lại dưới đây:
Mặc dù mô hình TE FP8 chưa được biên dịch hoạt động tốt hơn đáng kể so với mô hình FP8 trước đây của chúng tôi, nhưng mô hình PyTorch FP8 đã biên dịch vẫn mang lại kết quả tốt nhất. Điều quan trọng là tại thời điểm viết bài này, các mô-đun TE FP8 không hỗ trợ biên soạn mô hình. Như vậy, việc áp dụng ngọn đuốc.compile sẽ dẫn đến “biên dịch một phần”, tức là nó sẽ bao gồm nhiều ngắt biểu đồ (mỗi khi sử dụng FP8).
Chúng tôi cố tình giới hạn các thử nghiệm của mình chỉ ở các lớp tuyến tính của mô hình đồ chơi. Không có gì đáng ngạc nhiên khi áp dụng toàn bộ sức mạnh của TE vào mô hình của chúng tôi, như đã trình bày trong bài trướcsẽ dẫn đến mức tăng 72% (so với thử nghiệm cơ bản của chúng tôi).
Để so sánh chi tiết hơn giữa toán tử TE và toán tử FP8 gốc PyTorch, bao gồm nhiều kích thước ma trận, chúng tôi khuyên bạn nên làm theo vấn đề github này.
Mặc dù vẫn còn trong những ngày đầu chưa có cơ hội rõ ràng để cải thiện cả về mức độ bao phủ và hiệu suất API, nhưng chúng tôi đã thành công trong việc chứng minh một số lợi thế tiềm năng của hỗ trợ FP8 gốc của PyTorch. Đầu tiên, khả năng khai báo và vận hành rõ ràng trên các tensor FP8 sẽ cho phép các nhà phát triển tự do hơn nhiều trong việc tùy chỉnh các thuật toán dựa trên FP8. Thứ hai, sự hỗ trợ tích hợp cho quá trình biên dịch JIT tạo điều kiện cho tiềm năng tối ưu hóa thời gian chạy lớn hơn. Ưu điểm thứ ba (không được trình bày ở đây) là khả năng hỗ trợ nhiều loại thiết bị hỗ trợ FP8 hơn. Điều này trái ngược với TE được NVIDIA phát triển và được thiết kế riêng cho GPU của họ.
Kích thước ngày càng tăng của các mô hình AI đòi hỏi các kỹ thuật và thuật toán tiên tiến để giảm dung lượng bộ nhớ và tăng hiệu suất thời gian chạy. Việc sử dụng kiểu dữ liệu FP8 trên các máy gia tốc CTNH chuyên dụng mang lại khả năng đạt được cả hai. Mặc dù trọng tâm của chúng tôi là đào tạo mô hình, nhưng hàm ý không kém phần quan trọng đối với suy luận mô hình, trong đó thời gian cần thiết để tải một mô hình lớn vào bộ nhớ và chạy nó có thể có tác động quyết định đến trải nghiệm của người dùng.
Các kiểu dữ liệu và toán tử FP8 gốc PyTorch mới được xác định mà chúng tôi đã thử nghiệm trong bài đăng này chắc chắn sẽ tạo điều kiện thuận lợi và đẩy nhanh việc áp dụng công nghệ quan trọng này. Chúng tôi mong muốn được xem sự hỗ trợ gốc này phát triển và trưởng thành như thế nào.
Để biết thêm các công cụ và kỹ thuật tối ưu hóa mô hình AI, hãy nhớ xem một số công cụ của chúng tôi bài viết khác.
[ad_2]
Source link