Галоўная / Артыкулы

Адыяктна групаванне дадзеных і часткова прадзейнаванне ў vLLM і SGLang

Статычныя пакеты LLM марнаваюць рэсурсы GPU на дадзенняя-падставы. Раскладка па кроках і частковая падготавка дадзенняя дапамагаюць зберагчы павольны працэўнік дэкадавання, калі новыя запіты ўсё яшчэ працуюць.

Выдавец: REACTAPP.TOP5 хв чытання1194 слоў

Крыніцы: Continuous Batching & Chunked Prefill: How vLLM and SGLang Beat Static LLM Inference

Класычныя методы групавання на рэвэрсе запрашоў заставляюць значную частку цыклаў GPU працаваць над заповненням месца, калі дужыны выходных даных мовных модэляў сільна разніцяюцца. Методы непаўзальнага групавання на рэвэрсе — популярныя ў прымененні завдзяк vLLM і SGLang пасля доследжэнняў Orca — дазваляюць дадзеўваць і адключаць секвэнцы на кожны крок обработкі токенаў. Метод групавання з падзаполненнем пазбегае таго, каб вялікія запіты заморажвалі працэс дэкадавання, які ужо йдзе. Команды платформ, якія ўсё шчэ вікорыстоўваюць статычныя пайплайны Hugging Face, сталкнуцца з обомі проблемамі, калі трафік сумішуе кораткія чат-розмовы з дужа дзьвігучымі выходамі модэляў.

1. Пастка статычнага групавання

Серверы для обработкі візуальных дадзеных і таблічных даных групуюць фіксаваныя тэнзоры — шыстнаццаць зображэнняў фармату [16, 3, 224, 224], адна процедура обработкі, шыстнаццаць адпаведных выходаў разам. Адтворыўшы такі падход для вялікіх мовных модэляў, мы сталкнуліся з падвойным спадам праўоцэсу з двух структурных прычын:

  • Непрактычныя дужынкі выходных дадзеных. Адна запчатка можа завершыцца за калькі сотняў мілісекунд; іншая ў той жа пакет можа трансляваць сотні токенаў прыбліжна калькі секунд.
  • Блакаванне праз першую запчатку. Статычныя пакеты не можу рана вернуць короткі адказ. Іх месца заповненыя токенамі-падставкамі, пакуль GPU чакае на самую дзейнаю запчатку.

У статычнам пакетаванні найпамедлейшая, найдзейнаяшая запчатка задае тэнп для всіх іншых. У неперывным пакетаванні пакет атрымліваецца як стартавая лінія: завершаныя последовальнасці адразу выходзяць, а тые, што чакаюць, займаюць вольныя месца. Падставкі-падтрымкі — гэта не мелкая неэфекывнасць: калі распад дужынкі дадзеных мае сільную хвостатую частку, што ў стандарцы для чат- і кодавых асістэнтов, гэта рэгулярна вяртвае большую частку тэорычных FLOPs на безрэзультатную работу.

2. Як працюе неперывнае пакетаванне (планаванне на рэвень ітарацый)

У зямест на тое, каб запланаваць цэлы працэс ад пачатку да канца, двахарушчык запланавае адна ітэрацыя токена:

  1. Шаг токана наперад. Адна праходка праз кожную актыўную серыю выдае роўна адні новы токен.
  2. Мглевае адключэнне. Серыі, якія выдалі <eos>, вяртаюцца і зваліваюцца з VRAM.
  3. Мглевае прабачэнне. Авалікаваная ўместнасц пераходзіць да запісанага запіту без чакання на рэшту старага пакета.

Работа на рэгламентнай гранулярнасці дазволяе ядрам займацца рэальной работай, а не заполняць нулямі, і зазвычай дае калягодзінныя падышкі працэйной спроможнасці пры падобным сярэднім часе адклёкання. Едыныця работы планавальніка скушчваецца з «полной завершэння» да «адного крока для кожнай актыўной серыі», і самэльга памяцевыя менеджэры, такія як paged attention pair, па-прыроднаму супрацоўнаюць з непаўзлівым групаваннем: слоты з’яўляюцца і знікаюць без перакаштоввання жорсткага статычнага тэнзара.

