ინფერენციის ძრავა, როგორიცაა vLLM ან HuggingFace TGI, მოიცავს რიგს. რატომ გვჭირდება რიგი აქ? იმიტომ, რომ GPU-ზე გამოთვლები უფრო ეფექტურია და რესურსებს ნაკლებად მოიხმარს, როდესაც ისინი ხორციელდება ჯგუფურად და არა ინდივიდუალური მოთხოვნებისთვის იზოლირებულად. ეს ბექენდის რიგი საშუალებას აძლევს მგეგმავს, შეარჩიოს მრავალი მოთხოვნა და განათავსოს ისინი ერთსა და იმავე ჯგუფში დასამუშავებლად. აღსანიშნავია, რომ, როგორც წესი, თითოეული ინფერენციის ძრავა მხოლოდ ერთ მოდელს ემსახურება, და ჩვენ პარალელურად გვაქვს მრავალი განთავსება (deployment) სხვადასხვა მოდელისთვის. როდესაც ერთი მომხმარებელი "A" აგზავნის დიდი რაოდენობით მოთხოვნებს, ისინი სწრაფად ავსებენ რიგს. სხვა მომხმარებლები ("B" და "C"), რომლებიც თავიანთ მოთხოვნებს მალევე აგზავნიან, დაბლოკილნი არიან იმავე მოდელის გამოყენებისგან (სანამ "A"-ს ყველა მოთხოვნა არ დამუშავდება). გაითვალისწინეთ, რომ სურათი ფოკუსირებულია vLLM-ზე, როგორც ინფერენციის ძრავაზე, მაგრამ პრობლემა უფრო ზოგადია და ვრცელდება სხვა ბექენდებზეც. TNG-ში მომხმარებლები მოთხოვნებს პირდაპირ vLLM ბექენდზე კი არ აგზავნიან, არამედ API სერვერზე (რასაც ჩვენ "LLM-სერვერს" ვუწოდებთ). აქ ჩვენ შეგვიძლია გვქონდეს ცალკეული რიგები თითოეული მომხმარებლისთვის (და მოდელისთვის), და მგეგმავი, რომელიც არ არის FIFO (პირველი შესული - პირველი გამოსული), არამედ მოქმედებს მრგვალი მაგიდის პრინციპით (round-robin) ყველა მომხმარებლის რიგში. ეს უზრუნველყოფს გარკვეულ "სამართლიან დაგეგმვას": მაგალითად, დიაგრამაზე მომხმარებლები B და C თავიანთ მოთხოვნას ცოტა მოგვიანებით აგზავნიან, როდესაც მომხმარებელ A-ს პირველი მოთხოვნა უკვე დაგეგმილია. ამ დროს, მომხმარებელ A-ს სამი მოთხოვნა უკვე ელოდებოდა გარკვეული დროის განმავლობაში, მაგრამ მომხმარებელ C-ს მხოლოდ ერთი მოთხოვნის დასრულება უწევს ლოდინი. მთავარი იდეაა: სხვადასხვა მომხმარებლის მოთხოვნების პრიორიტეტიზაცია ჩვენს საკუთარ კომპონენტში და არა ინფერენციის ბექენდში! როგორც წესი, მოთხოვნების რიგის შეცვლა შეუძლებელია მას შემდეგ, რაც ისინი ინფერენციის ძრავას გაეგზავნება, ამიტომ მათი სწორ რიგში განთავსება უნდა მოხდეს, სანამ ისინი ჯერ კიდევ LLM-სერვერში არიან, სადაც ჩვენ გვაქვს სრული კონტროლი. შეგიძლიათ განიხილოთ სხვადასხვა ასპექტები იმის გადასაწყვეტად, თუ რა არის "სამართლიანი" დაგეგმვა. ზემოთ მოყვანილ მაგალითში, ნებისმიერ ახალ მომხმარებელს ერთი მოთხოვნით უნდა მოემსახუროს მანამდე, სანამ რომელიმე მომხმარებელს ზედიზედ ორჯერ ან მეტჯერ მოემსახურება. ეს არის "სამართლიანი" მოთხოვნების რაოდენობის დონეზე. მაგრამ თქვენ ასევე შეგიძლიათ შეხედოთ დამუშავების დროს: რამდენ ხანს იკავებს მოთხოვნა ბექენდს? გრძელი პრომპტები და გრძელი გენერაციები "დაბლოკავს" LLM-ს სხვა მომხმარებლებისთვის უფრო დიდი ხნით, ამიტომ შესაძლოა მოკლე მოთხოვნებს უნდა ჰქონდეთ უპირატესობა? სამწუხაროდ, გენერაციის სიგრძის შეფასება ძალიან რთულია. მიუხედავად იმისა, რომ არსებობს მოთხოვნები "max_tokens" ლიმიტით, ინტერაქტიული AI ასისტენტის ტიპურ ჩატის შეტყობინებას არ აქვს ტოკენის ლიმიტი და შეიძლება განსხვავდებოდეს ძალიან მოკლე გენერაციას ("ამ ტექსტის შეჯამება") და ძალიან გრძელს ("მომიყევი ამბავი" / "დაწერე მთელი კოდი xyz-ისთვის") შორის. პრომპტებიდან გამომდინარე, შეიძლება იყოს გარკვეული სარგებელი მოთხოვნების მსგავსების მიხედვით დალაგებაში, რათა vLLM-მა მაქსიმალურად გაზარდოს ქეშის ჰიტები, რაც აჩქარებს მუშაობას. KV-ქეშის ამგვარმა მარშრუტიზაციამ ცოტა ხნის წინ მოიპოვა ყურადღება ისეთი ფრეიმვორკებისგან, როგორიცაა NVIDIA Dynamo და AIBrix. ბიზნეს კონტექსტში და განთავსებული LLM-ებისთვის, ინდივიდუალური მოთხოვნების ღირებულება შეიძლება იყოს კიდევ ერთი გასათვალისწინებელი მეტრიკა, მაგრამ მას თან ახლავს მსგავსი გამოწვევები. გამოსავალი ასევე შეიძლება გაფართოვდეს არა მხოლოდ ერთი რიგის ქონით თითო მომხმარებელზე (და მოდელზე), არამედ რამდენიმე რიგის შექმნით, განსხვავებული პრიორიტეტებით. მაგალითად, ინტერაქტიულ აპლიკაციებს, როგორიცაა TNG-ის AI ასისტენტი ჩატის ინტერფეისით, უფრო მაღალი პრიორიტეტი უნდა ჰქონდეთ, რადგან მომხმარებლები, რომლებიც ხუთი წამის განმავლობაში ვერ ხედავენ პროგრესს, იფიქრებენ, რომ აპლიკაცია გაფუჭებულია. მომხმარებლები, რომლებიც აწარმოებენ კოდის მიმოხილვებს ათობით ფაილისთვის და რამდენიმე ათასი ხაზის კოდისთვის, თუმცა, მოელიან, რომ LLM მოთხოვნებს დასჭირდება გარკვეული დრო. და ზოგიერთ გამოყენების შემთხვევას (როგორიცაა ბენჩმარკის გაშვება, დაგეგმილი ჯგუფური API-ის საშუალებით) უნდა ჰქონდეს ისეთი დაბალი პრიორიტეტი, რომ მათ ხელი არ შეუშალონ სხვა გამოყენების შემთხვევებს და მხოლოდ მაშინ დაგეგმონ, როცა სხვა არაფერი მუშაობს. კიდევ ერთხელ განვიხილოთ სცენარი, რომ ერთი მომხმარებელი A ერთდროულად აგზავნის ბევრ მოთხოვნას, ხოლო გარკვეული დროის შემდეგ ახალი მომხმარებელი C უერთდება და სურს ერთი მოთოვნის დაგეგმვა. თუ LLM-სერვერის მხარეს მგეგმავი ყოველ მოთხოვნას (სამართლიანი პრიორიტეტიზაციის მიხედვით) დაუყოვნებლივ გაუგზავნიდა ბექენდს, A-ს ყველა მოთხოვნა კვლავ დაგროვდებოდა FIFO რიგში. ახალ მომხმარებელს C-ს კვლავ მოუწევდა ლოდინი, სანამ ყველა ადრე მიღებული მოთხოვნა არ დამუშავდებოდა. თითქმის არ იქნებოდა გაუმჯობესება საწყის სცენართან LLM-სერვერის გარეშე. იდეალურ შემთხვევაში, შესაძლებელი იქნებოდა ბექენდის რიგში მაქსიმალური ელემენტების რაოდენობის შეზღუდვა, მაგრამ vLLM არ გაძლევთ ამის საშუალებას. ამიტომ, ჩვენ უნდა დინამიურად მოვარგოთ სიჩქარე, რომლითაც LLM-სერვერის მხარეს მგეგმავი აგზავნის ახალ მოთხოვნებს ბექენდში. ჩვენი მიზანია: შევინარჩუნოთ ბექენდის რიგის სიგრძე მოკლე, რათა მინიმუმამდე დავიყვანოთ ახალი მომხმარებლების მიერ განცდილი დაყოვნებები. (უმარტივესი მიდგომა იქნებოდა სტატიკური სიხშირის ლიმიტი, მაგრამ ეს სავარაუდოდ გამოიწვევდა რესურსების არასრულფასოვან გამოყენებას, როდესაც მოთხოვნების უმეტესობა მოკლეა და მისი დაკალიბრება რთული იქნებოდა სხვადასხვა მოდელისა და დატვირთვის სქემებისთვის). იმისათვის, რომ ბექენდის რიგის სიგრძე ხელმისაწვდომი იყოს LLM-სერვერში, ჩვენ უნდა მოვიპოვოთ შესაბამისი Prometheus მეტრიკა vLLM /metrics ენდპოინტიდან. ჩვენს სამართლიან მგეგმავს მხოლოდ უფლება აქვს გაგზავნოს