6장. Qwen 모델 준비와 텍스트 생성
모델 크기와 GPU 메모리
이삿짐을 트럭에 싣기 전에는 상자 수 × 상자 하나의 부피로 짐의 크기를 어림합니다. 모델도 같습니다. 가중치만 올릴 때 필요한 메모리는 파라미터 수 × 파라미터 1개당 바이트 수 로 어림합니다. 상자 수(파라미터 수)는 모델이 정하고, 상자 크기(바이트 수)는 우리가 고르는 정밀도가 정합니다.
그림의 네 상자는 같은 Qwen 0.5B 를 네 가지 정밀도로 올렸을 때의 크기입니다. 왼쪽에서 오른쪽으로 갈수록 숫자 하나가 절반씩 작아지므로 전체도 절반씩 줄어듭니다. 실습 1에서 get_memory_footprint() 가 알려 준 942 MB 가 둘째 상자(BF16)의 손계산과 맞는지 아래에서 확인합니다.
정밀도별 가중치 크기
| 정밀도 | 1개당 크기 | Qwen 0.49B 가중치(손계산) | 8B 모델 가중치(약) | 쓰임 |
|---|---|---|---|---|
| FP32 | 4바이트 | 약 1,885 MB | 32GB | 32비트 부동소수점, 기본 형식 |
| BF16 / FP16 | 2바이트 | 약 942 MB | 16GB | LLM 학습과 추론에 주로 쓰는 16비트 |
| INT8 | 1바이트 | 약 471 MB | 8GB | 8비트 양자화 |
| 4bit(NF4) | 0.5바이트 | 약 236 MB | 4GB | QLoRA 에서 쓰는 4비트 양자화 |
손으로 한 번 계산해 봅니다. 파라미터 494,032,768 개 × 2바이트 = 988,065,536 바이트입니다. 1 MB 는 1024 × 1024 = 1,048,576 바이트이므로 988,065,536 ÷ 1,048,576 ≈ 942.3 MB 입니다. 실습 1의 출력 "메모리 사용량: 942 MB" 와 맞습니다.
8B 모델을 BF16 으로 올리면 가중치만 약 16GB 입니다. 모델을 돌리는 데 드는 메모리가 따로 있으므로 16GB GPU 한 장으로는 여유가 거의 없습니다. 학습할 때는 그래디언트(gradient, 고칠 방향), 옵티마이저 상태(optimizer state, 보폭 기록), 중간 계산 결과(activation)도 저장해야 하므로 훨씬 더 듭니다. 이 부담을 줄이는 LoRA 는 8장에서, 4비트로 더 줄이는 QLoRA 는 9장에서 봅니다.
짧은 예시
# 실습 1의 출력에 나온 파라미터 수
n_params = 494_032_768
# 정밀도 이름과 파라미터 1개당 바이트 수
bytes_per_param = {"FP32": 4, "BF16": 2, "INT8": 1, "4bit": 0.5}
for name, nbytes in bytes_per_param.items():
total_bytes = n_params * nbytes # 파라미터 수 × 바이트 수
mb = total_bytes / 1024**2 # 1024 × 1024 바이트 = 1 MB
# :>5 = 다섯 칸에 오른쪽 맞춤, :,.0f = 천 단위 쉼표와 소수점 없이
print(f"{name:>5}: {mb:,.0f} MB")
# 결과: FP32: 1,885 MB / BF16: 942 MB / INT8: 471 MB / 4bit: 236 MB
# 8B(80억) 모델을 BF16 으로 올리면 몇 GB 일까(1GB = 10억 바이트로 어림)
print(8e9 * 2 / 1e9, "GB") # 결과: 16.0 GB이 코드가 뜻하는 것
이삿짐 상자 수를 그대로 두고 상자 크기만 바꿔 가며 트럭 짐칸이 얼마나 필요한지 표로 뽑은 것입니다. 상자 수는 실습 1의 출력에서 가져왔고, 상자 크기는 정밀도표에서 가져왔습니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
n_params * nbytes | 전체 바이트 수 | 상자 수 × 상자 부피 |
/ 1024**2 | 바이트를 MB 로 | 단위 바꾸기 |
8e9 * 2 / 1e9 | 8B 모델의 BF16 크기 | 큰 트럭이 필요한 이사 |
BF16 줄의 942 MB 는 실습 1의 실제 측정값과 같습니다. 이 손계산 하나로 "내 GPU 에 어떤 모델까지 올릴 수 있나"를 미리 가늠할 수 있습니다.
주의할 점
- 이 계산은 가중치만의 크기입니다. 실제로 생성할 때는 입력 길이에 따라 중간값이 더 올라가고, 학습할 때는 몇 배가 더 듭니다.
nvidia-smi로 보는 사용량은 PyTorch 가 미리 잡아 둔 메모리까지 포함하므로get_memory_footprint()보다 큽니다.- 다 쓴 모델은
del model과torch.cuda.empty_cache()로 비웁니다. 원본 노트북이import gc를 해 둔 것도 이 정리를 위해서입니다.