root39058 commited on
Commit
f4217c0
·
verified ·
1 Parent(s): fa0920b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -99
app.py CHANGED
@@ -7,14 +7,17 @@ import re
7
  import gradio as gr
8
  from datetime import datetime
9
  import math
10
- import spaces # <--- ВАЖНО: Импортируем библиотеку spaces
11
 
12
- # ============ НАСТРОЙКИ ПУТЕЙ ============
13
  DATA_DIR = '/data'
14
  os.makedirs(DATA_DIR, exist_ok=True)
15
- MODEL_PATH = os.path.join(DATA_DIR, 'pytorch_model_andrey_v6.bin')
16
 
17
- DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
 
 
18
 
19
  # ============ СЛОВАРЬ ============
20
  WORDS = [
@@ -71,8 +74,7 @@ def pad_sequence(seq, max_len=MAX_LEN):
71
  if len(seq) >= max_len: return seq[:max_len]
72
  return seq + [PAD] * (max_len - len(seq))
73
 
74
- # ============ TRANSFORMER АРХИТЕКТУРА ============
75
-
76
  class PositionalEncoding(nn.Module):
77
  def __init__(self, d_model, dropout=0.1, max_len=5000):
78
  super(PositionalEncoding, self).__init__()
@@ -108,19 +110,13 @@ class AndreyTransformer(nn.Module):
108
  dropout=dropout, batch_first=True
109
  )
110
  self.fc_out = nn.Linear(d_model, vocab_size)
111
- self._init_weights()
112
-
113
- def _init_weights(self):
114
- initrange = 0.1
115
- self.embedding.weight.data.uniform_(-initrange, initrange)
116
- self.fc_out.bias.data.zero_()
117
- self.fc_out.weight.data.uniform_(-initrange, initrange)
118
 
119
- def generate_mask(self, tgt_len):
120
- return torch.triu(torch.ones(tgt_len, tgt_len), diagonal=1).bool().to(DEVICE)
121
 
122
- def create_pad_mask(self, seq, pad_idx=PAD):
123
- return (seq == pad_idx).to(DEVICE)
 
124
 
125
  def forward(self, src, tgt, src_mask=None, tgt_mask=None,
126
  src_key_padding_mask=None, tgt_key_padding_mask=None):
@@ -135,7 +131,7 @@ class AndreyTransformer(nn.Module):
135
 
136
  def encode(self, src):
137
  src_emb = self.pos_encoder(self.embedding(src) * math.sqrt(self.d_model))
138
- src_key_padding_mask = self.create_pad_mask(src)
139
  return self.transformer.encoder(src_emb, src_key_padding_mask=src_key_padding_mask)
140
 
141
  def decode_step(self, tgt, memory, tgt_mask=None, tgt_key_padding_mask=None):
@@ -146,7 +142,7 @@ class AndreyTransformer(nn.Module):
146
  )
147
  return self.fc_out(output)
148
 
