부산대학교 AI 융합교육원에서 학생연구원으로 근무하면서 부산대학교 코딩 실습 플랫폼 '코드플레이스' 개발을 맡게 되었다.
코드플레이스는 백준과 비슷한 온라인 저지 시스템이나, 단순 문제 제공, 채점 기능 뿐만 아니라, 실제 부산대학교 교양, 전공 기초 강의의 중간고사, 기말고사, 과제 등과 각종 교내 코딩 대회에 널리 사용되고 있다. (실 사용자 ~800명)
최근 이 코드플레이스에 학생들이 알고리즘 문제를 풀다 막히면 AI 기반의 힌트를 제공받을 수 있는 기능인 AI 조교 기능 추가를 기획하게 되었다. 프로덕션 서버에 이미 RTX 5090과 4070이 달려있어 자원은 충분했고, 외부 API 호출 시에 비용 처리나 예산 등의 복잡한 문제로 인해 로컬 LLM 서버를 구축해서 운영하는 방향으로 기획하게 되었다.
다만 현재 서버 구조는 하나의 k3s 환경에 stage, dev, prod 서버가 모두 떠 있는 상황이라 로컬 LLM 배포 자체가 쉽지는 않았다. 처음에는 Ollama를 사용해서 간단히 띄워보았고, 사용하다 보니 프로덕션 환경에서의 Ollama 사용은 한계가 명확해 vLLM으로 전환하기로 결정하게 되었다.
이 글에서는 Ollama의 한계와 vLLM으로 전환한 이유에 대해서 작성해보았다.
로컬 LLM 서빙 관련 요구사항
- 동시 사용자 50명 처리: 대회, 수업 상황에서 최대 50명의 학생이 동시에 힌트를 요청할 수 있다고 가정했다. 이 정도 동시 요청은 안정적으로 처리할 수 있어야 했다.
- 응답 시간 10초 이내: 학생들이 힌트를 기다리는 동안 너무 오래 걸리지 않도록, 평균 응답 시간은 10초 이내로 유지하는 것을 목표로 했다.
- 응답 성능: 학생들에게 도움이 될 만한 품질의 힌트를 제공할 수 있어야 했다. 모델의 적절한 응답 품질을 유지하는 것도 중요했다.
- 운영 안정성: OOM이나 서버 다운 같은 문제가 발생하지 않도록 안정적으로 운영할 수 있어야 했다.
기본 테스트 환경
- CPU: INTEL(R) XEON(R) GOLD 6526Y @ 2.80GHz 16C 32T x 2
- RAM: 128GB
- GPU: Geforce RTX 5090 32GB
- OS: Ubuntu 22.04.5 LTS
- 모델: Qwen2.5-Coder-7B-Instruct
Qwen2.5-Coder-7B-Instruct 모델을 선정한 이유는 파라미터 대비 성능이 뛰어난 편이었고, 구현하고자 하는 AI 조교 기능에 가장 적합하다고 판단했다.
당시 Qwen3.5-9B 모델도 고려했었으나, 테스트 당시에는 출시된 지 얼마 지나지 않아 테스트 대상으로는 적합하지 않다고 판단했다.
첫 번째 선택지: Ollama

Ollama Version: 0.17.7
첫 번째 선택지는 가장 빠르게 로컬 LLM을 서빙할 수 있는 Ollama였다. 팀원의 추천으로 선택하게 되었고, 라마가 귀여운 영향도 있었던 것 같다.
별도의 복잡한 설정 없이 모델을 다운로드하고 바로 API 형태로 사용할 수 있었고, 초기 기능 검증 단계에서는 매우 편리했다. 실제로 간단한 요청을 보내고 응답을 받아보는 수준에서는 큰 문제 없이 동작했다.
하지만 동시 요청이 발생하기 시작하면서부터 문제가 발생했다. Ollama는 단일 요청 처리에는 잘 작동했지만, 동시 요청이 몰리면 즉시 응답 시간이 급격히 늘어났고, 심지어 OOM(VRAM)이 발생하는 경우도 있었다.
Ollama 동시 요청 테스트 결과
OLLAMA_NUM_PARALLEL을 바꿔가면서 테스트하였고, Locust로 부하 테스트를 진행하였다.
'모델이 동시에 몇 개의 요청을 병렬로 처리할지'를 결정하는 옵션
동일 프롬프트로 단순 동시 요청만 테스트하였다. 실험 결과는 아래와 같다.
OLLAMA_NUM_PARALLEL = 2
| 동시 유저 수 | GPU 사용률 | 출력 완료 시간 | VRAM 사용 | 결과 요약 |
|---|---|---|---|---|
| 2 | ~98% | ~7초 | 21.3 GB | 안정적 |
| 5 | ~91% | ~40초 | 21.4 GB | Latency 증가, Queue 발생 |
| 10 | ~88% | ~80초 | 21.3 GB | 성능 저하 및 일부 500 에러 발생 |



