HiCache 시스템 설계와 최적화

HiCache 시스템 설계와 최적화

이 문서는 SGLang HiCache의 시스템 아키텍처, 워크플로, 핵심 컴포넌트를 종합적으로 다뤄요. 구성 파라미터, 최적화 기법, 다양한 L3 스토리지 백엔드와의 통합까지 상세히 설펴서, 효율적 LLM 추론을 위해 HiCache를 이해·튜닝하려는 사용자와 개발자에게 완전한 참조 자료가 되어 줘요.

출처: HiCache 시스템 설계와 최적화

HiCache란 무엇이며 왜 필요한가

대규모 언어 모델 추론에서 prefill 단계는 자주 시간이 걸려요. 입력 시퀀스를 먼저 Key-Value 캐시(KV cache)로 변환해야 이후 디코딩이 가능하기 때문이에요. 여러 요청이 같은 prefix를 공유할 때 그 prefix의 KV cache는 동일해요. 이런 공유 KV cache를 캐시하고 재사용하면 중복 계산을 피할 수 있어요. 이를 위해 SGLang은 RadixAttention을 도입했는데, 이는 유휴 GPU 메모리를 활용해 prefix KV cache를 캐시·재사용해요. 그리고 HiCache는 이 아이디어를 호스트 메모리와 분산 스토리지로 확장해요.

현대 CPU의 고전적인 3계층 캐시 설계에서 영감을 받아, HiCache는 GPU 메모리를 L1, 호스트 메모리를 L2, 분산 스토리지를 L3로 구성해요. 이 계층 구조 덕분에 HiCache는 GPU와 CPU의 "유휴" 저장 공간을 최대한 활용하면서, Mooncake, 3FS, NIXL, AIBrix KVCache 같은 분산 캐시 시스템을 전역 KV 캐시 저장·스케줄링에 통합할 수 있어요. 그 결과 HiCache는 강력한 읽기 성능을 유지하면서 KV 캐시 용량을 크게 확장해요 — 특히 멀티-QA, 긴 컨텍스트 추론처럼 KV 캐시 재사용이 잦은 워크로드에서요. 자세한 벤치마크 결과는 이 블로그를 보세요.

시스템 설계

전체 아키텍처

많은 현대 CPU 아키텍처에서 작지만 빠른 L1·L2 캐시는 각 코어에 전용(private)이라 가장 뜨거운 데이터에 빠르게 접근하게 해 주고, 더 큰 L3 캐시는 모든 코어가 공유해서 캐시 내 중복을 크게 줄여요. 마찬가지로 HiCache에서 L1·L2 KV 캐시는 각 추론 인스턴스에 전용이고, L3 KV 캐시는 클러스터 내 모든 추론 인스턴스가 공유해요.

티어 공유 범위

어느 티어가 전용이고 어느 것이 공유인지는 캐시 히트가 어디서 올 수 있는지를 결정하므로 명확히 짚고 갈게요.

티어 매체 범위 인스턴스 간 공유?
L1 GPU 메모리 (HBM) 한 추론 인스턴스 아니요
L2 호스트 메모리 (CPU DRAM) 한 추론 인스턴스 (자기 노드에서) 아니요
L3 스토리지 백엔드 (file, mooncake, hf3fs, nixl, aibrix 또는 커스텀) 백엔드가 구성된 범위 백엔드가 그렇게 설정된 경우에만

L2는 노드 로컬이자 인스턴스 전용이에요. 한 추론 인스턴스 프로세스가 소유한 호스트 메모리라서, 두 인스턴스는 서로의 L2를 읽지 못해요 — 같은 노드의 두 인스턴스조차요. 인스턴스 0이 만든 KV 캐시는 L3에 도달한 뒤에야 인스턴스 1에 보이게 돼요.

