콘텐츠로 이동

LLM을 위한 기초 CS 지식

LLM/VLM 기술을 제대로 이해하고 최적화하기 위해 알아야 할 컴퓨터 과학 기초


왜 이 지식이 필요한가?

LLM을 단순히 "사용"하는 것과 "최적화"하는 것은 다르다. 최적화를 위해서는:

  • 모델이 GPU에서 어떻게 실행되는지
  • 메모리가 어떻게 관리되는지
  • 숫자가 컴퓨터에서 어떻게 표현되는지

이런 기초를 알아야 왜 양자화가 효과적인지, KV 캐시가 왜 필요한지 이해할 수 있다.


목차

  1. 컴퓨터 기초 - 프로세스, 메모리, CPU vs GPU
  2. 숫자 표현 - FP32, FP16, INT8, 정밀도
  3. 메모리 계층 - RAM, VRAM, 캐시
  4. LLM 핵심 개념 - 토큰, KV 캐시, 배치
  5. 최적화 기초 - 왜 양자화? 왜 배치?

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 최적화를 위해 알아야 할 핵심:

  1. GPU가 핵심 - CPU 아닌 GPU에서 추론
  2. 메모리가 병목 - 연산보다 메모리 대역폭이 한계
  3. 정밀도가 크기 결정 - FP16 → INT4로 4배 압축
  4. KV 캐시가 메모리 잡아먹음 - 긴 컨텍스트 = 더 많은 메모리
  5. 배치가 효율 결정 - 동시 처리로 GPU 활용률 극대화

이 기초를 알면 왜 vLLM이 빠른지, 왜 양자화가 필요한지, 왜 70B 모델에 A100이 필요한지 이해할 수 있다.