ViuAI commited on
Commit
ffb997d
ยท
verified ยท
1 Parent(s): c6e9625

Update code/train_sft.py with user enhancements

Browse files
Files changed (1) hide show
  1. code/train_sft.py +71 -25
code/train_sft.py CHANGED
@@ -74,7 +74,9 @@ if cur_dir not in sys.path:
74
  from model import ViuAI, Transformer
75
  from config import ViuAIConfig, ModelArgs
76
 
77
- PAD_TOKEN_ID = 64000
 
 
78
 
79
  DOMAIN_NAMES_V18 = {
80
  0: "gk_polity_history",
@@ -308,10 +310,11 @@ def get_lr(it, warmup_steps, total_steps, max_lr, min_lr):
308
 
309
 
310
 
311
- DEFAULT_HF_TOKEN = "".join(["hf_", "ssyCVhuny", "XxjGdqKp", "VLPpkmWK", "FrrMOIFbg"])
 
312
 
313
  def ensure_cloud_data_and_checkpoint(version: str, data_dir: str, ckpt_path: str, token: str = None):
314
- token = token or os.environ.get("HF_TOKEN") or DEFAULT_HF_TOKEN
315
 
316
  if str(version).lower() == "viutranslate":
317
  stage_subfolder = "ViuTranslate"
@@ -404,7 +407,9 @@ def run_live_eval_previews(model, tokenizer, device, version=""):
404
  if tokenizer is None:
405
  return
406
 
407
- if any(k in str(version).lower() for k in ["translat", "viutranslate"]):
 
 
408
  test_suite = [
409
  ("Identity", "<|user|>\nWho created you and what is your purpose?<|endofturn|>\n<|assistant|>\n", 40),
410
  ("EN -> HI Direct", "<|user|>\nThe sun rises in the east and sets in the west.<|endofturn|>\n<|assistant|>\n", 45),
@@ -412,7 +417,7 @@ def run_live_eval_previews(model, tokenizer, device, version=""):
412
  ("Proverb Command", "<|user|>\nTranslate to Hindi: 'Consistency and discipline are the keys to long term success.'<|endofturn|>\n<|assistant|>\n", 50),
413
  ("Hinglish Command","<|user|>\nTranslate to Hinglish: 'I am waiting outside your office, please call me when you are free.'<|endofturn|>\n<|assistant|>\n", 50)
414
  ]
415
- elif any(k in str(version).lower() for k in ["v2", "sft_v2"]):
416
  test_suite = [
417
  ("Identity", "<|user|>\nWho created you and what is your name?<|endofturn|>\n<|assistant|>\n", 40),
418
  ("Indian Greet", "<|user|>\nRam Ram bhai! Kaise ho?<|endofturn|>\n<|assistant|>\n", 35),
@@ -509,6 +514,10 @@ def main():
509
 
510
  ckpt_filename = "viutranslate_final.pt" if str(args.version).lower() == "viutranslate" else f"sft_{args.version}_final.pt"
511
  save_path = os.path.join(output_dir, ckpt_filename)
 
 
 
 
512
 
513
  if args.init_ckpt:
514
  init_ckpt = args.init_ckpt
@@ -524,16 +533,20 @@ def main():
524
  tokenizer = None
525
  tok_path = os.path.join(root_dir, "tokenizer", "tokenizer.json")
526
  global PAD_TOKEN_ID, EOT_ID
527
- PAD_TOKEN_ID = 64000
 
528
  EOT_ID = 64002
 
529
  if os.path.exists(tok_path):
530
  try:
531
  from tokenizers import Tokenizer
532
  tokenizer = Tokenizer.from_file(tok_path)
533
  v = tokenizer.get_vocab()
534
- PAD_TOKEN_ID = v.get("<|user|>", 64000)
535
  EOT_ID = v.get("<|endofturn|>", 64002)
536
- print(f"โœ… Tokenizer bound dynamically: PAD_TOKEN_ID={PAD_TOKEN_ID}, EOT_ID={EOT_ID}")
 
 
 
537
  except Exception as e:
538
  print(f"โš ๏ธ Could not load tokenizer for special tokens ({e}). Using defaults.")
539
 
@@ -553,13 +566,17 @@ def main():
553
  print(f"โš ๏ธ Notice: Could not parse metadata.json domain_names ({e}). Falling back to version heuristic.")
554
 
555
  if not loaded_dynamic_domains:
556
- if any(v in str(args.version).lower() for v in ["v2", "2", "sft_v2"]):
 
 
 
 
557
  DOMAIN_NAMES = DOMAIN_NAMES_V2
558
- elif any(v in str(args.version).lower() for v in ["v1", "1", "sft_v1"]):
559
  DOMAIN_NAMES = DOMAIN_NAMES_V1
560
- elif any(v in str(args.version).lower() for v in ["22", "23", "24", "25", "26"]):
561
  DOMAIN_NAMES = DOMAIN_NAMES_V22
562
- elif any(v in str(args.version).lower() for v in ["19", "20", "21"]):
563
  DOMAIN_NAMES = DOMAIN_NAMES_V19
564
  else:
565
  DOMAIN_NAMES = DOMAIN_NAMES_V2
@@ -601,17 +618,34 @@ def main():
601
  return None
602
  return cand1
603
 
604
- # 1. Load Data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  train_dataset = SFTDataset(
606
- ids_path=find_shard_path(stage_data_dir, "train_tokens", args.version),
607
- labels_path=find_shard_path(stage_data_dir, "train_labels", args.version),
608
- offsets_path=find_shard_path(stage_data_dir, "train_offsets", args.version),
609
  domains_path=find_shard_path(stage_data_dir, "train_domains", args.version)
610
  )
611
  val_dataset = SFTDataset(
612
- ids_path=find_shard_path(stage_data_dir, "val_tokens", args.version),
613
- labels_path=find_shard_path(stage_data_dir, "val_labels", args.version),
614
- offsets_path=find_shard_path(stage_data_dir, "val_offsets", args.version),
615
  domains_path=find_shard_path(stage_data_dir, "val_domains", args.version)
616
  )
617
 
@@ -646,7 +680,7 @@ def main():
646
  print("๐Ÿ’พ Activation Checkpointing: ENABLED (low-VRAM mode)")
647
 
648
  model_args = ViuAIConfig(
649
- vocab_size=64003,
650
  context_length=2048,
651
  z_loss_weight=0.0,
652
  attn_dropout=0.05,
@@ -766,13 +800,20 @@ def main():
766
  inps = inps.to(device, non_blocking=True)
767
  lbls = lbls.to(device, non_blocking=True)
768
  with autocast_ctx:
769
- # Single forward pass: get hidden states
770
  B, T = inps.shape
771
  x = raw_m.tok_emb(inps)
772
  cos = raw_m.rope_cos.to(x.device)
773
  sin = raw_m.rope_sin.to(x.device)
 
 
 
 
 
 
 
774
  for block in raw_m.blocks:
775
- x = block(x, cos, sin, None)
776
  x = raw_m.final_norm(x)
777
  hidden = x[:, :-1, :].contiguous() # [B, T-1, D]
778
  shift_labels = lbls[:, 1:].contiguous() # [B, T-1]
@@ -855,7 +896,7 @@ def main():
855
  total_tokens_trained += active_tokens_count
856
 
857
  with autocast_ctx:
858
- _, loss = model(inputs, targets=labels, pad_id=None, shift_labels=True)
859
  loss_scaled = loss / grad_acc
860
 
861
  loss_scaled.backward()
@@ -925,7 +966,10 @@ def main():
925
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
926
  }
927
  torch.save(save_payload, save_path)
928
- print(f" ๐Ÿ’พ Saved checkpoint -> {save_path}\n")
 
 
 
929
 
930
  # End of Epoch Handling
931
  if epoch_batches > 0 and (micro_idx + 1) % grad_acc != 0:
@@ -969,11 +1013,13 @@ def main():
969
  }
