LMCache Local Disk Backend
[!tldr] 업무 관점 takeaway 우리 미션의 정확한 코드 위치. "청크 하나 =
.pt파일 하나"로 NVMe에 떨어진다 → small/medium file이 폭주 → [[WAF]] 증가 주범. PUT은 4-worker thread pool로 async를 흉내내는 sync I/O다 (진짜 async 아님).use_odirect=True옵션은 있지만 alignment 보장이 코드 레벨에서 검증되지 않았다. 여기를 io_uring + FDP-aware로 갈아엎는 게 [[기여-포인트-맵|기여 포인트 [1][2][8][9]]].
1. 무엇을 하는가
CPU DRAM에서 evict된 KV Cache 청크를 로컬 NVMe에 영속적으로 저장하는 [[LMCache-아키텍처|티어 3]]. CPU RAM과 Local Storage가 같은 머신에 있을 때의 오프로드 경로.
2. 설정 (핵심 옵션)
LMCACHE_CHUNK_SIZE=256 # KV Cache 청크 단위 (토큰 수)
LMCACHE_LOCAL_DISK="file:///mnt/nvme0/kvcache" # 디스크 경로
LMCACHE_MAX_LOCAL_DISK_SIZE=5.0 # 최대 사용 용량 (GB)
LMCACHE_EXTRA_CONFIG='{"use_odirect": True}' # Page Cache 우회
[!important]
use_odirect가 우리 실험에 결정적 [[O_DIRECT]]를 켜야 Page Cache를 거치지 않고 NVMe에 직접 I/O가 나간다. 이렇게 해야 FDP 효과가 실제 SSD에 그대로 보인다. 단, alignment 조건 깨지면 silently fallback → 코드 레벨 검증이 [[기여-포인트-맵|기여 포인트 [5]]]에 해당.
3. Multi-Path (per-GPU NVMe 분리)
cuda:0 → /mnt/nvme0/kvcache/
cuda:1 → /mnt/nvme1/kvcache/
local_disk_path_sharding="by_gpu" 옵션. device_id % num_paths로 매핑.
멀티 GPU FDP 검증 시 변수 통제용으로 유용: GPU별 I/O 격리하면 FDP 스트림 분석이 깔끔.
4. 동작 방식 — "청크 하나 = 파일 하나" (⚠️ 핵심)
CPU RAM 오프로딩과 달리 공간 미선점 X. 청크 저장 시마다 .pt 파일 1개 생성. 용량 초과 시 LRU evict (파일 삭제).
PUT (write): 청크 1개 → .pt 파일 1개 생성
GET (read): .pt 파일 1개 → CPU 버퍼로 읽음
EVICT: .pt 파일 1개 삭제
[!warning] FDP 설계 직접 영향 수천 개의 medium file이 NVMe에서 끊임없이 생기고 지워짐 → 서로 다른 lifetime이 같은 NAND block에 섞임 → [[Garbage-Collection|GC]] 폭주 → [[WAF]] 증가. FDP RUH로 hot/cold 파일을 다른 RU에 배치하면 이 패턴이 직접 완화된다. ([[NVMe-FDP]] 참조)
PUT/GET/Prefetch 의미
| 오퍼레이션 | 방식 | 함의 |
|---|---|---|
put() (write) | 비동기 | 추론 블로킹 없음, burst write 가능 |
get() (read) | 블로킹 | 읽을 땐 기다림 → [[TTFT-ITL|TTFT]]에 직결 |
prefetch() | 비동기 | 디스크 → CPU RAM (디스크 데이터는 유지) |
[!note] read가 blocking이 TTFT에 직결 NVMe read latency가 곧 warm TTFT다. FDP가 GC 부담을 줄여 read p99 안정화 → TTFT 직접 개선.
5. 내부 아키텍처 — 4 컴포넌트
LocalDiskBackend
├── Metadata Dictionary
│ CacheEngineKey → (file path, size, shape, dtype, pin)
├── Cache Policy
│ LRU / LFU / FIFO / MRU
├── LocalDiskWorker ← ⚠️ 주목
│ 4-worker thread pool, priority queue
└── Local Disk
.pt 파일 저장소
Worker priority queue (흥미로운 설계 철학)
| Priority | 작업 |
|---|---|
| 0 (최우선) | PREFETCH |
| 1 | DELETE |
| 2 (최하위) | PUT |
[!note] 의외의 통찰 LMCache는 "빨리 읽는 게 빨리 쓰는 것보다 중요" 라는 철학. TTFT 최소화가 최우선이니 당연. → 우리도 FDP 스트림 우선순위/QoS 설계 시 같은 원칙 적용 가능. ([[기여-포인트-맵|기여 포인트 [9]]])
6. 핵심 한계 — "진짜 async I/O가 아니다"
Async Loading Layer
└── batched_get_non_blocking() ← non-blocking 요청만 던짐
│
└── LocalDiskBackend.get() ← 실제 파일 읽기 (blocking)
│ 별도 스레드(LocalDiskWorker)에서 실행
└── .pt 파일 read 되니까 호출자는 안 기다림
LocalDiskBackend.get() 자체는 여전히 blocking syscall. 단지 별도 스레드에서 돌릴 뿐.
[!important] [[io_uring]] 도입 시 효과
- 스레드 컨텍스트 스위치 제거
- syscall 한 번에 다수 I/O batch
- FDP placement hint를 io_uring_cmd로 직접 전달 가능 ([[NVMe-FDP|FDP]] passthru 경로)
- 스레드 풀 자체 불필요해질 수 있음
7. 실측 예시 (LMCache 문서 — 15,376 토큰 프롬프트)
- Cold TTFT (캐시 없음): 6.314초
- Warm TTFT (캐시 있음): 0.148초
- 42.6× 빠름
우리 질문: FDP/HC-SSD로 0.148초를 더 줄일 수 있는가?
8. I/O 경로 추적 — Storage Stack 관점
LMCache LocalDiskBackend
│ .pt 파일 read/write (torch.save/load)
│ ⚠️ 정확한 syscall 종류 (read/pread/io_uring) — 코드 레벨 확인 필요
▼
File System (ext4 / xfs / f2fs) ← [[기여-포인트-맵|포인트 [4]]]
│ VFS Layer
▼
Block Layer (I/O Scheduler) ← [[기여-포인트-맵|포인트 [6][7]]]
│ bio 요청
▼
NVMe Driver ← [[기여-포인트-맵|포인트 [8]]]
▼
NVMe SSD (FDP/HC-SSD) ← [[기여-포인트-맵|포인트 [9][10][11]]]
9. FDP 관점 핵심 요약
| 발견 | FDP 적용 포인트 |
|---|---|
| 청크 하나 = 파일 하나 | medium file write 패턴 → FDP 스트림 분리 효과 큼 |
O_DIRECT 옵션 존재 | Page Cache 우회 → 실험 정확도 ↑ |
| GET은 blocking | read 레이턴시 개선이 TTFT에 직접 |
| PUT은 async (burst) | write 집중 → [[WAF]] 개선 여지 |
| LRU evict = 파일 삭제 | delete + write 반복 → GC pressure |
| 256-token chunk size | NVMe optimal I/O size와 정렬 가능성 검토 |
관련 페이지
- [[LMCache-아키텍처]] — 상위 페이지
- [[LMCache-Async-Loading]] — 이 백엔드를 비동기로 부르는 상위 레이어
- [[O_DIRECT]] —
use_odirect옵션 이해 - [[io_uring]] — 진짜 async I/O로 교체할 후보
- [[NVMe-FDP]] — RUH/RU 매핑 설계 대상
- [[기여-포인트-맵]] — [1][2][3][5][8][9] 포인트가 이 페이지의 코드를 건드린다