vLLM คืออะไร และทำไม throughput ถึงกระโดดเมื่อย้ายมาใช้
ถ้าคุณเคยย้ายโมเดลมาใช้ vLLM แล้วเห็น throughput เพิ่มขึ้นหลายเท่าโดยไม่ได้แตะตัวโมเดลเลย บทความนี้อธิบายว่ามันมาจากไหน
มีสองแนวคิดที่ทำงานหลัก
1. คอขวดคือ KV cache ไม่ใช่การคำนวณ
ตอนโมเดลสร้างข้อความ มันจะเก็บ KV cache — attention key และ value ของทุก token ที่ผ่านมา — เพื่อไม่ต้องคำนวณ prefix ใหม่ทั้งหมดในทุก token ที่สร้าง cache นี้ใหญ่ และโตขึ้นทุกครั้งที่สร้าง token เพิ่ม
วิธีเก็บแบบตรงไปตรงมาคือจองหน่วยความจำ GPU เป็นบล็อกต่อเนื่องหนึ่งก้อนต่อหนึ่ง request โดยจองเผื่อความยาวสูงสุดที่ request นั้นอาจสร้างได้ และนั่นคือจุดที่ความสูญเปล่าอยู่:
- request ที่อนุญาตไว้ 2,048 token แต่หยุดที่ 100 เท่ากับจองไว้มากกว่าที่ใช้จริง 20 เท่า
- หน่วยความจำที่จองแล้วไม่ได้ใช้ ให้ request อื่นยืมไม่ได้
- คุณต้องออกแบบทั้งเซิร์ฟเวอร์เผื่อกรณีแย่สุด ทำให้ concurrency ต่ำอยู่ตลอด
ทีมส่วนใหญ่เจอปัญหานี้ในรูป “GPU ยังมีหน่วยความจำว่างเยอะ แต่พอเพิ่ม concurrency แล้ว out-of-memory”
2. PagedAttention: มองหน่วยความจำ GPU แบบ virtual memory
ไอเดียหลักของ vLLM ยืมมาจากระบบปฏิบัติการ แทนที่จะจองพื้นที่ต่อเนื่องก้อนเดียวต่อ request มันแบ่ง KV cache เป็น บล็อก ขนาดคงที่ แล้วใช้ block table แมปตำแหน่ง token เชิงตรรกะของแต่ละ request ไปยังบล็อกจริงที่อยู่ตรงไหนของหน่วยความจำก็ได้
ผลที่ตามมาเห็นได้ทันที:
- แทบไม่มี internal fragmentation request ใช้บล็อกไปเรื่อย ๆ ตามที่สร้างจริง ไม่ใช่จองล่วงหน้า ความสูญเปล่าเหลืออย่างมากแค่บล็อกที่ยังเติมไม่เต็มหนึ่งบล็อกต่อ sequence
- การแชร์แทบไม่มีต้นทุน request สองอันที่มี prefix เดียวกัน — system prompt ร่วม, few-shot preamble, เอกสาร RAG — สามารถชี้ไปที่บล็อกจริง เดียวกัน แทนที่จะเก็บสองชุด สำหรับงานที่มี prefix ยาวคงที่ แค่ข้อนี้ก็ช่วยได้มาก
- concurrency สูงขึ้น ใส่ request ได้มากขึ้นบนการ์ดใบเดิม ซึ่งคือที่มาจริง ๆ ของตัวเลข throughput
ชื่อก็มาจากตรงนี้ตรง ๆ: paging ที่เอามาใช้กับ attention
3. Continuous batching: เลิกรอ request ที่ช้าที่สุด
แนวคิดที่สองเป็นเรื่องการจัดคิว static batching แบบดั้งเดิมจะรวบ request N อัน รันพร้อมกัน แล้วคืนผลเมื่อครบทั้ง N เสร็จ ในเมื่อความยาวการสร้างต่างกันมาก batch จึงเดินด้วยความเร็วของตัวที่ช้าที่สุด และ GPU ก็นั่งว่างในช่องที่เสร็จไปแล้ว
Continuous batching ทำงานที่ระดับ iteration แทน หลังจบ decoding step แต่ละครั้ง scheduler สามารถเอา sequence ที่เสร็จแล้วออกและรับตัวที่รออยู่เข้ามาได้ทันที batch จึงถูกเติมต่อเนื่อง แทนที่จะรอให้หมดแล้วค่อยเติมใหม่
สำหรับทราฟฟิกที่ปนกัน — บาง request อยากได้ 20 token บางอันอยากได้ 2,000 — นี่คือความต่างระหว่าง GPU ที่ทำงานอยู่กับ GPU ที่นั่งรอ
แล้วในทางปฏิบัติหมายความว่าอย่างไร
มีข้อสรุปบางอย่างที่ควรเข้าใจก่อนจะไปจูนอะไร
throughput กับ latency เป็นลูกบิด ไม่ใช่ค่าคงที่คู่กัน sequence ที่รันพร้อมกันมากขึ้น = token ต่อวินาทีรวมสูงขึ้น และ latency ต่อ request สูงขึ้น ต้องตัดสินใจว่าโปรดักต์ของคุณจ่ายเงินเพื่ออะไรกันแน่
prefix ร่วมที่ยาวคุ้มค่าที่จะออกแบบเผื่อ ถ้าทุก request แบก system prompt เดียวกัน 800 token การแชร์ prefix กำลังทำงานให้คุณอยู่จริง ๆ รักษาส่วนที่ใช้ร่วมกันให้เหมือนกันระดับไบต์และวางไว้ข้างหน้า — การใส่ timestamp รายคำขอไว้ตำแหน่งแรกทำลายการแชร์นี้ไปเงียบ ๆ
--max-model-len คือการตัดสินใจเรื่องหน่วยความจำ มันกำหนดขอบเขต KV cache ต่อ sequence และจึงกำหนดว่ากี่ sequence จะอยู่พร้อมกันได้ การตั้งไว้เท่าค่าสูงสุดของโมเดล “เผื่อไว้ก่อน” คือการตัด concurrency ของตัวเองแบบเงียบ ๆ
จับตาดู preemption เมื่อหน่วยความจำหมด vLLM จะ preempt แล้วค่อยคำนวณใหม่หรือ swap sequence กลับมา ตัวเลข preemption ที่ไต่ขึ้นใน metrics แปลว่าคุณรับงานเกินตัว — นั่นคือสัญญาณให้ลด concurrency หรือเพิ่มหน่วยความจำ ก่อนที่ latency พุ่งจะไปโผล่เป็นคำร้องเรียนจากผู้ใช้
สำหรับงานภาษาไทยโดยเฉพาะ
มีรายละเอียดหนึ่งที่ทีมในบ้านเรามักสะดุด: ภาษาไทย tokenize ได้ไม่ดีในโมเดล multilingual ส่วนใหญ่ ภาษาไทยไม่มีช่องว่างระหว่างคำ และ tokenizer ที่เทรนด้วยภาษาอังกฤษเป็นหลักมักออกมาเป็นหนึ่ง token ต่อหนึ่งอักขระหรือต่อกลุ่มสั้น ๆ prompt ภาษาไทยจึงกิน token มากกว่าคำแปลภาษาอังกฤษที่ความหมายเท่ากันอย่างเห็นได้ชัด
และมันกระทบทุกอย่างที่เล่ามาข้างบนโดยตรง — token ต่อ request มากขึ้น แปลว่า KV cache ต่อ request มากขึ้น แปลว่า sequence ที่รันพร้อมกันได้บนการ์ดใบเดิมน้อยลง ถ้าคุณตั้งงบหน่วยความจำ GPU จาก benchmark ภาษาอังกฤษแต่เสิร์ฟทราฟฟิกภาษาไทย วัดจำนวน token จริงของคุณก่อนกำหนดขนาด deployment
อ่านต่อ
- repository ของ vLLM และเอกสารประกอบ
- เปเปอร์ PagedAttention: Efficient Memory Management for Large Language Model Serving with PagedAttention
- และในวันที่ 6 กันยายน ถาม maintainer ได้โดยตรงที่ vLLM Bangkok Day — คนที่เขียนมันเอง