본문으로 건너뛰기

S2: raw_block checkpoint payload overflow

대상: lmcache/v1/storage_backend/raw_block/core.py 영향: MP / non-MP 공통 (RawBlockCore 공유) 심각도: 잠복형 스케일 버그 — 대용량 SSD 프로덕션 배포 전 수정 필요


TL;DR

raw_block은 재시작 복구를 위해 키→슬롯 인덱스를 64 MB JSON으로 디바이스에 백업한다. 엔트리가 ~32만 개(≈ 82M distinct 토큰)를 초과하면 백업이 silent fail하고, 다음 재시작 시 캐시 전량이 유실된다. 3.84 TB 이상 디바이스 + 70B/480B 모델 조합에서 디바이스가 가득 차면 반드시 도달한다.


1. 버그 메커니즘

디바이스 레이아웃 (core.py:975)

┌────────────────────────┬──────────────────────────────────────┐
│ ① 메타데이터 영역 │ ② 데이터 영역 │
│ (offset 0 ~ 128 MB) │ (128 MB ~ 디바이스 끝) │
│ key→offset 인덱스 │ 고정 크기 slot_bytes 슬롯 배열 │
└────────────────────────┴──────────────────────────────────────┘

런타임 인덱스는 RAM에만 존재 (core.py:241)

self._index: dict[str, _Entry] = {} # 키 → _Entry(offset, size, meta)

휘발성. 재시작하면 증발 → 데이터 영역 KV가 물리적으로 멀쩡해도 슬롯↔키 매핑 유실.

Checkpoint = 인덱스를 디바이스에 직렬화 (core.py:1200, 1102)

snapshot, _ = self._snapshot_state() # _index → dict
payload = json.dumps(snapshot, ...).encode() # → JSON bytes
return self._write_checkpoint(payload, ...) # → 메타 영역에 기록

엔트리 1개 JSON ≈ 209 B (dense, cached_positions=None):

"model@0@<64자 해시>": {"offset":,"size":,"shape":[],"dtype":"bfloat16","fmt":"…","cached_positions":null}

payload_cap: 64 MB 천장 (core.py:1032, 231)

메타 영역은 2벌 미러링(_meta_copy_count=2), 한 벌당 용량:

