레이어 4개만 CPU로 내려도 속도가 절반 — 오프로드 대가 실측
“VRAM이 조금 모자라도 돌아간다”는 말을 자주 봅니다. 맞습니다. llama.cpp와 Ollama는 VRAM에 다 못 올린 레이어를 시스템 RAM으로 내려서 CPU가 처리하게 합니다.
문제는 대가입니다. 같은 모델에서 GPU에 올리는 레이어 수만 바꿔가며 측정했더니, 48개 중 4개만 CPU로 내려도 생성 속도가 절반이 되었습니다.
대조 실험 결과
모델, 프롬프트, 시드, 컨텍스트를 모두 고정하고 num_gpu(GPU에 올릴 레이어 수)만 바꿨습니다. 모델은 Qwen3-Coder 30B-A3B Q4_K_M이고 레이어는 48개입니다.
| GPU 레이어 | 메모리 배치 | 생성 속도 | 기준 대비 |
|---|---|---|---|
| 48 / 48 | 100% GPU | 94.6 tok/s | 100% |
| 44 / 48 | 11% CPU | 46.3 tok/s | 49% |
| 40 / 48 | 19% CPU | 39.8 tok/s | 42% |
| 36 / 48 | 27% CPU | 35.2 tok/s | 37% |
| 28 / 48 | 42% CPU | 28.5 tok/s | 30% |
| 20 / 48 | 58% CPU | 24.2 tok/s | 26% |
각 조건 2회 측정했고 편차는 1% 이내였습니다(예: 44 레이어에서 46.2와 46.3).
곡선의 모양이 핵심입니다. 레이어 4개(8%)를 내리는 첫 걸음에서 속도가 51% 떨어집니다. 그 뒤로는 완만합니다. 44개에서 20개로 24개를 더 내려도 46.3에서 24.2로 절반 줄어드는 데 그칩니다.
즉 손실의 대부분이 “조금 넘치는” 구간에서 발생합니다. 오프로드가 늘어날수록 비례해서 나빠지는 것이 아니라, 경계를 넘는 순간 대부분의 손해를 봅니다.
이유는 구조에 있습니다. 토큰 하나를 생성할 때마다 모든 레이어를 순서대로 통과해야 합니다. 레이어 4개가 CPU에 있으면 매 토큰마다 그 구간에서 GPU가 멈추고 CPU 처리와 데이터 전송을 기다립니다. CPU에 있는 레이어 수보다 CPU를 거치는 일이 생기느냐 아니냐가 훨씬 큰 영향을 줍니다.
모델이 다르면 얼마나 다른가
같은 하드웨어에서 모델 4종을 3회씩 측정한 결과입니다. 이쪽은 통제된 비교가 아니라 관찰입니다.
| 모델 | 파라미터 | 메모리 배치 | 생성 속도 | GPU 전력 |
|---|---|---|---|---|
| Qwen3-Coder 30B-A3B | 30.5B (활성 3B) | 100% GPU | 93.9 tok/s | 178W |
| Gemma4 26B | 25.8B | 100% GPU | 73.3 tok/s | 191W |
| Qwen3.6 35B-A3B | 36.0B (활성 3B) | 18% CPU | 25.0 tok/s | 72W |
| Devstral Small 2 24B | 24.0B | 100% GPU | 22.4 tok/s | 242W |
여기서 인과를 읽으면 안 됩니다. 네 모델은 아키텍처, 파라미터 수, KV 구조, 커널 최적화가 모두 다릅니다. Qwen3.6이 느린 데는 오프로드가 관여했겠지만, 이 표만으로 얼마나 기여했는지는 분리되지 않습니다. 오프로드의 효과는 앞의 대조 실험에서만 측정된 것입니다.
관찰로 말할 수 있는 것은 두 가지입니다. 완전히 GPU에 올라간 모델끼리도 22.4에서 93.9까지 4배 차이가 납니다. 그리고 Devstral은 242W를 쓰면서 22.4 tok/s를 냅니다. 토큰당 GPU 에너지로 환산하면 10.8J로 Qwen3-Coder(1.9J)의 5.7배입니다. 같은 하드웨어에서 모델 선택만으로 GPU 에너지가 몇 배 갈립니다.
오프로드된 Qwen3.6의 GPU 전력이 72W로 유독 낮은 것도 눈에 띕니다. 전력이 낮은 것은 효율이 좋다는 뜻이 아닙니다. GPU가 CPU를 기다리는 시간이 길면 평균 전력이 내려갑니다. 다만 이 측정은 GPU 전력만 잰 것이고 CPU 소비는 포함하지 않았으므로, 시스템 전체 소비는 이 숫자보다 큽니다.
용어를 정확히 하면
Ollama의 ollama ps가 보여주는 값을 오해하기 쉬워서 짚어둡니다.
PROCESSOR의18%/82% CPU/GPU는 연산량이나 처리 시간의 비율이 아닙니다. Ollama가 보고하는 메모리 배치 비율입니다. 대략(전체 − VRAM 배치분) ÷ 전체로 계산됩니다. “18%의 작업을 CPU가 한다”는 뜻이 아닙니다.SIZE는 GGUF 파일 크기나 순수 가중치 크기가 아닙니다. 실행 중인 runner가 보고하는 총 메모리 크기이며 KV 캐시와 실행 버퍼가 포함됩니다. 따라서 컨텍스트 설정에 따라 값이 달라집니다.
이 글의 표에서 “메모리 배치”라고 쓴 것은 이 의미입니다.
실용적인 결론
모델을 고를 때 활성 파라미터보다 전체 크기를 먼저 보십시오. MoE의 “활성 파라미터가 작다”는 장점은 가중치 전체가 VRAM에 들어간다는 전제 위에서 성립합니다. 전제가 깨지면 이점이 크게 줄어듭니다.
조금 넘치는 것은 조금 느려지는 것이 아닙니다. 8% 오프로드에서 이미 절반을 잃었습니다.
넘칠 것 같으면 한 단계 작은 모델을 먼저 고려하십시오. 억지로 큰 모델을 오프로드로 굴리는 것보다 대체로 낫습니다. 컨텍스트를 줄이거나 KV 양자화로 여유를 만드는 것도 방법입니다.
GPU 전력이 유독 낮으면 병목을 의심하십시오. 오프로드나 다른 대기 요인이 있을 수 있습니다.
내 구성에 어떤 모델이 들어가는지는 VRAM 계산기로 확인할 수 있습니다. 계산식은 실측으로 검증했고 오차는 2~4% 수준이었습니다.
측정 환경과 방법
| 항목 | 값 |
|---|---|
| GPU | RTX 3060 12GB × 2 |
| CPU | AMD Ryzen 7 5800X (8코어) |
| RAM | 125GB |
| PCIe | GPU0 x4, GPU1 x16 (측정 시점 링크 폭) |
| 런타임 | Ollama 0.20.0, 드라이버 590.48.01 |
| KV 타입 | f16 (기본값) |
- 대조 실험:
num_ctx=8192,num_predict=200,seed=42,num_gpu만 변경, 조건당 2회 - 모델 비교:
num_ctx=32768,num_predict=300,seed=42, 모델당 3회 - 매 회 완전히 언로드한 뒤 다시 올림
- 전력은
nvidia-smi0.5초 샘플링 중 GPU 사용률 30% 초과 구간의 두 장 합계 평균
한계
- CPU와 시스템 전체 전력을 재지 않았습니다. 토큰당 에너지 비교는 100% GPU 구성끼리만 유효합니다.
- 전력 평균이 사용률 30% 초과 구간만 대상입니다. 전체 생성 시간의 평균 전력과 다를 수 있습니다.
- GPU0의 PCIe 링크 폭이 x4입니다. 오프로드는 CPU와 GPU 사이 전송에 민감하므로, x16 두 장 구성에서는 손실이 더 작을 수 있습니다.
- 오프로드 곡선은 모델 하나에서만 얻었습니다. dense 모델이나 다른 크기에서 같은 모양인지는 확인하지 않았습니다.
- 모델 비교 표는 통제된 실험이 아닙니다. 아키텍처 차이가 뒤섞여 있어 인과로 읽을 수 없습니다.
- 프롬프트 한 종류, 짧은 생성으로만 측정했습니다. 긴 컨텍스트를 실제로 채운 상태의 양상은 다를 수 있습니다.
- 조건당 2~3회 반복입니다. 편차는 작았지만 신뢰구간을 말할 만한 표본은 아닙니다.
- 측정 원본은 저장소에 공개해 두었습니다. 다른 환경에서 재현한 결과를 알려주시면 출처를 밝히고 반영하겠습니다.