noch inator commited on
Commit
e8bfacb
·
verified ·
1 Parent(s): c8d039e

Fixed more bugs, solved an issue with 16FP used for the GRU.

Browse files
Files changed (1) hide show
  1. ThoughtVectors.py +159 -144
ThoughtVectors.py CHANGED
@@ -1,13 +1,12 @@
1
- # thought_vectors.py
2
  import random
3
  import math
4
  import torch
5
  import torch.nn as nn
6
  import torch.optim as optim
7
  import sentencepiece as spm
8
- from torch.utils.data import Dataset, DataLoader
9
  from torch.amp import autocast, GradScaler
10
- from typing import List, Union, Generator, Sequence, Optional
11
  import os
12
  import csv
13
  import tempfile
@@ -156,9 +155,12 @@ class ThoughtEncoder(nn.Module):
156
  prev_context = torch.zeros_like(text_context)
157
 
158
  combined = torch.cat([text_context, prev_context], dim=1)
 
159
  next_thought = self.fc_thought(combined)
160
 
161
- thought_hidden = self.thought_rnn(next_thought.unsqueeze(1), thought_hidden)[1]
 
 
162
  termination_logit = self.fc_terminate(thought_hidden.squeeze(0))
163
  termination_score = torch.sigmoid(termination_logit).squeeze(-1)
164
 
@@ -372,13 +374,20 @@ class ThoughtVectors:
372
  self.dropout = None
373
  self.max_len = None
374
  self.termination_threshold = None
 
 
 
 
 
375
 
