พูดบทสรุปก่อน: แชทบอทบริการลูกค้าเผาเงิน ส่วนใหญ่ไม่ใช่เพราะราคาต่อหน่วยของโมเดลแพง แต่เป็นเพราะวิธีเรียกใช้งานมีปัญหา ภายในทีมเรามีบอทตอบคำถามหลังการขายที่มีผู้ใช้รายวันหลักพัน เปิดใช้งานเดือนแรกบิลพุ่งไปถึงสามเท่าของงบประมาณ ตรวจสอบแล้วราคาต่อหน่วยของโมเดลไม่เปลี่ยนเลย เป็นค่าใช้จ่ายแอบแฝงจากโครงสร้างการเรียกใช้งานล้วนๆ บทความนี้เขียนกระบวนการถอดบทเรียนออกมา ใครที่กำลังทำเรื่องการปรับต้นทุน API ของโมเดลขนาดใหญ่ ลองเทียบกับบิลของตัวเองดูได้
บิลที่ผิดปกติหน้าตาเป็นอย่างไร
ลักษณะของความผิดปกติไม่ใช่ "ยอดรวมสูง" แต่คือ "โครงสร้างแปลก" เราดึงรายละเอียดการเรียกใช้งานรายวันออกมาดู พบสามจุดที่ไม่ชอบมาพากล: วันที่ปริมาณการเรียกสูงสุด จำนวน token เฉลี่ยต่อคำขอมีแนวโน้มเพิ่มขึ้น; สัดส่วนจำนวนครั้งที่ retry ใกล้เคียงสองในสิบ; คำถามเดียวกันของผู้ใช้ บางครั้งใช้แค่ไม่กี่ร้อย token แต่บางครั้งพุ่งไปถึงหมื่น token ความแปรปรวนสูงมาก สามข้อนี้รวมกัน ก็สามารถระบุได้เบื้องต้นว่าปัญหาไม่ได้อยู่ที่ฝั่งโมเดล แต่อยู่ที่ห่วงโซ่การเรียกใช้งานของเราเอง
ค่าใช้จ่ายแอบแฝงสี่อย่าง ซ่อนลึกกว่ากันไปอีก
หนึ่ง ใช้โมเดลเรือธงทำงานหยาบๆ ตอนแรกเราเอาความสะดวกเป็นหลัก ให้ทุกคำขอวิ่งผ่านโมเดลเรือธงแบบเดียวกัน แต่ในสถานการณ์บริการลูกค้า กว่าเจ็ดในสิบเป็นงานประเภท "พัสดุถึงไหนแล้ว" "คืนสินค้าอย่างไร" ซึ่งเป็นการจดจำเจตนาและตอบด้วยบทพูดสำเร็จรูป งานแบบนี้ใช้โมเดลขนาดเล็กก็เพียงพอแล้ว ต้นทุนต่างกันเป็นลำดับขนาด ให้โมเดลเรือธงตอบคำถาม "เปิดกี่โมง" ก็เหมือนขับรถบรรทุกไปส่งอาหาร
สอง context บวมแบบไร้ขีดจำกัด ในการสนทนาหลายรอบ เราใส่ประวัติข้อความทั้งหมดกลับเข้าไป พอผู้ใช้คุยถึงรอบที่สิบ แค่ประวัติก็กิน token ไปเกือบหมดแล้ว ที่ยุ่งยากกว่านั้นคือ ประวัติจำนวนมากไม่เกี่ยวข้องกับคำถามปัจจุบันเลย เกาะกลุ่มมาเฉยๆ context ไม่ใช่ยิ่งยาวยิ่งฉลาด พอเกินความยาวหนึ่งไปแล้ว การเพิ่มความแม่นยำมีจำกัด แต่ต้นทุนกลับเพิ่มเป็นเส้นตรง
สาม พายุ retry เราตั้ง retry แบบง่ายๆ เมื่อล้มเหลว แต่ไม่ได้ทำ backoff และ circuit breaker พอ upstream timeout เป็นครั้งคราว คำขอชุดเดียวกันก็ถูกยิงซ้ำแล้วซ้ำอีก ล้มเหลวหนึ่งครั้ง retry หนึ่งครั้ง retry แล้วล้มเหลวก็ retry อีก บนบิลการเรียกส่วนนี้ล้วนเป็นเงินที่เสียเปล่า ฝั่งผู้ใช้ที่เห็นก็ยังเป็น error อยู่ดี
สี่ คิดเงินซ้ำระหว่าง streaming กับ non-streaming อันนี้ถูกละเลยง่ายที่สุด ห่วงโซ่บางเส้นของเราเพื่อให้ได้ผลลัพธ์ครบถ้วนมาประมวลผลต่อ ก็เรียกแบบ non-streaming หนึ่งรอบ; ฝั่ง frontend อยากได้เอฟเฟกต์พิมพ์ดีด ก็เรียกแบบ streaming อีกหนึ่งรอบ คำถามเดียวกัน เสียเงินสองต่อ หลังเปลี่ยนมาเป็นรับแบบ streaming แล้วประกอบเองที่เครื่อง ต้นทุนซ้ำซ้อนนี้ถึงหายไป
การทำ routing แบบแบ่งชั้นลงมืออย่างไร
แนวคิดไม่ซับซ้อน: แบ่งสายตามความยากของงาน การจดจำเจตนา, การดึง slot, บทพูดสำเร็จรูป พวกนี้วิ่งผ่านโมเดลขนาดเบา; เฉพาะบทสนทนาซับซ้อนที่ต้องใช้การอนุมาน การตัดสินหลายขั้นตอน การปลอบอารมณ์จริงๆ ค่อยยกให้โมเดลเรือธง ตรงกลางเพิ่มชั้น model gateway มาทำหน้าที่ตัดสิน คำขอเข้ามาก็ผ่าน classifier ก่อน ติดแท็กงานแล้วค่อยตัดสินใจว่าจะ route ไปโมเดลไหน
เราใช้ตรรกะแบบเลือกโมเดลที่ดีที่สุดอัตโนมัติตามงาน ได้ลองวิ่งเทียบบน multi-model routing ของ SiCore TokenWorks หลังจากงานง่ายๆ ถูกสลับไปใช้โมเดลขนาดเบา ต้นทุนโดยรวมลดลงอย่างชัดเจน และตอบสนองเร็วขึ้นด้วย กุญแจตรงนี้ไม่ใช่ "ใช้โมเดลไหน" แต่คือตารางแมป "งานแบบไหนคู่กับโมเดลแบบไหน" ต้องปรับอย่างต่อเนื่อง ตอนเปิดใช้งานช่วงเริ่มต้นเราตั้งตามประสบการณ์ ผ่านไปสองสัปดาห์ก็ปรับเทียบใหม่ตามอัตราการเข้าจริง ได้ผลดีกว่าตั้งตามความรู้สึกมาก ข้อดีของการเชื่อมต่อโมเดลหลายตัวแบบรวมศูนย์ก็ปรากฏตอนนี้เอง เปลี่ยนกลยุทธ์ routing ไม่ต้องแก้โค้ดธุรกิจ ปรับที่ชั้น gateway อย่างเดียวก็พอ
เปรียบเทียบบิลก่อนและหลังปรับปรุง
ไม่บอกตัวเลขเจาะจง บอกเป็นสัดส่วน ปริมาณการเรียกรวมไม่เปลี่ยน เพราะจำนวนผู้ใช้ไม่เปลี่ยน ต้นทุนรวมลดลงประมาณหกในสิบ โดยสัดส่วนการเรียกโมเดลเรือธงลดจากเกือบหนึ่งร้อยเปอร์เซ็นต์เหลือประมาณสามในสิบ ที่เหลือถูกแบ่งไปให้โมเดลขนาดเบา การเรียกที่เกี่ยวกับ retry ลดจากเกือบสองในสิบเหลือเลขหลักเดียวเป็นเปอร์เซ็นต์ จำนวน token เฉลี่ยต่อคำขอลดลงประมาณสี่ในสิบ ส่วนใหญ่มาจากการตัด context การคิดเงินซ้ำจาก streaming กลายเป็นศูนย์ไปเลย คิดรวมทั้งหมดแล้ว จากที่เกินงบสามเท่ากลับมาอยู่ในงบแถมยังมีเหลืออีก
ตั้ง monitoring และ alert อย่างไร
เงินที่ประหยัดได้ต้องรักษาไว้ให้ได้ อาศัย monitoring ไม่ใช่ความมีวินัย เราตั้ง alert สี่ข้อ: จำนวน token ต่อคำขอเกิน threshold ก็ trigger กัน context หลุดควบคุม; อัตรา retry เกินสัดส่วนที่ตั้งไว้ก็ trigger กันพายุ retry; สัดส่วนการเรียกโมเดลเรือธงพุ่งสูงผิดปกติก็ trigger บอกว่า routing อาจล้มเหลว; ต้นทุนรายวันเพิ่มขึ้นเทียบกับวันก่อนเกิน threshold ก็ trigger สี่ข้อนี้ไม่ต้องทำซับซ้อน แค่ aggregate รายวัน เกินเส้นก็ alert ก็เพียงพอ ภายใต้โมเดลคิดเงินแบบ Token ต้นทุนสะสมแบบเรียลไทม์ รอถึงสิ้นเดือนดูบิลแล้วค่อยปรับปรุง เงินก็จ่ายออกไปแล้ว
สรุปเป็นประโยคเดียว: ต้นทุนก้อนใหญ่ของแชทบอทบริการลูกค้าอยู่ที่โครงสร้างการเรียกใช้งาน ไม่ใช่ราคาต่อหน่วยของโมเดล ทำสี่เรื่องนี้ให้แน่น ได้แก่ การทำ routing แบบแบ่งชั้น, การตัด context, การควบคุม retry, การรวม streaming ให้เป็นหนึ่ง บิลก็จะลดลงเอง ขยายอีกขั้น ถ้าในสถานการณ์ของคุณมี RAG retrieval ด้วย จำนวนผลลัพธ์ที่ดึงจาก vector database ก็ควรตรวจสอบตามแนวคิดนี้เช่นกัน