AI Engineering/이론

vLLM은 어떻게 AI 엔지니어링에서 필수불가결한 선택지가 되었을까?

마늘냄새폴폴 2026. 1. 8. 22:27

안녕하세요! 요즘 일이 좀 널널해져서 마지막으로 글 쓴지 1달만에 다시 포스팅을 하기위해 오랜만에 개인 컴퓨터 앞에 앉았습니다. 오늘 쓸 내용은 일하면서 쓸 일은 없지만 vLLM에 대해 관심이 생겨 공부한 내용을 정리하고자 합니다. 

 

KV Cache란?

vLLM 얘기로 시작해서 KV Cache가 뜬금없이 왜 나오나싶지만 이 두개는 큰 연관이 있기 때문에 서론으로 집어넣어봤습니다. KV Cache의 본질로 들어가면 Attention 연산이라는게 나옵니다. 저도 깊이있게 이해한건 아니지만(무슨 수식이 한가득..) Attention 연산에는 세개의 매개변수가 필요합니다. 

 

  • Q (Query) : 현재 내가 찾고자 하는 정보에 대한 내용 (질문)
  • K (Key) : 현재 내가 가지고 있는 정보 (인덱스)
  • V (Value) : 현재 내가 가지고 있는 실제 값 (내용)

이 내용은 데이터베이스 쿼리와 모양새는 비슷하게 생겼습니다. 이렇게 Attention(Q, K, V) 를 이용해서 행렬 곱을 진행하게 되는데 여기서 Q와 K를 곱해 유사도 (Attention Score)를 구하고, 그 비중에 따라 V를 가중 합산하여 최종 결과를 냅니다. 

 

여기까지는 대충 이해하고 나중에 깊이있게 공부하고자 넘어갔습니다. 

 

이건 KV Cache를 데이터의 관점에서 본 것이고 실제 백엔드 개발자 관점에서 KV Cache를 바라보면

 

질문을 할때마다 뭔가 메모리를 미친놈처럼 많이 잡아먹고있다... 이게 뭐지..

 

정도의 느낌인데 사실 이 KV Cache는 Transformer 알고리즘에서 중요하게 사용되는 영역입니다. 

 

"나는 오늘 점심을 먹는다" 라는 문장을 생성하기 위해 Transformer는 다음과 같은 단계를 거칩니다. 

 

  1. 나(Q) -> 는(Next Token)
  2. 나는(Q) -> 오늘(Next Token)
  3. 나는 오늘(Q) -> 점심(Next Token)
  4. 나는 오늘 점심(Q) -> 을(Next Token)
  5. 나는 오늘 점심을(Q) -> 먹는다(Next Token)

뭔가 계속 등장하는 놈들이 생기고 이런 정적인 데이터의 반복은 우리를 캐싱으로 이끕니다. 우리가 흔히 웹개발에서도 자주 등장하는 정적인 데이터들은 캐싱을 도입해서 응답속도를 빠르게 해주는 것 처럼 KV Cache도 이름에 걸맞게 캐시의 역할을 톡톡히 하고 있다는 것입니다. 

 

하지만 캐싱이 마냥 좋은 것은 아니죠. 웹개발에서는 캐시 히트율을 높이는 것이 중점적인 관심사지만 결국 캐시를 도입하면서 생기는 운영적인 리스크와 비용적인 리스크를 지니게 된다는 것이 캐싱이 마냥 좋은 솔루션이 아니라는 것을 깨닫게 해주죠. 

 

결국 이 또한 개발자들이 항상 겪는 Trade-Off 문제에 빠질 수 밖에 없는 것이죠. 연산 속도와 메모리라는 두가지 중요 요건에서 한가지는 무조건 포기하게 되는 상황입니다. 

 

결국 AI 엔지니어들은 KV Cache를 적극적으로 도입하게 되었습니다. 왜냐하면 고객은 느린걸 참지 못하거든요. 지금의 Chat AI들은 속도가 어마어마하게 빠르지만 초기 AI들은 엄청나게 속도가 느려서 써먹기 힘든 경우도 있었으니까요. (GPT-4 시절이 생각나네요..)

 

KV Cache가 메모리를 너무 많이 차지하는데..?

