• 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 ในขั้นแพ็กเกจ
    • ตรรกะการรันแบบกำหนดเอง: vLLM backend ตั้งสมมติฐานว่ามีโมเดลมาตรฐานที่เข้ากันได้กับ HuggingFace และวงจรชีวิต inference เต็มรูปแบบ
      • การรันที่ไม่เป็นมาตรฐาน เช่น pre/post-processing แบบกำหนดเอง, ensemble pipeline, tokenization แยกต่างหาก ต้องใช้ Python backend ที่ควบคุม execute() ได้
      • บางโมเดลยังต้องใช้เส้นทางอ้อมนี้ต่อไป

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 ต่อไป และร่วมมือกับชุมชนที่เกี่ยวข้อง

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น