2-3명일때는 안정적으로 처리하는 모습을 보였지만, 위와 같이 5명부터는 응답 시간이 급격히 증가했고, 동시에 2-3명이라면 최대 50명은 수용해야하는 프로덕션 환경에 사용할 수 없겠다는 판단을 했다.
OLLAMA_NUM_PARALLEL = 3으로 조절해보았으나 크게 개선되지 않았고, 오히려 OOM이 발생하거나 GPU를 효율적으로 사용하지 못하는 경우가 많았다. 역시 귀여움에 속아 넘어가서는 안 된다는 교훈을 얻었다.
두 번째 선택지: vLLM

vLLM Version: 0.18.1
사실 처음부터 Ollama의 구조 자체가 애초에 서버용으로 설계된 것이 아니므로 여러 사용자에게 LLM 서비스를 동시에 서빙하는 것에 최적화되어 있지 않을 것이라고 생각은 했었다. 실험 결과도 그 직감을 뒷받침했기에 바로 대안을 찾게 되었다.
vLLM, SGLang 등 여러 대안이 있었지만, vLLM이 가장 우수하다는 평이 많았고, 실제 기업에서 사용하는 사례도 꽤 보였기에 눈여겨보았다. 특히 동시 요청 처리, continuous batching, KV cache 관리, prefix caching 등 다양한 최적화 기술이 요구사항을 충족시키는데 도움이 될 것이라 판단했다.
Triton의 경우에는 단일 LLM 모델을 서빙할 것이기에 크게 적용할 필요가 없다고 판단했다.
vLLM 동시 요청 테스트 결과
vLLM도 Ollama와 동일하게 Locust로 부하 테스트를 진행하였다.
Ollama 테스트와 비슷한 길이의 프롬프트로 단순 동시 요청을 보냈고, Prefix Cache Hit Rate의 정확한 측정을 위해 프롬프트 내용을 일부 겹치게 구성, 세부 내용은 다르게 해서 테스트하였다.
테스트 시 사용했던 실행 옵션은 다음과 같다. vLLM 문서를 참고해서 적절하게 조정해보았다.
vllm serve Qwen/Qwen2.5-Coder-7B-Instruct \
--port 9090 \
--dtype auto \
--gpu-memory-utilization 0.9 \
--max-model-len 4096 \
--kv-cache-dtype fp8 \
--enable-prefix-caching \
--max-num-seqs 40
| 항목 | 12명 테스트 | 30명 테스트 | 50명 테스트 |
|---|---|---|---|
| 평균 응답 종료 시간 | 3초 이내 유지 (2.7초) | 2.891초 | 3.400초 |
| 성공/실패 | 실패 0개 | 성공 1,817건 / 실패 0건 | 성공 2,724건 / 실패 0 |
| Prefix Cache Hit Rate | 약 95.45% | 약 95.56% | 약 95.5% |
| VRAM 사용량 | 약 31.1GB | 약 31.1GB | 약 31.1GB |

프롬프트 길이, 내용에 따라 캐시 히트율이라던지 응답 시간 등 결과가 조금 달라질 순 있겠지만 12명, 30명, 50명 테스트 모두 평균 응답 시간이 3~4초 이내로 유지되었고, 실패한 요청이 단 한 건도 없었다는 점에서 vLLM이 Ollama에 비해 훨씬 안정적이고 효율적으로 동시 요청을 처리할 수 있다는 것을 확인할 수 있었다.
즉, vLLM을 사용해서 서빙한다면 처음에 전제했던 요구사항을 모두 충족할 수 있을 것으로 판단했고, vLLM으로 전환을 결정했다.
결론
테스트 서버에 vLLM을 통해 로컬 LLM 서빙을 구축하는데에 성공했고 코드플레이스 백엔드 서버와 연결해 AI 조교 기능을 구현하였을 때도 동일하게 안정적인 모습을 보여주었다. 처음 정의했던 요구사항도 모두 만족하는 모습을 보였다.
Ollama와 vLLM의 비교 과정을 통해 vLLM의 캐싱이나 최적화 기술에 대해 좀 더 깊이 이해할 수 있었다. 다만, 구현 전 설계 단계에서부터 Ollama의 사양과 다른 후보들을 잘 비교했다면 전환 비용 없이 좀 더 빠르게 구현할 수 있었을 것 같다. 기술을 선정할 때는 명확한 근거를 가지고 선택하고 ADR로 기록하는 습관을 가져야겠다는 교훈도 얻었다.
vLLM을 서버 Kubernetes(k3s) 환경에 맞게 배포하는 과정도 쉽지 않았는데, 해당 과정에서 겪었던 트러블슈팅 내용은 k3s에서 vLLM GPU 워크로드 실행하기 글에 정리해두었다.