근데 요즘은 도가 좀 지나친 것 같더군요. 모델보다 KV Cache가 더 문제인 경우도 생깁니다. 실제로 성능은 좋지 않아도 입력 토큰을 많이 필요로하는 서비스의 경우 입력 토큰을 적게는 30만개에서 많게는 50만개 100만개도 사용하곤 하는데 실제 모델 크기는 7B수준이어서 8비트 양자화를 거치면 8기가 정도인데 토큰 30만개가 벌써 15기가씩 먹어치우니까요. 

 

30만개도 이정도인데 100만개를 처리하려면 거의 50기가에 가까운 KV Cache만을 위한 메모리가 필요해집니다. 

 

예를 들어서 70B짜리 LLM이 있다고 가정해봅시다. 흔히 사용하는 FP16으로 계산하면 모델 크기는 140GB입니다. 또 요즘 LLM 모델들은 엄청나게 많은 Input 토큰을 넣을 수 있죠? 100만토큰이라고 가정해봅시다. 

 

한사람이 100만토큰을 넣으면 이를 메모리로 변환했을 때 약 200GB정도 됩니다. 

 

오잉? 아까는 50기가라면서요? 

 

그건 7B일 때 이야기이고 KV Cache의 크기는 컨텍스트의 길이와 배치 사이즈에도 비례하기 때문에 작은 모델과 큰 모델에서는 크기 차이가 있습니다. 

 

아무튼 모델 크기는 140GB인데 KV Cache가 200GB나 된다라.. 만약 100명의 사용자가 붙으면? 2TB가 필요하네요? 

 

이거 DRAM도 아니고 HDD의 저장공간도 아닙니다. VRAM이라는게 중요한거죠. 요즘 나오는 H200이 80GB의 VRAM을 제공하니 그럼 VRAM이 2TB필요하면 H200을 250대나 있어야되네요. 

 

근데 GPT 활성 이용자수가 수억명씩 되니까 실제 동시접속자는 수백만명에서 수천만명이 사용할겁니다. 그래서 많은 AI 거대 기업들이 데이터센터를 미친듯이 늘리고 있는 이유가 되겠죠. 

 

하지만, 이건 틀렸습니다

어마어마하긴 하지만 이런 문제를 눈뜨고 지켜볼 개발자들이 아니었습니다. 우리는 LLM 모델에 양자화를 적용했던 것 처럼 KV Cache에도 양자화를 적용하기로 마음먹었습니다. 그리고 이 생각이 든건 KV Cache의 문제가 심각하다는 사실을 일찍 깨닫고 얼마 있지않아 바로 개발이 완료되었죠. 

 

양자화에 대한 내용은 아래의 링크에 자세히 설명 되어있습니다! 한번 보고 오시는 것을 추천드립니다!

https://coding-review.tistory.com/607

 

양자화란 무엇인가

4개월만에 블로그 포스팅을 재개하게 되었네요. 이런저런 난관도 있었고 새로운 직장에 안착해서 자리잡아야 했고 새로운 프로젝트에 투입되어 AIOps를 위해 개발하느라 포스팅이 늦어졌습니다.

coding-review.tistory.com

 

KV Cache를 양자화하면서 거대 모델에서 양자화를 진행했던 것과 똑같은 장점과 단점이 생기기 시작했습니다. KV Cache 크기 자체는 줄일 수 있었지만 여러 정합성 문제나 성능 문제가 대두되곤 했죠. 

 

KV Cache의 크기를 4비트 양자화를 하면 기존 FP16에 비해 4배 줄일 수 있었습니다만 또 다른 문제가 생기기 시작했습니다. 

 

메모리 단편화 문제

만약 OS를 공부해서 가상메모리의 개념을 알고 계시거나, JVM 기반 개발을 하고 계시다면 GC 알고리즘 중 Mark Sweep Compact 알고리즘에 대해서 공부하셨다면 들어보셨을 "메모리 단편화 문제"가 새롭게 떠오르는 문제가 되었습니다. 

 

메모리 단편화 문제는 쉽게 말해서 메모리를 점유하고 있던 프로세스가 종료되는 경우 해당 부분이 휘발되면서 구멍이 뚫리는 상황을 의미합니다. 

 

