กลับไปที่บล็อก

128k บริบทบนเดสก์ท็อปคือเรื่องโกหก KV cache กินมันหมด

22 กันยายน 2569

การ์ดโมเดลท้องถิ่นตกแต่ละตัวต่างอวดหน้าต่างบริบท 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 headKV cache @ 128kน้ำหนักแคชเทียบน้ำหนัก
Llama 3.1 8B32 × 8~16 GiB~16 GiB~100%
Qwen2.5 7B28 × 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

ความเห็นผ่านการทดสอบโหลดก่อนปล่อย เกือบทุกครั้ง

รับบทถกฉบับถัดไปทางอีเมล

อีเมลหนึ่งฉบับต่อหนึ่งโพสต์ เห็นด้วยก็ได้ หักล้างก็ได้

self-hosted · ไม่มีบุคคลที่สาม · ยกเลิกได้ในคลิกเดียว

นี่คืออะไร?

git stash: ไฟล์เดียว โดยไม่รบกวนที่เหลือ

ถ้าอ่านแล้วชอบ — ผมสร้างงานแบบนี้เป็นอาชีพ จ้างผม