제5부 추론·서빙(Inference/Serving)
5.4 서빙 프레임워크 — vLLM·TGI·TensorRT-LLM·SGLang
앞의 세 장은 추론을 빠르고 저렴하게 만드는 기법을 하나씩 분해했다 — KV 캐시(5.1), 양자화(5.2), 연속 배칭·투기적 디코딩·PagedAttention(5.3). 그런데 실무에서 이 기법들을 처음부터 구현하는 팀은 거의 없다. 연속 배칭 스케줄러를 직접 구현하고, PagedAttention 블록 할당기를 만들고, xgrammar급 제약 디코더를 붙이는 일은 그 자체로 수십 명·수년의 프로젝트다. 대신 다들 vLLM·TGI·TensorRT-LLM·SGLang 같은 프레임워크를 골라 추가한다. 그래서 이 장의 핵심 질문은 "어떻게 구현하나"가 아니라 무엇을 고르고, 무엇을 기준으로 고르나다.
이것은 의사결정을 다루는 장이고, 그 성격을 먼저 명시해 둘 필요가 있다. 서빙 프레임워크 영역은 이 책에서 가장 빨리 바뀌는 축에 속한다. 기능·성능·지원 하드웨어·양자화 포맷·라이선스가 버전마다, 달마다 바뀐다. 그래서 이 장에는 "X가 제일 빠르다"는 문장이 없다 — 워크로드·모델·하드웨어·버전을 못 박지 않은 채로 그런 문장을 쓰면 거의 무의미하거나, 나쁘게는 거짓이기 때문이다. 아래의 모든 구체적 기능 서술은 집필 시점의 대략이며, 채택 전에는 반드시 현재 문서로 다시 확인해야 한다. 이 장이 주려는 것은 순위표가 아니라 좌표계다 — 프레임워크가 어떤 축들로 갈리는지, 그 축에서 당신의 워크로드가 어디에 놓이는지, 그리고 후보를 두세 개로 좁힌 다음 왜 반드시 자기 워크로드로 직접 재야 하는지.
5.4.1 프레임워크가 실제로 대신 해 주는 여섯 가지
"모델을 서빙한다"는 겉보기에 단순하다 — 요청을 받아 토큰을 생성해 돌려준다. 그러나 이것을 낮은 지연·높은 처리량·안정적 운영으로 하려면, 앞 장들에서 본 기법이 동시에·정확히·서로 맞물려 동작해야 한다. 프레임워크가 파는 것은 "모델을 돌리는 능력"이 아니다 — 그것은 model.generate() 한 줄이면 된다. 프레임워크가 파는 것은 아래 여섯 가지를 한 몸으로 맞물려 동작시키는 통합이고, 그 통합이 순진한 서버와 프로덕션 서버 사이에 수십 배의 처리량 차이를 만든다.
각 부품이 어느 장에서 왔는지 정리하면, 이 장이 앞 5부 전체의 종합이라는 것이 드러난다.
| 부품 | 하는 일 | 근거 장 |
|---|---|---|
| 배칭 | 여러 요청의 토큰 스텝을 연속 배칭으로 묶어 GPU를 유휴 상태로 두지 않게 | 5.3 |
| KV 관리 | KV 캐시를 PagedAttention 블록으로 조각내 파편화 없이 할당, 공유 프리픽스는 재사용 | 5.1·5.3·5.6 |
| 양자화 | 가중치·KV·활성을 낮은 정밀도로 로딩·실행(INT8·FP8·GPTQ·AWQ)해 메모리·비용 절감 | 5.2 |
| API | OpenAI 호환 HTTP 엔드포인트, 스트리밍(SSE), 요청 검증·인증 | 이 장·7.2 |
| 스케줄링 | 요청 큐·우선순위·선점·프리필 청킹으로 지연·처리량 균형 | 5.3·5.5 |
| 모니터링 | TTFT·TPOT·처리량·큐 길이·KV 점유율 등 운영 지표 노출 | 7.6·5.5 |
하나만 빠져도 나머지가 붕괴한다는 것이 핵심이다. 연속 배칭이 있어도 KV 관리가 순진하면 파편화 때문에 배치를 못 키우고, 배치를 못 키우면 디코드에서 GPU가 유휴 상태다(5.3). 양자화로 가중치를 반으로 줄여도 스케줄러가 그 남은 메모리로 배치를 더 못 키우면 이득이 손실된다. 그래서 프레임워크의 가치는 어느 한 부품의 우수함이 아니라 여섯이 서로를 기아 상태로 만들지 않게 맞물리는 데 있다.
이 "수십 배"가 과장이 아님을 5.1의 숫자로 직접 확인해 두자. 8B fp16 모델(가중치 16 GB)을 대역폭 3.3 TB/s인 GPU에서 실행하면, 디코드 한 스텝은 가중치를 한 번 읽는 시간에 지배되어 약 4.8 ms, 즉 요청 하나당 약 200 tok/s가 상한이다(5.1.6). model.generate()를 요청마다 하나씩 처리하는 순진한 서버는 이 배치 1을 벗어나지 못한다 — 시스템 전체 처리량이 약 200 tok/s에 고정된다. 그런데 디코드는 대역폭 바운드라, 배치를 32로 키워도 스텝 시간은 거의 그대로다(가중치 읽기가 병목이고, 한 번 읽은 가중치로 32개 토큰을 처리하니까, 5.1.8). 같은 5 ms 스텝에서 이제 32개 토큰이 나오므로 시스템 처리량은 약 $32 \times 200 = 6{,}400$ tok/s로 증가한다. 같은 GPU에서 32배 처리량, 토큰당 비용 32분의 1이다. 여기에 요청 길이가 제각각이라 정적 배칭은 절반가량 낭비되는데(5.1.11), 연속 배칭이 그 누수까지 막는다. 순진한 서버 대비 프레임워크의 처리량 우위가 "수 배에서 수십 배"라는 말은 이렇게 나온다 — 비법이 아니라, 배치를 채워 유휴 대역폭을 회수하는 계산이다.
5.4.2 엔진과 서버, 그리고 요청 하나가 지나는 길
프레임워크를 고르기 전에 요청 하나가 시스템을 어떻게 통과하는지 확인해 두면, 각 프레임워크가 어느 계층을 잘하는지가 보인다. 연속 배칭 기반 프레임워크(vLLM류)의 전형적 경로는 이렇다.
이 그림에서 세 가지가 프레임워크의 우열을 가른다. 첫째, 프리필과 디코드가 한 배치에 섞인다. 스케줄러는 매 스텝 "새로 들어온 요청의 프리필"과 "진행 중 요청의 디코드"를 어떻게 섞을지 정하고, 긴 프롬프트가 진행 중인 스트림들을 기아 상태로 만들지 않도록 프리필을 잘게 분할해 삽입한다(chunked prefill). 이 정책이 TTFT와 TPOT의 균형을 좌우한다 — 프리필을 몰아 처리하면 그 요청의 첫 토큰은 빨리 나오지만 다른 사용자들의 토큰 간격이 급등한다. 둘째, 프리픽스 캐시 조회가 경로의 맨 앞단에 있다. 공유 시스템 프롬프트나 멀티턴 히스토리가 이미 캐시에 있으면 그만큼 prefill을 건너뛴다(5.4.4). 셋째, API·스케줄러가 아무리 영리해도 성능의 기반은 엔진 계층이 정한다. 실제 커널(FlashAttention·양자화 GEMM·투기적 디코딩)의 효율이 처리량 상한을 고정하고, 그 위 계층은 그 상한을 얼마나 잘 채우느냐의 문제일 뿐이다.
여기서 이 장 내내 쓸 두 단어를 구분해 두자. 엔진(engine) 은 실제로 GPU에서 토큰을 생성하는 최적화된 추론 코어 — 연속 배칭·커널·KV 관리·투기적 디코딩이다. 서버(server) 는 그 위에서 HTTP·스케줄링·인증을 담당하는 계층이다. 어떤 프레임워크는 둘을 한 몸으로 준다(vLLM·SGLang·TGI·LMDeploy — 설치하면 곧바로 OpenAI 호환 서버가 기동된다). 어떤 조합은 엔진과 서버가 분리돼 있다 — 대표적으로 TensorRT-LLM은 엔진이고, 이것을 흔히 NVIDIA의 Triton Inference Server라는 범용 추론 서버에 백엔드로 연결해 서빙한다.
| 구성 | 예 | 성격 |
|---|---|---|
| 통합형(엔진+서버 한 몸) | vLLM, SGLang, TGI, LMDeploy | 설치하면 곧바로 OpenAI 호환 서버가 기동된다. 운영이 단순. 대부분의 팀에 실용적 출발점 |
| 분리형(엔진 + 별도 서버) | TensorRT-LLM 엔진 + Triton Inference Server | 엔진을 빌드해 서버에 배포한다. 성능 상한이 높지만 빌드·배포 단계가 많고 운영 부담이 크다 |
| 오케스트레이션 계층 | Ray Serve, KServe, Triton | 위 엔진들을 여러 복제본·여러 모델로 묶어 오토스케일·라우팅. 프레임워크 "위"의 층 — 5.5 |
Triton과 TensorRT-LLM을 같은 물건으로 혼동하는 실수가 흔한데, 둘은 층이 다르다. Triton은 여러 백엔드(TensorRT-LLM·PyTorch·ONNX 등)를 호스팅하는 서버이고, TensorRT-LLM은 그 안에 통합되는 LLM 전용 엔진이다. "Triton으로 서빙한다"는 말은 실무에서 대개 "Triton + TensorRT-LLM 백엔드"를 뜻한다. 이 구분을 못 하면 벤치마크나 문서를 읽을 때 무엇을 무엇과 비교하는지 놓친다.
5.4.3 OpenAI 호환 API가 사실상의 표준이 된 이유
기능 목록에서 실무 선택을 실제로 가르는 것 중 첫째가 API 인터페이스다. 거의 모든 주요 프레임워크가 OpenAI의 /v1/chat/completions·/v1/completions 스키마와 호환되는 HTTP 서버를 제공하는데, 이것이 단순한 편의가 아니라 전략적 자산인 이유가 있다.
생태계가 이 스키마를 가정하고 자란다. OpenAI SDK, LangChain, LlamaIndex, 수많은 사내 클라이언트가 이 요청·응답 형식에 맞춰져 있다. 프레임워크가 호환 서버를 노출하면 애플리케이션은 base_url만 바꿔 곧바로 연결된다 — 코드를 거의 안 고친다.
# OpenAI 호환 서버는 대개 base_url만 바꾸면 연결된다 (개략 예)
export OPENAI_BASE_URL="http://localhost:8000/v1"
export OPENAI_API_KEY="dummy-or-your-key"
curl "$OPENAI_BASE_URL/chat/completions" \
-H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"my-model","messages":[{"role":"user","content":"안녕?"}],"stream":true}'
이 한 줄 교체가 뒤에서 볼 build vs managed(5.4.11)의 락인 전체를 완화한다. 클라이언트가 표준 인터페이스에만 의존하면, 매니지드 API로 시작해 규모가 커진 뒤 자체 서빙으로 옮기거나 그 반대로 가는 일이, 프레임워크를 vLLM에서 SGLang으로 바꾸는 일이, 전부 애플리케이션을 변경하지 않고 된다. 그래서 내 견해로는 서빙 아키텍처의 첫 결정은 프레임워크 선택이 아니라 "처음부터 모든 것을 OpenAI 호환 인터페이스 뒤에 숨긴다"여야 한다 — 그래야 나머지 결정이 되돌릴 수 있는 것이 된다.
다만 "호환"은 "완전 동일"이 아니라는 점을 대가로 안고 간다. 스트리밍 청크의 형태, usage 필드가 스트리밍에서 채워지는 시점, 함수 호출(tool call) 포맷, logprobs, 시드·결정성 보장, 구조화 출력 파라미터의 세부는 프레임워크마다 다르게 구현되거나 빠져 있다. 당신의 애플리케이션이 실제로 쓰는 필드가 그 프레임워크에서 그대로 동작하는지는 문서가 아니라 실제 요청으로 확인해야 한다 — "OpenAI 호환"이라는 배지 하나로 tool call과 logprobs가 동일하게 나온다고 가정하면 프로덕션에서 물린다.
5.4.4 프리픽스 캐시 — 겹치면 크게, 안 겹치면 무의미
여러 요청이 같은 앞부분(prefix)을 공유하는 경우가 실서비스에는 널려 있다 — 공유 시스템 프롬프트, 고정 few-shot 예시, 멀티턴 대화의 누적 히스토리, 에이전트가 매 스텝 끌고 다니는 도구 설명·컨텍스트. 이 겹치는 앞부분의 prefill을 캐시해 재사용하면 계산·지연·비용을 크게 줄인다. 원리와 용량 계획은 5.6에서 깊이 다루지만, 프레임워크 선택 관점에서 이 축이 왜 결정적일 수 있는지는 숫자로 보면 분명하다.
에이전트 워크로드를 하나 세워 보자. 시스템 프롬프트와 도구 스키마를 합친 공유 프리픽스가 2,000 토큰, 요청마다 다른 사용자 질의가 100 토큰, 출력이 200 토큰이라 하자. 8B 모델이고 프리필 비용은 토큰당 약 $2P$ FLOP이다(1.4.15의 $2P$ 규칙, $P=8\times10^9$).
프리픽스 캐시가 없으면 매 요청이 $2000+100=2100$ 토큰 전체를 프리필한다.
$$\text{prefill FLOP} = 2 \times (8\times10^9) \times 2100 \approx 3.36\times10^{13} = 33.6\ \text{TFLOP}$$
프리필은 연산 바운드라 GPU를 잘 쓴다 — dense fp16으로 실효 400 TFLOP/s가량 낸다 치면(개략) 프리필에 약 84 ms가 걸리고, TTFT가 그만큼부터 시작한다. 이제 같은 2,000 토큰 프리픽스가 이미 캐시에 있어 히트하면, 새로 프리필할 것은 사용자 질의 100 토큰뿐이다.
$$\text{prefill FLOP} = 2 \times (8\times10^9) \times 100 \approx 1.6\times10^{12} = 1.6\ \text{TFLOP} \;\Rightarrow\; \text{약 } 4\ \text{ms}$$
프리필 계산과 TTFT가 약 21배 줄었다. 게다가 그 2,000 토큰 프리픽스의 KV 캐시도 재계산하지 않고 그대로 참조하니, 반복되는 요청 사이에서 이 절약이 누적되어 시스템 전체 비용을 지배하게 된다. 에이전트처럼 매 스텝 같은 도구 설명을 끌고 다니거나, 수천 명이 같은 시스템 프롬프트를 공유하는 챗봇에서는 프리픽스 캐시 히트율이 곧 GPU 비용이다.
핵심 단서는 조건절에 있다. 이 이득은 겹침이 있을 때만 나온다. 매 요청이 완전히 다른 단발 프롬프트라면 히트율이 0에 수렴하고 절약도 0이다. 그러니 "프리픽스 캐시가 강한 프레임워크"가 후보 상위로 올라오는지는 오직 하나의 질문에 달렸다 — 내 워크로드의 프롬프트가 실제로 크게 겹치는가. 구현도 갈린다. vLLM의 자동 프리픽스 캐시나 SGLang의 RadixAttention(트리 기반으로 공유 프리픽스를 자동 탐지·재사용)은 겹치는 부분을 알아서 찾아 준다. 어떤 구현은 명시적 캐시 키를 요구하고, 어떤 것은 KV를 GPU 밖(CPU·디스크)으로 오프로드하는 다층 캐시를 둔다. 캐시 용량이 유한하므로 축출(eviction) 정책도 히트율을 좌우한다 — 이 세부는 5.6이다.
5.4.5 구조화 출력·제약 디코딩이 선택을 가르는 지점
애플리케이션이 JSON·스키마·특정 문법을 강제해야 하는 경우 — 함수 호출·도구 사용(6.5), 데이터 추출 파이프라인, 다운스트림 시스템에 그대로 먹일 출력 — 프레임워크의 제약 디코딩(constrained decoding) 지원이 선택 기준이 된다. 원리와 함정은 6.9에서 파지만, 선택 관점에서 챙길 것이 셋이다.
첫째, 무엇을 강제할 수 있는가. 백엔드가 outlines·xgrammar·lm-format-enforcer·자체 FSM 등으로 갈리고, 각자 JSON 스키마·정규식·문맥자유문법(CFG) 중 지원 범위가 다르다. 단순 JSON 객체면 대부분 되지만, 중첩 스키마나 재귀 문법이 필요하면 지원 여부가 후보를 자른다.
둘째, 성능 오버헤드다. 제약 디코딩의 메커니즘을 알면 왜 프레임워크마다 속도가 다른지가 보인다. 매 디코드 스텝에서, 현재 문법 상태에서 허용되는 다음 토큰의 집합을 계산해 어휘 전체($V$, 흔히 10만 이상)에 대한 마스크를 만들고, 금지된 토큰의 로짓을 $-\infty$로 설정해야 한다. 이 마스크를 매 스텝 순진하게 인터프리트 방식 FSM으로 계산하면 토큰당 CPU 시간이 1 ms를 넘길 수 있는데, 디코드 한 스텝이 5~10 ms인 상황에서 이것은 10~20%의 처리량 손실이다(개략). 그래서 문법을 미리 유한상태기계로 컴파일하고 허용 마스크를 사전 계산·캐시하는 구현(압축 FSM, xgrammar류)은 이 오버헤드를 거의 0으로 상각한다. 구조화 출력 비중이 높은 워크로드에서 이 최적화 성숙도가 큰 차이를 만들고, SGLang이 이 축을 강조하는 이유가 여기 있다.
셋째, 정확성과 품질의 맞바꿈이다. 강한 문법 강제는 형식 오류를 없애 주지만, 모델이 원래 내려던 토큰을 강제로 막는 순간 생성 품질이 미묘하게 떨어질 수 있다 — 문법이 허용하는 토큰만 남기다 보면 모델의 자연스러운 추론 경로가 꺾인다. 형식 보장과 내용 품질 사이의 균형은 6.9에서 다루지만, 프레임워크를 고를 때 "제약 디코딩을 지원하는가"뿐 아니라 "그 구현이 빠르고, 껐다 켰다 세밀하게 조절되는가"까지 봐야 한다.
5.4.6 멀티 LoRA — 하나의 베이스로 여러 테넌트
여러 파인튜닝 어댑터(3.8)를 하나의 베이스 모델 위에서 동적으로 서빙해야 하는 요구는 멀티테넌시에서 자주 나온다 — 고객사별 어댑터, 용도별 어댑터, 실험 그룹별 어댑터. 이것을 각각 별도의 완전 모델로 서빙하면 GPU 수가 급증하지만, 멀티 LoRA 서빙은 베이스 가중치와 KV를 공유한 채 어댑터만 요청별로 교체한다. 그 절약이 얼마나 큰지는 메모리로 계산하면 즉시 보인다.
LoRA 어댑터는 원래 가중치 행렬 $W$($d\times d$)를 전부 바꾸지 않고, 저랭크 두 행렬 $A$($d\times r$)와 $B$($r\times d$)의 곱만 더한다 — 행렬 하나당 파라미터가 $2dr$이다. 8B 모델($d_{model}=4096$)에서 랭크 $r=16$이면 어댑터가 적용되는 행렬 하나당 $2\times4096\times16 \approx 0.13$M 파라미터고, 어텐션과 MLP의 여러 투영에 걸쳐 32개 층에 적용하면 어댑터 하나가 대략 수천만 파라미터, fp16으로 수십~백여 MB다. 여유 있게 보아 어댑터당 80 MB라 하자.
베이스 8B fp16 가중치가 16 GB다. 어댑터 100개를 함께 서빙하면 $16\ \text{GB} + 100 \times 80\ \text{MB} = 16 + 8 = 24$ GB로, 80 GB GPU 한 장에 여유 있게 들어간다. 반면 100개 테넌트를 각각 완전 파인튜닝 모델로 서빙하면 $100 \times 16\ \text{GB} = 1600$ GB — GPU 스무 장이다. 멀티 LoRA는 스무 장 분량의 문제를 한 장으로 접는다. 이것이 어댑터 기반 멀티테넌시의 경제학이고, vLLM·TGI·SGLang 등이 이 기능을 갖춘 이유다.
무비용은 아니다. 동시에 활성화할 수 있는 어댑터 수, 요청마다 어댑터를 교체하는 전환 비용, 어댑터를 GPU에 상주시킬지 필요할 때 로드할지의 관리 방식이 프레임워크마다 다르다. 한 배치에 서로 다른 어댑터를 쓰는 요청이 섞이면 커널이 이를 효율적으로 처리하는지도 성능을 가른다. 멀티 LoRA가 요구의 큰 몫이라면, "지원한다"에서 멈추지 말고 동시 어댑터 수와 전환 비용이 내 부하에서 견디는지를 재야 한다.
5.4.7 주요 오픈 프레임워크의 태생과 성격
이제 프레임워크 자체를 본다. 아래는 순위가 아니라 성격 스케치다 — 각자 다른 태생과 다른 강점을 갖고 태어났고, 어제의 차별점을 오늘 서로 흡수하며 빠르게 닮아 간다. 구체 기능은 집필 시점의 대략이니, 채택 전엔 현재 문서로 확인해야 한다.
vLLM. UC Berkeley에서 나온 PagedAttention(5.3)의 원조 구현으로, 오픈소스 LLM 서빙의 사실상 기준점 중 하나가 됐다. 파이썬 중심이라 접근성이 좋고, 지원 모델·기능의 폭이 넓다. 연속 배칭과 PagedAttention이 처음부터 핵심이었고, 자동 프리픽스 캐시, 멀티 LoRA, 다양한 양자화(GPTQ·AWQ·FP8 등), 투기적 디코딩, 구조화 출력(outlines·xgrammar 등 백엔드), OpenAI 호환 서버를 대체로 갖췄다. pip install 후 한 줄로 서버가 기동되는 운영 편의가 강점이다. 하드웨어는 NVIDIA가 1급이고 AMD(ROCm)·일부 TPU·Intel Gaudi·CPU 등으로 확장 중인데 백엔드별 성숙도는 제각각이다. 특별한 제약이 없는 범용 GPU 서빙의 실용적 기본 출발점으로, 내 견해로는 "무엇을 골라야 할지 모르겠다"의 첫 답이 대개 여기다.
TGI (Text Generation Inference). Hugging Face의 프로덕션 지향 서버로, Rust로 구현한 서버·라우터에 Python 모델을 추가해 견고한 운영 특성을 노린다. 연속 배칭, FlashAttention·PagedAttention 통합, 텐서 병렬, 다양한 양자화(bitsandbytes·GPTQ·AWQ·EETQ·FP8 등), OpenTelemetry 기반 관측성, HF 모델 허브와의 밀착이 강점이고, OpenAI 호환(Messages) API와 구조화 출력도 지원한다. 컨테이너로 배포하기 좋게 다듬어져 있어 운영 난이도는 중간이며, 프로덕션 텔레메트리가 잘 갖춰진 것이 차별점이다. 라이선스는 주의가 필요하다 — TGI는 과거 한때 비-OSI 라이선스로 바뀌었다가 다시 Apache 2.0으로 돌아온 이력이 있으니, 상업 배포 전 현재 라이선스를 반드시 확인하라(9.2).
TensorRT-LLM. NVIDIA의 NVIDIA 전용 고성능 엔진으로, 모델을 GPU·정밀도별로 미리 컴파일(ahead-of-time)해 최적화된 실행 엔진을 만든다. 흔히 Triton Inference Server에 적재해 서빙한다. 잘 튜닝하면 NVIDIA GPU에서 최상위권 처리량·지연을 내며, in-flight 배칭(연속 배칭), FP8·INT8·INT4(AWQ·GPTQ)·SmoothQuant, paged KV, 투기적 디코딩을 갖췄다. 대가는 운영 난이도다 — 엔진 빌드 단계가 필수이고, 모델·GPU·정밀도·최대 배치·최대 시퀀스 길이 중 하나만 바꿔도 다시 빌드해야 한다. 유연성을 성능과 맞바꾼 설계라, 실험이 잦은 초기 단계보다 워크로드가 고정된 대규모 운영에 맞고, CI에 빌드 파이프라인을 넣을 각오가 필요하다. NVIDIA 전용이라는 점이 최대 제약이자 최적화의 원천이다.
SGLang. 복잡한 프롬프트·구조화 생성·에이전트 워크로드를 겨냥해 나왔고, RadixAttention(트리 기반 프리픽스 KV 재사용, 5.4.4)과 빠른 제약 디코딩이 특징이다. 공유 프리픽스를 자동·적극적으로 재사용하므로 시스템 프롬프트·few-shot·멀티턴·에이전트처럼 프리픽스가 크게 겹치는 워크로드에 유리하고, 압축 FSM·xgrammar류로 구조화 출력을 빠르게 처리한다. 연속 배칭·OpenAI 호환 서버에 더해 구조화 LLM 프로그램을 짜기 위한 프런트엔드 언어도 제공한다. 처리량이 vLLM과 겨루거나 일부 시나리오에서 앞선다는 보고가 있지만, 늘 그렇듯 워크로드에 달렸다. 운영 난이도는 낮음~중간, 하드웨어는 NVIDIA가 1급이다.
이 네 프레임워크의 관계를 오해하지 않는 것이 중요하다. 이들은 서로의 좋은 아이디어 — 프리픽스 캐시, 프리필 청킹, xgrammar 기반 제약 디코딩, 투기적 디코딩 — 를 빠르게 흡수한다. 그래서 "SGLang만 프리픽스에 강하다"거나 "vLLM에는 구조화 출력이 없다" 같은 문장은 반년만 지나도 틀리기 쉽다. 태생이 남긴 성격의 결은 남지만, 기능 격차는 시간과 함께 메워진다 — 이것이 순위표를 만들지 않는 또 하나의 이유다.
5.4.8 로컬·엣지 축 — llama.cpp·Ollama·LMDeploy·기타
프로덕션 GPU 서빙 바깥에도 중요한 축이 있고, 여기서 프레임워크의 지형이 완전히 달라진다.
llama.cpp는 C/C++로 짜였고 GGUF 포맷을 쓴다. CPU, Apple Silicon(Metal), 소비자 GPU(CUDA·Vulkan)에서 강하고, 다양한 GGUF 양자화(Q4_K_M 등)와 OpenAI 호환 서버(llama-server)를 내장한다. 대규모 동시성보다 엣지·로컬·단일 사용자에 최적화돼 있다(8.4). Ollama는 그 위에 추가된 개발자·로컬 친화 도구로, ollama run으로 모델을 당겨 쓰는 간편함이 핵심이고 OpenAI 호환 API를 제공한다. 로컬 개발·프로토타이핑에는 훌륭하지만 고동시성 프로덕션 서빙용으로 설계된 것은 아니다. LMDeploy는 TurboMind 엔진 기반의 고성능 서빙(NVIDIA 중심)으로, AWQ·W4A16·KV 캐시 양자화, 지속 배칭, OpenAI 호환 API를 갖췄고 특정 모델 계열에서 강점을 보인다. 그 밖에 컴파일 기반으로 WebGPU·모바일 등 폭넓은 하드웨어를 노리는 MLC-LLM, DeepSpeed-MII, OpenLLM 등이 각자 다른 니치를 겨냥한다.
이 축을 프로덕션 GPU 프레임워크와 같은 표에 넣고 점수로 비교하는 것은 범주 오류다. llama.cpp가 "연속 배칭이 제한적"인 것은 약점이 아니라, 애초에 노리는 자리(단일 사용자·엣지)에서 그것이 필요 없기 때문이다. 반대로 vLLM을 노트북에서 소비자 GPU로 실행해 보려는 것도 잘못된 도구 선택이다. 하드웨어와 배포 환경이 이 두 세계를 먼저 가른다 — 데이터센터 GPU에 고동시성으로 서빙하느냐, 소비자 하드웨어에 낮은 동시성으로 추가하느냐.
5.4.9 능력 지도 — 순위표를 만들지 않는 이유
아래 두 표는 "누가 1등"이 아니라 "각자 무엇을 지원하고 어떤 성격인가"를 정리한 것이다. 셀 값은 집필 시점의 대략이며 버전에 따라 바뀐다. 빈 칸이나 "부분"이 곧 "나쁨"을 뜻하지 않는다 — 당신의 요구에 그 칸이 중요한가만이 중요하다.
| vLLM | TGI | TensorRT-LLM | SGLang | llama.cpp/Ollama | |
|---|---|---|---|---|---|
| 연속 배칭 | ○ | ○ | ○(in-flight) | ○ | 제한적(로컬 지향) |
| PagedAttention류 KV | ○ | ○ | ○(paged KV) | ○(+RadixAttention) | 자체 방식 |
| 프리픽스 캐시(5.6) | ○(자동) | 부분 | 부분 | ○(RadixAttention) | 부분 |
| 양자화(5.2) | GPTQ·AWQ·FP8 등 | 다수 백엔드 | FP8·INT8·INT4·SmoothQuant | AWQ 등 | GGUF 다수 |
| 투기적 디코딩(5.3) | ○ | 부분 | ○ | 부분 | 부분 |
| 멀티 LoRA(3.8) | ○ | ○ | 부분 | ○ | 제한적 |
| 구조화 출력(6.9) | ○(outlines·xgrammar) | ○(문법·JSON) | 부분 | ○(빠른 제약) | 부분(문법) |
| OpenAI 호환 API | ○ | ○ | Triton 경유 | ○ | ○ |
| 엔진 유형 | 주 하드웨어 | 운영 난이도 | 대표적으로 맞는 자리 | |
|---|---|---|---|---|
| vLLM | 통합·런타임 최적화 | NVIDIA 1급, AMD 등 확장 | 낮음 | 범용 GPU 서빙의 실용적 기본 |
| TGI | 통합, Rust 서버 | NVIDIA·AMD·Gaudi·Inferentia | 중간 | HF 생태계·프로덕션 텔레메트리 중시 |
| TensorRT-LLM | 분리·사전 컴파일 | NVIDIA 전용 | 높음 | 고정 대규모 워크로드, NVIDIA를 끝까지 짜낼 때 |
| SGLang | 통합 | NVIDIA 1급 | 낮음~중간 | 프리픽스 재사용 큰 워크로드·구조화·에이전트 |
| llama.cpp/Ollama | 로컬 엔진 | CPU·Apple·소비자 GPU | 낮음(로컬) | 엣지·로컬·프로토타입·단일 사용자(8.4) |
이 표를 절대 "○의 개수 합계"로 읽지 말아야 한다. 당신에게 필요 없는 칸의 ○는 가치가 정확히 0이다. 멀티 LoRA도 투기적 디코딩도 안 쓰는 워크로드에서 그 두 칸의 ○는 선택에 아무 무게가 없다. 표는 후보를 좁히는 필터이지 점수판이 아니다.
프레임워크를 종합 순위로 줄 세우는 표가 위험한 이유는 세 가지를 숨기기 때문이다. 첫째, 워크로드 의존성이다. 짧은 프롬프트·긴 생성(챗)과 긴 프롬프트·짧은 생성(RAG·요약)은 병목이 완전히 다르다(5.1). 프리픽스가 겹치는 에이전트 워크로드에서 앞서는 프레임워크가, 매번 다른 단발 프롬프트에서는 그 이점 전체를 잃는다. 둘째, 하드웨어·모델 의존성이다. 같은 프레임워크도 A100·H100·소비자 GPU·AMD에서 상대 순위가 뒤집힐 수 있고, 특정 모델 구조(MoE·GQA, 1.6)에 대한 커널 최적화 성숙도도 제각각이다. 셋째, 버전 의존성이다. 릴리스 주기가 짧아 지난달 벤치마크가 이번 달에 뒤집히는 일이 흔하다 — 순위표는 발행되는 순간부터 노후화된다. 그래서 이 책의 입장은 분명하다. 범주로 후보를 두세 개로 좁히고, 자기 워크로드로 직접 재라(5.4.12).
5.4.10 요구별로 후보를 좁히기
이제 의사결정이다. 강요가 아니라 프로필별 후보 좁히기이며, 실서비스는 대개 아래 프로필 몇 개의 조합이다. 하드웨어가 가장 강하게 후보군을 자르므로 거기서 시작한다.
처리량이 최우선일 때 — 동시 요청이 많고 개별 지연보다 총 처리량(GPU당 토큰/초, 비용/토큰)이 중요한 경우(대량 문서 처리, 오프라인 생성, 저비용 대량 서비스). 연속 배칭과 PagedAttention이 성숙한 엔진으로 배치를 크게 채워 GPU를 포화시키는 것이 기본이고(vLLM·SGLang·TGI), 양자화로 메모리를 줄여 동시 시퀀스를 더 늘린다(5.2). NVIDIA를 최대한 활용해야 하고 워크로드가 고정적이면 TensorRT-LLM의 컴파일 이점이 커지지만 빌드 비용을 본다. 도입이 쉬운 쪽은 vLLM·SGLang, 튜닝 여력이 있으면 TensorRT-LLM이다.
지연이 최우선일 때 — 사용자가 실시간으로 기다리는 대화형이라 TTFT·TPOT가 우선인 경우. 투기적 디코딩으로 TPOT를 줄이고, 프리필 청킹으로 긴 프롬프트가 TTFT를 망치지 않게 하고, 프리픽스 캐시로 반복 컨텍스트의 prefill을 건너뛰어 TTFT를 단축한다. 다만 지연과 처리량은 상충한다 — 배치를 키우면 처리량은 오르고 개별 지연은 나빠진다(5.1.10). 그래서 지연 우선이면 배치를 낮게 설정해 GPU 활용도를 다소 포기하고 응답성을 사는데, 이 균형을 스케줄러의 우선순위·선점·SLA 정책이 조율한다(5.5). 도입이 쉬운 쪽은 SGLang·vLLM, 극한 저지연에 튜닝 여력이 있으면 TensorRT-LLM이다.
하드웨어가 제약일 때 — NVIDIA면 모든 후보가 가능하니 위 요구로 갈리지만, AMD(ROCm)면 vLLM·TGI 등의 ROCm 지원 성숙도를 버전별로 확인해야 하고, Apple Silicon·CPU·엣지면 llama.cpp·Ollama·MLC-LLM으로 넘어가며(8.4), TPU·기타 가속기는 지원이 제한적·유동적이라 현재 상태를 반드시 확인해야 한다. TensorRT-LLM은 NVIDIA 전용이라 이식성이 없다는 점이 최대 제약이다.
구조화 출력 비중이 높을 때 — JSON·스키마·문법 강제가 요청의 큰 몫이면(도구 사용·에이전트·데이터 추출), 제약 디코딩 백엔드와 그 성능이 강한 프레임워크(SGLang, vLLM+xgrammar 등)를 후보 상위에 배치하고, 6.9의 함정(문법 강제가 품질을 저하시킬 수 있음)을 함께 본다.
멀티 LoRA 멀티테넌시일 때 — 여러 어댑터를 하나의 베이스 위에서 동적으로 서빙해야 하면(5.4.6), 멀티 LoRA를 지원하는 프레임워크(vLLM·TGI·SGLang 등)가 필수이고, 동시 활성 어댑터 수·전환 비용·메모리 관리 방식이 내 부하에서 견디는지를 재야 한다.
간편함·로컬·프로토타입이 우선일 때 — 빠르게 시험해 보거나 로컬·엣지·단일 사용자·데모라면 Ollama(가장 간편)·llama.cpp(유연한 로컬), 혹은 프로덕션으로 이어 갈 로컬이면 vLLM이다. 프로토타입을 OpenAI 호환 API로 만들어 두면 나중에 프로덕션 프레임워크로 갈아탈 때 클라이언트를 거의 안 고친다(5.4.3).
| 최우선 요구 | 왜 | 우선 후보(출발점) |
|---|---|---|
| 처리량·비용/토큰 | 큰 배치로 GPU 포화 | vLLM·SGLang ↔ TensorRT-LLM(튜닝) |
| 저지연 대화 | TTFT·TPOT·투기적·프리필 청킹 | SGLang·vLLM ↔ TensorRT-LLM(극한) |
| NVIDIA 극한·고정 워크로드 | 사전 컴파일 이점 | TensorRT-LLM(+Triton) |
| 프리픽스 겹침·에이전트 | RadixAttention·자동 프리픽스 캐시 | SGLang·vLLM |
| 구조화 출력 비중 | 빠른 제약 디코딩 | SGLang·vLLM |
| 멀티 LoRA 멀티테넌시 | 어댑터 동적 서빙 | vLLM·TGI·SGLang |
| AMD·Apple·CPU·엣지 | 하드웨어 이식성 | vLLM/TGI(ROCm)·llama.cpp·Ollama·MLC |
| 간편·로컬·프로토타입 | 도입 속도 | Ollama·llama.cpp·vLLM |
| HF 생태계·프로덕션 텔레메트리 | 밀착·관측성 | TGI |
이 "우선 후보"는 후보를 좁히는 출발점이지 결론이 아니다. 실제 순위는 당신의 워크로드에서만 정해진다.
5.4.11 자체 서빙과 매니지드 — build vs managed
지금까지는 "어떤 프레임워크로 자체 서빙하나"였다. 그 앞에 더 큰 갈림이 있다 — 애초에 자체 서빙을 할 것인가, 매니지드 엔드포인트를 살 것인가. 이는 9.1(build vs buy 전략)의 서빙 판이고, 여기서는 서빙 관점의 경제학만 다룬다.
세 갈래의 성격이 다르다. 모델 API(완전 매니지드·serverless)는 벤더의 모델을 토큰 단위로 호출하며 GPU·프레임워크·스케일링을 전혀 안 만진다 — 가장 빠른 출발과 최소 운영 부담을 주는 대신 토큰당 비용·컨트롤 제약·데이터 이동·모델 종속을 안는다(Anthropic/Claude 등 특정 API의 모델 ID·가격·rate limit은 기억으로 단정하지 말고 공식 문서나 claude-api로 확인). 매니지드 엔드포인트는 오픈 모델이나 내 파인튜닝 모델을 추론 제공자의 관리형 GPU에 적재하는 것으로, 내부적으로 위 프레임워크(흔히 vLLM·TGI류)가 실행되지만 오토스케일·패치·인프라를 벤더가 관리한다 — 컨트롤과 편의의 중간이다. 자체 서빙은 내 GPU에 프레임워크를 직접 추가하는 것으로, 최대 컨트롤·규모의 비용 이점·데이터 잔류를 주는 대신 GPU 운영·오토스케일·용량 계획·장애 대응·업그레이드의 부담을 스스로 진다(5.5).
무엇이 갈림을 정하는지는 여섯 축으로 정리된다.
| 축 | 매니지드 쪽으로 | 자체 서빙 쪽으로 |
|---|---|---|
| 규모·꾸준함 | 트래픽이 작거나 불규칙 | 크고 꾸준(고정 GPU 상각의 손익분기, 7.5) |
| 팀 역량 | GPU·인프라 인력 부족 | 서빙·GPU 운영 역량 보유 |
| 컨트롤 요구 | 표준 기능이면 충분 | 커스텀 모델·커널·양자화·스케줄링 필요 |
| 데이터·규제 | 벤더 처리 허용 | 데이터 잔류·프라이버시·온프레미스 필수(8.4·9.3) |
| 출시 속도 | 지금 당장 | 셋업 기간 감수 가능 |
| 비용 구조 | 가변비(토큰당) 선호 | 고정비(GPU) 상각이 유리한 규모 |
"GPU 시간이 싸니까 자체 서빙이 무조건 저렴하다"는 흔한 오해를 손익분기 계산으로 깨 두자. 숫자는 전부 방향을 정하기 위한 개략값이고 실제 값은 반드시 확인해야 한다. H100 한 장을 시간당 약 $3에 빌린다 하고(스팟·약정에 따라 크게 다름), 연속 배칭으로 출력 토큰을 지속적으로 약 10,000 tok/s 뽑는다 하자(모델·GPU·프리필 부담에 따라 다름, 개략).
$$\text{출력 토큰당 비용}_{100\%} = \frac{\$3/\text{hr}}{10{,}000\ \text{tok/s} \times 3600\ \text{s/hr}} = \frac{\$3}{3.6\times10^{7}\ \text{tok}} \approx \$0.083\ /\text{백만 토큰}$$
여기까지만 보면 자체 서빙이 압도적으로 싸 보인다. 그런데 이 값은 GPU가 24시간 100% 포화라는 가정이다. 실제 트래픽은 불균일해서 평균 가동률이 30%면, 유휴 시간의 GPU 비용을 실제 처리한 토큰이 나눠 져야 하므로 유효 비용이 세 배가 넘는다.
$$\text{출력 토큰당 비용}_{30\%} = \frac{\$0.083}{0.30} \approx \$0.28\ /\text{백만 토큰}$$
여기에 프리필 계산 비용, 그리고 무엇보다 GPU 운영을 맡는 엔지니어의 인건비가 더 추가된다. 이제 매니지드 오픈 모델 엔드포인트가 백만 출력 토큰당 약 \$0.20~0.60(모델 크기·제공자에 따라 다름, 확인 필요)라 하면 그림이 뒤집힌다. 손익분기 가동률은 대략 "매니지드 단가 ÷ 100% 자체 서빙 단가"로, \$0.20 매니지드 기준 약 $0.083/\$0.20 \approx 0.42$ — 즉 GPU 비용만 놓고도 가동률이 40%를 넘어야 자체 서빙이 이기기 시작하고, 인건비까지 넣으면 그 문턱이 더 올라간다. 요점은 이것이다. 자체 서빙의 경제학은 "GPU 시간이 싸다"가 아니라 "GPU 시간 × 가동률 × (1 + 운영 오버헤드)"이고, 트래픽이 작거나 불균일하면 매니지드의 가변비가 이긴다(정밀한 유닛 이코노믹스는 7.5). 그래서 기본은 자체 서빙으로 상시 부하를 받고 스파이크·오프피크만 매니지드로 넘기는 하이브리드가 흔한 절충이다.
이 결정을 "지금 한 번의 되돌릴 수 없는 선택"으로 만들지 않는 핵심이 앞서 본 OpenAI 호환 인터페이스다(5.4.3). 세 갈래가 모두 같은 API를 노출하도록 클라이언트를 작성해 두면, 매니지드로 시작해 규모가 커진 뒤 자체 서빙으로 옮기거나 그 반대로 가는 스위칭 비용이 낮게 유지된다. build vs buy가 미룰 수 있고 되돌릴 수 있는 결정이 되는 것이다.
5.4.12 벤치마크를 의심하고 제대로 재기
이 장의 모든 서술은 하나로 수렴한다 — 후보를 좁힌 뒤 당신 워크로드로 직접 재라. 남의 벤치마크를 그대로 믿으면 안 되는 이유부터 분명히 해 두자.
발표된 벤치마크는 — 프레임워크 프로젝트든 GPU 벤더든 클라우드든 — 흔히 자기에게 유리한 조건에서 측정된다. 다섯 가지를 의심해야 한다. 워크로드가 내 것과 같은가 — 입력·출력 길이 분포, 동시성, 프롬프트 겹침, 배치 대 온라인이 다르면 결과가 뒤집힌다. 모델·하드웨어·정밀도가 같은가 — 특정 조합에 유리하게 고른 것일 수 있다. 버전이 언제인가 — 몇 주 만에 판이 바뀌는 분야다. 무슨 지표인가 — "처리량"만 자랑하고 꼬리 지연(p99 TTFT·TPOT)을 감출 수 있는데, 처리량과 지연은 상충하므로 한쪽만 보면 속는다. 그리고 누가 쟀는가 — 자기 프레임워크가 이기는 조건을 골랐거나, 경쟁 프레임워크는 기본 설정으로 두고 자기 것만 최적 튜닝한 것은 아닌가. 마지막 함정이 특히 흔하다.
제대로 재는 절차는 워크로드 정의에서 시작해 후보 좁히기, 조건 고정, 부하 스윕, 지표 판정, 재현·기록으로 이어진다.
측정할 핵심 지표는 5.1·7.6에서 정의한 것들이다 — 첫 토큰까지의 TTFT(대화형 체감 지연), 토큰 사이 간격 TPOT/ITL(스트리밍 속도), 시스템 전체와 GPU당 처리량, 그리고 동시성을 높일 때 처리량과 지연이 함께 어떻게 변하는지의 동시성 곡선(포화점·무릎). 여기서 두 가지를 놓치면 프로덕션에서 다른 그림을 만난다. 첫째, 평균이 아니라 꼬리(p95·p99)로 봐야 한다 — 사용자 경험은 평균이 아니라 꼬리가 정하고, 큐 대기가 길어지면 TTFT의 꼬리가 급증한다. 둘째, 한 동시성 수준에서만 재면 안 된다 — 프레임워크의 상대 우위는 동시성에 따라 뒤집힌다(낮은 동시성에선 A, 높은 동시성에선 B가 유리한 식). 부하를 스윕해야 실제 그림이 나온다. 이 모든 지표를 GPU 시간당 비용으로 나눈 비용/토큰이 최종 판정 기준이고, SLO를 만족하며 처리한 양만 세는 goodput으로 재는 것이 정직하다(7.5·5.1.7).
마지막으로, 이 영역이 빨리 바뀐다는 사실 자체를 운영에 내장해야 한다. OpenAI 호환 뒤로 서빙을 숨겨 프레임워크 교체 비용을 낮추고(5.4.3·5.4.11), 프레임워크 버전을 고정한 채 업그레이드는 회귀 테스트와 함께 높이며(새 버전이 늘 빠른 것도 아니고 회귀가 생기기도 한다, 8.2), 정기 재벤치마크를 운영 루틴에 넣는다. 반년 전의 선택이 지금도 최적이라는 보장은 없다. 양자화를 켰다면 처리량만 재지 말고 품질 저하도 함께 재야 한다 — 프레임워크가 지원한다는 것과 내 워크로드에서 품질이 견딘다는 것은 다른 문제다(5.2). 그리고 어떤 프레임워크든 상업 배포 전에는 현재 라이선스를 확인하라. TGI의 이력이 보여 주듯 라이선스는 바뀐다(9.2).
프레임워크를 골랐다면, 다음은 그것을 여러 GPU·여러 복제본·여러 테넌트로 운용하는 층이다 — 오토스케일, 멀티테넌시, GPU 스케줄링, 용량 계획. 5.5가 그 층을 다루고, 그다음 5.6에서 이 장이 "선택의 축"으로만 다룬 프리픽스 재사용과 롱컨텍스트·라우팅의 원리와 비용을 근본까지 분석한다.