가상 메모리(Paging)는 이 문제를 해결하기 위해 프로세스에는 메모리의 논리주소를 부여하고 실제 물리주소는 MMU (Memory Management Unit) 에게 위임해 논리 주소와 물리 주소를 재빠르게 슥삭 바꿔치기 할 수 있도록 개발되었습니다. 

 

가상메모리를 쉽게 풀어서 설명해보자면

 

□ □ □ □ □ □

 

메모리에 이렇게 여섯개의 공간이 있습니다. 이 때 A프로세스와 B프로세스가 각각 2와 1만큼의 메모리 공간을 달라고 요청합니다. 그럼 MMU는 A와 B에게 논리주소를 부여합니다. 

 

1 2 3 4 5 6

■ ■ ■  □ □

 

A에겐 1, 2번 메모리 공간을 부여하고 B에겐 3번 메모리 공간을 부여했습니다. 

 

그리고 A프로세스가 종료되어 메모리를 반환합니다. 

 

□ □ ■  □ □

 

이 다음에 C라는 프로세스가 4의 메모리를 달라고 요청합니다. 

 

원래대로라면 메모리는 순서대로 부여를 해야합니다. 실제 물리 주소를 곧바로 프로세스에게 부여했다면 포인터가 맨 마지막 주소를 가리키고 있을테니까요. 

 

그럼 지금 상황에서 크기 4의 메모리는 부여할 수 없습니다. 뒤에 메모리가 3밖에 남지 않았으니까요. 

 

그래서 MMU는 가상메모리를 할당해줍니다. 빈공간을 활용해서 C에게 4의 메모리를 부여합니다. 

 

1 2 3 4 5 6

■ ■ ■ ■ ■

 

이렇게 되면 B프로세스가 3번 메모리 주소를 가지고 C프로세스가 1, 2, 4, 5번 메모리 주소를 가지게 되었습니다. 

 

어.. 그럼 C프로세스는 중간에 한칸 건너 뛰는데요? 

 

그래서 가상 메모리라는 존재가 힘을 발휘하는 것입니다. 실제 C가 받은 메모리 주소는 1, 2, 4, 5지만 MMU가 이 작업을 슥삭 바꿔치기해서 1, 2, 3, 4로 보이게 만듭니다. 

 

C프로세스 입장에선 나한테 부여된 메모리 주소가 연속적이라고 인지하고 계속 동작하지만 실제 물리 주소는 떨어져 있습니다. 

 

이것이 가상 메모리의 개념이고 GC에서는 조금 다르게 Compaction 작업이 추가되어 단편화된 메모리를 왼쪽으로 한번에 몰아 넣어서 빈 공간을 억지로 만드는 "청소"에 가깝습니다. 그래서 STW가 생기는 것이구요. 아무튼 이번 포스팅은 GC와는 관계 없으니 넘어가도록 하겠습니다. 

 

vLLM의 등장

vLLM은 기존 CPU기반 프로세스들이 CPU 메모리 (DRAM)을 활용해도 OS가 가상 메모리로 알잘딱하게 메모리를 부여해주니 신경쓰지 않았던 것에서 착안하여 탄생했습니다. 

 

LLM은 VRAM을 DRAM처럼 사용하는 경향이 있으니 DRAM에도 적용했던 가상 메모리를 VRAM에도 적용하자는 컨셉이었죠. 

 

그리고 이는 Attention연산을 Paging처리한다 하여 PagedAttention이라는 개념으로 등장하였습니다. 이 도입은 생각보다 큰 파장을 불러일으켰습니다. 20TPS가 나오던 답변이 갑자기 50TPS가 찍히니 당황할 수 밖에 없었죠. 

 

처리량은 또 어떤가요, 갑자기 처리량이 두배 세배로 늘어나기 시작했습니다. 

 

개발자들은 이 현상을 잘 알고 있습니다. 처음 도커에 열광하던 개발자들이 도커의 엄청난 성능에 놀랐지만 결국 써보니 좋긴 좋은데 수백 수천개의 컨테이너를 관리하기가 힘들어졌다는 것을요. 

 

뒤이어 쿠버네티스가 등장했지만 어마어마한 운영 복잡도에 사람들이 쿠버네티스를 쉬이 도입하지 못하게 되는 계기가 되었습니다. 

 

이런 장점만 존재하는 기술이 존재할리 없어..! 뭔가 단점이 있을거야. 사이드 이펙트가 분명 어딘가에 있을거야..!

 

