[vLLM] EngineCore: 핵심 실행 루프
들어가며
vLLM v1에서 EngineCore는 별도 프로세스에서 실행되는 핵심 실행 루프이다. 스케줄러가 어떤 요청을 실행할지 결정하고, Executor가 GPU에서 모델을 실행하며, 결과를 다시 스케줄러에 반영하는 세 단계 반복이 step() 함수에 집약되어 있다.
소스 경로: vllm/v1/engine/core.py
핵심 구조/코드 분석
클래스 초기화
class EngineCore:
"""Inner loop of vLLM's Engine."""
def __init__(self, vllm_config, executor_class, log_stats, ...):
# Executor 생성 (GPU 워커 관리)
self.model_executor = executor_class(vllm_config)
# KV Cache 초기화 및 프로파일링
kv_cache_config = self._initialize_kv_caches(vllm_config)
# 스케줄러 생성
Scheduler = vllm_config.scheduler_config.get_scheduler_cls()
self.scheduler = Scheduler(
vllm_config=vllm_config,
kv_cache_config=kv_cache_config,
structured_output_manager=self.structured_output_manager,
...
)
초기화 과정에서 주목할 점은 KV 캐시 프로파일링이다. _initialize_kv_caches()는 모델의 피크 메모리 사용량을 프로파일링한 뒤 남은 GPU 메모리로 KV 캐시 블록 수를 결정한다.
step() - 핵심 실행 루프
def step(self) -> tuple[dict[int, EngineCoreOutputs], bool]:
if not self.scheduler.has_requests():
return {}, False
scheduler_output = self.scheduler.schedule()
future = self.model_executor.execute_model(scheduler_output, non_block=True)
grammar_output = self.scheduler.get_grammar_bitmask(scheduler_output)
model_output = future.result()
if model_output is None:
model_output = self.model_executor.sample_tokens(grammar_output)
self._process_aborts_queue()
engine_core_outputs = self.scheduler.update_from_output(
scheduler_output, model_output
)
return engine_core_outputs, scheduler_output.total_num_scheduled_tokens > 0
한 스텝은 정확히 세 단계로 진행된다:
- schedule(): continuous batching 스케줄러가 다음 배치를 구성
- execute_model(): 비동기로 GPU에서 모델 포워드 패스 실행 (Future 반환)
- update_from_output(): 모델 출력을 스케줄러에 반영 (토큰 추가, 완료 처리)
non_block=True로 실행하면서 그 사이에 grammar bitmask를 준비하는 것이 파이프라이닝의 핵심이다.
배치 큐 (Pipeline Parallelism)
self.batch_queue_size = self.model_executor.max_concurrent_batches
if self.batch_queue_size > 1:
self.batch_queue = deque(maxlen=self.batch_queue_size)
파이프라인 병렬 처리 시 여러 배치를 동시에 실행할 수 있다. step_with_batch_queue()는 큐가 가득 차지 않으면 새 배치를 스케줄링하고, 가득 차면 첫 번째 배치의 완료를 기다린다. 이를 통해 파이프라인 버블을 제거한다.
KV 캐시 초기화
def _initialize_kv_caches(self, vllm_config) -> KVCacheConfig:
kv_cache_specs = self.model_executor.get_kv_cache_specs()
available_gpu_memory = self.model_executor.determine_available_memory()
kv_cache_configs = get_kv_cache_configs(
vllm_config, kv_cache_specs, available_gpu_memory
)
self.model_executor.initialize_from_config(kv_cache_configs)
return scheduler_kv_cache_config
모델의 각 레이어가 필요한 KV 캐시 스펙을 수집하고, GPU 메모리 프로파일링 결과에 따라 블록 수를 자동 결정한다. max_model_len이 메모리에 맞지 않으면 자동으로 줄이는 auto-fit 기능도 있다.
왜 이 설계인가
-
별도 프로세스 실행: EngineCore는 ZMQ 소켓을 통해 AsyncLLM과 통신한다. 이렇게 하면 스케줄링과 GPU 실행이 API 서버의 asyncio 이벤트 루프를 블로킹하지 않는다.
-
Continuous Batching: 매 step마다 스케줄러가 새로운 요청을 추가하고 완료된 요청을 제거한다. 전통적인 static batching과 달리 GPU 활용률이 극대화된다.
-
비동기 스케줄링:
async_scheduling옵션을 켜면 모델 실행과 스케줄링을 오버랩할 수 있다. Speculative decoding의 draft token도 비동기로 업데이트된다. -
GC 최적화: 초기화가 끝나면
freeze_gc_heap()으로 힙을 고정하고enable_envs_cache()로 환경변수 조회를 캐싱한다. 이는 추론 루프에서의 마이크로 레이턴시를 줄이기 위함이다.
참고
관련 포스트
vLLM 의 다른글
- 이전글 [vLLM] AsyncLLM: 비동기 엔진의 최상위 객체
- 현재글 : [vLLM] EngineCore: 핵심 실행 루프
- 다음글 [vLLM] GPUModelRunner: GPU 모델 포워드 패스
댓글