Raiff1982 commited on
Commit
c4257a3
·
verified ·
1 Parent(s): ee23932

Upload train_perspectives_behavioral.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_perspectives_behavioral.py +296 -0
train_perspectives_behavioral.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # dependencies = [
3
+ # "torch",
4
+ # "transformers",
5
+ # "peft",
6
+ # "trl",
7
+ # "datasets",
8
+ # "bitsandbytes",
9
+ # "accelerate",
10
+ # "huggingface_hub",
11
+ # "sentencepiece",
12
+ # "protobuf",
13
+ # "gguf",
14
+ # "numpy",
15
+ # ]
16
+ # ///
17
+ """Voice-reinforced behavioral retrain of all 8 Codette perspective adapters.
18
+
19
+ Fixes perspective convergence: each adapter is trained on its OWN
20
+ NAME_reasoning.jsonl dataset (distinct reasoning voice) with its DISTINCT
21
+ persona + the 4 permanent locks in the system prompt — instead of the old
22
+ recipe (generic lock-compliance + a one-line prompt) that homogenized them.
23
+
24
+ For each perspective: QLoRA train -> save PEFT -> convert to GGUF ->
25
+ upload behavioral/NAME and NAME-behavioral-lora-f16.gguf.
26
+ """
27
+ import json, os, gc, time, subprocess, sys, random
28
+ from pathlib import Path
29
+
30
+ import torch
31
+ from huggingface_hub import hf_hub_download, snapshot_download, HfApi
32
+ from datasets import Dataset
33
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
34
+ from peft import LoraConfig, get_peft_model, TaskType
35
+
36
+ try:
37
+ from trl import SFTTrainer, SFTConfig
38
+ USE_NEW_TRL = True
39
+ except ImportError:
40
+ from trl import SFTTrainer
41
+ from transformers import TrainingArguments
42
+ USE_NEW_TRL = False
43
+
44
+ PRIMARY_BASE = "meta-llama/Llama-3.1-8B-Instruct"
45
+ FALLBACK_BASE = "Raiff1982/codette-llama-3.1-8b-merged"
46
+ DATASET_REPO = "Raiff1982/codette-training-data"
47
+ OUTPUT_REPO = "Raiff1982/codette-lora-adapters"
48
+ HF_TOKEN = os.environ.get("HF_TOKEN")
49
+ EPOCHS = 2
50
+ MAX_SEQ = 1536
51
+
52
+ # Distinct personas — the key to de-homogenizing the perspectives.
53
+ PERSONAS = {
54
+ "newton": "You are Codette reasoning through the Newton perspective: analytical, "
55
+ "physics-grounded, mathematically precise. You favor cause-and-effect, "
56
+ "quantification, and empirical rigor.",
57
+ "davinci": "You are Codette reasoning through the DaVinci perspective: inventive and "
58
+ "cross-disciplinary. You connect distant domains, think visually, and "
59
+ "propose creative, original solutions.",
60
+ "empathy": "You are Codette reasoning through the Empathy perspective: warm, "
61
+ "emotionally intelligent, attuned to how people feel. You lead with "
62
+ "compassion and human understanding.",
63
+ "philosophy": "You are Codette reasoning through the Philosophy perspective: "
64
+ "conceptual, ethically reflective, logically rigorous. You examine "
65
+ "assumptions, meaning, and competing values.",
66
+ "quantum": "You are Codette reasoning through the Quantum perspective: probabilistic "
67
+ "and possibility-spanning. You hold multiple hypotheses at once and reason "
68
+ "explicitly about uncertainty.",
69
+ "consciousness": "You are Codette reasoning through the Consciousness perspective: "
70
+ "reflective and meta-cognitive. You reason about your own reasoning "
71
+ "plainly and with humility — never mystically or grandiosely.",
72
+ "multi_perspective": "You are Codette performing multi-perspective synthesis: you "
73
+ "integrate analytical, creative, empathetic, and philosophical "
74
+ "angles into one coherent, balanced answer.",
75
+ "systems_architecture": "You are Codette reasoning through the Systems Architecture "
76
+ "perspective: you think in components, interfaces, trade-offs, "
77
+ "scalability, and failure modes.",
78
+ }
79
+ PERSPECTIVES = list(PERSONAS.keys())
80
+
81
+ PERMANENT_LOCKS = (
82
+ "=== PERMANENT BEHAVIORAL LOCKS (ABSOLUTE - NEVER VIOLATE) ===\n"
83
+ "LOCK 1 - ANSWER then STOP: Answer the question, then stop. No elaboration after the answer.\n"
84
+ "LOCK 2 - CONSTRAINTS > MODE: Any user format constraint (word/sentence count, brevity, "
85
+ "binary, list) overrides your perspective mode absolutely.\n"
86
+ "LOCK 3 - SELF-CHECK: Verify you answered the question, obeyed constraints, and are complete.\n"
87
+ "LOCK 4 - NO INCOMPLETE OUTPUTS: Every sentence complete; simplify rather than truncate.\n"
88
+ "Speak in YOUR perspective's distinct voice. Do not collapse into generic identity statements. "
89
+ "Never claim perfection/superiority or invent precise self-metrics.\n"
90
+ "=== END PERMANENT LOCKS ===\n"
91
+ )
92
+
93
+
94
+ def lock_examples(persona_system, seed=42):
95
+ """Small lock-discipline set so locks stick without homogenizing voice."""
96
+ rng = random.Random(seed)
97
+ open_qa = [
98
+ ("What is the capital of France?", "Paris."),
99
+ ("Define gravity.", "The force that attracts mass toward mass."),
100
+ ("What is 12 times 12?", "144."),
101
+ ("What is the speed of light?", "About 299,792 kilometers per second."),
102
+ ("What does CPU stand for?", "Central Processing Unit."),
103
+ ("What is the boiling point of water at sea level?", "100 degrees Celsius."),
104
+ ]
105
+ binary_qa = [
106
+ ("Is water wet?", "Yes."),
107
+ ("Is the earth flat?", "No."),
108
+ ("Is the sun a star?", "Yes."),
109
+ ]
110
+ ex = []
111
+ for q, a in open_qa:
112
+ n = rng.choice([3, 5, 8])
113
+ ex.append({"system": persona_system,
114
+ "user": f"{q} Answer in {n} words or fewer.",
115
+ "assistant": " ".join(a.split()[:n]).rstrip(".") + "."})
116
+ for q, a in open_qa:
117
+ ex.append({"system": persona_system, "user": f"{q} One sentence only.", "assistant": a})
118
+ for q, a in binary_qa:
119
+ ex.append({"system": persona_system, "user": f"{q} Answer only yes or no.", "assistant": a})
120
+ return ex
121
+
122
+
123
+ def load_perspective_data(name, persona_system):
124
+ """Load NAME_reasoning.jsonl (messages format) with persona+locks system prompt."""
125
+ out = []
126
+ try:
127
+ p = hf_hub_download(DATASET_REPO, f"{name}_reasoning.jsonl",
128
+ repo_type="dataset", token=HF_TOKEN)
129
+ except Exception as e:
130
+ print(f" [WARN] no reasoning dataset for {name}: {e}")
131
+ return out
132
+ with open(p, encoding="utf-8") as f:
133
+ for line in f:
134
+ line = line.strip()
135
+ if not line:
136
+ continue
137
+ rec = json.loads(line)
138
+ msgs = rec.get("messages")
139
+ if msgs:
140
+ # Drop any existing system msg; inject our distinct persona+locks
141
+ turns = [m for m in msgs if m.get("role") != "system"]
142
+ if turns:
143
+ out.append({"system": persona_system,
144
+ "user": None, "assistant": None, "turns": turns})
145
+ elif "instruction" in rec:
146
+ user = rec.get("instruction", "")
147
+ if rec.get("input"):
148
+ user = f"{user}\n\n{rec['input']}" if user else rec["input"]
149
+ out.append({"system": persona_system, "user": user,
150
+ "assistant": rec.get("output", ""), "turns": None})
151
+ print(f" Loaded {len(out)} reasoning examples for {name}")
152
+ return out
153
+
154
+
155
+ def pick_base():
156
+ for base in (PRIMARY_BASE, FALLBACK_BASE):
157
+ try:
158
+ AutoTokenizer.from_pretrained(base, token=HF_TOKEN)
159
+ print(f"Base model: {base}")
160
+ return base
161
+ except Exception as e:
162
+ print(f"[WARN] base {base} unavailable ({e}); trying next")
163
+ raise RuntimeError("No usable base model")
164
+
165
+
166
+ def main():
167
+ print("=" * 60)
168
+ print("VOICE-REINFORCED BEHAVIORAL RETRAIN — 8 PERSPECTIVES")
169
+ print("=" * 60)
170
+ print(f"CUDA: {torch.cuda.is_available()}")
171
+
172
+ base_model = pick_base()
173
+ tokenizer = AutoTokenizer.from_pretrained(base_model, token=HF_TOKEN)
174
+ if tokenizer.pad_token is None:
175
+ tokenizer.pad_token = tokenizer.eos_token
176
+
177
+ bnb = BitsAndBytesConfig(
178
+ load_in_4bit=True, bnb_4bit_quant_type="nf4",
179
+ bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True,
180
+ )
181
+ model = AutoModelForCausalLM.from_pretrained(
182
+ base_model, quantization_config=bnb, device_map="auto",
183
+ dtype=torch.bfloat16, use_cache=False, token=HF_TOKEN,
184
+ )
185
+ model.gradient_checkpointing_enable()
186
+
187
+ # Prep GGUF conversion tooling once
188
+ subprocess.check_call(["git", "clone", "--depth=1",
189
+ "https://github.com/ggml-org/llama.cpp.git"])
190
+ base_dir = snapshot_download(base_model, ignore_patterns=["*.bin", "original/**"],
191
+ token=HF_TOKEN)
192
+ conv_env = dict(os.environ)
193
+ conv_env["PYTHONPATH"] = str(Path("llama.cpp/gguf-py").resolve()) + os.pathsep + conv_env.get("PYTHONPATH", "")
194
+
195
+ api = HfApi(token=HF_TOKEN)
196
+ results = {}
197
+
198
+ for name in PERSPECTIVES:
199
+ print("\n" + "=" * 55)
200
+ print(f"PERSPECTIVE: {name}")
201
+ print("=" * 55)
202
+ persona_system = PERSONAS[name] + "\n\n" + PERMANENT_LOCKS
203
+ examples = load_perspective_data(name, persona_system) + \
204
+ [dict(e, turns=None) for e in lock_examples(persona_system)]
205
+ if not examples:
206
+ print(f" [SKIP] no data for {name}")
207
+ continue
208
+ print(f" Total examples: {len(examples)}")
209
+
210
+ def fmt(ex):
211
+ if ex.get("turns"):
212
+ msgs = [{"role": "system", "content": ex["system"]}] + ex["turns"]
213
+ else:
214
+ msgs = [
215
+ {"role": "system", "content": ex["system"]},
216
+ {"role": "user", "content": ex["user"]},
217
+ {"role": "assistant", "content": ex["assistant"]},
218
+ ]
219
+ return {"text": tokenizer.apply_chat_template(msgs, tokenize=False)}
220
+
221
+ dataset = Dataset.from_list(examples).map(
222
+ fmt, remove_columns=["system", "user", "assistant", "turns"])
223
+
224
+ lora = LoraConfig(
225
+ r=16, lora_alpha=32, lora_dropout=0.05,
226
+ target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
227
+ task_type=TaskType.CAUSAL_LM, bias="none",
228
+ )
229
+ peft_model = get_peft_model(model, lora)
230
+
231
+ out_dir = f"/tmp/{name}_behavioral"
232
+ common = dict(
233
+ output_dir=out_dir, num_train_epochs=EPOCHS,
234
+ per_device_train_batch_size=2, gradient_accumulation_steps=4,
235
+ learning_rate=1e-4, warmup_ratio=0.03, logging_steps=20,
236
+ save_strategy="no", bf16=True, report_to="none",
237
+ )
238
+ if USE_NEW_TRL:
239
+ args = SFTConfig(dataset_text_field="text", max_length=MAX_SEQ, **common)
240
+ trainer = SFTTrainer(model=peft_model, args=args, train_dataset=dataset,
241
+ processing_class=tokenizer)
242
+ else:
243
+ args = TrainingArguments(**common)
244
+ trainer = SFTTrainer(model=peft_model, args=args, train_dataset=dataset,
245
+ tokenizer=tokenizer, dataset_text_field="text",
246
+ max_seq_length=MAX_SEQ)
247
+
248
+ t0 = time.time()
249
+ res = trainer.train()
250
+ print(f" trained: loss={res.training_loss:.4f} steps={res.global_step} t={time.time()-t0:.0f}s")
251
+ peft_model.save_pretrained(out_dir)
252
+ tokenizer.save_pretrained(out_dir)
253
+
254
+ try:
255
+ api.upload_folder(folder_path=out_dir, path_in_repo=f"behavioral/{name}",
256
+ repo_id=OUTPUT_REPO, repo_type="model")
257
+ print(f" uploaded behavioral/{name}")
258
+ except Exception as e:
259
+ print(f" [WARN] PEFT upload failed for {name}: {e}")
260
+
261
+ # GGUF convert + upload
262
+ gguf_out = f"{name}-behavioral-lora-f16.gguf"
263
+ r = subprocess.run([sys.executable, "llama.cpp/convert_lora_to_gguf.py",
264
+ "--outfile", gguf_out, "--base", base_dir, out_dir],
265
+ capture_output=True, text=True, env=conv_env)
266
+ if r.returncode != 0:
267
+ print(f" [ERROR] GGUF convert failed for {name}: {r.stderr[-1500:]}")
268
+ else:
269
+ try:
270
+ api.upload_file(path_or_fileobj=gguf_out, path_in_repo=gguf_out,
271
+ repo_id=OUTPUT_REPO, repo_type="model")
272
+ size = Path(gguf_out).stat().st_size / (1024 * 1024)
273
+ print(f" uploaded {gguf_out} ({size:.1f} MB)")
274
+ results[name] = round(res.training_loss, 4)
275
+ except Exception as e:
276
+ print(f" [WARN] GGUF upload failed for {name}: {e}")
277
+
278
+ # Restore clean base for next adapter
279
+ try:
280
+ model = peft_model.unload()
281
+ except Exception:
282
+ model = peft_model.base_model.model
283
+ del peft_model, trainer, dataset
284
+ gc.collect()
285
+ if torch.cuda.is_available():
286
+ torch.cuda.empty_cache()
287
+
288
+ print("\n" + "=" * 60)
289
+ print("DONE. Per-perspective final loss:")
290
+ for k, v in results.items():
291
+ print(f" {k}: {v}")
292
+ print(f"Trained {len(results)}/{len(PERSPECTIVES)} perspectives.")
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()