376
  def train(self,
377
  group_data: Union[str, List[List[str]]],
378
  test_data: Union[str, List[List[str]]] = None,
379
  model: str = None,
380
- num_epochs: int = 30,
381
- batch_size: int = 8,
 
 
382
  accum_steps: int = 4,
383
  learning_rate: float = 2e-4,
384
  weight_decay: float = 1e-5,
@@ -405,6 +414,8 @@ class ThoughtVectors:
405
  model (str, optional): Path to pre-existing model to load. If None, creates a new model. Defaults to None.
406
  num_epochs (int, optional): Number of training epochs. Defaults to 30.
407
  batch_size (int, optional): Batch size. Defaults to 8.
 
 
408
  accum_steps (int, optional): Number of gradient accumulation steps. Defaults to 4.
409
  learning_rate (float, optional): Learning rate for Adam optimizer. Defaults to 2e-4.
410
  weight_decay (float, optional): Weight decay for regularization. Defaults to 1e-5.
@@ -422,7 +433,7 @@ class ThoughtVectors:
422
  dropout (float, optional): Dropout probability. Defaults to 0.1.
423
  max_len (int, optional): Maximum sequence length for positional encoding. Defaults to 1024.
424
  termination_threshold (float, optional): Threshold for stopping thought generation. Defaults to 0.75.
425
- patience (int, optional): Epochs to wait for improvement before early stopping. Defaults to 5.
426
  """
427
  self.d_model = d_model
428
  self.max_thoughts = max_thoughts
@@ -442,7 +453,7 @@ class ThoughtVectors:
442
  if model and os.path.exists(model):
443
  print("loading model...")
444
  loaded_instance = self.load(model)
445
- self.__dict__.update(loaded_instance.__dict__) # actually update self
446
  print(f"Loaded pre-existing model from {model}")
447
  else:
448
  print("building models")
@@ -452,11 +463,11 @@ class ThoughtVectors:
452
  temp_file.write(sentence.strip() + "\n")
453
  temp_dataset_path = temp_file.name
454
 
455
- temp_dir = tempfile.mkdtemp()
456
- temp_spm_prefix = os.path.join(temp_dir, "spm")
 
457
  try:
458
  print("training sentence piece (could take a while on massive datasets)")
459
- print("Also you will get some log spamming from it cause it won't shut up")
460
  spm.SentencePieceTrainer.Train(
461
  input=temp_dataset_path,
462
  model_prefix=temp_spm_prefix,
@@ -467,13 +478,25 @@ class ThoughtVectors:
467
  input_sentence_size=8_388_608,
468
  train_extremely_large_corpus=True
469
  )
 
470
  shutil.copy(f"{temp_spm_prefix}.model", f"{spm_model_prefix}.model")
471
  shutil.copy(f"{temp_spm_prefix}.vocab", f"{spm_model_prefix}.vocab")
472
  if not self.sp.Load(f"{spm_model_prefix}.model"):
473
  raise RuntimeError("Failed to load SentencePiece model.")
474
  finally:
475
  os.remove(temp_dataset_path)
476
- shutil.rmtree(temp_dir, ignore_errors=True)
 
 
 
 
 
 
 
 
 
 
 
477
 
478
  self.vocab_size = self.sp.GetPieceSize()
479
  self.encoder = ThoughtEncoder(
@@ -494,6 +517,7 @@ class ThoughtVectors:
494
 
495
  best_val_loss = float('inf')
496
  patience_counter = 0
 
497
 
498
  print("Beginning training")
499
  try:
@@ -501,8 +525,8 @@ class ThoughtVectors:
501
  total_train_loss = 0.0
502
  num_train_batches = (len(train_lazy_data) + batch_size - 1) // batch_size
503
 
504
- # training
505
  for i in range(0, len(train_lazy_data), batch_size):
 
506
  raw_batch = train_lazy_data[i:i + batch_size]
507
  tokenized_batch = [
508
  [[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
@@ -519,101 +543,114 @@ class ThoughtVectors:
519
  thought_vectors = self.encoder(group_tensor, force_single_vector)
520
  output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
521
 
522
- # Masked loss
523
  mask = (group_tensor[:, 1:] != self.sp.pad_id()).float()
524
  loss = criterion(output_logits.reshape(-1, self.vocab_size),
525
  group_tensor[:, 1:].reshape(-1))
526
  loss = (loss * mask.reshape(-1)).sum() / mask.sum().clamp(min=1.0)
 
 
 
 
 
 
527
  if not force_single_vector:
528
  loss += length_penalty * thought_vectors.shape[1]
529
  batch_loss += loss / accum_steps
530
 
531
  scaler.scale(batch_loss).backward()
532
  total_train_loss += batch_loss.item() * accum_steps
533
- if (i // batch_size + 1) % accum_steps == 0:
 
 
 
534
  scaler.step(optimizer)
535
  scaler.update()
536
  optimizer.zero_grad()
537
  torch.cuda.empty_cache()
538
 
539
- print(f"Batch {i // batch_size + 1}/{num_train_batches} - Loss: {batch_loss.item():.4f}")
540
- # Debugging
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
542
- print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n\n")
543
-
544
- avg_train_loss = total_train_loss / num_train_batches if num_train_batches > 0 else float('inf')
545
-
546
- # Validation
547
- if test_lazy_data is not None:
548
- self.encoder.eval()
549
- self.decoder.eval()
550
- total_val_loss = 0.0
551
- num_val_batches = (len(test_lazy_data) + batch_size - 1) // batch_size
552
- print(f"Starting validation with {num_val_batches} batches")
553
 
554
- with torch.no_grad():
555
- for i in range(0, len(test_lazy_data), batch_size):
556
- raw_batch = test_lazy_data[i:i + batch_size]
557
- tokenized_batch = [
558
- [[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
559
- for group in raw_batch
560
- ]
561
- group_batch = group_collate_fn(tokenized_batch)
562
-
563
- batch_val_loss = 0.0
564
- for group_tensor in group_batch:
565
- if group_tensor.shape[1] <= self.max_len and group_tensor.numel() > 0:
566
- group_tensor = group_tensor.to(self.device)
567
- thought_vectors = self.encoder(group_tensor)
568
- output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
569
- val_loss = criterion(output_logits.reshape(-1, self.vocab_size),
570
- group_tensor[:, 1:].reshape(-1))
571
- val_loss += length_penalty * thought_vectors.shape[1]
572
- batch_val_loss += val_loss.item()
573
- total_val_loss += batch_val_loss
574
- print(f"VAL batch loss: {batch_val_loss:.4f}")
575
- print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
576
- print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n\n")
577
- avg_val_loss = total_val_loss / num_val_batches if num_val_batches > 0 else float('inf')
578
- else:
579
- avg_val_loss = avg_train_loss
580
-
581
- print(f"Epoch {epoch + 1}/{num_epochs} - Train Loss: {avg_train_loss:.4f} - Val Loss: {avg_val_loss:.4f}\n\n")
582
-
583
- if avg_val_loss < best_val_loss:
584
- best_val_loss = avg_val_loss
585
- patience_counter = 0
586
- self.save(save_path, spm_model_prefix, clean=False)
587
- else:
588
- self.load(save_path)
589
- self.save(save_path, spm_model_prefix, clean=True)
590
- patience_counter += 1
591
- if patience_counter >= patience:
592
- print(f"Early stopping triggered after {epoch + 1} epochs.")
593
- break
594
 
 
 
595
  scheduler.step()
596
- # Allow saving model on keyboard interrupt
597
  except KeyboardInterrupt:
598
  while True:
599
- saving = input("Would you like to save the best (b), current (c), or no (n) model?")
600
- saving = saving.strip().lower
601
- if saving == "best" or saving == "b":
602
- self.load(save_path)
603
- self.save(save_path, spm_model_prefix, clean=True)
604
  break
605
- elif saving == "current" or saving == "c" or saving == "curr":
606
- self.save(save_path, spm_model_prefix, clean=True)
607
  break
608
- elif saving == "n" or saving == "no" or saving == "":
609
- self.save(save_path, spm_model_prefix, clean=True)
610
- os.remove(save_path)
611
  break
612
  else:
613
- print("invalid input, type 'b' 'c' or 'n'")
614
-
615
- print(f"Best model saved to {save_path}")
616
 
 
 
617
  def encode(self, text: Union[str, List[str]], force_single_vector: bool = False) -> torch.Tensor:
618
  """Encodes text into thought vectors.
619
 
@@ -721,14 +758,7 @@ class ThoughtVectors:
721
 
722
  return results
723
 
724
- def save(self, path: str, spm_model_prefix: str, clean: bool = True) -> None:
725
- """Saves the model and SentencePiece data to a tar file.
726
-
727
- Args:
728
- path (str): Path to save the tar file.
729
- spm_model_prefix (str): Prefix for SentencePiece model files.
730
- clean (bool, optional): If True, removes temporary files after saving. Defaults to True.
731
- """
732
  if self.encoder is None or self.decoder is None:
733
  raise RuntimeError("No model to save.")
734
 
@@ -749,16 +779,11 @@ class ThoughtVectors:
749
  'dropout': self.dropout,
750
  'max_len': self.max_len,
751
  'termination_threshold': self.termination_threshold,
752
- 'spm_model_path': f"{spm_model_prefix}.model" # Added to match loading logic
753
  }, "model.pth")
