Qwen3.8-Flash-Next의 KV 캐시 대부분을 RAM으로 오프로드하여 디코드 속도 저하 없이 사용하기
You can offload most of Qwen3.8-Flash-Next's KV cache to RAM with little decode slowdown
핵심 요약
Qwen3.8-Flash-Next 모델의 KV 캐시를 시스템 RAM으로 오프로드하여 VRAM 부족 문제를 해결하고 긴 컨텍스트를 효율적으로 처리하는 방법입니다.
- KV 캐시 오프로드 — 모델의 KV 캐시 대부분을 시스템 RAM에 저장하여 VRAM 사용량을 획기적으로 줄임
- 디코드 성능 최적화 — QSA 레이어의 인덱싱 방식을 통해 컨텍스트 길이에 따른 디코드 속도 저하를 방지함
- vLLM 패치 적용 — 작성자가 직접 구현한 vLLM 패치를 통해 3개의 3090 GPU로 1M 컨텍스트 처리 가능
- 성능 수치 — 248k 컨텍스트에서 3,701 tok/s의 프리필 속도와 4개 동시 요청 시 150 tok/s의 처리량 달성
qwen4exp 기반 모델이면 다 될 거다. 어차피 Qwen 차기 로컬 모델들도 이걸 기반으로 나올 테니까. VRAM에 간신히 들어가는 양자화 모델 써도 KV 캐시 양자화 없이 모델 최대 컨텍스트 길이까지 다 돌릴 수 있음. KV 캐시 대부분은 시스템 RAM에 박아두면 되거든.
나 실제로 vLLM에서 구현해서 3090 3장으로 1M 컨텍스트 돌리는 중임. 짧은 컨텍스트에선 초당 80토큰 정도 나오다가, QSA가 2048 토큰 예산 다 채우면 초당 60토큰 정도로 떨어짐. 그 뒤로는 컨텍스트가 아무리 늘어나도 디코드 속도 일정하게 유지된다. 처리량도 꽤 괜찮아서 동시 요청 4개 들어와도 초당 150토큰 정도 뽑아줌. 248k 프리필(prefill) 속도는 초당 3,701토큰 찍힌다. (관심 있으면 내 허깅페이스 페이지에 패치랑 모델 올려놨으니 확인해 봐)
디코드 속도는 결국 대역폭 문제임. 디코드 한 단계마다 토큰 하나를 만드는데, 이때 GPU가 가중치랑 어텐션 상태를 전부 다 읽어와야 하거든. 단일 스트림으로 돌리면 GPU는 연산보다 메모리 기다리는 데 시간을 다 씀. 그래서 단계마다 읽어오는 데이터 크기가 곧 토큰 속도를 결정하는 거다.
보통 모델들이 KV 캐시를 VRAM에 처박아두는 이유가 이거임. Qwen3-Next 아키텍처 기반인 Qwen3.8-27B를 예로 들어보자. 얘는 Qwen3.8-Flash-Next(qwen4_exp)랑 특성을 거의 공유하는데, 몇 층마다 한 번씩 풀 어텐션 레이어가 껴있음. 이 풀 어텐션 레이어는 매 단계마다 KV 캐시 전체를 다 읽어야 해. 컨텍스트가 길어질수록 읽어야 할 데이터도 늘어나니까 대화가 길어질수록 디코딩이 느려지는 거지. 게다가 PCIe 같은 호스트 링크 대역폭을 넘어서는 수준까지 커지니까, 캐시를 연산 장치 옆에 붙여둘 수밖에 없는 거임.
이 모델 수치 보면 문제가 얼마나 심각한지 딱 보임. QSA 레이어 하나가 256 차원의 KV 헤드 2개를 K, V로 각각 2바이트씩 들고 있는데, 토큰당 2,048바이트임. 262,144 토큰이면 레이어 하나당 512 MiB고, 12개 레이어 다 합치면 매 단계마다 6 GiB를 읽어야 함. PCIe 4.0 x16 슬롯 대역폭이 초당 32 GiB 정도니까, 호스트에 캐시를 두면 초당 5토큰 정도밖에 안 나오는 거임.
근데 여기서 재밌는 점이 있음. Qwen3.8-Flash-Next는 이걸 두 가지 방식으로 피함.
전체 48개 레이어 중 12개만 KV 캐시를 씀. 나머지 36개는 게이트 델타넷(gated delta-net) 레이어인데, 이건 고정된 크기의 순환 상태를 가진 선형 어텐션임. 컨텍스트가 늘어나도 상태 크기가 안 커짐.
그 12개 레이어도 컨텍스트 전체를 다 보지 않음. QSA는 풀링되고 압축된 키 위에서 가벼운 인덱서를 돌리는데, indexer_head_dim=128을 indexer_compress_ratio=4로 나눈 값이 풀링된 너비가 됨. 인덱서는 최대 indexer_budget=2048개의 위치만 골라냄. 레이어는 인덱서가 고른 위치의 메인 KV 행만 읽어오는 식임.
결국 indexer_budget이 디코드 단계에서 읽는 바이트 수를 제한하니까, 컨텍스트 길이는 상관이 없게 되는 거임.
2048 선택됨 x 2 kv 헤드 x 256 차원 x 2 (K 및 V) x 2 B = 레이어당 4 MiB
x 12 레이어 = 토큰당 48 MiB
예를 들어 초당 80토큰이면 링크 대역폭을 초당 3.9 GB 정도 쓴다는 건데, PCIe 4.0 x16 슬롯 대역폭의 아주 일부일 뿐이고 그마저도 연산이랑 겹쳐서 처리됨.
GPU에 남겨둬야 할 건 별로 없음. 모델 본체랑, 2바이트 슬롯, 그리고 풀링된 인덱스 키(1 x (128 / 4) x 2 B = 64 B) 정도임. 다 합쳐도 레이어당 토큰 하나에 66바이트 수준인데, 전체 행을 다 읽을 때 필요한 2,048바이트랑 비교하면 엄청나게 줄어든 거지.

