📌 Notice
LLM Serving and Optimization Study (=LLMSO)
직접 실습을 통해 LLM 서비스 제공 및 최적화에 대한 지식에 대한 학습 및 실습 내용을 정리한 블로그입니다.
CloudNet@에서 스터디를 진행하고 있습니다.
EKS 관련 이전 스터디 내용은 아래 링크를 통해 확인할 수 있습니다.
이번 글에서는 첫 번째 주제인 CH1. Introduction to Model Serving and Optimization을 정리합니다.
아직 LLM을 깊게 다뤄보지 않은 입장에서, “모델을 학습하면 바로 서비스할 수 있을까?”라는 질문에서 출발해 모델 서빙의 기본 개념을 이해하고 정리합니다.
LLM을 학습하거나 이미 만들어진 모델을 내려받는 것과, 사용자가 실제로 호출할 수 있는 서비스로 제공하는 것은 서로 다른 문제입니다.
모델 서빙은 학습이 완료된 모델을 실행 환경에 올리고, 사용자 요청을 받아 추론을 수행한 뒤 결과를 안정적으로 반환하는 과정을 의미합니다. 이때 단순히 정답을 반환하는 것만으로는 충분하지 않습니다.
실제 서비스에서는 다음 질문에 답할 수 있어야 합니다.
- 요청이 들어왔을 때 얼마나 빠르게 응답할 수 있는가?
- 동시에 얼마나 많은 요청을 처리할 수 있는가?
- GPU 메모리를 얼마나 사용하는가?
- 서비스 운영 비용은 어느 정도인가?
- 트래픽이 증가했을 때 안정적으로 확장할 수 있는가?
이번 글에서는 모델의 구성 요소와 생명주기, 서빙의 기본 흐름, 최적화가 필요한 이유, 대표적인 서빙 방식, 그리고 vLLM을 살펴봅니다.
💡 핵심
모델을 학습했다는 것과 모델을 서비스한다는 것은 서로 다른 문제다.
학습 데이터
↓
모델 학습
↓
모델 산출물 패키징
↓
서빙 엔진에 로딩
↓
API 요청 처리
↓
모니터링 및 업데이트
처음에는 모델 파일만 있으면 곧바로 서비스를 만들 수 있다고 생각했습니다. 하지만 실제로 모델을 실행하려면 가중치 외에도 모델 구조를 해석하고 계산을 수행할 실행 환경이 필요합니다.
모델을 서비스로 제공한다는 것은 다음 과정을 포함합니다.
모델 준비 → 실행 환경 구성 → 요청 수신 → 추론 수행 → 결과 반환 → 운영 및 모니터링
즉, 모델 서빙은 모델을 한 번 실행해보는 것보다 더 넓은 범위의 시스템 문제입니다.
학습(training)은 데이터를 이용해 모델의 파라미터를 업데이트하는 과정이고, 추론(inference)은 학습이 끝난 모델에 입력을 전달해 결과를 생성하는 과정입니다. 서빙은 이 추론을 사용자의 요청에 맞춰 반복적이고 안정적으로 수행할 수 있도록 만드는 일이라고 이해했습니다.
💡 핵심
모델은 가중치 파일 하나가 아니라, 데이터·구조·실행 코드가 함께 있어야 실행 가능한 소프트웨어가 된다.

