- Các LLM cỡ lớn khó triển khai do giới hạn bộ nhớ GPU, nhưng DFloat11 có thể giảm trọng số BFloat16 xuống khoảng 70% kích thước mà vẫn giữ đầu ra giống hệt từng bit so với bản gốc
- Điểm cốt lõi là exponent 8 bit của BFloat16 trên thực tế chỉ chứa khoảng 2,6 bit thông tin; DFloat11 giữ nguyên sign và mantissa, chỉ nén exponent bằng Huffman coding
- Vì mã hóa độ dài động dễ trở thành nút thắt trên GPU, DFloat11 dùng LUT phân cấp, kernel hai giai đoạn và giải nén theo từng transformer block để phù hợp với suy luận song song
- Trên Llama 3.3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5..., phương pháp này cho thấy giảm khoảng 30% kích thước mô hình nhưng vẫn bảo toàn đầu ra gốc
- So với phương án CPU offload, throughput tạo token cao hơn 2,3–46,2 lần, đồng thời cho phép suy luận không mất dữ liệu với Llama 3.1 405B dung lượng 810GB trên một node GPU 8×80GB
Nút thắt bộ nhớ mà DFloat11 nhắm tới
- Các foundation model như LLM và diffusion model đang tăng kích thước rất nhanh, khiến việc triển khai hiệu quả trên phần cứng có giới hạn bộ nhớ trở nên khó khăn
- Llama 3.1 405B lưu 405 tỷ tham số ở định dạng BFloat16, cần khoảng 810GB bộ nhớ để suy luận toàn bộ
- Mức này vượt quá dung lượng của một máy chủ GPU cao cấp phổ biến với 8 GPU 80GB
- Khi phải dùng nhiều node, chi phí triển khai và rào cản tiếp cận đều tăng lên
- DFloat11 đặt mục tiêu nén mô hình BFloat16 xuống còn khoảng 70% kích thước ban đầu nhưng vẫn bảo toàn 100% độ chính xác trên mọi tác vụ
Cách tiếp cận khác với lượng tử hóa mất dữ liệu
- Lượng tử hóa là cách nén mất dữ liệu bằng cách hạ độ chính xác của trọng số để giảm bộ nhớ
- Nó có thể giảm mạnh mức dùng bộ nhớ và tăng tốc suy luận, nhưng sẽ tạo ra sai số xấp xỉ
- Mức suy giảm độ chính xác khó dự đoán vì phụ thuộc vào mô hình nền, phương pháp lượng tử hóa, benchmark đánh giá và bit-width mục tiêu
- Ví dụ, khi áp dụng 8-bit SmoothQuant cho DeepSeek-R1-Distill-Qwen-1.5B, độ chính xác trung bình trên các reasoning task giảm 9,09%
- Ngay cả khi chỉ số độ chính xác tổng thể gần giống nhau, mô hình đã lượng tử hóa vẫn có thể cho hành vi trả lời khác với bản gốc
- Dutta et al. quan sát hiện tượng flips, khi câu trả lời đúng thành sai hoặc sai thành đúng
- Qwen2-1.5B được lượng tử hóa bằng W8A16 GPTQ chỉ giảm 0,3% độ chính xác GSM8K 8-shot, nhưng trạng thái đúng/sai thay đổi ở 6,37% câu trả lời
- Trong các lĩnh vực như tài chính hay y tế, thay đổi đầu ra từ mô hình lượng tử hóa có thể khiến việc đáp ứng yêu cầu về quy định và độ tin cậy trở nên khó khăn
- Các phương pháp nén mô hình không mất dữ liệu trước đây chủ yếu tập trung vào lưu checkpoint hiệu quả hơn, rút ngắn thời gian tải từ model hub, hoặc phần cứng chuyên dụng như FPGA, nên ít lợi ích cho suy luận GPU thông thường
Dư địa nén còn lại trong exponent của BFloat16
- BFloat16 chia 16 bit thành 1 bit sign, 8 bit exponent, 7 bit mantissa
- Điểm xuất phát của DFloat11 là phân tích Shannon entropy theo từng thành phần BFloat16 của trọng số LLM
- Entropy của sign và mantissa gần với bit-width tương ứng nên dư địa nén khá hạn chế
- Trong khi đó exponent được cấp phát 8 bit nhưng entropy chỉ khoảng 2,6 bit
- Phân bố giá trị exponent rất mất cân bằng
- Trong 256 giá trị 8 bit có thể có, chỉ khoảng 40 giá trị thực sự được dùng
- Các giá trị còn lại không xuất hiện
- Tần suất theo thứ hạng cũng giảm rất nhanh
- Nhờ entropy thấp, exponent trở thành mục tiêu nén không mất dữ liệu, với khoảng 5,4 bit thông tin exponent có thể được nén
Định dạng DFloat11
- DFloat11 hay DF11 là định dạng số thực độ dài động nén bằng entropy coding chỉ trên phần exponent của trọng số BFloat16
- Cây Huffman được xây dựng dựa trên phân bố exponent trong trọng số mô hình
- Các giá trị exponent xuất hiện thường xuyên sẽ được gán mã ngắn
- Các giá trị hiếm hơn sẽ có mã dài hơn
- Sign và mantissa được giữ nguyên như bản gốc
- Exponent được lưu ở dạng bit-packed trong mảng byte
EncodedExponent - Sign và mantissa được lưu riêng trong mảng byte
PackedSignMantissa
- Exponent được lưu ở dạng bit-packed trong mảng byte
- Kết quả là trọng số BFloat16 giảm xuống trung bình còn khoảng 11 bit, và có thể khôi phục lại đúng giá trị BFloat16 ban đầu mà không mất độ chính xác
Giải nén tối ưu cho suy luận GPU
- Trọng số được entropy-code dùng mã độ dài động nên không thể đưa trực tiếp vào phép nhân ma trận
- Cần giải nén weight matrix cần dùng ngay về BFloat16 gốc
- Sau khi nhân ma trận xong, ma trận BFloat16 sẽ bị loại bỏ ngay để tiết kiệm bộ nhớ GPU
- Giải mã Huffman thông thường phải duyệt cây tuần tự từng bit nên không phù hợp với cấu trúc song song của GPU
- Nếu một thread đơn lẻ đảm nhiệm giải nén, mức sử dụng GPU sẽ thấp và latency tăng cao
Giải mã dựa trên LUT phân cấp
- DFloat11 dùng giải mã dựa trên lookup table thay cho việc duyệt cây Huffman
- Nếu độ dài mã Huffman tối đa là L, một LUT đơn sẽ cần kích thước
2^L- Với LLM, L thường nằm trong khoảng 24–32
- LUT cỡ
2^32phần tử rất khó đưa vào GPU SRAM
- Để tránh điều này, cây Huffman được chia thành các subtree không chồng lấp có chiều cao 8, và mỗi subtree được biểu diễn bằng một compact LUT 256 phần tử
- Trong LUT phân cấp, một số phần tử phải đóng vai trò tham chiếu tới LUT cấp dưới
- DFloat11 tận dụng thực tế là nhiều giá trị exponent không được dùng trong LLM
- Các giá trị exponent không dùng trong khoảng 240–255 được tái sử dụng làm con trỏ nội bộ
- Các giá trị này biểu diễn độ lớn rất lớn ở mức ±2^113 đến ±2^128 nên không xuất hiện trong trọng số LLM
- Trong thí nghiệm, số compact LUT k của cây Huffman exponent BFloat16 nằm trong khoảng 4–8
- Cùng với
CodeLengths, tổng bộ nhớ dùng tối đa là(8 + 1) × 256 bytes - Kích thước này đủ nhỏ để nằm trong SRAM và cho phép tra cứu lặp lại nhanh
- Cùng với
Kernel GPU hai giai đoạn và metadata bổ trợ
- Mỗi GPU thread phụ trách một đoạn n byte liên tiếp của encoded exponent
- Trong thí nghiệm, dùng n = 8
- Thread sẽ giải mã các Huffman code bắt đầu trong chính đoạn của nó
- Mã độ dài động tạo ra hai vấn đề
- Không xác định rõ vị trí bit bắt đầu chính xác cho mỗi thread
- Ngoại trừ thread đầu tiên, khó biết output index của phần tử đã giải mã
- Vấn đề đầu được giải quyết bằng mảng Gaps
- Gaps có một phần tử cho mỗi thread
- Mỗi phần tử biểu thị bit offset của Huffman code hợp lệ đầu tiên tính từ byte bắt đầu của thread
- Vì độ dài mã tối đa là 32 bit, offset nằm trong khoảng
[0, 31]và được lưu bằng 5 bit
- Vấn đề vị trí đầu ra được xử lý bằng cách chỉ lưu vị trí theo từng thread block để giảm overhead bộ nhớ
- Nếu lưu vị trí đầu ra 32 bit cho mọi thread, overhead sẽ lớn vì mỗi weight matrix có thể cần tới hàng chục nghìn thread
- DFloat11 chỉ lưu vị trí đầu ra của phần tử đầu tiên trong mỗi thread block
- Kernel hoạt động theo hai giai đoạn
- Ở giai đoạn 1, mỗi thread giải mã đoạn của mình chỉ để đếm số phần tử, chưa ghi xuống HBM
- Các thread trong block dùng Blelloch algorithm để tính prefix sum và suy ra vị trí đầu ra cho từng thread
- Ở giai đoạn 2, cùng đoạn đó được giải mã lại và các giá trị giải mã được ghi vào SRAM write buffer theo vị trí đã tính
- Encoded exponent được nạp vào SRAM trước pass đầu tiên để tránh truy cập global memory lặp lại
- Sau khi mọi exponent đã giải mã được ghi vào SRAM, hệ thống chỉ thực hiện một lần coalesced write xuống HBM
Giải nén theo từng transformer block
- Giải nén một weight matrix đơn lẻ có thể quá nhỏ để tận dụng hết tài nguyên GPU
- Khi kích thước ma trận tăng, throughput giải nén của DFloat11 cũng được cải thiện
- Có thể giải nén nhiều ma trận cùng lúc để tăng throughput và che giấu latency
- Tất cả weight matrix DFloat11 trong một transformer block được giải nén như một batch duy nhất
- Việc batched decompression này diễn ra ngay trước forward pass của transformer block đó
- Token embedding và language modeling head của LLM cũng được nén
- Các ma trận này đủ lớn để bão hòa tài nguyên GPU nên không cần batching riêng
Kết quả đánh giá và hiệu quả thực tế
- DFloat11 được đánh giá trên Llama 3, Qwen 3, Mistral 3, DeepSeek R1 Distilled, FLUX.1, Stable Diffusion 3.5 và các diffusion transformer khác
- Kết quả cho thấy giảm khoảng 30% kích thước mô hình đồng thời bảo toàn hoàn toàn đầu ra gốc
- Đầu ra giống hệt bản gốc ở mức bit-for-bit
- Vì không phải nén mất dữ liệu, phương pháp này giữ nguyên độ chính xác của trọng số BFloat16 ban đầu
- So với phương án offload một phần mô hình không nén sang CPU để đáp ứng giới hạn bộ nhớ, DFloat11 đạt throughput tạo token cao hơn 2,3–46,2 lần
- Trong cùng ngân sách bộ nhớ GPU cố định, nó cho phép generation length dài hơn 5,7–14,9 lần so với mô hình không nén
- Llama 3.1 405B có dung lượng 810GB, nhưng với DFloat11 có thể suy luận không mất dữ liệu trên một node dùng 8 GPU A100 80GB
- Kết quả này cho thấy có thể giảm một nửa yêu cầu phần cứng để chạy Llama-3.1-405B mà vẫn suy luận không mất accuracy
1 bình luận
Ý kiến trên Hacker News
Đây chỉ là hệ quả của việc dải động của bfloat16 rất rộng, nhưng trên thực tế không dùng hết dải đó
Mọi người thích các siêu tham số trông như 0,01 hơn là 10^10, nhưng có thể dùng cùng độ chính xác tương đối cho từng số mũ. Ngay cả khi nhân toàn bộ siêu tham số của mạng, trọng số khởi tạo, dữ liệu huấn luyện, v.v. lên 10^6 lần, nhìn chung nó vẫn hoạt động tương tự vì hầu như không dùng đến vùng trên cùng. Tuy nhiên một số hàm đặc biệt có thể là ngoại lệ
Entropy điển hình của các giá trị bfloat16 thấy trong trọng số và activation vào khoảng 10–12 bit, và thực tế chỉ dùng khoảng 65–75% dải giá trị. Bit dấu và bit phần định trị gần như là nhiễu khó nén
Tính chất này đã được khai thác nhiều lần trong cả điện toán hiệu năng cao cổ điển lẫn AI. Ví dụ như các công trình nén không mất dữ liệu của phòng thí nghiệm Martin Burtscher (https://userweb.cs.txstate.edu/~burtscher/), fpzip của LLNL (https://computing.llnl.gov/projects/fpzip), và thư viện dietgpu của tôi làm năm 2021 (https://github.com/facebookresearch/dietgpu). Trên các cụm GPU lớn, chúng tôi nén không mất dữ liệu toàn bộ dữ liệu trước khi truyền, chẳng hạn gradient hoặc trọng số từ bản sao lưu, rồi giải nén khi nhận, nhờ đó giảm khoảng 10% tổng thời gian huấn luyện theo đồng hồ thực; vì là không mất dữ liệu nên kết quả tính toán vẫn như trước
Ngoài ra rANS hiệu quả hơn Huffman coding trên các tập lệnh kiểu SIMD và cũng dễ triển khai hơn. Trong DFloat11 cũng phải giải nén trước khi thực hiện phép toán số học, nên có thể giảm tổn thất về độ trễ và thông lượng
Lượng tử hóa là công cụ cốt lõi với người chạy LLM cục bộ, và thường RAM là nút thắt. Tôi cũng tò mò liệu có kiểu nén không mất dữ liệu nào tốt hơn cho trọng số BF16 không
DFloat11 có vẻ có thể chèn vào quy trình lượng tử hóa hiện có tương đối dễ, nhưng có vẻ bài báo khá hoài nghi, nên tôi muốn hiểu mình đã bỏ lỡ điểm nào
Trong một tầng mạng nơ-ron, đầu vào được nhân với trọng số rồi cộng lại, sau đó đầu ra đó trở thành đầu vào của tầng tiếp theo, và quá trình này có thể lặp lại hơn trăm lần. Khi tới tầng đầu ra cuối cùng, hệ số 10^6 đó sẽ được áp dụng nhiều lần và phình ra như quả cầu tuyết đến mức 10^600
Những trường hợp DFloat11 có số token mỗi giây cao hơn chỉ là khi so với suy luận có offload một số tầng sang CPU
Đây là đánh đổi không gian–tốc độ điển hình trong khoa học máy tính, không có bữa trưa miễn phí
Dù vậy chi phí cắt bỏ rồi lấp bằng 0 thì nhỏ
Điều nổi bật nhất là hàm ý thực tiễn. Việc có thể suy luận không mất dữ liệu một mô hình 405B tham số trên một node đơn với 8×80GB GPU là khá đáng kinh ngạc
Đây có thể là một yếu tố mở khóa lớn cho cả các phòng thí nghiệm lẫn startup muốn chạy mô hình frontier mà không cần chi phí hạ tầng khổng lồ
Thật biết ơn vì được sống trong một thời đại thú vị như thế này. Mỗi lần mở HN lại thấy liên tục có tin mới về machine learning và mô hình transformer
Tôi cần đọc sâu hơn, nhưng tò mò liệu llama.cpp dùng một dạng custom kernel cùng với cuBLAS, hay chỉ là tận dụng tốt các kernel cuBLAS
2 tuần? Hai tháng? Hai ngày? 2 phút?
Đôi khi tất cả đều đúng. Đúng là một thời đại thú vị
Khi cuộc chiến định dạng trọng số này ngã ngũ, phần cứng có thể được tạo ra để hỗ trợ nó. Dù định dạng trọng số tối ưu hợp lý cuối cùng được xác định là gì, ta sẽ cần phần cứng nhân ma trận được tối ưu cho định dạng đó
Trong các trường hợp sử dụng agent thực tế, thường rất khó cân bằng giữa chất lượng, chi phí và hiệu năng. Kỹ thuật này có thể giúp tránh các đánh đổi do kỹ thuật lượng tử hóa tạo ra, bao gồm cả những kết quả khó dự đoán khi cố tối ưu chi phí cho agent
Nếu DFloat11 giúp nhét vừa vào GPU rẻ hơn thì trong một số trường hợp mức tiết kiệm chi phí có thể đáng kể. Tôi làm việc tại xmad.ai
So với phương án thay thế là offload một phần mô hình chưa nén sang CPU do hạn chế bộ nhớ, DFloat11 được cho là có thông lượng sinh token cao hơn 1,9–38,8 lần. Với ngân sách bộ nhớ GPU cố định, nó cho phép độ dài ngữ cảnh dài hơn 5,3–13,17 lần so với mô hình chưa nén
Chỉ riêng độ dài ngữ cảnh đã khiến nó trông có vẻ hữu dụng ngay cả khi mô hình vừa trong bộ nhớ, nhưng theo hiểu biết cơ bản rằng LLM thường bị giới hạn bởi băng thông bộ nhớ, tôi tò mò liệu số token mỗi giây có được cải thiện ngay cả khi toàn bộ mô hình nằm trên GPU hay không
Trong bài báo, họ tuyên bố dưới 200GB/s trên A100, và theo benchmark thì ở batch size 1 có vẻ chậm hơn 1,5–4 lần tùy GPU và mô hình. Tất nhiên nếu batch size đủ lớn thì phần lớn overhead này sẽ biến mất
Các codec không mất dữ liệu khác có thể đạt 600GB/s trên cùng phần cứng, nên có vẻ vẫn còn dư địa cải thiện. Nhưng băng thông bộ nhớ thô của A100 là 1,6TB/s
Điều này có nghĩa là có thể giảm 30% yêu cầu bộ nhớ của LLM chưa lượng tử hóa một cách phổ quát không? Nếu đúng thì khá lớn
Nếu có thể cộng thêm vào lượng tử hóa hiện có thì chắc chắn thú vị, nhưng K-quantization vốn đã dùng độ chính xác khác nhau theo từng lớp tùy tác động tổng thể lên perplexity. Ví dụ Q6 trộn 4-bit và 8-bit, điều này tương tự chỉ số entropy được dùng ở đây. Nếu tính cả imatrix đã hiệu chỉnh, về mặt khái niệm nó nén mạnh hơn theo cách tương tự FFT
Cái này có khác ZipNN không? https://arxiv.org/pdf/2411.05239
Tôi thấy có nhắc đến, nhưng không hiểu nó dựa trên cái đó, hay khác, hoặc tốt hơn
Nhưng các bộ nén kiểu LZ có vẻ không có nhiều ý nghĩa với trọng số mạng nơ-ron. Chúng không dư thừa cao như hầu hết dữ liệu văn bản có nhiều lặp lại, và nếu dữ liệu không rất thưa thì có thể cũng không có đủ mẫu lặp để bù cho overhead của từ điển
Nếu thêm bộ nén kiểu LZ và đưa nó vào đường xử lý trọng yếu của suy luận, quá trình giải nén sẽ chậm hơn nhiều. Tốt nhất là hợp nhất giải nén với kernel tính toán. Ví dụ có thể làm nó giống GEMM giải nén từng tile trước khi thực hiện phép toán số học, và routine giải nén càng đơn giản thì việc này càng dễ
Khá tuyệt khi thấy mọi thứ đang chuyển động nhanh đến mức nào. Cảm giác như mỗi tuần lại có một kỹ thuật tối ưu hiệu suất mới hoặc một nâng cấp phần cứng mới
Rất dễ bị cuốn sự chú ý vào những cải tiến kiểu này
Có thể chạy cái này trên các mô hình mới không? Nếu tôi không hiểu nhầm thì code có vẻ chỉ dành cho suy luận