추론·서빙 엔진
f4edf9f 기준입니다.
vLLM은 학습된 모델의 가중치를 불러와 텍스트 등을 생성하고, 그 계산을 서버 자원에 맞춰 배분하는 시스템입니다.
Python에서 LLM.generate()를 부르는 라이브러리로도, vllm serve로 실행하는 API 서버로도 쓸 수 있습니다.
이 저장소에서 배울 핵심은 모델의 정답을 바꾸는 법보다, 같은 모델로 더 많은 요청을 효율적으로 처리하는 법입니다.
주문 하나를 끝낸 뒤 다음 주문을 받으면 조리대가 자주 빕니다. 여러 주문을 함께 처리하되, 먼저 끝난 주문의 자리에 새 주문을 넣으면 같은 주방으로 더 많은 손님을 받을 수 있습니다.
GPU는 조리대, 요청은 주문표, KV 캐시는 조리 중인 재료를 놓는 공간에 비유할 수 있습니다. vLLM은 이번 차례에 무엇을 얼마나 계산하고, 그 중간 상태를 어디에 보관할지 결정합니다.
첫 진입점은 vllm/entrypoints/llm.py의 LLM.generate()입니다.
공식 예제 examples/basic/offline_inference/basic.py도 LLM과 SamplingParams를 사용합니다.
아래는 같은 API를 작은 모델에 적용한 학습용 예시입니다.
from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen3-0.6B", max_model_len=2048)
params = SamplingParams(temperature=0.0, max_tokens=64)
outputs = llm.generate(["KV 캐시를 쉬운 비유로 설명해 줘."], params)
for output in outputs:
print(output.outputs[0].text)
SamplingParams는 출력 길이와 토큰 선택 규칙을 담습니다.
위 코드는 준비된 추론 환경에서 사용하는 예시이며, 생성 문장과 실행 속도는 모델·장치·설정에 따라 달라집니다.
오프라인 추론은 HTTP 서버 없이 함수로 결과를 받는 방식입니다.
입문 단계에서는 모델 수식 전체보다 요청 → 스케줄링 → 메모리 확보 → 실행 → 정리의 경계를 잡으세요. 이 흐름은 데이터 처리 서버, 작업 큐, 게임 서버의 자원 관리에도 이어지는 시스템 설계 지식입니다.
vLLM은 2026-10-23 분석 기준 GitHub Trending Python Weekly #14로 선정된 저장소입니다. 스타 95,202개와 포크 23,834개는 넓은 관심과 활용의 신호입니다. 다만 그 숫자만으로 이번 주의 유입 원인을 특정할 수는 없습니다. 다음 세 가지는 현재 기능과 소스 구조에서 읽을 수 있는 주목할 만한 기술적 이유입니다.
큰 모델의 서빙에서는 계산을 빠르게 하는 일과, 계산할 요청을 잘 모으고 메모리를 덜 낭비하는 일이 함께 중요합니다.
커밋 f4edf9f의 제목은 Intel XPU의 커널·샘플러 단위 테스트 활성화입니다.
이는 장치별 실행 경로를 검증하는 개발이 진행 중이라는 사례이며, 주간 트렌딩의 직접 원인이라는 뜻은 아닙니다.
| 접근 / 도구 | 강점과 초점 | vLLM에서 주목할 점 |
|---|---|---|
| 직접 만든 생성 루프 | 한 요청의 모델 호출과 샘플링을 이해하기 쉽습니다. | 대기열·취소·배칭·캐시 관리를 직접 조합하는 부담을 줄입니다. |
| llama.cpp | README 기준 C/C++ 추론, 여러 장치와 CPU·GPU 혼합 실행을 지원합니다. | Python 모델 실행 계층부터 분산 서빙 제어까지 연결해 공부할 수 있습니다. |
| TensorRT LLM | NVIDIA GPU에 맞춘 커널·추론 런타임 최적화가 중심입니다. | 여러 하드웨어 백엔드와 PyTorch 기반 실행 구조를 함께 살펴볼 수 있습니다. |
| vLLM | KV 캐시·스케줄링·모델 실행·API 서빙의 통합이 중심입니다. | 모델·커널·장치의 조합에 따라 지원 범위와 실제 성능을 확인해야 합니다. |
비교 근거는 llama.cpp 공식 저장소와 NVIDIA의 TensorRT LLM 소개입니다. TensorRT LLM도 in-flight batching과 paged KV cache를 지원합니다. 따라서 이 개념들을 vLLM만의 독점 기능으로 외우면 안 됩니다.
2023년 공식 소개 글은 특정 GPU·모델·데이터 조건에서 Hugging Face Transformers와 TGI 대비 높은 처리량을 보고했습니다. 그 결과는 PagedAttention의 배경을 이해하는 자료입니다. 2026년의 모든 모델에서 같은 배수가 나온다는 근거로 사용할 수는 없습니다.
버스의 좌석 수, 운행 횟수, 승객 대기시간을 함께 봐야 하듯, 추론 엔진도 같은 모델·정밀도·입출력 길이·동시 요청 수를 맞춰 비교해야 합니다. 혼자 쓰는 작은 모델과 수백 요청을 받는 서버는 최적의 선택이 다를 수 있습니다.
pyproject.toml은 Python 배포와 빌드의 출발점입니다.
dependencies가 dynamic으로 선언되어 있어 의존성 전체가 이 파일 안에 나열되지는 않습니다.
setup.py의 get_requirements()가 대상 장치에 맞는
requirements/cuda.txt·requirements/cpu.txt·requirements/xpu.txt 등을 읽습니다.
| 기술 | 실제 선언값 | 역할 / 근거 파일 |
|---|---|---|
| Python | >=3.11,<3.15 | API·설정·스케줄러. pyproject.toml |
| PyTorch | torch==2.14.0 | 텐서·모델 실행. requirements/cuda.txt와 빌드 의존성 |
| Transformers | >=5.16.1,<5.21.0 | 모델 설정·생태계 연동. requirements/common.txt |
| tokenizers | >=0.23.2 | 토큰화와 문자열 복원 관련 의존성. requirements/common.txt |
| safetensors | >=0.6.2 | 모델 가중치 형식 지원. requirements/common.txt |
| FlashInfer | flashinfer-python==0.7.2 | GPU 커널 경로. requirements/cuda.txt |
| 양자화 | compressed-tensors==0.17.0 | 압축·양자화된 모델 표현 연동. requirements/common.txt |
| 구조화 출력 | xgrammar==0.2.8 | 문법 제약을 이용한 출력 생성. 아키텍처 조건부 의존성 |
| 프로세스 통신 | pyzmq>=25.0.0, msgspec | 엔진 경계의 메시지 통신과 직렬화. requirements/common.txt |
vLLM은 Python으로 계산 순서를 정하지만, 실제 텐서 연산은 PyTorch와 장치별 커널에 맡기는 여러 계층의 시스템입니다.
vllm/model_executor/models/llama.py의 LlamaAttention.forward()를 보면
Q·K·V 투영, 회전 위치 임베딩, Attention 호출이 연결됩니다.
Hugging Face 가중치와 호환되는 자체 추론용 모델 구현이므로, 단순히 외부 모델의 생성 함수를 감싼 구조보다 깊습니다.
==는 특정 버전을 요구하고,
>=와 <는 설치 가능한 범위를 정합니다.
범위 선언은 실제 설치 버전을 뜻하지 않습니다. 표는 커밋 f4edf9f의 의존성 선언값입니다.이 저장소에서 프런트엔드라는 말은 주로 추론 엔진 앞의 API 처리 계층을 가리킵니다.
루트에는 package.json이 없으며 React나 Vue 기반의 일반 사용자용 웹 화면을 주 구성요소로 두지 않습니다.
따라서 웹 UI를 찾기보다 라우터·렌더러·토크나이저·스트리밍 처리를 읽는 편이 목적에 맞습니다.
| 계층 | 실제 선언값 | 읽을 코드 |
|---|---|---|
| Python HTTP | fastapi[standard]>=0.133.0,<0.137.0 | vllm/entrypoints/launchers/app.py |
| 검증 / ASGI | pydantic>=2.12.0, starlette>=1.0.1 | requirements/common.txt와 각 API의 요청 자료형 |
| 스트리밍 | SSE, StreamingResponse | vllm/entrypoints/openai/chat_completion/api_router.py |
| Rust HTTP | axum="0.8.8", tokio="1.47.1" | rust/Cargo.toml, rust/src/server/ |
| Rust 직렬화 | serde="1.0.228", rmp-serde="1.3.1" | Rust API 계층과 Python 엔진 사이의 메시지 표현 |
rust/README.md는 Rust 프런트엔드를 실험적이며 기능이 완전하지 않은 대안으로 명시합니다.
VLLM_USE_RUST_FRONTEND=1로 선택하는 경로도 Python 엔진과 ZMQ 경계를 통해 통신합니다.
Rust가 존재한다고 Python 추론 엔진 전체가 Rust로 교체되었다고 해석하면 안 됩니다.
| 부품 | 실제 설정 | 배울 점 |
|---|---|---|
| 패키지 빌드 | setuptools.build_meta, setuptools>=77.0.3,<81.0.0 | pyproject.toml의 Python 배포 경계 |
| 네이티브 빌드 | cmake>=3.26.1, Ninja | Python 설치와 C++·CUDA 빌드 연결 |
| C++ / CUDA / HIP | 언어 표준 모두 20, GCC >=11.3 검사 | CMakeLists.txt의 컴파일러 호환성 관리 |
| Rust 빌드 | edition 2024, workspace 버전 0.1.0 | rust/Cargo.toml의 여러 크레이트 구성 |
| Docker | CUDA_VERSION=13.0.3, PYTHON_VERSION=3.12 | docker/Dockerfile의 여러 빌드 단계와 실행 이미지 |
| 관측 | prometheus_client>=0.18.0, OpenTelemetry >=1.27.0 | 지표 수집과 요청 추적. requirements/common.txt |
docker/Dockerfile은 rust-build, csrc-build,
extensions-build, build 등을 나눕니다.
서버 이미지인 vllm-openai 단계의 진입점은 ["vllm", "serve"]입니다.
컴파일 도구가 필요한 단계와 실제 서버 실행 단계를 나누는 컨테이너 설계를 배울 수 있습니다.
.github/workflows/pre-commit.yml은 Python 3.12에서 저장소 전체 정적 검사를 수행하도록 구성합니다.
하드웨어 테스트는 .buildkite/ci_config.yaml의
.buildkite/image_build/·.buildkite/test_areas/·.buildkite/hardware_tests/ 경로로 나뉩니다.
공통 의존성이나 csrc/ 변경은 넓은 테스트를 실행하는 패턴에 포함됩니다.
.buildkite/intel_jobs/samplers_intel.yaml에는
VLLM_TEST_DEVICE=xpu와 VLLM_XPU_USE_SAMPLER_KERNEL=1이 등장합니다.
같은 샘플링 동작도 장치 경로를 실제로 선택해야 검증된다는 의미입니다.
테스트 명령이 있다는 사실과 특정 실행에서 테스트가 통과했다는 사실은 구분해야 합니다.
pyproject.toml → setup.py → requirements/ → CMakeLists.txt → docker/ → CI 설정 순으로 읽어 보세요.
필요한 라이브러리 목록이 어떻게 설치 가능한 패키지와 실행 이미지로 바뀌는지 연결됩니다.
아래는 일반적인 Python 온라인 서빙 경로를 단순화한 그림입니다. 실제 프로세스 수와 실행기는 병렬화·장치·실행 설정에 따라 달라집니다. Rust 프런트엔드를 선택하는 경우에는 그림 맨 위의 API 처리 계층이 달라집니다.
vllm/entrypoints/openai/chat_completion/api_router.py의
create_chat_completion()은 처리 결과의 종류에 따라 JSON 응답이나
text/event-stream 응답을 반환합니다.
OpenAIServingChat._create_chat_completion()은
render_chat_request()로 입력을 준비한 뒤 engine_client.generate()를 호출합니다.
vllm/entrypoints/openai/api_server.py는 현재 여러 함수를 다시 내보내며 폐기 예정 경고를 냅니다.
서버 조립은 vllm/entrypoints/launchers/api_server/entry.py와
vllm/entrypoints/launchers/app.py부터 따라가세요.
파일 이름만 보고 오래된 구조를 현재 구조로 설명하기 쉬운 지점입니다.
vllm/v1/engine/core.py의 EngineCore.step()은
스케줄링·실행·결과 반영을 한 반복으로 묶습니다.
아래는 핵심 호출 순서를 보존하고 계측·문법 처리 등을 생략한 학습용 축약입니다.
# vllm/v1/engine/core.py: EngineCore.step()의 흐름
scheduler_output = self.scheduler.schedule(self._should_throttle_prefills())
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()
outputs = self.scheduler.update_from_output(scheduler_output, model_output)
non_block=True라고 지정해도 곧바로 모든 결과가 준비되는 것은 아닙니다.
이 경로에서는 future.result()로 결과를 기다립니다.
별도의 step_with_batch_queue()도 있으므로 이 축약을 모든 실행 모드의 유일한 시간표로 이해하면 안 됩니다.
vllm/v1/engine/async_llm.py의 AsyncLLM.generate()는
add_request()로 요청을 등록하고 요청별 수집기에서 결과를 꺼내 yield합니다.
별도의 출력 처리 루프가 엔진 결과를 요청별 수집기에 전달합니다.
API 요청을 받는 속도와 GPU가 토큰을 만드는 속도를 연결하는 구조입니다.
# AsyncLLM.generate()의 핵심 구조를 줄인 예시
q = await self.add_request(request_id, prompt, sampling_params)
finished = False
while not finished:
out = q.get_nowait() or await q.get()
finished = out.finished
yield out
# 원본은 취소·오류 분기와 종료 표지도 별도로 처리함
이 함수의 asyncio.CancelledError·GeneratorExit 처리에서는
등록된 요청을 abort()합니다.
연결이 끊어진 사용자를 위해 계속 GPU를 쓰지 않도록, 전송 계층의 취소를 엔진의 작업 정리와 연결합니다.
다른 비동기 서버에서도 유용한 자원 수명 관리 패턴입니다.
await는 기다리는 동안 다른 일을 진행할 수 있게 합니다.
여러 GPU나 프로세스가 동시에 계산하는 것은 병렬 실행입니다.
vLLM은 두 방식 모두 사용하지만, async def 하나가 GPU 계산을 병렬화하는 것은 아닙니다.vllm/v1/core/sched/scheduler.py의 Scheduler.schedule()은
각 요청의 num_computed_tokens를 필요한 토큰 수에 가까워지도록 진행시킵니다.
함수 주석도 스케줄러 내부에서 prefill과 decode를 별개 단계로 고정하지 않는다고 설명합니다.
이 관점으로 긴 입력의 분할 처리와 추측 토큰 검증을 같은 예산 체계에 담습니다.
# 실행 중인 요청을 배분하는 부분의 축약
num_new_tokens = (
request.num_tokens_with_spec
+ request.num_output_placeholders
- request.num_computed_tokens
)
num_new_tokens = min(num_new_tokens, token_budget, input_budget - draft_slots)
# 원본에는 긴 입력 제한, 모델 길이 제한, 인코더 예산 등이 추가됨
new_blocks = self.kv_cache_manager.allocate_slots(request, num_new_tokens)
# 성공한 요청의 배분을 반영하는 단계
token_budget -= num_new_tokens
작은 예: 한 반복의 예산을 8토큰이라고 가정해 봅시다. 실행 중인 A와 B가 각각 다음 1토큰을 계산한다면 6토큰이 남습니다. 긴 입력을 가진 C는 그 6토큰만 먼저 계산하고 나머지를 뒤로 넘길 수 있습니다. 이는 개념 예시이며 실제 배분에는 캐시, 인코더, 우선순위와 장치 제약이 함께 작용합니다.
엘리베이터에 사람 수 제한과 무게 제한이 따로 있듯,
max_num_seqs와 토큰 예산은 서로 다른 제약입니다.
짧은 요청 10개와 긴 요청 10개를 같은 비용으로 계산할 수 없습니다.
블록 확보에 실패하면 실행 중인 요청을 선점하는 분기가 있습니다.
Scheduler._preempt_request()는 블록을 정리하고 상태를 PREEMPTED로 바꾼 뒤
num_computed_tokens=0으로 되돌려 대기열에 넣습니다.
다시 계산할 비용이 생길 수 있으므로 선점을 많이 한다고 효율적인 것은 아닙니다.
vllm/v1/core/kv_cache_manager.py의 KVCacheManager는
요청별 캐시 배치와 슬롯 확보를 조율하고,
vllm/v1/core/block_pool.py의 BlockPool은 실제 블록 메타데이터의 할당·반납·캐시 퇴거를 관리합니다.
큰 메모리 문제를 여러 작은 블록의 수명 관리로 바꾸는 설계입니다.
vllm/v1/core/kv_cache_utils.py의 KVCacheBlock에는
block_id, ref_cnt, block_hash, 자유 블록 연결 정보가 있습니다.
BlockPool.get_new_blocks()는 자유 큐에서 블록을 꺼내 참조 수를 늘리고,
free_blocks()는 참조 수를 줄여 0이 된 블록을 재사용 대상으로 돌립니다.
반납된 캐시 블록이 곧바로 지워지는 것은 아닙니다.
접두사 캐시는 단순히 문자열 몇 글자가 같은지 비교하지 않습니다.
hash_block_tokens()는 이전 블록의 해시, 현재 토큰 ID 묶음, 추가 키를 함께 해시합니다.
앞 문맥이 달라지면 같은 토큰 묶음이라도 다른 계산 상태가 되기 때문입니다.
# vllm/v1/core/kv_cache_utils.py: hash_block_tokens()
curr_block_token_ids_tuple = tuple(curr_block_token_ids)
return BlockHash(
hash_function((parent_block_hash, curr_block_token_ids_tuple, extra_keys))
)
KVCacheManager.get_computed_blocks()는 가장 긴 캐시 적중 구간을 찾습니다.
이 함수는 다음 토큰의 점수를 얻기 위해 마지막 토큰을 다시 계산하도록
최대 적중 길이를 request.num_tokens - 1로 제한합니다.
블록 정렬 제약 때문에 한 토큰보다 많은 부분을 다시 계산할 수도 있다는 주석도 있습니다.
해시 기본값은 CacheConfig.prefix_caching_hash_algo="sha256"입니다.
generate_block_hash_extra_keys()에서는 LoRA·멀티모달 입력 등 추가 문맥을 고려합니다.
따라서 같은 화면상의 문장이라고 언제나 캐시가 공유된다고 단정하면 안 됩니다.
BlockHashToBlockMap의 주석은 동일한 해시의 블록을 항상 하나로 합치지는 않는다고 밝힙니다.
블록 테이블이 뒤에 덧붙는 성질을 유지하려는 선택입니다.
또한 자유 블록 큐는 캐시 없는 블록의 LIFO 재사용과 캐시 블록의 FIFO 재사용을 구분합니다.
단순한 “해시 딕셔너리 하나”보다 많은 수명 정책이 들어 있습니다.
KV 캐시 최적화의 핵심은 기억할 내용을 바꾸는 것이 아니라, 같은 중간 상태를 안전하게 공유하고 필요한 공간만 확보하는 것입니다. 고전적인 PagedAttention의 블록 관리 아이디어와, 현재 선택 가능한 FlashAttention·FlashInfer 등의 실제 계산 백엔드는 구분해서 이해하세요. 모든 모델이 같은 캐시 형식이나 같은 커널을 쓰는 것은 아닙니다.
vllm/v1/executor/abstract.py의 Executor.get_class()는
distributed_executor_backend 값에 따라 실행기를 고릅니다.
uni는 UniProcExecutor, mp는 MultiprocExecutor,
ray는 RayExecutorV2로 이어집니다.
스케줄러가 직접 모든 장치 실행 방식을 구현하지 않게 만드는 전략 선택 구조입니다.
| 선택하는 대상 | 파일 / 함수 | 분리의 효과 |
|---|---|---|
| 실행기 | vllm/v1/executor/abstract.pyExecutor.get_class() | 단일 프로세스와 분산 작업 지시 방식을 분리합니다. |
| Attention 백엔드 | vllm/v1/attention/selector.pyget_attn_backend() | 자료형·헤드 크기·장치 등의 조건에 맞는 구현을 선택합니다. |
| 모델 클래스 | vllm/model_executor/models/registry.py_ModelRegistry.resolve_model_cls() | 설정의 모델 아키텍처와 실행 클래스를 연결합니다. |
GPU 쪽에서는 vllm/v1/worker/gpu_worker.py의 Worker.execute_model()과
vllm/v1/worker/gpu/model_runner.py의 GPUModelRunner.execute_model()을 따라가면 됩니다.
별도 지정이 없고 Triton·기능 지원 조건을 만족하면 V2 실행기가 선택되며,
조건에 따라 vllm/v1/worker/gpu_model_runner.py의 V1 구현으로 전환됩니다.
처음부터 커널 전체를 읽기보다 입력으로 받은 스케줄 결과가 어떤 텐서와 블록 테이블로 바뀌는지 추적하세요.
제어 정책과 계산 구현을 분리하는 이유가 드러납니다.
트리 맨 위의 vllm/은 저장소를 나타내는 표기이고, 그 안의 vllm/이 Python 패키지입니다.
실제 파일 경로는 vllm/v1/engine/core.py처럼 저장소 루트 기준으로 읽으면 됩니다.
v1은 엔진 계층의 이름이며 설치 패키지의 릴리스 번호와는 별개입니다.
| 궁금한 질문 | 먼저 열 파일 | 다음으로 이어질 곳 |
|---|---|---|
| CLI는 어디서 시작하나? | vllm/entrypoints/cli/main.py | vllm/entrypoints/cli/serve.py와 서버 launcher |
| 누가 다음 요청을 고르나? | vllm/v1/core/sched/scheduler.py | vllm/v1/core/kv_cache_manager.py와 vllm/v1/core/sched/output.py |
| 모델 종류는 어떻게 찾나? | vllm/model_executor/models/registry.py | 해당 모델 파일과 공통 계층 |
| GPU 없이 읽을 단서는? | tests/v1/core/test_scheduler.py | 가짜 요청·블록 수·예산을 만드는 보조 함수 |
| 대기시간은 어디서 재나? | vllm/v1/metrics/loggers.py | examples/observability/의 대시보드 자료 |
tests/는 Python 2,227개 파일·779,412줄,
vllm/ 패키지는 Python 2,929개 파일·1,136,955줄입니다.
주석·빈줄을 포함한 물리적 줄 수이므로 이를 테스트 커버리지나 품질 점수로 환산하면 안 됩니다.
처음 읽을 때는 구현 하나와 그 구현의 테스트를 짝지어 선택하는 편이 효과적입니다.
Scheduler.schedule()의 반환형은 SchedulerOutput입니다.
스케줄러가 텐서를 직접 계산하지 않고 “어떤 요청의 몇 토큰을 계산할지”를 자료 구조로 전달한다는 점에 주목하세요.
vllm/v1/core/sched/output.py와 vllm/v1/outputs.py를 나란히 읽으면
실행 전 계획과 실행 후 결과가 서로 다른 계약이라는 사실이 보입니다.
실습 아이디어: 두 자료형에서 요청 ID·토큰 수·새 블록 정보를 찾아 표로 옮기세요. 그다음 필드 하나가 빠지면 어느 계층이 다음 작업을 할 수 없게 되는지 설명해 보세요.
AsyncLLM.generate()는 비동기 생성기로 결과를 여러 번 반환합니다.
등록 → 대기 → 여러 번 출력 → 종료와, 등록 → 취소 → 정리의 두 경로를 구분해 읽으세요.
정상 경로만 빠른 서버보다 실패한 요청의 자원까지 회수하는 서버가 운영에서 더 예측 가능합니다.
실습 아이디어: 요청 하나의 상태를 종이에 그린 뒤
CancelledError가 어느 위치에서 발생해도 정리할 수집기와 엔진 요청이 무엇인지 표시하세요.
KVCacheBlock의 참조 수와 자유 큐는 객체 풀의 구체적인 사례입니다.
블록이 “요청에 사용 중”인지, “재사용 가능한 캐시”인지,
“새 내용으로 덮어써도 되는 공간”인지 구분하는 기준을 찾아보세요.
해시 인덱스에 남아 있다는 사실과 사용 중이라는 사실은 같지 않습니다.
실습 아이디어: 블록 세 개를 두 요청이 나누어 쓰는 상황을 만들고,
할당·공유·반납할 때마다 ref_cnt와 자유 큐를 손으로 갱신하세요.
vllm/model_executor/models/llama.py에는
QKVParallelLinear, RowParallelLinear, RMSNorm 등이 나타납니다.
일반적인 신경망 레이어에 장치 분할과 추론 최적화가 어떻게 결합되는지 볼 수 있습니다.
LlamaAttention.forward()의 Q·K·V 분리부터 읽으면 수식과 코드의 대응이 비교적 분명합니다.
실습 아이디어: 헤드 수를 8개, tensor parallel 크기를 2라고 가정해 각 장치가 담당하는 query 헤드를 그려 보세요. KV 헤드는 개수가 작을 때 복제될 수 있으므로 모든 텐서가 동일하게 반으로 나뉜다고 가정하지 마세요.
| 지표 | 물어볼 질문 | 실제 지표 이름 |
|---|---|---|
| TTFT | 첫 결과를 보기까지 오래 기다리는가? | vllm:time_to_first_token_seconds |
| ITL | 생성이 시작된 뒤 토큰 사이가 느린가? | vllm:inter_token_latency_seconds |
| 전체 지연 | 요청 완료까지 얼마나 걸리는가? | vllm:e2e_request_latency_seconds |
| 대기 요청 | 실행보다 유입이 빠른가? | vllm:num_requests_waiting |
이 이름들은 vllm/v1/metrics/loggers.py에서 확인할 수 있습니다.
평균만 보지 말고 p50·p95 같은 백분위수를 함께 비교하세요.
첫 실행의 모델 로딩·컴파일·그래프 준비 시간과 준비가 끝난 뒤의 요청 지연도 구분해야 합니다.
tests/v1/core/test_scheduler.py의 test_preempt_during_execution()은
블록 크기 16, 블록 수 11, 토큰 예산 100 같은 작은 조건으로 선점 상황을 구성합니다.
블록 0이 null 블록으로 예약되어 실제 가용 블록은 10개라는 주석까지 읽어야 계산이 맞습니다.
거대한 모델 없이도 스케줄러의 경계 조건을 설명하는 좋은 독서 자료입니다.
tests/v1/core/test_prefix_caching.py에는 pytest.mark.cpu_test가 있고,
.buildkite/test_areas/misc.yaml도 CPU 테스트와 그 외 테스트를 나눕니다.
다만 CPU 표시가 있다고 의존성 설치 없이 모든 테스트가 독립 실행된다는 뜻은 아닙니다.
좋은 소스 독서는 함수 이름을 외우는 일이 아니라, 입력·출력·자원 소유자·실패 시 정리 규칙을 설명할 수 있게 되는 일입니다. 이 네 가지를 한 장에 정리하면 처음 보는 분산 시스템도 훨씬 덜 막막해집니다.
| 목적 / 경로 | 확인할 요구사항 | 근거 |
|---|---|---|
| 소스 공부 | 텍스트 편집기와 Git만으로 핵심 흐름을 읽을 수 있습니다. | 스케줄러·캐시·테스트 자료 구조 추적 |
| 일반 실행 환경 | 빠른 시작 문서 기준 Linux, Python 3.11~3.14 | docs/getting_started/quickstart.md |
| NVIDIA GPU | 문서 기준 compute capability 7.5 이상. 선택 커널은 추가 조건이 있을 수 있습니다. | docs/getting_started/installation/gpu.cuda.inc.md |
| AMD GPU | 지원 GPU와 ROCm 조합 확인. 현재 문서는 기본 ROCm 10.0·별도 7.2 계열 wheel을 설명합니다. | docs/getting_started/installation/gpu.rocm.inc.md |
| Intel GPU | Data Center GPU·ARC 계열 안내. XPU wheel 문서는 Python 3.12를 요구합니다. | docs/getting_started/installation/gpu.xpu.inc.md |
| x86 CPU | Linux, AVX512 권장·AVX2 기능 제한. GPU와 다른 패키지 경로입니다. | docs/getting_started/installation/cpu.x86.inc.md |
| Apple Silicon | GPU 사용은 별도 vLLM-Metal 플러그인·MLX 경로. 문서는 macOS Sonoma 이상을 안내합니다. | docs/getting_started/installation/gpu.apple.inc.md |
Python 패키지가 지원하는 전체 버전 범위와 특정 장치 wheel이 제공되는 범위는 같지 않습니다. 장치 이름 하나만 확인하지 말고 OS·Python·PyTorch·드라이버·커널의 조합을 확인하세요. 기본 CUDA 패키지 하나가 모든 GPU와 CPU 환경을 동시에 해결하지는 않습니다.
가중치 메모리의 거친 하한은 파라미터 수 × 파라미터당 바이트입니다. 예를 들어 70억 파라미터를 2바이트로 저장하면 약 14GB, 즉 약 13.0GiB입니다. 이는 가중치만의 계산입니다. 실행 중간 텐서, 그래프, 통신 버퍼, KV 캐시와 할당 여유가 추가됩니다.
# 일반적인 decoder의 full attention, 단일 요청에 대한 근사식
KV 바이트 ≈ 2 × 레이어 수 × KV 헤드 수 × 헤드 차원
× 저장 토큰 수 × 원소당 바이트
# 예시: 32층, KV 헤드 8개, 차원 128, 4096토큰, 2바이트
2 × 32 × 8 × 128 × 4096 × 2 = 536,870,912바이트
= 512MiB
이 예시에서 서로 캐시를 공유하지 않는 요청 8개라면 KV 데이터만 약 4GiB입니다. 실제 사용량은 블록 단위 반올림, 병렬화, 자료형과 모델 구조에 따라 달라집니다. sliding window, MLA, Mamba 같은 구조에는 위 공식을 그대로 적용할 수 없습니다.
vllm/config/cache.py의 gpu_memory_utilization 기본값은 0.92입니다.
이는 모델 실행기에 사용할 장치 메모리 비율이며 KV 캐시만의 비율이나 GPU 연산 사용률이 아닙니다.
Worker.determine_available_memory()는 프로파일링으로 캐시에 쓸 수 있는 메모리를 계산합니다.
kv_cache_memory_bytes를 직접 지정하는 경로는 이 비율 설정을 따르지 않는다고 명시합니다.
CacheConfig.DEFAULT_BLOCK_SIZE=16은 출발점이며 최종 블록 크기는 백엔드 제약의 영향을 받습니다.
SchedulerConfig의 2,048토큰·128시퀀스 기본값도 테스트 편의를 위한 값이라는 주석이 있습니다.
실제 실행 기본값은 vllm/engine/arg_utils.py에서 사용 맥락·장치·모델에 맞춰 조정됩니다.
CUDA 설치 문서는 기본 바이너리를 CUDA 12.9로 설명하지만,
docker/Dockerfile의 기본 인자는 CUDA 13.0.3입니다.
배포 wheel과 컨테이너 빌드는 다른 경로이므로 하나를 모든 배포물의 버전으로 일반화하면 안 됩니다.
XPU 문서에는 triton==3.7.2+xpu가 남아 있지만
requirements/xpu.txt는 triton==3.8.0+xpu를 선언합니다.
이 부분은 실제 불일치입니다. 장치별 공식 설치 안내와 선택한 릴리스의 의존성을 함께 확인해야 합니다.
어떤 모델이 실행되는지는 파라미터 수 하나가 아니라 정밀도·문맥 길이·동시 요청 수·커널·장치 메모리의 조합으로 결정됩니다. 디스크에는 가중치와 다운로드 캐시가, 시스템 RAM에는 로딩·전처리·CPU 경로용 공간이 필요합니다. 모든 모델에 통하는 단일 최소 VRAM·RAM 수치를 제시하기 어려운 이유입니다.
과제 1은 GPU와 설치 없이 할 수 있습니다. 과제 2~5는 7절의 장치별 설치 조건을 만족하는 별도 학습 환경을 전제로 합니다. 실행 예시는 기본 Python 프런트엔드와 작은 생성 모델을 기준으로 하며, 학습 결과에는 패키지 버전·모델 revision·장치·정밀도·길이 설정을 함께 기록하세요.
create_chat_completion()에서 AsyncLLM.generate(),
EngineCore.step(), Scheduler.schedule()까지 호출 순서를 찾아보세요.
각 화살표에 전달하는 자료형과 실행 프로세스를 적습니다.
완료 기준: “HTTP 요청 하나가 모델 실행 한 번과 같지 않은 이유”를 설명하고, 정상 종료·사용자 취소 시 정리할 자원을 각각 표시한 그림 한 장을 만듭니다.
1절의 예제에 프롬프트를 세 개 넣고 max_tokens를 16과 64로 바꿉니다.
RequestOutput의 입력·출력 토큰 ID와 종료 사유를 살펴보세요.
토큰 수 제한은 한국어 글자 수 제한과 다릅니다.
완료 기준: 입력 토큰 수·생성 토큰 수·종료 사유를 표로 남깁니다. 모델 로딩 시간은 요청 처리 시간과 구분하고, 출력 문장이 매번 같다는 보장은 하지 않습니다.
호환 환경에서 아래처럼 서버를 시작한 뒤 다른 터미널에서 스트리밍 요청을 보냅니다. 이 설정값들은 작은 실습을 위한 명시적 예시이며 모든 GPU에서의 실행을 보장하지 않습니다.
vllm serve Qwen/Qwen3-0.6B \
--host 127.0.0.1 --port 8000 \
--served-model-name study-model \
--max-model-len 2048 \
--max-num-seqs 8 --max-num-batched-tokens 2048 \
--gpu-memory-utilization 0.80
curl -N http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "study-model",
"messages": [{"role": "user", "content": "캐시의 역할을 설명해 줘."}],
"max_tokens": 128,
"stream": true
}'
완료 기준: SSE의 data: 이벤트와 종료 표지를 찾습니다.
이어 응답 도중 요청을 취소하고, 서버 지표에서 실행 요청이 정리되는지 확인합니다.
네트워크 청크 하나가 항상 토큰 하나와 일치한다고 가정하지 마세요.
과제 3의 서버에서 동일한 입력·출력 길이 조건으로 요청률을 바꿉니다.
아래 옵션은 vllm/benchmarks/serve.py와 vllm/benchmarks/datasets/datasets.py의 인자 처리를 따라 읽을 수 있습니다.
vllm bench serve \
--backend openai --endpoint /v1/completions \
--model study-model --tokenizer Qwen/Qwen3-0.6B \
--dataset-name random \
--random-input-len 128 --random-output-len 64 \
--num-prompts 40 --request-rate 4 \
--metric-percentiles 50,95 --ignore-eos
요청률을 1·4·8로 바꾸되 모델과 서버 설정은 유지합니다.
--request-rate는 요청의 유입 속도이고 동시 실행 개수와 동일하지 않습니다.
기본값 inf는 모든 요청을 처음에 보내는 조건이므로 별도의 부하 형태로 다루세요.
완료 기준: 성공·실패 요청 수, 초당 출력 토큰, TTFT p50·p95, ITL p95를 표로 비교합니다. 워밍업 조건을 맞추고 대기 요청 수가 늘어나는 구간을 찾아 원인을 설명합니다.
긴 공통 시스템 메시지와 서로 다른 짧은 질문을 준비합니다. 첫 요청과 후속 요청을 순차 전송해 접두사 재사용을 관찰하고, 시스템 메시지 앞부분을 바꿨을 때의 결과와 비교하세요. 모델·chat template·토큰화 결과·출력 길이 조건을 고정합니다.
KVCacheManager.get_computed_blocks()와 hash_block_tokens()를 함께 읽고,
마지막 토큰 재계산과 블록 경계가 왜 남는지 설명하세요.
반복마다 캐시를 초기화하는 조건과 누적하는 조건을 구분해야 합니다.
완료 기준: 적중 토큰 관련 정보와 TTFT를 함께 기록하고, 짧은 공통 입력에서는 차이가 작을 수 있는 이유를 씁니다. 캐시를 끄는 비교는 해당 모델이 이를 지원하는지 먼저 확인하세요.
성능 실습의 결과물은 빠르다는 감상이 아니라, 조건을 고정한 표와 그 차이를 설명하는 가설이어야 합니다. 캐시·배치·유입률을 한꺼번에 바꾸면 어떤 설정이 영향을 주었는지 알기 어렵습니다. 한 번에 한 축을 바꾸고 예외와 실패도 기록하세요.
첫 목표는 거대한 엔진을 다시 만드는 것이 아니라, 각 계층의 책임을 설명하고 작은 실험으로 설계 이유를 확인하는 것입니다. GPU가 없다면 1~4주와 7~8주의 설계·소스 과제부터 진행할 수 있습니다.
| 주차 | 공부할 개념 | 소스와 결과물 |
|---|---|---|
| 1주 | Python 타입·자료 클래스·반복자, 추론과 서빙의 구분 | vllm/entrypoints/llm.py, vllm/sampling_params.py를 읽고 입력·출력 계약 표 작성 |
| 2주 | 토큰화·chat template·자기회귀 생성·prefill / decode | LlamaAttention.forward()의 Q·K·V 흐름과 토큰 증가 그림 작성 |
| 3주 | 비동기 생성기·수집기·취소·프로세스 통신 | AsyncLLM.generate()의 정상·취소 시퀀스 작성, 실습 1 완료 |
| 4주 | 대기열·예산·선점·자원 풀·참조 수 | Scheduler.schedule()과 BlockPool을 손으로 시뮬레이션 |
| 5주 | 가중치 메모리·KV 캐시·캐시 키·블록 경계 | 소형 모델의 입력 길이별 메모리 추정과 접두사 실험 설계 |
| 6주 | 지표·부하 생성·워밍업·백분위수 | vllm/benchmarks/serve.py와 지표 정의를 읽고 실습 4의 비교 표 작성 |
| 7주 | tensor / pipeline / data parallel, 장치 간 통신 | Executor.get_class()와 vllm/distributed/의 역할 지도 작성 |
| 8주 | 컴파일·장치별 백엔드·패키징·API 계약 | vllm/compilation/, docker/Dockerfile, rust/README.md 비교 노트 작성 |
“긴 입력의 요청이 몰릴 때 첫 토큰 시간이 늘어나는 이유는 무엇인가?” “모든 요청이 다른 접두사를 쓰면 어떤 최적화의 효과가 작아지는가?” 두 질문에 스케줄러·캐시·지표를 연결해 답할 수 있다면 주요 구조를 이해한 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 가중치 | 모델이 학습으로 얻은 숫자 묶음. 추론을 시작할 때 불러오는 주요 데이터입니다. |
| 토큰 | 모델이 처리하는 텍스트 등의 단위. 한국어 한 글자나 한 단어와 항상 일치하지 않습니다. |
| 토큰화 | 텍스트를 토큰 ID 목록으로 바꾸는 과정입니다. |
| chat template | 사용자·시스템·도우미 메시지를 모델이 학습한 대화 형식으로 배열하는 규칙입니다. |
| 자기회귀 생성 | 앞의 입력과 지금까지 생성한 토큰을 바탕으로 다음 토큰을 이어 만드는 방식입니다. |
| prefill | 입력 문맥을 처리해 생성에 필요한 중간 상태를 준비하는 계산입니다. |
| decode | 준비된 문맥과 이전 결과를 사용해 다음 토큰을 이어 가는 계산입니다. |
| KV 캐시 | Attention에서 재사용하는 Key·Value 중간 상태. 전체 대화 원문이나 완성 답변의 저장소와는 구별됩니다. |
| PagedAttention | KV 상태를 블록으로 나누고 블록 테이블을 통해 접근하는 Attention 설계입니다. |
| 블록 테이블 | 요청의 논리적 토큰 구간과 실제 캐시 블록 번호를 연결하는 표입니다. |
| 참조 수 | 블록을 현재 몇 군데에서 사용하는지 나타내는 수. 안전한 재사용 판단에 필요합니다. |
| 접두사 캐싱 | 토큰화된 공통 앞 문맥의 계산 상태를 재사용해 입력 계산을 줄이는 방법입니다. |
| 캐시 퇴거 | 새 데이터를 담기 위해 재사용 가능한 옛 캐시 항목을 내보내는 일입니다. |
| 연속 배칭 | 반복 사이에 완료 요청을 빼고 새 요청을 넣으며 처리 묶음을 갱신하는 방식입니다. |
| chunked prefill | 긴 입력 계산을 토큰 예산에 맞춰 여러 번으로 나누는 방법입니다. |
| 선점 | 실행 중인 요청을 일시적으로 밀어내고 자원을 회수하는 처리. 이후 재계산 비용이 생길 수 있습니다. |
| 샘플링 | 모델이 낸 점수에서 다음 출력 토큰을 고르는 과정입니다. |
| 추측 디코딩 | 초안 토큰을 미리 제안하고 목표 모델로 검증하는 방법. 효율은 수용률과 추가 비용에 좌우됩니다. |
| 양자화 | 숫자를 적은 비트로 표현해 메모리와 연산 비용을 줄이는 기술. 정확도와 커널 지원을 확인해야 합니다. |
| tensor parallel | 한 모델 연산의 텐서를 여러 장치에 나누어 계산하는 병렬화입니다. |
| pipeline parallel | 모델의 층들을 여러 장치의 단계로 나누는 병렬화입니다. |
| data parallel | 모델 실행 복제본들에 서로 다른 요청을 배분하는 방식입니다. |
| expert parallel | MoE 모델의 전문가 네트워크를 장치들에 나누어 배치하는 방식입니다. |
| SSE | HTTP 연결로 서버 이벤트를 순차 전달하는 형식. 생성 결과의 스트리밍에 쓰입니다. |
| TTFT / ITL | 첫 토큰까지의 시간 / 생성 토큰 사이의 시간. 사용자가 느끼는 대기의 서로 다른 부분입니다. |
| ZMQ | 프로세스 사이에서 메시지를 주고받는 통신 라이브러리 계열입니다. |
| 커널 | 여기서는 GPU 등에서 특정 연산을 수행하는 낮은 수준의 계산 함수입니다. |
| CUDA Graph | GPU 작업 흐름을 기록해 반복 실행의 호출 부담을 줄이는 기능입니다. |
| ABI | 컴파일된 프로그램끼리 호출·자료 표현을 맞추는 규약. PyTorch·CUDA 조합이 중요한 이유 중 하나입니다. |
이 단어들은 서로 연결됩니다. 연속 배칭을 많이 활용하려면 충분한 KV 공간이 필요하고, 캐시와 양자화로 공간을 아껴도 장치 간 통신이나 긴 대기열이 새 병목이 될 수 있습니다. 한 기능 이름보다 전체 실행 흐름을 기준으로 이해하세요.
| 항목 | 상태 |
|---|---|
| 선정 | GitHub Trending Python Weekly #14 |
| 스타 / 포크 | 95,202 / 23,834 — 2026-10-23 GitHub 기준 |
| 생성일 | 2023-02-09 |
| 주 언어 | Python |
| 최신 릴리스 | v0.32.0 — 2026-10-18 |
| 최근 push | 2026-10-23 |
| 최근 커밋 | f4edf9f — 2026-10-23[XPU] Enable kernel/sampler unit tests on Intel XPU (#62419) |
| 추적 파일 규모 | 일반 파일 8,407개. 하위 모듈 내부·Git 메타데이터·심볼릭 링크 제외 |
| Python 규모 | 5,514개 파일 / 2,014,179줄. 패키지·테스트·예제·도구 등 전체 포함 |
| Rust 규모 | 375개 파일 / 152,025줄 |
| CUDA 규모 | .cu 113개 / 56,745줄 + .cuh 74개 / 23,618줄 |
| C++ / 공통 헤더 | .cpp·.cc 54개 / 29,923줄 + .h·.hpp 86개 / 33,170줄 |
| 기타 언어 | Shell 147개 / 23,753줄, JavaScript 7개 / 166줄 |
| 라이선스 | Apache-2.0 — 모델 가중치의 이용 조건은 각 모델에서 별도 확인 |
코드 규모는 커밋 f4edf9f의 Git 추적 파일을 확장자별로 나누고
각 파일의 줄바꿈 문자 수를 합산한 물리적 줄 수입니다.
주석·빈줄·테스트 fixture·추적된 외부 코드가 포함되며, 순수 구현량이나 실행 성능을 뜻하지 않습니다.
전체 커밋 수·기여자별 비중은 이 규모 표의 집계 대상에 포함하지 않습니다.
소스 설명은 개발 커밋 f4edf9f 기준입니다.
최신 안정 릴리스 v0.32.0과 기능·의존성·기본값이 같다고 보장할 수 없습니다.
README의 여러 하드웨어·모델 지원 목록도 모든 모델·양자화·커널 조합의 지원을 의미하지 않습니다.
OpenAI 호환 API의 기능 범위와 모델별 대화 템플릿·도구 호출 지원을 확인해야 합니다. Rust 프런트엔드는 자체 안내에서 실험적이며 기능이 완전하지 않다고 밝힙니다. 실습의 관찰 항목은 검증할 과제이며, 특정 장치의 성능이나 실행 성공을 보장하는 결과가 아닙니다.
보안 문서는 노드 간 통신이 기본적으로 안전하지 않으며 격리된 네트워크로 보호해야 한다고 설명합니다. 로컬 학습용 HTTP 실행 예시를 그대로 외부 공개 서비스 설정으로 사용하지 마세요.