NVMe에서 전문가(experts)를 스트리밍하여 8GB GPU에서 133GB MoE 모델 구동하기 (11 토큰/초)
요약
133GB의 대규모 MoE 모델을 8GB VRAM의 로컬 장비에서 구동하는 방법을 제시합니다. 전문가(experts)를 NVMe 드라이브에서 스트리밍하여 메모리 제약을 극복했으며, 이를 위해 PyTorch와 Triton 엔진 기반의 커스텀 런타임이 구축되었습니다.
핵심 포인트
- MoE 모델은 '모델 크기'보다 '토큰당 필요한 전문가 바이트'가 중요합니다.
- 전문가를 NVMe 드라이브에서 스트리밍하여 VRAM 용량 제약을 극복했습니다.
- 커스텀 엔진을 PyTorch와 Triton으로 구축하고, 디스크 기반의 전문가 재패킹 과정을 거쳤습니다.
저는 집에서 사용하는 한 대의 컴퓨터에 로컬 모델을 구동합니다. 이 장비는 RTX 5060 (8 GB), Core Ultra 5 225F CPU, RAM 31 GiB와 모델 파일 전용으로 사용되는 Gen5 NVMe 드라이브로 구성되어 있습니다. NVIDIA가 NVFP4 형식으로 Qwen3.8-Flash-Next를 발표했을 때(디스크에 133 GB), 당연히 '들어가지 않는다'는 결론이었습니다. 하지만 이제 가능해졌습니다. 이 모델은 Open WebUI에서 일반적인 채팅 모델처럼 9–12 토큰/초로 구동되며, 출력 결과는 Hugging Face의 참조 구현과 일치합니다.
코드: https://github.com/helgard-orlm/qwen-flash-next-8gb
누가 무엇을 했는지 미리 알려드립니다: 제가 목표를 설정하고 모델을 선택했으며 그 과정에서 필요한 호출들을 진행했습니다. 엔진, 커널 및 서버는 제 세션 동안 **Claude (Anthropic)**가 작성했고; CUDA-graph 속도 향상 부분은 병렬 세션에서 **Codex (OpenAI)**가 작성했으며, Claude가 이를 찾아내고 수정했습니다. 여기서 '우리'는 이 세 명을 의미합니다.
본 게시물은 방법론, 측정치, 그리고 실패했던 것들에 대해 다룹니다.
왜 이 모델인가
Mixture-of-experts (MoE) 모델의 가중치 대부분은 전문가(expert)들로 구성되어 있으며, 각 토큰은 그중 몇 개만을 사용합니다. 따라서 질문은 '모델이 얼마나 큰가'가 아니라 **'토큰당 필요한 전문가 바이트가 얼마나 되는가'**입니다.
Qwen3.8-Flash-Next: 48개 레이어, 레이어당 512개 전문가(experts), top-10 라우팅을 사용하며, 각 전문가는 매우 작습니다—2560×640 행렬 세 개로 구성되어 있으며, NVFP4 형식에서 스케일과 함께 2.70 MiB입니다.
10 experts × 48 layers × 2.70 MiB ≈ 1.27 GiB per token
비교하자면, 동일한 장비와 같은 트릭으로 구동하는 DeepSeek-V4.1-Flash는 토큰당 약 4.2 GiB가 필요합니다. 디스크 용량은 같지만 바이트 수는 3분의 1 수준입니다.
나머지 모든 부분(임베딩, 어텐션, 36개 Gated DeltaNet 레이어, 라우터, 공유 전문가, lm_head)은 BF16에서 약 7.2 GB를 차지합니다. 큰 DeltaNet 행렬을 FP8로 저장하고 행별 스케일 하나를 적용하면 메모리 상주 부분(resident part)은 6.13 GiB의 VRAM이 됩니다. 전문가들 63 GiB는 NVMe 드라이브에 보관됩니다.
엔진
이 아키텍처에서 전문가(experts)를 스트리밍할 수 있는 런타임이 없었기 때문에, 이는 처음부터 PyTorch와 Triton 엔진을 사용해 구축되었습니다. 수학적 계산은 transformers 5.18의 qwen4_exp에서 가져왔습니다: DeltaNet, 블록 인덱서가 ~2048 토큰을 넘어서 작동하는 전체 어텐션(full attention), 네 개의 하이퍼 연결 스트림(hyper-connection streams), 그리고 510억 개 매개변수를 가진 n-gram 임베딩 테이블(PLE, 레이어 1 사용).
작동 순서대로 설명하면 다음과 같습니다:
1. 디스크를 위한 전문가 재패킹. 원래 파일에서는 하나의 전문가가 여섯 개의 텐서로 분산되어 있었습니다. 우리는 이를 전문가당 단일 2,764,800바이트 블록(4 KiB 페이지 675개)으로 다시 작성했습니다: gate | up | down | gate_scale | up_scale | down_scale. 전문가 하나 = O_DIRECT를 사용한 pread로 페이지 정렬된 고정 메모리(pinned memory)에 쓰기 = GPU로 복사되는 하나의 작업.
2. 디스크 앞에 RAM 캐시. 고정 메모리에 대한 LRU(Least Recently Used) 방식(16 GB는 모든 전문가의 약 4분의 1을 저장할 수 있음), 리더 스레드 풀, 그리고 다음 레이어가 계산하는 동안 이 레이어에서 계산이 진행되도록 하는 두 개의 GPU 슬롯 은행(banks)을 갖추었습니다.
3. 간격에 미리 가져오기(Prefetch into the gap). 한 레이어의 필요한 읽기가 요청된 후, 다음 레이어의 전문가를 추측하고 그중 두 개를 읽으면서 GPU가 바쁜 동안 처리합니다 (추측한 것 중 91%가 사용됨). 첫 번째 버전에서는 필요한 읽기와 동시에 미리 가져오기를 시작했는데, 이들이 디스크를 공유하여 모든 것이 느려졌습니다. 이는 실제 작업 옆이 아니라 간격(gap)에 들어가야 합니다.
4. 자체 커널(Own kernels). Blackwell 하드웨어의 cvt.rn.f16x2.e2m1x2 명령어를 사용한 단일 토큰용 NVFP4 SwiGLU 전문가 커널(레이어당 115 µs, torch 참조 대비 3.6e-7), 그리고 FP8 행 스케일 매트릭스-벡터 커널을 구현했습니다. FP8 DeltaNet 매트릭스의 일반적인 언패킹 경로는 자체적으로 토큰당 약 106 ms의 비용이 들었습니다.
5. 프롬프트 청크당 66 GB를 읽지 않기. 첫 번째 프롬프트 경로는 한 번에 512개의 토큰을 처리했으며, 각 청크는 거의 모든 전문가가 필요했기 때문에 전체 전문가 세트가 청크마다 다시 읽혔습니다. 전문가를 32개 단위로 배치하여 한 레이어에서 전체 프롬프트를 한 번에 실행했을 때, 71.6초 걸리던 2451 토큰짜리 프롬프트가 23.3초로 단축되었습니다.
6. 필요한 PLE 행만 읽기. n-gram 테이블은 53.7 GB 파일이며, 토큰당 소수의 행이 필요합니다. NVMe에서 무작위 읽기 속도는 토큰당 0.3–0.5 ms입니다. (HDD의 경우, 행마다 디스크 탐색(disk seek)이 발생할 것입니다. 첫 번째 실행에서 이 테이블이 여전히 HDD에 있다는 것을 발견한 두 번째 AI가 이를 지적했습니다.)**
시간이 실제로 소요되는 부분
모든 전문가(expert)가 이미 RAM에 있을 때, 토큰 하나를 처리하는 데 80–88 ms가 걸렸습니다. 분해하면 다음과 같습니다:
- 39 ms — Python이 GPU 작업을 지시하는 시간 (GPU는 Python을 기다리고 있었고, 그 반대는 아니었습니다);
- 47 ms — PCIe를 통해 전문가 1.33 GB를 이동시키는 시간 (측정 결과 28.6 GB/s, PCIe 5.0 ×8);
- 그리고 이 두 과정이 순차적으로 실행되었습니다.
토큰당 52번의 CPU↔GPU 동기화(sync)가 발생합니다 (대부분 라우터가 선택한 전문가를 읽는 작업). 저희는 이것이 문제가 될 것이라고 예상했지만, 측정 결과 비용이 저렴했습니다. 이들이 문제를 일으키는 유일한 이유는 레이어 L의 전문가 사본을 시작할 수 있으려면 레이어 L의 라우터가 먼저 응답해야 하기 때문입니다. 이는 세 가지 변경 사항을 가리켰습니다:
- 추측적 복사(Speculative copies). 레이어 L+1의 라우터를 레이어 L의 MoE 입력에 실행합니다. 이 방법은 10개의 상위 후보 중 65%의 확률로 올바른 것을 선택하며, 대부분의 사본을 한 레이어 일찍 시작할 수 있을 만큼 충분합니다. (히트율을 높이기 위해 top-16을 미리 가져오는 것보다 버스를 통해 전송하는 바이트 양이 더 많았습니다.)
- 36개의 원-토큰 DeltaNet 레이어에 대한 CUDA 그래프(Codex). 공유 메모리 풀 하나: 8 GB 카드에서는 36개의 개별 풀이 메모리 부족을 일으켰습니다. 그래프는 길이가 1024 토큰 이상인 프롬프트가 있을 때까지 사용되지 않았습니다.
- 디코딩 스레드를 P-core에 고정(Pin). 하이브리드 CPU에서 고정되지 않은 스레드는 P 코어와 E 코어 사이를 돌아다녔습니다: 토큰당 93–108 ms, 여기저기 이동했습니다. 코어 0에 고정했을 때: 일정한 81.6 ms였습니다.
결과
| 구성 | tokens/s |
|---|---|
| 첫 번째 버전, 디스크에서 전문가 로드, 캐시 없음 | 2.68 |
| ... |
프롬프트 처리: 4.5초에 24 토큰, 23.3초에 2451 토큰, 98초에 7182 토큰을 처리했습니다. 후속 메시지는 서버가 전체 순환 상태(DeltaNet 행렬 + KV + 인덱서 키)를 RAM에 스냅샷하고 새로운 끝 부분만 처리하기 때문에 빠릅니다.
작동 방식 확인 방법
정확성 없는 속도는 난수 생성기와 같으므로, 모든 변경 사항은 동일한 양자화 가중치(quantized weights)를 공급받은 transformers 참조 모델을 기준으로 확인했습니다:
- next-token argmax: 32/32, 평균 |Δlogit| 0.077;
- 주의 인덱서가 블록 선택을 시작하기에 충분히 긴 2451 토큰 프롬프트: 11/11로, 필요한 정보(needle-in-a-haystack fact)가 정확하게 돌아왔습니다;
- 퍼플렉시티 EN 2.21 / RU 2.22 — 그리고 반드시 실패해야 하는 제어 실행(control runs): nibbles를 교체했을 때 각각 2016 / 33,269로 나타났고, 전문가(experts)를 제거했을 때는 588 / 5,351로 나타났습니다. 이는 참조 모델과 독립적으로 NVFP4 언패킹을 확인합니다.
작동하지 않은 것들
- 융합된 DeltaNet 커널(반복(recurrence)에 대한 단일 커널, 36개 레이어에서 토큰당 4.3 → 0.43 ms). 256개의 토큰 중 5개가 참조 모델과 다르게 나왔습니다. 거부되었습니다 — 4ms는 다른 결과를 내놓는 모델을 감당할 가치가 없습니다.
- 8192 토큰 컨텍스트가 실제로 작동하지 않았습니다, 이전 버전에서도 마찬가지였습니다: 7182 토큰 프롬프트에서 주의(attention) 메모리(VRAM) 부족 현상이 발생했습니다.
piece × (position + piece) ≤ 3072²로 프롬프트 조각 크기를 조정하여 해결했습니다. - 긴 프롬프트 후에만 발생하는 충돌: 기본
global모드의 CUDA 그래프 캡처가 전문가 리더 스레드(expert-reader threads)가event.synchronize()를 호출하면서 무효화되었습니다. `capture_error_mode=
Blackwell GPU(NVFP4 커널은 sm_120a e2m1 명령어를 사용함), 약 130GB의 여유 공간이 있는 빠른 NVMe 드라이브, 그리고 133GB 대부분을 읽어들이는 첫 설정에 대한 인내심이 필요합니다.
더 중요한 점은 다음과 같습니다. 전문가 혼합(mixture-of-experts) 모델에서는 'VRAM에 들어가는가'라는 질문 자체가 잘못된 질문이라는 것입니다. 올바른 질문은 토큰당 바이트 수이며, 빠른 드라이브를 가진 가정용 PC는 겉보기보다 훨씬 많은 양을 처리할 수 있습니다.
엔진, 커널, 서버 및 이 글: Claude (Anthropic). CUDA 그래프와 추측/고정 A/B: Codex (OpenAI). 목표, 모델 선택 및 결정: 나. 모든 숫자는 위에 설명된 장치의 로그에서 가져온 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기