콘텐츠로 이동

KV 캐시 오프로딩

Model implementation 지원

이 기능은 vllm model implementation에서만 동작합니다. 기능별 지원 여부는 Feature Matrix에 모두 정리되어 있습니다. 설정하기 전에 확인하는 것을 권합니다.

개요

Prefix caching은 KV 캐시가 NPU 디바이스 메모리에 남아 있는 동안에만 이를 재사용합니다. 디바이스 메모리는 계층 중 가장 작기 때문에, 한 번 evict된 프리픽스는 이후 요청에 같은 프리픽스가 포함되면 재계산해야 합니다.

KV 캐시 오프로딩은 요청을 처리하는 동안 재사용 가능한 KV 청크를 더 크고 느린 계층에 저장합니다. NPU 캐시에서 프리픽스가 evict된 뒤에도, 이후 요청은 이 계층에서 KV를 복원해 재계산을 줄일 수 있습니다. RBLN vLLM은 LMCache와 그 RBLN 확장인 lmcache-rbln을 통해 이를 지원합니다.

아키텍처

lmcache-rbln은 RBLN NPU를 위한 LMCache의 하드웨어 특화 확장입니다. 청크 분할, 조회, eviction 정책 같은 캐시 관리는 upstream lmcache 패키지에 의존하고, 디바이스에 닿는 부분만 대체합니다. NPU 메모리와 오프로드 계층 사이의 KV 캐시 전송, 그리고 NVMe 직접 스토리지용 저장 백엔드가 그 대상입니다.

RBLN vLLM은 이 확장의 connector인 RBLNLMCacheConnectorV1을 자신의 KV connector factory에 등록하므로, 다른 vLLM KV connector와 마찬가지로 --kv-transfer-config에서 이름으로 선택할 수 있습니다.

저장 계층

오프로드 계층은 LMCache 설정으로 선택하며, 이 문서는 다음 두 계층을 다룹니다.

  • CPU 메모리: 호스트 메모리 풀이며, LMCache의 기본 백엔드입니다.
  • Rebellions Direct Storage(RDS): 호스트 메모리를 거치지 않고 KV 청크를 NPU 메모리에서 NVMe로 직접 기록합니다.

Rebellions Direct Storage(RDS)

RDS는 RBLN NPU가 NVMe SSD에 직접 읽고 쓰는 데이터 경로입니다. NVMe와 NPU 메모리를 PCIe peer-to-peer DMA로 연결해 호스트 바운스 버퍼를 제거합니다.

RDS 백엔드에는 두 가지 제약이 있습니다.

  • RDS 백엔드는 인덱스를 메모리에 유지하므로, 오프로드된 청크는 재시작 후에는 남아 있지 않습니다.
  • RDS 백엔드는 저장하는 청크 수를 rds_max_chunks로 제한하며, 상한에 도달하면 가장 오래 사용되지 않은 청크를 evict해 공간을 만듭니다. 상한 전에 NVMe가 가득 차면 쓰기 실패 후 같은 방식으로 evict하고 다시 시도합니다.

사전 준비 사항

서버 환경에 lmcache-rbln 설치

KV 캐시 오프로딩에는 lmcache-rbln이 필요하며, 기본 vllm-rbln 설치에는 포함되지 않습니다. vllm serve를 실행하는 동일한 Python 환경에 kv_connectors extra를 설치해야 합니다. 필요한 upstream lmcache 의존성도 함께 설치됩니다. 설치 방법은 기능별 추가 패키지에 정리되어 있습니다.

해당 환경에서 패키지를 확인합니다.

python -m pip show vllm-rbln lmcache-rbln lmcache torch-rbln rebel-compiler
python -m pip check

Connector는 vllm model implementation에서 동작하므로 서버를 --model-impl vllm으로 기동합니다. 이 구현이 KV 캐시를 디바이스 텐서로 두며, connector가 지원하는 방식은 이것뿐입니다. Connector는 캐시를 device='rbln' 위의 torch 텐서로 전송합니다.

  • 실행이 vllm model implementation으로 결정되지 않으면 RBLN vLLM이 RBLNLMCacheConnectorV1을 등록하는 모듈을 임포트하지 않으므로, 엔진 기동이 connector 이름 검증 단계에서 Unsupported connector type: RBLNLMCacheConnectorV1로 실패합니다. auto는 optimum-rbln이 지원하는 아키텍처에 transformers를 고르므로, vLLM 구현을 확실히 사용하려면 --model-impl vllm을 지정해야 합니다.
  • PYTHONHASHSEED는 고정된 값으로 설정해야 합니다. 설정하지 않으면 vLLM이 블록 해시 체인의 기준값을 프로세스별 난수로 만들어, 스케줄러와 워커 사이에서 해시 체인이 갈라지고 모든 캐시 조회가 miss합니다. Connector는 조용히 실패하는 대신 생성 시점에 RuntimeError로 거부합니다. vLLM은 이 변수의 문자열을 해시해 체인의 시드로 쓰므로, 고정된 값이면 무엇이든 동작합니다.