이건 흔한 질문에 대한 답이기도 해요: HiCache는 여러 머신의 호스트 메모리를 하나의 더 큰 L2로 풀링하지 못해요. --hicache-ratio--hicache-size를 키우는 건 각 인스턴스의 전용 L2만 더 크게 만들 뿐이에요. 크로스-인스턴스 재사용은 L3의 일이라 --hicache-storage-backend가 필요해요.

L3는 공유할 수 있는 티어지만 실제로 공유되는지는 백엔드와 그 구성에 달려 있어요. file 백엔드는 기본적으로 /tmp/hicache에 써요(SGLANG_HICACHE_FILE_BACKEND_STORAGE_DIR로 덮어씀). 이 경로가 공유 마운트가 아니면 노드 로컬이에요. mooncake, hf3fs, nixl, aibrix 같은 분산 백엔드는 클러스터 전체 범위를 주지만, 모든 인스턴스가 같은 네임스페이스와 구성에 연결됐을 때만 그렇죠.

HiRadixTree: HiCache의 메타데이터 조직

KV 캐시 데이터 조직을 위해 HiCache는 RadixAttention에서 도입한 RadixTree 구조 위에 HiRadixTree를 제안해요. RadixAttention에서 RadixTree의 각 노드는 GPU 메모리 내 연속 토큰 구간의 KV 캐시에 대응해요. 루트에서 리프 노드로 가는 경로는 요청 하나의 prefix를 나타내고, 여러 요청이 공유하는 prefix는 같은 노드를 재사용해서 중복 저장을 피해요.

HiRadixTree는 이 아이디어를 확장해요. 각 노드는 연속 토큰 구간의 KV 캐시에 대응하고, 그 KV 캐시가 어디에 저장됐는지 — 로컬 GPU 메모리, CPU 메모리, L3 스토리지, 또는 이 중 여러 곳 — 기록해요. 로컬에 저장되면 HiRadixTree는 정확한 저장 주소를 포함한 정밀 메타데이터를 유지해요. 하지만 오버헤드를 줄이기 위해 HiRadixTree는 L3 KV 캐시의 메타데이터를 저장하거나 지속적으로 동기화하지 않아요. 대신 L3 데이터에 접근할 때 백엔드에 실시간으로 질의해서 데이터 존재 여부, 어느 서버·어느 위치에 있는지 같은 필요한 메타데이터를 가져와요.

전체 워크플로

HiCache의 워크플로는 세 가지 핵심 연산인 로컬 매칭(local match), 프리페치(prefetch), **라이트백(write-back)**으로 이뤄져요. 시스템이 새 요청을 받으면 먼저 로컬 L1·L2 캐시에서 일치하는 KV 캐시를 찾아요. 로컬에 없는 부분은 L3에서 프리페치를 시도해요. 프리페치 후 필요한 모든 KV 캐시가 GPU에 로드돼 계산돼요. prefill 계산이 끝나면 시스템은 새로 생성된 데이터를 L2나 L3에 저장할지 고려해요.

로컬 매칭

로컬 매칭은 HiCache 워크플로의 첫 단계로, 들어온 요청 토큰을 HiRadixTree에 매칭시켜 로컬 메모리 티어(L1 GPU 메모리, L2 호스트 메모리)에 있는 캐시된 KV 데이터를 찾아요.

매칭 알고리즘은 HiRadixTree를 루트 노드에서 탐색하면서 토큰 시퀀스 prefix에 일치하는 자식 노드를 따라가요. 각 노드에서 들어온 토큰 시퀀스를 노드의 저장된 토큰 시퀀스와 비교해요. page_size > 1이면 메모리 접근 패턴 최적화를 위해 페이지 단위로 매칭해요. 일치가 노드의 저장된 시퀀스 안에서 끝나면 노드를 자동 분할해서 정확한 경계를 만들고, 미래 매칭 효율을 높여요.

알고리즘은 요청의 연속 prefix를 반환하는데, 앞부분은 L1, 뒷부분은 L2에 있어요.