149
- # ============ ДИАЛОГИ ============
150
  DIALOGUES = [
151
  ("привет", "привет как дела"), ("здравствуй", "здравствуй рад тебя видеть"),
152
  ("доброе утро", "доброе утро хорошего дня"), ("добрый день", "добрый день чем помочь"),
@@ -271,59 +267,45 @@ class AndreyAI:
271
  self.bin_file = bin_file
272
  self.memory = {'chat_history': [], 'epochs_trained': 0}
273
  self.model = None
274
- self.load()
275
-
276
- def _get_state(self):
277
- return {
278
- 'model_state': self.model.state_dict() if self.model else None,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  'vocab_size': vocab_size, 'd_model': 128, 'nhead': 4,
280
  'num_encoder_layers': 2, 'num_decoder_layers': 2, 'dim_feedforward': 256,
281
  'word_to_idx': word_to_idx,
282
  'idx_to_word': {str(k): v for k, v in idx_to_word.items()},
283
- 'memory': self.memory, 'version': '7.0-ZeroGPU',
284
  'created': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
285
  }
286
-
287
- def _restore(self, data):
288
- self.memory = data.get('memory', self.memory)
289
- if data.get('model_state'):
290
- self.model = AndreyTransformer(
291
- vocab_size=data.get('vocab_size', vocab_size),
292
- d_model=data.get('d_model', 128), nhead=data.get('nhead', 4),
293
- num_encoder_layers=data.get('num_encoder_layers', 2),
294
- num_decoder_layers=data.get('num_decoder_layers', 2),
295
- dim_feedforward=data.get('dim_feedforward', 256)
296
- )
297
- self.model.load_state_dict(data['model_state'])
298
- self.model.to(DEVICE).eval()
299
- return True
300
- return False
301
-
302
- def load(self):
303
- if os.path.exists(self.bin_file):
304
- try:
305
- if self._restore(torch.load(self.bin_file, map_location=DEVICE)):
306
- print(f"✅ Андрей загружен из {self.bin_file}")
307
- return True
308
- except Exception as e: print(f"⚠️ Ошибка загрузки: {e}")
309
- print("📝 Создаю нового Андрея...")
310
- self.model = AndreyTransformer().to(DEVICE).eval()
311
- return False
312
-
313
- def save(self):
314
- os.makedirs(os.path.dirname(self.bin_file), exist_ok=True)
315
- torch.save(self._get_state(), self.bin_file)
316
- print(f"✅ Сохранён в /data: {os.path.getsize(self.bin_file)/1024:.1f} КБ")
317
-
318
- @spaces.GPU(duration=120) # <--- ВАЖНО: Декоратор для ZeroGPU
319
- def train(self, epochs=150):
320
- print("="*60)
321
- print(f"🧠 ОБУЧЕНИЕ АНДРЕЯ — {epochs} ЭПОХ")
322
- print("="*60)
323
  X_data, Y_input, Y_target = prepare_data()
324
  criterion = nn.CrossEntropyLoss(ignore_index=PAD)
325
  optimizer = optim.Adam(self.model.parameters(), lr=0.001)
326
- self.model.train()
327
 
328
  for epoch in range(epochs):
329
  total_loss, n_batches = 0, 0
@@ -331,13 +313,13 @@ class AndreyAI:
331
  random.shuffle(indices)
332
 
333
  for idx in indices:
334
- src = X_data[idx].unsqueeze(0).to(DEVICE)
335
- tgt_in = Y_input[idx].unsqueeze(0).to(DEVICE)
336
- tgt_tar = Y_target[idx].unsqueeze(0).to(DEVICE)
337
 
338
- tgt_mask = self.model.generate_mask(tgt_in.size(1))
339
- src_pad = self.model.create_pad_mask(src)
340
- tgt_pad = self.model.create_pad_mask(tgt_in)
341
 
342
  optimizer.zero_grad()
343
  output = self.model(src, tgt_in, tgt_mask=tgt_mask, src_key_padding_mask=src_pad, tgt_key_padding_mask=tgt_pad)
@@ -349,12 +331,13 @@ class AndreyAI:
349
  total_loss += loss.item()
350
  n_batches += 1
351
 
352
- if epoch % 10 == 0: print(f"Эпоха {epoch:3d}/{epochs} | Потери: {total_loss/n_batches:.4f}")
 
353
 
354
  self.memory['epochs_trained'] = epochs
355
- print("\n✅ Обучение готово!")
356
- self.save()
357
- self.model.eval()
358
 
359
  def get_fallback_answer(self, question):
360
  q_clean = question.lower().strip()
@@ -363,7 +346,12 @@ class AndreyAI:
363
  if q in q_clean or q_clean in q: return a
364
  return "Интересный вопрос! Я еще учусь."
365
 
 
366
  def generate(self, question, history=None, temperature=0.6, max_length=15):
 
 
 
 
367
  q = question.lower().strip()
368
  context_parts = []
369
  if history:
@@ -374,20 +362,24 @@ class AndreyAI:
374
  full_context = " ".join(context_parts)
375
 
376
  ctx_tokens = tokenize(full_context)
377
- if not ctx_tokens: return self.get_fallback_answer(q)
 
 
 
378
  if len(ctx_tokens) > MAX_LEN: ctx_tokens = ctx_tokens[-MAX_LEN:]
379
 
380
- src = torch.tensor([pad_sequence(ctx_tokens, MAX_LEN)], dtype=torch.long).to(DEVICE)
 
381
  generated_text = ""
382
 
383
  try:
384
  with torch.no_grad():
385
  memory = self.model.encode(src)
386
  response_tokens = []
387
- decoder_input = torch.tensor([[START]], dtype=torch.long).to(DEVICE)
388
 
389
  for i in range(max_length):
390
- tgt_mask = self.model.generate_mask(decoder_input.size(1)).to(DEVICE)
391
  output = self.model.decode_step(decoder_input, memory, tgt_mask=tgt_mask)
392
  logits = output[:, -1, :] / temperature
393
  probs = torch.softmax(logits, dim=-1)
@@ -396,36 +388,37 @@ class AndreyAI:
396
 
397
  if next_token in [PAD, UNK]: break
398
  response_tokens.append(next_token)
399
- decoder_input = torch.cat([decoder_input, torch.tensor([[next_token]], dtype=torch.long).to(DEVICE)], dim=1)
400
 
401
  generated_text = detokenize(response_tokens)
402
- except Exception: pass
 
 
 
403
 
404
  return generated_text if generated_text else self.get_fallback_answer(q)
405
 
406
- # ============ GRADIO ИНТЕРФЕЙС ============
407
- def gradio_chat(question, history):
408
- if not question: return "", history
409
- answer = andrey.generate(question, history=history)
410
- new_history = history + [(question, answer)]
411
- andrey.memory['chat_history'] = new_history
412
  return "", new_history
413
 
414
- def launch_gradio():
415
- # Исправлено для Gradio 6.0: theme передается в launch
416
- with gr.Blocks(title="Андрей AI") as demo:
417
- gr.Markdown("# 🤖 Андрей AI v7.0 (ZeroGPU)\n### Transformer с реальной памятью")
418
- chatbot = gr.Chatbot(height=400, label="Диалог")
419
- msg = gr.Textbox(label="Сообщение", placeholder="Напишите что-нибудь...")
420
- clear = gr.Button("🧹 Очистить")
421
-
422
- msg.submit(gradio_chat, [msg, chatbot], [msg, chatbot])
423
- clear.click(lambda: [], None, chatbot)
424
 
425
- demo.launch(theme=gr.themes.Soft()) # <--- Исправлено место передачи темы
 
426
 
427
  if __name__ == "__main__":
428
  andrey = AndreyAI(MODEL_PATH)
429
- if andrey.model is None or andrey.memory.get('epochs_trained', 0) == 0:
430
- andrey.train(150)
431
- launch_gradio()
 
 
 
 
7
  import gradio as gr
8
  from datetime import datetime
9
  import math
10
+ import spaces # Обязательно для ZeroGPU
11
 
12
+ # ============ НАСТРОЙКИ ============
13
  DATA_DIR = '/data'
14
  os.makedirs(DATA_DIR, exist_ok=True)
15
+ MODEL_PATH = os.path.join(DATA_DIR, 'andrey_zerogpu_v8.bin')
16
 
17
+ # На ZeroGPU мы не можем полагаться на глобальный DEVICE при инициализации,
18
+ # поэтому будем определять его внутри функций, обернутых в @spaces.GPU
19
+ # или использовать CPU для легких операций, если нужно.
20
+ # Но для модели нужен CUDA.
21
 
22
  # ============ СЛОВАРЬ ============
23
  WORDS = [
 
74
  if len(seq) >= max_len: return seq[:max_len]
75
  return seq + [PAD] * (max_len - len(seq))
76
 
77
+ # ============ МОДЕЛЬ ============
 
78
  class PositionalEncoding(nn.Module):
79
  def __init__(self, d_model, dropout=0.1, max_len=5000):
80
  super(PositionalEncoding, self).__init__()
 
110
  dropout=dropout, batch_first=True
111
  )
112
  self.fc_out = nn.Linear(d_model, vocab_size)
 
 
 
 
 
 
 
113
 
114
+ def generate_mask(self, tgt_len, device):
115
+ return torch.triu(torch.ones(tgt_len, tgt_len, device=device), diagonal=1).bool()
116
 
117
+ def create_pad_mask(self, seq, pad_idx=PAD, device=None):
118
+ if device is None: device = seq.device
119
+ return (seq == pad_idx).to(device)
120
 
121
  def forward(self, src, tgt, src_mask=None, tgt_mask=None,
122
  src_key_padding_mask=None, tgt_key_padding_mask=None):
 
131
 
132
  def encode(self, src):
133
  src_emb = self.pos_encoder(self.embedding(src) * math.sqrt(self.d_model))
134
+ src_key_padding_mask = self.create_pad_mask(src, device=src.device)
135
  return self.transformer.encoder(src_emb, src_key_padding_mask=src_key_padding_mask)
136
 
137
  def decode_step(self, tgt, memory, tgt_mask=None, tgt_key_padding_mask=None):
 
142
  )