_meta_container_bytes = (meta_total_bytes // 2 // block_align) * block_align
payload_cap = _meta_container_bytes - block_align
# 기본값(128 MB, block_align=4096) → payload_cap ≈ 64 MB

천장 초과 시 silent skip (core.py:1164)

if len(payload) > payload_cap:
logger.warning(
"RawBlockCore metadata payload too large (%d > %d), skipping checkpoint",
len(payload), payload_cap,
)
return False # 예외 없음, WARNING 한 줄, 메타 영역 미기록

복구 경로는 checkpoint가 유일

  • _validate_loaded_entries — checkpoint에서 로드된 _index검증만 함. 슬롯 스캔으로 인덱스를 재구축하는 경로 없음.
  • 슬롯 헤더(_encode_header)에는 slot_identity(8 B 해시) + payload_len만 있고 원본 키 문자열 없음 → 헤더만으로 키 복원 불가.
  • ∴ checkpoint JSON이 키→offset 매핑의 유일한 영구 저장소. fallback 없음.

실패 시나리오

인덱스 누적 → JSON > 64 MB → checkpoint 매번 silent skip
→ 메타 영역은 이전 상태(또는 meta_seq=0 빈 상태)
→ 운영자 인지 불가 (WARNING 한 줄, 알림/예외 없음)
→ 재시작/크래시 → RAM의 _index 증발
→ 메타 영역에서 복구 → 비었거나 구버전 → KV 전부 미아
→ 캐시 전량 유실 (물리 데이터는 디스크에 있으나 키를 모름)

2. Overflow 임계점

  • 임계 엔트리 수: 64 MB / 209 B ≈ 321,000
  • 임계 토큰 수: ≈ 82 M distinct KV (chunk_size=256 기준)
  • sparse(cached_positions 있음) 엔트리는 1개 ≈ 1.2 KB → 훨씬 빨리 도달

slot_bytes 주의: non-layerwise 기본에서 slot_bytes = full_chunk_bytes이며 full_chunk_bytes는 num_layers × chunk_tokens × hidden_dim × dtype_bytes에서 파생. TP가 높을수록(KV head 분산) slot 작아짐 → 같은 디바이스에 엔트리 더 많이 쌓임.

설정slot_bytesoverflow 임계 (디바이스 가득 찰 때)
non-layerwise — Llama-3-8B TP=132 MB9.8 TB
non-layerwise — Llama-3-70B TP=810 MB3.1 TB
non-layerwise — Qwen3-480B TP=87.75 MB2.4 TB
layerwise=True~1 MB314 GB
sparse (cached_positions 있음)10 MB0.5 TB

디바이스 크기별 실제 overflow 여부 (Qwen3-480B TP=8 기준)

디바이스max 엔트리JSONoverflow
1.92 TB260 K52 MB간당간당
3.84 TB520 K104 MBYES
7.68 TB1.04 M207 MBYES
15 TB2.08 M414 MBYES
30 TB4.16 M828 MBYES

핵심: HC SSD 전용 문제가 아님. 큰 모델(높은 TP로 slot 작아짐) + 3.84 TB 이상 표준 SSD에서도 발생.


3. 재현

_FakeRawBlockDevice(메모리 bytearray)로 실제 디바이스 없이 동일 메커니즘 재현. payload_cap을 4 KB로 축소:

payload_cap = 4096 bytes
저장 성공: 60/60 entries, _index 크기 = 60 ← 데이터 정상 기록
checkpoint payload 크기 = 10808 bytes (cap 4096)
→ overflow? True
_checkpoint_once(force=True) 반환값 = False ← silent skip
meta_seq = 0 ← 한 번도 기록 안 됨
--- 재시작 (같은 디바이스 재오픈) ---
복구된 _index 크기 = 0 ← 60개 전부 유실

로그 (WARNING 1줄, 예외 없음):

RawBlockCore metadata payload too large (10808 > 4096), skipping checkpoint (core.py:1165)

버그는 디바이스 물리 크기가 아닌 엔트리 개수로 트리거됨 → 8 KB 가짜 디바이스로 15 TB SSD 상황을 동일 재현 가능.


4. 영향 범위

MP / non-MP 공통 — 단 기본값 다름

S2는 공유 RawBlockCore에 있어 두 모드 모두 영향받지만, meta_total_bytes 기본값이 다르다.

모드기본값 위치meta_total_bytespayload_capoverflow 임계 엔트리
non-MPrust_raw_block_backend.py:234128 MB64 MB~321K
MP (L2 adapter)raw_block_l2_adapter.py:78256 MB128 MB~642K

MP가 2배 높아 임계점이 늦지만, 7.68 TB 이상 디바이스에서는 두 모드 모두 overflow 도달.

디바이스Qwen3-480B TP=8 엔트리 수MP (256 MB)non-MP (128 MB)
3.84 TB520 K안전overflow
7.68 TB1.04 Moverflowoverflow
15 TB2.08 Moverflowoverflow
모드eviction 주체인덱스 누적
MP외부 SM이 L2 eviction 관장SM 예산 범위 내
non-MP자체 eviction 없음 (→ S1 별도 이슈)max_slots까지 자연 누적

non-MP는 S1(eviction 없음)과 맞물려 인덱스가 max_slots까지 자연 누적 → 대용량 디바이스에서 S2 필연적으로 도달.

왜 아직 안 터졌나

raw_block은 2026-02-04 최초 도입(#2482), 아직 user-facing 문서에 미등재(docs에 Redis/Weka/S3 등만 있음). 현 프로덕션 배포들은 파일시스템 기반 백엔드를 사용 — inode/디렉토리가 인덱스를 대신 관리해 S2가 구조적으로 없음.

"아무도 안 밟음 ≠ 버그 없음" — "이 기능이 아직 그 규모로 배포 안 됨"


5. 수정 방향

단계별 커버리지

단계meta_total_bytesStd SSD 커버HC SSD 커버layerwise
0 현재128 MB
1 (권장, 즉시)512 MB✅ 전부
2a2 GB✅ ~30 TB
2b (바이너리 포맷)128 MB 유지

단계 1 — 지금, 머지 가능성 높음

변경 대상: plugins/rust_raw_block_backend.py:234 — 기본값 한 줄

# before
meta_total_bytes = int(extra.get("rust_raw_block.meta_total_bytes", 128 * 1024 * 1024))
# after
meta_total_bytes = int(extra.get("rust_raw_block.meta_total_bytes", 512 * 1024 * 1024))
payload_cap: 64 MB → 256 MB
임계 엔트리: 321 K → 1.28 M
임계 디바이스 (Qwen3-480B TP=8): 2.4 TB → 9.5 TB
  • Std SSD (1.92/3.84/7.68 TB) 전부 안전 (모든 모델, layerwise 제외)
  • 7.68 TB 디바이스에서 메타 영역 비율 0.025% 잠식 — 사실상 무료
  • 기존 128 MB 메타 영역으로 기록된 디바이스는 _meta_container_bytes가 달라져 재오픈 시 체크포인트를 못 읽음 → meta_version bump + 인덱스 재구축 1회 필요
  • 회귀 테스트: tests/v1/storage_backend/test_rust_raw_block_backend.py 헬퍼(_FakeRawBlockDevice)로 overflow + 복구 경로 커버

단계 2 — 단계 1 머지 후 결정

옵션 2a — 512 MB → 2 GB (총 16x)

  • 임계 디바이스 ≈ 38 TB, HC SSD 30 TB까지 커버 (layerwise 제외)
  • 30 TB에서 메타 영역 비율 0.007% — 사실상 무료

옵션 2b — 바이너리 직렬화 (entry ≈ 209 B → ~30 B, 약 7x)

  • 천장 크기 그대로 헤드룸 7배 확보, layerwise 포함 전 케이스 해결
  • _snapshot_state / _apply_loaded_state 전면 수정 필요, RFC 소요 3-6개월

단계 1 머지 반응 보고 2a(빠르게) vs 2b(장기 본질 해결) 결정.


6. 관련 이슈

  • #1175, #2840 — 사용자 영속성 요구 이슈 (S2가 근본 원인 중 하나)
  • #2482 — raw_block 최초 도입 (2026-02-04)
  • #3119 — MP L2 어댑터 통합 머지 (2026-05-04)