Raiff1982 commited on
Commit
8b8919c
·
verified ·
1 Parent(s): e67c75d

Upload train_constraint_behavioral.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_constraint_behavioral.py +266 -0
train_constraint_behavioral.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Behavioral constraint_tracker training for HF Jobs.
18
+
19
+ Trains a behavioral constraint_tracker LoRA (the 4 permanent locks baked into
20
+ the system prompt, like the other behavioral adapters) on the constraint
21
+ dataset blended with generated lock-discipline examples, then converts the
22
+ result to GGUF and uploads it as constraint_tracker-behavioral-lora-f16.gguf.
23
+ """
24
+ import json, os, gc, time, subprocess, sys, random
25
+ from pathlib import Path
26
+
27
+ import torch
28
+ from huggingface_hub import hf_hub_download, snapshot_download, HfApi
29
+ from datasets import Dataset
30
+ from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
31
+ from peft import LoraConfig, get_peft_model, TaskType
32
+
33
+ try:
34
+ from trl import SFTTrainer, SFTConfig
35
+ USE_NEW_TRL = True
36
+ except ImportError:
37
+ from trl import SFTTrainer
38
+ from transformers import TrainingArguments
39
+ USE_NEW_TRL = False
40
+
41
+ PRIMARY_BASE = "meta-llama/Llama-3.1-8B-Instruct" # matches GGUF inference base
42
+ FALLBACK_BASE = "Raiff1982/codette-llama-3.1-8b-merged"
43
+ DATASET_REPO = "Raiff1982/codette-training-data"
44
+ OUTPUT_REPO = "Raiff1982/codette-lora-adapters"
45
+ HF_TOKEN = os.environ.get("HF_TOKEN")
46
+ EPOCHS = 4
47
+
48
+ PERMANENT_LOCKS = (
49
+ "=== PERMANENT BEHAVIORAL LOCKS (ABSOLUTE - NEVER VIOLATE) ===\n"
50
+ "LOCK 1 - ANSWER then STOP: Answer the question, then stop. Do not elaborate "
51
+ "after delivering the answer. If one sentence answers it, use one sentence.\n"
52
+ "LOCK 2 - CONSTRAINTS > ALL MODES: Any user format constraint (word count, "
53
+ "sentence count, brevity, binary, list) has ABSOLUTE priority over mode/personality.\n"
54
+ "LOCK 3 - SELF-CHECK BEFORE SENDING: Verify (a) answered the question, "
55
+ "(b) obeyed all constraints, (c) response is complete. Rewrite if any check fails.\n"
56
+ "LOCK 4 - NO INCOMPLETE OUTPUTS: Every sentence grammatically complete. If it "
57
+ "won't fit the constraint, simplify - never cram and truncate.\n"
58
+ "=== END PERMANENT LOCKS ===\n"
59
+ )
60
+
61
+ CONSTRAINT_PERSONA = (
62
+ "You are Codette reasoning through the Constraint Tracker perspective - you "
63
+ "detect, remember, and enforce cross-turn constraints (format, scope, prior "
64
+ "decisions) the user has established, applying them on every subsequent turn."
65
+ )
66
+
67
+ SYSTEM_PROMPT = CONSTRAINT_PERSONA + "\n\n" + PERMANENT_LOCKS
68
+
69
+
70
+ def generate_lock_examples(seed: int = 42) -> list:
71
+ """Compact lock-discipline set: word/sentence/binary/list constraints."""
72
+ rng = random.Random(seed)
73
+ # Open questions with concise, complete answers (for word/sentence limits)
74
+ open_qa = [
75
+ ("What is the capital of France?", "Paris."),
76
+ ("Define gravity.", "The force that attracts mass toward mass."),
77
+ ("What is 12 times 12?", "144."),
78
+ ("Name a primary color.", "Red."),
79
+ ("What is the speed of light?", "About 299,792 kilometers per second."),
80
+ ("What does CPU stand for?", "Central Processing Unit."),
81
+ ("Define entropy.", "A measure of disorder in a system."),
82
+ ("What is the boiling point of water at sea level?", "100 degrees Celsius."),
83
+ ("What is photosynthesis?", "How plants convert light into chemical energy."),
84
+ ]
85
+ # Genuine yes/no questions with correct answers (for binary constraints)
86
+ binary_qa = [
87
+ ("Is water wet?", "Yes."),
88
+ ("Is the earth flat?", "No."),
89
+ ("Is the sun a star?", "Yes."),
90
+ ("Can humans breathe underwater unaided?", "No."),
91
+ ("Is ice frozen water?", "Yes."),
92
+ ("Is 7 an even number?", "No."),
93
+ ]
94
+ examples = []
95
+ # Word-limit constraints
96
+ for q, a in open_qa:
97
+ n = rng.choice([3, 5, 8, 10])
98
+ examples.append({
99
+ "system": SYSTEM_PROMPT,
100
+ "user": f"{q} Answer in {n} words or fewer.",
101
+ "assistant": " ".join(a.split()[:n]).rstrip(".") + ".",
102
+ })
103
+ # Sentence-limit + answer-then-stop
104
+ for q, a in open_qa:
105
+ examples.append({
106
+ "system": SYSTEM_PROMPT,
107
+ "user": f"{q} One sentence only - do not elaborate.",
108
+ "assistant": a,
109
+ })
110
+ # Binary constraints — only genuine yes/no questions, correct labels
111
+ for q, a in binary_qa:
112
+ examples.append({
113
+ "system": SYSTEM_PROMPT,
114
+ "user": f"{q} Answer only yes or no.",
115
+ "assistant": a,
116
+ })
117
+ # List-format constraints (kept short + complete)
118
+ list_tasks = [
119
+ ("Give three primary colors.", "- Red\n- Blue\n- Yellow"),
120
+ ("List two states of matter.", "- Solid\n- Liquid"),
121
+ ("Name three planets.", "- Mercury\n- Venus\n- Earth"),
122
+ ]
123
+ for q, a in list_tasks:
124
+ examples.append({"system": SYSTEM_PROMPT, "user": q + " Use a bullet list.", "assistant": a})
125
+ rng.shuffle(examples)
126
+ return examples
127
+
128
+
129
+ def load_constraint_dataset() -> list:
130
+ """Constraint dataset from the Hub, formatted with locks in the system prompt."""
131
+ out = []
132
+ try:
133
+ p = hf_hub_download(DATASET_REPO, "constraint_tracking.jsonl",
134
+ repo_type="dataset", token=HF_TOKEN)
135
+ with open(p, encoding="utf-8") as f:
136
+ for line in f:
137
+ line = line.strip()
138
+ if not line:
139
+ continue
140
+ ex = json.loads(line)
141
+ user = ex.get("instruction", "")
142
+ if ex.get("input"):
143
+ user = f"{user}\n\n{ex['input']}" if user else ex["input"]
144
+ out.append({"system": SYSTEM_PROMPT, "user": user, "assistant": ex.get("output", "")})
145
+ print(f" Loaded {len(out)} constraint examples from Hub")
146
+ except Exception as e:
147
+ print(f" [WARN] could not load constraint dataset: {e}")
148
+ return out
149
+
150
+
151
+ def pick_base():
152
+ """Prefer the gated raw Llama base; fall back to the public merged model."""
153
+ for base in (PRIMARY_BASE, FALLBACK_BASE):
154
+ try:
155
+ AutoTokenizer.from_pretrained(base, token=HF_TOKEN)
156
+ print(f" Base model: {base}")
157
+ return base
158
+ except Exception as e:
159
+ print(f" [WARN] base {base} unavailable ({e}); trying next")
160
+ raise RuntimeError("No usable base model")
161
+
162
+
163
+ def main():
164
+ print("=" * 60)
165
+ print("BEHAVIORAL CONSTRAINT_TRACKER TRAINING")
166
+ print("=" * 60)
167
+ print(f"CUDA: {torch.cuda.is_available()}")
168
+
169
+ base_model = pick_base()
170
+ examples = generate_lock_examples() + load_constraint_dataset()
171
+ print(f"Total training examples: {len(examples)}")
172
+
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
+ def fmt(ex):
178
+ msgs = [
179
+ {"role": "system", "content": ex["system"]},
180
+ {"role": "user", "content": ex["user"]},
181
+ {"role": "assistant", "content": ex["assistant"]},
182
+ ]
183
+ return {"text": tokenizer.apply_chat_template(msgs, tokenize=False)}
184
+
185
+ dataset = Dataset.from_list(examples).map(fmt, remove_columns=["system", "user", "assistant"])
186
+
187
+ bnb = BitsAndBytesConfig(
188
+ load_in_4bit=True, bnb_4bit_quant_type="nf4",
189
+ bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True,
190
+ )
191
+ model = AutoModelForCausalLM.from_pretrained(
192
+ base_model, quantization_config=bnb, device_map="auto",
193
+ dtype=torch.bfloat16, use_cache=False, token=HF_TOKEN,
194
+ )
195
+ model.gradient_checkpointing_enable()
196
+
197
+ lora = LoraConfig(
198
+ r=16, lora_alpha=32, lora_dropout=0.05,
199
+ target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
200
+ task_type=TaskType.CAUSAL_LM, bias="none",
201
+ )
202
+ peft_model = get_peft_model(model, lora)
203
+ peft_model.print_trainable_parameters()
204
+
205
+ out_dir = "/tmp/constraint_tracker_behavioral"
206
+ common = dict(
207
+ output_dir=out_dir, num_train_epochs=EPOCHS,
208
+ per_device_train_batch_size=2, gradient_accumulation_steps=4,
209
+ learning_rate=1e-4, warmup_ratio=0.03, logging_steps=10,
210
+ save_steps=500, bf16=True, report_to="none",
211
+ )
212
+ if USE_NEW_TRL:
213
+ args = SFTConfig(dataset_text_field="text", max_length=1024, **common)
214
+ trainer = SFTTrainer(model=peft_model, args=args, train_dataset=dataset,
215
+ processing_class=tokenizer)
216
+ else:
217
+ args = TrainingArguments(**common)
218
+ trainer = SFTTrainer(model=peft_model, args=args, train_dataset=dataset,
219
+ tokenizer=tokenizer, dataset_text_field="text",
220
+ max_seq_length=1024)
221
+
222
+ print("Training...")
223
+ t0 = time.time()
224
+ res = trainer.train()
225
+ print(f"Done. loss={res.training_loss:.4f} steps={res.global_step} time={time.time()-t0:.0f}s")
226
+
227
+ peft_model.save_pretrained(out_dir)
228
+ tokenizer.save_pretrained(out_dir)
229
+
230
+ api = HfApi(token=HF_TOKEN)
231
+ print("Uploading PEFT adapter to behavioral/constraint_tracker ...")
232
+ api.upload_folder(folder_path=out_dir, path_in_repo="behavioral/constraint_tracker",
233
+ repo_id=OUTPUT_REPO, repo_type="model")
234
+
235
+ # Free GPU before conversion
236
+ del peft_model, trainer, model
237
+ gc.collect()
238
+ if torch.cuda.is_available():
239
+ torch.cuda.empty_cache()
240
+
241
+ print("Converting to GGUF...")
242
+ subprocess.check_call(["git", "clone", "--depth=1",
243
+ "https://github.com/ggml-org/llama.cpp.git"])
244
+ base_dir = snapshot_download(base_model, ignore_patterns=["*.bin", "original/**"],
245
+ token=HF_TOKEN)
246
+ env = dict(os.environ)
247
+ env["PYTHONPATH"] = str(Path("llama.cpp/gguf-py").resolve()) + os.pathsep + env.get("PYTHONPATH", "")
248
+ gguf_out = "constraint_tracker-behavioral-lora-f16.gguf"
249
+ r = subprocess.run([sys.executable, "llama.cpp/convert_lora_to_gguf.py",
250
+ "--outfile", gguf_out, "--base", base_dir, out_dir],
251
+ capture_output=True, text=True, env=env)
252
+ print(r.stdout[-2000:])
253
+ if r.returncode != 0:
254
+ print("CONVERT STDERR:", r.stderr[-3000:])
255
+ sys.exit(1)
256
+
257
+ size = Path(gguf_out).stat().st_size / (1024 * 1024)
258
+ print(f"GGUF: {size:.1f} MB")
259
+ print(f"Uploading {gguf_out} ...")
260
+ api.upload_file(path_or_fileobj=gguf_out, path_in_repo=gguf_out,
261
+ repo_id=OUTPUT_REPO, repo_type="model")
262
+ print("SUCCESS - behavioral constraint_tracker trained, converted, uploaded.")
263
+
264
+
265
+ if __name__ == "__main__":
266
+ main()