이 과정은 로컬 HiRadixTree 탐색만 하고 실제 데이터 복사가 없어서 로컬 매칭은 매우 빨라요.

L3에서 프리페치

데이터 프리페치는 HiCache의 핵심 최적화 기법 중 하나로, L3 스토리지에서 로컬 L2 메모리로 KV 캐시를 선제적으로 로드해서 이후 연산의 접근 지연을 줄여요.

프리페치 트리거 조건: 로컬 매칭 후 L1이나 L2에 없는 부분에 대해, 시스템은 L3에 질의해서 다음 연속 매칭 KV 캐시의 메타데이터를 가져와요. L3의 히트 캐시 길이가 임계값(기본 256 토큰, 구성 가능)을 넘으면 프리페치 연산이 트리거돼요.

프리페치 전략: HiCache는 서로 다른 시나리오 요구를 다루기 위해 세 가지 프리페치 종료 전략을 제공해요.

  • best_effort: GPU가 prefill 계산을 실행할 수 있으면 대기 없이 즉시 종료. 지연에 극도로 민감한 시나리오에 적합.
  • wait_complete: 모든 프리페치 연산이 완료될 때까지 기다려야 함. 높은 캐시 히트율이 필요한 시나리오에 적합.
  • timeout: 지정 시간 후 또는 완료 시 종료. 지연과 캐시 히트율 요구를 균형.

프리페치가 멈추면 이미 가져온 데이터를 로컬 데이터와 함께 prefill 계산에 써요.

timeout 전략에서 HiCache는 프리페치 타임아웃 조건을 세밀하게 제어할 세 가지 구성 파라미터를 도입해요.

  • prefetch_timeout_base: 기본 타임아웃. 토큰 수와 무관한 오버헤드(예: 스케줄링·동기화). 기본: 2초.
  • prefetch_timeout_per_ki_token: 천 토큰당 증가 타임아웃. 기본: 1024 토큰당 0.1초.
  • prefetch_timeout_max: 선형 타임아웃에 적용되는 상한. 매우 긴 프롬프트가 무한정 기다리지 않게 방지. 기본: 30초.

타임아웃은 이렇게 계산돼요.

timeout = min(
    prefetch_timeout_max,
    prefetch_timeout_base + prefetch_timeout_per_ki_token * num_token_to_fetch / 1024,
)

데이터 라이트백

라이트백 메커니즘은 자주 접근되는 KV 캐시를 L1에서 L2·L3로 옮겨, 더 크고 장기적인 저장과 인스턴스 간 캐시 공유를 가능하게 해요.

구성 가능한 라이트백 정책: HiCache는 세 가지 라이트백 전략을 지원해요.

  • write_through: 모든 접근을 즉시 다음 레벨로 라이트백. 대역폭이 충분하면 가장 강한 캐싱 효과를 주는 전략.
  • write_through_selective: 접근 빈도가 임계값을 넘은 후에만 라이트백. 뜨거운 데이터만 백업해서 I/O 오버헤드를 줄여요.
  • write_back: 상위 레벨에서 evict될 때만 다음 레벨로 라이트백. 저장 압박을 완화하고, 저장 용량이 제한적이지만 메모리 활용을 극대화해야 하는 시나리오에 적합.

인스턴스 간 공유: 데이터가 L2에서 L3로 라이트백될 때, 이미 L3에 있는 데이터만 제외하고 전송돼요(L3에 이미 없는 데이터만 전송). L3에 저장된 KV 캐시는 클러스터의 모든 SGLang 인스턴스가 공유할 수 있어서(L3 백엔드 구현에 따라), 같은 메모리 예산 안에서 캐시 히트율을 크게 높여요.

멀티-rank 동기화

텐서 병렬(TP) 같은 멀티-GPU 병렬 계산 중 HiCache는 서로 다른 rank 간에 일관된 상태를 보장해야 해요. 그래서 중요한 계산 단계에는 상태 동기화를 위해 all_reduce를 사용해요.

