16장. vLLM 서빙 벤치마킹과 RAG 최적화
서빙 엔진이 하는 일
식당에서 요리사 혼자 주문을 하나씩 받아 요리하면, 손님이 많아질수록 줄이 길어집니다. 주방을 잘 돌리는 식당은 주문을 모아 화구를 빈틈없이 쓰고, 다 된 접시는 바로 내보내고 다음 주문을 올립니다. 서빙 엔진은 이런 주방장입니다. 모델(요리법)은 같아도 주방을 돌리는 솜씨에 따라 같은 시간에 내보내는 접시 수가 크게 달라집니다.
그림의 세 카드는 지금까지 쓴 두 가지와 이 장에서 새로 보는 vLLM 입니다. 왼쪽 transformers 의 generate 는 학습과 실험에 좋고, 가운데 Ollama 는 한 사람이 PC 에서 편하게 쓰기에 좋고, 오른쪽 vLLM 은 많은 사용자가 동시에 부르는 서버에 맞춥니다. 여기서 요리사가 모델, 주방을 돌리는 방식이 서빙 엔진입니다.
서빙 엔진 견주기
| 엔진 | 잘하는 것 | 이 과목에서 |
|---|---|---|
transformers generate | 모델을 고치며 실험, 학습 | 3, 5, 15장 |
| Ollama(llama.cpp) | PC 에서 양자화 모델을 쉽게, CPU 와 GPU 를 나눠서 | 6장부터 |
| vLLM | 동시 요청이 많은 GPU 서버, 높은 처리량 | 이 장(안내 코드) |
| TGI, SGLang | vLLM 과 비슷한 서버용 엔진 | 이름만 |
같은 코드로 엔진 바꾸기
Ollama 와 vLLM 은 모두 OpenAI 호환 주소(/v1/chat/completions)를 엽니다. 그래서 6장의 openai 코드에서 주소와 모델 이름만 바꾸면 엔진을 갈아 끼울 수 있습니다.
# 엔진마다 다른 것은 주소와 모델 이름뿐이다
engines = {
"Ollama": {"base_url": "http://127.0.0.1:11434/v1", "model": "qwen2.5:3b"},
"vLLM": {"base_url": "http://127.0.0.1:8000/v1", "model": "Qwen/Qwen2.5-3B-Instruct"},
}
# 요청 본문은 둘 다 같은 꼴이다
body = {"messages": [{"role": "user", "content": "연차는 며칠인가요?"}], "max_tokens": 80, "temperature": 0}
for name, e in engines.items():
print(f"{name:7s} POST {e['base_url']}/chat/completions model={e['model']} 본문 칸={list(body)}")코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
base_url | base(바탕)와 URL(Uniform Resource Locator, 인터넷 주소)을 붙인 말이다. 요청을 보낼 서버의 기본 주소다 |
11434, 8000 | 포트(port, 항구) 번호다. 한 PC 안에서 어느 프로그램에게 보낼지 가리킨다. Ollama 는 11434, vLLM 은 8000 을 흔히 쓴다 |
/chat/completions | completion(완성, 마저 채움)에서 온 이름이다. 대화를 보내면 다음 답을 채워 주는 OpenAI 꼴의 주소다 |
"messages" | 대화 기록을 담는 칸이다. 말 하나는 role(역할, 누가 말했나)과 content(내용)로 적는다 |
"max_tokens": 80 | 답을 최대 80토큰까지만 쓰게 한다. 출력 길이를 묶어 시간을 맞춘다 |
"temperature": 0 | temperature(온도)를 0 으로 두면 가장 그럴듯한 토큰만 골라 매번 같은 답이 나온다(3장) |
list(body) | 사전의 열쇠만 목록으로 꺼낸다. 본문에 어떤 칸이 있는지 보여 준다 |
이 코드가 뜻하는 것
배달 앱으로 같은 메뉴를 시킨다고 생각해 봅니다. 주문서의 꼴(메뉴, 수량, 요청 사항)은 어느 가게에 보내든 같고, 바뀌는 것은 받는 가게의 주소뿐입니다. 여기서 주문서가 요청 본문 body 이고, 가게 주소가 base_url 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
engines = {...} | 엔진마다 주소와 모델 이름을 적어 둔다 | 가게 두 곳의 주소록 |
body = {...} | 두 엔진에 똑같이 보낼 요청 본문을 만든다 | 한 장으로 쓴 주문서 |
for name, e in engines.items() | 엔진을 하나씩 돌며 보낼 곳을 출력한다 | 같은 주문서를 가게마다 어디로 보낼지 적어 본다 |
출력 두 줄에서 본문 칸=['messages', 'max_tokens', 'temperature'] 는 똑같고 주소와 모델 이름만 다릅니다. 이 코드는 출력만 하고 실제로 요청을 보내지는 않습니다. 비유와 다른 점은, 엔진마다 같은 모델이라도 답이 조금씩 다를 수 있다는 것입니다.
주의할 점
- 엔진마다 같은 모델이라도 기본 설정(양자화, 최대 길이, 샘플링 기본값)이 달라 답이 조금씩 다릅니다. 엔진을 바꾸면 품질도 다시 잽니다.
- 사용자가 한 명이면 vLLM 의 장점이 잘 드러나지 않습니다. 엔진은 동시 사용자 수와 GPU 에 맞춰 고릅니다.