my-llm/
├── config.json
├── generation_config.json
├── model.safetensors
├── tokenizer.json
├── tokenizer_config.json
└── special_tokens_map.json
from transformers import AutoTokenizer, AutoModelForCausalLM
model_id = '<model-id>'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map='auto'
)
model.eval()
inputs = tokenizer('모델 서빙이란?', return_tensors='pt')
outputs = model.generate(**inputs, max_new_tokens=32)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
📌 이 코드에서 중요한 점
가중치만 로드하는 것이 아니라 tokenizer, 모델 구조, 실행 코드가 함께 동작해야 한다.
CH1에서는 모델을 다음 세 가지 구성 요소의 결합으로 설명합니다.
학습을 통해 만들어진 가중치와 파라미터입니다. 모델의 지식과 동작 특성이 저장되어 있지만, 이 데이터만으로는 모델을 실행할 수 없습니다.
가중치를 어떤 구조와 계산 순서로 사용할지 정의합니다. LLM에서는 Transformer 구조가 핵심적인 역할을 합니다.
입력을 받아 토큰화하고, 모델 계산을 실행하고, 결과를 다시 사람이 읽을 수 있는 형태로 변환하는 코드입니다. 실제 서빙에서는 이 실행 코드와 프레임워크의 선택이 성능과 운영 방식에 영향을 줍니다.
따라서 모델을 배포할 때는 가중치 파일만 복사하는 것이 아니라, 모델 구조와 실행 코드, 그리고 이를 동작시킬 라이브러리와 하드웨어까지 함께 고려해야 합니다.
💡 학습 단계의 관심사
정확도, loss, 학습 시간, 데이터 품질
💡 서빙 단계의 관심사
latency, throughput, GPU memory, 비용, 안정성
model:
name: <model-name>
version: v1
framework: transformers
precision: fp16
lifecycle:
trained: true
evaluated: true
packaged: true
deployed: false
monitored: false

모델은 학습 단계에서 끝나지 않고 서비스 운영 단계까지 이어지는 생명주기를 가집니다.
데이터 준비
↓
모델 학습 또는 파인튜닝
↓
모델 저장 및 검증
↓
서빙 엔진에 모델 로딩
↓
추론 API 제공
↓
성능·오류·자원 사용량 모니터링
↓
모델 또는 실행 환경 업데이트
이 흐름에서 모델 서빙은 모델 로딩 이후부터 사용자 요청에 응답하는 구간에 해당하지만, 실제 운영에서는 모니터링과 업데이트까지 함께 고려해야 합니다.
예를 들어 모델의 응답 품질이 좋아도 GPU 메모리가 부족하거나, 요청이 몰렸을 때 응답 지연시간이 급격히 증가한다면 서비스로 사용하기 어렵습니다.
사용자의 문장은 어떤 과정을 거쳐 GPU에서 처리되고, 다시 응답 문장으로 변환될까?
POST /v1/chat/completions
Content-Type: application/json
{
"model": "<model-name>",
"messages": [
{"role": "user", "content": "LLM 서빙이란 무엇인가?"}
],
"max_tokens": 64
}
문자열 입력
→ Tokenizer
→ Token IDs
→ 모델 추론
→ 생성 Token IDs
→ 문자열 응답
LLM 서빙은 다음과 같은 흐름으로 동작합니다.
사용자 요청
↓
입력 전처리 및 토큰화
↓
모델 추론
↓
토큰 생성 및 디코딩
↓
응답 후처리
↓
사용자에게 응답 반환
각 단계가 모두 중요하지만, LLM에서는 특히 모델 추론 단계가 많은 연산과 메모리를 사용합니다.
실제 서빙 엔진은 여러 요청을 효율적으로 처리하기 위해 요청을 모아 실행하거나, 실행 순서를 조정하거나, GPU 메모리를 재사용합니다. 따라서 같은 모델이라도 어떤 서빙 엔진과 설정을 사용하는지에 따라 성능이 달라질 수 있습니다.
Latency = 하나의 요청이 완료될 때까지 걸린 시간
Throughput = 일정 시간 동안 처리한 요청 또는 토큰의 양
TTFT = 요청 후 첫 토큰이 생성될 때까지 걸린 시간
GPU Memory = 모델과 요청 처리에 사용된 GPU 메모리

