LLM 서빙의 숨은 병목, KVCache를 무기로 바꾸다
Moonshot AI의 Kimi 서비스를 지탱하는 Mooncake 플랫폼의 KVCache 중심 분리형 아키텍처를 분석한다. Prefill과 Decode를 분리하고 유휴 CPU·DRAM·SSD 자원을 캐시 풀로 전환해 처리량을 75% 이상 끌어올린 설계 철학과 vLLM·SGLang 생태계 확장을 추적한다.
DevInsight 편집팀 발행
AI 보조 초안과 편집 검수를 거쳐 발행했습니다.
LLM을 서빙한다는 건 결국 기억력을 서빙한다는 말과 다르지 않다. 사용자가 입력한 프롬프트, 이전 대화의 맥락, 시스템 지시문, 도구 호출 결과까지 — 모델이 매 토큰을 생성할 때마다 이 모든 컨텍스트를 다시 "읽어야" 한다. 그런데 실제로 GPU 위에서 일어나는 일을 들여다보면, 이 기억을 유지하는 비용이 컴퓨테이션 비용을 한참 뛰어넘는 순간이 찾아온다. 그 임계점에서 평범한 서빙 아키텍처는 한계를 드러내고, 정작 GPU는 데이터를 기다리며 놀고 있는 역설이 펼쳐진다.
128K 컨텍스트 하나가 40GB를 먹는다
트랜스포머의 추론 파이프라인은 크게 두 단계로 나뉜다. 모든 입력 토큰을 병렬로 처리하는 Prefill 단계, 그리고 이전 토큰들의 Key-Value를 캐시 삼아 한 토큰씩 자동회귀적으로 생성하는 Decode 단계다. 이때 Prefill에서 생성된 Key와 Value 행렬이 바로 KVCache이고, Decode는 매 스텝마다 이 캐시 전체를 읽으며 다음 토큰을 추론한다.
문제는 KVCache의 크기가 모델 레이어 수, 히든 디멘션, 입력 길이에 따라 선형적으로 — 실상은 모델이 클수록 초선형적으로 — 불어난다는 점이다. LLaMA3-70B에 128K 토큰 컨텍스트를 넣으면 KVCache만 약 40GB를 차지한다. H100 한 장의 HBM이 80GB인 시대에 말도 안 되는 수치는 아니지만, 여기에 동시 요청 수백 건이 곱해지면 얘기가 달라진다. KVCache만으로 GPU 메모리를 가득 채우고도 모자라서, 결국은 이 캐시를 CPU DRAM이나 SSD로 밀어내는 offloading이 불가피해진다.
그런데 그 "밀어내기"가 진짜 함정이다. GPU와 CPU 사이를 잇는 PCIe 대역폭은 고작 32~64 GB/s 수준이다. 40GB짜리 캐시 하나를 옮기려면 1초 가까이 걸린다. 이 시간 동안 GPU는 compute unit을 태우지 못하고 대기 상태로 빠진다. 기존 연구에 따르면 KVCache offloading이 들어가는 순간 GPU가 소비하는 전력은 정격 TDP의 28%까지 떨어진다. 비싼 GPU를 사놓고 30%도 못 쓰는 셈이다.
Prefill은 compute-bound, Decode는 memory-bound
더 근본적인 문제는 두 단계가 GPU를 쓰는 방식 자체가 정반대라는 데 있다. Prefill은 모든 입력 토큰을 한 번에 행렬 곱셈으로 처리하기 때문에 tensor core를 풀로 태우는 compute-bound 작업이다. 반면 Decode는 매 스텝 단 하나의 토큰을 위해 KVCache 전체와 모델 웨이트를 메모리에서 읽어오는 memory-bound 작업이다. GPU의 SM(Streaming Multiprocessor)은 데이터가 도착하기를 기다리며 대부분의 사이클을 허비한다.
이 두 워크로드를 같은 GPU, 같은 스케줄러로 동시에 처리하려고 하면 상호 간섭이 발생한다. 4,000토큰짜리 Prefill이 돌고 있으면 다른 요청의 Decode가 블로킹된다. 반대로 Decode 작업이 산발적으로 GPU를 점유하면 Prefill의 계산 효율이 떨어진다. 더 큰 문제는 스케일링이다. Prefill이 병목이든 Decode가 병목이든, 두 작업이 묶여 있으면 각각의 필요에 맞춰 독립적으로 GPU 노드를 늘릴 수 없다. 트래픽 패턴이 변할 때마다 전체 클러스터의 구성을 바꿔야 한다.
이 지점에서 등장한 아이디어가 Prefill-Decode 분리형(Disaggregated) 아키텍처다. Prefill 전용 노드와 Decode 전용 노드를 물리적으로 분리하고, Prefill 노드에서 생성된 KVCache를 고속 인터커넥트로 Decode 노드에 전달하는 방식이다. 이론은 단순하지만, 그 "고속 전달"을 실제로 구현하는 건 전혀 단순하지 않다.
달 케이크가 골칫거리를 무기로 바꾸는 법
Moonshot AI가 개발한 Mooncake는 이 지점에서 독특한 전환을 시도한다. 전통적인 접근이 "KVCache를 최대한 GPU 안에 가두자"였다면, Mooncake는 "KVCache를 GPU 밖으로 과감하게 빼내고, 그 대신 옮기는 속도를 극단적으로 높이자"는 역발상을 취한다.
핵심은 Transfer Engine이라 불리는 RDMA 기반 데이터 전송 프레임워크다. 기존 TCP 기반 전송이 40GB 데이터를 옮기는 데 수십 Gbps에서 맴돌았다면, Mooncake의 Transfer Engine은 4포트 200Gbps RoCE 환경에서 87 GB/s, 8포트 400Gbps RoCE에서는 190 GB/s를 기록한다. TCP 대비 각각 2.4배, 4.6배 빠른 속도다. 단순히 빠르기만 한 게 아니라, 전송이 지연되는 동안 Prefill 계산을 다른 GPU에서 계속 진행하는 파이프라이닝이 가능해진다. 실제 vLLM 연동 벤치마크를 보면, Qwen3-8B 모델에서 32,768토큰(4.50GB)의 KVCache를 전송하는 데 걸린 시간은 31.65ms로 전체 TTFT(Time To First Token)의 4.2%에 불과했다.
Transfer Engine이 극복한 것은 속도만이 아니다. 현대 GPU 서버는 여러 CPU 소켓과 여러 RDMA NIC을 갖고 있는데, 데이터가 어느 NIC을 타고 나가느냐에 따라 UPI나 PCIe 스위치 대역폭에 발목이 잡힐 수 있다. Mooncake는 서버마다 토폴로지 매트릭스를 생성해 브로드캐스트하고, 메모리 주소가 속한 NUMA 노드에 가장 가까운 NIC을 자동으로 선택한다. 전송 중 네트워크 장애가 발생하면 대체 경로로 우회하고, 64KB보다 큰 전송은 여러 NIC에 분산시켜 대역폭을 합산한다. 마치 소프트웨어 정의 네트워킹을 추론 시스템 안으로 끌어들인 형국이다.
유휴 자원으로 만드는 분산 KVCache 풀
빠른 전송만으로는 충분하지 않다. 진짜 통찰은 "어차피 유휴 상태인 CPU DRAM과 SSD를 글로벌 캐시 풀로 엮어버리자"는 발상에 있다. GPU 클러스터에는 항상 유휴 자원이 존재한다. 모든 GPU가 동시에 VRAM을 꽉 채우지도 않고, CPU DRAM은 대부분 비어 있으며, NVMe SSD의 읽기 대역폭은 쓰이지 않고 놀고 있다.
Mooncake는 Mooncake Store라는 분산 KV 저장 엔진을 통해 이 자원들을 하나의 계층형 캐시로 추상화한다. GPU HBM을 L1, 로컬 CPU DRAM을 L2, 원격 DRAM 풀을 L3, 그리고 SSD를 콜드 스토리지 티어로 구성하는 식이다. 데이터가 로컬에 없으면 원격 DRAM에서 RDMA로 가져오고, 거기에도 없으면 SSD에서 읽어온다. 이 모든 과정이 vLLM이나 SGLang의 표준 KVConnector 인터페이스를 통해 추론 엔진에 투명하게 통합된다.
여기서 놓치기 쉬운 지점은, 단일 서버의 DRAM 캐시로는 규모의 경제가 성립하지 않는다는 사실이다. Mooncake 팀의 분석에 따르면 1TB의 로컬 DRAM 캐시는 약 300만 토큰 분량의 KVCache만 저장할 수 있고, 이 정도 용량으로는 대부분의 실제 워크로드에서 이론적 최대 히트율의 50%도 달성하지 못한다. 반면 노드 20대의 DRAM을 풀링해 5,000만 토큰 이상의 캐시 풀을 만들면 거의 100%에 가까운 히트율에 도달한다. Prefill에서 재계산해야 할 컨텍스트가 줄어들수록 GPU compute time도, 사용자가 체감하는 TTFT도 함께 줄어든다.
글로벌 스케줄러 Conductor가 푸는 줄타기
분산 캐시 풀의 또 다른 난제는 "누가, 언제, 어디에 KVCache를 배치할 것인가"이다. Mooncake는 Conductor라는 글로벌 스케줄러를 통해 이 문제를 푼다. Conductor는 각 요청에 대해 Prefill 노드와 Decode 노드를 하나씩 선택하고, 다음과 같은 4단계 워크플로를 조율한다.
먼저 해당 요청에서 재사용 가능한 prefix 캐시 블록이 원격 DRAM에 존재하는지 확인하고, 있다면 RDMA로 Prefill 노드의 GPU 메모리에 선탑재한다. Prefill 노드는 청크 단위 혹은 레이어 단위로 계산을 진행하면서, 생성된 KVCache를 즉시 Messenger 컴포넌트를 통해 Decode 노드의 CPU 메모리로 스트리밍한다. Decode 노드는 이 KVCache를 GPU 메모리에 로드하여 continuous batching 대기열에 추가한다. 이 모든 과정이 비동기 파이프라인으로 중첩되며, 전송 지연은 계산 시간 뒤에 숨겨진다.
Conductor의 또 다른 책무는 "과부하 상태에서 무엇을 버릴 것인가"를 결정하는 것이다. LLM 서비스는 트래픽 스파이크에 취약하다. 요청이 폭주하면 SLO(Service Level Objective)를 맞출 수 없는 요청이라도 일단 Prefill까지는 처리해버리고, 결국 데드라인을 넘겨 폐기되는 낭비가 발생한다. Mooncake는 예측 기반 조기 거절(early rejection) 정책을 통해, 애초에 SLO를 못 맞출 요청을 Prefill 이전에 걸러낸다. 이걸 통해 시뮬레이션 환경에서 기준 대비 최대 525%의 처리량 증가를 달성했고, Kimi의 실운영 환경에서는 75% 더 많은 요청을 처리할 수 있게 됐다.
에코시스템 침투 속도가 말해주는 것
2024년 6월 첫 논문 공개 이후, Mooncake의 생태계 확장 속도는 주목할 만하다. 2024년 11월 Transfer Engine 오픈소스화를 시작으로, 불과 몇 달 만에 vLLM의 공식 KVConnector 인터페이스를 통해 PD 분리 서빙을 지원하게 됐다. 2025년 3월에는 Mooncake Store가 오픈소스로 공개됐고, SGLang은 HiCache라는 3단계 계층형 캐시 시스템에 Mooncake를 L3 백엔드로 통합했다. LMCache, LMDeploy, TensorRT-LLM, xLLM, FlexKV가 차례로 Mooncake를 전송 백엔드로 채택했고, 2026년 2월에는 PyTorch 생태계의 공식 프로젝트로 편입됐다.
단순한 채택 숫자보다 중요한 건 통합의 깊이다. vLLM의 MooncakeStoreConnector는 여러 vLLM 인스턴스가 하나의 분산 KVCache 풀을 공유하게 해준다. Agentic 워크로드 — 여러 도구 호출과 멀티턴 대화가 반복되며 동일한 시스템 프롬프트와 컨텍스트 prefix를 계속 재계산해야 하는 시나리오 — 에서 이 공유 캐시의 효과는 극적이다. Codex 기반 에이전틱 트레이스를 사용한 벤치마크에서는 처리량 3.8배, P50 TTFT는 46배 단축, 종단 간 지연 시간은 8.6배 감소를 기록했다. 로컬 캐시 히트율이 1.7%에 불과하던 워크로드가 분산 풀링으로 80% 이상의 히트율을 달성하면서 Prefill 컴퓨팅을 대부분 제거했기 때문이다.
SGLang의 HiCache는 한 걸음 더 나아간다. Mooncake, 3FS, NIXL, AIBrix KVCache 등 다양한 분산 KVCache 백엔드를 플러그인 형태로 지원하며, PD 분리 배포 모드에서 Prefill 노드와 Decode 노드 양쪽에 HiCache를 활성화할 수 있다. Decode 노드에서 생성된 새로운 KVCache도 자동으로 L3에 다시 기록되므로, 후속 요청의 캐시 히트율을 실시간으로 높인다. CPU의 L1→L2→L3 캐시 계층 구조를 추론 시스템 수준으로 확장한 셈이다.
이 모든 움직임은 하나의 방향을 가리킨다. 이제 추론 서비스를 "고립된 vLLM 레플리카들의 집합"으로 바라볼 수 없다는 것. 클러스터 전체를 하나의 KVCache 공간으로 추상화하고, 그 위에서 스케줄러가 캐시 지역성과 네트워크 토폴로지를 동시에 고려하며 요청을 라우팅하는 — 그런 세계관이 실전에 진입하고 있다.
100B 토큰을 매일 처리하는 플랫폼이 증명한 것
Mooncake는 현재 Kimi의 실서비스 환경에서 수천 대 노드 규모로 운영되며 하루 100B 이상의 토큰을 처리한다. 논문이나 PoC 단계의 아이디어가 아니라, 실제로 수억 명의 사용자가 체감하는 응답 속도 뒤에 이 아키텍처가 숨어 있다는 사실은 의미심장하다. NVIDIA A800 클러스터에서는 115%, H800 클러스터에서는 107% 더 많은 요청을 이전 시스템 대비 처리했다. 단순한 소프트웨어 최적화로 달성한 수치가 아니다. 컴퓨테이션보다 스토리지와 전송에 무게 중심을 두는 근본적인 패러다임 전환의 결과다.
"스토리지를 더 써서 컴퓨테이션을 아끼겠다"는 Mooncake의 설계 철학은, 생각해보면 너무나 당연한 트레이드오프에서 출발한다. GPU의 부동소수점 연산은 희소하고 비싸지만, CPU DRAM과 NVMe SSD는 상대적으로 풍부하고 싸다. 그런데 그동안은 이 값싼 자원을 KVCache 전송에 활용할 방법이 마땅치 않았다. RDMA와 토폴로지 인지 전송, 다중 NIC 대역폭 집합을 하나의 엔진으로 추상화한 Transfer Engine이 그 간극을 메웠다.
물론 이런 아키텍처가 만능은 아니다. 네트워크 홉이 늘어나는 만큼 꼬리 지연(tail latency)에 취약해질 수 있고, Conductor 자체가 새로운 단일 장애점이 될 위험도 있다. 캐시 일관성 문제, 노드 장애 시 KVCache 재구성 전략, heterogeneous 클러스터에서의 부하 분산은 여전히 진행 중인 연구 과제다. 그러나 Mooncake의 오픈소스 생태계 전략 — Transfer Engine을 표준 인터페이스로 제공하고 vLLM, SGLang, LMCache 등이 각자 자기 방식으로 통합하는 — 은 이런 문제들을 커뮤니티 수준에서 분산 해결할 수 있는 토대를 만들어가고 있다.
더 넓은 시야에서 보면, KVCache 중심 설계는 단순한 추론 최적화 기법이 아니라 LLM 인프라가 나아갈 방향에 대한 하나의 선언이다. 모델이 처리해야 하는 컨텍스트가 길어질수록, 멀티턴 대화와 에이전트 워크플로가 보편화될수록, "기억을 어떻게 옮기고 공유할 것인가"는 "얼마나 빨리 계산할 것인가"보다 더 결정적인 질문이 된다. Mooncake가 제시한 답변은 그 질문을 무기로 바꾸는 법이다.
댓글
댓글을 읽어오는 중입니다.
같이 읽으면 좋은 글
방금 읽은 주제와 이어지는 글을 골랐습니다.
LLM이 JSON만 뱉기로 약속했을 때 실제로 일어나는 일
OpenAI의 json_object 모드는 구문 유효성만 보장할 뿐 스키마를 강제하지 않는다. Zod/Pydantic 검증, json_repair로 복구하고 validation error를 피드백해 재시도하는 단계별 방어선과 서킷 브레이커, 멀티 프로바이더 폴백까지 실무 패턴을 파헤친다.
AI 에이전트는 서버에서 태어났지만 브라우저에서 산다
대부분의 AI 에이전트가 여전히 서버에서 오케스트레이션을 돌리고 있지만, 브라우저가 가진 런타임 맥락과 WebGPU·WebLLM의 발전이 이 판도를 바꾸고 있다. 서버 중심과 브라우저 네이티브 아키텍처의 지연 시간, 개인정보 보호, 비용, 확장성을 비교하며 왜 지금 이 전환이 중요한지 분석한다.
로컬 AI가 당신 대신 HTML을 쓰는 시대, 마크다운은 초안으로 충분하다
로컬 AI 에이전트가 마크다운, CSV, JSON 등 다양한 입력을 75가지 디자인 템플릿과 9가지 출력 형식으로 변환해 완성된 HTML을 즉시 생성하는 패러다임을 다룬다. API 키 없이 Claude Code, Cursor 등 기존 CLI 세션을 재활용하며 원클릭으로 여러 소셜 플랫폼에 배포할 수 있다.
이전 글
로컬 AI가 당신 대신 HTML을 쓰는 시대, 마크다운은 초안으로 충분하다
다음 글
TypeScript 설계 부채를 숫자로 드러내고 CI가 대신 막게 하라
DevInsight Digest
새 글이 쌓이면, 피드에서 바로 이어 읽으세요.
과장된 알림 대신 발행한 글 전체를 RSS로 제공합니다.