คอขวดด้านประสิทธิภาพของการอนุมานโมเดลภาษาขนาดใหญ่ ไม่ได้จำกัดอยู่แค่ในมิติเดียวว่า “ความเร็วของการคำนวณเมทริกซ์คูณนั้นเร็วพอหรือไม่” อีกต่อไป
เมื่อระบบ ตัวแทนเข้ารหัสอัจฉริยะ อย่าง Claude Code, Codex, Cursor ได้พัฒนาเปลี่ยนจากผลิตภัณฑ์ระดับสาธิตมาเป็นเครื่องมือเพิ่มผลผลิตที่แท้จริง ลักษณะการรับส่งข้อมูลที่ระบบอนุมานต้องรับมือก็เกิดการเปลี่ยนแปลงขั้นพื้นฐานเช่นกัน: ความยาวของบริบทมักเกิน 50K tokens จำนวนรอบการสนทนาทะลุหลายสิบรอบ การเรียกใช้เครื่องมือ การค้นหาโค้ด การสร้างแพตช์ และการดำเนินการแก้ไขสะท้อนกลับ ต่างก็ถูกถักทอสลับกันไปมา
- TokenSpeed: A Speed-of-Light LLM Inference Engine for Agentic Workloads
- ที่อยู่บล็อก: https://lightseek.org/blog/lightseek-tokenspeed.html
- คลังโค้ด: https://github.com/lightseekorg/tokenspeed
- เนื้อหาประมาณ 8000 คำ ใช้เวลาอ่านประมาณ 35 นาที เวอร์ชันพอดแคสต์ประมาณ 22 นาที
บล็อกอย่างเป็นทางการของ LightSeek ชี้ให้เห็นว่า เพื่อรองรับแนวโน้มการเติบโตนี้ อุตสาหกรรมกำลังสร้างศูนย์ข้อมูลขนาดใหญ่ที่ต้องการพลังงานไฟฟ้าหลายสิบกิกะวัตต์ และลงทุนในระดับหลายแสนล้านดอลลาร์สหรัฐ
ในระดับขนาดนี้ แม้ปริมาณงานต่อ GPU จะเพิ่มขึ้นเพียงไม่กี่เปอร์เซ็นต์ ก็อาจกลายเป็นการประหยัดพลังการคำนวณที่มหาศาลได้ ความทะเยอทะยานของ TokenSpeed อยู่ตรงนี้: มันไม่ได้แค่ปรับแต่ง kernel ใด kernel หนึ่งเท่านั้น แต่เป็นการสร้างเอนจินการอนุมาน LLM ที่ออกแบบมาสำหรับ agentic workloads ขึ้นมาใหม่ทั้งหมด ตั้งแต่การสร้างแบบจำลอง การจัดตารางเวลา การจัดการทรัพยากร KV การนำกลับมาใช้ใหม่อย่างปลอดภัย ระบบเคอร์เนล ไปจนถึงจุดรับคำขอ
ภาพเปรียบเทียบชุดนี้เผยให้เห็นข้อได้เปรียบด้านประสิทธิภาพที่ชัดเจนของ TokenSpeed ในการอนุมานโมเดลขนาดใหญ่แบบ MoE: แกนนอนแสดงถึงอัตรา Token ของคำขอผู้ใช้ (สะท้อนถึงแรงกดดันด้านการทำงานพร้อมกัน) แกนตั้งแสดงถึงประสิทธิภาพการประมวลผลของ GPU (หน่วย: พัน Token/นาที) ครอบคลุมการกำหนดค่าแบบขนานสี่แบบคือ Attn TP4/TP8 และ MoE TP/EP ในทุกสถานการณ์ ประสิทธิภาพของ TokenSpeed ดีกว่า TensorRT-LLM โดยเฉพาะอย่างยิ่งในสถานการณ์ MoE Expert Parallel (EP) และการทำงานพร้อมกันสูง ซึ่งความแตกต่างยิ่งชัดเจน แสดงให้เห็นว่า TokenSpeed ได้ทำการเพิ่มประสิทธิภาพเชิงลึกสำหรับการกำหนดเส้นทางผู้เชี่ยวชาญและการสื่อสารข้ามอุปกรณ์ของ MoE ซึ่งช่วยลดค่าใช้จ่ายในการจัดตารางเวลาได้อย่างมีประสิทธิภาพ ในขณะเดียวกัน ประสิทธิภาพโดยรวมของ Attn TP4 สูงกว่า TP8 อย่างมีนัยสำคัญ สะท้อนให้เห็นว่าระดับความขนานของเทนเซอร์ที่สูงเกินไปจะทำให้เกิดคอขวดด้านการสื่อสาร ซึ่งจะหักล้างผลประโยชน์ด้านพลังการคำนวณ ในขณะที่ TokenSpeed ภายใต้การกำหนดค่า TP8 ยังคงรักษาข้อได้เปรียบด้านประสิทธิภาพได้ ซึ่งแสดงให้เห็นถึงความทนทานของการเพิ่มประสิทธิภาพแบบขนาน โดยรวมแล้ว TokenSpeed บรรลุ “สมดุลระหว่างโหลดและประสิทธิภาพ” ที่ดีกว่า สามารถรักษาอัตราการใช้งาน GPU ที่สูงขึ้นภายใต้การทำงานพร้อมกันของผู้ใช้ที่สูง และเหมาะกับความต้องการของภาระงานของตัวแทนที่ต้องการเวลาแฝงต่ำและปริมาณงานสูง ภาพนี้แสดงการเปรียบเทียบประสิทธิภาพการอนุมานของโมเดลขนาดใหญ่ Kimi K2.5 MoE บน GPU NVIDIA B200 สำหรับภาระงานของตัวแทน (Agentic) แกนนอนคืออัตรา Token ต่อคำขอผู้ใช้ฝั่งผู้ใช้ (Token/Sec/User แสดงถึงเวลาแฝงของบริการและแรงกดดันด้านการทำงานพร้อมกัน) แกนตั้งคือประสิทธิภาพการประมวลผล Token ฝั่ง GPU (Token/Min/GPU แสดงถึงอัตราการใช้งานฮาร์ดแวร์) โดยเปรียบเทียบประสิทธิภาพของระบบอนุมานที่พัฒนาขึ้นเอง (TS) กับ NVIDIA TensorRT-LLM (TRTLLM) ภายใต้กลยุทธ์ต่างๆ เช่น Tensor Parallel (TP), Expert Parallel (EP), Data Parallel (DP) จากแนวโน้ม การกำหนดค่าทั้งหมดแสดงให้เห็นถึง “สมดุลระหว่างโหลดและประสิทธิภาพ” โดยทั่วไป: ยิ่งอัตราคำขอของผู้ใช้สูงขึ้น (เวลาแฝงของบริการต่ำลง) ประสิทธิภาพการประมวลผลของ GPU ก็จะยิ่งลดลงเนื่องจากค่าใช้จ่ายในการจัดตารางเวลาและการสื่อสาร ข้อสรุปสำคัญ ได้แก่: ประการแรก กรอบงาน TS ที่พัฒนาขึ้นเองมีประสิทธิภาพดีกว่า TRTLLM อย่างมีนัยสำคัญ โดยเฉพาะอย่างยิ่งการกำหนดค่า TP4 ภายใต้โหลดต่ำ ประสิทธิภาพ GPU สามารถสูงถึง 1500k Token/Min/GPU ซึ่ง远超กว่าการกำหนดค่าเดียวกันของ TRTLLM ที่ 1100k ซึ่งสะท้อนให้เห็นถึงข้อได้เปรียบของการเพิ่มประสิทธิภาพเชิงลึกสำหรับโมเดล MoE และภาระงานของตัวแทน ประการที่สอง MoE Expert Parallel (EP) ดีกว่า MoE Tensor Parallel (TP) เสมอ โดยเส้นโค้ง EP ในการกำหนดค่าทั้งหมดอยู่เหนือเส้นโค้ง TP แสดงให้เห็นว่า EP เหมาะกับคุณลักษณะการกำหนดเส้นทางผู้เชี่ยวชาญของ MoE มากกว่า และลดค่าใช้จ่ายในการสื่อสารข้ามอุปกรณ์ ประการที่สาม ระดับความขนานไม่ได้ยิ่งสูงยิ่งดี ประสิทธิภาพของ TP8 ต่ำกว่า TP4 และ DP8 แย่ที่สุด แสดงให้เห็นว่าความหน่วงในการสื่อสารของระดับความขนานที่สูงเกินไปได้หักล้างผลประโยชน์ด้านพลังการคำนวณ ภายใต้ภาระงานของตัวแทน การรวมกันของระดับความขนานต่ำและ EP มีข้อได้เปรียบมากกว่า ดูข้อมูลเพิ่มเติมได้ที่: https://github.com/lightseekorg/tokenspeed/tree/main/test/agentic_benchmark
กราฟทั้งสองนี้เปรียบเทียบประสิทธิภาพเวลาแฝงของเฟส Prefill และ Decode ภายใต้กลไก MLA (Multi-Head Latent Attention) ของโมเดลขนาดใหญ่ระหว่าง TensorRT-LLM MLA และ TokenSpeed MLA (เวอร์ชันโอเพนซอร์ส OSS/ไบนารี) ในเฟส Prefill (กราฟบน) ครอบคลุมสถานการณ์ทางธุรกิจ 5 ประเภท เวอร์ชันไบนารีของ TokenSpeed มีเวลาแฝงต่ำที่สุดเสมอ โดยใน use case 3 ลดลงประมาณ 20% เมื่อเทียบกับ TensorRT-LLM แสดงให้เห็นถึงข้อได้เปรียบของการเพิ่มประสิทธิภาพโอเปอเรเตอร์แบบกำหนดเอง เวอร์ชันโอเพนซอร์สของ TokenSpeed มีเวลาแฝงสูงกว่า TensorRT-LLM เล็กน้อยในบางสถานการณ์ ซึ่งอาจหมายความว่าการเพิ่มประสิทธิภาพของเวอร์ชันโอเพนซอร์สยังไม่ได้ถูกปลดปล่อยออกมาอย่างเต็มที่ ในเฟส Decode (กราฟล่าง) มุ่งเน้นไปที่ขนาดแบตช์ที่แตกต่างกัน (4/8/16) ข้อได้เปรียบด้านเวลาแฝงของ TokenSpeed ถูกขยายเพิ่มเติม: เมื่อขนาดแบตช์เท่ากับ 16 เวลาแฝงของ TensorRT-LLM สูงถึง 338.8us ในขณะที่ TokenSpeed มีเพียง 146.3us ลดลงมากกว่า 56% แสดงให้เห็นว่ามันได้ทำการเพิ่มประสิทธิภาพเชิงลึกสำหรับคอขวดหลักของการถอดรหัส MLA (เช่น การคำนวณความสนใจ การดำเนินการลดขนาด) โดยเฉพาะอย่างยิ่งเหมาะกับความต้องการเวลาแฝงต่ำภายใต้สถานการณ์การทำงานพร้อมกันสูง โดยรวมแล้ว การใช้งาน MLA ของ TokenSpeed โดยเฉพาะเวอร์ชันที่ปรับให้เหมาะสมแบบไบนารี แสดงให้เห็นถึงข้อได้เปรียบด้านประสิทธิภาพที่สำคัญทั้งในเฟส Prefill และ Decode ซึ่งเป็นโซลูชันการใช้งานระดับล่างที่มีประสิทธิภาพมากขึ้นสำหรับการอนุมานโมเดลขนาดใหญ่
unsetunsetสารบัญunsetunset
- หนึ่ง. เริ่มต้นใช้งานอย่างรวดเร็วและเส้นทางการใช้งานขั้นต่ำ
- สอง. ทำไมภาระงานของตัวแทนถึงต้องการเอนจินการอนุมานใหม่
- สาม. ภาพรวมสถาปัตยกรรม: การแบ่งงานระหว่างระนาบการดำเนินการ Python และระนาบควบคุม C++
- สี่. ชั้นทางเข้า: แกนหลักคำขอแบบอะซิงโครนัสเบื้องหลัง CLI ที่มีน้ำหนักเบา
- ห้า. เส้นทางหลักของคำขอ: จาก tokenize ไปยังตัวจัดตารางเวลาและกลับมาหาผู้ใช้
- หก. การจัดตารางเวลาสถานะเครื่อง C++: การออกแบบระบบที่สำคัญที่สุดของ TokenSpeed
- เจ็ด. แคช KV: จากแคชเทนเซอร์กลายเป็นระบบความเป็นเจ้าของทรัพยากร
- แปด. เค้าโครง KV ของ MLA: การประนีประนอมเชิงโครงสร้างสำหรับบริบทยาวและแคช FP8
- เก้า. แบ็กเอนด์ Attention: ลงตำแหน่งโดยอัตโนมัติตามสถาปัตยกรรมโมเดลและฮาร์ดแวร์
- สิบ. ระบบ Kernel: เปลี่ยนการเพิ่มประสิทธิภาพฮาร์ดแวร์ที่แตกต่างกันให้เป็นกลไกที่ลงทะเบียนได้ อธิบายได้ และครอบคลุมได้
- สิบเอ็ด. การเพิ่มประสิทธิภาพประสิทธิภาพ MLA: สนามรบเวลาแฝงต่ำของ Blackwell ที่ TokenSpeed วางเดิมพัน
- สิบสอง. ตัวอย่างประสิทธิภาพ: สร้างขอบเขต Pareto ที่แท้จริงด้วย TPS/User และ TPM/GPU
- สิบสาม. การแยก PD และโทโพโลยีบริการ: สนามรบของระบบในระยะต่อไป
- สิบสี่. ระบบนิเวศความร่วมมือและตำแหน่งโอเพนซอร์ส: ก้าวต่อไปบนไหล่ของ TensorRT-LLM
- สิบห้า. ขอบเขตปัจจุบัน: เวอร์ชันพรีวิวหมายถึงอะไร
- บทสรุป: แก่นแท้ของ TokenSpeed คือการทำให้ปัญหาประสิทธิภาพการอนุมานเป็นระบบ
unsetunsetหนึ่ง. เริ่มต้นใช้งานอย่างรวดเร็วและเส้นทางการใช้งานขั้นต่ำunsetunset
เริ่มต้นใช้งานอย่างรวดเร็ว: การปรับใช้และทดลองใช้เวอร์ชันพรีวิว
TokenSpeed ยังคงอยู่ในขั้นตอนพรีวิว เอกสาร README ระบุอย่างชัดเจนว่าเป้าหมายหลักของเวอร์ชันนี้คือการจำลองข้อมูลประสิทธิภาพของ Kimi K2.5 on B200 และ TokenSpeed MLA[1] on B200 โครงการอยู่ในช่วงการพัฒนาอย่างรวดเร็ว ทางการไม่แนะนำให้นำไปใช้ในสภาพแวดล้อมการผลิตโดยตรง หากต้องการทดลองใช้อย่างรวดเร็ว แนะนำให้ใช้ runner container เพื่อสร้างสภาพแวดล้อมการพัฒนา
ขั้นแรก ดึงอิมเมจทางการและเริ่มคอนเทนเนอร์:
docker pull lightseekorg/tokenspeed-runner:latest
docker run -itd
--shm-size 32g
--gpus all
-v /raid/cache:/home/runner/.cache
--ipc=host
--network=host
--pid=host
--privileged
--name tokenspeed
lightseekorg/tokenspeed-runner:latest
/bin/bash
หลังจากเข้าไปในคอนเทนเนอร์ ให้โคลนโค้ดโปรเจกต์และติดตั้ง dependencies:
git clone https://github.com/lightseekorg/tokenspeed.git
cd tokenspeed
export PIP_BREAK_SYSTEM_PACKAGES=1
pip install -e "./python" --no-build-isolation
pip install -e tokenspeed-kernel/python/ --no-build-isolation
pip install -e tokenspeed-scheduler/
หลังจากกำหนดค่าสภาพแวดล้อมเสร็จแล้ว สามารถใช้คำสั่งต่อไปนี้เพื่อดูความช่วยเหลือหรือเริ่มบริการ:
tokenspeed env
tokenspeed serve --help
คำสั่งเริ่มบริการที่ง่ายที่สุดมีดังนี้:
tokenspeed serve openai/gpt-oss-20b
--host 0.0.0.0
--port 8000
--tensor-parallel-size 1
หากต้องการเริ่มการกำหนดค่า Kimi K2.5 ที่ใกล้เคียงกับตัวอย่างประสิทธิภาพทางการมากขึ้น สามารถอ้างอิงพารามิเตอร์โครงสร้างต่อไปนี้:
tokenspeed serve nvidia/Kimi-K2.5-NVFP4
--served-model-name kimi-k2.5
--host 0.0.0.0
--port 8000
--trust-remote-code
--max-model-len 262144
--kv-cache-dtype fp8
--quantization nvfp4
--tensor-parallel-size 4
--enable-expert-parallel
--chunked-prefill-size 8192
--max-num-seqs 256
--attention-backend trtllm_mla
--moe-backend flashinfer_trtllm
--reasoning-parser kimi_k2
--tool-call-parser kimi_k2
หลังจากบริการเริ่มทำงานแล้ว ไคลเอนต์ที่เข้ากันได้กับ OpenAI API สามารถเข้าถึงได้ดังนี้:
from openai import OpenAI
client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
response = client.chat.completions.create(
model="kimi-k2.5",
messages=[{"role": "user", "content": "Write a concise deployment checklist."}],
max_tokens=256,
)
print(response.choices[0].message.content)
รายละเอียดพารามิเตอร์เพิ่มเติมสามารถดูได้ที่: Getting Started[2], Launching a Server[3], Server Parameters[4]
สอง. ทำไมภาระงานของตัวแทนถึงต้องการเอนจินการอนุมานใหม่?
เฟรมเวิร์กการให้บริการ LLM แบบดั้งเดิมส่วนใหญ่ได้รับการปรับให้เหมาะสมสำหรับสถานการณ์ “ปริมาณงานแบบแบตช์” และ “การสนทนาทั่วไป” อย่างไรก็ตาม รูปแบบการรับส่งข้อมูลของ coding agent นั้นเหมือนกับการจัดการจราจรในเมืองที่ซับซ้อนมากกว่า: คำขอบางรายการมีบริบทยาวเป็นพิเศษ บางรายการต้องการขั้นตอนการถอดรหัสที่สั้นมาก บางรายการใช้พรอมต์ระบบและบริบทพื้นที่เก็บโค้ดเดียวกันซ้ำๆ และบางรายการเข้าสู่การโต้ตอบรอบถัดไปทันทีหลังจากเรียกใช้เครื่องมือ บล็อกทางการกำหนดสถานการณ์นี้ว่าเป็น agentic-inference regime
การออกแบบดั้งเดิมของ TokenSpeed คือการให้บริการโหลดประเภทนี้โดยเฉพาะจากหลักการพื้นฐาน คุณสมบัติหลัก ได้แก่:
- กลไกการสร้างแบบจำลองที่ขับเคลื่อนด้วยคอมไพเลอร์ เพื่อสร้าง placement แบบขนานและ collective โดยอัตโนมัติ
- ตัวจัดตารางเวลา C++ ประสิทธิภาพสูง
- กลไกการนำทรัพยากร KV กลับมาใช้ใหม่อย่างปลอดภัยภายใต้ข้อจำกัดของระบบประเภท
- ระบบเคอร์เนลแบบเสียบได้และเป็นชั้น รองรับตัวเร่งความเร็วที่แตกต่างกัน
- จุดเข้าคำขอ CPU ที่มีค่าใช้จ่ายต่ำแบบบูรณาการ SMG[5]
สิ่งนี้บ่งชี้ว่า TokenSpeed ไม่ใช่ “ตัวแทน vLLM อีกตัว” หรือ “wrapper น้ำหนักเบาของ TensorRT-LLM” มันพยายามตอบคำถามพื้นฐานกว่า: เมื่อแอปพลิเคชันตัวแทนผลักดันขนาดการผลิต token ไปสู่ระดับศูนย์ข้อมูล ระบบอนุมานจะรับประกันปริมาณงาน GPU, TPS ฝั่งผู้ใช้, การนำบริบทกลับมาใช้ซ้ำ และความสามารถในการทำซ้ำทางวิศวกรรมได้อย่างไร?
สาม. ภาพรวมสถาปัตยกรรม: การแบ่งงานระหว่างระนาบการดำเนินการ Python และระนาบควบคุม C++
แนวคิดการออกแบบหลักของ TokenSpeed สามารถสรุปได้ดังนี้: Python รับผิดชอบความสะดวกในการใช้งานและการทำซ้ำอย่างรวดเร็ว C++ รับผิดชอบการควบคุมการจัดตารางเวลาและความถูกต้องของทรัพยากร ในขณะที่ระบบย่อยเคอร์เนลมุ่งเน้นไปที่การเพิ่มประสิทธิภาพประสิทธิภาพที่เกี่ยวข้องกับฮาร์ดแวร์
ส่วนประกอบหลักที่ระบุไว้ใน README ได้แก่:
- Modeling layer: ใช้การออกแบบ local-SPMD ผ่านคำอธิบายประกอบ I/O placement ที่ขอบเขตโมดูล คอมไพเลอร์แบบคงที่น้ำหนักเบาจะสร้าง collective communication โดยอัตโนมัติ
- Scheduler: ประกอบด้วยระนาบควบคุม C++ และระนาบการดำเนินการ Python วงจรชีวิตของคำขอ ความเป็นเจ้าของ KV cache และจังหวะเวลาการซ้อนทับ (overlap timing) แสดงโดย finite state machine
- Kernels: ระบบเคอร์เนลแบบเป็นชั้น เสียบได้ และลงทะเบียนแบบรวมศูนย์
- Entrypoint: AsyncLLM ที่รวม SMG มีเป้าหมายเพื่อลดค่าใช้จ่ายในการประมวลผลคำขอฝั่ง CPU
การออกแบบแบบเป็นชั้นของ scheduler มีความสำคัญอย่างยิ่ง:
- ระนาบควบคุม เขียนด้วย C++ และรวมเข้ากับระบบประเภทอย่างใกล้ชิด พยายามจำกัดการเปลี่ยนสถานะและการใช้ทรัพยากรของ KV cache ในช่วงเวลาคอมไพล์
- ระนาบการดำเนินการ เขียนด้วย Python เพื่อให้นักวิจัยและวิศวกรสามารถเพิ่มโมเดล คุณสมบัติใหม่ หรือทำการทดลองใหม่ได้อย่างรวดเร็ว
การแบ่งงานนี้คล้ายกับ “ห้องนักบินและห้องเครื่องยนต์ของเครื่องบิน”: ตรรกะการควบคุมต้องมีความเสถียร เชื่อถือได้ และตรวจสอบได้ง่าย ในขณะที่ตรรกะการดำเนินการต้องมีความยืดหยุ่นสูงเพื่อให้สามารถปรับเปลี่ยนได้อย่างรวดเร็ว
สี่. ชั้นทางเข้า: แกนหลักคำขอแบบอะซิงโครนัสเบื้องหลัง CLI ที่มีน้ำหนักเบา
TokenSpeed มีวิธีการเข้าถึงสามวิธีสู่โลกภายนอก: คำสั่งบรรทัดคำสั่ง tokenspeed serve, เซิร์ฟเวอร์ HTTP ที่เข้ากันได้กับ OpenAI, และอินเทอร์เฟซ Engine.generate ในระดับ Python ส่วน CLI ได้รับการออกแบบให้เรียบง่ายมาก หน้าที่หลักคือการกระจายและจัดตารางเวลาคำสั่ง และเลื่อนการนำเข้าโมดูลที่มีน้ำหนักมากออกไปจนกว่าจะจำเป็นจริงๆ
# ที่มา: python/tokenspeed/cli.py
def main() -> None:
parser = argparse.ArgumentParser(
prog="tokenspeed",
description="TokenSpeed is a speed-of-light LLM inference engine.",
)
subparsers = parser.add_subparsers(dest="command")
serve_parser = subparsers.add_parser(
"serve",
help="Launch the TokenSpeed inference server.",
)
if len(sys.argv) >= 2 and sys.argv[1] == "serve":
from tokenspeed.runtime.utils.server_args import ServerArgs
ServerArgs.add_cli_args(serve_parser)
serve_parser.set_defaults(func=_serve)
args, extra_args = parser.parse_known_args()
if args.command is None:
parser.print_help()
sys.exit(1)
args.func(args)
จุดเริ่มต้นที่แท้จริงคือคลาส Engine จากความคิดเห็นในโค้ดสามารถเห็นได้ชัดเจนว่าระบบทั้งหมดประกอบด้วยสามส่วนหลัก: TokenizerManager, กระบวนการย่อย Scheduler และห่วงโซ่การประมวลผลเอาต์พุต ซึ่งสื่อสารกันผ่านไลบรารี ZMQ
# ที่มา: python/tokenspeed/runtime/entrypoints/engine.py
class Engine(EngineBase):
"""
The entry point to the inference engine.
- The engine consists of three components:
1. TokenizerManager: Tokenizes the requests and sends them to the scheduler.
2. Scheduler (subprocess): Receives requests from the Tokenizer Manager, schedules batches, forwards them, and sends the output tokens to the Detokenizer Manager.
3. DetokenizerManager (subprocess): Detokenizes the output tokens and sends the result back to the Tokenizer Manager.
Note:
1. The HTTP server, Engine, and TokenizerManager both run in the main process.
2. Inter-process communication is done through ICP (each process uses a different port) via the ZMQ library.
"""
ในเมธอด Engine.generate อินพุตของผู้ใช้จะถูกห่อหุ้มเป็นออบเจ็กต์ GenerateReqInput ก่อน จากนั้นจะเข้าสู่เส้นทางการประมวลผลที่แตกต่างกันสองเส้นทาง ขึ้นอยู่กับว่าเปิดใช้งานการส่งคืนแบบสตรีมหรือไม่
# ที่มา: python/tokenspeed/runtime/entrypoints/engine.py
obj = GenerateReqInput(
text=prompt,
input_ids=input_ids,
sampling_params=sampling_params,
image_data=image_data,
audio_data=audio_data,
video_data=video_data,
return_logprob=return_logprob,
logprob_start_len=logprob_start_len,
top_logprobs_num=top_logprobs_num,
token_ids_logprob=token_ids_logprob,
custom_logit_processor=custom_logit_processor,
return_hidden_states=return_hidden_states,
stream=stream,
bootstrap_host=bootstrap_host,
bootstrap_port=bootstrap_port,
bootstrap_room=bootstrap_room,
)
if stream:
return self.llm.generate_stream(obj)
else:
return self.llm.generate(obj)
API แบบซิงโครนัสไม่ได้ใช้ไคลเอนต์ IPC แบบซิงโครนัสแยกต่างหาก แต่ใช้คลาส `LLM` เพื่อเชื่อมต่อไปยังลูป asyncio ของแบ็กเอนด์:
```python
# ที่มา: python/tokenspeed/runtime/engine/llm.py
def run(self, coro) -> Any:
return asyncio.run_coroutine_threadsafe(coro, self._loop).result()
def generate(self, obj: GenerateReqInput) -> dict:
async def _one() -> dict:
gen = self.async_llm.generate_request(obj)
return await gen.__anext__()
return self.run(_one())
def generate_stream(self, obj: GenerateReqInput) -> Iterator[dict]:
q: queue.Queue[Any] = queue.Queue()
async def _drain() -> None:
pending_exc: BaseException | None = None
try:
async for item in self.async_llm.generate_request(obj):
q.put(item)
except BaseException as exc:
pending_exc = exc
finally:
if pending_exc is not None:
q.put(pending_exc)
q.put(_STREAM_END)
asyncio.run_coroutine_threadsafe(_drain(), self._loop)
while True:
item = q.get()
if item is _STREAM_END:
return
if isinstance(item, BaseException):
raise item
yield item
ความสำคัญของการออกแบบนี้คือ: ฝั่งธุรกิจสามารถใช้อินเทอร์เฟซแบบซิงโครนัสได้ ในขณะที่ภายในยังคงรักษา AsyncLLM เป็นแกนหลักเดียว หลีกเลี่ยงปัญหาการแยกสถานะที่เกิดจากเส้นทางคู่แบบซิงโครนัส/อะซิงโครนัส
ห้า. เส้นทางหลักของคำขอ: จาก tokenize ไปยังตัวจัดตารางเวลาและกลับมาหาผู้ใช้
AsyncLLMทำหน้าที่เป็นฟรอนต์เอนด์แบบอะซิงโครนัสของกระบวนการหลัก รับผิดชอบการรับคำขอ การจัดการสถานะคำขอ การสื่อสาร IPC ของ scheduler และการกระจายเอาต์พุตลูป แกนหลักของเส้นทางการสร้างอยู่ที่generate_request:
# ที่มา: python/tokenspeed/runtime/engine/async_llm.py
async def generate_request(
self,
obj: GenerateReqInput | EmbeddingReqInput,
):
created_time = time.time()
self.auto_create_handle_loop()
self.input_processor.validate_request(obj)
obj.normalize_batch_and_arguments()
async with self.model_update_lock.reader_lock:
is_single = obj.is_single
if is_single:
tokenized_obj = await self._tokenize_one_request(obj)
self._send_one_request(obj, tokenized_obj, created_time)
async for response in self._wait_one_response(obj):
yield response
else:
async for response in self._handle_batch_request(obj, created_time):
yield response
async def _tokenize_one_request(
self,
obj: GenerateReqInput | EmbeddingReqInput,
) -> TokenizedGenerateReqInput | TokenizedEmbeddingReqInput:
return await self.input_processor.tokenize_one_request(obj)
โปรดสังเกต model_update_lock.reader_lock ที่นี่ มันบ่งชี้ว่า TokenSpeed นำการอัปเดตน้ำหนักออนไลน์และการสร้างคำขอมาอยู่ภายใต้กลไกการควบคุมการทำงานพร้อมกันเดียวกัน เพื่อให้แน่ใจว่าน้ำหนักโมเดลจะไม่ถูกสลับอย่างไม่ปลอดภัยในระหว่างการอนุมาน
ตรรกะในการส่งคำขอไปยัง scheduler มีดังนี้:
# ที่มา: python/tokenspeed/runtime/engine/async_llm.py
def _send_one_request(
self,
obj: GenerateReqInput | EmbeddingReqInput,
tokenized_obj: TokenizedGenerateReqInput | TokenizedEmbeddingReqInput,
created_time: float | None = None,
):
state = ReqState(
RequestOutputCollector(),
False,
asyncio.Event(),
obj,
created_time=created_time,
tokenized_time=tokenized_obj.created_time,
)
self.rid_to_state[obj.rid] = state
self.engine_core_client.send_to_scheduler.send_pyobj(tokenized_obj)
### ภาพรวมลิงก์ทั้งหมด
กระบวนการประมวลผลทั้งหมดสามารถสรุปได้ดังนี้:
HTTP/OpenAI/Python API
→ Engine.generate / async_generate
→ AsyncLLM.generate_request
→ InputProcessor.tokenize_one_request
→ EngineCoreClient.send_to_scheduler
→ C++ Scheduler สร้าง ExecutionPlan
→ Python ดำเนินการ forward/cache op
→ เอาต์พุตกลับไปยัง AsyncLLM.handle_loop
→ OutputProcessor ปลุก ReqState ที่เกี่ยวข้อง
→ stream หรือ final response กลับไปยังผู้ใช้
## หก. การจัดตารางเวลาสถานะเครื่อง C++: การออกแบบระบบที่สำคัญที่สุดของ TokenSpeed
บล็อกทางการของ TokenSpeed ชี้ให้เห็นเป็นพิเศษว่า: scheduler แยกระนาบควบคุมและระนาบการดำเนินการออกจากกัน ระนาบควบคุมถูกนำไปใช้โดย C++ finite state machine และใช้ระบบประเภทให้มากที่สุดเท่าที่จะเป็นไปได้เพื่อให้แน่ใจถึงความถูกต้องของการเปลี่ยนสถานะ KV cache การนำทรัพยากรกลับมาใช้ใหม่ และการจัดการวงจรชีวิต
ในโค้ด สถานะคำขอไม่ได้เป็นเพียง waiting/running/finished ธรรมดา แต่ถูกแบ่งออกเป็นชุดสถานะที่ละเอียดอ่อน:
// ที่มา: tokenspeed-scheduler/csrc/fsm/states.h
// Put resources into each particular state
namespace tokenspeed::fsm {
using State = std::variant<Bootstrapping, Submitted, Prefetching, PrefetchDone, Aborting, Prefilling, PrefillDone,
Decoding, Draining, WritingBack, Retracting, Retracted, Finished>;
} // namespace tokenspeed::fsm
`std::variant` ที่นี่มีความสำคัญอย่างยิ่ง มันหมายความว่าทรัพยากรไม่ได้ถูกแขวนไว้กับฟิลด์ที่สามารถเป็นค่าว่างได้หลายฟิลด์บนออบเจ็กต์คำขออีกต่อไป แต่ถูกผูกไว้อย่างแน่นหนากับสถานะ
- ตัวอย่างเช่น คำขอที่อยู่ในสถานะ `Decoding` มี KV allocator และ req pool index ที่จำเป็นสำหรับการถอดรหัส
- คำขอที่อยู่ในสถานะ `WritingBack` จะถือ device/host node ref เพื่อให้แน่ใจว่าหน้าเว็บที่เกี่ยวข้องจะไม่ถูกปล่อยก่อนเวลาระหว่างการเขียนกลับแบบอะซิงโครนัส
ในแต่ละรอบ scheduler จะสร้าง `ExecutionPlan`:
// ที่มา: tokenspeed-scheduler/csrc/scheduler/execution_plan.h
class ExecutionPlan {
public:
template <typename OperationType>
ExecutionPlan& With(OperationType operation) {
operations_.emplace_back(operation);
return *this;
}
template <typename OperationType>
ExecutionPlan& With(std::vector<OperationType> ops) {
for (auto& op : ops) {
operations_.emplace_back(std::move(op));
}
return *this;
}
const std::vector<Operation>& Operations() const { return operations_; }
private:
std::vector<Operation> operations_;
};
`NextExecutionPlan` มีหน้าที่ล้างคำขอที่เสร็จสมบูรณ์ ข้ามคำขอที่กำลังอยู่ในระหว่างการดำเนินการแคช และสร้าง forward/cache operation จากตัวเลือกที่จัดตารางเวลาได้:
// ที่มา: tokenspeed-scheduler/csrc/scheduler/scheduler.cpp
ExecutionPlan Scheduler::NextExecutionPlan() {
ExecutionPlan plan;
std::vector<WriteBackOperation> write_back_ops;
write_back_ops = std::move(newWriteBackOperation(requests_));
std::erase_if(requests_, [](const auto& req) { return req.second->template Is<fsm::Finished>(); });
std::vector<Request*> candidates;
for (auto& [id, req] : requests_) {
if (!req->Is<fsm::Draining>() && !req->Is<fsm::Prefetching>() && !req->Is<fsm::Retracting>() &&
!req->Is<fsm::WritingBack>()) {
candidates.push_back(req.get());
}
}
auto [fwd_ops, cache_ops] = newForwardOperation(candidates);
plan.With(FlatForwardOperation{std::move(fwd_ops)});
if (!write_back_ops.empty()) {
plan.With(CacheOperation{FlatWriteBackOperation{write_back_ops}});
}
if (auto* lb = std::get_if<std::vector<LoadBackOperation>>(&cache_ops)) {
if (!lb->empty()) {
plan.With(CacheOperation{FlatLoadBackOperation{*lb}});
}
}
return plan;
}
นี่คือความแตกต่างที่สำคัญระหว่าง TokenSpeed กับตัวจัดตารางเวลาหลายตัวที่ใช้ Python เป็นหลัก: มันไม่ได้อาศัยกฎเกณฑ์และการตรวจสอบขณะรันไทม์เพื่อรับประกันความถูกต้องของทรัพยากร แต่**ทำให้ "ระยะที่คำขออยู่ในปัจจุบัน ทรัพยากรที่ถือครอง และเหตุการณ์ที่สามารถตอบสนองได้" กลายเป็นคุณสมบัติโดยธรรมชาติของระบบควบคุม**
## เจ็ด. แคช KV: จากแคชเทนเซอร์วิวัฒนาการเป็นระบบความเป็นเจ้าของทรัพยากร
> ในสถานการณ์แอปพลิเคชันตัวแทนที่มีบริบทยาว แคช KV มีมูลค่าสูงมาก **พรอมต์ระบบ บริบทพื้นที่เก็บโค้ด คำอธิบายเครื่องมือ และร่องรอยการดำเนินการในอดีตมักถูกใช้ซ้ำๆ ดังนั้น TokenSpeed จึงถือว่าแคช KV เป็นทรัพยากรที่จัดการตามหน้า และแนะนำหลายระดับ เช่น device, host, L3 storage**
ในฝั่ง scheduler rolling hash ถูกใช้เพื่อให้การจับคู่คำนำหน้าในระดับหน้า
⚠️ หมายเหตุ: เนื้อหาได้รับการแปลโดย AI และตรวจสอบโดยมนุษย์ หากมีข้อผิดพลาดโปรดแจ้ง
☕ สนับสนุนค่ากาแฟทีมงาน
หากคุณชอบบทความนี้ สามารถสนับสนุนเราได้ผ่าน PromptPay
SCAN TO PAY WITH ANY BANK
本文来自网络搜集,不代表คลื่นสร้างอนาคต立场,如有侵权,联系删除。转载请注明出处:https://www.itsolotime.com/th/archives/33573
