การเปิดใช้งาน prompt caching ไม่ได้ช่วยประหยัดอะไรให้ผมเลย—ในความเป็นจริง ใบแจ้งหนี้ OpenAI-API ของผมกลับพุ่งสูงขึ้นประมาณหนึ่งในสี่ สาเหตุมาจากบรรทัดเพียงบรรทัดเดียวที่เปลี่ยนไปในทุกๆ คำขอ นั่นคือ timestamp ที่ฝังอยู่ใน system prompt
ผู้ให้บริการ LLM อนุญาตให้นักพัฒนาทำ cache prompt fragments เพื่อลดต้นทุนในการประมวลผล token การอ่าน cache (หรือ “hit”) มีราคาถูกเพียงหนึ่งในสิบของอัตราปกติ ในขณะที่การเขียน cache (หรือ “miss”) จะมีราคาประมาณ 1.25 เท่าของราคาปกติ หากมีการเขียนเกิดขึ้นแต่ fragment ที่ทำ cache ไว้ไม่เคยถูกอ่านเลย ค่าธรรมเนียมส่วนเกิน 25% นั้นก็จะสูญเปล่า และนั่นคือสิ่งที่เกิดขึ้นเมื่อ timestamp ทำให้ prompt ไม่ตรงกับรายการใน cache ที่มีอยู่เดิม
ทำไมการทำ caching ถึงอาจให้ผลตรงกันข้าม
Prompt caching ทำงานโดยการจับคู่ลำดับ byte ที่ ตรงกันทุกประการ ของส่วนที่ทำ cache ไว้ ผู้ให้บริการจะทำการ hash ข้อมูลนำเข้า หาก hash ตรงกับรายการที่จัดเก็บไว้ ระบบจะนำการคำนวณก่อนหน้ามาใช้ใหม่และใช้อัตราการอ่านราคาถูก หากมีการเปลี่ยนแปลงใดๆ—แม้เพียงตัวอักษรเดียว—ก็จะทำให้การจับคู่ล้มเหลวและบังคับให้ต้องคำนวณใหม่ ซึ่งจะถูกคิดเงินในอัตราการเขียนที่สูงกว่า
ในกรณีของผม system prompt เริ่มต้นด้วย:
Current session started: 2026-07-14T09:41:07Z
เนื่องจาก timestamp มีการอัปเดตในทุกๆ การเรียก API ทำให้ byte แรกๆ ของคำขอไม่เคยเหมือนกันเลย ผู้ให้บริการจึงปฏิบัติกับทุกการเรียกเสมือนเป็นรายการ cache ใหม่ โดยคิดค่าธรรมเนียมการเขียนเพิ่ม และไม่เคยมีการบันทึกการอ่านเลย ผลลัพธ์ที่ได้คือ cache_creation_input_tokens เพิ่มขึ้นอย่างต่อเนื่อง ในขณะที่ cache_read_input_tokens ยังคงเป็นศูนย์ ซึ่งเป็นสัญญาณที่ชัดเจนว่าไม่มีการใช้งาน cache เลย
วิธีสังเกตว่า cache มีปัญหา
บันทึกการใช้งาน (usage logs) ที่ API ให้มาจะมีตัวนับสำคัญสองตัว:
- cache_creation_input_tokens – tokens ที่ทำให้เกิดการเขียน (write)
- cache_read_input_tokens – tokens ที่ได้รับประโยชน์จากการอ่าน (read)
เมื่อตัวแรกเพิ่มขึ้นและตัวหลังคงที่ แสดงว่าไม่ได้มีการนำ cache กลับมาใช้ใหม่ วิธีตรวจสอบอย่างรวดเร็วคือการส่งคำขอเดิมซ้ำสองครั้ง การเรียกครั้งที่สองควรจะแสดงจำนวน read tokens ที่เพิ่มขึ้นหาก cache ทำงานได้อย่างถูกต้อง
วิธีแก้ไขปัญหา
วิธีแก้ไขนั้นง่ายมาก: ตรวจสอบให้แน่ใจว่าส่วนที่ทำ cache นั้นเป็นแบบ static (คงที่) ในทุกๆ การเรียกใช้งาน โดยปฏิบัติตามกฎสองข้อนี้:
- วางเนื้อหาที่ไม่เปลี่ยนแปลง (immutable) ไว้ก่อน System prompts, การนิยามเครื่องมือ (tool definitions) หรือคำสั่งใดๆ ที่ไม่มีวันเปลี่ยนแปลง ควรอยู่เป็น byte แรกๆ ของคำขอ
- ต่อท้ายด้วยเนื้อหาที่เปลี่ยนแปลงได้ (mutable) ไว้หลังสุด Timestamps, ข้อความที่ผู้ใช้สร้างขึ้น, request IDs หรือข้อมูลใดๆ ที่เปลี่ยนไปในแต่ละการเรียก จะต้องอยู่หลังส่วนที่ทำ cache ไว้
หากตัวอักษรเปลี่ยนไปแม้เพียงตัวเดียว ค่า hash จะเปลี่ยนไปและจะเกิด cache miss ต่อไป การจัดเรียง prompt ใหม่เพื่อให้ timestamp อยู่ที่ส่วนท้ายจะช่วยกู้คืนอัตรา cache hit และทำให้ค่าใช้จ่ายกลับลงมาอยู่ในระดับต่ำตามที่คาดหวัง
เมื่อไหร่ที่การทำ caching จะมีประโยชน์จริงๆ
Prompt caching จะโดดเด่นมากในสถานการณ์ที่มีการใช้ชุดคำสั่งเดิมซ้ำหลายครั้ง:
- Agent loops ที่ AI เรียกใช้งานชุดเครื่องมือ (tools) ชุดเดิมซ้ำๆ
- Chat sessions ที่มีการอ้างอิงเอกสารยาวๆ ที่เป็นแบบ static ในขณะที่มีเพียงคำถามล่าสุดของผู้ใช้เท่านั้นที่เปลี่ยนไป
- Bulk data extraction ที่ใช้ prompt ในการ parse ข้อมูลแบบเดิมกับข้อมูลจำนวนมาก
สำหรับการเรียกใช้งานแบบครั้งเดียว (single-shot calls) ที่มีบริบทใหม่ในทุกครั้ง—เช่น คำถามที่ถามเพียงครั้งเดียวพร้อมบทนำที่ไม่ซ้ำกัน—การทำ caching จะไม่ให้ประโยชน์ใดๆ และอาจเพิ่มต้นทุนด้วยซ้ำหากคำขอนั้นไปกระตุ้นให้เกิดการเขียน (write) โดยไม่ตั้งใจ
กับดักที่ซ่อนอยู่
แม้ว่าตัว prompt เองจะคงที่ แต่คำขออาจถูกเปลี่ยนแปลงในขั้นตอนถัดไป (downstream):
- Proxies หรือ aggregators ที่จัดเรียงลำดับใหม่หรือแทรกช่องว่าง (whitespace) สามารถทำให้การจับคู่แบบ byte-for-byte ล้มเหลวได้
- Gateway services ที่มีการเพิ่ม authentication headers ไว้ข้างหน้า หรือแก้ไขรูปแบบ JSON อาจทำให้ fragment ที่ทำ cache ไว้เปลี่ยนแปลงไปโดยไม่ตั้งใจ
การทดสอบผ่าน gateway โดยการส่งคำขอที่เหมือนกันสองครั้งและตรวจสอบตัวนับการอ่าน (read counters) จะช่วยยืนยันว่าเส้นทางการทำ caching ยังคงทำงานได้ตามปกติ
ภาพรวมของต้นทุนในมุมที่กว้างขึ้น
ค่าธรรมเนียมส่วนเพิ่ม 25% สำหรับการเขียนไม่ใช่บทลงโทษจากการใช้ caching แต่เป็นสิ่งที่สะท้อนถึงการประมวลผลเพิ่มเติมที่จำเป็นในการจัดเก็บ fragment ไว้เพื่อนำกลับมาใช้ใหม่ในอนาคต เมื่อเกิด cache hit ต้นทุนจะลดลงอย่างมหาศาล—บ่อยครั้งเหลือเพียงเศษเสี้ยวของอัตราปกติ หัวใจสำคัญคือต้องทำให้ระบบสามารถใช้งาน cache ได้จริง มิฉะนั้นคุณจะต้องจ่ายราคาพรีเมียมโดยไม่ได้รับความประหยัดเลย
ข้อโต้แย้ง: การทำ caching ยังไม่ตาย
นักพัฒนาบางคนแย้งว่าความซับซ้อนในการจัดการส่วนของ prompt ที่เป็นแบบ static เทียบกับ dynamic นั้นไม่คุ้มกับส่วนต่างที่ประหยัดได้ แต่มุมมองนี้มองข้ามความจริงที่ว่า pipeline ในการใช้งานจริง (production) หลายแห่งมีการแยกส่วนการตั้งค่า (static) ออกจากข้อมูลผู้ใช้ (dynamic) อยู่แล้ว หากจัดโครงสร้าง prompt ให้เหมาะสม คุณจะสามารถใช้ประโยชน์จากกลไกการทำ caching แบบเดียวกับที่ช่วยประหยัดต้นทุนให้กับผู้พัฒนา API ดั้งเดิมได้โดยไม่ต้องใช้ความพยายามเพิ่มเติม สิ่งที่ต้องแลกมาคือระเบียบวินัยเล็กน้อยในการออกแบบ prompt ไม่ใช่ข้อบกพร่องพื้นฐานของเทคโนโลยี
สิ่งที่ควรทำต่อไป
- ตรวจสอบตัวนับ cache ทั้งสองตัว ใน dashboard การใช้งานของคุณทุกสัปดาห์
- ตรวจสอบการสร้าง prompt เพื่อยืนยันว่าองค์ประกอบที่มีการเปลี่ยนแปลง (variable element) อยู่ต่อจากบล็อกที่ทำ cache ไว้
- ทำการทดสอบ A/B test ทั้งแบบที่มีและไม่มีการทำ caching กับปริมาณงานที่เป็นตัวแทน เพื่อวัดผลการประหยัดที่เกิดขึ้นจริง
- ตรวจสอบความถูกต้องของ gateway โดยการเปรียบเทียบ raw request payloads ก่อนและหลังผ่าน proxy ใดๆ
บทสรุป
การทำ prompt caching สามารถลดค่าใช้จ่าย LLM API ได้อย่างมหาศาล แต่ต้องเป็นกรณีที่ส่วนที่ทำ cache นั้นเหมือนกันทุกประการในการเรียกใช้งานแต่ละครั้งเท่านั้น การมี timestamp ที่หลุดรอดมา หรือ dynamic token อื่นๆ ที่ส่วนต้นของ prompt จะบังคับให้ต้องมีการเขียนข้อมูลใหม่ที่มีราคาแพงทุกครั้ง ซึ่งจะทำให้ค่าใช้จ่ายสูงขึ้น ด้วยการนำคำสั่งแบบ static มาไว้ด้านหน้า และย้ายข้อมูลที่เปลี่ยนแปลงได้ไปไว้ที่ส่วนท้าย คุณจะช่วยให้ cache ทำงานได้อย่างเต็มประสิทธิภาพและควบคุมค่าใช้จ่ายให้อยู่ในระดับที่เหมาะสม
