Upload 3 files
Browse files- README.md +60 -0
- chat.py +199 -0
- comment_gpt_finetuned.pt +3 -0
README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
tags:
|
| 4 |
+
- text-generation
|
| 5 |
+
- char-level
|
| 6 |
+
- comment_gpt
|
| 7 |
+
- youtube-shorts
|
| 8 |
+
- tiny
|
| 9 |
+
language: en
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# GPT-5horts
|
| 13 |
+
|
| 14 |
+
*(not affiliated with, endorsed by, or aware of OpenAI)*
|
| 15 |
+
|
| 16 |
+
A 6-layer, 6-head, 384-dim character-level GPT (~10.7M params), fine-tuned exclusively on scraped YouTube Shorts comments. It has fully absorbed the linguistic patterns of that environment. This was not a mistake, this was the plan.
|
| 17 |
+
|
| 18 |
+
## Model description
|
| 19 |
+
|
| 20 |
+
GPT-5horts predicts one character at a time and has learned, with complete confidence, that this is how YouTube Shorts commenters actually talk. It does not know what Roblox is but mentions it constantly. It has strong opinions about "gayme" (its word, not ours). It occasionally threatens legal action against people who do not exist.
|
| 21 |
+
|
| 22 |
+
## Benchmarks
|
| 23 |
+
|
| 24 |
+
Run in-house against 8 held-out prompts (temp 0.8, top_k 40, 150 tokens each). Results are real, unedited model output.
|
| 25 |
+
|
| 26 |
+
| Benchmark | Score |
|
| 27 |
+
|---|---|
|
| 28 |
+
| Average response length | 90.1 characters |
|
| 29 |
+
| Emoji deployed | 15 across 8 responses (1.9 emoji/response) |
|
| 30 |
+
| ALL-CAPS WORDS | 5 |
|
| 31 |
+
| Question marks fired | 11 |
|
| 32 |
+
| Times it brought up Roblox unprompted | 2 |
|
| 33 |
+
| Coherence | Present, briefly, then gone |
|
| 34 |
+
| Vibes | Immaculate |
|
| 35 |
+
|
| 36 |
+
## Sample outputs
|
| 37 |
+
|
| 38 |
+
> "the way she dunt finally someone this AHHH MOST POPULAR SHORT OF APPIL?????????!"
|
| 39 |
+
|
| 40 |
+
> "pov: 🗿🗿 The swindows people liking fishing 🗿 The people batter 🗿 The sound engling it on dang 🗿"
|
| 41 |
+
|
| 42 |
+
> "not me it safe, I was just gonna have a feel them dying dont have some but existed which school😢😢😢😢😢😢 why u sy así delete roblox"
|
| 43 |
+
|
| 44 |
+
> "bro really concerning to the predators on where?? No is metapper. So They were secues will never who made be made umm"
|
| 45 |
+
|
| 46 |
+
## Intended use
|
| 47 |
+
|
| 48 |
+
Commenting on things. Poorly. With feeling.
|
| 49 |
+
|
| 50 |
+
## Limitations
|
| 51 |
+
|
| 52 |
+
Cannot spell. Occasionally threatens legal action against fictional people. Has never heard of context. Mentions Roblox with no relevant prompting. Emotional state fluctuates mid-sentence for no stated reason.
|
| 53 |
+
|
| 54 |
+
## Usage
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
python chat.py --ckpt comment_gpt_finetuned.pt
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Empty prompt = generates from scratch. Ctrl+C to quit before it starts crying about Roblox again.
|
chat.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
chat.py
|
| 3 |
+
Interactive REPL for generating YouTube-Shorts-style comments from your
|
| 4 |
+
trained checkpoint (comment_gpt.pt). Type a prompt, get a comment back.
|
| 5 |
+
Empty prompt = generate from scratch.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python chat.py --ckpt comment_gpt.pt
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import torch
|
| 13 |
+
import torch.nn as nn
|
| 14 |
+
from torch.nn import functional as F
|
| 15 |
+
|
| 16 |
+
BLOCK_SIZE = 128
|
| 17 |
+
N_LAYER = 6
|
| 18 |
+
N_HEAD = 6
|
| 19 |
+
N_EMBD = 384
|
| 20 |
+
DROPOUT = 0.1
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_device():
|
| 24 |
+
if torch.backends.mps.is_available():
|
| 25 |
+
return "mps"
|
| 26 |
+
if torch.cuda.is_available():
|
| 27 |
+
return "cuda"
|
| 28 |
+
return "cpu"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class Head(nn.Module):
|
| 32 |
+
def __init__(self, head_size):
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.key = nn.Linear(N_EMBD, head_size, bias=False)
|
| 35 |
+
self.query = nn.Linear(N_EMBD, head_size, bias=False)
|
| 36 |
+
self.value = nn.Linear(N_EMBD, head_size, bias=False)
|
| 37 |
+
self.register_buffer("tril", torch.tril(torch.ones(BLOCK_SIZE, BLOCK_SIZE)))
|
| 38 |
+
self.dropout = nn.Dropout(DROPOUT)
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
B, T, C = x.shape
|
| 42 |
+
k = self.key(x)
|
| 43 |
+
q = self.query(x)
|
| 44 |
+
wei = q @ k.transpose(-2, -1) * (C ** -0.5)
|
| 45 |
+
wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
|
| 46 |
+
wei = F.softmax(wei, dim=-1)
|
| 47 |
+
wei = self.dropout(wei)
|
| 48 |
+
v = self.value(x)
|
| 49 |
+
return wei @ v
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class MultiHeadAttention(nn.Module):
|
| 53 |
+
def __init__(self, num_heads, head_size):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
|
| 56 |
+
self.proj = nn.Linear(N_EMBD, N_EMBD)
|
| 57 |
+
self.dropout = nn.Dropout(DROPOUT)
|
| 58 |
+
|
| 59 |
+
def forward(self, x):
|
| 60 |
+
out = torch.cat([h(x) for h in self.heads], dim=-1)
|
| 61 |
+
return self.dropout(self.proj(out))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class FeedForward(nn.Module):
|
| 65 |
+
def __init__(self, n_embd):
|
| 66 |
+
super().__init__()
|
| 67 |
+
self.net = nn.Sequential(
|
| 68 |
+
nn.Linear(n_embd, 4 * n_embd),
|
| 69 |
+
nn.GELU(),
|
| 70 |
+
nn.Linear(4 * n_embd, n_embd),
|
| 71 |
+
nn.Dropout(DROPOUT),
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def forward(self, x):
|
| 75 |
+
return self.net(x)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class Block(nn.Module):
|
| 79 |
+
def __init__(self, n_embd, n_head):
|
| 80 |
+
super().__init__()
|
| 81 |
+
head_size = n_embd // n_head
|
| 82 |
+
self.sa = MultiHeadAttention(n_head, head_size)
|
| 83 |
+
self.ffwd = FeedForward(n_embd)
|
| 84 |
+
self.ln1 = nn.LayerNorm(n_embd)
|
| 85 |
+
self.ln2 = nn.LayerNorm(n_embd)
|
| 86 |
+
|
| 87 |
+
def forward(self, x):
|
| 88 |
+
x = x + self.sa(self.ln1(x))
|
| 89 |
+
x = x + self.ffwd(self.ln2(x))
|
| 90 |
+
return x
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class CommentGPT(nn.Module):
|
| 94 |
+
def __init__(self, vocab_size):
|
| 95 |
+
super().__init__()
|
| 96 |
+
self.token_embedding = nn.Embedding(vocab_size, N_EMBD)
|
| 97 |
+
self.position_embedding = nn.Embedding(BLOCK_SIZE, N_EMBD)
|
| 98 |
+
self.blocks = nn.Sequential(*[Block(N_EMBD, N_HEAD) for _ in range(N_LAYER)])
|
| 99 |
+
self.ln_f = nn.LayerNorm(N_EMBD)
|
| 100 |
+
self.lm_head = nn.Linear(N_EMBD, vocab_size)
|
| 101 |
+
self.vocab_size = vocab_size
|
| 102 |
+
|
| 103 |
+
def forward(self, idx, targets=None):
|
| 104 |
+
B, T = idx.shape
|
| 105 |
+
tok_emb = self.token_embedding(idx)
|
| 106 |
+
pos_emb = self.position_embedding(torch.arange(T, device=idx.device))
|
| 107 |
+
x = tok_emb + pos_emb
|
| 108 |
+
x = self.blocks(x)
|
| 109 |
+
x = self.ln_f(x)
|
| 110 |
+
logits = self.lm_head(x)
|
| 111 |
+
return logits, None
|
| 112 |
+
|
| 113 |
+
@torch.no_grad()
|
| 114 |
+
def generate(self, idx, max_new_tokens, temperature=0.8, top_k=40):
|
| 115 |
+
for _ in range(max_new_tokens):
|
| 116 |
+
idx_cond = idx[:, -BLOCK_SIZE:]
|
| 117 |
+
logits, _ = self(idx_cond)
|
| 118 |
+
logits = logits[:, -1, :] / temperature
|
| 119 |
+
if top_k is not None:
|
| 120 |
+
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
| 121 |
+
logits[logits < v[:, [-1]]] = -float("inf")
|
| 122 |
+
probs = F.softmax(logits, dim=-1)
|
| 123 |
+
idx_next = torch.multinomial(probs, num_samples=1)
|
| 124 |
+
idx = torch.cat((idx, idx_next), dim=1)
|
| 125 |
+
return idx
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class CharTokenizer:
|
| 129 |
+
def __init__(self, stoi, itos):
|
| 130 |
+
self.stoi = stoi
|
| 131 |
+
self.itos = {int(k): v for k, v in itos.items()}
|
| 132 |
+
|
| 133 |
+
def encode(self, s):
|
| 134 |
+
# skip characters not seen during training instead of crashing
|
| 135 |
+
return [self.stoi[c] for c in s if c in self.stoi]
|
| 136 |
+
|
| 137 |
+
def decode(self, ids):
|
| 138 |
+
return "".join(self.itos[i] for i in ids)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def load_model(ckpt_path, device):
|
| 142 |
+
ckpt = torch.load(ckpt_path, map_location=device)
|
| 143 |
+
model = CommentGPT(ckpt["vocab_size"]).to(device)
|
| 144 |
+
model.load_state_dict(ckpt["model_state"])
|
| 145 |
+
model.eval()
|
| 146 |
+
tokenizer = CharTokenizer(ckpt["stoi"], ckpt["itos"])
|
| 147 |
+
return model, tokenizer
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def generate_comment(model, tokenizer, device, prompt="", max_new_tokens=200,
|
| 151 |
+
temperature=0.8, top_k=40):
|
| 152 |
+
if prompt:
|
| 153 |
+
ids = tokenizer.encode(prompt)
|
| 154 |
+
if not ids:
|
| 155 |
+
ids = [0]
|
| 156 |
+
else:
|
| 157 |
+
ids = [0]
|
| 158 |
+
context = torch.tensor([ids], dtype=torch.long, device=device)
|
| 159 |
+
out = model.generate(context, max_new_tokens=max_new_tokens,
|
| 160 |
+
temperature=temperature, top_k=top_k)[0].tolist()
|
| 161 |
+
text = tokenizer.decode(out)
|
| 162 |
+
# cut at the first <|end|> after the prompt so you get one clean comment
|
| 163 |
+
text = text.split("<|end|>")[0].strip()
|
| 164 |
+
return text
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def main():
|
| 168 |
+
p = argparse.ArgumentParser()
|
| 169 |
+
p.add_argument("--ckpt", default="comment_gpt.pt")
|
| 170 |
+
p.add_argument("--temperature", type=float, default=0.8)
|
| 171 |
+
p.add_argument("--top_k", type=int, default=40)
|
| 172 |
+
p.add_argument("--max_new_tokens", type=int, default=200)
|
| 173 |
+
args = p.parse_args()
|
| 174 |
+
|
| 175 |
+
device = get_device()
|
| 176 |
+
print(f"using device: {device}")
|
| 177 |
+
print(f"loading {args.ckpt}...")
|
| 178 |
+
model, tokenizer = load_model(args.ckpt, device)
|
| 179 |
+
print("loaded. type a prompt (or leave blank) and hit enter. ctrl+c to quit.\n")
|
| 180 |
+
|
| 181 |
+
while True:
|
| 182 |
+
try:
|
| 183 |
+
prompt = input("> ")
|
| 184 |
+
except (KeyboardInterrupt, EOFError):
|
| 185 |
+
print("\nbye")
|
| 186 |
+
break
|
| 187 |
+
comment = generate_comment(
|
| 188 |
+
model, tokenizer, device,
|
| 189 |
+
prompt=prompt,
|
| 190 |
+
max_new_tokens=args.max_new_tokens,
|
| 191 |
+
temperature=args.temperature,
|
| 192 |
+
top_k=args.top_k,
|
| 193 |
+
)
|
| 194 |
+
print(comment)
|
| 195 |
+
print()
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
if __name__ == "__main__":
|
| 199 |
+
main()
|
comment_gpt_finetuned.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cc04b5e999d03ff6b85564fcfe70fcc939a47ca5ddd64ac93a83044ec05f3e08
|
| 3 |
+
size 46874411
|