754
  tar.add("model.pth")
755
  tar.add(f"{spm_model_prefix}.model")
756
  tar.add(f"{spm_model_prefix}.vocab")
757
-
758
- if clean:
759
- os.remove("model.pth")
760
- os.remove(f"{spm_model_prefix}.model")
761
- os.remove(f"{spm_model_prefix}.vocab")
762
 
763
  @classmethod
764
  def load(cls, path: str) -> 'ThoughtVectors':
@@ -772,17 +797,12 @@ class ThoughtVectors:
772
  """
773
  translator = cls()
774
  translator.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # load based on available device, not model train device
775
- try:
776
- with tarfile.open(path, "r") as tar:
777
- tar.extractall()
778
- checkpoint = torch.load("model.pth", map_location=torch.device(translator.device))
779
- translator.sp = spm.SentencePieceProcessor()
780
- if not translator.sp.Load("spm.model"):
781
- raise RuntimeError("Failed to load SentencePiece model.")
782
- finally:
783
- for f in ["model.pth", "spm.model", "spm.vocab"]:
784
- if os.path.exists(f):
785
- os.remove(f)
786
 
787
  translator.vocab_size = checkpoint['vocab_size']
788
  translator.d_model = checkpoint['d_model']
@@ -810,44 +830,40 @@ class ThoughtVectors:
810
  translator.decoder.load_state_dict(checkpoint['decoder_state_dict'])
811
 
812
  return translator
813
-
814
-
815
- if __name__ == "__main__":
816
- def process_train(input_file):
817
- data = []
818
- with open(input_file, 'r', encoding='utf-8') as f_in:
819
- reader = csv.reader(f_in)
820
- for row in reader:
821
- sentence1 = row[0].strip()
822
- data.append([sentence1])
823
-
824
- return data
825
 
826
- def process_test(input_file):
827
- data = []
828
- with open(input_file, 'r', encoding='utf-8') as f_in:
829
- reader = csv.reader(f_in)
830
- for row in reader:
831
- sentence1 = row[0].strip()
832
- data.append([sentence1])
833
- return data
834
-
835
- data = process_train("SNLItrain.csv")
836
- val = process_test("SNLIval.csv")
837
-
 
 
 
 
 
 
838
  tv = ThoughtVectors()
839
  tv.train(
840
- group_data=data,
841
- test_data=val,
842
- model="thought_vectors_prototype-0.1.tar",
843
- num_epochs=1000,
844
- batch_size=128,
 
 
845
  accum_steps=1,
846
- learning_rate=5e-4,
847
- weight_decay=2e-5,
848
  length_penalty=0.001,
849
  single_vector_prob=0.1,
850
- save_path="thought_vectors_prototype-0.2.tar",
851
  spm_model_prefix="spm",
852
  vocab_size=8192,
853
  d_model=512,
@@ -859,12 +875,11 @@ if __name__ == "__main__":
859
  dropout=0.1,
860
  max_len=256,
861
  termination_threshold=0.8,
862
- patience=5
863
  )
864
- loaded_tv = ThoughtVectors.load("thought_vectors_prototype-0.2.tar")
865
- thought_vectors = loaded_tv.encode("AI is smart")
866
- generated_text_greedy = loaded_tv.decode(thought_vectors, temperature=0.7, beam_width=0)
867
- generated_text_beam = loaded_tv.decode(thought_vectors, beam_width=5)
868
  print(f"Greedy decoding: {generated_text_greedy}")
869
  print(f"Beam search decoding: {generated_text_beam}")
870
 
 
1
+ import atexit
2
  import random
3
  import math
4
  import torch
5
  import torch.nn as nn
6
  import torch.optim as optim
7
  import sentencepiece as spm
 
8
  from torch.amp import autocast, GradScaler
9
+ from typing import List, Union, Generator, Sequence
10
  import os
11
  import csv
12
  import tempfile
 
155
  prev_context = torch.zeros_like(text_context)
156
 
157
  combined = torch.cat([text_context, prev_context], dim=1)
158
+
159
  next_thought = self.fc_thought(combined)
160
 
161
+ with autocast('cuda', enabled=False): # FP32
162
+ thought_hidden = self.thought_rnn(next_thought.unsqueeze(1).float(), thought_hidden.float())[1]
163
+
164
  termination_logit = self.fc_terminate(thought_hidden.squeeze(0))
165
  termination_score = torch.sigmoid(termination_logit).squeeze(-1)
166
 
 
374
  self.dropout = None
375
  self.max_len = None
376
  self.termination_threshold = None
377
+
378
+ # Track files to clean up
379
+ self._temp_files = set()
380
+ # Register cleanup function
381
+ atexit.register(self._cleanup)
382
 
383
  def train(self,
384
  group_data: Union[str, List[List[str]]],
385
  test_data: Union[str, List[List[str]]] = None,
386
  model: str = None,
387
+ num_epochs: int = 2048,
388
+ batch_size: int = 128,
389
+ batches_per_val: int = 16,
390
+ val_batches: int = 8,
391
  accum_steps: int = 4,
392
  learning_rate: float = 2e-4,
393
  weight_decay: float = 1e-5,
 
414
  model (str, optional): Path to pre-existing model to load. If None, creates a new model. Defaults to None.
415
  num_epochs (int, optional): Number of training epochs. Defaults to 30.
416
  batch_size (int, optional): Batch size. Defaults to 8.
417
+ batches_per_val (int, optional): Validate model every x batches. Defaults to 16
418
+ val_batches (int, optional): Number of batches to use in validation, speeds up validation. Defaults to 8
419
  accum_steps (int, optional): Number of gradient accumulation steps. Defaults to 4.
420
  learning_rate (float, optional): Learning rate for Adam optimizer. Defaults to 2e-4.
421
  weight_decay (float, optional): Weight decay for regularization. Defaults to 1e-5.
 
433
  dropout (float, optional): Dropout probability. Defaults to 0.1.
434
  max_len (int, optional): Maximum sequence length for positional encoding. Defaults to 1024.
435
  termination_threshold (float, optional): Threshold for stopping thought generation. Defaults to 0.75.
436
+ patience (int, optional): BATCHES to wait for improvement before early stopping. Defaults to 5.
437
  """