예를 들어 프리페치 중 all_reduce(op=min)으로 모든 rank가 같은 수의 L3 히트를 얻도록 보장해서, 프리페치 임계값에 도달했는지에 대한 불일치 판단을 방지해요. 마찬가지로 프리페치가 완료되거나 종료된 뒤에도 all_reduce(op=min)이 다시 필요해서, 성공적으로 가져온 KV 캐시의 prefix 길이에 대해 rank 간 합의를 보장해요.

데이터 전송 최적화

제로카피 데이터 전송: 프리페치와 라이트백 둘 다 상당한 데이터 이동을 수반해요. 데이터 복사 횟수를 최소화하면 시스템 성능을 크게 높일 수 있어요. HiCache는 L2 메모리에서 L3 백엔드로 데이터를 전송할 때 메모리 주소와 크기를 직접 전달하는 것을 지원해요.

"배치 지향" 데이터 구성: 데이터 읽기·쓰기의 입도(granularity)는 성능에 큰 영향을 줘요. 이를 위해 HiCache L3는 KV 캐시 데이터를 페이지 단위로 저장·전송하고, 기존 layer first 스킴 외에 page first, page first direct 같은 다른 데이터 레이아웃을 지원해요. page firstpage first direct 레이아웃에서는 같은 페이지에 속한 모든 KV 캐시 데이터가 연속 메모리에 놓이고, 제로카피 전송으로 단일 객체로 L3에 넘길 수 있어요.

하지만 GPU KV 계산은 자연스럽게 레이어 단위로 이뤄져서, GPU는 본질적으로 layer first 레이아웃에서 동작해요. page first 데이터를 L2에서 GPU로 전송할 때는 레이어당 토큰 하나의 입도로 전송해야 해요. page first direct 레이아웃은 페이지 내에서 주어진 레이어의 모든 토큰을 그룹화해서 L2→GPU 전송을 페이지-레이어 수준으로 묶을 수 있게 함으로써 이 문제를 완화해요.

CPU→GPU 전송 최적화: HiCache에서 CPU 메모리에서 GPU로 데이터를 옮기는 것은 L3에서 L2로 프리페치하는 것만큼 성능에 중요해요. HiCache는 이 과정에 몇 가지 최적화를 적용해요.

  • 계산-전송 오버랩: prefill 단계에서 CPU에서 GPU로 전송할 때, HiCache는 레이어 N을 계산하면서 레이어 N+1의 KV 캐시를 동시에 로드해서 레이어를 겹쳐요. 이러면 데이터 전송 지연이 효과적으로 숨겨져요.
  • GPU 보조 I/O 커널: cudaMemcpyAsync 위에 HiCache는 CPU와 GPU 사이의 KV 캐시 전송에 특화된 GPU 보조 I/O 커널 세트를 구현해요. 기준 방식과 비교해 이 커널은 전송 속도를 최대 3배 높여요.

MLA 라이트백 최적화: MHA(Multi-Head Attention) 모델은 멀티-TP에서 각 rank가 토큰 KV 데이터의 1/tp_size를 보유해요. 반대로 MLA(Multi-Layer Attention) 모델은 모든 rank가 각 토큰의 완전하고 동일한 KV 데이터를 보유해요. HiCache는 MLA 전용 최적화를 포함해요. 한 rank만 라이트백 연산을 시작해서 데이터가 rank 간에 중복 저장되지 않도록 해요.

PD-분리 배포 모드와의 통합

SGLang은 Mooncake TransferEngine을 통해 PD(Prefill-Decode) 분리 배포 모드를 지원해요(자세한 내용은 이 문서를 보세요). PD-분리 배포 모드에서 HiCache는 prefill 노드와 decode 노드 둘 다에서 활성화해 prefill 성능을 최적화할 수 있어요. decode 노드에서 활성화하면 decode 출력도 L3로 라이트백돼요.