143
  return self.fc_out(output)
144
 
145
+ # ============ ДАННЫЕ ============
146
  DIALOGUES = [
147
  ("привет", "привет как дела"), ("здравствуй", "здравствуй рад тебя видеть"),
148
  ("доброе утро", "доброе утро хорошего дня"), ("добрый день", "добрый день чем помочь"),
 
267
  self.bin_file = bin_file
268
  self.memory = {'chat_history': [], 'epochs_trained': 0}
269
  self.model = None
270
+ # Загружаем структуру, но веса загрузим позже
271
+ self.model = AndreyTransformer()
272
+
273
+ def load_weights(self):
274
+ if os.path.exists(self.bin_file):
275
+ try:
276
+ data = torch.load(self.bin_file, map_location='cpu')
277
+ self.model.load_state_dict(data['model_state'])
278
+ self.memory = data.get('memory', self.memory)
279
+ print(f"✅ Веса загружены из {self.bin_file}")
280
+ return True
281
+ except Exception as e:
282
+ print(f"⚠️ Ошибка загрузки весов: {e}")
283
+ return False
284
+
285
+ def save_weights(self):
286
+ os.makedirs(os.path.dirname(self.bin_file), exist_ok=True)
287
+ state = {
288
+ 'model_state': self.model.state_dict(),
289
  'vocab_size': vocab_size, 'd_model': 128, 'nhead': 4,
290
  'num_encoder_layers': 2, 'num_decoder_layers': 2, 'dim_feedforward': 256,
291
  'word_to_idx': word_to_idx,
292
  'idx_to_word': {str(k): v for k, v in idx_to_word.items()},
293
+ 'memory': self.memory, 'version': '8.0-ZeroGPU-Fixed',
294
  'created': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
295
  }
296
+ torch.save(state, self.bin_file)
297
+ print(f"✅ Сохранено в /data: {os.path.getsize(self.bin_file)/1024:.1f} КБ")
298
+
299
+ @spaces.GPU(duration=120)
300
+ def train(self, epochs=50): # Уменьшил эпохи для быстроты теста, можно вернуть 150
301
+ print("🚀 Начало обучения на ZeroGPU...")
302
+ device = torch.device('cuda')
303
+ self.model.to(device)
304
+ self.model.train()
305
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  X_data, Y_input, Y_target = prepare_data()
307
  criterion = nn.CrossEntropyLoss(ignore_index=PAD)
308
  optimizer = optim.Adam(self.model.parameters(), lr=0.001)
 
309
 
310
  for epoch in range(epochs):
311
  total_loss, n_batches = 0, 0
 
313
  random.shuffle(indices)
314
 
315
  for idx in indices:
316
+ src = X_data[idx].unsqueeze(0).to(device)
317
+ tgt_in = Y_input[idx].unsqueeze(0).to(device)
318
+ tgt_tar = Y_target[idx].unsqueeze(0).to(device)
319
 
320
+ tgt_mask = self.model.generate_mask(tgt_in.size(1), device)
321
+ src_pad = self.model.create_pad_mask(src, device=device)
322
+ tgt_pad = self.model.create_pad_mask(tgt_in, device=device)
323
 
324
  optimizer.zero_grad()
325
  output = self.model(src, tgt_in, tgt_mask=tgt_mask, src_key_padding_mask=src_pad, tgt_key_padding_mask=tgt_pad)
 
331
  total_loss += loss.item()
332
  n_batches += 1
333
 
334
+ if epoch % 10 == 0:
335
+ print(f"Эпоха {epoch}/{epochs} | Loss: {total_loss/n_batches:.4f}")
336
 
337
  self.memory['epochs_trained'] = epochs
338
+ self.model.cpu() # Возвращаем на CPU для сохранения
339
+ self.save_weights()
340
+ print("✅ Обучение завершено!")
341
 
342
  def get_fallback_answer(self, question):
343
  q_clean = question.lower().strip()
 
346
  if q in q_clean or q_clean in q: return a
347
  return "Интересный вопрос! Я еще учусь."
348
 
349
+ @spaces.GPU(duration=10) # ВАЖНО: Генерация тоже должна быть на GPU
350
  def generate(self, question, history=None, temperature=0.6, max_length=15):
351
+ device = torch.device('cuda')
352
+ self.model.to(device)
353
+ self.model.eval()
354
+
355
  q = question.lower().strip()
356
  context_parts = []
357
  if history:
 
362
  full_context = " ".join(context_parts)
363
 
364
  ctx_tokens = tokenize(full_context)
365
+ if not ctx_tokens:
366
+ self.model.cpu()
367
+ return self.get_fallback_answer(q)
368
+
369
  if len(ctx_tokens) > MAX_LEN: ctx_tokens = ctx_tokens[-MAX_LEN:]
370
 
371
+ # Создаем тензор уже на устройстве
372
+ src = torch.tensor([pad_sequence(ctx_tokens, MAX_LEN)], dtype=torch.long).to(device)
373
  generated_text = ""
374
 
375
  try:
376
  with torch.no_grad():
377
  memory = self.model.encode(src)
378
  response_tokens = []
379
+ decoder_input = torch.tensor([[START]], dtype=torch.long).to(device)
380
 
381
  for i in range(max_length):
382
+ tgt_mask = self.model.generate_mask(decoder_input.size(1), device)
383
  output = self.model.decode_step(decoder_input, memory, tgt_mask=tgt_mask)
384
  logits = output[:, -1, :] / temperature
385
  probs = torch.softmax(logits, dim=-1)
 
388
 
389
  if next_token in [PAD, UNK]: break
390
  response_tokens.append(next_token)
391
+ decoder_input = torch.cat([decoder_input, torch.tensor([[next_token]], dtype=torch.long).to(device)], dim=1)
392
 
393
  generated_text = detokenize(response_tokens)
394
+ except Exception as e:
395
+ print(f"Ошибка генерации: {e}")
396
+ finally:
397
+ self.model.cpu() # Всегда возвращаем на CPU после работы
398
 
399
  return generated_text if generated_text else self.get_fallback_answer(q)
400
 
401
+ # ============ GRADIO ============
402
+ def gradio_chat(message, history):
403
+ if not message: return "", history
404
+ answer = andrey.generate(message, history=history)
405
+ new_history = history + [(message, answer)]
 
406
  return "", new_history
407
 
408
+ with gr.Blocks(title="Андрей AI") as demo:
409
+ gr.Markdown("# 🤖 Андрей AI (ZeroGPU)\n### Transformer с памятью")
410
+ chatbot = gr.Chatbot(height=400, label="Диалог")
411
+ msg = gr.Textbox(label="Сообщение", placeholder="Напишите что-нибудь...")
412
+ clear = gr.Button("🧹 Очистить")
 
 
 
 
 
413
 
414
+ msg.submit(gradio_chat, [msg, chatbot], [msg, chatbot])
415
+ clear.click(lambda: [], None, chatbot)
416
 
417
  if __name__ == "__main__":
418
  andrey = AndreyAI(MODEL_PATH)
419
+ loaded = andrey.load_weights()
420
+
421
+ if not loaded or andrey.memory.get('epochs_trained', 0) == 0:
422
+ andrey.train(150) # Обучаем если нет весов
423
+
424
+ demo.launch()