KV Cache
LLM은 답변을 한 번에 쓰지 않는다. 토큰을 하나 만들고, 그 토큰을 이어 붙인 뒤, 다시 다음 토큰을 만든다. KV cache는 이 반복 과정에서 앞에서 이미 계산한 내용을 다시 계산하지 않도록 저장해두는 메모리다.
여기서 토큰은 모델이 읽고 쓰는 단어 조각이라고 보면 된다. “안녕하세요”가 한 덩어리로 처리될 수도 있고, 여러 조각으로 나뉘어 처리될 수도 있다.
왜 필요한가
ChatGPT에 긴 질문을 던졌다고 해보자. 모델은 첫 단어를 만들 때 질문 전체를 읽는다. 그다음 두 번째 단어를 만들 때도 지금까지의 문맥을 봐야 한다. 세 번째 단어를 만들 때도 마찬가지다.
문제는 여기서 생긴다.
100번째 토큰을 만들 때 1~99번째 토큰을 다시 계산하고, 101번째 토큰을 만들 때 1~100번째 토큰을 다시 계산하면 같은 일을 계속 반복하게 된다.
KV cache는 이 반복을 줄인다. 이전 토큰에서 이미 계산한 중간 결과를 GPU 메모리에 저장해두고, 다음 토큰을 만들 때 그대로 꺼내 쓴다. 새로 들어온 토큰에 대해서만 계산하면 된다.
flowchart LR
subgraph Before["토큰 5개 생성 후"]
C1["KV₁ | KV₂ | KV₃ | KV₄ | KV₅"]
end
subgraph After["6번째 토큰 생성 시"]
C2["KV₁ | KV₂ | KV₃ | KV₄ | KV₅ | KV₆"]
N6["6번째 토큰만 새로 처리"]
end
Before --> After
6번째 토큰을 만들 때 앞의 K₁~K₅, V₁~V₅는 cache에서 읽는다. 새 토큰의 K₆, V₆만 계산해서 cache에 붙인다.
한마디로 말하면, 모델이 앞부분을 다시 읽지 않도록 책갈피와 메모를 남겨두는 것이다.
무엇을 저장하는가
KV cache의 K와 V는 attention에서 나오는 중간 결과다. attention은 모델이 “지금 다음 말을 만들 때 앞 문장 중 어디를 참고해야 하지?”를 고르는 과정이다.
수식까지 몰라도 된다. 대략 이렇게 이해하면 충분하다.
- K는 “어디를 참고해야 하는가”를 찾기 위한 단서다.
- V는 실제로 가져올 내용에 가깝다.
새 토큰을 만들 때 모델은 지금까지 나온 모든 토큰을 다시 훑어본다. 이때 이전 토큰들의 K, V가 이미 cache에 있으면, 그 부분은 다시 만들 필요가 없다.
이름은 cache지만 CPU의 L1/L2 cache와는 다르다. CPU cache는 하드웨어가 자동으로 관리한다. KV cache는 LLM 서버 프로그램이 요청 단위로 만들고, GPU 메모리에 보관하고, 답변이 끝나면 지운다.
왜 메모리를 많이 쓰는가
KV cache는 답변을 만드는 동안 계속 자란다. 프롬프트가 길수록, 답변이 길수록, 동시에 처리하는 사용자가 많을수록 더 커진다.
모델을 GPU에 올릴 때 필요한 메모리는 모델 파일만이 아니다. 대화가 진행되면서 쌓이는 KV cache도 GPU 메모리를 차지한다.
짧은 질문 하나만 처리할 때는 크게 눈에 띄지 않는다. 그런데 긴 문서를 넣거나, 여러 사용자의 요청을 동시에 받으면 이야기가 달라진다. 이때는 모델 자체보다 KV cache가 더 부담이 될 수 있다.
왜 속도가 빨라지는가
LLM이 느린 이유 중 하나는 같은 문맥을 반복해서 처리하기 때문이다. KV cache를 쓰면 이미 처리한 앞부분은 다시 계산하지 않는다.
특히 첫 응답 이후에 토큰이 줄줄 나오는 단계에서 효과가 크다. 모델은 매번 전체 문장을 처음부터 다시 계산하는 대신, 저장해둔 K, V를 읽고 새 토큰 하나에 해당하는 부분만 계산한다.
물론 공짜는 아니다. 계산을 줄이는 대신 GPU 메모리를 더 쓴다. KV cache는 속도와 메모리 사이의 교환이다.
서빙 시스템에서는 어떻게 다루는가
실제 서비스에서는 요청마다 길이가 다르다. 어떤 사용자는 한 줄만 묻고, 어떤 사용자는 긴 문서를 붙여 넣는다. 답변 길이도 미리 정확히 알기 어렵다.
그래서 vLLM 같은 LLM 서버 프로그램은 KV cache를 작은 블록으로 나눠 관리한다. 필요한 만큼만 할당하고, 답변이 끝나면 돌려준다. 이런 아이디어가 PagedAttention이다.
또 같은 앞부분을 가진 요청은 cache를 공유할 수 있다. 예를 들어 모든 요청 앞에 같은 시스템 프롬프트가 붙는 서비스라면, 그 부분을 매번 새로 계산하지 않아도 된다. SGLang의 RadixAttention이나 vLLM의 automatic prefix caching은 이런 prefix 재사용을 더 잘 하려는 기법이다.
세부 구현은 몰라도 된다. 핵심은 하나다. KV cache가 커지면 GPU 메모리가 빨리 찬다. 그래서 좋은 서빙 시스템은 cache를 잘게 나누고, 공유할 수 있는 부분은 공유하고, 필요 없는 부분은 빠르게 비운다.
정리
KV cache는 답변 속도를 높이지만 GPU 메모리를 많이 쓴다. 모델이 커지고 대화가 길어지고 동시 사용자가 늘수록, KV cache가 메모리 예산에서 큰 비중을 차지한다.
그래서 KV cache는 단순한 내부 구현 디테일이 아니다. LLM이 빠르게 답변하도록 만드는 장치이면서, 동시에 긴 문맥과 많은 동시 사용자를 어렵게 만드는 메모리 비용이다.