438
  self.d_model = d_model
439
  self.max_thoughts = max_thoughts
 
453
  if model and os.path.exists(model):
454
  print("loading model...")
455
  loaded_instance = self.load(model)
456
+ self.__dict__.update(loaded_instance.__dict__)
457
  print(f"Loaded pre-existing model from {model}")
458
  else:
459
  print("building models")
 
463
  temp_file.write(sentence.strip() + "\n")
464
  temp_dataset_path = temp_file.name
465
 
466
+ # Use a persistent directory instead of temp_dir to avoid premature cleanup
467
+ os.makedirs("temp_spm_dir", exist_ok=True)
468
+ temp_spm_prefix = os.path.join("temp_spm_dir", "spm")
469
  try:
470
  print("training sentence piece (could take a while on massive datasets)")
 
471
  spm.SentencePieceTrainer.Train(
472
  input=temp_dataset_path,
473
  model_prefix=temp_spm_prefix,
 
478
  input_sentence_size=8_388_608,
479
  train_extremely_large_corpus=True
480
  )
481
+ # Ensure files are copied to the final location
482
  shutil.copy(f"{temp_spm_prefix}.model", f"{spm_model_prefix}.model")
483
  shutil.copy(f"{temp_spm_prefix}.vocab", f"{spm_model_prefix}.vocab")