통합 인터페이스와 풍부한 L3 스토리지 백엔드

HiCache는 L3 백엔드에 대한 모든 읽기·쓰기·질의 연산을 class HiCacheStorage(ABC) 안에 캡슐화해서, 단순하고 일관된 인터페이스 세트를 노출해요. 이 설계는 다양한 L3 스토리지 백엔드를 지원하고, 사용자가 자신의 특정 사용 사례에 가장 잘 맞는 것을 선택하게 해요.

  • Mooncake: Mooncake는 RDMA와 멀티-NIC 리소스를 활용해 제로카피, 초고속 데이터 전송을 가능하게 하는 LLM 추론용 고성능 캐싱 시스템이에요. Mooncake는 여기에서 시도해 보세요.

  • DeepSeek 3FS (HF3FS): HF3FS는 operator 기반 배포의 Kubernetes 네이티브 분산 스토리지 솔루션이에요. HF3FS는 여기에서 시도해 보세요.

  • NIXL: NIXL은 DeepSeek의 3FS, GPU Direct Storage(GDS), Amazon S3 호환 객체 스토리지를 포함해(그 외에도) 다양한 스토리지 플러그인에 접근하는 통합 API를 제공해요. NIXL은 여기에서 시도해 보세요.

  • AIBrix KVCache: AIBrix KVCache는 프로덕션급 KVCache Offloading 프레임워크로, 효율적 메모리 티어링과 저오버헤드 크로스-엔진 재사용을 가능하게 해요. AIBrix KVCache는 여기에서 시도해 보세요.

  • HiCacheFile: 데모용 단순 파일 기반 스토리지 백엔드.

구체적으로 LMCache, 엔터프라이즈 규모 LLM 추론용 효율적 KV 캐시 레이어는 HiCache의 대안 솔루션을 제공해요. LMCache는 여기에서 시도해 보세요.

관련 파라미터

  • --enable-hierarchical-cache: 계층 캐시 기능 활성화. HiCache를 쓰려면 필요.

  • --hicache-ratio HICACHE_RATIO: 호스트 KV 캐시 메모리 풀 크기와 디바이스 풀 크기의 비율. 예를 들어 값 2는 호스트 메모리 풀이 디바이스 메모리 풀의 두 배라는 뜻. 이 값은 1보다 커야 해요. 현재 구현이 KV 캐시에 할당된 호스트 메모리가 디바이스 메모리보다 커야 하기 때문이에요.

  • --hicache-size HICACHE_SIZE: 호스트 KV 캐시 메모리 풀 크기(GB 단위). 설정하면 hicache-ratio를 덮어써요. 예를 들어 --hicache-size 30rank마다 호스트 메모리 풀에 30GB(1GB = 1e9 바이트)를 할당해요. 8개 rank면 총 240GB예요. hicache-ratio와 마찬가지로 이 값도 KV 캐시에 할당된 디바이스 메모리 크기보다 커야 해요.

