목차
sLLM 서빙하기
학습한 sLLM은 파일로 저장하는 것만으로 사용자가 호출할 수 없다. 요청을 받아 토큰으로 바꾸고, 모델을 실행한 뒤, 생성된 토큰을 다시 응답으로 보내는 서빙 서버가 필요하다. 모델이 작아도 동시 사용자가 늘면 입력 처리, 출력 생성, KV 캐시가 GPU 자원을 나눠 쓴다.
sequenceDiagram
participant U as 사용자
participant A as 애플리케이션/API
participant S as 모델 서빙 서버
participant G as GPU 모델
U->>A: 질문 전송
A->>A: 인증·입력 길이 확인
A->>S: 채팅 메시지와 생성 제한
S->>G: 토큰화·배치·추론
G-->>S: 생성 토큰
S-->>A: 스트리밍 또는 완성 응답
A-->>U: 검증된 답변
서빙 서버 앞의 애플리케이션 계층은 인증과 입력 제한을 맡고, 모델 서버는 추론을 맡는다. 모델 응답을 그대로 권한 있는 명령으로 실행하는 구조라면 별도 검증 단계가 필요하다.
1. 서빙 전에 함께 보관할 것
미세 조정 모델을 배포할 때는 기본 모델 ID와 리비전, 어댑터, 토크나이저, 채팅 템플릿, 생성 설정을 한 묶음으로 기록한다. 어댑터만 복사하고 다른 기본 모델 리비전을 불러오면 학습 때와 다른 결과가 나올 수 있다. 입력 길이와 최대 출력 토큰도 서비스 정책으로 정한다.
모델을 올리기 전 대표 요청 20~50개를 고정해 로컬 추론으로 확인한다. 형식 준수, 빈 응답, 반복 생성, 종료 토큰 처리, 긴 입력에서의 잘림을 먼저 점검한다.
2. 로컬 서버로 API 흐름 확인하기
vLLM 공식 문서는 OpenAI 호환 채팅 API를 설명한다. 아래는 공개 모델을 로컬 주소에 띄워 HTTP 호출 형식을 확인하는 예시다. 설치와 하드웨어 지원은 사용하는 vLLM 버전의 문서를 확인한다.
vllm serve Qwen/Qwen2.5-0.5B-Instruct --host 127.0.0.1 --port 8000
curl http://127.0.0.1:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "Qwen/Qwen2.5-0.5B-Instruct",
"messages": [{"role": "user", "content": "배송 지연 문의를 한 문장으로 요약해 줘."}],
"max_tokens": 80,
"temperature": 0
}'
model 값과 채팅 템플릿은 실제 서빙한 모델에 맞춰야 한다. 학습한 LoRA 어댑터를 사용할 때는 서버가 해당 어댑터를 어떻게 로드하고 이름을 노출하는지 확인한다. 로컬 테스트 명령을 외부 네트워크에 그대로 노출하지 않는다. 운영 환경에서는 API 게이트웨이 또는 애플리케이션 계층에서 인증, 요청 크기 제한, 속도 제한을 적용한다.
3. 속도는 한 숫자로 설명하기 어렵다
LLM 응답 시간은 입력을 처리하는 프리필(prefill)과 토큰을 하나씩 생성하는 디코딩(decoding)으로 나눠 볼 수 있다. 입력이 길면 첫 토큰까지의 시간(TTFT)이 늘고, 출력이 길면 완료 시간과 KV 캐시 사용량이 늘어난다. 요청을 여러 개 동시에 처리하면 대기열도 생긴다.
| 지표 | 뜻 | 함께 기록할 조건 |
|---|---|---|
| TTFT | 요청 후 첫 토큰까지의 시간 | 입력 길이, 동시 요청 수 |
| TPOT | 출력 토큰 하나를 만드는 평균 시간 | 출력 길이, 배치 상태 |
| 처리량 | 단위 시간에 처리한 요청/토큰 | 성공률, 지연 시간 |
| KV 캐시 사용률 | 생성 중 문맥을 보관한 비율 | 최대 문맥 길이, 동시성 |
vLLM 메트릭 문서는 /metrics에서 TTFT, 토큰별 지연, 대기 요청, KV 캐시 사용량 등을 확인하는 방법을 설명한다. 평균 지연만 기록하면 일부 사용자에게 발생하는 긴 대기 시간을 놓치므로 p95 같은 상위 지연도 함께 본다.
4. 장애와 품질을 함께 운영하기
- 메모리 부족: 동시 요청 수, 최대 입력·출력 길이, KV 캐시 예산을 줄여 원인을 분리한다.
- 느린 첫 토큰: 긴 프롬프트와 대기열을 나눠 측정한다. 검색 결과를 과도하게 붙인 것은 아닌지도 본다.
- 응답 반복 또는 형식 오류: 배포된 토크나이저·채팅 템플릿·종료 토큰이 학습 때와 같은지 확인한다.
- 품질 회귀: 새 모델을 전체 요청에 적용하기 전에 고정 평가셋과 제한된 실제 트래픽에서 비교한다.
서빙의 완료 조건은 서버가 켜졌다는 사실이 아니다. 예상 동시성에서 품질, 응답 시간, 메모리, 실패율이 함께 목표를 만족하는지 확인해야 한다. 모델 버전과 요청 설정을 로그에서 추적할 수 있게 기록하면 회귀가 생겼을 때 원인을 찾기 쉽다.
요청 한 건을 해석하기
위의 curl이 성공하면 응답의 choices[0].message.content에 생성된 텍스트가 들어가고, usage에 입력·출력 토큰 수가 기록될 수 있다. 다음 형태는 응답 구조 예시이며 실행 결과가 아니다.
{
"choices": [{"message": {"role": "assistant", "content": "배송 지연에 대한 문의입니다."}}],
"usage": {"prompt_tokens": 28, "completion_tokens": 12, "total_tokens": 40}
}
입력 토큰이 예상보다 많다면 애플리케이션이 붙인 시스템 지시문, 대화 이력, RAG 검색 결과까지 확인한다. 출력이 max_tokens에 걸려 중간에 끊기는 경우는 finish_reason과 실제 출력 길이를 함께 본다. max_tokens만 크게 늘리면 긴 답변이 늘어 비용과 대기 시간도 증가한다. 이 응답 형식은 vLLM의 OpenAI 호환 서버 문서에서 확인할 수 있다.
한 명일 때 빠른 모델이 동시에 들어와도 빠를까?
동시 요청을 처리할 때 서버는 입력을 읽는 작업과 출력을 생성하는 작업을 배치로 묶을 수 있다. 사용자의 질문이 짧아도 앞선 사용자가 매우 긴 답변을 생성 중이면 대기할 수 있다. 그래서 테스트는 동시성 1, 동시성 4, 동시성 16처럼 실제 예상 부하를 포함해야 한다. 각 단계에서 요청 성공률, p50·p95 TTFT, p95 완료 시간, 초당 출력 토큰, GPU 메모리를 같이 기록한다.
예를 들어 동시성 1에서 p95 TTFT가 0.2초이고 동시성 16에서 4초라면, 토큰 생성 속도만 보고 좋은 서버라고 평가할 수 없다. 그 시간 중 서버의 대기열이 3초를 차지하는지, 긴 프롬프트의 프리필이 3초를 차지하는지 구분한다. vLLM의 /metrics에는 대기 요청 수, 큐 시간, TTFT, KV 캐시 사용률 등이 포함된다. 공식 메트릭 목록의 이름은 설치 버전에 맞춰 확인한다.
curl -s http://127.0.0.1:8000/metrics | rg 'vllm:(num_requests_waiting|kv_cache_usage_perc|time_to_first_token_seconds)'
이 명령은 메트릭이 노출되는지 확인하는 예시다. 실제 분석에는 시간에 따른 변화와 히스토그램의 분위수를 관측 시스템에서 집계한다. 메트릭 원문에서 한 줄을 읽은 값만으로 p95를 계산할 수 없다.
배포 시 모델 서버 앞에 둘 경계
로컬 검증에서는 127.0.0.1에 직접 요청하지만 운영에서는 모델 서버 앞에 API 계층을 둔다. 사용자 인증, 요청 본문의 최대 크기, 허용된 모델 이름, 최대 입력·출력 토큰 수, 동시 요청 수와 요청 제한을 여기서 확인한다. 모델은 자신에게 전달된 개인정보를 출력에 되풀이할 수 있으므로 전체 요청·응답을 기본 로그로 남기는 것도 피한다. 감사가 필요한 경우 요청 ID, 모델 버전, 토큰 수, 실패 코드처럼 필요한 필드만 기록하고 민감 내용의 보관 정책을 따로 정한다.
새 모델 또는 양자화 형식을 배포할 때는 같은 질문 집합을 새 서버와 이전 서버에 보내고 품질 회귀와 p95를 함께 비교한다. 문제가 생기면 모델 파일만이 아니라 토크나이저·채팅 템플릿·서빙 옵션을 묶어 이전 버전으로 돌린다. 그래야 “같은 모델인데 답이 달라졌다”는 문제를 재현할 수 있다.