[vLLM] 오프라인 LLM API: 배치 추론 Python API
들어가며
vLLM은 HTTP 서버 없이도 Python 코드에서 직접 LLM 추론을 수행할 수 있는 오프라인 API를 제공한다. vllm.LLM 클래스가 그 핵심이며, 배치 추론, 임베딩, 빔서치 등 다양한 기능을 지원한다. 코드는 vllm/entrypoints/llm.py에 위치한다.
공식 문서: https://docs.vllm.ai/en/latest/getting_started/quickstart.html
공식 문서
vLLM 공식 문서: Offline Inference
핵심 구조/코드 분석
LLM 클래스 초기화
LLM 클래스는 모델명과 다양한 설정을 받아 내부적으로 LLMEngine을 초기화한다.
class LLM:
def __init__(
self,
model: str,
*,
tokenizer: str | None = None,
tensor_parallel_size: int = 1,
dtype: ModelDType = "auto",
quantization: QuantizationMethods | None = None,
gpu_memory_utilization: float = 0.9,
enforce_eager: bool = False,
**kwargs: Any,
) -> None:
engine_args = EngineArgs(
model=model,
tokenizer=tokenizer,
tensor_parallel_size=tensor_parallel_size,
dtype=dtype,
quantization=quantization,
gpu_memory_utilization=gpu_memory_utilization,
enforce_eager=enforce_eager,
**kwargs,
)
self.llm_engine = LLMEngine.from_engine_args(
engine_args=engine_args,
usage_context=UsageContext.LLM_CLASS,
)
초기화 시 EngineArgs를 통해 모든 설정이 정규화되고, LLMEngine이 v1 엔진으로 생성된다. 주목할 점은 렌더러, IO 프로세서, 인풋 프로세서가 모두 엔진에서 가져와 LLM 인스턴스에 바인딩된다는 것이다.
self.renderer = self.llm_engine.renderer
self.io_processor = self.llm_engine.io_processor
self.input_processor = self.llm_engine.input_processor
generate() 메서드
핵심 API인 generate()는 프롬프트 배치를 받아 자동으로 배칭하고 결과를 반환한다.
def generate(
self,
prompts: PromptType | Sequence[PromptType],
sampling_params: SamplingParams | Sequence[SamplingParams] | None = None,
*,
use_tqdm: bool | Callable[..., tqdm] = True,
lora_request: Sequence[LoRARequest] | LoRARequest | None = None,
priority: list[int] | None = None,
tokenization_kwargs: dict[str, Any] | None = None,
) -> list[RequestOutput]:
if sampling_params is None:
sampling_params = self.get_default_sampling_params()
return self._run_completion(
prompts=prompts,
params=sampling_params,
output_type=RequestOutput,
use_tqdm=use_tqdm,
lora_request=lora_request,
tokenization_kwargs=tokenization_kwargs,
priority=priority,
)
내부적으로 _run_completion은 프롬프트를 전처리(토크나이징, 멀티모달 처리)하고, 엔진에 요청을 제출한 뒤, tqdm 진행률 표시와 함께 결과를 수집한다.
태스크별 API 분기
LLM 클래스는 모델의 runner_type에 따라 다른 메서드를 활성화한다.
def generate(self, ...):
runner_type = self.model_config.runner_type
if runner_type != "generate":
raise ValueError(
"LLM.generate() is only supported for generative models."
)
임베딩 모델을 위한 embed(), 분류를 위한 classify(), 스코어링을 위한 score() 등이 별도로 존재하며, 각각 적절한 PoolingParams를 사용한다.
기본 샘플링 파라미터
모델 설정에서 기본 샘플링 파라미터를 자동으로 가져온다.
def get_default_sampling_params(self) -> SamplingParams:
if self.default_sampling_params is None:
self.default_sampling_params = self.model_config.get_diff_sampling_param()
if self.default_sampling_params:
return SamplingParams.from_optional(**self.default_sampling_params)
return SamplingParams()
HuggingFace의 generation_config.json에 정의된 값(temperature, top_p 등)을 자동으로 반영하므로, 사용자가 별도 지정하지 않아도 모델 제작자의 권장 설정이 적용된다.
빔서치 지원
vLLM의 빔서치는 스텝 단위로 후보를 확장하는 방식으로 구현되어 있다.
sampling_params = SamplingParams(
logprobs=2 * beam_width,
max_tokens=1,
temperature=temperature,
skip_clone=True,
)
한 번에 1토큰만 생성하되, 2 * beam_width개의 logprobs를 받아 빔 후보를 확장한다. HuggingFace Transformers의 빔서치 구현을 참고한 방식이다.
왜 이 설계인가
-
단일 인터페이스, 다중 태스크:
LLM하나로 생성, 임베딩, 분류, 스코어링을 모두 처리한다. 내부적으로runner_type으로 분기하여, 사용자는 같은 패턴으로 다른 모델을 사용할 수 있다. -
자동 배칭: 프롬프트 리스트를 한 번에 넘기면 vLLM이 메모리 제약 내에서 자동으로 배칭한다. continuous batching과 PagedAttention이 결합되어 높은 처리량을 달성한다.
-
엔진 직접 내장: 서버 모드와 달리 HTTP 오버헤드 없이 엔진을 직접 호출한다. 대량 데이터 처리나 파이프라인 통합에서 유리하다.
-
Data Parallel 제한: DP > 1일 때 단일 프로세스
LLM은 의도적으로 에러를 발생시킨다. 데드락을 방지하기 위해 명시적으로 멀티프로세스 패턴을 강제한다.
정리
vllm.LLM은 vLLM의 가장 직관적인 진입점이다. 3줄의 코드로 LLM 추론이 가능하면서도, 내부적으로는 v1 엔진의 모든 최적화(PagedAttention, continuous batching, CUDA graph)가 적용된다. 배치 데이터 처리나 평가 파이프라인을 구축할 때 가장 먼저 고려해야 할 API이다.
관련 포스트
vLLM 의 다른글
- 이전글 [vLLM] OpenAI 호환 API 서버: FastAPI 기반 HTTP 서빙
- 현재글 : [vLLM] 오프라인 LLM API: 배치 추론 Python API
- 다음글 [vLLM] Pooling Tasks: 임베딩, 분류, 스코어링
댓글