ข้ามไปยังเนื้อหาหลัก

[LLM 6/10] Context Distillation: ย้าย system prompt เข้าไปเก็บในน้ำหนักโมเดล

· อ่าน 8 นาที
Kobkrit Viriyayudhakorn
CEO, iApp Technology

ทุกครั้งที่ผู้ใช้ส่งข้อความหาแชตบอตของคุณ คุณแนบ system prompt ก้อนเดิมยาวหลายร้อย token ไปด้วยเสมอ — ทุก request ตลอดอายุของระบบ จ่ายซ้ำไม่มีวันจบ บทนี้เราจะย้ายความรู้ก้อนนั้นจาก prompt เข้าไปอยู่ในน้ำหนักโมเดลด้วยเทคนิคชื่อ Context Distillation ในเวอร์ชัน on-policy (OPCD) จุดที่สวยที่สุดคือ ครูกับนักเรียนเป็นโมเดลตัวเดียวกันเป๊ะ ๆ — สิ่งเดียวที่ต่างกันคือใครได้เห็น prompt

Open in Colab06_context_distillation.ipynb

1. ปัญหา (Problem statement)

ระบบผู้ช่วยลูกค้าภาษาไทยทั่วไปมี system prompt หน้าตาประมาณนี้: กำหนด persona, บังคับให้ตอบภาษาไทยเสมอ, ต้องสุภาพลงท้ายครับ/ค่ะ, ห้ามให้คำแนะนำทางการแพทย์และกฎหมาย เขียนออกมาดี ๆ ก็ราว 400 token — และมันถูกส่งไปกับทุก request

ลองคิดเลขดูครับ ระบบที่รับ 100,000 request ต่อวัน จ่ายค่า token ให้ข้อความก้อนเดิมซ้ำ ๆ วันละ 40 ล้าน token เดือนละ 1,200 ล้าน token — ทั้งที่เนื้อหาไม่เคยเปลี่ยนเลยสักตัวอักษร และนี่ยังไม่นับราคาอีกสองอย่างที่มองไม่เห็นในบิล:

  • Latency — โมเดลต้อง prefill 400 token ก่อนจะเริ่มคิดคำตอบแรกทุกครั้ง
  • Context budget — ทุก token ของ persona คือที่ที่หายไปจากประวัติการสนทนาและเอกสารแนบ

มองในกรอบของซีรีส์นี้ ความรู้มีที่เก็บได้สามที่ และแต่ละที่มี "กำหนดจ่าย" ต่างกัน:

ที่เก็บความรู้จ่ายเมื่อไหร่เหมาะกับ
System promptทุก request ตลอดไปพฤติกรรม/นโยบายที่ยังเปลี่ยนบ่อย
RAGทุก request (ค้น + prompt ยาว)ข้อเท็จจริงจำนวนมาก เปลี่ยนบ่อย ต้องอ้างอิงแหล่งที่มา
น้ำหนักโมเดลครั้งเดียวตอนเทรนพฤติกรรม/นโยบายที่นิ่งแล้ว

system prompt ที่นิ่งแล้วแต่ยังแนบไปทุกครั้ง คือความรู้ที่เก็บผิดที่ — มันควรย้ายจากแถวแรกลงไปอยู่แถวสุดท้ายของตารางนี้ บทนี้คือวิธีย้าย

2. เราจะทำอะไร (Solution)

Context distillation คือการเทรนนักเรียนที่ไม่เห็น context cc ให้ทำตัวเหมือนครูที่เห็น cc — พูดอีกแบบคือย้ายผลของ cc จาก prompt เข้าไปในน้ำหนัก แนวคิด offline ดั้งเดิมมีมาตั้งแต่งานของ Askell และคณะ (2021) ส่วนเวอร์ชันที่เราใช้ในบทนี้คือ OPCD (On-Policy Context Distillation) ของ Ye, Dong, Wu, Huang และ Wei (2026, arXiv:2602.12275) ซึ่งเพิ่มส่วนผสมสำคัญสองอย่างที่หัวข้อ 3 จะแกะทีละตัว:

  1. นักเรียนสุ่มคำตอบของตัวเอง (on-policy) โดยไม่เห็น cc
  2. บนคำตอบเหล่านั้น minimize reverse KL เทียบกับครูที่เห็น cc
แนวคิดหลักของบทนี้

system prompt คือความรู้ที่เก็บผิดที่ — เก็บใน prompt คุณจ่ายทุก request ตลอดไป OPCD ย้ายมันเข้าไปในน้ำหนัก แล้วคุณจ่ายครั้งเดียวตอนเทรน

และในบทนี้ ครูกับนักเรียนคือน้ำหนักชุดเดียวกัน — ครูคือโมเดลตอนที่มี cc อยู่ตรงหน้า นักเรียนคือโมเดลตัวเดิมที่ไม่มี cc สิ่งที่ระยะห่างระหว่างสองตัวนี้วัด คือ "อิทธิพลของ cc" ล้วน ๆ

