files for inference
Browse filesArchitecture, Huggingface generator and interaction code
- Architecture.py +146 -0
- GeneratorHF.py +199 -0
- chat.py +131 -0
Architecture.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class MaskedMultiHeadedSelfAttention(nn.Module):
|
| 7 |
+
def __init__(self, d_in, d_out, context_length, dropout, num_heads, qkv_bias=False):
|
| 8 |
+
super().__init__()
|
| 9 |
+
assert (d_out % num_heads == 0), "d_out must be divisible by num_heads"
|
| 10 |
+
|
| 11 |
+
self.d_out = d_out
|
| 12 |
+
self.num_heads = num_heads
|
| 13 |
+
self.head_dim = d_out // num_heads
|
| 14 |
+
|
| 15 |
+
# ββ Fused QKV projection (one matmul instead of three) ββββββββββββββ
|
| 16 |
+
self.W_qkv = nn.Linear(d_in, 3 * d_out, bias=qkv_bias)
|
| 17 |
+
self.out_proj = nn.Linear(d_out, d_out, bias=False)
|
| 18 |
+
self.dropout = nn.Dropout(dropout)
|
| 19 |
+
|
| 20 |
+
def forward(self, x):
|
| 21 |
+
batch_size, num_tokens, d_in = x.shape
|
| 22 |
+
|
| 23 |
+
# ββ Single fused projection then split ββββββββββββββββββββββββββββββ
|
| 24 |
+
qkv = self.W_qkv(x) # (B, T, 3 * d_out)
|
| 25 |
+
q, k, v = qkv.split(self.d_out, dim=-1) # each: (B, T, d_out)
|
| 26 |
+
|
| 27 |
+
# Reshape to (B, num_heads, T, head_dim)
|
| 28 |
+
q = q.view(batch_size, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)
|
| 29 |
+
k = k.view(batch_size, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)
|
| 30 |
+
v = v.view(batch_size, num_tokens, self.num_heads, self.head_dim).transpose(1, 2)
|
| 31 |
+
|
| 32 |
+
# ββ Flash Attention β no manual mask, is_causal handles it ββββββββββ
|
| 33 |
+
context_vec = torch.nn.functional.scaled_dot_product_attention(
|
| 34 |
+
q, k, v,
|
| 35 |
+
attn_mask=None,
|
| 36 |
+
dropout_p=self.dropout.p if self.training else 0.0,
|
| 37 |
+
is_causal=True
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
# Merge heads and project
|
| 41 |
+
context_vec = context_vec.transpose(1, 2).reshape(batch_size, num_tokens, self.d_out)
|
| 42 |
+
context_vec = self.out_proj(context_vec)
|
| 43 |
+
return context_vec
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class FeedForward(nn.Module):
|
| 47 |
+
def __init__(self, configuration):
|
| 48 |
+
super().__init__()
|
| 49 |
+
dim = configuration["embedding_dim"]
|
| 50 |
+
self.layers = nn.Sequential(
|
| 51 |
+
nn.Linear(dim, 4 * dim),
|
| 52 |
+
nn.GELU(approximate='tanh'), # fused kernel, faster than manual tanh approx
|
| 53 |
+
nn.Linear(4 * dim, dim),
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
return self.layers(x)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class TransformerBlock(nn.Module):
|
| 61 |
+
def __init__(self, configuration):
|
| 62 |
+
super().__init__()
|
| 63 |
+
self.attention = MaskedMultiHeadedSelfAttention(
|
| 64 |
+
d_in=configuration["embedding_dim"],
|
| 65 |
+
d_out=configuration["embedding_dim"],
|
| 66 |
+
context_length=configuration["context_length"],
|
| 67 |
+
num_heads=configuration["n_heads"],
|
| 68 |
+
dropout=configuration["dropout_rate"],
|
| 69 |
+
qkv_bias=configuration["qkv_bias"]
|
| 70 |
+
)
|
| 71 |
+
self.feed_forward = FeedForward(configuration)
|
| 72 |
+
|
| 73 |
+
# ββ nn.LayerNorm uses a fused CUDA kernel β faster than custom impl ββ
|
| 74 |
+
self.norm1 = nn.LayerNorm(configuration["embedding_dim"])
|
| 75 |
+
self.norm2 = nn.LayerNorm(configuration["embedding_dim"])
|
| 76 |
+
self.drop_shortcut = nn.Dropout(configuration["dropout_rate"])
|
| 77 |
+
|
| 78 |
+
def forward(self, x):
|
| 79 |
+
# ββ Attention block with residual ββββββββββββββββββββββββββββββββββββ
|
| 80 |
+
shortcut = x
|
| 81 |
+
x = self.norm1(x)
|
| 82 |
+
x = self.attention(x)
|
| 83 |
+
x = self.drop_shortcut(x)
|
| 84 |
+
x = x + shortcut
|
| 85 |
+
|
| 86 |
+
# ββ Feed-forward block with residual βββββββββββββββββββββββββββββββββ
|
| 87 |
+
shortcut = x
|
| 88 |
+
x = self.norm2(x)
|
| 89 |
+
x = self.feed_forward(x)
|
| 90 |
+
x = self.drop_shortcut(x)
|
| 91 |
+
x = x + shortcut
|
| 92 |
+
|
| 93 |
+
return x
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class LanguageModel(nn.Module):
|
| 97 |
+
def __init__(self, configuration):
|
| 98 |
+
super().__init__()
|
| 99 |
+
self.config = configuration
|
| 100 |
+
|
| 101 |
+
self.token_embedding = nn.Embedding(configuration["vocab_size"], configuration["embedding_dim"])
|
| 102 |
+
self.pos_embedding = nn.Embedding(configuration["context_length"], configuration["embedding_dim"])
|
| 103 |
+
self.drop_embedding = nn.Dropout(configuration["dropout_rate"])
|
| 104 |
+
|
| 105 |
+
self.transformer_blocks = nn.Sequential(
|
| 106 |
+
*[TransformerBlock(configuration) for _ in range(configuration["n_layers"])]
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# ββ Final norm also switched to nn.LayerNorm βββββββββββββββββββββββββ
|
| 110 |
+
self.final_norm = nn.LayerNorm(configuration["embedding_dim"])
|
| 111 |
+
self.out_head = nn.Linear(configuration["embedding_dim"], configuration["vocab_size"], bias=False)
|
| 112 |
+
|
| 113 |
+
# Weight tying β output projection shares weights with token embedding
|
| 114 |
+
self.out_head.weight = self.token_embedding.weight
|
| 115 |
+
|
| 116 |
+
# ββ GPT-2 style weight initialization ββββββββββββββββββββββββββββββββ
|
| 117 |
+
self.apply(self._init_weights)
|
| 118 |
+
|
| 119 |
+
# Scale residual-path projections by 1/β(2*n_layers) to prevent the
|
| 120 |
+
# residual stream from blowing up in deep networks. Targets:
|
| 121 |
+
# - attention out_proj (ends with 'out_proj.weight')
|
| 122 |
+
# - FFN second linear (ends with 'layers.2.weight')
|
| 123 |
+
residual_std = 0.02 / math.sqrt(2 * configuration["n_layers"])
|
| 124 |
+
for pn, p in self.named_parameters():
|
| 125 |
+
if pn.endswith('out_proj.weight') or pn.endswith('layers.2.weight'):
|
| 126 |
+
torch.nn.init.normal_(p, mean=0.0, std=residual_std)
|
| 127 |
+
|
| 128 |
+
def _init_weights(self, module):
|
| 129 |
+
"""Initialize weights following GPT-2: N(0, 0.02) for all projections."""
|
| 130 |
+
if isinstance(module, nn.Linear):
|
| 131 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 132 |
+
if module.bias is not None:
|
| 133 |
+
torch.nn.init.zeros_(module.bias)
|
| 134 |
+
elif isinstance(module, nn.Embedding):
|
| 135 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 136 |
+
|
| 137 |
+
def forward(self, in_idx):
|
| 138 |
+
batch_size, seq_len = in_idx.shape
|
| 139 |
+
tok_embeds = self.token_embedding(in_idx)
|
| 140 |
+
pos_embeds = self.pos_embedding(torch.arange(seq_len, device=in_idx.device))
|
| 141 |
+
x = tok_embeds + pos_embeds
|
| 142 |
+
x = self.drop_embedding(x)
|
| 143 |
+
x = self.transformer_blocks(x)
|
| 144 |
+
x = self.final_norm(x)
|
| 145 |
+
logits = self.out_head(x)
|
| 146 |
+
return logits
|
GeneratorHF.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
HuggingFace-compatible generator for custom LanguageModel.
|
| 3 |
+
|
| 4 |
+
Wraps the custom model in a minimal HF-compatible interface so we can use
|
| 5 |
+
transformers.generate() with all its bells and whistles (beam search,
|
| 6 |
+
contrastive search, repetition penalty, etc.) while keeping our own weights.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
from GeneratorHF import HFTextGenerator
|
| 10 |
+
|
| 11 |
+
gen = HFTextGenerator(model, tokenizer, device, context_size=1024)
|
| 12 |
+
text = gen.generate("Once upon a time", max_new_tokens=200)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
from transformers import GenerationConfig, GenerationMixin, PretrainedConfig
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class LanguageModelHF(nn.Module, GenerationMixin):
|
| 21 |
+
"""Thin wrapper that makes our custom LanguageModel compatible with
|
| 22 |
+
HuggingFace's GenerationMixin (generate, beam_search, sample, etc.)."""
|
| 23 |
+
|
| 24 |
+
_is_stateful = False
|
| 25 |
+
_supports_cache_class = False
|
| 26 |
+
|
| 27 |
+
def __init__(self, model, context_size, device):
|
| 28 |
+
super().__init__()
|
| 29 |
+
self.model = model
|
| 30 |
+
self.config = _make_hf_config(model, context_size)
|
| 31 |
+
self.generation_config = GenerationConfig(
|
| 32 |
+
max_new_tokens=200,
|
| 33 |
+
do_sample=True,
|
| 34 |
+
temperature=0.8,
|
| 35 |
+
top_k=40,
|
| 36 |
+
top_p=0.95,
|
| 37 |
+
repetition_penalty=1.1,
|
| 38 |
+
eos_token_id=50256,
|
| 39 |
+
pad_token_id=50256,
|
| 40 |
+
)
|
| 41 |
+
self.main_input_name = "input_ids"
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def device(self):
|
| 45 |
+
return next(self.parameters()).device
|
| 46 |
+
|
| 47 |
+
def forward(self, input_ids, attention_mask=None, **kwargs):
|
| 48 |
+
# Trim to context window (HF might feed longer sequences)
|
| 49 |
+
input_ids = input_ids[:, -self.config.n_positions:]
|
| 50 |
+
logits = self.model(input_ids)
|
| 51 |
+
return _CausalLMOutput(logits=logits)
|
| 52 |
+
|
| 53 |
+
def prepare_inputs_for_generation(self, input_ids, attention_mask=None, **kwargs):
|
| 54 |
+
return {"input_ids": input_ids}
|
| 55 |
+
|
| 56 |
+
def can_generate(self):
|
| 57 |
+
return True
|
| 58 |
+
|
| 59 |
+
def _reorder_cache(self, past, beam_idx):
|
| 60 |
+
return past
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class _CausalLMOutput:
|
| 64 |
+
"""Minimal output container matching HF's CausalLMOutput interface."""
|
| 65 |
+
def __init__(self, logits):
|
| 66 |
+
self.logits = logits
|
| 67 |
+
self.past_key_values = None
|
| 68 |
+
self.hidden_states = None
|
| 69 |
+
self.attentions = None
|
| 70 |
+
|
| 71 |
+
def __getitem__(self, key):
|
| 72 |
+
return getattr(self, key)
|
| 73 |
+
|
| 74 |
+
def __contains__(self, key):
|
| 75 |
+
return hasattr(self, key) and getattr(self, key) is not None
|
| 76 |
+
|
| 77 |
+
def keys(self):
|
| 78 |
+
return [k for k in ["logits", "past_key_values", "hidden_states", "attentions"]
|
| 79 |
+
if getattr(self, k, None) is not None]
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class _MinimalConfig(PretrainedConfig):
|
| 83 |
+
"""Config that satisfies HF GenerationMixin by inheriting PretrainedConfig."""
|
| 84 |
+
model_type = "custom_gpt2"
|
| 85 |
+
|
| 86 |
+
def __init__(self, **kwargs):
|
| 87 |
+
super().__init__(**kwargs)
|
| 88 |
+
self.is_encoder_decoder = False
|
| 89 |
+
self.n_positions = kwargs.get("n_positions", 1024)
|
| 90 |
+
self.vocab_size = kwargs.get("vocab_size", 50257)
|
| 91 |
+
self.eos_token_id = 50256
|
| 92 |
+
self.pad_token_id = 50256
|
| 93 |
+
self.bos_token_id = 50256
|
| 94 |
+
self.use_cache = False
|
| 95 |
+
self.num_hidden_layers = kwargs.get("num_hidden_layers", 12)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _make_hf_config(model, context_size):
|
| 99 |
+
vocab_size = 50257
|
| 100 |
+
n_positions = context_size
|
| 101 |
+
num_hidden_layers = 12
|
| 102 |
+
if hasattr(model, 'config') and isinstance(model.config, dict):
|
| 103 |
+
vocab_size = model.config.get("vocab_size", 50257)
|
| 104 |
+
n_positions = model.config.get("context_length", context_size)
|
| 105 |
+
num_hidden_layers = model.config.get("n_layers", 12)
|
| 106 |
+
|
| 107 |
+
cfg = _MinimalConfig(vocab_size=vocab_size, n_positions=n_positions, num_hidden_layers=num_hidden_layers)
|
| 108 |
+
return cfg
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class HFTextGenerator:
|
| 112 |
+
"""Drop-in replacement for TextGenerator using HF generate()."""
|
| 113 |
+
|
| 114 |
+
def __init__(self, model, tokenizer, device, context_size=1024):
|
| 115 |
+
self.tokenizer = tokenizer
|
| 116 |
+
self.device = device
|
| 117 |
+
self.context_size = context_size
|
| 118 |
+
self.hf_model = LanguageModelHF(model, context_size, device)
|
| 119 |
+
self.hf_model.to(device)
|
| 120 |
+
self.hf_model.eval()
|
| 121 |
+
|
| 122 |
+
def generate(self, prompt, max_new_tokens=200,
|
| 123 |
+
temperature=0.8, top_k=40, top_p=0.95,
|
| 124 |
+
repetition_penalty=1.1, num_beams=1,
|
| 125 |
+
do_sample=True, eos_id=50256):
|
| 126 |
+
"""Generate text from a string prompt. Returns the generated string."""
|
| 127 |
+
input_ids = torch.tensor(
|
| 128 |
+
[self.tokenizer.encode(prompt, allowed_special={"<|endoftext|>"})]
|
| 129 |
+
).to(self.device)
|
| 130 |
+
|
| 131 |
+
gen_config = GenerationConfig(
|
| 132 |
+
max_new_tokens=max_new_tokens,
|
| 133 |
+
do_sample=do_sample,
|
| 134 |
+
temperature=temperature,
|
| 135 |
+
top_k=top_k,
|
| 136 |
+
top_p=top_p,
|
| 137 |
+
repetition_penalty=repetition_penalty,
|
| 138 |
+
num_beams=num_beams,
|
| 139 |
+
eos_token_id=eos_id,
|
| 140 |
+
pad_token_id=eos_id,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
attention_mask = torch.ones_like(input_ids)
|
| 144 |
+
|
| 145 |
+
with torch.no_grad():
|
| 146 |
+
output_ids = self.hf_model.generate(
|
| 147 |
+
input_ids,
|
| 148 |
+
attention_mask=attention_mask,
|
| 149 |
+
generation_config=gen_config,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# Decode only the newly generated tokens
|
| 153 |
+
new_tokens = output_ids[0, input_ids.shape[1]:]
|
| 154 |
+
|
| 155 |
+
# Filter out the EOS token if it was generated
|
| 156 |
+
if len(new_tokens) > 0 and new_tokens[-1].item() == eos_id:
|
| 157 |
+
new_tokens = new_tokens[:-1]
|
| 158 |
+
|
| 159 |
+
decoded_text = self.tokenizer.decode(new_tokens.tolist())
|
| 160 |
+
return decoded_text.replace("<|endoftext|>", "").strip()
|
| 161 |
+
|
| 162 |
+
def generate_ids(self, idx, max_new_tokens=200,
|
| 163 |
+
temperature=0.8, top_k=40, top_p=0.95,
|
| 164 |
+
repetition_penalty=1.1, num_beams=1,
|
| 165 |
+
do_sample=True, eos_id=50256):
|
| 166 |
+
"""Generate from token IDs tensor (same interface as original).
|
| 167 |
+
Returns full sequence including prompt."""
|
| 168 |
+
gen_config = GenerationConfig(
|
| 169 |
+
max_new_tokens=max_new_tokens,
|
| 170 |
+
do_sample=do_sample,
|
| 171 |
+
temperature=temperature,
|
| 172 |
+
top_k=top_k,
|
| 173 |
+
top_p=top_p,
|
| 174 |
+
repetition_penalty=repetition_penalty,
|
| 175 |
+
num_beams=num_beams,
|
| 176 |
+
eos_token_id=eos_id,
|
| 177 |
+
pad_token_id=eos_id,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
idx = idx.to(self.device)
|
| 181 |
+
attention_mask = torch.ones_like(idx)
|
| 182 |
+
|
| 183 |
+
with torch.no_grad():
|
| 184 |
+
output_ids = self.hf_model.generate(
|
| 185 |
+
idx,
|
| 186 |
+
attention_mask=attention_mask,
|
| 187 |
+
generation_config=gen_config,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
return output_ids
|
| 191 |
+
|
| 192 |
+
# ββ Backward-compatible helpers (match TextGenerator interface) βββββββ
|
| 193 |
+
def token_ids_to_text(self, token_ids):
|
| 194 |
+
flat = token_ids.squeeze(0)
|
| 195 |
+
return self.tokenizer.decode(flat.tolist())
|
| 196 |
+
|
| 197 |
+
def text_to_token_ids(self, text):
|
| 198 |
+
encoded = self.tokenizer.encode(text, allowed_special={"<|endoftext|>"})
|
| 199 |
+
return torch.tensor(encoded).unsqueeze(0).to(self.device)
|
chat.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Interactive chat with your GPT-2 model β Notebook version
|
| 3 |
+
Run Cell 1 and Cell 2 in order, then use the chat widget.
|
| 4 |
+
You can copy the code into colab cells. ensure you rename to your model path
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
# ββ Cell 1: Setup & load model ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 8 |
+
import torch
|
| 9 |
+
import tiktoken
|
| 10 |
+
import ipywidgets as widgets
|
| 11 |
+
from IPython.display import display, HTML
|
| 12 |
+
from Architecture import LanguageModel
|
| 13 |
+
from GeneratorHF import HFTextGenerator
|
| 14 |
+
|
| 15 |
+
CHECKPOINT_PATH = "/content/drive/MyDrive/GPT2-small/refinedGPT2small.pth" #on google colab
|
| 16 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 17 |
+
|
| 18 |
+
def load_model(checkpoint_path, device):
|
| 19 |
+
print(f"Loading model from {checkpoint_path}...")
|
| 20 |
+
ckpt = torch.load(checkpoint_path, map_location=device, weights_only=False)
|
| 21 |
+
config = ckpt["config"]
|
| 22 |
+
model = LanguageModel(config)
|
| 23 |
+
state_dict = ckpt["model_state_dict"]
|
| 24 |
+
|
| 25 |
+
cleaned = {}
|
| 26 |
+
for k, v in state_dict.items():
|
| 27 |
+
for prefix in ["module.", "_orig_mod."]:
|
| 28 |
+
while k.startswith(prefix):
|
| 29 |
+
k = k[len(prefix):]
|
| 30 |
+
cleaned[k] = v
|
| 31 |
+
|
| 32 |
+
model.load_state_dict(cleaned)
|
| 33 |
+
model.to(device)
|
| 34 |
+
model.eval()
|
| 35 |
+
|
| 36 |
+
params = sum(p.numel() for p in model.parameters()) / 1e6
|
| 37 |
+
val_loss = ckpt.get("loss", "?")
|
| 38 |
+
print(f" {params:.1f}M params | val_loss {val_loss} | device {device}")
|
| 39 |
+
return model, config
|
| 40 |
+
|
| 41 |
+
model, config = load_model(CHECKPOINT_PATH, DEVICE)
|
| 42 |
+
enc = tiktoken.get_encoding("gpt2")
|
| 43 |
+
gen = HFTextGenerator(model, enc, DEVICE, context_size=config["context_length"])
|
| 44 |
+
print("Ready.")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# ββ Cell 2: Chat widget βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 48 |
+
|
| 49 |
+
# ββ Sampling controls βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 50 |
+
temp_slider = widgets.FloatSlider(
|
| 51 |
+
value=0.8, min=0.1, max=2.0, step=0.05,
|
| 52 |
+
description="Temperature",
|
| 53 |
+
style={"description_width": "90px"},
|
| 54 |
+
layout=widgets.Layout(width="400px")
|
| 55 |
+
)
|
| 56 |
+
topk_slider = widgets.IntSlider(
|
| 57 |
+
value=40, min=1, max=200, step=1,
|
| 58 |
+
description="Top-k",
|
| 59 |
+
style={"description_width": "90px"},
|
| 60 |
+
layout=widgets.Layout(width="400px")
|
| 61 |
+
)
|
| 62 |
+
tokens_slider = widgets.IntSlider(
|
| 63 |
+
value=150, min=10, max=500, step=10,
|
| 64 |
+
description="Max tokens",
|
| 65 |
+
style={"description_width": "90px"},
|
| 66 |
+
layout=widgets.Layout(width="400px")
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
# ββ Input & buttons βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 70 |
+
prompt_box = widgets.Textarea(
|
| 71 |
+
placeholder="Type your prompt here...",
|
| 72 |
+
layout=widgets.Layout(width="700px", height="70px")
|
| 73 |
+
)
|
| 74 |
+
generate_btn = widgets.Button(
|
| 75 |
+
description="Generate",
|
| 76 |
+
button_style="primary",
|
| 77 |
+
layout=widgets.Layout(width="120px")
|
| 78 |
+
)
|
| 79 |
+
clear_btn = widgets.Button(
|
| 80 |
+
description="Clear",
|
| 81 |
+
button_style="warning",
|
| 82 |
+
layout=widgets.Layout(width="80px")
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
# ββ Output area βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 86 |
+
output_area = widgets.Output()
|
| 87 |
+
|
| 88 |
+
def on_generate(b):
|
| 89 |
+
prompt = prompt_box.value.strip()
|
| 90 |
+
if not prompt:
|
| 91 |
+
return
|
| 92 |
+
|
| 93 |
+
with output_area:
|
| 94 |
+
print(f"\nYOU > {prompt}")
|
| 95 |
+
print("MODEL > ", end="", flush=True)
|
| 96 |
+
|
| 97 |
+
continuation = gen.generate(
|
| 98 |
+
prompt,
|
| 99 |
+
max_new_tokens=tokens_slider.value,
|
| 100 |
+
temperature=temp_slider.value,
|
| 101 |
+
top_k=topk_slider.value,
|
| 102 |
+
)
|
| 103 |
+
print(continuation)
|
| 104 |
+
print("-" * 70)
|
| 105 |
+
|
| 106 |
+
def on_clear(b):
|
| 107 |
+
output_area.clear_output()
|
| 108 |
+
prompt_box.value = ""
|
| 109 |
+
|
| 110 |
+
generate_btn.on_click(on_generate)
|
| 111 |
+
clear_btn.on_click(on_clear)
|
| 112 |
+
|
| 113 |
+
# ββ Layout ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 114 |
+
controls = widgets.VBox([
|
| 115 |
+
widgets.HTML("<b>Sampling settings</b>"),
|
| 116 |
+
temp_slider,
|
| 117 |
+
topk_slider,
|
| 118 |
+
tokens_slider,
|
| 119 |
+
])
|
| 120 |
+
buttons = widgets.HBox([generate_btn, clear_btn])
|
| 121 |
+
ui = widgets.VBox([
|
| 122 |
+
widgets.HTML("<h3>GPT-2 Small β Chat</h3>"),
|
| 123 |
+
controls,
|
| 124 |
+
widgets.HTML("<br><b>Prompt</b>"),
|
| 125 |
+
prompt_box,
|
| 126 |
+
buttons,
|
| 127 |
+
widgets.HTML("<br><b>Output</b>"),
|
| 128 |
+
output_area,
|
| 129 |
+
])
|
| 130 |
+
|
| 131 |
+
display(ui)
|