- llama.cpp PR #7154 menulis ulang perhitungan SiLU dan SoftMax untuk CPU di GGML berdasarkan implementasi
expf() tervektorisasi dari llamafile, dan digabungkan ke master pada 17 Mei 2024
- GGML sebelumnya menggunakan lookup table
short[65536] demi kecepatan, tetapi implementasi baru menargetkan perhitungan yang lebih akurat sambil mempertahankan galat pembulatan terburuk 2 ULP pada aarch64 dan SSE2+
- Dalam uji performa CPU
SOFT_MAX, SSE2+FMA menjadi 1,5x lebih cepat, AVX2+FMA 1,9x, dan AVX512 2,1x; hasil sekitar 1,5x lebih cepat dibanding master juga dikonfirmasi pada AMD Ryzen 9 5950X dan M2 Ultra
- Perubahan mencakup penambahan
ggml_v_expf() dan ggml_v_silu(), ekstraksi kode duplikat ke ggml_vec_soft_max_f32(), penghapusan fungsi terkait GGML_SILU_FP16, serta penyesuaian jalur SiLU bersyarat untuk SSE2 atau ARM NEON
- Setelah merge, hasil nondeterministik dapat direproduksi saat server berjalan dengan
>1 slots, lalu penyebabnya dipersempit ke -ffinite-math-only, yang berujung pada batasan build bahwa -fno-finite-math-only diperlukan
Tujuan perubahan PR dan status merge
- PR #7154 berjudul
ggml : rewrite silu and softmax for cpu, yang menulis ulang perhitungan SiLU dan SoftMax pada jalur CPU GGML di llama.cpp
- Perubahan dimulai sebagai upaya meng-upstream fungsi
expf() tervektorisasi dari llamafile
- PR ini digabungkan ke
ggml-org:master pada 17 Mei 2024, dan commit merge ditandai sebagai 934266c
- Penulis menyatakan bahwa pendekatan baru dapat menghitung SoftMax dan SiLU dengan lebih akurat dibanding lookup table
short[65536] yang sebelumnya digunakan GGML demi kecepatan
Akurasi dan cakupan dukungan
- Jalur baru berbasis
expf() mendukung aarch64 dan SSE2+, dengan galat pembulatan terburuk sebesar 2 ULP
- Pada penjelasan awal, implementasi AVX2 dan AVX512 juga telah dibuat, tetapi disebut belum dimasukkan karena manfaatnya dianggap belum cukup besar dibanding kompleksitas kode relatif terhadap SSE2+FMA
- Setelah itu, berdasarkan hasil benchmark, kode AVX2 dan AVX512 juga dimasukkan
- Output pengujian terpisah menampilkan
4294967296 numbers tested successfully, disertai perbandingan hasil exp dan implementasi llamafile untuk berbagai nilai input
Cakupan perubahan kode
- Perubahan utama yang dirangkum reviewer adalah sebagai berikut
- menghapus
#define yang dikomentari
- mengekstrak 5 baris duplikat ke
ggml_vec_soft_max_f32()
- menghapus sejumlah fungsi terkait
GGML_SILU_FP16
- menambahkan
ggml_v_expf()
- menambahkan
ggml_v_silu()
- menyesuaikan preprocessor agar
ggml_vec_silu_f32() menggunakan fungsi berbeda sesuai flag SSE2 atau __ARM_NEON
- Jumlah file yang diubah ditampilkan sebagai 1 pada metadata GitHub
- PR ini diberi label
refactoring dan Review Complexity : High; label kedua disertai penjelasan bahwa perubahan ini dapat memerlukan pengetahuan mendalam tentang LLM atau GPU
Benchmark dan hasil performa
ggerganov mengonfirmasi bahwa SOFT_MAX pada AMD Ryzen 9 5950X dan M2 Ultra sekitar 1,5x lebih cepat daripada master
- Perintah pengujian yang digunakan adalah sebagai berikut
make -j tests && ./tests/test-backend-ops -o SOFT_MAX -b CPU perf
- Setelah itu, penulis menyatakan bahwa pada perintah yang sama keuntungan performanya meningkat sebagai berikut
- SSE2+FMA: 1,5x
- AVX2+FMA: 1,9x
- AVX512: 2,1x
- Dalam skrip pengembangan terpisah, angka berikut dibagikan
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 GitHub Actions untuk
llama.cpp server mencatat 543 iterations pada Standard_NC4as_T4_v3 dengan konfigurasi phi-2 q4_0
- pengguna bersamaan: 8
- duration: 10 menit
- rata-rata request HTTP: 8626.19ms
- p95: 21696.44ms
- rata-rata pemrosesan prompt: 94.59 tk/s
- rata-rata generasi token: 33.43 tk/s
Diskusi optimisasi AVX512
chriselrod mengusulkan penggunaan vscalefps pada AVX512
vscalefps menghitung zmm0 = zmm1 * 2^{zmm2}
- disebut dapat menangani overflow dan underflow dengan tepat sehingga checks dan blends bisa dihapus
- Contoh implementasi Julia dan loop assembly dibagikan, dan jika pengujiannya benar maka galat maksimum pada
x=47.483456f disebut kurang dari 1 ULP
- Pendekatan
vscalefps tidak menggunakan lookup table, dan dijelaskan bahwa implementasi Float64/double menggunakan lookup table 16 elemen melalui vpermi2pd
- Tautan implementasi C++ juga dibagikan
- ExpAVX512
- sumbernya ada di
include/ExpAVX512.hpp
- README menyertakan benchmark, tetapi disebut tidak melakukan benchmark perbandingan dengan implementasi lain
Masalah nondeterminisme setelah merge
- Setelah merge, dilaporkan ada kasus reproduksi hasil nondeterministik di server saat menggunakan
>1 slots
- Langkah reproduksi minimalnya adalah sebagai berikut
make clean && make server
./server -m models/opt/llama_2-7b-q4_0.gguf --parallel 2 --threads 1
- Request yang dijalankan dari shell lain adalah sebagai berikut
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
- Dikatakan bahwa probabilitas token untuk token terakhir berputar antara dua nilai pada setiap pemanggilan
curl, dan saat memakai 4 slots, nilainya berputar di antara empat kemungkinan nilai
-ffinite-math-only dan batasan build
- Commit terkait setelahnya merujuk pada hasil penyelidikan yang mempersempit penyebab masalah ke
-ffinite-math-only
- Masalah tersebut dicatat diduga terjadi karena SiLU mengembalikan NaN atau nilai garbage lain alih-alih mem-flush nilai kecil menjadi 0
- Perbaikannya memeriksa apakah
-fno-finite-math-only telah disetel, dan memaksa pemeriksaan bahwa mode kompilasi tidak berada dalam finite math mode
- Pesan error menjelaskan bahwa beberapa rutin GGML memerlukan non-finite math arithmetic dan meminta pengguna meneruskan
-fno-finite-math-only ke compiler
- Setelah itu, pengguna lain membagikan pengalaman bahwa
-Ofast atau -ffast-math dapat merusak build karena menyertakan -ffinite-math-only
- dilaporkan bahwa hingga GCC 13.2,
-Ofast masih bisa dipakai, tetapi mulai GCC 14 hasilnya menjadi garbage
- pada beberapa pengujian, selain
-fno-finite-math-only, -fmath-errno juga diperlukan
- beberapa commit lanjutan di berbagai repositori dirujuk setelah kesalahan kompilasi ggml diselesaikan dengan menghapus
-ffast-math atau secara eksplisit menambahkan -fno-finite-math-only
1 komentar
Komentar Hacker News
Sekitar 20 tahun lalu, saat memprogram untuk prosesor sinyal radar Hughes, saya perlu menghitung e^x untuk rentang 0 < x < 1
Prosesor itu punya operasi perkalian, jadi saya membuat 4 tabel e^x yang masing-masing berisi 256 nilai yang mungkin untuk tiap blok 8-bit dari sebuah word 32-bit, lalu mengalikannya untuk mendapatkan nilai akhirnya
Hasilnya sekitar 5x lebih cepat daripada rutin e^x terbaik sebelumnya, dan meski sekarang sudah kuno, itu mesin yang menyenangkan karena sempat memproses sinyal radar lebih cepat daripada prosesor-prosesor yang secara nominal jauh lebih cepat
Secara ketat, a berbentuk seperti
high byte << 24, jadi tabel e^a memetakan a => e^(a<<24), dan byte-byte lainnya diperlakukan serupaSaya penasaran seberapa besar peningkatan silu dan softmax seperti ini memengaruhi keseluruhan kecepatan inferensi LLM
Tolong koreksi kalau saya salah, tetapi karena sebagian besar waktu dipakai untuk perkalian matriks, rasanya efek perubahan ini akan kecil
Sedikit menyimpang, tetapi saat membaca sekilas saya berpikir, “ini optimisasi yang cukup gila. Rumit, dan ini kode yang sudah dilihat banyak orang,” lalu setelah melihat kontributornya saya langsung berpikir, “oh, tentu saja jart. Solusi yang gila tapi bagus memang selalu dari jart”
Seperti banyak hal di ranah itu, rasa sakit ini cukup banyak yang dibuat sendiri
Setahu saya ada juga pustaka C++ yang memungkinkan sintaks SIMD dan hardware intrinsic ala C#, tetapi kekurangannya adalah mnemonic di dokumentasi set instruksi jadi lebih sulit dicari langsung
Bukan untuk mengecilkan pentingnya pekerjaan di sini, hanya maksudnya mungkin ini bisa dibuat lebih mudah diakses pembaca yang lebih luas. Meski begitu, saya juga tidak sedang mengusulkan hal absurd yang akan dianggap konyol semua orang di sini, seperti menulis ulang backend inferensi ke C#
adapted from arm limited optimized routine, jadi pada akhirnya ini memang berdiri di atas bahu para raksasaSaya jadi teringat profesor yang terkenal berkata, “konstanta yang diabaikan semua orang itu, secara rekayasa, bisa memakan seluruh kepalamu”
Katanya ini menggantikan lookup table
short[65536], dan saya jadi bertanya apakah itu pilihan yang agak tumpul sejak awalItu pada dasarnya menaruh lookup table sebesar seluruh cache L1; apakah ini bekerja cukup baik hanya karena secara probabilistik kebetulan pas?
Mengosongkan cache L1 tidak terlalu masalah, dan data yang tersingkir untuk memberi ruang pada LUT hampir tidak akan dipakai lagi
Beban machine learning umumnya adalah streaming load yang membaca seluruh dataset secara linear pada setiap iterasi
Dari pengalaman saya yang terbatas, Anda bisa melakukan cukup banyak komputasi real-time sebelum akhirnya lebih cepat daripada lookup
Di llama.cpp, ini pembahasan untuk CPU
Sekarang ini sedang di-upstream ke proyek llama.cpp
Ada juga peningkatan performa lain yang saat ini hanya tersedia di llamafile, misalnya pekerjaan Kawrakow yang membuat K quants jauh lebih cepat
Mungkin agak di luar topik, tetapi adakah yang tahu bagaimana ggml dibandingkan dengan runtime seperti tensorflow lite atau onnxruntime?
Singkatnya, untuk LLM pakailah llama.cpp, dan dengan GGML sebagai dependensi intinya Anda juga bisa menjalankan whisper
Selain itu, pakai ONNX saja
TF itu seperti Apple di dunia machine learning: luar biasa jika Anda sepenuhnya terikat pada ekosistem Google ML, tetapi di luar itu pada dasarnya sudah mati. Proporsi model HF yang konyol, kira-kira 94%, adalah PyTorch
Perbandingan performa inferensi langsung yang cukup layak mungkin hanya Whisper di ONNX versus GGML, dan seseorang pernah menjalankan pustaka llama.cpp saya bersama Whisper tanpa melaporkan perbedaan performa yang berarti
Untuk inferensi non-batched pada perangkat CUDA saat ini, apakah gguf/llama.cpp adalah solusi dengan performa lebih baik, atau exllamav2+flashattention masih unggul?
Ada perbedaan lain yang lebih penting, seperti KV cache 4-bit
LUT juga bisa divektorkan
https://www.intel.com/content/www/us/en/docs/intrinsics-guid...
Saya juga pernah menulis tentang hal-hal yang bisa dilakukan dengan LUT https://darkcephas.blogspot.com/2018/10/validating-utf8-stri...
expsecara langsung, tergantung akurasi yang diinginkan, biasanya cukup 10~20 FMASulit bagi gather atau permutation untuk bersaing dengan komputasi murni
Dalam konteks yang mirip, ada juga tanh yang lebih cepat https://github.com/microsoft/onnxruntime/pull/20612
Tetapi apa targetnya? Apakah ini untuk mempercepat aproksimasi GeLU itu?
Jika kembali ke
erff(), sepertinya itu mungkin akan jauh lebih cepatApakah ini juga membantu untuk kasus penggunaan partial offloading GPU di gguf?
Apakah sisi CPU juga jadi lebih cepat?