คู่มือแบบภาพสำหรับการทำ Quantization ของ LLM
(newsletter.maartengrootendorst.com)- โมเดลภาษาขนาดใหญ่ (LLM) มีขนาดใหญ่เกินกว่าจะรันบนฮาร์ดแวร์ทั่วไปได้ โดยปกติจะมีพารามิเตอร์ระดับหลายพันล้านตัว จึงต้องใช้ GPU ที่มี VRAM จำนวนมาก
- ด้วยเหตุนี้ งานวิจัยจำนวนมากขึ้นจึงมุ่งไปที่การทำให้โมเดลเหล่านี้เล็กลงผ่านการฝึกที่ดีขึ้น อะแดปเตอร์ และแนวทางอื่น ๆ โดยหนึ่งในเทคนิคหลักของสายงานนี้คือการทำ Quantization
Part 1: "ปัญหา" ของโมเดลภาษาขนาดใหญ่
- LLM (Large Language Model) ได้ชื่อมาจากจำนวนพารามิเตอร์ที่มันมี
- โดยทั่วไปโมเดลเหล่านี้มีพารามิเตอร์หลายพันล้านตัว (ส่วนใหญ่เป็นค่าน้ำหนัก) ทำให้มีต้นทุนในการจัดเก็บค่อนข้างสูง
- ระหว่างการทำ inference ค่า activation จะถูกสร้างจากผลคูณของอินพุตกับค่าน้ำหนัก และมีขนาดใหญ่ได้เช่นกัน
- ดังนั้นจึงพยายามแทนค่าจำนวนมหาศาลเหล่านี้ให้มีประสิทธิภาพที่สุด พร้อมกับลดพื้นที่ที่ต้องใช้ในการเก็บค่าต่าง ๆ ให้น้อยที่สุด
วิธีแทนค่าตัวเลข
- ค่าที่กำหนดมักถูกแทนด้วยเลขทศนิยมลอยตัว (จำนวนจริง)
- ค่าเหล่านี้ถูกแทนด้วย "บิต" และมาตรฐาน IEEE-754 อธิบายว่าบิตต่าง ๆ ใช้แสดงองค์ประกอบอย่างเครื่องหมาย เลขชี้กำลัง และแมนทิสซา (fraction) เพื่อแทนค่าอย่างไร
- โดยทั่วไปยิ่งใช้บิตในการแทนค่ามาก ก็ยิ่งมีความแม่นยำสูงขึ้น
- และยิ่งมีบิตมาก ช่วงของค่าที่สามารถแทนได้ก็ยิ่งกว้างขึ้น
ข้อจำกัดด้านหน่วยความจำ
- หากสมมติว่าเป็นโมเดลที่มีพารามิเตอร์ 70 พันล้านตัว การใช้ FP32 (full-precision) จะต้องใช้หน่วยความจำถึง 280GB เพียงเพื่อโหลดโมเดล
- ดังนั้นการลดจำนวนบิตที่ใช้แทนพารามิเตอร์ของโมเดลจึงสำคัญมาก แต่เมื่อความแม่นยำลดลง ความถูกต้องของโมเดลก็มักลดลงตามไปด้วย
- เป้าหมายคือการลดจำนวนบิตที่ใช้แทนค่าโดยยังคงรักษาความแม่นยำไว้ให้ได้มากที่สุด และนี่คือจุดที่ Quantization เข้ามามีบทบาท
Part 2: แนะนำ Quantization
- Quantization มีเป้าหมายเพื่อลดความละเอียดของพารามิเตอร์โมเดลจากความกว้างบิตสูงกว่า (เช่น floating point 32 บิต) ไปเป็นความกว้างบิตต่ำกว่า (เช่น integer 8 บิต)
- ทุกครั้งที่ลดจำนวนบิตลง จะต้องมีการแมปเพื่อ "บีบอัด" พารามิเตอร์ดั้งเดิมให้ไปอยู่ในรูปแบบบิตต่ำ
ประเภทข้อมูลที่พบบ่อย
FP16
- เมื่อเปลี่ยนจาก FP32 ไปเป็น FP16 (half precision) ช่วงค่าที่ FP16 สามารถแทนได้จะเล็กกว่า FP32 อย่างมาก
BF16
- เพื่อให้ได้ช่วงค่าที่ใกล้เคียง FP32 จึงมีการนำ bfloat16 ซึ่งเป็นเสมือน "FP32 ที่ถูกตัดทอน" มาใช้
- BF16 ใช้จำนวนบิตเท่ากับ FP16 แต่มีช่วงค่าที่กว้างกว่า และมักถูกใช้ในงาน deep learning
INT8
- เมื่อจำนวนบิตลดลงไปอีก การแทนค่าจะเริ่มเข้าใกล้รูปแบบที่อิงกับจำนวนเต็มแทนการใช้ floating point
Symmetric Quantization
- ช่วงของค่าทศนิยมลอยตัวดั้งเดิมจะถูกแมปไปยังช่วงที่สมมาตรรอบค่า 0 ในพื้นที่ที่ถูก quantize
- ค่า quantized ของ 0 ในพื้นที่ floating point จะยังคงเป็น 0 อย่างพอดีในพื้นที่ quantized
Asymmetric Quantization
- ต่างจาก symmetric quantization ตรงที่ไม่ได้สมมาตรรอบ 0
- โดยจะแมปค่าต่ำสุด (β) และค่าสูงสุด (α) ในช่วง floating point ไปเป็นค่าต่ำสุดและค่าสูงสุดของช่วง quantized
- หนึ่งในวิธีนี้เรียกว่า zero-point quantization
การแมปช่วงค่าและการทำ clipping
- หากแมปช่วงทั้งหมดของเวกเตอร์ ค่า outlier อาจทำให้ค่าขนาดเล็กทั้งหมดถูกแมปเป็นค่าบิตต่ำแบบเดียวกันจนสูญเสียความสามารถในการแยกแยะ
- ทางเลือกหนึ่งคือเลือกทำ clipping กับค่าบางส่วน
- Clipping คือการกำหนดช่วงไดนามิกใหม่ให้ค่าดั้งเดิม เพื่อให้ outlier ทั้งหมดมีค่าเดียวกัน
- วิธีนี้ช่วยลด quantization error ของค่าที่ไม่ใช่ outlier ได้มาก แต่จะเพิ่ม quantization error ของ outlier
การปรับเทียบ (Calibration)
ค่าน้ำหนัก (และ bias)
- ค่าน้ำหนักและ bias ถือเป็นค่าคงที่แบบสถิตที่ทราบล่วงหน้าก่อนรันโมเดลได้
- เนื่องจาก bias มีจำนวนน้อยกว่าค่าน้ำหนักมาก จึงมักถูกเก็บไว้ด้วยความละเอียดสูงกว่า (เช่น INT16) และความพยายามหลักของ quantization จะมุ่งไปที่ค่าน้ำหนัก
- เทคนิคการปรับเทียบสำหรับค่าน้ำหนักที่เป็นค่าคงที่และทราบล่วงหน้ามีหลายแบบ เช่น เลือกเปอร์เซ็นไทล์ของช่วงอินพุตด้วยตนเอง, ปรับให้ค่า mean squared error (MSE) ระหว่างค่าน้ำหนักเดิมกับค่าน้ำหนักที่ถูก quantize ต่ำที่สุด, หรือทำให้ entropy (KL divergence) ระหว่างค่าเดิมกับค่าที่ถูก quantize ต่ำที่สุด
Activation
- อินพุตจะถูกอัปเดตอย่างต่อเนื่องตลอดทั้ง LLM และโดยทั่วไปเรียกว่า "activation"
- ค่านี้เปลี่ยนไปทุกครั้งที่ข้อมูลอินพุตแต่ละชุดถูกป้อนเข้าโมเดลระหว่างการทำ inference จึงทำให้ quantize ได้อย่างแม่นยำยาก
- ค่านี้จะถูกอัปเดตหลังแต่ละ hidden layer ดังนั้นเราจะรู้ได้ว่ามันเป็นอะไรระหว่าง inference ก็ต่อเมื่อข้อมูลอินพุตวิ่งผ่านโมเดลแล้วเท่านั้น
Part 3: Post-Training Quantization (PTQ)
Dynamic Quantization
- ค่า activation จะถูกรวบรวมหลังจากข้อมูลผ่าน hidden layer ไปแล้ว
- การกระจายของ activation นี้ถูกใช้คำนวณค่า zero-point (z) และ scale factor (s) ที่จำเป็นสำหรับการ quantize เอาต์พุต
- กระบวนการนี้จะเกิดซ้ำทุกครั้งที่ข้อมูลผ่านเลเยอร์ใหม่ ดังนั้นแต่ละเลเยอร์จึงมีค่า z และ s เป็นของตัวเอง และมีสคีมการ quantization ต่างกัน
Static Quantization
- คำนวณค่า zero-point (z) และ scale factor (s) ล่วงหน้า แทนที่จะคำนวณระหว่าง inference
- เพื่อหาค่าเหล่านี้ จะใช้ชุดข้อมูลสำหรับการปรับเทียบป้อนเข้าโมเดลเพื่อเก็บการกระจายที่เป็นไปได้เหล่านี้
- เมื่อทำ inference จริง ค่า s และ z จะไม่ถูกคำนวณใหม่ แต่จะถูกใช้แบบ global กับ activation ทั้งหมดเพื่อทำ quantization
- โดยทั่วไป dynamic quantization มักแม่นยำกว่าเล็กน้อย เพราะพยายามคำนวณค่า s และ z สำหรับแต่ละ hidden layer แต่ก็อาจใช้เวลาคำนวณมากขึ้น
- ส่วน static quantization แม่นยำน้อยกว่า แต่เร็วกว่าเพราะรู้ค่า s และ z อยู่แล้ว
โลกของการทำ 4-bit quantization
- การลดต่ำกว่า 8 บิตเป็นงานที่ท้าทาย เพราะ quantization error จะเพิ่มขึ้นทุกครั้งที่จำนวนบิตลดลง
- สำรวจสองแนวทางที่มักถูกแชร์กันใน HuggingFace คือ GPTQ และ GGUF
GPTQ
- เป็นหนึ่งในวิธีที่เป็นที่รู้จักมากที่สุดสำหรับการ quantize ลงไปที่ 4 บิต
- ใช้ asymmetric quantization และทำแบบรายเลเยอร์ โดยประมวลผลแต่ละเลเยอร์อย่างอิสระก่อนจะไปยังเลเยอร์ถัดไป
- ระหว่างกระบวนการ quantization รายเลเยอร์ จะทำการแปลงค่าน้ำหนักของเลเยอร์ด้วย inverse Hessian ก่อน ซึ่งเป็นอนุพันธ์อันดับสองของฟังก์ชัน loss ของโมเดล และบอกได้ว่าเอาต์พุตของโมเดลไวต่อการเปลี่ยนแปลงของค่าน้ำหนักแต่ละตัวมากเพียงใด
- พูดง่าย ๆ คือมันแสดงให้เห็นถึงระดับความสำคัญ (แบบผกผัน) ของค่าน้ำหนักแต่ละตัวในเลเยอร์
- ค่าน้ำหนักที่มีค่าน้อยในเมทริกซ์ Hessian มีความสำคัญมากกว่า เพราะการเปลี่ยนแปลงเพียงเล็กน้อยของค่าน้ำหนักเหล่านี้อาจทำให้ประสิทธิภาพของโมเดลเปลี่ยนไปอย่างมาก
GGUF
- GPTQ เป็นวิธี quantization ที่ดีสำหรับการรัน LLM ทั้งโมเดลบน GPU แต่เราไม่ได้มีความจุระดับนั้นเสมอไป
- ทางเลือกคือใช้ GGUF เพื่อ offload ทุกเลเยอร์ของ LLM ไปยัง CPU
- ทำให้สามารถใช้ทั้ง CPU และ GPU ร่วมกันได้เมื่อมี VRAM ไม่เพียงพอ
Part 4: Quantization Aware Training (QAT)
- ในส่วนที่ 3 เราดูวิธี quantize โมเดลหลังการฝึกไปแล้ว แต่ข้อเสียคือ quantization แบบนี้ไม่ได้คำนึงถึงกระบวนการฝึกจริง
- ตรงนี้เองที่ Quantization Aware Training (QAT) เข้ามามีบทบาท ต่างจาก PTQ ตรงที่ QAT มีเป้าหมายให้โมเดลเรียนรู้ขั้นตอน quantization ไปพร้อมกันระหว่างการฝึก
- QAT มักมีความแม่นยำมากกว่า PTQ เพราะมีการคำนึงถึง quantization ตั้งแต่ระหว่างการฝึกแล้ว
ยุคของ LLM แบบ 1 บิต: BitNet
- BitNet แทนค่าน้ำหนักของโมเดลด้วย 1 บิตเดียว คือ -1 หรือ 1
- ทำได้โดยฉีดกระบวนการ quantization เข้าไปในสถาปัตยกรรม Transformer โดยตรง
- สถาปัตยกรรม Transformer เป็นพื้นฐานของ LLM ส่วนใหญ่ และประกอบด้วยการคำนวณที่มี linear layer อยู่ภายใน
- BitNet แทน linear layer เหล่านี้ด้วยสิ่งที่เรียกว่า BitLinear
Weight quantization
- ระหว่างการฝึก ค่าน้ำหนักจะถูกเก็บในรูป INT8 แล้วจึงถูก quantize เป็น 1 บิตด้วยกลยุทธ์พื้นฐานคือฟังก์ชันสัญญาณ
- โดยหลักแล้วคือเลื่อนการกระจายของค่าน้ำหนักให้มาอยู่รอบ 0 จากนั้นกำหนดทุกค่าที่อยู่ซ้ายของ 0 เป็น -1 และทุกค่าที่อยู่ขวาของ 0 เป็น 1
Activation quantization
- เพื่อ quantize activation, BitLinear ใช้ absmax quantization เพื่อแปลง activation จาก FP16 ไปเป็น INT8 เพราะการคูณเมทริกซ์ (×) ต้องการความละเอียดที่สูงกว่า
Dequantization
- มีการติดตามค่า α (ค่าสัมบูรณ์ที่มากที่สุดของ activation) และ β (ค่าเฉลี่ยของค่าสัมบูรณ์ของค่าน้ำหนัก) ซึ่งค่าทั้งสองนี้จะช่วยในการ dequantize activation กลับไปเป็น FP16 ในภายหลัง
- activation ของเอาต์พุตจะถูก rescale ด้วย {α, γ} เพื่อ dequantize กลับสู่ความละเอียดเดิม
โมเดลภาษาขนาดใหญ่ทั้งหมดคือ 1.58 บิต
- BitNet 1.58b ถูกนำเสนอเพื่อแก้ปัญหาเรื่อง scaling ที่กล่าวถึงก่อนหน้านี้ให้ดีขึ้น
- ในวิธีใหม่นี้ ค่าน้ำหนักแต่ละตัวของโมเดลไม่ได้มีเพียง -1 หรือ 1 อีกต่อไป แต่สามารถเป็น 0 ได้ด้วย จึงกลายเป็นแบบ ternary
- ที่น่าสนใจคือเพียงแค่เพิ่ม 0 เข้าไป BitNet ก็พัฒนาขึ้นอย่างมาก และความเร็วในการคำนวณก็ดีขึ้นมากด้วย
พลังของเลข 0
- ทำไมการเพิ่ม 0 ถึงช่วยได้มากขนาดนั้น? คำตอบเกี่ยวข้องกับการคูณเมทริกซ์ทั้งหมด
- เมื่อมีค่าน้ำหนักที่ถูก quantize เป็น 1.58 บิตแล้ว ไม่เพียงแต่จะเพิ่มความเร็วในการคำนวณได้มาก เพราะต้องทำเพียงการคูณเท่านั้น แต่ยังช่วยกรองฟีเจอร์ได้ด้วย
Quantization
- เพื่อทำ weight quantization, BitNet 1.58b ใช้ absmean quantization ซึ่งเป็นรูปแบบดัดแปลงของ absmax quantization ที่เราเห็นไปก่อนหน้า
- แนวคิดคือบีบอัดการกระจายของค่าน้ำหนัก และใช้ค่าเฉลี่ยสัมบูรณ์ (α) เพื่อ quantize ค่าเหล่านั้น จากนั้นปัดเศษให้เป็น -1, 0 หรือ 1
- เมื่อเทียบกับ BitNet แล้ว activation quantization ยังคงเหมือนเดิมเกือบทั้งหมด ยกเว้นอยู่หนึ่งจุด แทนที่จะปรับ activation ไปยังช่วง [0, 2ᵇ⁻¹] ตอนนี้จะใช้ absmax quantization เพื่อปรับไปยังช่วง [-2ᵇ⁻¹, 2ᵇ⁻¹] แทน
- การทำ 1.58-bit quantization ต้องอาศัยเคล็ดลับ (เป็นหลัก) สองอย่าง:
- เพิ่ม 0 เพื่อสร้างการแทนค่าแบบ ternary [-1, 0, 1]
- ใช้ absmean quantization กับค่าน้ำหนัก
-
"13B BitNet b1.58 มีประสิทธิภาพมากกว่า 3B FP16 LLM ในแง่ของ latency, การใช้หน่วยความจำ และการใช้พลังงาน"
- ดังนั้นจึงสามารถได้โมเดลขนาดเบา เพราะมีการคำนวณแบบ 1.58 บิตที่มีประสิทธิภาพสูง
ยังไม่มีความคิดเห็น