484
  if not self.sp.Load(f"{spm_model_prefix}.model"):
485
  raise RuntimeError("Failed to load SentencePiece model.")
486
  finally:
487
  os.remove(temp_dataset_path)
488
+ # Only remove temp_spm_dir after successful save, handled in save method
489
+
490
+ self.vocab_size = self.sp.GetPieceSize()
491
+ self.encoder = ThoughtEncoder(
492
+ vocab_size=self.vocab_size, d_model=d_model, max_thoughts=max_thoughts,
493
+ nhead=encoder_nhead, num_layers=encoder_layers, dropout=dropout,
494
+ termination_threshold=termination_threshold, max_len=self.max_len
495
+ ).to(self.device)
496
+ self.decoder = ThoughtDecoder(
497
+ vocab_size=self.vocab_size, d_model=d_model, num_layers=decoder_layers,
498
+ nhead=decoder_nhead, dropout=dropout, max_len=self.max_len
499
+ ).to(self.device)
500
 
501
  self.vocab_size = self.sp.GetPieceSize()
502
  self.encoder = ThoughtEncoder(
 
517
 
518
  best_val_loss = float('inf')
519
  patience_counter = 0
520
+ best_batch = 0 # Track best batch number
521
 
522
  print("Beginning training")
523
  try:
 
525
  total_train_loss = 0.0
526
  num_train_batches = (len(train_lazy_data) + batch_size - 1) // batch_size
527
 
 
528
  for i in range(0, len(train_lazy_data), batch_size):
529
+ batch_idx = i // batch_size + 1
530
  raw_batch = train_lazy_data[i:i + batch_size]
531
  tokenized_batch = [
532
  [[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
 
543
  thought_vectors = self.encoder(group_tensor, force_single_vector)
544
  output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
545
 
 
546
  mask = (group_tensor[:, 1:] != self.sp.pad_id()).float()
547
  loss = criterion(output_logits.reshape(-1, self.vocab_size),
548
  group_tensor[:, 1:].reshape(-1))
549
  loss = (loss * mask.reshape(-1)).sum() / mask.sum().clamp(min=1.0)
550
+ if torch.isnan(loss):
551
+ print(f"NaN detected: mask_sum={mask.sum().item()}, loss_pre_mask={loss.item()}")
552
+ print(f"group_tensor={group_tensor}\n\n")
553
+ print(f"thought vectors={thought_vectors}\n\n")
554
+ print("Raising keyboard interrupt to allow preservation.")
555
+ raise KeyboardInterrupt
556
  if not force_single_vector:
557
  loss += length_penalty * thought_vectors.shape[1]
558
  batch_loss += loss / accum_steps
559
 
560
  scaler.scale(batch_loss).backward()
561
  total_train_loss += batch_loss.item() * accum_steps
562
+ if (batch_idx % accum_steps) == 0:
563
+ scaler.unscale_(optimizer)
564
+ torch.nn.utils.clip_grad_norm_(
565
+ list(self.encoder.parameters()) + list(self.decoder.parameters()), max_norm=0.5)
566
  scaler.step(optimizer)
567
  scaler.update()
568
  optimizer.zero_grad()
569
  torch.cuda.empty_cache()
570
 
571
+ # Per group of batches validation
572
+ if (batch_idx + 1) % batches_per_val == 0 or batch_idx == num_train_batches:
573
+ if test_lazy_data is None:
574
+ avg_val_loss = batch_loss.item() # Use train loss if no val
575
+ else:
576
+ self.encoder.eval()
577
+ self.decoder.eval()
578
+ total_val_loss = 0.0
579
+ num_val_batches = min(val_batches,
580
+ (len(test_lazy_data) + batch_size - 1) // batch_size) # Quick val subset
581
+
582
+ with torch.no_grad():
583
+ val_indices = list(range(0, len(test_lazy_data), batch_size))[:num_val_batches]
584
+ for i in val_indices:
585
+ raw_batch = test_lazy_data[i:i + batch_size]
586
+ tokenized_batch = [
587
+ [[self.sp.bos_id()] + self.sp.EncodeAsIds(s) + [self.sp.eos_id()] for s in group]
588
+ for group in raw_batch
589
+ ]
590
+ group_batch = group_collate_fn(tokenized_batch)
591
+
592
+ batch_val_loss = 0.0
593
+ for group_tensor in group_batch:
594
+ if group_tensor.shape[1] <= self.max_len and group_tensor.numel() > 0:
595
+ group_tensor = group_tensor.to(self.device)
596
+ thought_vectors = self.encoder(group_tensor)
597
+ output_logits = self.decoder(thought_vectors, group_tensor[:, :-1])
598
+ val_loss = criterion(output_logits.reshape(-1, self.vocab_size),
599
+ group_tensor[:, 1:].reshape(-1))
600
+ val_loss += length_penalty * thought_vectors.shape[1]
601
+ batch_val_loss += val_loss.item()
602
+ total_val_loss += batch_val_loss
603
+ print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
604
+ print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n")
605
+
606
+ avg_val_loss = total_val_loss / num_val_batches if num_val_batches > 0 else float('inf')
607
+ print(f"Val Loss: {avg_val_loss:.4f}\n\n")
608
+
609
+ # Per-batch patience
610
+ if avg_val_loss < best_val_loss:
611
+ best_val_loss = avg_val_loss
612
+ best_batch = batch_idx + epoch * num_train_batches # Total batch count
613
+ if patience_counter > 0:
614
+ patience_counter -= 1
615
+ self.save(save_path, spm_model_prefix)
616
+ else:
617
+ patience_counter += 1
618
+ if patience_counter >= patience:
619
+ print(
620
+ f"Early stopping triggered at batch {batch_idx} (total {best_batch + patience}), best val loss: {best_val_loss:.4f}")
621
+ self.load(save_path) # Roll back to best
622
+ self.save(save_path, spm_model_prefix) # Save and clean up
623
+ return # Exit training
624
+
625
+ print(f"Batch {batch_idx}/{num_train_batches} - Loss: {batch_loss.item():.4f}")
626
  print(f" Input: {self.sp.DecodeIds(group_tensor[0].tolist())}")
627
+ print(f"Output: {self.sp.DecodeIds(output_logits.argmax(-1)[0].tolist())}\n")
 
 
 
 
 
 
 
 
 
 
628
 
629
+ self.encoder.train()
630
+ self.decoder.train()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
 
632
+ avg_train_loss = total_train_loss / num_train_batches if num_train_batches > 0 else float('inf')
633
+ print(f"Epoch {epoch + 1}/{num_epochs} - Train Loss: {avg_train_loss:.4f}\n")
634
  scheduler.step()
635
+
636
  except KeyboardInterrupt:
637
  while True:
638
+ saving = input("Would you like to save the best (b), current (c), or no (n) model: ")
639
+ saving = saving.strip().lower()
640
+ if saving in ("best", "b"):
 
 
641
  break
642
+ elif saving in ("current", "c", "curr"):
643
+ self.save(save_path, spm_model_prefix)
644
  break
645
+ elif saving in ("n", "no", ""):
646
+ if os.path.exists(save_path):
647
+ os.remove(save_path)
648
  break
649
  else:
650
+ print("invalid input, type 'b', 'c', or 'n'")
 
 
651
 
652
+ print(f"Model saved to {save_path}")
653
+
654
  def encode(self, text: Union[str, List[str]], force_single_vector: bool = False) -> torch.Tensor:
655
  """Encodes text into thought vectors.
656
 
 
758
 
759
  return results
760
 
761
+ def save(self, path: str, spm_model_prefix: str) -> None:
 
 
 
 
 
 
 
762
  if self.encoder is None or self.decoder is None:
763
  raise RuntimeError("No model to save.")
764
 
 
779
  'dropout': self.dropout,
780
  'max_len': self.max_len,
781
  'termination_threshold': self.termination_threshold,
782
+ 'spm_model_path': f"{spm_model_prefix}.model"
783
  }, "model.pth")
784
  tar.add("model.pth")
785
  tar.add(f"{spm_model_prefix}.model")
786
  tar.add(f"{spm_model_prefix}.vocab")
 
 
 
 
 
787
 
788
  @classmethod
789
  def load(cls, path: str) -> 'ThoughtVectors':
 
797
  """
798
  translator = cls()
799
  translator.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # load based on available device, not model train device
800
+ with tarfile.open(path, "r") as tar:
801
+ tar.extractall()
802
+ checkpoint = torch.load("model.pth", map_location=torch.device(translator.device))
803
+ translator.sp = spm.SentencePieceProcessor()
804
+ if not translator.sp.Load("spm.model"):
805
+ raise RuntimeError("Failed to load SentencePiece model.")
 
 
 
 
 
806
 
807
  translator.vocab_size = checkpoint['vocab_size']
808
  translator.d_model = checkpoint['d_model']
 
830
  translator.decoder.load_state_dict(checkpoint['decoder_state_dict'])
831
 
832
  return translator
 
 
 
 
 
 
 
 
 
 
 
 
833
 
834
+ def _add_temp_file(self, filepath: str):
835
+ """Add a file to the set of temporary files to clean up on close."""
836
+ self._temp_files.add(os.path.abspath(filepath))
837
+
838
+ def _cleanup(self):
839
+ """Remove all tracked temporary files."""
840
+ for filepath in self._temp_files:
841
+ if os.path.exists(filepath):
842
+ try:
843
+ os.remove(filepath)
844
+ except OSError as e:
845
+ print(f"Failed to clean up {filepath}: {e}")
846
+ # Clear the set after cleanup
847
+ self._temp_files.clear()
848
+
849
+
850
+ # example usage
851
+ if __name__ == "__main__":
852
  tv = ThoughtVectors()
853
  tv.train(
854
+ group_data="train.csv",
855
+ test_data="val.csv",
856
+ model="thought_vectors_prototype.tar",
857
+ num_epochs=2048,
858
+ batch_size=256,
859
+ batches_per_val=32,
860
+ val_batches=16,
861
  accum_steps=1,
862
+ learning_rate=2e-4,
863
+ weight_decay=1e-5,
864
  length_penalty=0.001,
865
  single_vector_prob=0.1,
866
+ save_path="thought_vectors_prototype-0.2.0.tar",
867
  spm_model_prefix="spm",
868
  vocab_size=8192,
869
  d_model=512,
 
875
  dropout=0.1,
876
  max_len=256,
877
  termination_threshold=0.8,
878
+ patience=10
879
  )
880
+ thought_vectors = tv.encode("AI is smart")
881
+ generated_text_greedy = tv.decode(thought_vectors, temperature=0.7, beam_width=0)
882
+ generated_text_beam = tv.decode(thought_vectors, beam_width=5)
 
883
  print(f"Greedy decoding: {generated_text_greedy}")
884
  print(f"Beam search decoding: {generated_text_beam}")
885