LLM을 위한 기초 CS 지식¶
LLM/VLM 기술을 제대로 이해하고 최적화하기 위해 알아야 할 컴퓨터 과학 기초
왜 이 지식이 필요한가?¶
LLM을 단순히 "사용"하는 것과 "최적화"하는 것은 다르다. 최적화를 위해서는:
- 모델이 GPU에서 어떻게 실행되는지
- 메모리가 어떻게 관리되는지
- 숫자가 컴퓨터에서 어떻게 표현되는지
이런 기초를 알아야 왜 양자화가 효과적인지, KV 캐시가 왜 필요한지 이해할 수 있다.
목차¶
- 컴퓨터 기초 - 프로세스, 메모리, CPU vs GPU
- 숫자 표현 - FP32, FP16, INT8, 정밀도
- 메모리 계층 - RAM, VRAM, 캐시
- LLM 핵심 개념 - 토큰, KV 캐시, 배치
- 최적화 기초 - 왜 양자화? 왜 배치?
1. 컴퓨터 기초¶
프로그램 실행 과정¶
┌─────────────────────────────────────────────────────────────────┐
│ 프로그램 실행 흐름 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [소스코드] │
│ python inference.py │
│ │ │
│ ▼ │
│ [프로세스 생성] │
│ OS가 메모리 공간 할당, PID 부여 │
│ │ │
│ ▼ │
│ [메모리 로드] │
│ - 코드 영역: 실행할 명령어 │
│ - 데이터 영역: 전역 변수 │
│ - 힙: 동적 할당 (모델 가중치!) │
│ - 스택: 함수 호출, 지역 변수 │
│ │ │
│ ▼ │
│ [CPU/GPU 실행] │
│ - CPU: 일반 로직, I/O │
│ - GPU: 행렬 연산 (LLM 추론) │
│ │
└─────────────────────────────────────────────────────────────────┘
프로세스 vs 스레드¶
| 구분 | 프로세스 | 스레드 |
|---|---|---|
| 메모리 | 독립 공간 | 프로세스 내 공유 |
| 생성 비용 | 높음 | 낮음 |
| 통신 | IPC 필요 | 직접 공유 |
| 예시 | 별도 Python 실행 | asyncio, threading |
LLM 서빙에서: - 프로세스: vLLM 서버 인스턴스 - 스레드: 동시 요청 처리 - GPU: 별도 메모리 공간 (VRAM)
CPU vs GPU¶
┌─────────────────────────────────────────────────────────────────┐
│ CPU vs GPU 구조 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CPU (Central Processing Unit) │
│ ┌─────┬─────┬─────┬─────┐ │
│ │Core │Core │Core │Core │ 4-64개 강력한 코어 │
│ │ 1 │ 2 │ 3 │ 4 │ 복잡한 연산, 분기 처리에 강함 │
│ └─────┴─────┴─────┴─────┘ │
│ │
│ GPU (Graphics Processing Unit) │
│ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 수천 개의 작은 코어 │
│ ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┤ 단순 연산 대량 병렬 │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 행렬 곱셈에 최적화 │
│ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
왜 LLM에 GPU? - Transformer = 행렬 곱셈의 연속 - 70B 모델 추론 = 수조 번의 곱셈/덧셈 - GPU의 병렬 처리가 필수
2. 숫자 표현 (부동소수점)¶
부동소수점이란?¶
컴퓨터는 실수를 부호 + 지수 + 가수로 표현한다.
FP32 (32비트 = 4바이트)
┌───┬──────────┬───────────────────────┐
│ S │ Exponent │ Mantissa │
│ 1 │ 8 │ 23 │ 비트
└───┴──────────┴───────────────────────┘
예시: -6.5 = -1.625 × 2²
S = 1 (음수)
E = 129 (2 + 127 바이어스)
M = 0.625의 이진수
정밀도 비교¶
| 형식 | 비트 | 메모리 | 정밀도 | 범위 | 용도 |
|---|---|---|---|---|---|
| FP64 | 64 | 8B | 최고 | 매우 넓음 | 과학 계산 |
| FP32 | 32 | 4B | 높음 | 넓음 | 학습 기본 |
| TF32 | 19 | 4B | 중간 | FP32 | A100 학습 |
| FP16 | 16 | 2B | 낮음 | 좁음 | 추론 |
| BF16 | 16 | 2B | 낮음 | FP32급 | 학습/추론 |
| INT8 | 8 | 1B | 256단계 | -128~127 | 양자화 |
| INT4 | 4 | 0.5B | 16단계 | -8~7 | 극한 압축 |
FP16 vs BF16¶
FP16 (Half Precision)
┌───┬───────┬────────────┐
│ S │ Exp │ Mantissa │
│ 1 │ 5 │ 10 │ 정밀도 높음, 범위 좁음
└───┴───────┴────────────┘
BF16 (Brain Float)
┌───┬──────────┬────────┐
│ S │ Exponent │Mantissa│
│ 1 │ 8 │ 7 │ 범위 FP32급, 정밀도 낮음
└───┴──────────┴────────┘
BF16이 LLM에 좋은 이유: - FP32와 동일한 지수 범위 → 오버플로우 방지 - 학습 안정성 유지 - 메모리 절반으로 감소
왜 정밀도가 중요한가?¶
모델 크기 계산:
Llama 70B 모델
- 파라미터: 70,000,000,000개
- FP32: 70B × 4B = 280GB (불가능)
- FP16: 70B × 2B = 140GB (A100 2대)
- INT8: 70B × 1B = 70GB (A100 1대)
- INT4: 70B × 0.5B = 35GB (RTX 4090 가능!)
3. 메모리 계층¶
메모리 속도 피라미드¶
┌─────────────────────────────────────────────────────────────────┐
│ 메모리 계층 구조 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │GPU 레지스터│ ~TB/s │
│ └────┬────┘ │
│ ┌────┴────┐ │
│ │GPU L1/L2│ ~10TB/s │
│ │ 캐시 │ │
│ └────┬────┘ │
│ ┌─────────┴─────────┐ │
│ │ GPU VRAM │ ~2TB/s (HBM) │
│ │ (24-80GB) │ 모델 가중치 저장 │
│ └─────────┬─────────┘ │
│ ┌──────────────┴──────────────┐ │
│ │ 시스템 RAM │ ~50GB/s │
│ │ (64-512GB) │ CPU 작업, 배치 대기 │
│ └──────────────┬──────────────┘ │
│ ┌───────────────────┴───────────────────┐ │
│ │ SSD/NVMe │ ~7GB/s │
│ │ (1-8TB) │ 모델 파일 저장 │
│ └───────────────────────────────────────┘ │
│ │
│ 속도: 위로 갈수록 빠름 │
│ 용량: 아래로 갈수록 큼 │
│ 가격: 위로 갈수록 비쌈 │
│ │
└─────────────────────────────────────────────────────────────────┘
GPU 메모리 (VRAM)¶
VRAM 사용 내역 (추론 시):
Llama 70B INT4 추론 예시:
┌─────────────────────────────────────┐
│ VRAM 사용량 │
├─────────────────────────────────────┤
│ 모델 가중치 (INT4) 35 GB │
│ KV 캐시 (동적) 10-20 GB │
│ 활성화 메모리 2-5 GB │
│ CUDA 컨텍스트 1-2 GB │
├─────────────────────────────────────┤
│ 총 필요 ~50-60 GB │
└─────────────────────────────────────┘
→ A100 80GB: 가능
→ RTX 4090 24GB: 불가능 (작은 모델 필요)
대역폭이 왜 중요한가?¶
LLM 추론 = 메모리 바운드 (Memory Bound)
추론 속도 계산:
- 모델 크기: 70GB (INT8)
- VRAM 대역폭: 2TB/s (A100)
- 이론상 최대: 2000GB/s ÷ 70GB = ~28 토큰/초
실제로는:
- KV 캐시 읽기/쓰기
- 활성화 값 저장
- → 실제 ~20 토큰/초
대역폭이 2배 → 속도도 ~2배
4. LLM 핵심 개념¶
토큰 (Token)¶
┌─────────────────────────────────────────────────────────────────┐
│ 토큰화 과정 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 입력: "안녕하세요, 저는 AI입니다." │
│ │ │
│ ▼ │
│ 토크나이저 (BPE/SentencePiece) │
│ │ │
│ ▼ │
│ 토큰: ["안녕", "하세요", ",", " 저는", " AI", "입니다", "."] │
│ │ │
│ ▼ │
│ 토큰 ID: [31245, 8472, 11, 12847, 9583, 29384, 13] │
│ │
│ 특징: │
│ - 한글: 1글자 ≈ 2-3토큰 (비효율적) │
│ - 영어: 1단어 ≈ 1-2토큰 │
│ - 어휘집 크기: 32K-128K (모델마다 다름) │
│ │
└─────────────────────────────────────────────────────────────────┘
KV 캐시 (Key-Value Cache)¶
왜 KV 캐시가 필요한가?
┌─────────────────────────────────────────────────────────────────┐
│ KV 캐시 없이 생성 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ "오늘 날씨가" → [오늘, 날씨가] 전체 계산 → "좋" │
│ "오늘 날씨가 좋" → [오늘, 날씨가, 좋] 전체 재계산 → "습" │
│ "오늘 날씨가 좋습" → [오늘, 날씨가, 좋, 습] 전체 재계산 → "니" │
│ ... │
│ │
│ 문제: 매번 전체 시퀀스 다시 계산 → O(n²) 복잡도 │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ KV 캐시 사용 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ "오늘 날씨가" 처리: │
│ - K1, V1 (오늘) 계산 → 캐시 저장 │
│ - K2, V2 (날씨가) 계산 → 캐시 저장 │
│ → 출력: "좋" │
│ │
│ "좋" 추가 시: │
│ - K1, V1, K2, V2는 캐시에서 로드 (재계산 X) │
│ - K3, V3 (좋) 만 새로 계산 │
│ → 출력: "습" │
│ │
│ 효과: O(n²) → O(n) │
│ │
└─────────────────────────────────────────────────────────────────┘
KV 캐시 메모리 계산:
KV 캐시 크기 = 2 × 레이어 수 × 시퀀스 길이 × 히든 크기 × 배치 × 정밀도
예시: Llama 70B, 4K 시퀀스, 배치 1, FP16
= 2 × 80 × 4096 × 8192 × 1 × 2바이트
= 10.7 GB
시퀀스 8K면: 21.4 GB
시퀀스 32K면: 85.6 GB → VRAM 폭발!
→ 긴 컨텍스트 = 더 많은 메모리 필요
배치 (Batch) 처리¶
┌─────────────────────────────────────────────────────────────────┐
│ 배치 처리 효과 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 배치 1 (요청 하나씩): │
│ ┌───────┐ │
│ │ Req 1 │ ────────────────────────────────────▶ 완료 │
│ └───────┘ │
│ ┌───────┐ │
│ │ Req 2 │ ────────────────────────▶ 완료 │
│ └───────┘ │
│ 시간: ████████████████████████████████████ │
│ GPU 활용률: ~30% │
│ │
│ 배치 8 (동시 처리): │
│ ┌───────┐ │
│ │ Req 1 │ ──────────────▶ 완료 │
│ ├───────┤ │
│ │ Req 2 │ ──────────────▶ 완료 │
│ ├───────┤ │
│ │ ... │ ──────────────▶ 완료 │
│ ├───────┤ │
│ │ Req 8 │ ──────────────▶ 완료 │
│ └───────┘ │
│ 시간: ████████████████ │
│ GPU 활용률: ~90% │
│ │
│ 효과: 처리량 3-5배 향상 │
│ │
└─────────────────────────────────────────────────────────────────┘
5. 최적화 기초¶
왜 양자화를 하는가?¶
┌─────────────────────────────────────────────────────────────────┐
│ 양자화 효과 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ FP16 → INT4 양자화: │
│ │
│ 메모리: 140GB → 35GB (75% 감소) │
│ 속도: 1x → 2-3x (메모리 대역폭 여유) │
│ 품질: 100% → 95-98% (약간의 손실) │
│ │
│ 트레이드오프: │
│ ┌─────────────────────────────────────────────┐ │
│ │ 품질 │ │
│ │ 100% ┤ ●FP16 │ │
│ │ 98% ┤ ●INT8 │ │
│ │ 95% ┤ ●INT4 │ │
│ │ 90% ┤ ●INT2 │ │
│ │ └───────────────────────────────── │ │
│ │ 메모리 사용량 → │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
최적화 체크리스트¶
| 최적화 기법 | 효과 | 비용 | 난이도 |
|---|---|---|---|
| 양자화 (INT8) | 메모리 50% 감소 | 품질 1-2% 손실 | 쉬움 |
| 양자화 (INT4) | 메모리 75% 감소 | 품질 3-5% 손실 | 쉬움 |
| KV 캐시 최적화 | 긴 컨텍스트 가능 | 구현 복잡도 | 중간 |
| 배치 처리 | 처리량 3-5배 | 지연시간 증가 | 쉬움 |
| Flash Attention | 메모리 효율 | - | 중간 |
| 텐서 병렬화 | 큰 모델 가능 | 다중 GPU 필요 | 어려움 |
정리¶
LLM 최적화를 위해 알아야 할 핵심:
- GPU가 핵심 - CPU 아닌 GPU에서 추론
- 메모리가 병목 - 연산보다 메모리 대역폭이 한계
- 정밀도가 크기 결정 - FP16 → INT4로 4배 압축
- KV 캐시가 메모리 잡아먹음 - 긴 컨텍스트 = 더 많은 메모리
- 배치가 효율 결정 - 동시 처리로 GPU 활용률 극대화
이 기초를 알면 왜 vLLM이 빠른지, 왜 양자화가 필요한지, 왜 70B 모델에 A100이 필요한지 이해할 수 있다.