LLM 구현 가이드: 바닥부터 만들어보는 대화형 언어 모델
LLM 처리 단계 요약
단계 |
입력 |
출력 |
설명 |
1. 토큰화 |
"hello" |
[2, 5, 6, 6, 7] |
문자를 숫자로 변환합니다. 컴퓨터가 텍스트를 이해할 수 있도록 각 문자를 고유한 숫자로 매핑합니다. |
2. 임베딩 |
[2, 5, 6, 6, 7] |
[[0.1, 0.3], [0.2, 0.4], ...] |
숫자 토큰을 고차원 벡터로 변환합니다. 단어 간의 의미적 관계를 수치화합니다. |
3. 트랜스포머 |
임베딩 벡터 |
컨텍스트 벡터 |
주변 단어의 문맥을 고려하여 각 단어의 표현을 개선합니다. |
4. 출력층 |
벡터 |
확률 분포 |
현재까지의 문맥을 바탕으로 다음에 올 단어의 확률을 계산합니다. |
5. 토큰 예측 |
확률 분포 |
선택된 토큰 |
가장 높은 확률을 가진 토큰을 선택하여 최종 출력을 생성합니다. |
예제: "hel" 입력 처리 과정
- 토큰화
'h' → 4, 'e' → 2, 'l' → 5
→ [4, 2, 5]
- 임베딩
[4, 2, 5] → [[0.1, 0.3], [0.4, 0.2], [0.5, 0.6]]
- 트랜스포머 처리
- 문맥 분석 ('l' 다음에 'o'가 자주 등장)
- 벡터 업데이트
- 출력층
- 'o' 확률: 85%
- 다른 문자 확률: 15%
- 최종 출력
'o' 선택 → "hello" 완성
핵심 원리
처리 파이프라인
텍스트 → 토큰화 → 임베딩 → 트랜스포머 처리 → 출력 생성
학습 목표
- 언어의 통계적 패턴 학습
- 문맥에 맞는 다음 단어 예측
- 문법적, 의미적 규칙 포착
간단한 LLM 구현하기
1. 개발 환경 설정
필수 사항
의존성 설치
pip install torch numpy
설치 확인
import torch
import numpy as np
print(f"PyTorch 버전: {torch.__version__}")
print(f"NumPy 버전: {np.__version__}")
2. 아키텍처 개요
데이터 처리 흐름
[입력 텍스트] → [토큰화] → [임베딩] → [트랜스포머] → [출력 예측] → [다음 토큰]
주요 컴포넌트
- 토크나이저: 텍스트 → 토큰 ID
- 임베딩 레이어: 토큰 ID → 벡터
- 트랜스포머: 문맥 이해
- 출력층: 다음 토큰 예측
3. 구현 예제
학습 데이터
data = "hello world. hello ai. hello gpt."
문자 기반 토크나이저
chars = sorted(list(set(data)))
vocab_size = len(chars)
# 문자 ↔ 정수 변환
stoi = {ch:i for i,ch in enumerate(chars)}
itos = {i:ch for ch,i in stoi.items()}
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
모델 정의
import torch
import torch.nn as nn
import torch.nn.functional as F
class TinyGPT(nn.Module):
def __init__(self, vocab_size, n_embd):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, n_embd)
self.linear = nn.Linear(n_embd, vocab_size)
def forward(self, idx):
emb = self.token_embedding(idx)
logits = self.linear(emb)
return logits
학습 루프
model = TinyGPT(vocab_size, n_embd=16)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
# 학습 데이터 준비
block_size = 4
xs, ys = [], []
for i in range(len(data) - block_size):
x = encode(data[i:i+block_size])
y = encode(data[i+1:i+block_size+1])
xs.append(x)
ys.append(y)
X = torch.tensor(xs)
Y = torch.tensor(ys)
# 학습 실행
for step in range(1000):
logits = model(X)
loss = F.cross_entropy(logits.view(-1, vocab_size), Y.view(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if step % 100 == 0:
print(f"step {step}, loss {loss.item():.4f}")
텍스트 생성
def generate(model, start, max_new_tokens):
model.eval()
idx = torch.tensor([encode(start)])
for _ in range(max_new_tokens):
logits = model(idx)
last_logits = logits[0, -1]
probs = F.softmax(last_logits, dim=0)
next_id = torch.multinomial(probs, num_samples=1)
idx = torch.cat([idx, next_id.unsqueeze(0)], dim=1)
return decode(idx[0].tolist())
print(generate(model, start="hel", max_new_tokens=10))
4단계: 발전 방향
- 트랜스포머 블록 (Self-Attention 포함) 추가
- 더 긴 문장 처리
- 다층 구조
- 텍스트 파일 데이터셋으로 확장
- LayerNorm, Dropout 추가
- 정식 학습 데이터셋 (ex. Tiny Shakespeare)
4. 학습 리소스
추천 자료
5. 미니 GPT 구현
간단한 문자 기반 모델
# mini_gpt.py
import torch
import torch.nn as nn
import torch.nn.functional as F
# 1. 데이터 준비
data = "hello world. hello ai. hello gpt. "
chars = sorted(list(set(data)))
vocab_size = len(chars)
# 2. 인코딩/디코딩
stoi = {ch: i for i, ch in enumerate(chars)}
itos = {i: ch for ch, i in stoi.items()}
encode = lambda s: [stoi[c] for c in s]
decode = lambda l: ''.join([itos[i] for i in l])
# 3. 학습 데이터셋
block_size = 4
xs, ys = [], []
for i in range(len(data) - block_size):
x = encode(data[i:i+block_size])
y = encode(data[i+1:i+block_size+1])
xs.append(x)
ys.append(y)
X = torch.tensor(xs)
Y = torch.tensor(ys)
# 4. 모델 정의
class TinyGPT(nn.Module):
def __init__(self, vocab_size, n_embd):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, n_embd)
self.linear = nn.Linear(n_embd, vocab_size)
def forward(self, idx):
emb = self.token_embedding(idx)
return self.linear(emb)
# 5. 학습
model = TinyGPT(vocab_size, n_embd=16)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
for step in range(1000):
logits = model(X)
loss = F.cross_entropy(logits.view(-1, vocab_size), Y.view(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if step % 100 == 0:
print(f"step {step}, loss {loss.item():.4f}")
# 6. 텍스트 생성
def generate(model, start_text, max_new_tokens=20):
model.eval()
idx = torch.tensor([encode(start_text)], dtype=torch.long)
for _ in range(max_new_tokens):
logits = model(idx)
probs = F.softmax(logits[0, -1], dim=0)
next_token = torch.multinomial(probs, num_samples=1)
idx = torch.cat([idx, next_token.unsqueeze(0)], dim=1)
return decode(idx[0].tolist())
# 실행
print("n생성 결과:")
print(generate(model, "hell", 20))
실행 방법
python mini_gpt.py
6. 고급: 트랜스포머 기반 모델
데이터 준비
data.txt
파일 생성:
hello world this is a simple transformer model demo
전체 코드
# mini_transformer_gpt.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# 1. 데이터 로드
with open("data.txt", "r", encoding="utf-8") as f:
data = f.read()
# 2. 토크나이저
words = data.strip().split()
vocab = sorted(set(words))
vocab_size = len(vocab)
stoi = {word: i for i, word in enumerate(vocab)}
itos = {i: word for word, i in stoi.items()}
encode = lambda s: [stoi[w] for w in s.strip().split()]
decode = lambda l: ' '.join([itos[i] for i in l])
# 3. 하이퍼파라미터
block_size = 8
batch_size = 16
n_embd = 32
n_head = 2
n_layer = 2
# 4. 데이터 배치 생성
def get_batches(data_ids, batch_size):
xs, ys = [], []
for i in range(0, len(data_ids) - block_size):
x = data_ids[i:i+block_size]
y = data_ids[i+1:i+block_size+1]
xs.append(x)
ys.append(y)
return torch.tensor(xs[:batch_size]), torch.tensor(ys[:batch_size])
data_ids = encode(data)
X, Y = get_batches(data_ids, batch_size)
# 5. 포지셔널 인코딩
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=100):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.pe = pe.unsqueeze(0)
def forward(self, x):
return x + self.pe[:, :x.size(1)].to(x.device)
# 6. 셀프 어텐션
class SelfAttentionHead(nn.Module):
def __init__(self, head_size):
super().__init__()
self.key = nn.Linear(n_embd, head_size, bias=False)
self.query = nn.Linear(n_embd, head_size, bias=False)
self.value = nn.Linear(n_embd, head_size, bias=False)
self.dropout = nn.Dropout(0.1)
def forward(self, x):
B, T, C = x.shape
k = self.key(x)
q = self.query(x)
v = self.value(x)
scores = q @ k.transpose(-2, -1) / math.sqrt(k.size(-1))
mask = torch.tril(torch.ones(T, T)).to(x.device)
scores = scores.masked_fill(mask == 0, float('-inf'))
weights = F.softmax(scores, dim=-1)
return self.dropout(weights) @ v
# 7. 트랜스포머 블록
class TransformerBlock(nn.Module):
def __init__(self, n_embd, n_head):
super().__init__()
head_size = n_embd // n_head
self.heads = nn.ModuleList([SelfAttentionHead(head_size) for _ in range(n_head)])
self.proj = nn.Linear(n_embd, n_embd)
self.ln1 = nn.LayerNorm(n_embd)
self.ff = nn.Sequential(
nn.Linear(n_embd, 4 * n_embd),
nn.ReLU(),
nn.Linear(4 * n_embd, n_embd),
)
self.ln2 = nn.LayerNorm(n_embd)
def forward(self, x):
x = x + self.proj(torch.cat([h(x) for h in self.heads], dim=-1))
x = self.ln1(x)
x = x + self.ff(x)
return self.ln2(x)
# 8. 전체 모델
class MiniTransformer(nn.Module):
def __init__(self):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, n_embd)
self.positional_encoding = PositionalEncoding(n_embd)
self.blocks = nn.Sequential(*[TransformerBlock(n_embd, n_head) for _ in range(n_layer)])
self.ln_f = nn.LayerNorm(n_embd)
self.head = nn.Linear(n_embd, vocab_size)
def forward(self, idx):
tok_emb = self.token_embedding(idx)
x = self.positional_encoding(tok_emb)
x = self.blocks(x)
x = self.ln_f(x)
return self.head(x)
# 9. 학습
model = MiniTransformer()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for step in range(300):
logits = model(X)
loss = F.cross_entropy(logits.view(-1, vocab_size), Y.view(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if step % 50 == 0:
print(f"Step {step}, Loss: {loss.item():.4f}")
# 10. 텍스트 생성
def generate(model, start_text, max_new_tokens=20):
model.eval()
idx = torch.tensor([encode(start_text)], dtype=torch.long)
for _ in range(max_new_tokens):
idx_cond = idx[:, -block_size:]
logits = model(idx_cond)
probs = F.softmax(logits[0, -1], dim=0)
next_id = torch.multinomial(probs, num_samples=1)
idx = torch.cat([idx, next_id.view(1, 1)], dim=1)
return decode(idx[0].tolist())
# 실행
print("n생성 결과:")
print(generate(model, "hello", 20))
실행 방법
data.txt
파일에 학습할 텍스트 추가
- 스크립트 실행:
python mini_transformer_gpt.py