8GB GPU에서 510GB DeepSeek-V4.1-Flash 실행하기와 에러를 발생시키지 않는 세 가지 버그
요약
본 글은 8GB GPU 환경에서 510GB에 달하는 DeepSeek-V4.1-Flash와 같은 대형 로컬 모델을 구동하고 최적화하는 기술적인 과정을 다룹니다. 단순히 저장 공간을 옮기는 것보다, 기존의 참조 추론 코드를 활용하여 가중치만 대체함으로써 효율성을 높이는 방법을 제시합니다. 또한 RTX 50xx GPU에서 발생할 수 있는 세 가지 특정 버그와 해결책을 공유하며 실질적인 개발 지침을 제공합니다.
핵심 포인트
- 8GB VRAM 환경에서도 대형 모델 구동이 가능함을 보여줍니다.
- 모델 재구성은 참조 추론 코드를 활용하여 가중치만 대체하는 방식으로 진행됩니다.
- RTX 50xx GPU에서 발생하는 세 가지 특정 버그와 해결책을 공유합니다.
- 성능 향상에 집중하기보다, 작동 여부와 정확도 유지에 초점을 맞춥니다.
로컬 모델을 위한 제 홈 머신은 소박합니다. RTX 5060 (8 GB), Core Ultra 5 225F, RAM 31 GiB, 그리고 모델 파일 전용으로 사용되는 Gen5 NVMe 드라이브가 있습니다. DeepSeek-V4.1-Flash는 디스크에 510 GB 크기입니다. 현재 Open WebUI에서 일반 채팅 모델로 해당 장비에서 실행됩니다: 디스크에서 전문가(experts)를 읽을 때 토큰당 약 1.6 토큰/초, RAM 캐시 16 GB 사용 시 토큰당 약 2.4 토큰/초입니다. 느린 속도이며, 이 글은 그 이유에 대해 솔직하게 다루지만 — 작동하며, 모든 속도 향상은 DeepSeek의 자체 코드로 수학을 처리하는 순수 버전과 출력 토큰이 일치하도록 유지합니다.
코드: https://github.com/helgard-orlm/deepseek-v41-flash-8gb
누가 무엇을 했는지, 미리 말씀드립니다: 저는 목표를 설정하고 모델을 선택했으며, 몇 가지 아이디어(전문가 중 다음 레이어의 전문가 예측 등)를 제안하고 호출했습니다. 엔진, 서버 및 버그 추적은 세션 동안 **Claude (Anthropic)**이 수행했고, 오버랩 스킴과 1토큰 부족 경로는 **Codex (OpenAI)**가 엔진을 검토하면서 가져왔습니다. 아래의
| part | size | where it lives |
|---|---|---|
| attention (FP8), shared experts, norms, router | ~6.7 GiB | GPU |
| ... |
접근 방식: 모델을 재작성하는 것이 아니라 저장 공간만 이동시키기
DeepSeek은 참조 추론 코드(model.py + kernel.py, MIT)를 공개했습니다. 우리는 모든 산술 연산에 이 코드를 있는 그대로 사용하고, 가중치가 오는 부분만 대체합니다. 이것은 좋은 결과를 가져옵니다: 변환 단계가 없다는 것입니다. 원래의 safetensors 파일에서 각 전문가의 세 개 행렬은 이미 서로 나란히 위치해 있고(17.7 MB), 그 스케일 값들은 하나의 블록으로 더 존재합니다(1.1 MB). 따라서 한 전문가는 Hugging Face 파일에서 O_DIRECT를 사용한 두 번의 pread 호출로 처리할 수 있습니다. 읽기 벤치마크에 따르면, 이는 이미 드라이브가 할 수 있는 용량의 94–97%를 사용하므로, 디스크에 데이터를 재배열하는 것은 아무런 이득이 없었습니다.
우리는 Gen5 드라이브를 구매하기 전에 임대한 RTX 5090에서 VRAM을 인위적으로 7.3 GiB로 제한하여 전체 과정을 측정했습니다. 수치가 가치 있어 보였을 때, 모델은 집으로 이동했습니다.
에러를 발생시키지 않지만 RTX 50xx에서 발생하는 세 가지 버그
이것은 Blackwell 소비자 카드에서 유사한 것을 시도하기 전에 읽고 싶은 부분입니다.
1. TileLang 0.1.8은 sm_120에서 쓰레기 값을 계산하며, 조용히 작동합니다. 참조 버전은 이 버전을 고정합니다. 저희 카드의 경우 FP4 행렬 곱셈은 일반적인 torch 연산(즉, 관련 없는 숫자)과 코사인 값 0.0006을 보였고, FP8은 NaN을 반환했으며, 모델은 아무 문제 없이
2. 참조(reference) act_quant 커널의 문제. DeepSeek의 스케일 형식(ue8m0, 이 모델에서 항상 활성화됨) 때문에 해당 커널은 num_stages = 0으로 빌드됩니다. RTX 5060에서는 64행을 초과하는 경우, FP8 출력 일부가 NaN(Not a Number)이 되며, 심지어 매번 다르게 발생합니다. 동일한 입력을 다섯 번 넣었더니 367이라는 값이 나오기도 했고, 최대 2065개의 NaN 값을 생성하기도 했습니다. 토큰을 하나씩 생성하는 것은 깨끗했기 때문에 프롬프트에서만 문제가 발생했습니다. 이는 컴파일러(CUDA 12.8도 동일하게 작동함)의 문제는 아니었습니다. num_stages = 2로 설정하면 NaN이 전혀 발생하지 않으며, 1행, 17행, 300행 및 1024행에서 torch 구현과 비트 단위로 정확합니다. 이 한 줄이 설정 스크립트가 DeepSeek의 코드에 가하는 유일한 변경 사항입니다.
3. 희소 어텐션(sparse attention) 커널은 141 KB의 공유 메모리를 요구하지만, 컨슈머 Blackwell는 약 99 KB만 제공합니다. 헤드들은 독립적이므로, 우리는 동일한 커널을 16개 헤드의 그룹으로 호출합니다. 작은 함정이 있습니다: 1토큰 텐서의 헤드 슬라이스에 대한 .contiguous()는 스트라이드를 변경하지 않지만, 해당 커널은 스트라이드를 확인하며, clone(memory_format=torch.contiguous_format)가 이를 수행합니다.
공통적인 교훈은 다음과 같습니다: 새로운 하드웨어에서는
v1 — 일반 스트리밍(plain streaming): 디스크에서 0.89 토큰/초, 14GB RAM 캐시 사용 시 1.17 토큰/초를 기록했습니다. 레이어의 6개 전문가(experts)를 읽고, 이를 GPU로 복사한 다음, 기다리고 계산하는 과정이 필요합니다. 디스크, 버스, 컴퓨팅 자원이 단순히 합산되었습니다.
v2 — 오버랩(overlap) (Codex 방식): CUDA 이벤트를 사용하여 전역 동기화(global synchronize) 대신 별도의 복사 스레드를 사용했습니다. 놀랍게도 6개 전문가를 한 번에 읽어오는 것이 더 느렸습니다. 이들은 드라이브를 공유하기 때문에, 모든 작업이 레이어 끝에서 함께 완료되며 계산은 그보다 일찍 시작할 수 없었습니다. 효과적이었던 방식은 깊은 대기열(deep queue)을 사용하되 전문가들을 순차적으로 처리하는 것이었습니다. 즉, 동시에 진행되는 것은 최대 2개였고, 각 읽기는 4개의 병렬 조각으로 이루어졌습니다. 디스크를 기다리는 시간이 토큰당 0.65초에서 0.31초로 줄어들었습니다. (이 버전의 초기에는 자체적인 경쟁 문제가 있었습니다. 링 버퍼(ring buffer)가 아직 GPU에 도달하지 않은 전문가를 덮어썼고, 그 결과 토큰 29가 변경되었습니다. 이 문제를 토큰 확인 과정에서 발견했습니다.)
v3 — 단일 토큰을 위한 짧은 경로: +5.7%. 기존 방식은 torch.where(indices == e)를 사용하여 각 전문가의 토큰을 찾았는데, 이는 CPU와 GPU 간 동기화(sync)가 발생하며 토큰당 240번 이루어졌고, 각각이 다음 복사 작업을 지연시켰습니다. 이제 단일 토큰에 대해서는 인덱스를 레이어당 한 번만 CPU로 이동합니다. 계산 방식은 동일합니다.
v4 — 다음 레이어의 두 전문가를 간격(gap)에 미리 가져오기: 디스크에서 1.64 토큰/초 (+21%), RAM 사용 시 2.3 토큰/초. 레이어 i+1의 라우터(router)를 레이어 i의 입력에 적용합니다 (두 레이어의 정규화 가중치로 재조정). 이렇게 하면 6개 전문가 중 상위 2개를 71%의 확률로 얻을 수 있습니다. 레이어 i의 실제 읽기 작업이 진행되는 동안, RAM에 이미 없는 상위 2개의 추측(guesses)을 미리 읽습니다. 이들 중 94%가 사용됩니다. 세 개나 네 개의 추측은 더 나쁩니다. 실제로 필요한 읽기 작업과 경쟁하기 시작하기 때문입니다.
우리가 과정에서 잘못 이해한 한 가지가 있습니다. 저는 예산 내에서 이미 RAM에 있는 추측을 계산하여
| version | disk only | with RAM cache |
|---|---|---|
| v1 plain | 0.89 | 1.17 |
| ... | ||
| A 311 토큰 프롬프트는 18초가 소요되며 (v1에서는 27초), 최대 VRAM은 7.06 GiB입니다. 후속 메시지는 대화 내용을 다시 읽지 않습니다. 서버는 각 답변 후에 전체 모델 상태(30 MiB — KV, 슬라이딩 윈도우, 압축 테일, 인덱서 캐시, Engram 히스토리)를 스냅샷하고 새로운 테일만 처리합니다. |
측정 및 제외된 사항
이 중 일부는 제 아이디어였고, 일부는 Claude의 아이디어였습니다. 모두 논쟁하는 것이 아니라 실제 가중치로 측정되었습니다.
- 전문가(expert) 또는 레이어 간 델타 저장: 압축률이 더 좋을 것입니다. FP4 코드는 3.895 비트의 엔트로피를 가지고 있습니다. 다음 레이어의 동일 전문가와, 또는 이웃과의 차이는 3.997로 원래 값보다 나쁩니다. 최고의 뉴런 순열 및 뉴런별 스케일링을 거친 후에도 남아 있는 차이는 원본의 0.999–1.000에 불과하며, 이는 무작위 행렬 두 개가 제공하는 것과 정확히 같습니다.
- '침묵하는(silent)' 뉴런 건너뛰기 및 다운 프로젝션 부분 읽지 않기. 각 전문가를 5% 오류 범위 내로 유지하려면 여전히 뉴런의 61–75%가 필요합니다. 이는 바이트 수를 약 12% 줄이지만, 읽기는 계산의 절반을 기다려야 합니다. 이 모델의 SwiGLU 뉴런은 ReLU² 뉴런처럼 조용해지지 않습니다.
- 더 스마트한 캐시 정책. 기록된 경로에 대한 시뮬레이션 결과: LRU가 에이징(ageing)을 적용한 LFU, 레이어별 LRU 및 SLRU를 능가했습니다. 이론적 최적값은 약 15포인트 더 높지만, 간단한 방법으로는 근접하기 어렵습니다.
- 더 많은 리더 스레드 사용 및 읽기 분할: 드라이브 자체가 이미 한계에 가까웠습니다.
디스크에서 토큰 하나를 가져오는 데는 약 0.6초가 걸리며, 드라이브가 제공하는 8.2 GiB/s 속도로 4.2 GiB를 읽는 것만 해도 그중 약 0.5초가 소요됩니다. 이 수치와 우리 사이에 남은 소프트웨어적인 부분은 매우 적습니다. 더 빠르게 가려면 바이트 수를 줄이거나(캐시를 위한 RAM 추가 — 전체 전문가 세트가 269 GiB이므로 캐시는 항상 부분적입니다) 두 번째 드라이브가 필요합니다. Qwen의 경우 같은 방법으로 토큰당 1/3 바이트가 필요하기 때문에 초당 11개의 토큰을 제공합니다. 만약 가정용 PC에서 스트리밍할 MoE 모델을 선택한다면, 파라미터 개수보다 토큰당 바이트 수를 확인하세요.
시도해 보기
약 520 GB의 여유 공간이 있는 빠른 NVMe 드라이브와 Blackwell GPU가 필요합니다(위의 수정 사항은 sm_120용입니다. 다른 카드는 더 적게 필요할 수 있습니다). setup.sh는 사용된 정확한 모델 버전을 다운로드하고, kernel.py가 예상되는 파일인지 확인하며, DeepSeek의 코드를 한 줄 수정하여 복사본을 만듭니다. 게시하기 전에 같은 장비에서 저장소를 새로 클론하고, 다운로드한 모델에 대해 설정을 실행한 후 클론된 곳에서 서버를 시작했습니다. 모든 파라미터가 계산되어 로드되었고 올바르게 응답했습니다. 엔진 및 서버 파일은 가정에서 실행되는 것과 바이트 단위로 동일합니다. 처음부터 전체 510 GB 다운로드는 이 테스트를 위해 반복되지 않았습니다.
엔진, 서버, 버그 추적 및 본 글: Claude (Anthropic). 오버랩 방식과 토큰 하나가 부족한 경로: Codex (OpenAI). 목표, 모델 선택, 전문가 예측 아이디어 및 결정: 저. 모든 수치는 위에 설명된 장비의 로그에서 가져왔습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기