[LLM 3/10] RLHF และ PPO: เทรนโมเดลด้วยรางวัลที่หาอนุพันธ์ไม่ได้
บทที่ 2 เราสอนโมเดลด้วยการ "เลียนแบบเฉลย" ทีละ token แต่คุณสมบัติที่ทำให้ผู้ช่วย AI ใช้งานได้จริง — ตอบถูก สุภาพ ไม่มั่ว ไม่หลุดเป็นภาษาอังกฤษ — ไม่มีเฉลยให้เลียนแบบ และเขียนเป็น loss function ตรง ๆ ไม่ได้ บทนี้คือคำตอบแบบดั้งเดิมที่สุดของปัญหานั้น: RLHF (Reinforcement Learning from Human Feedback) ด้วย PPO เราจะเทรน reward model จริงจากคู่ preference ภาษาไทย แล้วเขียนลูป PPO เองจากศูนย์ราว 120 บรรทัด และปิดท้ายด้วยการทดลองที่ผมชอบที่สุดในซีรีส์: ถอดสายจูง KL ออก แล้วดูโมเดลโกงรางวัลกันสด ๆ นี่คือบทที่หนักที่สุดของซีรีส์โดยตั้งใจ เพราะบทที่ 4 (DPO) และบทที่ 5 (GRPO) ต่างก็เริ่มจากสมการของบทนี้ แล้วเลือก "ลบ" ชิ้นส่วนออกคนละชิ้น
Open in Colab03_rlhf_ppo.ipynb
1. ปัญหา (Problem statement)
SFT ในบทที่ 2 มีสมมติฐานซ่อนอยู่หนึ่งข้อ: ต้องมีเฉลยให้เลียนแบบ แต่ลองนึกถึงสิ่งที่เราอยากได้จริง ๆ เช่น "ตอบโจทย์เลขให้ถูก และอธิบายเป็นภาษาไทยที่อ่านรู้เรื่อง" ประโยคนี้ไม่มีเฉลยเดียว คำตอบที่ดีมีได้ร้อยแบบ และคำว่า "อ่านรู้เรื่อง" ก็เขียนเป็นสมการไม่ออก
พอพยายามจะ optimize สิ่งเหล่านี้ตรง ๆ เราจะชนกำแพงสองชั้นเสมอ:
กำแพงที่หนึ่ง — คุณภาพเขียนเป็น loss ไม่ได้ "ดีกว่า" นิยามเป็นฟังก์ชันไม่ได้ แต่มนุษย์เปรียบเทียบได้เก่งมาก ให้ดูคำตอบสองอันแล้วชี้ว่าชอบอันไหน ทำได้ทันทีและตรงกันพอสมควร ข้อมูลที่เก็บได้จริงจึงเป็นสามสิ่ง: prompt , คำตอบที่ถูกเลือก , คำตอบที่ถูกปัด
กำแพงที่สอง — ต่อให้มีคะแนน ก็ backprop ไม่ได้ สมมติมีฟังก์ชันวิเศษ ให้คะแนนทุกคำตอบ คุณก็ยังเทรนแบบ supervised ไม่ได้อยู่ดี เพราะคำตอบ เกิดจากการสุ่ม token ทีละตัว คะแนนมาถึงหลังการสุ่มจบแล้ว และอนุพันธ์เดินทางย้อนผ่านการสุ่มไม่ได้ — เส้นทางจาก กลับไปหา weights ขาดตอนตรงนั้นพอดี
| ทางที่อยากเดิน | ติดกำแพงอะไร |
|---|---|
| เขียน loss ของ "คำตอบที่ดี" ตรง ๆ | นิยาม "ดี" เป็นสมการไม่ได้ มีแต่การเปรียบเทียบ |
| ให้คนให้คะแนน แล้ว backprop | คะแนนอยู่หลังการสุ่ม token — gradient เดินผ่านการสุ่มไม่ได้ |
| ให้คนนั่งให้คะแนนสดระหว่างเทรน | มนุษย์ให้คะแนนไม่ทันแม้แต่เสี้ยวเดียวของ rollout |
นี่คือที่มาของชื่อบท: เรากำลังจะ optimize รางวัลที่หาอนุพันธ์ไม่ได้ เครื่องมือที่ทำแบบนั้นได้ชื่อว่า reinforcement learning
2. เราจะทำอะไร (Solution)
RLHF แก้กำแพงทั้งสองชั้นด้วยการเดินสองขั้น:
- Stage A — Reward Model: เทรนโมเดล ให้เลียนแบบการเปรียบเทียบของมนุษย์จากคู่ preference (แก้กำแพงที่หนึ่ง และแทนมนุษย์ที่ให้คะแนนไม่ทัน)
- Stage B — PPO: ใช้ policy-gradient RL ดัน policy ไปหาคะแนนของ โดยไม่ต้องหาอนุพันธ์ผ่านการสุ่ม (แก้กำแพงที่สอง) พร้อมสายจูง KL รั้งไม่ให้วิ่งหนีโมเดลตั้งต้น
ราคาที่จ่ายคือความซับซ้อน: ระหว่างเทรนจะมีโมเดลสี่ตัวอยู่ใน VRAM พร้อมกัน — policy (ตัวที่เทรน), reference (ตัวตั้งต้นที่แช่แข็ง), reward model และ value network ที่ยังไม่ได้แนะนำตัว (รอหัวข้อ 3.4)
RLHF คือการ optimize รางวัลที่คุณหาอนุพันธ์ไม่ได้ ผ่านตัวแทนที่ไม่สมบูรณ์ (reward model) และการ optimize ตัวแทนแรง ๆ โดยไม่มีอะไรรั้ง จะพังตาม Goodhart's law เสมอ: เมื่อตัวชี้วัดกลายเป็นเป้าหมาย มันจะเลิกเป็นตัวชี้วัดที่ดี
พจน์ KL ในสมการ 3.2 จึงไม่ใช่ regularizer ที่ใส่ไว้กันเหนียว — มันคือสิ่งเดียวที่ขวางอยู่ระหว่างคุณกับ reward hacking หัวข้อ 8 จะพิสูจน์ประโยคนี้ด้วยการถอดมันออกให้ดูกับตา
และอีกประโยคที่อยากให้ถือไว้ทั้งซีรีส์: สมการ objective ของบทนี้คือสมการแม่ของครึ่งหลังของซีรีส์ บทที่ 4 (DPO) แก้สมการนี้ในรูปปิดจน reward model และ RL loop ตัดกันหายไป บทที่ 5 (GRPO) เปลี่ยนวิธีประมาณ advantage จน value network หายไป เข้าใจบทนี้บทเดียว อีกสองบทจะกลายเป็น "การลบชิ้นส่วน" ที่อ่านออกทันที
3. สมการ (Equation)
3.1 Reward model: Bradley–Terry
Stage A เทรน ด้วย loss เดียวสั้น ๆ:
- = คะแนนสเกลาร์หนึ่งตัวต่อหนึ่งข้อความ — ในทางปฏิบัติคือโมเดลภาษาที่เปลี่ยนหัวเป็น linear ชั้นเดียว (
num_labels=1) - = sigmoid แปลงผลต่างคะแนนเป็นความน่าจะเป็นที่มนุษย์จะเลือก (โมเดล Bradley–Terry)
- ยิ่งผลต่าง กว้าง loss ยิ่งต่ำ
จุดที่คนมองข้ามแล้วไปเจ็บตัวทีหลัง: loss นี้เห็นแค่ผลต่างของคะแนน ลองแทน ด้วย ด้วยค่าคงที่ ใดก็ได้ — loss ไม่เปลี่ยนเลย แปลว่าสเกลสัมบูรณ์ของ reward model ไม่มีความหมายและไม่ถูกกำหนดโดยการเทรน รันสองครั้งอาจได้คะแนนเฉลี่ย 3.7 กับ −12.4 ที่จัดอันดับเหมือนกันเป๊ะ นี่คือเหตุผลที่ต้อง standardize reward ก่อนป้อนเข้า PPO เสมอ (ลบ mean หารด้วย std) — จำจุดนี้ไว้ มันจะกลับมาในหัวข้อ 7 และ 9
3.2 สมการแม่: RLHF objective
ถ้าทั้งซีรีส์นี้จะท่องจำได้สมการเดียว จงจำสมการนี้:
อ่านเป็นภาษาคน: "เก็บคะแนน reward ให้มากที่สุด แต่ทุกก้าวที่เดินห่างจากโมเดลตั้งต้น ต้องจ่ายค่าปรับ"
- = policy คือโมเดลที่กำลังเทรน — สังเกตว่า ถูกสุ่มจาก เอง นี่คือความต่างเชิงโครงสร้างจาก SFT ที่เรียนจากข้อมูลนิ่ง ๆ ในไฟล์
- = reference คือโมเดลตั้งต้น (โมเดลหลัง SFT จากบทที่ 2) แช่แข็งตลอดการเทรน
- = ราคาต่อหนึ่ง nat ของการเดินห่าง — ความตึงของสายจูง
- = ระยะห่างเชิงการกระจายระหว่าง policy กับ reference
บทที่ 4 (DPO) จะพิสูจน์ว่าสมการนี้มีคำตอบในรูปปิด แล้วพลิกกลับด้านจน และ RL loop หายไปทั้งคู่ บทที่ 5 (GRPO) จะเก็บโครง RL ไว้ แต่เปลี่ยนวิธีคำนวณ advantage จน หายไป ทั้งสองบทไม่ได้เสนอ objective ใหม่ — พวกมันแก้สมการเดียวกันนี้ด้วยเครื่องมือที่ต่างกัน
3.3 PPO clipped surrogate: เครื่องยนต์ของ Stage B
policy gradient แบบดิบ (REINFORCE) ใช้ rollout หนึ่งชุดอัปเดตได้ครั้งเดียวแล้วต้องทิ้ง ซึ่งแพงมากเพราะการ generate คือคอขวด PPO อยากรีดค่า rollout ชุดเดิมหลาย epoch จึงต้องมีตัวคูณแก้ทาง (importance sampling ratio):
- = สถานะ ณ ตำแหน่ง คือ prompt บวก token ที่สุ่มมาแล้วทั้งหมด
- = "การกระทำ" คือ token ตัวถัดไปที่ถูกสุ่มไปแล้วตอน rollout
- = snapshot ของ policy ณ ตอน rollout — ค่านี้ถูกคำนวณครั้งเดียวแล้วแช่แข็ง
แล้วหนีบ ไว้ด้วย clip:
- = advantage คือ "token นี้ดีกว่าที่คาดไว้เท่าไหร่" (นิยามในข้อถัดไป)
- = ความกว้างของ trust region (ค่ามาตรฐาน 0.2)
หัวใจอยู่ที่ min + clip ทำงานร่วมกันแบบมองโลกแง่ร้ายอย่างจงใจ: ถ้า เป็นบวก (token ดี) ผลตอบแทนจากการดัน ถูกตัดเพดานที่ — ดันเกินนั้นไม่ได้อะไรเพิ่ม gradient เป็นศูนย์ แต่ถ้า เป็นลบ (token แย่) ตัว min จะเลือกฝั่งที่แย่กว่าเสมอ — ค่าปรับไม่มีเพดาน สรุปหนึ่งประโยค: ได้จำกัด เสียไม่จำกัด นโยบายจึงขยับทีละก้าวเล็ก ๆ ใกล้ ๆ ตัวเดิม
ในสมการ 3.2 แช่แข็งตลอดการเทรน ทำหน้าที่สายจูง KL ในสมการ 3.3 คือ snapshot ณ ตอน rollout ล่าสุด เปลี่ยนทุกรอบ ทำหน้าที่ trust region บั๊กยอดฮิตอันดับหนึ่งของคนเขียน PPO เองคือจับสองตัวนี้ใส่ตัวแปรเดียวกัน
3.4 GAE: คำนวณ advantage อย่างไรไม่ให้จมน้ำเสียง noise
advantage สร้างจาก TD error ของ value network :
- = value network ทำนายว่า "จากจุดนี้ไปจนจบ จะเก็บ reward ได้อีกเท่าไหร่" — นี่คือโมเดลตัวที่สี่
- = reward ต่อ token (ในงานของเรา: ค่าปรับ KL ทุกตำแหน่ง บวกคะแนนงานที่ token สุดท้าย)
- = discount factor (งาน LLM มักใช้ 1.0)
- = ปุ่มหมุน bias–variance: เชื่อ สุดใจ (bias สูงถ้า ทำนายพลาด), ไม่เชื่อเลยและรอดูผลจริงจนจบ (variance สูงเพราะแบก noise ทั้งสาย), ค่าที่นิยมใช้คือ 0.95
เป็นโมเดลขนาดเท่า ๆ policy ที่ต้องเทรนไปพร้อมกันด้วย loss ของมันเอง ถ้า ทำนายมั่ว advantage ก็มั่ว แล้ว policy ก็เรียนจากสัญญาณมั่ว — จุดพังคลาสสิกของ PPO บทที่ 5 จะตอบคำถามว่า "ถ้าแทน ด้วยค่าเฉลี่ยของกลุ่มคำตอบที่สุ่มจาก prompt เดียวกันล่ะ?" นั่นแหละคือ GRPO ทั้งอัลกอริทึม — ลบโมเดลตัวที่สี่ทิ้งด้วยค่าเฉลี่ยตัวเดียว
3.5 Loss เต็มของ PPO: สามพจน์ สองโมเดล
รวมทุกชิ้นเป็น loss เดียวที่ optimizer เห็นจริง (เขียนในรูป minimize):
- พจน์แรก = clipped surrogate จากข้อ 3.3 (ติดลบเพราะเราต้องการ maximize)
- พจน์ที่สอง = value loss สอน ให้ทายเข้าใกล้ return จริง , มักเป็น 0.5
- พจน์ที่สาม = entropy bonus กันการกระจายยุบเร็วเกินไป, มักเป็น 0.01
- ส่วนสายจูง KL ของสมการ 3.2 ในทางปฏิบัตินิยมยัดเข้าไปใน reward ต่อ token: ซึ่งเป็นวิธีที่เราใช้ในหัวข้อ 7
นับของเล่นทั้งหมดที่ต้องจูน: โมเดล 4 ตัว บวก และ learning rate อีกสองชุด นี่คือเหตุผลที่ PPO ขึ้นชื่อว่า "รันสองรอบด้วย seed ต่างกัน ได้ผลคนละเรื่อง" และเป็นเหตุผลการมีอยู่ของบทที่ 4 ทั้งบท
บทความนี้คือประมาณ 30% แรกของบทเรียน — ส่วนที่เหลือ (การเตรียมสภาพแวดล้อม, การเตรียมข้อมูล, โค้ดหลัก, ผลลัพธ์จริง และบทสรุป) อยู่ในคอร์ส LLM Finetuning ซึ่งเรียนฟรี เพียงเข้าสู่ระบบด้วย Google
อ่านเนื้อหาเต็มในคอร์ส →