⚠️ 주의
빠르다는 결론만으로는 부족하다. 어떤 GPU, 모델, 입력 길이, 출력 길이, 동시 요청 수에서 측정했는지 함께 기록해야 한다.
모델의 정확도가 높더라도 실제 서비스에서 다음과 같은 문제가 발생할 수 있습니다.
서빙 성능을 볼 때는 하나의 숫자만 확인해서는 안 됩니다.
| 지표 | 의미 |
|---|---|
| Latency | 하나의 요청이 응답을 받기까지 걸린 시간 |
| Throughput | 일정 시간 동안 처리할 수 있는 요청 또는 토큰의 양 |
| GPU Memory Usage | 모델과 요청 처리에 사용되는 GPU 메모리 |
| Cost | 같은 품질과 트래픽을 처리하기 위해 필요한 운영 비용 |
응답을 빠르게 만드는 것이 항상 최선은 아닙니다. 단일 사용자에게 가장 빠른 시스템과, 많은 사용자의 요청을 효율적으로 처리하는 시스템은 서로 다른 설정을 가질 수 있습니다.
결국 모델, 트래픽 패턴, GPU 자원, 응답 목표에 맞춰 latency와 throughput, 메모리와 비용 사이의 균형을 찾아야 합니다.
개인정보와 오프라인 실행이 중요한가?
├─ Yes → On-device
└─ No
├─ 모델 하나의 성능이 중요한가? → Single-model
├─ 모델 수가 많고 트래픽이 낮은가? → Multi-model
└─ 여러 서비스와 운영 자동화가 필요한가? → Serving platform
💡 정답은 하나가 아니다
모델 크기, 트래픽 규모, 보안 요구사항, 하드웨어와 비용을 함께 보고 서빙 방식을 선택해야 한다.
CH1에서는 모델을 제공하는 방식을 네 가지로 구분합니다.
서버가 아니라 사용자의 PC, 스마트폰, 임베디드 장치 등에서 모델을 직접 실행하는 방식입니다.
장점
고려사항
하나의 모델 또는 하나의 모델 버전을 전용 서비스로 배포하는 방식입니다.
장점
고려사항
하나의 실행 환경이나 컨테이너에서 여러 모델이 자원을 공유하는 방식입니다.
장점
고려사항
모델 실행뿐 아니라 배포, 리소스 그룹, 오케스트레이션, 워크플로우와 같은 운영 기능까지 포함하는 플랫폼 형태입니다.
장점
고려사항
어떤 방식이 항상 정답인 것은 아닙니다. 모델 크기, 트래픽 규모, 보안 요구사항, 하드웨어와 비용을 함께 보고 선택해야 합니다.
“The high-throughput and memory-efficient inference and serving engine for LLMs.”
— vLLM 공식 소개 문구
# 기본 Transformers 실행 예시
from transformers import pipeline
generator = pipeline(
'text-generation',
model='<model-id>',
device_map='auto'
)
result = generator('모델 서빙이란?', max_new_tokens=64)
# vLLM 실행 예시
vllm serve <model-id> \
--dtype auto \
--max-model-len 2048
📌 이 글에서의 vLLM 이해
vLLM은 새로운 모델이 아니라, LLM을 효율적으로 실행하고 서비스하기 위한 서빙 엔진이다.
CH1에서는 vLLM을 다음과 같이 소개합니다.
The High-Throughput and Memory-Efficient Inference and Serving Engine for LLMs
즉, vLLM은 LLM 추론과 서빙을 더 높은 처리량과 메모리 효율을 목표로 수행하기 위한 엔진입니다.
같은 Hugging Face 모델을 사용하더라도 일반적인 실행 방식과 전용 서빙 엔진의 처리 방식은 다를 수 있습니다. 특히 여러 요청이 동시에 들어오는 상황에서는 요청을 어떻게 묶고, GPU 메모리를 어떻게 사용하며, 생성 작업을 어떤 순서로 스케줄링하는지가 성능에 영향을 줍니다.
이번 CH1에서는 vLLM의 내부 구현을 모두 이해하기보다는 다음 내용을 먼저 확인하는 것을 목표로 합니다.
이번 CH1에서 정리한 핵심은 다음과 같습니다.
CH1을 통해 LLM 서비스는 단순히 모델을 실행하는 문제가 아니라, 모델·소프트웨어·GPU 자원·트래픽·운영 비용을 함께 다루는 시스템 문제라는 점을 이해하게 되었습니다.
다음에는 CH2에서 LLM 서빙 시스템의 구성 요소와 실제 최적화 기법을 더 자세히 살펴보겠습니다.