การ์ดโมเดลท้องถิ่นตกแต่ละตัวต่างอวดหน้าต่างบริบท 128k กันหมด บนเดสก์ท็อป ตัวเลขนั้นคือสัญญาเช่าที่ RAM ของคุณเซ็นโดยไม่อ่าน สิ่งที่ตัดสินว่าคุณจะเดินเอกสารยาวได้ไกลแค่ไหนไม่ใช่น้ำหนักโมเดล — แต่คือ KV cache ที่ขยายตัวตามบริบทเหมือนภาษีที่ตกลงกันไว้ด้วยสกุลเงินของคนอื่น
หน้าต่างบริบทไม่ใช่ฟีเจอร์ที่เปิดปิดได้ มันคือสัญญาเช่าหน่วยความจำที่คุณจ่ายทุกวินาทีที่เซิร์ฟเวอร์ยังรันอยู่
บริบท 128k กิน RAM จริง ๆ เท่าไร?
คณิตของมันเป็นสาธารณะและสั้น ดังที่ Transformer Inference Arithmetic อธิบาย KV cache เก็บเทนเซอร์สองตัว — key กับ value — ต่อทุกเลเยอร์ ทุก KV head ทุก token:
cache_bytes = 2 × เลเยอร์ × บริบท × kv_head × head_dim × ไบต์ต่ออิลิเมนต์ ลอง Llama 3.1 8B: 32 เลเยอร์, 8 KV head, head_dim 128 — เอาตรง ๆ จาก config.json บน Hugging Face ที่ 131,072 tokens ใน fp16 (2 ไบต์):
2 × 32 × 131072 × 8 × 128 × 2 = 17,179,869,184 ไบต์ ≈ 16 GiB น้ำหนักของโมเดลเดียวกันใน fp16 หนักราว 16 GiB ที่บริบทเต็มสุด แคชใหญ่เท่ากับโมเดลที่มันอยู่ด้วยพอดี “โมเดล 8B ที่ลงใน 16 GiB” กลายเป็นปัญหา 32 GiB อย่างเงียบ ๆ ทันทีที่คุณเขยิบ num_ctx ขึ้นไปที่ 128k
ทำไมแคชโตตามหน้าต่าง?
เพราะ attention ต้องเทียบทุก token กับทุก token ที่มาก่อน และอินเฟอเรนซ์เป็นแบบออโตรีเกรสซีฟ — ไม่มีอะไรคำนวณใหม่ ทุกอย่างถูกจดจำ นั่นแหละคือกลทั้งหมดที่ทำให้การสร้างข้อความเร็ว: key และ value ของแต่ละ token คำนวณครั้งเดียวแล้วเก็บไว้ พื้นที่ต่อ token คงที่ ดังนั้นพื้นที่รวมจึงไล่เชิงเส้นตามบริบท 128k ไม่ใช่ “ตัวเลขที่ใหญ่ขึ้น” — มันคือแคชของหน้าต่าง 1k คูณ 128 และถูกจองทิ้งไว้ทั้งเซสชัน
Grouped-query attention (GQA) คือส่วนลดฝั่งโมเดล: KV head น้อยลง แคชผอมลง Qwen2.5 7B ใช้ 28 เลเยอร์แต่มี KV head เพียง 4 (config อยู่ที่นี่) หน้าต่าง 128k เดิมจึงเสียเพียงราว 7 GiB ใน fp16 — การผ่อนคลายที่แท้จริง และเป็นเหตุผลหนึ่งว่าทำไมโมเดลพวกนี้ถึงดูใจดีกับเครื่องเบา ๆ
| โมเดล (fp16) | เลเยอร์ × KV head | KV cache @ 128k | น้ำหนัก | แคชเทียบน้ำหนัก |
|---|---|---|---|---|
| Llama 3.1 8B | 32 × 8 | ~16 GiB | ~16 GiB | ~100% |
| Qwen2.5 7B | 28 × 4 | ~7 GiB | ~15 GiB | ~47% |
คำนวณดูเองได้ — การตรวจสอบทั้งหมดอยู่แค่นี้:
def kv_cache_gib(layers, kv_heads, head_dim, ctx, bytes_per=2):
return 2 * layers * ctx * kv_heads * head_dim * bytes_per / 2**30
print(kv_cache_gib(32, 8, 128, 131_072)) # Llama 3.1 8B -> 16.0
print(kv_cache_gib(28, 4, 128, 131_072)) # Qwen2.5 7B -> 7.0 สิ่งที่ตัวเลขการตลาดไม่บอก
นี่คือบัญชีที่ตรงไปตรงมาของข้อโต้แย้งนี้ — รวมถึงจุดที่มันโก่งด้วย:
- prefill คือบิลใบที่สอง ก่อน token ตอบแรกจะออกมา prompt ทั้งก้อนถูกประมวลผลรอบเดียวเต็ม ๆ prompt 100k tokens หมายความว่าต้องเคี้ยว 100k tokens ก่อนเห็นตัวอักษรแรก บนการ์ด 3060 นั่นคือหน่วยนาที ไม่ใช่มิลลิวินาที
- โมเดล sliding-window ทำลายสมการนี้ — ในทางที่ดีกับคุณ สถาปัตยกรรมแนว Mistral จำกัด attention ไว้ที่หน้าต่างคงที่ต่อเลเยอร์ แคชจึงไม่โตไปเรื่อย ๆ สูตรง่าย ๆ ประเมินกลุ่มนี้เกินจริง บทความนี้พูดถึง transformer แบบเต็ม attention ซึ่งคือสิ่งที่คนส่วนใหญ่รันอยู่
- การควอนไทซ์ KV เป็นจริง แต่แค่บางส่วน llama.cpp เก็บ KV แบบ q8_0 ได้ แคชเหลือราวครึ่งหนึ่ง แลกกับความเสี่ยงคุณภาพบ้าง ครึ่งหนึ่งของ 16 GiB ก็ยังเป็น 8 GiB
- สเปกแทบไม่เขียนจำนวน KV head คุณต้องรื้น config.json เอง — ซึ่งบอกอะไรบางอย่างว่าตัวเลขนั้นถูกตั้งใจให้อ่านยังไง
ไม่มีข้อไหนทำให้บริบทยาวไร้ความหมาย RAG มีอยู่ก็เพราะการยัดหน้าต่างให้เต็มคือวิธีพูดว่า “อ่านหัวข้อ 4” ที่แพงที่สุด แต่การ์ดที่พิมพ์ “128k” โดยไม่พิมพ์บิลหน่วยความจำ ก็คือขายรถด้วยความเร็วสูงสุดแล้วไม่เอ่ยถึงถังน้ำมันเลยสักคำ
ไม่มีใครอ่าน 128,000 tokens หรอก แต่ RAM ของคุณจ่ายเงินให้ครบทุกตัวอยู่ดี
ยาคือน่าเบื่อ: ตั้งบริบทเท่าที่งานของคุณใช้จริง หน้าต่าง 32k มีต้นทุนแค่หนึ่งในสี่ของแคช 128k และครอบคลุม prompt เกือบทุกอันที่เดฟคนเดียวส่งกันจริง ๆ นี่คือวินัยบัญชีชุดเดียวกับ คณิต RAM สำหรับ LLM ท้องถิ่น — ขนาดโมเดลคือครึ่งเดียวของงบ ส่วน ระดับการควอนไทซ์ หดน้ำหนักได้ แต่แตะไม่ถึงบัญชีของแคช
ถ้าแคชคือราคาจริงของบริบท ทำไมการ์ดถึงขายหน้าต่างแต่ไม่เคยขายบิล? และรอบสารภาพ: บริบทที่คุณเติมจนถึง token สุดท้ายจริง ๆ ใหญ่สุดเท่าไร — ผลลัพธ์คุ้มกี่กิกะไบต์? คอมเมนต์เปิดอยู่ บทถัดไปของซีรีส์นี้จะยืนอยู่ฝั่งที่แพ้
FAQ
หน้าต่างบริบท 128k ใช้ RAM เท่าไร?
Llama 3.1 8B ที่ fp16 KV cache เพียงอย่างเดียวกินราว 16 GiB ที่ 131,072 tokens — เท่ากับน้ำหนักโมเดลเกือบเป๊ะ สูตร: 2 × เลเยอร์ × บริบท × KV head × head_dim × ไบต์
ทำไมบริบทยาวถึงกินหน่วยความจำมากขึ้น?
ทุก token ในแคชเก็บ tensor ของ key และ value สำหรับทุกเลเยอร์ attention บริบทเป็นสองเท่า แคชก็สองเท่า แม้ไม่เคยเติมหน้าต่างให้เต็ม การจองพื้นที่ก็ยังอยู่
ลดหน่วยความจำของ KV cache ใน LLM ท้องถิ่นอย่างไร?
ตั้งบริบทเท่าที่ใช้จริง เลือกโมเดลแบบ grouped-query attention (KV head น้อย) ควอนไทซ์ KV cache เป็น q8 ถ้ารันไทม์รองรับ หรือใช้สถาปัตยกรรม sliding-window และแบบไฮบริด
— mrsaynothing
— mrsaynothing
ความเห็นผ่านการทดสอบโหลดก่อนปล่อย เกือบทุกครั้ง
รับบทถกฉบับถัดไปทางอีเมล
อีเมลหนึ่งฉบับต่อหนึ่งโพสต์ เห็นด้วยก็ได้ หักล้างก็ได้
นี่คืออะไร?git stash: ไฟล์เดียว โดยไม่รบกวนที่เหลือ
ถ้าอ่านแล้วชอบ — ผมสร้างงานแบบนี้เป็นอาชีพ จ้างผม