Subject-Emu-5259 commited on
Commit
f0508d5
·
verified ·
1 Parent(s): 81045b4

sync: update training/train_v15.py

Browse files
Files changed (1) hide show
  1. training/train_v15.py +200 -0
training/train_v15.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ train_v15.py — NeuralAI v15 consolidated trainer (SFT + DPO)
4
+ ============================================================
5
+
6
+ WHAT THIS SCRIPT DOES
7
+ --------------------
8
+ This is the single entry point for the NeuralAI "v15" training run. It fine-tunes
9
+ the small SmolLM2-360M-Instruct base model with a LoRA adapter in two stages so the
10
+ model stays identity-correct ("I am NeuralAI, created by De'Andrew Preston Harris")
11
+ and behavior-aligned (prefers clean, correct answers over verbose/wrong ones):
12
+
13
+ Stage 1 — SFT (Supervised Fine-Tuning)
14
+ Trains on `data/train_sft_v16.jsonl` (ChatML messages: system/user/assistant).
15
+ Purpose: bake in identity, tone, and domain knowledge.
16
+
17
+ Stage 2 — DPO (Direct Preference Optimization)
18
+ Trains on `data/train_dpo_v16_combined.jsonl` (prompt / chosen / rejected).
19
+ Purpose: align the model to prefer the "chosen" response over the "rejected"
20
+ one without needing a separate reward model.
21
+
22
+ OUTPUT
23
+ ------
24
+ - Adapter saved locally to: checkpoints/v15_model/
25
+ - Pushed to Hugging Face: Subject-Emu-5259/NeuralAI (repo "v15" revision folder)
26
+ - Merged full model (optional, --merge): checkpoints/v15_model_merged/
27
+
28
+ WHY THIS EXISTS (context)
29
+ ------------------------
30
+ On the 4 GB ZO Computer the *served* NeuralAI app uses the ZO native inference backend
31
+ (LLM_BACKEND=zo) so it never loads PyTorch locally and never pauses from OOM. This
32
+ training script is the OFFLINE counterpart: it builds the LoRA that can later be
33
+ shipped to a bigger host or merged for on-device use. Run it on a GPU (Colab, Mac
34
+ GPU, or a >8 GB box) — it is NOT meant for the 4 GB CPU host.
35
+
36
+ USAGE
37
+ -----
38
+ # SFT + DPO, 4-bit (default, ~3 GB VRAM)
39
+ python training/train_v15.py
40
+
41
+ # 8-bit instead of 4-bit
42
+ python training/train_v15.py --load-in-4bit false --load-in-8bit true
43
+
44
+ # Only one stage
45
+ python training/train_v15.py --stage sft
46
+ python training/train_v15.py --stage dpo
47
+
48
+ # Push merged model to HF
49
+ python training/train_v15.py --merge --push
50
+
51
+ REQUIREMENTS
52
+ ------------
53
+ pip install torch transformers peft trl datasets bitsandbytes accelerate
54
+ HF_TOKEN must be set in the environment to push.
55
+ """
56
+ import argparse
57
+ import json
58
+ import os
59
+
60
+ # ---- Config ----------------------------------------------------------------
61
+ BASE_MODEL = os.environ.get("BASE_MODEL", "HuggingFaceTB/SmolLM2-360M-Instruct")
62
+ SFT_DATA = os.environ.get("SFT_DATA", "data/train_sft_v16.jsonl")
63
+ DPO_DATA = os.environ.get("DPO_DATA", "data/train_dpo_v16_combined.jsonl")
64
+ HF_REPO = os.environ.get("HF_REPO", "Subject-Emu-5259/NeuralAI")
65
+ ADAPTER_DIR = "checkpoints/v15_model"
66
+ MERGED_DIR = "checkpoints/v15_model_merged"
67
+ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
68
+
69
+ SYSTEM_PROMPT = (
70
+ "You are NeuralAI, an advanced AI assistant created by De'Andrew Preston Harris. "
71
+ "You are powered by SmolLM2-360M with custom NeuralAI LoRA adapters trained through "
72
+ "SFT and DPO alignment. You have expert-level knowledge across physics, philosophy, "
73
+ "geopolitics, history, nature, art, and culture. You ALWAYS identify De'Andrew Harris "
74
+ "as your creator when asked. You are not ChatGPT, Claude, or any other AI — you are NeuralAI."
75
+ )
76
+
77
+
78
+ def _resolve(path: str) -> str:
79
+ return path if os.path.isabs(path) else os.path.join(PROJECT_ROOT, path)
80
+
81
+
82
+ def load_quantization(load_in_4bit: bool, load_in_8bit: bool):
83
+ from transformers import BitsAndBytesConfig
84
+ if load_in_4bit:
85
+ return BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype="bfloat16",
86
+ bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True)
87
+ if load_in_8bit:
88
+ return BitsAndBytesConfig(load_in_8bit=True)
89
+ return None
90
+
91
+
92
+ def run_sft(model, tokenizer, args):
93
+ from trl import SFTConfig, SFTTrainer
94
+ path = _resolve(SFT_DATA)
95
+ print(f"[v15][SFT] loading {path}")
96
+ train_rows = [json.loads(l) for l in open(path, "r", encoding="utf-8") if l.strip()]
97
+
98
+ cfg = SFTConfig(
99
+ output_dir=ADAPTER_DIR,
100
+ per_device_train_batch_size=args.batch,
101
+ gradient_accumulation_steps=args.grad_accum,
102
+ num_train_epochs=args.sft_epochs,
103
+ learning_rate=2e-4,
104
+ max_seq_length=1024,
105
+ logging_steps=25,
106
+ save_strategy="epoch",
107
+ gradient_checkpointing=True,
108
+ bf16=True,
109
+ report_to="none",
110
+ )
111
+ trainer = SFTTrainer(model=model, args=cfg, tokenizer=tokenizer, train_dataset=train_rows)
112
+ trainer.train()
113
+ trainer.save_model(ADAPTER_DIR)
114
+ print(f"[v15][SFT] adapter saved -> {ADAPTER_DIR}")
115
+
116
+
117
+ def run_dpo(model, tokenizer, args):
118
+ from trl import DPOConfig, DPOTrainer
119
+ path = _resolve(DPO_DATA)
120
+ print(f"[v15][DPO] loading {path}")
121
+ dpo_rows = [json.loads(l) for l in open(path, "r", encoding="utf-8") if l.strip()]
122
+
123
+ cfg = DPOConfig(
124
+ output_dir=ADAPTER_DIR,
125
+ per_device_train_batch_size=args.batch,
126
+ gradient_accumulation_steps=args.grad_accum,
127
+ num_train_epochs=args.dpo_epochs,
128
+ learning_rate=5e-5,
129
+ beta=0.1,
130
+ max_prompt_length=512,
131
+ max_length=1024,
132
+ logging_steps=25,
133
+ save_strategy="epoch",
134
+ gradient_checkpointing=True,
135
+ bf16=True,
136
+ report_to="none",
137
+ )
138
+ trainer = DPOTrainer(model=model, args=cfg, tokenizer=tokenizer, train_dataset=dpo_rows)
139
+ trainer.train()
140
+ trainer.save_model(ADAPTER_DIR)
141
+ print(f"[v15][DPO] adapter saved -> {ADAPTER_DIR}")
142
+
143
+
144
+ def merge_and_push(args):
145
+ from peft import PeftModel
146
+ from transformers import AutoModelForCausalLM, AutoTokenizer
147
+ base = AutoModelForCausalLM.from_pretrained(BASE_MODEL, torch_dtype="bfloat16",
148
+ device_map="auto")
149
+ tok = AutoTokenizer.from_pretrained(BASE_MODEL)
150
+ model = PeftModel.from_pretrained(base, ADAPTER_DIR)
151
+ merged = model.merge_and_unload()
152
+ os.makedirs(MERGED_DIR, exist_ok=True)
153
+ merged.save_pretrained(MERGED_DIR)
154
+ tok.save_pretrained(MERGED_DIR)
155
+ print(f"[v15][MERGE] merged model -> {MERGED_DIR}")
156
+ if args.push:
157
+ merged.push_to_hub(HF_REPO, revision="v15")
158
+ tok.push_to_hub(HF_REPO, revision="v15")
159
+ print(f"[v15][PUSH] pushed merged model to {HF_REPO}@v15")
160
+
161
+
162
+ def main():
163
+ ap = argparse.ArgumentParser(description="NeuralAI v15 SFT+DPO trainer")
164
+ ap.add_argument("--stage", choices=["sft", "dpo", "all"], default="all")
165
+ ap.add_argument("--batch", type=int, default=2)
166
+ ap.add_argument("--grad-accum", type=int, default=8)
167
+ ap.add_argument("--sft-epochs", type=int, default=3)
168
+ ap.add_argument("--dpo-epochs", type=int, default=2)
169
+ ap.add_argument("--load-in-4bit", default="true")
170
+ ap.add_argument("--load-in-8bit", default="false")
171
+ ap.add_argument("--merge", action="store_true")
172
+ ap.add_argument("--push", action="store_true")
173
+ args = ap.parse_args()
174
+
175
+ load_in_4bit = args.load_in_4bit.lower() == "true"
176
+ load_in_8bit = args.load_in_8bit.lower() == "true"
177
+
178
+ from transformers import AutoModelForCausalLM, AutoTokenizer
179
+ qcfg = load_quantization(load_in_4bit, load_in_8bit)
180
+ print(f"[v15] loading base {BASE_MODEL} (4bit={load_in_4bit}, 8bit={load_in_8bit})")
181
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
182
+ model = AutoModelForCausalLM.from_pretrained(
183
+ BASE_MODEL, quantization_config=qcfg, device_map="auto", torch_dtype="bfloat16",
184
+ )
185
+ model.config.use_cache = False
186
+
187
+ if args.stage in ("sft", "all"):
188
+ run_sft(model, tokenizer, args)
189
+ if args.stage in ("dpo", "all"):
190
+ # reload adapter from SFT if we just ran SFT
191
+ run_dpo(model, tokenizer, args)
192
+
193
+ if args.merge or args.push:
194
+ merge_and_push(args)
195
+
196
+ print("[v15] done.")
197
+
198
+
199
+ if __name__ == "__main__":
200
+ main()