boyuia commited on
Commit
54fe977
·
verified ·
1 Parent(s): 0efc23e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -55
app.py CHANGED
@@ -3,18 +3,63 @@ import torch
3
  import torch.nn as nn
4
  from torch.nn import functional as F
5
  import json
6
- import os # <-- Added for file path checks
7
 
8
- # --- Model Definition (same as before) ---
9
- # NOTE: The model class MUST be defined in your app.py file
10
- # so that torch.load knows how to reconstruct it.
11
  batch_size = 32
12
  block_size = 8
13
  n_embd = 32
14
  n_head = 4
15
  n_layer = 4
16
  dropout = 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  class Head(nn.Module):
19
  def __init__(self, head_size):
20
  super().__init__()
@@ -81,7 +126,6 @@ class LanguageModel(nn.Module):
81
  self.lm_head = nn.Linear(n_embd, vocab_size)
82
  self.block_size = block_size
83
  self.vocab_size = vocab_size
84
-
85
  def forward(self, idx, targets=None):
86
  B, T = idx.shape
87
  tok_emb = self.token_embedding_table(idx)
@@ -97,7 +141,6 @@ class LanguageModel(nn.Module):
97
  targets = targets.view(B * T)
98
  loss = F.cross_entropy(logits, targets)
99
  return logits, loss
100
-
101
  def generate(self, idx, max_new_tokens):
102
  for _ in range(max_new_tokens):
103
  idx_cond = idx[:, -self.block_size:]
@@ -108,77 +151,76 @@ class LanguageModel(nn.Module):
108
  idx = torch.cat((idx, idx_next), dim=1)
109
  return idx
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- # --- Utility functions and model loading ---
113
- # Read from the same dataset file to ensure vocabulary consistency
114
- file_path = 'dataset.jsonl'
115
- corpus = ""
116
- try:
117
- with open(file_path, 'r') as f:
118
- for line in f:
119
- data_point = json.loads(line)
120
- corpus += data_point['header'] + '\n' + data_point['formal_statement'] + '\n'
121
- except FileNotFoundError:
122
- print(f"Error: The file '{file_path}' was not found.")
123
- # Exit or handle gracefully as the app can't run without the dataset
124
- exit()
125
- except json.JSONDecodeError:
126
- print(f"Error: There was a problem parsing a line in '{file_path}'.")
127
- exit()
128
- except KeyError:
129
- print(f"Error: A line in '{file_path}' does not have the expected keys.")
130
- exit()
131
-
132
- if not corpus:
133
- print("Error: The corpus is empty.")
134
- exit()
135
-
136
- chars = sorted(list(set(corpus)))
137
- vocab_size = len(chars)
138
- stoi = {ch: i for i, ch in enumerate(chars)}
139
- itos = {i: ch for i, ch in enumerate(chars)}
140
- # Corrected the encode function
141
- encode = lambda s: [stoi[c] for c in s]
142
- decode = lambda l: ''.join([itos[i] for i in l])
143
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
144
 
145
- # Load the trained model.
146
- model = LanguageModel(vocab_size, block_size, n_embd, n_head, n_layer, dropout)
147
- model.load_state_dict(torch.load('model.pt', map_location=device))
148
- model.eval() # Set the model to evaluation mode for inference.
149
  model.to(device)
150
 
151
-
152
  # --- Gradio UI & Inference function ---
153
  def generate_text_chat(message, history):
154
- # We'll just use the most recent message as the prompt.
155
  prompt = message
156
- # You can adjust this to a different number of tokens if you like.
157
  max_new_tokens = 50
158
-
159
- # Encode the prompt text into tokens.
160
  encoded_prompt = [stoi.get(c, 0) for c in prompt]
161
  if not encoded_prompt:
162
  return "Prompt is empty or contains unknown characters."
163
-
164
  context = torch.tensor(encoded_prompt, dtype=torch.long, device=device).unsqueeze(0)
165
- # Generate new tokens.
166
  generated_text_indices = model.generate(context, max_new_tokens=max_new_tokens)
167
- # Decode the tokens back into text.
168
  generated_text = decode(generated_text_indices[0].tolist())
169
-
170
- # Return only the newly generated part of the text, removing the original prompt
171
  return generated_text[len(prompt):]
172
 
173
- # Using gr.ChatInterface for a conversational experience
174
  demo = gr.ChatInterface(
175
  fn=generate_text_chat,
176
  title="Tiny Language Model Chat",
177
  description="A simple character-level language model trained in PyTorch, now with a chat interface.",
178
- # You can customize these components further if you like
179
  chatbot=gr.Chatbot(height="500px"),
180
  textbox=gr.Textbox(placeholder="Ask me anything...", container=False, scale=7),
181
  theme="soft",
182
  )
183
 
184
- demo.launch()
 
3
  import torch.nn as nn
4
  from torch.nn import functional as F
5
  import json
6
+ import os
7
 
8
+ # --- Model Hyperparameters (same as before) ---
 
 
9
  batch_size = 32
10
  block_size = 8
11
  n_embd = 32
12
  n_head = 4
13
  n_layer = 4
14
  dropout = 0.0