RDS 디바이스 가용성

RDS 오프로딩에는 호스트에서 RDS용으로 바인딩된 NVMe 디바이스가 필요합니다. systemd 호스트에서는 바인딩 서비스와 부팅 로그를 확인합니다.

systemctl status rebellions-rds-binding.service --no-pager
journalctl -u rebellions-rds-binding.service -b --no-pager

바인딩 서비스는 oneshot이므로 active (exited)도 정상 상태일 수 있습니다. 컨테이너를 사용한다면 서비스는 호스트에서 확인해야 합니다. 이후 서버를 실행할 Python 환경에서 런타임의 디바이스 가시성을 확인합니다.

python -c 'from rebel import rds; print("RDS available:", rds.is_available()); print("RDS devices:", rds.query_available_device_count())'

RDS available: True와 0보다 큰 디바이스 수가 출력되어야 합니다. 이 점검은 런타임 가시성을 확인합니다. 서버 기동 후에는 오프로딩 동작 확인 절차로 쓰기와 복원도 확인할 수 있습니다.

설정

캐시는 LMCache의 LMCACHE_* 환경 변수로 설정하고, connector는 --kv-transfer-config로 선택합니다. 저장 계층을 고르고 아래 설정을 확인한 뒤, 서버 기동의 전체 블록을 복사해 실행합니다.

CPU 오프로딩

CPU 오프로딩은 LMCache의 기본 백엔드인 LocalCPUBackend를 통해 KV 청크를 호스트 메모리에 보관합니다. 추가 하드웨어가 필요하지 않으며, 사용 방법은 upstream LMCache와 동일합니다.

Variable Description Default
LMCACHE_LOCAL_CPU 호스트 메모리 백엔드를 활성화합니다. True
LMCACHE_MAX_LOCAL_CPU_SIZE 호스트 메모리 풀 크기(GB)입니다. 5.0
LMCACHE_CHUNK_SIZE KV 청크당 토큰 수입니다. 부분 청크 저장이 비활성화된 경우, 프리픽스가 한 청크를 채워야 저장됩니다. 짧은 프리픽스에서는 hit이 발생하지 않습니다. 256

Tip

LMCACHE_CHUNK_SIZE는 --block-size와 같은 값으로 설정하는 것을 권장합니다. 청크 하나가 KV 캐시 블록 하나에 대응해 KV 복사 성능이 가장 좋습니다.

LMCache 설정 키 전체는 LMCache 문서에서 확인할 수 있습니다.

SSD 오프로딩(RDS)

RDS 오프로딩은 KV 청크를 NPU 메모리에서 호스트 메모리를 거치지 않고 NVMe에 기록해 보관합니다. 먼저 CPU 오프로딩으로 서버가 동작하는지 확인한 뒤 이 계층으로 전환하는 것을 권장합니다.

Info

RDS는 호스트에 바인딩된 NVMe RDS 디바이스가 있어야 동작합니다. 폴백 경로가 없으므로, 디바이스가 없으면 이 계층을 사용할 수 없습니다.

백엔드는 LMCache의 스토리지 플러그인 launcher를 통해 등록합니다. RDS를 1차 할당자로 만들려면 CPU 계층을 명시적으로 꺼야 합니다. CPU 계층을 켠 채로 두면 호스트 메모리가 1차 계층이 되고 청크가 호스트 메모리를 거쳐 NVMe에 도달하는 하이브리드 구성이 됩니다.