참고: --hicache-ratio--hicache-size는 두 가지 중요한 파라미터예요. 일반적으로 HiCache 크기가 클수록 캐시 히트율이 높아져 prefill 성능이 좋아져요. 하지만 캐시 크기와 히트율의 관계는 선형이 아니에요. 재사용 가능한 KV 데이터 — 특히 핫 토큰 — 가 대부분 캐시된 후에는 크기를 더 늘려도 성능 향상이 미미할 수 있어요. 워크로드 특성과 성능 요구에 따라 이 파라미터를 설정하면 돼요.

  • --page-size PAGE_SIZE: 페이지당 토큰 수. 이 파라미터는 KV 캐시 저장·검색의 입도를 결정해요. 페이지가 클수록 메타데이터 오버헤드가 줄고 스토리지 백엔드의 I/O 효율이 좋아지지만, 페이지의 일부만 저장된 KV 캐시와 일치하면 캐시 히트율이 낮아질 수 있어요. 긴 공통 prefix가 있는 워크로드에서는 큰 페이지가 성능을 높일 수 있고, 더 다양한 prefix가 있는 워크로드는 작은 페이지가 유리할 수 있어요. 페이지 입도가 I/O 성능에 미치는 영향은 데이터 전송 최적화에서 설명해요.

  • --hicache-storage-prefetch-policy {best_effort,wait_complete,timeout}: 스토리지에서 프리페치를 언제 멈출지 제어해요. 자세한 내용은 L3에서 프리페치를 보세요.

    • best_effort: 블로킹 없이 최대한 프리페치.
    • wait_complete: 진행 전에 프리페치 완료를 기다림.
    • timeout: 지정 시간 후 또는 완료 시 종료(프로덕션 환경 권장 — 적절한 타임아웃 설정이 요구 SLO를 충족시키는 데 도움).
  • --hicache-write-policy {write_back,write_through,write_through_selective}: 빠른 메모리 티어에서 느린 티어로 데이터를 어떻게 쓸지 제어해요. 자세한 내용은 데이터 라이트백을 보세요.

    • write_through: 모든 티어에 즉시 데이터 기록(가장 강한 캐싱 효과).
    • write_through_selective: 히트 수 추적으로 자주 접근되는 데이터만 백업.
    • write_back: eviction이 필요할 때만 느린 티어로 데이터 기록(I/O 부하 감소).
  • --hicache-io-backend {direct,kernel}: CPU와 GPU 사이의 KV 캐시 전송용 I/O 백엔드 선택. 자세한 내용은 데이터 전송 최적화를 보세요.

    • direct: 표준 CUDA 메모리 복사 연산.
    • kernel: GPU 보조 I/O 커널(더 나은 성능 권장).
  • --hicache-mem-layout {layer_first,page_first,page_first_direct}: 호스트 메모리 풀의 메모리 레이아웃. 자세한 내용은 데이터 전송 최적화를 보세요.

    • layer_first: GPU 계산 커널과 호환(GPU 메모리 기본값).
    • page_first: I/O 효율에 최적화.
    • page_first_direct: 페이지 내에서 주어진 레이어의 모든 토큰을 그룹화해 L2→GPU 전송을 페이지-레이어 수준으로 묶을 수 있게 함.
  • --hicache-storage-backend {file,mooncake,hf3fs,nixl,aibrix,dynamic}: L3 티어의 스토리지 백엔드 선택. 내장 백엔드: file, mooncake, hf3fs, nixl, aibrix. dynamic 백엔드에는 --hicache-storage-backend-extra-configbackend_name(커스텀 이름), module_path(Python 모듈 경로), class_name(백엔드 클래스 이름)을 지정하세요. 사용 가능한 백엔드는 통합 인터페이스와 풍부한 L3 스토리지 백엔드를 보세요.

  • --enable-lmcache: LMCache를 대안 계층 캐시 솔루션으로 사용.

  • --lmcache-config-file: LMCache YAML 구성 파일 경로.

  • --hicache-storage-backend-extra-config HICACHE_STORAGE_BACKEND_EXTRA_CONFIG: extra config는 다음 중 하나일 수 있어요.

    • 스토리지 백엔드의 extra 구성을 담은 JSON 문자열. 예: --hicache-storage-backend-extra-config '{"prefetch_threshold":512, "prefetch_timeout_base": 0.5, "prefetch_timeout_per_ki_token": 0.25}'.
    • 스토리지 백엔드의 extra 구성을 지정하는 TOML/JSON/YAML 파일(JSON 문자열 입력과 구분하려면 파일 이름 앞에 @를 붙이세요). 예: --hicache-storage-backend-extra-config "@config.toml" — 여기서 config.toml은 복잡한 구성을 담은 구성 파일이에요. 키-값 쌍이 많거나 복잡하면 유용해요(예: NIXL 백엔드는 구성이 복잡할 수 있어 구성 파일을 쓰는 걸 선호해요).