- Published on
로컬 LLM 속도 2배 만들기 - vLLM vs TGI
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
로컬에서 Transformers 모델을 돌릴 때 “GPU는 놀고 있는데 응답은 느린” 상황을 자주 겪습니다. 원인은 대개 모델 자체가 아니라 서빙 엔진의 스케줄링·메모리 관리·배치 전략에 있습니다. 같은 GPU, 같은 모델이라도 vLLM이나 TGI 같은 전용 서빙 엔진을 쓰면 처리량(throughput) 과 지연시간(latency) 이 눈에 띄게 바뀝니다.
이 글은 “Transformers 로컬 LLM 속도 2배”를 목표로, vLLM과 TGI(Text Generation Inference) 를 비교하고 어떤 상황에서 무엇을 선택해야 하는지, 그리고 실제로 속도를 끌어올리는 튜닝 포인트를 코드와 함께 정리합니다.
프롬프트 구조 최적화까지 같이 보면 체감 속도가 더 좋아집니다: Chain-of-Thought 없이 성능↑ 구조화 프롬프트
속도 체감이 갈리는 3가지 지표
로컬 LLM “빠르다”는 말을 정확히 하려면 지표를 나눠야 합니다.
- TTFT(Time To First Token): 첫 토큰이 나오기까지 걸리는 시간
- TPOT(Time Per Output Token) 또는 tokens/sec: 생성 단계의 속도
- Throughput: 동시 요청이 늘어날 때 초당 처리 가능한 총 토큰량
일반적으로
- 채팅 UX는
TTFT가 중요합니다. - 배치 작업이나 다수 동시 사용자 서비스는
throughput이 중요합니다.
vLLM과 TGI는 이 지표들에서 강점이 다르게 나타납니다.
vLLM vs TGI: 핵심 차이 한 장 요약
vLLM의 강점
- PagedAttention 기반 KV 캐시 관리로 메모리 단편화와 낭비를 줄임
- Continuous batching 이 매우 공격적으로 동작해 동시성에서 처리량이 잘 나옴
- OpenAI 호환 서버를 띄우기 쉬워 기존 클라이언트와 연결이 간단
TGI의 강점
- Hugging Face 생태계 친화적(토크나이저·모델 로딩·배포 패턴)
- 운영 기능이 탄탄함: 레디니스, 로깅, 메트릭, 제한, 스트리밍 등
- 텐서 병렬 등 멀티 GPU 구성에서 안정적인 선택지가 많음
결론적으로
- 단일 GPU에서 동시 요청 처리량을 올려 “체감 2배”를 노리면 vLLM이 유리한 경우가 많습니다.
- 운영 표준화(관측·배포·거버넌스)와 멀티 GPU 확장을 중시하면 TGI가 편합니다.
다만 “2배”는 절대값이 아니라 워크로드 특성(입력 길이, 출력 길이, 동시성, 스트리밍 여부) 에 따라 달라집니다.
왜 Transformers 기본 파이프라인은 느리게 느껴질까
로컬에서 흔히 하는 방식은 Python에서 transformers 를 직접 호출해 generate 를 수행하는 것입니다. 이 방식은 단일 요청에는 단순하지만, 동시 요청이 들어오면 다음 문제가 겹칩니다.
- 요청별로 KV 캐시가 쌓이며 GPU 메모리 사용이 비효율적
- 배치가 잘 안 되거나, 배치가 되더라도 동적으로 섞기 어려움
- 토크나이징·후처리·스트리밍에서 CPU 병목이 발생
즉 “모델”이 아니라 “서빙”이 병목입니다.
vLLM 빠르게 시작하기 (OpenAI 호환)
아래는 vLLM을 OpenAI 호환 API로 띄우는 예시입니다.
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--host 0.0.0.0 \
--port 8000 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192
호출 예시 (Python)
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
resp = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "로컬 LLM 속도 최적화 핵심만 요약해줘."},
],
temperature=0.2,
)
print(resp.choices[0].message.content)
OpenAI SDK를 붙일 때 요청 JSON이 깨지면 400 invalid_json 으로 터질 수 있으니, 프록시나 로깅 미들웨어를 끼웠다면 다음 글의 체크리스트가 도움이 됩니다: Python OpenAI SDK 400 invalid_json 원인과 해결
vLLM에서 속도에 영향 큰 옵션
--gpu-memory-utilization: 너무 낮으면 KV 캐시 여유가 없어 배치 효율이 떨어지고, 너무 높으면 OOM 리스크가 커집니다.--max-model-len: 크게 잡을수록 KV 캐시 상한이 커져 메모리 압박이 증가합니다. 실제 필요 길이에 맞추는 게 유리합니다.
TGI 빠르게 시작하기 (Docker)
TGI는 컨테이너로 띄우는 패턴이 일반적입니다.
docker run --gpus all --shm-size 1g -p 8080:80 \
-e HF_TOKEN=$HF_TOKEN \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id Qwen/Qwen2.5-7B-Instruct \
--max-input-length 4096 \
--max-total-tokens 8192
호출 예시 (curl)
TGI는 OpenAI 호환 엔드포인트를 제공하는 구성도 가능하지만, 기본 엔드포인트 예시는 다음과 같습니다.
curl http://localhost:8080/generate \
-H "Content-Type: application/json" \
-d '{
"inputs": "속도 최적화 관점에서 vLLM과 TGI 차이를 설명해줘.",
"parameters": {
"max_new_tokens": 256,
"temperature": 0.2,
"return_full_text": false
}
}'
“2배”를 만드는 진짜 레버: 배치와 KV 캐시
1) Continuous batching이 만드는 처리량 차이
동시 요청이 1개일 때는 어떤 엔진이든 큰 차이가 안 날 수 있습니다. 하지만 요청이 5개, 20개로 늘면 차이가 벌어집니다.
- Naive batching: 같은 시점에 들어온 것만 묶음. 늦게 들어온 요청은 다음 라운드를 기다림.
- Continuous batching: 토큰 생성 루프 중간에도 새 요청을 끼워 넣어 GPU를 계속 바쁘게 만듦.
vLLM은 이 지점에서 강점을 보이는 경우가 많고, 결과적으로 동시성에서 tokens/sec가 크게 상승합니다.
2) KV 캐시 메모리 단편화와 PagedAttention
Transformer 디코딩은 토큰이 늘어날수록 KV 캐시가 커집니다. 요청이 많아지면 KV 캐시가 GPU 메모리를 잡아먹고, 단편화가 생기면 “남는 메모리가 있는데도 OOM” 같은 현상이 발생할 수 있습니다.
vLLM은 PagedAttention으로 KV 캐시를 페이지 단위로 관리해 낭비를 줄입니다. 이게 곧 더 많은 동시 요청을 유지하게 해주고, throughput을 끌어올립니다.
TGI도 메모리 관리와 배치 최적화를 하지만, 모델·버전·설정에 따라 체감 차이는 달라질 수 있습니다.
공정 비교를 위한 벤치마크 방법
“vLLM이 빠르다” 혹은 “TGI가 빠르다”는 결론은 벤치마크 설계에 따라 쉽게 뒤집힙니다. 아래 원칙을 지키면 비교가 훨씬 정확해집니다.
비교 조건 체크리스트
- 동일 모델, 동일 정밀도(예:
fp16또는bf16), 동일 GPU - 입력 길이 분포 고정(짧은 프롬프트만 넣으면 TTFT만 좋게 나올 수 있음)
- 출력 길이 고정(예:
max_new_tokens동일) - 동시성 시나리오 분리: 동시 1, 4, 16, 64 등
- 스트리밍 사용 여부 고정
간단 부하 테스트 스크립트 예시
아래는 OpenAI 호환 서버(vLLM 등)에 동시 요청을 날려 평균 지연을 보는 예시입니다.
import asyncio
import time
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
MODEL = "Qwen/Qwen2.5-7B-Instruct"
PROMPT = "다음 요구사항으로 LLM 서빙 성능을 높이는 방법을 5가지로 정리해줘."
async def one_call():
t0 = time.perf_counter()
resp = await client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": PROMPT}],
temperature=0.2,
max_tokens=256,
)
dt = time.perf_counter() - t0
return dt, resp.usage
async def run(n: int):
tasks = [asyncio.create_task(one_call()) for _ in range(n)]
out = await asyncio.gather(*tasks)
dts = [x[0] for x in out]
print("concurrency=", n)
print("avg_latency_sec=", sum(dts) / len(dts))
print("p95_latency_sec=", sorted(dts)[int(len(dts)*0.95)-1])
if __name__ == "__main__":
asyncio.run(run(16))
이때 서버가 스트리밍을 지원하면, TTFT를 별도로 계측하는 편이 UX 관점에서 더 정확합니다.
운영에서 속도를 갉아먹는 숨은 병목
엔진을 바꿨는데도 “생각보다 안 빨라졌다”면 다음을 의심해야 합니다.
1) CPU 토크나이징 병목
- 동시 요청이 많을수록 토크나이징이 CPU에서 병목이 됩니다.
- 해결: 워커 수 조정, 토크나이저 병렬화, 가능하면 빠른 토크나이저 사용.
2) 컨테이너 shm 부족
TGI를 Docker로 띄울 때 공유 메모리 부족은 성능 저하나 예기치 않은 오류를 부릅니다. 위 예시처럼 --shm-size 를 명시하는 습관이 좋습니다.
3) 프록시·게이트웨이에서 스트리밍이 끊김
스트리밍은 체감 속도를 크게 올리지만, Nginx나 LB 설정이 맞지 않으면 버퍼링으로 인해 “한 번에 몰아서 출력”되는 문제가 생깁니다. 이 경우는 LLM 엔진 문제가 아니라 엣지 설정 문제일 가능성이 큽니다.
서비스가 Next.js 기반이라면 렌더링 폭증이나 캐시 설정이 응답 지연으로 관측되는 경우도 있으니, 프론트 병목도 함께 점검해볼 만합니다: Next.js App Router 렌더링 폭증 진단 - RSC 캐시·useMemo
어떤 상황에 vLLM, 어떤 상황에 TGI?
vLLM을 우선 추천하는 경우
- 단일 GPU 또는 소수 GPU에서 동시성 처리량을 최대화하고 싶다
- OpenAI 호환 API로 빠르게 붙이고 싶다
- 긴 컨텍스트와 다양한 길이의 요청이 섞여 들어온다(메모리 효율이 중요)
TGI를 우선 추천하는 경우
- 운영 표준화가 중요하다(관측, 배포, 정책, 멀티 GPU 확장)
- Hugging Face 기반 파이프라인을 그대로 가져가고 싶다
- 팀 내에서 컨테이너 운영이 기본이고, 안정적인 릴리스/롤백이 필요하다
속도 2배를 위한 실전 튜닝 레시피
마지막으로, “체감 2배”에 가장 자주 기여하는 조합을 정리합니다.
- 동시 요청이 있는 서비스라면: continuous batching이 강한 엔진(vLLM 또는 TGI 설정 최적화)을 우선 적용
max_model_len또는max_total_tokens를 실제 요구사항에 맞게 축소- 스트리밍을 켜서 TTFT 체감 개선
- 토크나이징 병목을 제거(워커/스레드/런타임 튜닝)
- 프록시 버퍼링, 타임아웃 등 네트워크 계층을 점검
특히 2번과 3번은 비용 없이 바로 체감이 좋아지는 경우가 많습니다. “긴 컨텍스트가 필요할 것 같아서” 상한을 과도하게 잡는 순간, KV 캐시 때문에 동시성이 떨어지고 결과적으로 더 느려지는 역설이 자주 발생합니다.
마무리
vLLM과 TGI는 둘 다 “Transformers를 빠르게 서빙하기 위한 정답 후보”이지만, 워크로드가 무엇인지에 따라 선택이 달라집니다. 동시 요청이 많고 처리량이 핵심이면 vLLM이 강하고, 운영 표준화와 확장성이 중요하면 TGI가 편합니다.
중요한 건 엔진을 바꾸는 것만이 아니라, 배치·KV 캐시·토큰 상한·스트리밍·CPU 병목을 함께 다루는 것입니다. 이 조합을 제대로 맞추면 “GPU는 그대로인데 응답은 2배 빨라진” 느낌을 충분히 만들 수 있습니다.