Key Value Description
storage_plugin.RDSBackend.module_path lmcache_rbln.storage_plugin.rds_backend 백엔드 클래스가 있는 모듈입니다.
storage_plugin.RDSBackend.class_name RDSBackend 백엔드 클래스 이름입니다.
rds_buffer_size 2048 NVMe 용량이 아닌 디바이스 메모리 스테이징 예산(MiB)입니다. 기본값은 2048이며, <= 0이면 스테이징 sub-batching을 비활성화합니다.
rds_failure_policy ignore ignore는 best-effort 동작을 유지하며, fail은 복구되지 않는 RDS 오류를 serving worker에 전달합니다. 기본값은 ignore입니다.
rds_max_chunks 1000 NVMe에 저장하는 청크 수의 상한입니다. 저장된 청크 1개는 캐시에 남아 있는 동안 NVMe 한 개당 2 MiB의 NPU 디바이스 메모리를 차지합니다. 따라서 기본값에서는 청크가 저장되면서 NVMe 한 개당 최대 2 GiB의 디바이스 메모리를 동적으로 차지할 수 있습니다. 이 메모리는 KV 캐시 크기를 정할 때 미리 확보되지 않습니다. 0이면 상한이 없습니다. 기본값은 1000입니다.

Warning

스테이징 메모리는 필요할 때 할당됩니다. 예산을 설정해도 NPU 메모리가 예약되거나 저장 성공이 보장되지는 않습니다. 기본 rds_failure_policy=ignore에서는 실패한 쓰기 배치를 버리고 서빙을 계속할 수 있으며, 백엔드가 자동으로 비활성화되지는 않습니다. fail에서는 용량 부족 이외의 쓰기 오류와 읽기 오류를 worker에 전달합니다. 인식 가능한 쓰기 용량 오류는 캐시 청크를 evict하고 재시도할 수 있습니다. 첫 요청 후 로그를 확인하는 것을 권장합니다.

RDS 사용 시 디바이스 메모리 여유

RDS는 KV 캐시 밖의 NPU 디바이스 메모리를 사용합니다. 스테이징 버퍼(rds_buffer_size)와, 저장된 청크 1개당 NVMe 한 개당 2 MiB(rds_max_chunks)입니다. 디바이스 메모리 여유가 적으면 rds_max_chunks나 --gpu-memory-utilization을 낮춰야 합니다. Rebel100™에서 RDS를 사용할 때는 rds_max_chunks와 rds_buffer_size를 기본값으로 두고 --gpu-memory-utilization 0.8을 사용하는 것을 권장합니다.

서버 기동

서버 환경을 활성화한 새 셸에서 아래 블록 중 하나를 실행합니다. 다른 LMCACHE_* 설정이 남아 있으면 실행에 영향을 줄 수 있으므로 추가로 설정해서는 안 됩니다. LMCACHE_USE_LAYERWISE=True는 지원하지 않으며 기동 시 거부됩니다.

export PYTHONHASHSEED=0
export LMCACHE_LOCAL_CPU=True
export LMCACHE_MAX_LOCAL_CPU_SIZE=5.0
export LMCACHE_CHUNK_SIZE=1024
export LMCACHE_USE_LAYERWISE=False

vllm serve Qwen/Qwen3-1.7B \
  --model-impl vllm \
  --max-model-len 8192 \
  --block-size 1024 \
  --enable-prefix-caching \
  --max-num-batched-tokens 128 \
  --max-num-seqs 4 \
  --kv-transfer-config \
  '{"kv_connector":"RBLNLMCacheConnectorV1", "kv_role":"kv_both"}'
export PYTHONHASHSEED=0
export LMCACHE_LOCAL_CPU=False
export LMCACHE_MAX_LOCAL_CPU_SIZE=0
export LMCACHE_CHUNK_SIZE=1024
export LMCACHE_USE_LAYERWISE=False
export LMCACHE_STORAGE_PLUGINS=RDSBackend
export LMCACHE_EXTRA_CONFIG='{
    "storage_plugin.RDSBackend.module_path": "lmcache_rbln.storage_plugin.rds_backend",
    "storage_plugin.RDSBackend.class_name": "RDSBackend",
    "rds_buffer_size": 2048,
    "rds_failure_policy": "ignore"
}'

vllm serve Qwen/Qwen3-1.7B \
  --model-impl vllm \
  --max-model-len 8192 \
  --block-size 1024 \
  --enable-prefix-caching \
  --max-num-batched-tokens 128 \
  --max-num-seqs 4 \
  --kv-transfer-config \
  '{"kv_connector":"RBLNLMCacheConnectorV1", "kv_role":"kv_both"}'
Key Value Description
kv_connector RBLNLMCacheConnectorV1 RBLN vLLM이 등록한 connector 이름입니다.
kv_role kv_both 이 인스턴스가 KV 캐시의 저장과 복원을 모두 담당합니다.