15
+ max_iters = 3000
16
+ eval_interval = 300
17
+ learning_rate = 1e-2
18
+ eval_iters = 200
19
+
20
+ # --- Data Preparation & Vocabulary Creation ---
21
+ file_path = 'dataset.jsonl'
22
+ corpus = ""
23
+ try:
24
+ with open(file_path, 'r') as f:
25
+ for line in f:
26
+ data_point = json.loads(line)
27
+ corpus += data_point['header'] + '\n' + data_point['formal_statement'] + '\n'
28
+ except FileNotFoundError:
29
+ print(f"Error: The file '{file_path}' was not found.")
30
+ exit()
31
+ except (json.JSONDecodeError, KeyError):
32
+ print(f"Error: There was a problem parsing a line in '{file_path}'. Check for malformed JSON or missing keys.")
33
+ exit()
34
 
35
+ if not corpus:
36
+ print("Error: The corpus is empty.")
37
+ exit()
38
+
39
+ chars = sorted(list(set(corpus)))
40
+ vocab_size = len(chars)
41
+ stoi = {ch: i for i, ch in enumerate(chars)}
42
+ itos = {i: ch for i, ch in enumerate(chars)}
43
+ encode = lambda s: [stoi.get(c, 0) for c in s]
44
+ decode = lambda l: ''.join([itos[i] for i in l])
45
+
46
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
47
+
48
+ # Split the data for training and validation
49
+ data = torch.tensor(encode(corpus), dtype=torch.long)
50
+ n = int(0.9 * len(data))
51
+ train_data = data[:n]
52
+ val_data = data[n:]
53
+
54
+ def get_batch(split):
55
+ data = train_data if split == 'train' else val_data
56
+ ix = torch.randint(len(data) - block_size, (batch_size,))
57
+ x = torch.stack([data[i:i + block_size] for i in ix])
58
+ y = torch.stack([data[i + 1:i + block_size + 1] for i in ix])
59
+ x, y = x.to(device), y.to(device)
60
+ return x, y
61
+
62
+ # --- Model Definition (same as before) ---
63
  class Head(nn.Module):
64
  def __init__(self, head_size):
65
  super().__init__()
 
126
  self.lm_head = nn.Linear(n_embd, vocab_size)
127
  self.block_size = block_size
128
  self.vocab_size = vocab_size
 
129
  def forward(self, idx, targets=None):
130
  B, T = idx.shape
131
  tok_emb = self.token_embedding_table(idx)
 
141
  targets = targets.view(B * T)
142
  loss = F.cross_entropy(logits, targets)
143
  return logits, loss
 
144
  def generate(self, idx, max_new_tokens):
145
  for _ in range(max_new_tokens):
146
  idx_cond = idx[:, -self.block_size:]
 
151
  idx = torch.cat((idx, idx_next), dim=1)
152
  return idx
153
 
154
+ # --- Training and Generation ---
155
+ model = LanguageModel(vocab_size, block_size, n_embd, n_head, n_layer, dropout)
156
+ m = model.to(device)
157
+
158
+ # --- Check if a trained model exists, otherwise train a new one ---
159
+ model_file = 'model.pt'
160
+ if os.path.exists(model_file):
161
+ print(f"Loading existing model from {model_file}")
162
+ try:
163
+ model.load_state_dict(torch.load(model_file, map_location=device))
164
+ except RuntimeError as e:
165
+ print(f"Error loading model: {e}")
166
+ print("Model file might be incompatible with current vocabulary. Retraining...")
167
+ # If loading fails, fall through to training logic
168
+ model.train() # Set back to train mode just in case
169
+ else:
170
+ print("No trained model found. Starting a new training session...")
171
+
172
+ # Define a helper function for loss estimation
173
+ @torch.no_grad()
174
+ def estimate_loss():
175
+ out = {}
176
+ model.eval()
177
+ for split in ['train', 'val']:
178
+ losses = torch.zeros(eval_iters)
179
+ for k in range(eval_iters):
180
+ X, Y = get_batch(split)
181
+ logits, loss = model(X, Y)
182
+ losses[k] = loss.item()
183
+ out[split] = losses.mean()
184
+ model.train()
185
+ return out
186
 
187
+ # The training loop
188
+ optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
189
+ for iter in range(max_iters):
190
+ if iter % eval_interval == 0:
191
+ losses = estimate_loss()
192
+ print(f"step {iter}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}")
193
+ xb, yb = get_batch('train')
194
+ logits, loss = model(xb, yb)
195
+ optimizer.zero_grad(set_to_none=True)
196
+ loss.backward()
197
+ optimizer.step()
198
+
199
+ torch.save(m.state_dict(), model_file)
200
+ print(f"Training complete. Model saved to {model_file}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
+ model.eval()
 
 
 
203
  model.to(device)
204
 
 
205
  # --- Gradio UI & Inference function ---
206
  def generate_text_chat(message, history):
 
207
  prompt = message
 
208
  max_new_tokens = 50
 
 
209
  encoded_prompt = [stoi.get(c, 0) for c in prompt]
210
  if not encoded_prompt:
211
  return "Prompt is empty or contains unknown characters."
 
212
  context = torch.tensor(encoded_prompt, dtype=torch.long, device=device).unsqueeze(0)
 
213
  generated_text_indices = model.generate(context, max_new_tokens=max_new_tokens)
 
214
  generated_text = decode(generated_text_indices[0].tolist())
 
 
215
  return generated_text[len(prompt):]
216
 
 
217
  demo = gr.ChatInterface(
218
  fn=generate_text_chat,
219
  title="Tiny Language Model Chat",
220
  description="A simple character-level language model trained in PyTorch, now with a chat interface.",
 
221
  chatbot=gr.Chatbot(height="500px"),
222
  textbox=gr.Textbox(placeholder="Ask me anything...", container=False, scale=7),
223
  theme="soft",
224
  )
225
 
226
+ demo.launch()