DevOps··3분 읽기·0

9. 하드웨어: GPU, 메모리, 속도의 관계

LLM에 GPU가 필요한 이유. 진짜 병목이 메모리 대역폭인 이유. 내 GPU에서 어떤 모델을 돌릴 수 있는지 VRAM 사용량을 직접 계산하는 방법.

글꼴

8편까지 LLM의 소프트웨어적 동작을 전부 다뤘다. 이번 편부터는 하드웨어다. 왜 GPU가 필요한지, 진짜 병목이 어디인지, 내 GPU에서 어떤 모델을 얼마나 빠르게 돌릴 수 있는지.

시스템 엔지니어에게는 이 편이 가장 실무적일 거다.


CPU vs GPU — 왜 GPU가 필요한가

CPU (i7-13700K):
  - 24코어 (복잡한 분기/조건문에 강함)
  - 비유: 박사급 엔지니어 24명
 
GPU (GTX 1060):
  - CUDA 코어 1,280개 (같은 연산을 동시에)
  - 비유: 계산기 든 인턴 1,280명
 
GPU (RTX 4090):
  - CUDA 코어 16,384개 + Tensor 코어 512개
  - Tensor 코어: 행렬곱 전용, 한 클럭에 4x4 행렬곱 = 64번 연산

LLM의 모든 연산이 행렬곱이다. 행렬곱은 독립적인 내적의 집합이니까 완벽한 병렬화 대상이다. Q 벡터 2048차원이면 2048개의 독립적인 내적이다. CPU 24개로 나누면 85개씩 순차 처리하지만, GPU 1280개로 나누면 2개씩 동시에 처리한다.


O(n^2) 문제 — 어텐션의 계산량

어텐션에서 모든 토큰 쌍의 내적을 계산하니까, 토큰 수의 제곱에 비례하는 비용이 든다.

토큰 수     어텐션 내적 횟수          x 2048차원 x 36레이어
--------   ------------------   ------------------
   100           10,000               7.4억
 1,000        1,000,000             737억
10,000      100,000,000            7.4조
128,000   16,384,000,000          1,209조
 
토큰 10배 -> 계산 100배.
Flash Attention: 메모리 절약 (블록 단위 계산), 계산량은 동일.

토큰이 10배 늘면 어텐션 계산은 100배 늘어난다. 128K 토큰이면 164억 번의 내적이다. 이걸 어떻게 감당하는 걸까?

솔직히 계산량 자체는 줄일 수 없다. 다만 Flash Attention이라는 최적화가 메모리 문제를 해결해준다. 어텐션 행렬 전체를 VRAM에 올려놓는 대신 블록 단위로 나눠서 계산하고, 중간 결과를 바로 버리는 방식이다. 계산 횟수는 같지만 VRAM 사용량이 크게 줄어든다.


진짜 병목 — 메모리 대역폭

6편에서 Decode 시 매 토큰마다 모든 W를 VRAM에서 읽어야 한다고 했다. 여기서 진짜 병목이 나온다.

매 토큰 생성 시 모든 W를 VRAM에서 GPU 코어로 읽어야 함.
 
                                         이론 속도
GPU          VRAM    대역폭      3B 모델  (토큰/초)
----------   -----  ---------   --------------
GTX 1060     6GB    192 GB/s     ~48
RTX 4090    24GB   1008 GB/s    ~252
H100        80GB   3350 GB/s    ~838
Mac M4      16GB    120 GB/s     ~30
Mac M4 Pro  24GB    273 GB/s     ~68
 
코어가 아무리 빨라도 VRAM->코어 읽기 속도가 전체 속도 결정.
= 메모리 바운드 (memory-bound)

비유하면 계산기를 두드리는 속도는 빠른데, 참고할 표를 꺼내오는 게 느린 거다. GPU를 고를 때 CUDA 코어 수보다 메모리 대역폭이 더 중요한 이유다.

GTX 1060으로 3B 모델을 돌리면 이론상 초당 48토큰. 실제로는 오버헤드 때문에 30~40 정도 나온다. 직접 돌려본 사람은 알 거다.


VRAM 사용량 계산법

내 GPU에 어떤 모델이 올라가는지 직접 계산할 수 있다.

필요 VRAM = 파라미터 크기 + KV 캐시 + 오버헤드
 
예: 7B 모델, Q4 양자화, 입력 2000토큰
 
  파라미터:  70억 x 0.5바이트(Q4)  = 3.5 GB
  KV 캐시:   2000 x 288KB         = 0.6 GB
  오버헤드:                        = 0.5 GB
  ----------------------------------------
  합계:                            = 4.6 GB -> GTX 1060(6GB) 가능
 
예: 7B 모델, Q4 양자화, 입력 32000토큰
 
  파라미터:                        = 3.5 GB
  KV 캐시:  32000 x 288KB         = 9.0 GB  <- VRAM 초과!

파라미터 크기는 고정이지만, KV 캐시는 입력 길이에 비례해서 늘어난다. 7B Q4 모델은 GTX 1060(6GB)에 올라가지만, 입력이 32K 토큰이면 KV 캐시만 9GB라서 VRAM이 터진다.