오프로딩 동작 확인

기동, 저장, 복원 로그를 먼저 확인합니다.

로그 의미
RDS staging buffer: 2048 MiB 스테이징 예산이 적용되었습니다. 메모리 할당이나 I/O 성공을 의미하지는 않습니다.
RDS _sync_batched_write: issuing N offloads 쓰기 배치가 시작되었습니다. 이후 오류를 확인한 뒤 성공 여부를 판단해야 합니다.
RDS batched_get_blocking: requested=N, hot_cache hits=M 메모리 인덱스에서 요청한 NVMe 엔트리 M개를 찾았습니다. hot_cache는 호스트 KV 데이터 계층이 아닌 인덱스입니다.
RDS batched_get_blocking: issuing N restores 복원 호출을 시작합니다. 이후 오류가 발생하면 조회한 KV를 가져오지 못할 수 있습니다.

복원을 확인하려면 --no-enable-prefix-caching으로 서버를 시작한 뒤, 한 청크보다 긴 같은 프롬프트를 두 번 보냅니다. /metrics의 vllm:external_prefix_cache_hits_total이 첫 요청 전보다 두 번째 요청 후에 커졌으면 두 번째 요청이 오프로드 계층의 KV를 재사용한 것입니다. RDS에서는 로그에 RDS batched write failed 오류 없이 Retrieved N out of N required tokens가 출력되는지도 확인합니다. 확인이 끝나면 prefix caching을 다시 켭니다. 가져온 토큰이 없을 때의 조치는 문제 해결에서 확인할 수 있습니다.

문제 해결

RDS batched write failed

RDS batched write failed for 1 keys

이 메시지는 배치 쓰기 실패를 나타냅니다. 뒤에 출력되는 traceback에서 원인을 확인합니다. 실패한 쓰기는 RDS 인덱스에 새 엔트리로 등록되지 않습니다.

증상 점검 사항
NVMe not enabled 또는 RDS 사용 불가 사전 준비 사항의 호스트 바인딩 서비스, journal, 런타임 디바이스 가시성을 확인합니다. 호스트 설정을 수정한 뒤 서버를 재시작해야 합니다.
스테이징 할당 실패 또는 staging pool exhausted NPU 여유 메모리, KV pool 크기, 청크 크기, 스테이징 예산을 확인합니다. 예산을 늘려도 물리 메모리가 늘어나지는 않으므로 먼저 메모리 압박을 줄여야 합니다.
기동 중 KV pool 할당 실패 워크로드에 맞게 pool 크기를 조정합니다. 자동 산정이 실패하면 --num-gpu-blocks-override로 블록 수를 지정할 수 있습니다. RDS 테스트는 Qwen3-1.7B와 블록 크기 1024에서 32개를 사용하지만, 모든 운영 환경에 적용할 값은 아닙니다.
LRU eviction과 함께 offload 실패 반복 원본 예외를 확인합니다. ignore에서는 용량 부족 외의 오류도 eviction과 재시도를 유발하므로, 이 현상만으로 NVMe가 가득 찼다고 판단할 수 없습니다. rds_failure_policy=fail은 용량 부족 외의 쓰기 오류에 대한 eviction 재시도를 하지 않습니다.

캐시 저장 또는 복원이 없음

서버 환경에 lmcache-rbln이 설치되었는지, --model-impl vllm을 선택했는지, 원하는 백엔드가 로드되었는지 확인합니다. 적용된 청크 크기와 프롬프트 토큰 수를 확인합니다. RDS에서 디바이스 스테이징을 1차 할당자로 사용하려면 LMCACHE_LOCAL_CPU=False와 LMCACHE_MAX_LOCAL_CPU_SIZE=0을 유지해야 합니다.

반복 요청은 NPU prefix caching이나 sub-block caching만으로 처리될 수 있습니다. 외부 조회만 따로 확인하는 방법은 오프로딩 동작 확인에 정리되어 있습니다. 서버를 재시작하면 RDS 엔트리가 사라지므로 첫 요청으로 캐시를 다시 채워야 합니다.

제약 사항

  • Sub-block prefix caching과 KV connector는 둘 다 full-block prefix cache hit을 연장하려 하므로, 스케줄러는 요청마다 둘 중 하나만 사용합니다. 더 많은 토큰을 확보하는 쪽이 선택되고, 같으면 sub-block이 우선합니다. 자세한 내용은 Prefix Caching에서 확인할 수 있습니다.