바닥부터 만들어보는 LLM

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" 입력 처리 과정

  1. 토큰화
'h' → 4, 'e' → 2, 'l' → 5
→ [4, 2, 5]
  1. 임베딩
[4, 2, 5] → [[0.1, 0.3], [0.4, 0.2], [0.5, 0.6]]
  1. 트랜스포머 처리
  • 문맥 분석 ('l' 다음에 'o'가 자주 등장)
  • 벡터 업데이트
  1. 출력층
  • 'o' 확률: 85%
  • 다른 문자 확률: 15%
  1. 최종 출력
'o' 선택 → "hello" 완성

핵심 원리

처리 파이프라인

텍스트 → 토큰화 → 임베딩 → 트랜스포머 처리 → 출력 생성

학습 목표

  • 언어의 통계적 패턴 학습
  • 문맥에 맞는 다음 단어 예측
  • 문법적, 의미적 규칙 포착

간단한 LLM 구현하기

1. 개발 환경 설정

필수 사항

  • Python 3.8+
  • pip 패키지 관리자

의존성 설치

pip install torch numpy

설치 확인

import torch
import numpy as np

print(f"PyTorch 버전: {torch.__version__}")
print(f"NumPy 버전: {np.__version__}")

2. 아키텍처 개요

데이터 처리 흐름

[입력 텍스트] → [토큰화] → [임베딩] → [트랜스포머] → [출력 예측] → [다음 토큰]

주요 컴포넌트

  1. 토크나이저: 텍스트 → 토큰 ID
  2. 임베딩 레이어: 토큰 ID → 벡터
  3. 트랜스포머: 문맥 이해
  4. 출력층: 다음 토큰 예측

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. 학습 리소스

추천 자료

리소스 설명
nanoGPT Karpathy의 GPT 코드 기반
minGPT 간단한 GPT 구현체
Andrej Karpathy 유튜브 이론과 구현 설명

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))

실행 방법

  1. data.txt 파일에 학습할 텍스트 추가
  2. 스크립트 실행:
python mini_transformer_gpt.py

댓글 남기기