통합 메모리 — GPU와 CPU가 메모리를 공유하는 구조

일반적인 데스크톱은 CPU 메모리(RAM)와 GPU 메모리(VRAM)가 물리적으로 분리되어 있다. 데이터를 GPU에 보내려면 PCIe 버스를 통해 복사해야 한다.

일반 구조 (데스크톱 GPU):
  CPU ←── PCIe 버스 ──→ GPU
  RAM (32GB)               VRAM (6GB)
  
  모델은 VRAM에 올라가야 함.
  VRAM이 부족하면 -> 모델이 안 올라감 or CPU offloading.
 
통합 메모리 구조 (Apple Silicon, 일부 iGPU):
  CPU ←── 동일 메모리 ──→ GPU
         통합 메모리 (16~192GB)
 
  CPU와 GPU가 같은 메모리를 공유.
  PCIe 복사 오버헤드 없음.
  16GB 전체를 모델에 쓸 수 있음.
  -> VRAM이 적은 dGPU보다 큰 모델을 올릴 수 있음.
 
  단점: 메모리 대역폭이 dGPU보다 낮음.
    Mac M4:     120 GB/s
    RTX 4090:  1008 GB/s
  -> 모델은 올라가지만 토큰 생성 속도는 느림.

통합 메모리 구조에서는 CPU와 GPU가 같은 메모리를 공유하므로, 메모리 전체를 모델에 할당할 수 있다. 16GB 통합 메모리면 14B Q4 모델을 올릴 수 있다. 같은 16GB라도 일반 GPU(VRAM 6GB + RAM 16GB 분리)에서는 불가능한 크기다.

다만 대역폭은 전용 GPU에 비해 훨씬 낮다. 6편에서 다뤘듯이 Decode의 병목이 메모리 대역폭이니까, 모델은 올라가지만 토큰 생성 속도는 전용 GPU보다 느리다. 큰 모델을 느리게 돌릴 건지, 작은 모델을 빠르게 돌릴 건지 트레이드오프다.


CPU Offloading — VRAM에 안 올라가면 RAM을 빌려 쓴다

VRAM이 부족할 때 모델의 일부 레이어를 CPU 메모리(RAM)에 올려놓고, 필요할 때 GPU로 가져와서 계산하는 방식이다.

7B Q4 모델 (4.4GB) + GTX 1060 (6GB VRAM):
  전부 VRAM에 올림. -> 정상 동작.
 
14B Q4 모델 (8.8GB) + GTX 1060 (6GB VRAM):
  VRAM 부족. 전체가 안 올라감.
 
  CPU Offloading:
    레이어 1~20: VRAM (5.2GB)  -> GPU에서 빠르게 계산
    레이어 21~48: RAM (3.6GB)  -> 필요할 때 GPU로 전송 후 계산
 
  대가:
    PCIe 3.0 x16 대역폭: ~16 GB/s
    VRAM 내부 대역폭:    ~192 GB/s (GTX 1060)
    -> RAM에 있는 레이어는 12배 느림.
    -> 전체 속도 = 가장 느린 경로에 맞춰짐.

llama.cpp(ollama의 백엔드)는 이걸 자동으로 해준다. --n-gpu-layers 옵션으로 GPU에 올릴 레이어 수를 지정할 수 있다. 전부 GPU에 올리면 빠르고, 일부만 올리면 느리지만 큰 모델을 돌릴 수 있다.

느리더라도 14B 모델을 돌리는 게 나은지, 빠르더라도 7B에 머무는 게 나은지는 태스크에 따라 다르다. 단순 분류나 패턴 매칭이면 7B로 충분하고, 복잡한 추론이 필요하면 느려도 14B가 결과가 좋다. 실제로 offloading으로 14B를 돌려보면, 느리긴 해도 답변 품질이 확 달라지는 걸 느낄 수 있다.


비용 감각

-- API 비용 (참고) --
  Claude Sonnet: 입력 $3/백만토큰, 출력 $15/백만토큰
  로그 1000줄 분석 1회 = 입력 5000토큰 + 출력 500토큰
                      = $0.02 (약 30원)
 
-- 로컬 LLM --
  전기세만 (GPU 전력 x 시간)
  GTX 1060 TDP 120W x 1시간 = 약 15원

API는 건당 비용이 들고, 로컬은 전기세만 든다. 하루에 수백 번 분석을 돌린다면 로컬이 압도적으로 싸다. 가끔 한 번 쓰는 정도면 API가 편하다. 이건 결국 사용 빈도의 문제다.

다음 편에서는 모델 파일 자체를 파고든다. ollama가 모델을 어떻게 로드하는지, 양자화가 뭐고 어떤 레벨을 선택해야 하는지.

이 글이 어떠셨나요?

이 글이 도움이 되셨나요?
공유:

관련 포스트

뉴스레터 구독

새 글이 올라오면 이메일로 알려드려요.

댓글

댓글을 불러오는 중...