| """๋ฐฐํฌ์ฉ ๋
๋ฆฝ ๋ชจ๋ธ ์ ์. |
| |
| ์ด ํ์ผ ํ๋์ `config.json`, `model.safetensors`, `tokenizer.json` ๋ง ์์ผ๋ฉด |
| ๊ฐ์ค์น๋ฅผ ์ด ์ ์๋ค. ํ์ต ํ๋ค์ค(deeptool)๋ ํ์ ์๋ค -- ๊ฐ์ค์น ํ๋ ์ด์๊ณ |
| ํ์ต์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๊ฒ ๋ง๋ค ์ด์ ๊ฐ ์๋ค. |
| |
| `src/tinyllm/model.py` ์ TinyLM ๊ณผ ๋ชจ๋ ์ด๋ฆ์ด ๊ฐ์์ผ state_dict ํค๊ฐ ๋ง๋๋ค. |
| ๊ทธ ๋์ผ์ฑ์ tests/test_modeling.py ๊ฐ ์ค์ ์ฒดํฌํฌ์ธํธ๋ก ๊ฒ์ฆํ๋ค. |
| |
| from modeling_tinyllm import TinyLM |
| model = TinyLM.from_pretrained(".") |
| """ |
|
|
| import json |
| from pathlib import Path |
|
|
| import torch |
| from torch import nn |
|
|
|
|
| class TinyLM(nn.Module): |
| """GPT-2 ํํ์ decoder-only LM, torch ๋ด์ฅ ๋ ์ด์ด๋ก ์กฐ๋ฆฝ. |
| |
| decoder-only ๋ฅผ `TransformerEncoderLayer` ๋ก ๋ง๋๋ ์ด์ ๋ |
| `TransformerDecoderLayer` ๊ฐ cross-attention ์ฉ `memory` ๋ฅผ ํ์๋ก ์๊ตฌํ๊ธฐ |
| ๋๋ฌธ์ด๋ค. causal mask ๋ฅผ ๋๊ธฐ๋ฉด encoder layer ๊ฐ ๊ณง GPT ๋ธ๋ก์ด๋ค. |
| """ |
|
|
| def __init__(self, vocab_size=8000, d_model=512, n_head=8, n_layer=8, |
| d_ff=2048, block_size=512, dropout=0.0): |
| super().__init__() |
| self.vocab_size = vocab_size |
| self.block_size = block_size |
|
|
| self.tok = nn.Embedding(vocab_size, d_model) |
| self.pos = nn.Embedding(block_size, d_model) |
| self.drop = nn.Dropout(dropout) |
| layer = nn.TransformerEncoderLayer( |
| d_model, n_head, d_ff, dropout, |
| activation="gelu", norm_first=True, batch_first=True, |
| ) |
| self.blocks = nn.TransformerEncoder( |
| layer, n_layer, norm=nn.RMSNorm(d_model), enable_nested_tensor=False, |
| ) |
| self.head = nn.Linear(d_model, vocab_size, bias=False) |
| self.head.weight = self.tok.weight |
|
|
| def forward(self, ids): |
| """ids: (B, T) int64 -> logits (B, T, vocab_size)""" |
| T = ids.size(1) |
| pos = torch.arange(T, device=ids.device) |
| h = self.drop(self.tok(ids) + self.pos(pos)) |
| mask = nn.Transformer.generate_square_subsequent_mask(T, device=ids.device) |
| return self.head(self.blocks(h, mask=mask, is_causal=True)) |
|
|
| @torch.no_grad() |
| def generate(self, ids, max_new_tokens, temperature=0.8, top_k=40, |
| stop_id=None): |
| """ids: (B, T) ํ๋กฌํํธ -> (B, T + n). stop_id ๊ฐ ์ ๋ฐฐ์น์ ๋์ค๋ฉด ์กฐ๊ธฐ ์ข
๋ฃ. |
| |
| KV ์บ์๋ ์ฐ์ง ์๋๋ค. 29M ยท 512 ํ ํฐ์ด๋ฉด forward ๊ฐ ์ ms ๋ผ ์บ์๊ฐ ์์ด๋ |
| 100 ํ ํฐ ์์ฑ์ด 1 ์ด ๋ฏธ๋ง์ด๋ค. |
| """ |
| was_training = self.training |
| self.eval() |
| for _ in range(max_new_tokens): |
| window = ids[:, -self.block_size:] |
| logits = self(window)[:, -1] / temperature |
| if top_k: |
| kth = logits.topk(min(top_k, logits.size(-1)), dim=-1).values[:, -1:] |
| logits = logits.masked_fill(logits < kth, float("-inf")) |
| nxt = torch.multinomial(logits.softmax(-1), num_samples=1) |
| ids = torch.cat([ids, nxt], dim=1) |
| if stop_id is not None and bool((nxt == stop_id).all()): |
| break |
| self.train(was_training) |
| return ids |
|
|
| @classmethod |
| def from_pretrained(cls, path, device="cpu"): |
| """`config.json` ๊ณผ `model.safetensors` ๊ฐ ์๋ ํด๋์์ ๋ชจ๋ธ์ ์ธ์ด๋ค. |
| |
| ์ ์ฅ๋ณธ์๋ `head.weight` ๊ฐ ์๋ค. tying ๋๋ฌธ์ `tok.weight` ์ ์ ์ฅ์๋ฅผ |
| ๊ณต์ ํ๋๋ฐ safetensors ๋ ๊ณต์ ์ ์ฅ์๋ฅผ ๊ฑฐ๋ถํ๊ธฐ ๋๋ฌธ์ด๋ค. ์์ฑ์๊ฐ ๋ค์ |
| ๋ฌถ์ผ๋ฏ๋ก `strict=False` ๋ก ๋ฃ์ด๋ head ๊ฐ ๋น์ง ์๋๋ค. |
| """ |
| from safetensors.torch import load_file |
|
|
| path = Path(path) |
| config = json.loads((path / "config.json").read_text()) |
| model = cls(**{k: config[k] for k in |
| ("vocab_size", "d_model", "n_head", "n_layer", |
| "d_ff", "block_size")}) |
| state = load_file(path / "model.safetensors") |
| missing, unexpected = model.load_state_dict(state, strict=False) |
| if unexpected: |
| raise ValueError(f"์ ์ฅ๋ณธ์ ๋ชจ๋ฅด๋ ํ
์๊ฐ ์๋ค: {unexpected}") |
| if missing != ["head.weight"]: |
| raise ValueError(f"๋น ์ง ํ
์๊ฐ head.weight ๋ง์ด ์๋๋ค: {missing}") |
| return model.to(device).eval() |
|
|