- Netflix ไม่แยก LLM ออกเป็นไซโลต่างหาก แต่รันร่วมกับโครงสร้างพื้นฐาน ML เดิม และเชื่อม vLLM กับ Triton เข้ากับระบบเสิร์ฟแบบรวมศูนย์
- vLLM ที่เลือกเป็นเอนจินหลักรองรับโมเดลแบบกำหนดเอง ดีบักง่าย มี hook สำหรับขยาย และคุ้นเคยกับสภาพแวดล้อมวิจัย อีกทั้งลดการผูกติดระหว่างโมเดลกับฟรอนต์เอนด์ผ่าน vLLM backend ของ Triton
- แม้จะให้ทั้ง gRPC เดิมและ API ที่เข้ากันได้กับ OpenAI แต่ต้องอุด ช่องว่างที่เผยออกมาในโปรดักชัน เอง เช่น การขาด
response_format, เวอร์ชัน Triton·vLLM ไม่ตรงกัน และการจัดการโมเดลที่ไม่เป็นมาตรฐาน - สำหรับการดีพลอยที่เสถียร จะใช้ กลยุทธ์ Red-Black ที่ต้นทุนต่ำเป็นหลัก และใช้กลยุทธ์ Versioned ที่คงหลายเวอร์ชันพร้อมกันเฉพาะเมื่อจำเป็นต้องเปลี่ยน I/O ที่เข้ากันไม่ได้
- นำ logits processor ที่บังคับข้อจำกัดรายคำขอใน decoding loop มาสร้างใหม่ด้วยการประมวลผลแบบ batch ของ vLLM V1 และ C++ แบบมัลติเธรด โดยมีแผนขยายต่อไปด้วย GPU fused kernel, asynchronous scheduling และโมเดลความแม่นยำต่ำ
โครงสร้างการเสิร์ฟที่ผสานเข้ากับโครงสร้างพื้นฐาน ML เดิม
- ระบบเสิร์ฟแบบรวมศูนย์ของ Netflix ที่ใช้ JVM จัดการ routing และ A/B testing, การสร้าง candidate, การดึง feature, inference, post-processing และ logging ตามแต่ละขั้นตอน พร้อมรองรับทั้งเส้นทาง real-time และ batch ที่แคชไว้
- ผู้เรียกใช้งานเข้าถึง inference ผ่าน เส้นทาง gRPC ของระบบเสิร์ฟเดิม หรือเส้นทาง HTTP โดยตรงสำหรับแอปพลิเคชัน LLM ใหม่
- ตำแหน่งที่รันขึ้นอยู่กับขนาดโมเดล
- โมเดล CPU ขนาดเล็กรันในโปรเซสเพื่อหลีกเลี่ยงต้นทุนการเรียกจากระยะไกล
- โมเดล GPU ขนาดใหญ่ทำ pre/post-processing ในเครื่อง แล้วมอบหมาย inference ให้ Model Scoring Service(MSS) ระยะไกล
- MSS ให้บริการ XGBoost, TensorFlow, PyTorch และ LLM ผ่านอินเทอร์เฟซเดียว โดย NVIDIA Triton Inference Server ที่อยู่ชั้นล่างรับผิดชอบการโหลดโมเดล การประมวลผลแบบ batch และการจัดตารางงาน GPU
- control plane ฝั่ง Java บน Triton จัดการ deployment, version management, health check, autoscaling และ multi-region rollout
- เมื่อผู้พัฒนาโมเดลแพ็กเกจ artifact และการตั้งค่า deployment ระบบจะ provision อินสแตนซ์ GPU และกำหนดค่า Triton
- การอัปเกรดถูกประสานงานแบบ ไม่มี downtime
เลือก vLLM เป็นเอนจิน inference หลัก
- แพลตฟอร์มระยะแรกใช้ TensorRT-LLM ซึ่งตอนนั้นมีประสิทธิภาพสูงและผสานกับ Triton ของ MSS อยู่แล้ว
- ภายในฤดูร้อนปี 2025 เอนจินโอเพนซอร์สลดช่องว่างประสิทธิภาพกับสแต็กเฉพาะทางได้เกือบหมด และ workload ก็ขยายไปยังขอบเขตต่อไปนี้
- การสร้าง embedding
- inference แบบ prefill-only สำหรับ ranking และ search
- autoregressive decoding
- โมเดลแบบกำหนดเองที่มีตรรกะ constraint เป็นขั้นตอนซับซ้อน
- หลัง benchmark workload เหล่านี้อีกครั้ง จึงเลือก vLLM เป็นเอนจินเส้นทางหลักตามความเหมาะสมด้านปฏิบัติการ
- โหลดสถาปัตยกรรมโมเดลแบบกำหนดเองได้โดยไม่ต้องคอมไพล์หลายขั้น ทำให้การ iterate โมเดลที่ไม่เป็นมาตรฐานเร็วขึ้น
- มี hook สำหรับขยายเพื่อใส่ตรรกะ decoding แบบกำหนดเอง
- ตรวจสอบ failure และสถานะระหว่างทางได้ง่ายกว่า TensorRT-LLM รุ่นแรกที่เป็นเอนจินแบบคอมไพล์
- ผู้ปฏิบัติงาน ML จำนวนมากคุ้นเคยกับ vLLM อยู่แล้วในขั้นวิจัย จึงลดต้นทุนการย้ายสู่โปรดักชัน
วิธีแพ็กเกจ Triton และ vLLM
- Triton มีเส้นทางแพ็กเกจ 2 แบบคือ Python backend และ vLLM backend โดยความต่างหลักคือการอัปเกรดฟรอนต์เอนด์กับ model artifact ผูกติดกันมากแค่ไหน
- ใน Python backend ผู้พัฒนากำหนดสเปก tensor ขาเข้า/ขาออก ณ ตอนแพ็กเกจ
- สเปกถูกตรึงไว้กับ artifact และต้องตรงกับ request builder ของฟรอนต์เอนด์ภายนอก
- หากการอัปเกรดฟรอนต์เอนด์ทำให้ I/O เปลี่ยน ก็ต้องแก้โค้ดแพ็กเกจตามไปด้วย ไม่เช่นนั้นคำขอขณะรันจะล้มเหลว
- artifact ของ vLLM backend ประกอบด้วยการตั้งค่า JSON ที่ชี้ไปยังน้ำหนักโมเดลและ tokenizer
- ตอน deployment Triton backend จะสร้างสเปก I/O tensor แบบไดนามิก
- ผู้พัฒนาโมเดลไม่ต้องกำหนดสเปก tensor และสามารถเปลี่ยนโมเดลกับฟรอนต์เอนด์ได้อย่างอิสระ
- ค่าเริ่มต้นคือ vLLM backend แต่พบข้อจำกัด 2 อย่างในโปรดักชัน
- เวอร์ชันไม่ตรงกัน: Triton backend ถูกคอมไพล์โดยอิง vLLM API บางเวอร์ชัน หากสองเวอร์ชันคลาดกัน จะโหลด backend ทั้งหมดไม่ได้
- เช่น Triton 25.09 import
vllm.engine.metricsแต่โมดูลดังกล่าวถูกลบใน vLLM 0.11.2 - ตอนสร้าง service image ต้องตรึงเวอร์ชันที่เข้ากันได้ และไม่ให้ผู้พัฒนาโมเดล override เวอร์ชัน vLLM ในขั้นแพ็กเกจ
- เช่น Triton 25.09 import
- ตรรกะการรันแบบกำหนดเอง: vLLM backend ตั้งสมมติฐานว่ามีโมเดลมาตรฐานที่เข้ากันได้กับ HuggingFace และวงจรชีวิต inference เต็มรูปแบบ
- การรันที่ไม่เป็นมาตรฐาน เช่น pre/post-processing แบบกำหนดเอง, ensemble pipeline, tokenization แยกต่างหาก ต้องใช้ Python backend ที่ควบคุม
execute()ได้ - บางโมเดลยังต้องใช้เส้นทางอ้อมนี้ต่อไป
- การรันที่ไม่เป็นมาตรฐาน เช่น pre/post-processing แบบกำหนดเอง, ensemble pipeline, tokenization แยกต่างหาก ต้องใช้ Python backend ที่ควบคุม
- เวอร์ชันไม่ตรงกัน: Triton backend ถูกคอมไพล์โดยอิง vLLM API บางเวอร์ชัน หากสองเวอร์ชันคลาดกัน จะโหลด backend ทั้งหมดไม่ได้
gRPC และ HTTP API ที่เข้ากันได้กับ OpenAI
- ประเมินทุกอย่างตั้งแต่ XGBoost ensemble ไปจนถึง LLM ขนาดใหญ่ด้วย การเรียก gRPC แบบเดียวกัน เพื่อนำ client library, health check และ deployment pipeline เดิมกลับมาใช้
- เนื่องจาก inference engine, orchestration framework, evaluation tool และ client library ในระบบนิเวศ LLM ใช้อินเทอร์เฟซที่เข้ากันได้กับ OpenAI จึงให้บริการควบคู่กับ gRPC
- การคง API เดิมทำให้เมื่อต้องย้ายจากโมเดลโฮสต์ไปยังโมเดล self-hosted ที่ fine-tune เอง ด้วยเหตุผลด้านคุณภาพ latency ต้นทุน หรือ data privacy ต้องแก้โค้ดเพียงเล็กน้อย
- การใช้งานนำ ฟรอนต์เอนด์ที่เข้ากันได้กับ OpenAI ของ Triton จาก NVIDIA มาใช้ซ้ำ
- เริ่ม Triton server แบบฝังในตัว
TritonLLMEngineแปลง request schema เป็นคำขอ inference ของ Triton- ส่ง response ผ่าน FastAPI
- เปิดใช้ KServe HTTP/gRPC frontend ร่วมด้วย เพื่อให้ control plane ฝั่ง Java เข้าถึง Triton instance เดียวกันผ่าน gRPC ได้
- พบปัญหาที่ฟรอนต์เอนด์ ทิ้ง
response_formatไปเงียบ ๆ ก่อนส่งต่อให้ vLLM ทั้งที่ schema อนุญาตไว้- แม้ร้องขอเอาต์พุต JSON ก็อาจรันโดยไม่มี guided decoding constraint และคืน JSON ที่ไม่ถูกต้อง อีกทั้งไม่แสดง platform error
- นำฟรอนต์เอนด์เข้ามาเป็น Git subtree แล้ว patch ให้แปลงคำขอ
response_formatเป็นพารามิเตอร์ guided decoding ของ vLLM
กลยุทธ์ deployment โมเดลแบบไม่มี downtime
- การดีพลอย GPU มีเวลาเริ่มต้นนานกว่าบริการ CPU และ I/O schema อาจเปลี่ยนระหว่างเวอร์ชันโมเดล จึงต้องประสานงานเพิ่มเติมสำหรับ rollout ที่ไม่หยุดรับคำขอ
- Red-Black deployment วางเวอร์ชันใหม่ไว้ข้างเวอร์ชันเดิม แล้วค่อย ๆ ย้ายทราฟฟิกเมื่อผ่าน health check
- scale up/scale down เวอร์ชันใหม่และเดิมในอัตราเดียวกัน
- หากล้มเหลวในขั้นใดก็ rollback แบบ atomic
- เหมาะเมื่ออินเทอร์เฟซโมเดลเสถียร
- หาก I/O schema เปลี่ยน เช่น มิติ tensor ใหม่ จะเกิด coordination gap ใน Red-Black
- ก่อนที่โมเดลใหม่จะ active เต็มที่ consumer ชั้นบนยังเปลี่ยนการตั้งค่าไม่ได้
- ในช่วงเปลี่ยนผ่าน หากคำขอรูปแบบเดิมถูกส่งไปยัง deployment ใหม่ จะล้มเหลว
- Versioned deployment แก้ปัญหานี้ด้วยการคง deployment แยกตามคู่
(modelId, modelVersion)- ให้บริการหลายเวอร์ชันพร้อมกัน ทำให้การดีพลอยโมเดลแยกจากการอัปเดต consumer
- consumer เปลี่ยนการตั้งค่าหลังเวอร์ชันใหม่พร้อมเต็มที่ ส่วนเวอร์ชันเดิมยังประมวลผลทราฟฟิก legacy ต่อไป
- deployment เก่าที่ inactive จะถูกล้าง แต่เวอร์ชันล่าสุดจะถูกเก็บไว้เสมอ
- ในช่วงที่เวอร์ชันทับซ้อนระหว่างการเปลี่ยนผ่าน ต้นทุน GPU จะเพิ่มขึ้นชั่วคราว
- แนะนำให้ใส่การตั้งค่าที่อาจเปลี่ยนได้ เช่น รูปร่าง tensor ไว้ในโมเดล inference โดยตรงเพื่อให้ เป็นอิสระจากเวอร์ชัน และใช้ Red-Black ที่มีต้นทุนต่ำ
- ใช้ Versioned เฉพาะเมื่อหลีกเลี่ยงการเปลี่ยนอินเทอร์เฟซที่เข้ากันไม่ได้ไม่ได้เท่านั้น
ขั้นตอนเริ่มต้นและแคชโมเดล
- อินสแตนซ์ vLLM-on-Triton ต้องผ่านหลายขั้นตอนการเริ่มต้นก่อนจะเปิดพอร์ต gRPC ได้
- เมื่อเริ่ม LLM ขนาดใหญ่ หากดาวน์โหลดตรงจาก S3 หรือ Hugging Face cold start จะยาวจนเกินขอบเขตที่ scheduler อนุญาต
- ตอนเผยแพร่โมเดล จะ materialize โมเดลไว้ล่วงหน้าบน Amazon FSx
- หลังจากนั้นในขั้นตอนเริ่มต้นจะใช้ระบบไฟล์ประสิทธิภาพสูงแทน object storage
- ใน deployment ที่ต้องใช้ API ที่เข้ากันได้กับ OpenAI จะรัน Triton เป็น embedded server ภายในโปรเซสฟรอนต์เอนด์นั้น
- deployment อื่นจะรัน Triton แบบ standalone
- วิธีรันถูกตั้งค่าแยกตาม deployment ตอนแพ็กเกจ
- ขั้นตอนเริ่มต้นที่เหลือรวมถึงการแตกไฟล์แพ็กเกจโมเดล, การติดตั้งปลั๊กอิน vLLM แบบกำหนดเองผ่าน Python
entry_points, การล้างไดเรกทอรี Prometheus multiprocess และการบล็อกพอร์ต gRPC จนกว่าเอนจินจะพร้อม
การรวมเมตริก Triton และ vLLM
- vLLM บันทึกเมตริกเป็นไฟล์
.dbในPROMETHEUS_MULTIPROC_DIRส่วน Triton ให้เมตริกเซิร์ฟเวอร์ผ่าน Prometheus endpoint แยกต่างหาก - ทั้งสองระบบไม่รับรู้เมตริกของกันและกัน และ bridge ในตัวของ Triton แสดงเพียง 9 รายการจากเมตริกมากกว่า 40 รายการ ของ vLLM
- token throughput
- อัตราการใช้ KV cache
- ขาดตัวชี้วัดสำคัญอย่าง prefix cache hit rate
- HTTP proxy แบบเบาดึงเมตริก Triton ผ่าน HTTP และอ่านเมตริก vLLM บนดิสก์ด้วย Prometheus
MultiProcessCollectorแล้วรวมเป็น response/metricsเดียว - dashboard และ alert เดิมใช้งานต่อได้โดยไม่ต้องแก้ไข
บังคับ constraint ของเอาต์พุตระหว่าง decoding
- workload โปรดักชันบางส่วนต้องการการควบคุมการสร้าง token อย่างละเอียด จึงใช้ constraint ภายใน decoding loop แทนการ retry หรือกู้ผลลัพธ์ผิดพลาดหลัง inference
- แต่ละ constraint ถูก model เป็น state machine ที่สถานะเปลี่ยนตามประวัติ token ที่สร้าง และส่งออก mask ของ token ที่อนุญาตในแต่ละขั้น
- ใช้อินเทอร์เฟซ logits processor แบบกำหนดเองของ vLLM และเนื่องจากกฎต่างกันในแต่ละคำขอ จึงกำหนด processor ที่ตั้งค่าแยกกันให้แต่ละคำขอ
- ช่วงแรกใช้ vLLM V0 เนื่องจากช่องว่างด้านฟีเจอร์ และย้ายมา V1 ในไตรมาส 4 ปี 2025 เมื่อ V1 เติบโตพอ
คอขวดด้านการขยายใน vLLM V0
- implementation แรกที่เป็น Python ล้วนทำงานได้ตามฟังก์ชัน แต่ไม่ scale เมื่อคำขอพร้อมกันเพิ่มขึ้น
- logits processor แบบกำหนดเองของ vLLM V0 รันแยกตามคำขอ
- GPU สร้าง logits ของ batch ทั้งหมด
- CPU คัดลอกและรอจนการส่งจบ
- รันตรรกะ constraint ของแต่ละคำขอตามลำดับ
- เพราะ GIL ของ Python จึง parallelize งานรายคำขอไม่ได้ ทำให้เวลา CPU สำหรับประมวลผล logits เพิ่มตาม batch size และ tail latency สูงขึ้น
- แม้ forward pass ของโมเดลบน GPU จะ batch ได้อย่างมีประสิทธิภาพ แต่ latency รวมถูก CPU จำกัด
- คอขวดนี้ไม่ปรากฏในการ benchmark คำขอเดียว และเห็นเฉพาะเมื่อมี concurrency ระดับใช้งานจริง
การประมวลผลระดับ batch ใน vLLM V1
- vLLM V1 ย้ายการประมวลผล logits จากแบบรายคำขอเป็น ระดับ batch
- เขียน processor แบบกำหนดเองใหม่บนโครงสร้างข้อมูล batch เพื่อคำนวณ mask ของหลายคำขอพร้อมกัน
- เส้นทางหลักด้าน performance ถูกสร้างใหม่ด้วย C++ แบบมัลติเธรดเพื่อหลบ GIL และทำให้เวลา processing logits คงที่แม้ batch size โตขึ้น
- ใน V1 API ต้องติดตามการเปลี่ยนแปลงสมาชิก batch อย่างชัดเจนผ่าน
update_state(batch_update)- ซับซ้อนกว่าอินเทอร์เฟซรายคำขอของ V0
- จำเป็นต่อการรักษาสถานะรายคำขอให้ถูกต้องใน batch ที่เปลี่ยนแบบไดนามิก
การเสริมความแข็งแรงในการดำเนินงานของการจัดการ constraint แบบมีสถานะ
- หลังแก้คอขวดด้าน performance แล้ว ยังพบปัญหา 2 อย่างในตรรกะ decoding ที่มีสถานะ
-
partial prefill
- V1 ทำ prefilling เป็น chunk ดังนั้น prefill ของคำขอหนึ่งอาจกินหลาย engine step
BatchUpdateเพียงอย่างเดียวแยก prefill สมบูรณ์กับ partial prefill ไม่ได้ จึงเพิ่มการติดตามภายใน
-
preemption
- เมื่อหน่วยความจำไม่พอ vLLM อาจลบ KV cache ของคำขอที่เสร็จไปบางส่วน แล้วภายหลัง schedule ใหม่ด้วยรายการ prompt และ output token อื่น
- สิ่งนี้ทำลายสมมติฐานของ state machine ที่ว่ารายการ output token จะเพิ่มขึ้นต่อเนื่อง
- ตรวจจับว่าประวัติ token สั้นลงระหว่างขั้น decoding หรือไม่ จากนั้นรีเซ็ต state machine แล้วสร้างใหม่ด้วย prompt ใหม่
พื้นที่ลงทุนถัดไป
- แพลตฟอร์มปัจจุบันมุ่ง latency ต่ำ การปรับแต่งเชิงลึก และการผสานกับโครงสร้างพื้นฐานเดิม โดยให้เส้นทางจากการทดลองสู่โปรดักชันผ่าน vLLM และ Triton กับ API ที่สอดคล้องกัน
- ปรับปรุงความเสถียรของแพลตฟอร์มและประสบการณ์นักพัฒนา ด้วยการชดเชยเรื่องการตรึงเวอร์ชัน ฟิลด์ API ที่ถูกละทิ้งเงียบ ๆ และ trade-off ของตัวเลือกแพ็กเกจ
- วางแผนปรับปรุง 4 เรื่องต่อไปนี้
- system prompt compression เพื่อลดความยาว prompt โดยไม่เสียคุณภาพ
- asynchronous scheduling ของ vLLM V1
- vectorized logits processor ที่รันด้วย GPU fused kernel แทนโค้ด CPU
- โมเดลความแม่นยำต่ำเพื่อลดการใช้หน่วยความจำและเพิ่ม throughput
- มีแผนจะใช้ไลบรารี ML โอเพนซอร์สอย่าง Triton, vLLM, PyTorch ต่อไป และร่วมมือกับชุมชนที่เกี่ยวข้อง
ยังไม่มีความคิดเห็น