LMCache MP 모드 vs Non-MP 모드
[!tldr] 업무 관점 takeaway 우리(FDP plugin)는 MP 모드의
L2AdapterInterface만 구현하면 된다. Non-MP legacy는 건드릴 필요 없음. 다만raw_block의 "공유 core 패턴" (Core 위에 두 wrapper를 얹는 구조)은 베껴 쓸 가치 있음 — 나중에 Non-MP 호환 요구가 생기면 wrapper만 추가하면 되는 구조.
한 줄 정의
| 모드 | 한 줄 |
|---|---|
| MP (Multi-Process) | LMCache가 **별도 프로세스(서버)**로 떠 있고, vLLM들이 ZMQ로 접속해 캐시 요청을 보내는 형태 |
| Non-MP | LMCache가 vLLM 프로세스 안 라이브러리로 동작 (vLLM이 import lmcache해서 직접 호출) |
현재 권장 = MP 모드. Non-MP는 legacy 호환 + 단일 프로세스 시나리오용으로 남아 있다.
왜 두 모드가 존재하는가
LMCache는 처음에 vLLM 안 라이브러리(Non-MP)로 시작했다. 다음 문제가 드러나 MP 모드가 도입됐다:
- vLLM 추론 스레드와 LMCache의 Python 작업(해싱, 메모리 관리, L2 I/O)이 같은 프로세스 안에서 GIL 경쟁 → 추론 latency 영향
- 노드 위 vLLM pod가 여러 개일 때 L1 캐시를 공유 못 함 (각자 자기 프로세스 안에 들고 있음)
- LMCache 쪽 버그가 나면 vLLM 프로세스가 같이 죽음
- CPU 메모리(캐시)와 GPU 메모리(추론) 자원 스케일이 묶여 있음
프로세스 / 통신 구조 비교
Non-MP (legacy, vLLM 임베드)
┌────────────────────────── vLLM 프로세스 ──────────────────────────┐
│ vLLM 추론 코드 │
│ │ Python 함수 호출 (in-process) │
│ ▼ │
│ LMCacheEngine │
│ ├─ L1 (CPU memory) │
│ └─ StorageBackendInterface │
│ └─ StoragePluginInterface (Mooncake, S3, raw_block …) │
└──────────────────────────────────────────────────────────────────┘
│
▼ (옵션) Local SSD / Remote KV store
- 통신: 함수 호출 (같은 Python 프로세스)
- 진입 인터페이스:
StoragePluginInterface←StorageBackendInterface - 비동기 모델: asyncio event loop (vLLM이 깔아 둠 → plugin은
run_coroutine_threadsafe)
MP (현재 권장, 별도 서버)
┌── vLLM Pod 1 ──┐ ┌── vLLM Pod 2 ──┐ ┌── vLLM Pod N ──┐
│ vLLM client │ │ vLLM client │ │ vLLM client │
└────────┬───────┘ └────────┬───────┘ └────────┬───────┘
└─────────── ZMQ (DEALER/ROUTER, tcp) ───┘
│
▼
┌────────────────────────────────────────────┐
│ lmcache server (별도 프로세스) │
│ MessageQueueServer (mq.py) │
│ MPCacheEngine (server.py) │
│ StorageManager (distributed/) │
│ ├─ L1Manager (CPU memory + TTL lock) │
│ ├─ StoreController │
│ ├─ PrefetchController │
│ └─ EvictionController │
│ L2AdapterInterface (raw_block, s3 …) │
└────────────────────────────────────────────┘
│
▼ Local SSD / Remote KV store
- 통신: ZMQ DEALER/ROUTER (tcp). RequestType enum으로 명령 dispatch.
- 진입 인터페이스:
L2AdapterInterface(lmcache/v1/distributed/l2_adapters/base.py) - 비동기 모델: eventfd 3개 + select.poll (controller가 직접 관리)
인터페이스 비교 (★ FDP plugin 구현 관점)
| 항목 | Non-MP | MP |
|---|---|---|
| 추상 클래스 | StoragePluginInterface | L2AdapterInterface |
| 위치 | v1/storage_backend/abstract_backend.py:424 | v1/distributed/l2_adapters/base.py |
| 메서드 형식 | async coroutine 위주 + prefix-only get/contains | submit_* (non-blocking) + pop_* / query_* + 3개 eventfd |
| 비동기 트리거 | asyncio loop (vLLM 제공) | eventfd → controller select.poll |
| 등록 방법 | extra_config.storage_plugin.<name> (yaml) | --l2-adapter JSON + register_l2_adapter_* |
| 동시성 | asyncio 단일 스레드 | StoreController + PrefetchController 동시 호출 → thread-safe 필수 |
| 락 책임 | 없음 | L2-side lock refcount 직접 구현 (lookup-and-lock → load → unlock) |
공유 Core 패턴 (raw_block이 모범 사례)
같은 디바이스를 두 모드 다 지원하려는 표준 패턴 — Core 하나 위에 두 wrapper를 얹는다.
RawBlockCore
(슬롯 할당 / 인덱스 / 체크포인트 / lock refcount)
▲ ▲
│ │
RustRawBlockBackend RawBlockL2Adapter
(StoragePluginInterface) (L2AdapterInterface)
= Non-MP wrapper = MP wrapper
prefix-only get/contains non-blocking + eventfd
asyncio.run_coroutine_.. ThreadPoolExecutor 3개
FDP plugin 설계 시 이 패턴을 베껴 쓸 것. Phase 0에서는 MP wrapper만 만들고, Non-MP 호환이 필요해지면 Core만 공유해서 Non-MP wrapper 추가.
FDP Plugin 구현 시 체크리스트
MP만 짠다 (L2AdapterInterface + plugin 또는 native_plugin 타입).
신경 써야 할 것:
- eventfd 3개 (store/lookup/load) —
lmcache.v1.platform.create_event_notifier()로 생성 submit_*은 non-blocking, 결과는pop_*/query_*로 따로 회수- thread-safe (StoreController + PrefetchController 동시 호출)
- L2-side lock refcount (
lookup_and_lock→load→unlock)
필요 없는 것:
- asyncio event loop (framework가 안 깔아 줌)
- Non-MP의 prefix-only get/contains 인터페이스
TP > 1 주의: MP는 현재 per_tp_device_paths 거부 → TP별 디바이스 분산이 필요하면 MP 쪽 별도 PR이 선행돼야 함. Phase 0에서 우리는 단일 디바이스로 시작.
핵심 차이 요약
| 항목 | Non-MP (legacy) | MP (권장) |
|---|---|---|
| 프로세스 | vLLM 안 임베드 | 별도 서버 (lmcache server) |
| 통신 | in-process 함수 호출 | ZMQ tcp (DEALER/ROUTER) |
| L1 캐시 공유 | pod별 따로 | 노드 안 모든 pod가 공유 |
| GIL 경쟁 | 있음 | 없음 (다른 프로세스) |
| 추상 클래스 | StoragePluginInterface | L2AdapterInterface |
| 비동기 모델 | asyncio | eventfd + poll |
per_tp_device_paths | 지원 | 거부 (raw_block_l2_adapter.py:147-150) |
| 향후 방향 | 유지보수 모드 | 활성 개발 |
관련 페이지
- [[LMCache-아키텍처]] — 2-mode 구조 전체 그림
- [[L2-어댑터]] — MP 전용 L2AdapterInterface 상세 계약
- [[Plugin-Pipeline]] — plugin 타입으로 FDP 로직 외부 주입
- [[raw_block-종단-분석]] — raw_block 전계층, FDP 삽입 지점 H1-H8
- [[raw_block-개선-Task]] — M1/M2(io_uring), S1(eviction) 착수 조건
- [[Issue-3394-NonMP-Eviction]] — Non-MP eviction 부재 이슈 (S1)
- [[LMCache-동시성-비동기-기초]] — 두 모드의 완료 알림(Future vs eventfd) 상세