ขอปักหมุดประโยคหนึ่งไว้ตั้งแต่ตอนนี้ เพราะบทที่ 7 จะพูดถึง "distillation" อีกตัวที่คนสับสนกันบ่อย:

Context distillation เปลี่ยน "สิ่งที่โมเดลรู้โดยไม่ต้องบอก" — model distillation เปลี่ยน "ขนาดของโมเดล"

ในบทนี้โมเดลไม่ได้เล็กลงแม้แต่พารามิเตอร์เดียว มันแค่เลิกต้องการ prompt ส่วนบทที่ 7 คือเรื่องของการย่อโมเดลใหญ่ลงเป็นโมเดลเล็ก — คนละแกนกันโดยสิ้นเชิง

3. สมการ (Equation)

3.1 Objective ของ OPCD

L(θ)=E(x,c),  yπθ(x)[1yt=1yDKL(πθ(x,y<t)πteacher(c,x,y<t))]\mathcal{L}(\theta) = \mathbb{E}_{(x,c),\; y\sim\pi_\theta(\cdot|x)}\left[\frac{1}{|y|}\sum_{t=1}^{|y|} \mathbb{D}_{\text{KL}}\Big(\pi_\theta(\cdot \mid x, y_{<t}) \,\Big\|\, \pi_{\text{teacher}}(\cdot \mid c, x, y_{<t})\Big)\right]

โดย KL ในแต่ละตำแหน่ง token คือผลรวมข้ามทั้ง vocabulary V\mathcal{V}:

DKL(πθπteacher)=vVπθ(vx,y<t)logπθ(vx,y<t)πteacher(vc,x,y<t)\mathbb{D}_{\text{KL}}\Big(\pi_\theta \,\Big\|\, \pi_{\text{teacher}}\Big) = \sum_{v\in\mathcal{V}} \pi_\theta(v \mid x, y_{<t})\,\log\frac{\pi_\theta(v \mid x, y_{<t})}{\pi_{\text{teacher}}(v \mid c, x, y_{<t})}
  • cc = context ที่อยากย้ายเข้า weights (persona + นโยบายความปลอดภัย ~400 token)
  • xx = คำถามของผู้ใช้, yy = คำตอบที่นักเรียนสุ่มเองโดยไม่เห็น cc
  • πθ\pi_\theta = นักเรียน (ทำนายโดยเห็นแค่ xx), πteacher\pi_{\text{teacher}} = ครู (น้ำหนักเดิม แต่เห็น cc ด้วย)
  • 1y\frac{1}{|y|} = เฉลี่ยต่อ token กันคำตอบยาวได้น้ำหนักเกิน (คุ้น ๆ ไหมครับ — length bias จากบทที่ 4)

สังเกตว่านี่ไม่ใช่ cross-entropy กับ "เฉลย" ใด ๆ — เป้าหมายคือการกระจายความน่าจะเป็นทั้งแถวของครู ในทุกตำแหน่ง token นักเรียนไม่ได้เรียนว่า "คำถัดไปคืออะไร" แต่เรียนว่า "ถ้ามี cc อยู่ตรงหน้า ความน่าจะเป็นของทุกคำใน vocab จะหน้าตาเป็นอย่างไร"

สมการนี้มีจุดตัดสินใจสองจุดที่แบกน้ำหนักทั้งวิธีเอาไว้ แยกดูทีละจุด

3.2 จุดที่หนึ่ง — KL ต้องเป็น reverse (πθ\pi_\theta อยู่หน้า)

KL ไม่สมมาตร และลำดับของมันคือการเลือกพฤติกรรม:

  • Forward KL DKL(πteacherπθ)\mathbb{D}_{\text{KL}}(\pi_{\text{teacher}} \| \pi_\theta) ระเบิดเมื่อครูมีมวลแต่นักเรียนไม่มี → นักเรียนถูกบังคับให้ "ครอบคลุม" ทุก mode ของครู (mode-covering) ถ้าความจุไม่พอ มันจะถัวเฉลี่ยแผ่มวลไปคลุมทุกอย่าง รวมถึงหุบเขาระหว่าง mode ที่ครูไม่เคยไป — ในภาษาของ LLM นั่นคือคำตอบประเภท "ผสมสองสไตล์จนเพี้ยน" หรือ hallucination
  • Reverse KL DKL(πθπteacher)\mathbb{D}_{\text{KL}}(\pi_\theta \| \pi_{\text{teacher}}) ระเบิดเมื่อนักเรียนมีมวลตรงที่ครูไม่มี → นักเรียนถูกบังคับให้ไม่ทำสิ่งที่ครูไม่ทำ แล้วเลือกยึด mode ใด mode หนึ่งของครูให้มั่น (mode-seeking)