vLLM에도 부작용이 있었는데 그건 바로 가상메모리에서의 부작용에서 찾을 수 있었습니다. MMU는 논리 주소와 물리 주소를 연결하기 위한 Paging Table을 가지고 있는데 프로세스에 메모리 주소를 할당할 때마다 이 테이블을 조회해야하는 추가 연산이 필요했고 이는 오버헤드로 이어지게 되었습니다. 

 

하지만 이는 OS가 가지고 가야할 숙명같은 것이었죠. 오버헤드 때문에 가상 메모리를 포기할 수는 없었습니다. 때문에 가상 메모리는 분명 좋은 개념이었지만 오버헤드라는 사이드 이펙트를 남기고 우리와 같이 살아가고 있습니다. 

 

vLLM을 도입하니 가상메모리처럼 테이블 조회 연산이 필요했습니다. 근데 희한하게도 이 부작용이 크게 대두되지 않았습니다. 왜일까요? 

 

1. VRAM을 쓰는 것이 CPU가 아닌 GPU라는 것

CPU는 정수 계산에 특화되어 있고 병렬 연산에 쥐약입니다. CPU가 사용하는 DRAM에서 가상 메모리의 테이블 조회는 분명 눈에 띄는 오버헤드를 가지고 왔습니다. 

 

하지만 VRAM에 가상메모리 개념을 도입하니 문제가 크게 보이지 않았습니다. 분명 레이턴시가 존재했지만 VRAM을 사용하는게 GPU라는게 크게 다른 점이었죠. 

 

GPU는 소수점 계산에 능하고 병렬 연산에 최적화되어있습니다. 테이블을 조회할 때 GPU는 다음 연산에 필요한 준비 동작을 하거나 다른 연산을 하고 있으면 되기 때문에 실질적으로 느끼게 되는 레이턴시는 매우 작았던 것이죠. 

 

2. 레이턴시가 뭐가 중헌디

안그래도 병렬 연산에 특화된 GPU인데 거기다 VRAM에 가상메모리 개념을 도입하니 처리량이 어마어마어마하게 늘어났습니다. 

 

개발자들이 환장하는 두 단어 "레이턴시"와 "처리량"

 

vLLM을 도입하니 엄청난 처리량으로 같은 하드웨어 리소스로 두배 세배 많은 사용자를 받을 수 있었으니 엄청난 이득이 되었던 것이죠. 

 

거기다 레이턴시가 눈에 보이지도 않아서 개발자들은 너도나도 vLLM을 도입하게 된 계기가 되었습니다.

 

vLLM의 유일한 단점

NVIDA 그래픽카드만 지원을 한다는게 가장 마음아픈 단점입니다... 저는 요즘 맥으로 개발하고 있어서 gguf밖에 못쓰는데 NVIDA 그래픽카드가 없어서 vLLM을 못쓴다는게 제 천추의 한입니다 ㅜㅜ..

 

그래서 병렬로 실행하고싶은 연산이 있으면 LLama cpp서버를 띄워서 병렬 작업을 하고 있는데 성능이 영시원찮네요... 

 

프로젝트 들어가면 저도 H100이나 H200을 만져보는 기회가 있으면 좋겠습니다.. ㅜㅜ

 

 

 

마치며

vLLM은 오늘날 AI 엔지니어링에서 가장 핫한 LLM 서빙 도구가 되었습니다. 나중에 구구절절 쓸 수도 있는 포스팅 내용 중에 NVIDA의 생태계가 준 윈도우 운영체제에 맞먹는 현 상황에 대해 푸념하는 날이 있을 것 같습니다. 

 

좋다고 좋다고 말로만 들었지 실제로 써보고 체감한게 아니라서 vLLM에 대한 개인적인 평가를 글에 녹이지 못한 것이 조금 아쉬운 포스팅입니다. 혹시 모르죠 저희 회사에도 H200이 들어오게 될지 으흐흐

 

오늘 포스팅은 여기까지 적어보도록 하겠습니다. 오늘도 긴 글 읽어주셔서 감사합니다. 새해 복 많이 받으세요!

 

'AI Engineering > 이론' 카테고리의 다른 글

양자화란 무엇인가  (0) 2025.12.09