3. Проблема прадакументавання і частковае прадакументавання

Непаўзліва дэкодаванне адкрывае новы канфлікт межу прадакументаванням і дэкодаваннем:

  • Прадакументавання залежыць ад вычыслаў: мадэль прыймае цэлы запіт, каб стварыць KV-кеш. Запіт з аднамі тысячамі токенав можа зайняць сотні мілісекундаў на вялікую працу з матрыцамі.
  • Дэкодаванне залежыць ад пропускной спроможнасі каналу памяці: крокі по аднам токенам, які трываюць калькуючы дзесяткі мілісекунд, вплываюць на інтэрактыўнасць.
  • Наўмовітны планавальнік, які на час зупыняе трыдцаць актыўных процэсаў дэкодавання, каб обработаць адну вялікую запітанне, спрычынае перэрывы ў стрыме — класычныя проблемы з прадзейсвам. Разбіўка запітання на часткі паўтарае яго на фрагменты (напрыклад, на 512 токенавых частак) і перамішвае гэтыя часткі з крокамі дэкодавання, так што обработка запітання распадаецца на кілька ітэрацыяў без рэзкагіх падскокамі затрымкаў. Ужо актываныя корыстувальнікі продовжаюць прымоўляць токены; новыя корыстувальнікі таксама працуюць над запрашанням. Тая ж ідэя застосоўваецца, калі RAG вводзіць дужыя кантэксты ў серэдзіне сесіі: без разбівкі на часткі адна масовая загрузка дадзенняў можа заморозіць дзесяткі суседніх процэсаў.

    4. Порэванне прадзейсвама і пропускной спроможнасцю

    Архітэктура обслугавання Іспользацыя GPU Затрымка ў канцы (p99) Нестабільнасць між токенамі
    Статычная групаванне Нізкая (частаю менш 40%) з-бэга падпаду Высока — блакуецца з-бэга дзялёгіх секвэнсаў Нізкая пакуль не будзе змены групавання Толькі безперывная групаванне Высока (частаю вышэй 85%) з дынамічнымі слотамі Серэдняя Падвышэнне калі прыходзяць большыя падпады Безперывная + частковыя падпады Оптымальная (частаю вышэй 90%) насыценая Роўная і стабільная Мінімальны, прыгадваны час на токен

    Этыя дыяпазоны ўсунутыя; точныя цыфры залежаць ад размеру модэлю, апаратуры і сумешання запросаў. Калітэтны рангінг падтрымліваецца ў большасці публічных аналізаў: статычная групаванне прасядзе ў паўтарэнні, толькі безперывная групаванне усуне марнатратнаасць дэкодавання, але не втручанне падпадаў, а частковыя падпады вярнуць роўны стрым пад сумешаным навантажэнням.

    5. Адмініструванне сімулявання планавача на рэвеніі ў Python

    У наступным апісанні паказаны працэс прыйнятня задач, часткова падготовка даных і выключэнне задач на кожным кроці. Гэта навчальная мадэль, а не рэальны сервіс для експлуатацыі, але яна чытка паказвае логіку кантролю:

    from dataclasses import dataclass
    from typing import List
    
    
    @dataclass
    class LLMRequest:
        request_id: str
        remaining_prefill: int
        tokens_to_generate: int
        tokens_generated: int = 0
    
        @property
        def is_finished(self) -> bool:
            return self.remaining_prefill == 0 and self.tokens_generated >= self.tokens_to_generate
    
    
    class ContinuousBatchEngine:
        """
        Simulates iteration-level scheduling with dynamic slot allocation
        and chunked prefill budgeting.
        """
        def __init__(self, max_batch_slots: int = 3, prefill_chunk_size: int = 256):
            self.max_slots = max_batch_slots
            self.chunk_size = prefill_chunk_size
            self.waiting_queue: List[LLMRequest] = []
            self.running_slots: List[LLMRequest] = []
    
        def submit_request(self, request: LLMRequest):
            self.waiting_queue.append(request)
    
        def step_iteration(self, step_number: int):
            print(f"\n--- [ITERATION STEP {step_number}] ---")
    
            # 1. Admit waiting requests if open slots exist
            while len(self.running_slots) < self.max_slots and self.waiting_queue:
                new_req = self.waiting_queue.pop(0)
                self.running_slots.append(new_req)
                print(f"  [ADMITTED] {new_req.request_id} entered running batch.")
    
            # 2. Execute one token step across running batch
            for req in self.running_slots:
                if req.remaining_prefill > 0:
                    # Process a chunk of prefill
                    chunk = min(req.remaining_prefill, self.chunk_size)
                    req.remaining_prefill -= chunk
                    print(f"  [PREFILL CHUNK] {req.request_id}: Processed {chunk} prompt tokens (Remaining: {req.remaining_prefill})")
                else:
                    # Process single decode token
                    req.tokens_generated += 1
                    print(f"  [DECODE TOKEN] {req.request_id}: Emitted token #{req.tokens_generated}/{req.tokens_to_generate}")
    
            # 3. Evict finished requests immediately
            finished = [r for r in self.running_slots if r.is_finished]
            for req in finished:
                self.running_slots.remove(req)
                print(f"  [COMPLETED & EVICTED] {req.request_id} returned to client.")
    
    
    if __name__ == "__main__":
        engine = ContinuousBatchEngine(max_batch_slots=2, prefill_chunk_size=200)
    
        # Simulate 3 incoming requests of varying prompt & response sizes
        engine.submit_request(LLMRequest("Req_A (Short)", remaining_prefill=0, tokens_to_generate=2))
        engine.submit_request(LLMRequest("Req_B (Long)", remaining_prefill=0, tokens_to_generate=4))
        engine.submit_request(LLMRequest("Req_C (Large Prompt)", remaining_prefill=400, tokens_to_generate=2))
    
        # Run 5 continuous iterations
        for step in range(1, 6):
            engine.step_iteration(step)
    

    6. Практычныя рэкамендацыі для інжынераў платформ AI

    • Не выкорыстоўвайце простыя паіплайны Hugging Face як серверы для експлуатацыі. Стандартная статычная групавання даных у transformers.pipeline ўжоўцяецца толькі ў лабораторных умовах. Мадэлі з адкрытым кодам трэба запакаваць у сістэмы з безперывной групаванням даных, такія як vLLM або SGLang.
    • Увёліце частковую падготовку даных для сумешаных навантажэнняў у чаце. У vLLM флагі, такія як --enable-chunked-prefill, абмежваюць кантэнт за рэвенію, ўбліжаючы такім чынам даўгія запіты сістэмы аб кантэксты RAG, каб гэта не паўлікало працу іншых корыстнікаў.
  • Следзіце за показнікамi p95/p99, а не толькі за сярэгамі. Сярэга токенав на секунду можа здацца нормальной, хоць практыка падготавлівых заповнэнняў яшчэ вельмі вплывае на адзінкавыя сесіі. Аналізуйце час да першага токена окрэма ад затрымкі межы токенамі, каб час чакання ў чэрзе і стан дэкадавання не зліваліся ў адну вводзячую ў глухоеце цыфру.
  • Распрацавваючы бюджэт памяці KV, вядомы ўсё пра планавальнік. Пастаянная групаваўка дапамагае толькі тады, калі незаканчаныя последовасці можаць чыста пакінуцца, а новыя — увайсці; перанасытаная VRAM прыводзіць да таго, што новыя даны не можаць буць прыjęтыя.
  • Якшча аднаго разу рассмотрыць усё, пастаянная групаваўка і частковыя падготавлівыя заповнэння ёсць стандартным падходам у сучасных сістэмах аддачы даных з відкрытым вагам — а не додатковымі налаштаваннямі, якія прымаюцца пасля таго, калі статычны дызайн не працуе ў рэальных умовах.