สำหรับงานของบทนี้ — persona และนโยบายความปลอดภัย — เราต้องการอย่างหลังแบบไม่ต้องคิดเลย: นักเรียนที่ "ทำเหมือนครูได้สักทางหนึ่ง อย่างมั่นคง" มีค่ากว่านักเรียนที่ "เผื่อความน่าจะเป็นให้ทุกทางของครู แถมทางที่ครูห้ามด้วย"

ถ้าคุ้น ๆ ว่าเคยเห็นที่ไหน — ใช่ครับ KL ในสมการ RLHF ของบทที่ 3–4 ก็เอา π\pi ไว้หน้าเหมือนกัน ด้วยเหตุผลเดียวกัน: เราคุมพฤติกรรมของตัวที่กำลังเทรน ไม่ใช่ของตัวอ้างอิง

3.3 จุดที่สอง — rollout ต้องเป็นของนักเรียนเอง (on-policy)

สังเกต yπθ(x)y\sim\pi_\theta(\cdot|x) ในสมการ 3.1: คำตอบที่ใช้เทรนสุ่มมาจากนักเรียน ไม่ใช่จากครู

ทางเลือกที่ง่ายกว่าคือให้ครู (ที่เห็น cc) เขียนคำตอบมาชุดหนึ่ง แล้วให้นักเรียนทำ SFT ตาม — วิธีนั้นมีปัญหาเชิงโครงสร้างชื่อ exposure bias: นักเรียนถูกสอนเฉพาะบนเส้นทางข้อความที่ครูเขียน แต่ตอนใช้งานจริง มันต้องเดินต่อจาก prefix ที่ตัวเองเขียน พลาดหนึ่ง token ก็หลุดไปอยู่ในสถานะที่ไม่เคยถูกสอน แล้วความผิดพลาดจะทบต้นไปเรื่อย ๆ

การสุ่มแบบ on-policy ลบปัญหานี้โดยโครงสร้าง: สถานะที่นักเรียนเจอระหว่างเทรน คือสถานะแบบเดียวกับที่มันจะเจอตอน inference เพราะมันเป็นคนสร้างเองทั้งคู่ ครูมีหน้าที่เดียวคือ "ยืนตรวจ" อยู่บนเส้นทางของนักเรียน — บอกว่า ณ จุดที่เธอเพิ่งเดินมาถึงนี้ ถ้ามี cc ควรจะไปทางไหนต่อ (นี่คือเหตุผลเดียวกับที่บทที่ 5 ต้องสุ่มคำตอบตัวเองแทนที่จะใช้ DPO ต่อ)

หมายเหตุความซื่อตรงหนึ่งบรรทัด: ตอนคำนวณ gradient เรา treat yy ที่สุ่มมาเป็นค่าคงที่ ไม่ส่ง gradient ย้อนผ่านการสุ่ม — เป็นแนวปฏิบัติมาตรฐานของ on-policy distillation

3.4 Baseline ที่ต้องสู้ให้ชนะ: offline context distillation

สิ่งที่บล็อกส่วนใหญ่เรียกว่า "context distillation" คือเวอร์ชัน offline:

Loffline(θ)=Eyπteacher(c,x)[t=1ylogπθ(ytx,y<t)]\mathcal{L}_{\text{offline}}(\theta) = -\mathbb{E}_{y\sim\pi_{\text{teacher}}(\cdot|c,x)}\left[\sum_{t=1}^{|y|}\log\pi_\theta(y_t \mid x, y_{<t})\right]

อ่านออกมาตรง ๆ: ให้ครูที่เห็น cc เขียนคำตอบ แล้วเอาคำตอบนั้นมา SFT นักเรียนที่ไม่เห็น cc — เป็น cross-entropy บนข้อความของครูธรรมดา ๆ ไม่มี KL ทั้งแถว ไม่มี on-policy

นี่ไม่ใช่หุ่นฟางนะครับ มันคือ baseline ที่แข็งจริงและถูกกว่า (เทรนเหมือนบทที่ 2 เป๊ะ) หัวข้อ 9 จะให้ OPCD สู้กับมันแบบแฟร์ ๆ บนข้อมูลเดียวกัน ถ้าสองส่วนผสมของ OPCD (reverse KL + on-policy) มีค่าจริง มันต้องชนะตรงที่ทฤษฎีบอกว่าจะชนะ: การ generalize ไปยัง prompt แบบที่ไม่เคยเห็น

เนื้อหาเต็มอยู่ในคอร์ส

บทความนี้คือประมาณ 30% แรกของบทเรียน — ส่วนที่เหลือ (การเตรียมสภาพแวดล้อม, การเตรียมข้อมูล, โค้ดหลัก, ผลลัพธ์จริง และบทสรุป) อยู่ในคอร์ส LLM Finetuning ซึ่งเรียนฟรี เพียงเข้าสู่ระบบด้วย Google

อ่านเนื้อหาเต็มในคอร์ส →