- PR #7154 của llama.cpp đã viết lại phần tính toán SiLU và SoftMax cho CPU của GGML bằng triển khai dựa trên
expf() đã được vector hóa của llamafile, và được merge vào master ngày 17/05/2024
- GGML trước đây dùng bảng tra cứu
short[65536] để tăng tốc, nhưng triển khai mới hướng tới phép tính chính xác hơn, trong khi vẫn giữ sai số làm tròn tệ nhất ở mức 2 ULP trên aarch64 và SSE2+
- Trong bài kiểm thử hiệu năng CPU
SOFT_MAX, SSE2+FMA nhanh hơn 1,5 lần, AVX2+FMA nhanh hơn 1,9 lần, AVX512 nhanh hơn 2,1 lần; kết quả trên AMD Ryzen 9 5950X và M2 Ultra cũng xác nhận nhanh hơn khoảng 1,5 lần so với master
- Thay đổi bao gồm bổ sung
ggml_v_expf(), ggml_v_silu(), tách mã trùng lặp thành ggml_vec_soft_max_f32(), loại bỏ các hàm liên quan đến GGML_SILU_FP16, và điều chỉnh nhánh SiLU có điều kiện cho SSE2 hoặc ARM NEON
- Sau khi merge, kết quả không xác định đã được tái hiện khi chạy server với
>1 slots; về sau nguyên nhân được thu hẹp là do -ffinite-math-only, dẫn tới ràng buộc ở mức build rằng cần dùng -fno-finite-math-only
Mục tiêu thay đổi của PR và trạng thái merge
- PR #7154, với tiêu đề
ggml : rewrite silu and softmax for cpu, viết lại phần tính toán SiLU và SoftMax trong đường xử lý CPU của GGML thuộc llama.cpp
- Thay đổi bắt đầu dưới dạng upstream hàm
expf() đã được vector hóa của llamafile
- PR được merge vào
ggml-org:master ngày 17/05/2024, và commit merge được ghi là 934266c
- Tác giả cho biết cách mới có thể tính SoftMax và SiLU chính xác hơn so với bảng tra cứu
short[65536] mà GGML trước đây dùng để tăng tốc
Độ chính xác và phạm vi hỗ trợ
- Đường xử lý mới dựa trên
expf() hỗ trợ aarch64 và SSE2+, với sai số làm tròn tệ nhất được nêu là 2 ULP
- Trong mô tả ban đầu, các triển khai AVX2 và AVX512 cũng đã được viết, nhưng không được đưa vào vì lợi ích không đủ lớn để đánh đổi độ phức tạp mã so với SSE2+FMA
- Sau đó, dựa trên kết quả benchmark, mã AVX2 và AVX512 cũng được đưa vào
- Một đầu ra kiểm thử riêng nêu
4294967296 numbers tested successfully, kèm so sánh kết quả giữa exp và triển khai của llamafile trên nhiều giá trị đầu vào
Phạm vi thay đổi mã
- Các thay đổi chính được reviewer tóm tắt như sau
- Xóa
#define đã bị comment
- Tách 5 dòng trùng lặp thành
ggml_vec_soft_max_f32()
- Xóa nhiều hàm liên quan đến
GGML_SILU_FP16
- Thêm
ggml_v_expf()
- Thêm
ggml_v_silu()
- Điều chỉnh tiền xử lý để
ggml_vec_silu_f32() dùng hàm khác nhau tùy theo cờ SSE2 hoặc __ARM_NEON
- Số lượng file thay đổi được hiển thị trong metadata của GitHub là 1 file
- PR được gắn nhãn
refactoring và Review Complexity : High; nhãn sau đi kèm mô tả rằng có thể cần kiến thức sâu về LLM hoặc GPU
Benchmark và kết quả hiệu năng
ggerganov xác nhận SOFT_MAX trên AMD Ryzen 9 5950X và M2 Ultra nhanh hơn khoảng 1,5 lần so với master
- Lệnh kiểm thử được sử dụng như sau
make -j tests && ./tests/test-backend-ops -o SOFT_MAX -b CPU perf
- Sau đó, tác giả cho biết với cùng lệnh, lợi thế hiệu năng tăng lên như sau
- SSE2+FMA: 1,5 lần
- AVX2+FMA: 1,9 lần
- AVX512: 2,1 lần
- Một script phát triển riêng đưa ra các số liệu sau
run_expf(): 2.98601 ns
run_llamafile_expf_sse2(): 1.35154 ns
run_llamafile_expf_avx2(): 1.16659 ns
run_llamafile_expf_avx512(): 1.18844 ns
- Benchmark
llama.cpp server trên GitHub Actions ghi nhận 543 iterations với cấu hình phi-2 q4_0 trên Standard_NC4as_T4_v3
- Người dùng đồng thời: 8
- duration: 10 phút
- Trung bình request HTTP: 8626.19ms
- p95: 21696.44ms
- Trung bình xử lý prompt: 94.59 tk/s
- Trung bình sinh token: 33.43 tk/s
Thảo luận tối ưu hóa AVX512
chriselrod đề xuất dùng vscalefps trên AVX512
vscalefps tính zmm0 = zmm1 * 2^{zmm2}
- Được cho là có thể xử lý overflow và underflow phù hợp, nhờ đó loại bỏ checks và blends
- Ví dụ triển khai bằng Julia và vòng lặp assembly đã được chia sẻ; nếu kiểm thử đúng, sai số tối đa tại
x=47.483456f là dưới 1 ULP
- Cách tiếp cận
vscalefps không dùng lookup table; với triển khai Float64/double, nó dùng lookup table 16 phần tử thông qua vpermi2pd
- Sau đó, liên kết triển khai C++ cũng được chia sẻ
- ExpAVX512
- Mã nguồn nằm trong
include/ExpAVX512.hpp
- README có benchmark, nhưng cho biết không benchmark so sánh với các triển khai khác
Vấn đề không xác định sau khi merge
- Sau khi merge, một trường hợp tái hiện được báo cáo: khi dùng
>1 slots trên server, kết quả trở nên không xác định
- Quy trình tái hiện tối thiểu như sau
make clean && make server
./server -m models/opt/llama_2-7b-q4_0.gguf --parallel 2 --threads 1
- Request chạy ở shell khác như sau
curl --request POST --url http://localhost:8080/completion --header "Content-Type: application/json" --data '{"prompt": "", "n_predict":10, "n_probs": 2, "temperature": -1}' | python3 -m json.tool
- Token probabilities của token cuối cùng luân phiên giữa hai giá trị sau mỗi lần gọi
curl; nếu dùng 4 slots thì luân phiên giữa bốn giá trị khả dĩ
-ffinite-math-only và ràng buộc build
- Các commit liên quan sau đó tham chiếu việc thu hẹp nguyên nhân vấn đề về
-ffinite-math-only
- Vấn đề này được ghi nhận là có thể do SiLU trả về NaN hoặc giá trị rác khác thay vì flush các giá trị nhỏ về 0
- Bản fix kiểm tra xem
-fno-finite-math-only đã được đặt hay chưa, và ép buộc kiểm tra rằng chế độ biên dịch không được là finite math mode
- Thông báo lỗi hướng dẫn rằng một số routine của GGML cần non-finite math arithmetic, và cần truyền
-fno-finite-math-only cho compiler
- Sau đó, người dùng chia sẻ kinh nghiệm rằng
-Ofast hoặc -ffast-math có thể bao gồm -ffinite-math-only và làm hỏng build
- Có báo cáo rằng tới GCC 13.2 vẫn có thể dùng
-Ofast, nhưng từ GCC 14 thì kết quả trở thành rác
- Trong một số kiểm thử, ngoài
-fno-finite-math-only còn cần cả -fmath-errno
- Nhiều commit tiếp theo ở các repository khác tham chiếu việc loại bỏ
-ffast-math hoặc chỉ định rõ -fno-finite-math-only để giải quyết lỗi biên dịch ggml
1 bình luận
Ý kiến trên Hacker News
Khoảng 20 năm trước, khi lập trình cho bộ xử lý tín hiệu radar Hughes, tôi cần tính e^x trong phạm vi 0 < x < 1
Bộ xử lý đó có phép nhân, nên tôi tạo 4 bảng e^x, mỗi bảng gồm 256 giá trị khả dĩ cho từng khối 8 bit trong 4 khối 8 bit của một word 32 bit, rồi nhân chúng lại để ra giá trị cuối cùng
Nó nhanh hơn routine e^x tốt nhất trước đó khoảng 5 lần, và dù giờ đã lỗi thời, đó là một cỗ máy thú vị từng xử lý tín hiệu radar nhanh hơn nhiều bộ xử lý trên danh nghĩa là nhanh hơn trong một thời gian
Nói nghiêm ngặt thì a có dạng như
high byte << 24, nên bảng e^a sẽ là ánh xạ a => e^(a<<24), và các byte khác cũng được xử lý tương tựTôi tò mò những cải tiến silu và softmax như thế này ảnh hưởng đến tốc độ suy luận LLM tổng thể đến mức nào
Nếu tôi sai thì mong được sửa, nhưng vì phần lớn thời gian dùng cho nhân ma trận nên có lẽ tác động của thay đổi này sẽ nhỏ
Hơi lạc đề một chút, nhưng khi lướt qua tôi đã nghĩ “cái này trông như một tối ưu hóa khá điên rồ. Mã phức tạp và đã được nhiều người xem rồi”, rồi nhìn người đóng góp thì thấy “đúng là jart. Những lời giải điên rồ mà hay ho lúc nào cũng là jart”
Cũng như nhiều thứ trong mảng đó, nỗi đau này phần nào là tự mình gây ra
Tôi biết có các thư viện C++ cho phép cú pháp SIMD kiểu C# và hardware intrinsic, nhưng nhược điểm là khó tra cứu trực tiếp mnemonic trong tài liệu tập lệnh
Tôi không có ý hạ thấp tầm quan trọng của việc làm ở đây, chỉ muốn nói rằng có lẽ nó đã có thể dễ tiếp cận hơn với nhóm độc giả rộng hơn. Tuy vậy tôi chưa định đưa ra đề xuất mà hẳn mọi người ở đây sẽ thấy kỳ quặc là viết lại backend suy luận bằng C#
adapted from arm limited optimized routinecơ à, cuối cùng cũng là đứng trên vai người khổng lồNhớ đến câu nói nổi tiếng của một giáo sư: “cái hằng số mà mọi người đều phớt lờ ấy, trong kỹ thuật nó có thể ăn sạch cả cái đầu của bạn”
Nói là thay thế bảng tra cứu
short[65536], nhưng ngay từ đầu đó chẳng phải là một lựa chọn hơi chậm chạp sao?Tức là đặt một bảng tra cứu bằng cả kích thước L1 cache; có phải do xác suất tình cờ khá khớp nên nó lại hoạt động tốt bất ngờ không?
Thổi bay L1 cache cũng không quan trọng lắm, và dữ liệu bị đẩy ra để nhường chỗ cho LUT đằng nào cũng gần như không được tái sử dụng
Tải máy học nhìn chung là streaming load, đọc tuyến tính toàn bộ dataset ở mỗi vòng lặp
Theo kinh nghiệm hạn hẹp của tôi, bạn có thể tính toán realtime khá nhiều trước khi nó chậm hơn lookup
Trong llama.cpp, đây là chuyện dành cho CPU
Giờ họ đang upstream nó vào dự án llama.cpp
Hiện cũng có những cải tiến hiệu năng khác chỉ có trong llamafile, ví dụ công việc của Kawrakow giúp K quants nhanh hơn nhiều
Có thể hơi lạc đề, nhưng có ai biết những thứ như ggml so với các runtime như tensorflow lite, onnxruntime thì thế nào không?
Nói ngắn gọn: với LLM thì dùng llama.cpp là đúng, và với GGML là phụ thuộc cốt lõi thì cũng chạy được whisper
Ngoài ra thì dùng ONNX
TF giống như Apple của giới máy học: nếu bạn hoàn toàn bị khóa trong hệ sinh thái Google ML thì rất tuyệt, nhưng bên ngoài đó thì thực tế gần như đã chết. Một tỷ lệ vô lý các mô hình HF, khoảng 94%, là PyTorch
So sánh hiệu năng suy luận trực tiếp đáng làm có lẽ là Whisper của ONNX với GGML, nhưng từng có người chạy thư viện llama.cpp của tôi cùng Whisper và không báo cáo khác biệt hiệu năng đáng kể nào
Ở thời điểm hiện tại, với suy luận không batching trên thiết bị CUDA, gguf/llama.cpp có phải là giải pháp hiệu năng tốt hơn không, hay exllamav2+flashattention vẫn chiếm ưu thế?
Có những khác biệt quan trọng hơn, như KV cache 4 bit
LUT cũng có thể vector hóa
https://www.intel.com/content/www/us/en/docs/intrinsics-guid...
Trước đây tôi cũng từng viết về những thứ có thể làm với LUT https://darkcephas.blogspot.com/2018/10/validating-utf8-stri...
expthì cũng chỉ cần khoảng 10–20 FMA tùy độ chính xác mong muốngather hay permutation khó cạnh tranh với tính toán thuần túy
Cùng mạch đó, cũng có tanh nhanh hơn https://github.com/microsoft/onnxruntime/pull/20612
Nhưng mục tiêu là gì nhỉ? Là làm cho phép xấp xỉ GeLU đó nhanh hơn sao?
Nếu quay lại
erff()thì có lẽ sẽ nhanh hơn nhiềuCái này có giúp cho trường hợp dùng offload một phần GPU của gguf không?
Phía CPU cũng nhanh hơn à?