970
  torch.save(final_payload, save_path)
971
  print(f"๐Ÿ’พ Final master checkpoint saved to: {save_path}")
 
 
972
 
973
  # Push to Hugging Face Hub (Only Final Checkpoint)
974
  if args.push_to_hf:
975
  print("\n๐Ÿš€ Pushing Final Checkpoint to Hugging Face Model Hub (ViuAI/ViuAI-500M)...")
976
- token = args.hf_token or os.environ.get("HF_TOKEN") or DEFAULT_HF_TOKEN
977
  if token:
978
  try:
979
  api = HfApi(token=token)
 
74
  from model import ViuAI, Transformer
75
  from config import ViuAIConfig, ModelArgs
76
 
77
+ # PAD = EOT (end-of-turn) used as padding, standard for models without a dedicated
78
+ # pad token. Never use <|user|> (64000) as pad: it pollutes turn-start semantics.
79
+ PAD_TOKEN_ID = 64002
80
 
81
  DOMAIN_NAMES_V18 = {
82
  0: "gk_polity_history",
 
310
 
311
 
312
 
313
+ # SECURITY: HF token must come from environment only. Never hardcode tokens.
314
+ # Set HF_TOKEN env var before running. See README for instructions.
315
 
316
  def ensure_cloud_data_and_checkpoint(version: str, data_dir: str, ckpt_path: str, token: str = None):
317
+ token = token or os.environ.get("HF_TOKEN")
318
 
319
  if str(version).lower() == "viutranslate":
320
  stage_subfolder = "ViuTranslate"
 
407
  if tokenizer is None:
408
  return
409
 
410
+ _vnorm = str(version).lower().replace("sft_", "").replace("sft", "").strip()
411
+ _vnum = _vnorm[1:] if _vnorm.startswith("v") and _vnorm[1:].isdigit() else _vnorm
412
+ if _vnorm in ("translat", "viutranslate") or "translat" in _vnorm:
413
  test_suite = [
414
  ("Identity", "<|user|>\nWho created you and what is your purpose?<|endofturn|>\n<|assistant|>\n", 40),
415
  ("EN -> HI Direct", "<|user|>\nThe sun rises in the east and sets in the west.<|endofturn|>\n<|assistant|>\n", 45),
 
417
  ("Proverb Command", "<|user|>\nTranslate to Hindi: 'Consistency and discipline are the keys to long term success.'<|endofturn|>\n<|assistant|>\n", 50),
418
  ("Hinglish Command","<|user|>\nTranslate to Hinglish: 'I am waiting outside your office, please call me when you are free.'<|endofturn|>\n<|assistant|>\n", 50)
419
  ]
420
+ elif _vnum == "2":
421
  test_suite = [
422
  ("Identity", "<|user|>\nWho created you and what is your name?<|endofturn|>\n<|assistant|>\n", 40),
423
  ("Indian Greet", "<|user|>\nRam Ram bhai! Kaise ho?<|endofturn|>\n<|assistant|>\n", 35),
 
514
 
515
  ckpt_filename = "viutranslate_final.pt" if str(args.version).lower() == "viutranslate" else f"sft_{args.version}_final.pt"
516
  save_path = os.path.join(output_dir, ckpt_filename)
517
+ # Best-checkpoint is kept in a SEPARATE file so later (worse) evals can never
518
+ # overwrite the best weights. Previously both shared save_path.
519
+ best_filename = "viutranslate_best.pt" if str(args.version).lower() == "viutranslate" else f"sft_{args.version}_best.pt"
520
+ best_save_path = os.path.join(output_dir, best_filename)
521
 
522
  if args.init_ckpt:
523
  init_ckpt = args.init_ckpt
 
533
  tokenizer = None
534
  tok_path = os.path.join(root_dir, "tokenizer", "tokenizer.json")
535
  global PAD_TOKEN_ID, EOT_ID
536
+ # EOT-as-pad: padding must never reuse <|user|> (turn-start semantics).
537
+ PAD_TOKEN_ID = 64002
538
  EOT_ID = 64002
539
+ MODEL_VOCAB = 64003
540
  if os.path.exists(tok_path):
541
  try:
542
  from tokenizers import Tokenizer
543
  tokenizer = Tokenizer.from_file(tok_path)
544
  v = tokenizer.get_vocab()
 
545
  EOT_ID = v.get("<|endofturn|>", 64002)
546
+ # EOT-as-pad: padding must never reuse <|user|> (turn-start semantics).
547
+ PAD_TOKEN_ID = EOT_ID
548
+ MODEL_VOCAB = tokenizer.get_vocab_size()
549
+ print(f"โœ… Tokenizer bound dynamically: PAD_TOKEN_ID={PAD_TOKEN_ID}, EOT_ID={EOT_ID}, VOCAB={MODEL_VOCAB}")
550
  except Exception as e:
551
  print(f"โš ๏ธ Could not load tokenizer for special tokens ({e}). Using defaults.")
552
 
 
566
  print(f"โš ๏ธ Notice: Could not parse metadata.json domain_names ({e}). Falling back to version heuristic.")
567
 
568
  if not loaded_dynamic_domains:
569
+ # Exact normalized match only. Substring matching ("2" in "v26") previously
570
+ # mis-assigned v21/v22-v26 to the wrong domain map, corrupting per-domain telemetry.
571
+ vnorm = str(args.version).lower().replace("sft_", "").replace("sft", "").strip()
572
+ vnum = vnorm[1:] if vnorm.startswith("v") and vnorm[1:].isdigit() else vnorm
573
+ if vnorm == "viutranslate" or vnum in ("2",):
574
  DOMAIN_NAMES = DOMAIN_NAMES_V2
575
+ elif vnum in ("1",):
576
  DOMAIN_NAMES = DOMAIN_NAMES_V1
577
+ elif vnum in ("22", "23", "24", "25", "26"):
578
  DOMAIN_NAMES = DOMAIN_NAMES_V22
579
+ elif vnum in ("19", "20", "21"):
580
  DOMAIN_NAMES = DOMAIN_NAMES_V19
581
  else:
582
  DOMAIN_NAMES = DOMAIN_NAMES_V2
 
618
  return None
619
  return cand1
620
 
621
+ # 1. Load Data (resolve + validate shard paths BEFORE touching np.load so a
622
+ # wrong --version/--data_dir fails with a clear message, not a cryptic error)
623
+ _shard_paths = {
624
+ name: find_shard_path(stage_data_dir, name, args.version)
625
+ for name in ["train_tokens", "train_labels", "train_offsets",
626
+ "val_tokens", "val_labels", "val_offsets"]
627
+ }
628
+ for _label, _p in _shard_paths.items():
629
+ if _p is None or not os.path.exists(_p):
630
+ try:
631
+ _have = sorted(os.listdir(stage_data_dir))
632
+ except OSError:
633
+ _have = []
634
+ raise FileNotFoundError(
635
+ f"Missing required shard '{_label}*' for version '{args.version}' in: {stage_data_dir} "
636
+ f"(found: {_have}). Check --version/--data_dir, or run the dataset builder first."
637
+ )
638
+
639
  train_dataset = SFTDataset(
640
+ ids_path=_shard_paths["train_tokens"],
641
+ labels_path=_shard_paths["train_labels"],
642
+ offsets_path=_shard_paths["train_offsets"],
643
  domains_path=find_shard_path(stage_data_dir, "train_domains", args.version)
644
  )
645
  val_dataset = SFTDataset(
646
+ ids_path=_shard_paths["val_tokens"],
647
+ labels_path=_shard_paths["val_labels"],
648
+ offsets_path=_shard_paths["val_offsets"],
649
  domains_path=find_shard_path(stage_data_dir, "val_domains", args.version)
650
  )
651
 
 
680
  print("๐Ÿ’พ Activation Checkpointing: ENABLED (low-VRAM mode)")
681
 
682
  model_args = ViuAIConfig(
683
+ vocab_size=MODEL_VOCAB,
684
  context_length=2048,
685
  z_loss_weight=0.0,
686
  attn_dropout=0.05,
 
800
  inps = inps.to(device, non_blocking=True)
801
  lbls = lbls.to(device, non_blocking=True)
802
  with autocast_ctx:
803
+ # Single forward pass: get hidden states (with pad masking)
804
  B, T = inps.shape
805
  x = raw_m.tok_emb(inps)
806
  cos = raw_m.rope_cos.to(x.device)
807
  sin = raw_m.rope_sin.to(x.device)
808
+ eval_mask = None
809
+ if (inps == PAD_TOKEN_ID).any():
810
+ causal = torch.tril(torch.ones(T, T, dtype=torch.bool, device=x.device))
811
+ key_mask = (inps != PAD_TOKEN_ID).unsqueeze(1).unsqueeze(2)
812
+ bool_mask = causal.unsqueeze(0).unsqueeze(0) & key_mask
813
+ eval_mask = torch.zeros(B, 1, T, T, dtype=x.dtype, device=x.device)
814
+ eval_mask = eval_mask.masked_fill(~bool_mask, float("-inf"))
815
  for block in raw_m.blocks:
816
+ x = block(x, cos, sin, eval_mask)
817
  x = raw_m.final_norm(x)
818
  hidden = x[:, :-1, :].contiguous() # [B, T-1, D]
819
  shift_labels = lbls[:, 1:].contiguous() # [B, T-1]
 
896
  total_tokens_trained += active_tokens_count
897
 
898
  with autocast_ctx:
899
+ _, loss = model(inputs, targets=labels, pad_id=PAD_TOKEN_ID, shift_labels=True)
900
  loss_scaled = loss / grad_acc
901
 
902
  loss_scaled.backward()
 
966
  "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
967
  }
968
  torch.save(save_payload, save_path)
969
+ print(f" ๐Ÿ’พ Saved checkpoint -> {save_path}")
970
+ if is_best:
971
+ torch.save(save_payload, best_save_path)
972
+ print(f" ๐Ÿ† Saved BEST checkpoint -> {best_save_path}")
973
 
974
  # End of Epoch Handling
975
  if epoch_batches > 0 and (micro_idx + 1) % grad_acc != 0:
 
1013
  }
1014
  torch.save(final_payload, save_path)
1015
  print(f"๐Ÿ’พ Final master checkpoint saved to: {save_path}")
1016
+ if os.path.exists(best_save_path):
1017
+ print(f"๐Ÿ† Best checkpoint (lowest val loss) kept at: {best_save_path}")
1018
 
1019
  # Push to Hugging Face Hub (Only Final Checkpoint)
1020
  if args.push_to_hf:
1021
  print("\n๐Ÿš€ Pushing Final Checkpoint to Hugging Face Model Hub (ViuAI/ViuAI-500M)...")
1022
+ token = args.hf_token or os.environ.get("HF_TOKEN")
1023
  if token:
1024
  try:
1025
  api = HfApi(token=token)