Nanthasit commited on
Commit
71fedc5
Β·
verified Β·
1 Parent(s): 43ac2a5

Upload training script for 1.5B LoRA fine-tune

Browse files
Files changed (1) hide show
  1. train.py +159 -0
train.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """sakthai_lora_train_1.5b.py
2
+ LoRA fine-tune SakThai on Qwen2.5-1.5B-Instruct with v4 curated dataset.
3
+ Runs on HF Jobs (t4-small / L4).
4
+
5
+ After training the adapter is pushed to:
6
+ https://huggingface.co/Nanthasit/sakthai-context-1.5b-tools
7
+ """
8
+
9
+ import os, sys
10
+
11
+ try:
12
+ from datasets import load_dataset
13
+ from transformers import (
14
+ AutoTokenizer,
15
+ TrainingArguments,
16
+ Trainer,
17
+ DataCollatorForLanguageModeling,
18
+ )
19
+ from transformers import Qwen2ForCausalLM
20
+ from peft import LoraConfig, get_peft_model
21
+ except ImportError as e:
22
+ print(f"❌ Missing dependency: {e}")
23
+ sys.exit(1)
24
+
25
+ # ── Config (optimised for 16GB T4) ────────────────────────────────────────────
26
+ BASE_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
27
+ DATASET = "Nanthasit/sakthai-combined-v4"
28
+ TARGET_REPO = "Nanthasit/sakthai-context-1.5b-tools"
29
+ MERGE_REPO = "Nanthasit/sakthai-context-1.5b-merged"
30
+ OUTPUT_DIR = "/tmp/lora-adapter"
31
+
32
+ MAX_LENGTH = 768 # longer context for tool definitions
33
+ LR = 2e-4
34
+ EPOCHS = 4 # more epochs on cleaner v4
35
+ BATCH_SIZE = 1 # 1.5B is bigger β€” 1 per device
36
+ GRAD_ACCUM = 16 # effective batch = 16
37
+ WARMUP_RATIO = 0.1
38
+ WEIGHT_DECAY = 0.01
39
+
40
+ PUSH_TO_HUB = "--no-push" not in sys.argv
41
+
42
+ import transformers as _tf
43
+ _TF_MAJOR = int(_tf.__version__.split(".")[0])
44
+ print(f"πŸ“Š transformers v{_tf.__version__}")
45
+
46
+ # ── 1. Dataset ───────────────────────────────────────────────────────────────
47
+ print(f"\nπŸ“¦ Loading dataset: {DATASET}")
48
+ ds = load_dataset(DATASET, split="train")
49
+ print(f" {len(ds)} examples loaded")
50
+
51
+ # ── 2. Tokenizer + model ─────────────────────────────────────────────────────
52
+ print(f"\nπŸ“₯ Loading base model: {BASE_MODEL}")
53
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
54
+ if tokenizer.pad_token is None:
55
+ tokenizer.pad_token = tokenizer.eos_token
56
+
57
+ model = Qwen2ForCausalLM.from_pretrained(
58
+ BASE_MODEL,
59
+ torch_dtype="auto",
60
+ device_map="auto",
61
+ )
62
+
63
+ # Enable gradient checkpointing to save memory
64
+ model.gradient_checkpointing_enable()
65
+ model.config.use_cache = False
66
+ print(f" Model loaded ({sum(p.numel() for p in model.parameters()):,} params)")
67
+
68
+ # ── 3. LoRA ──────────────────────────────────────────────────────────────────
69
+ print("\nπŸ”§ Applying LoRA (r=16, alpha=32)")
70
+ lora_config = LoraConfig(
71
+ r=16, lora_alpha=32,
72
+ target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
73
+ lora_dropout=0.1, bias="none", task_type="CAUSAL_LM",
74
+ )
75
+ model = get_peft_model(model, lora_config)
76
+ model.print_trainable_parameters()
77
+
78
+ # ── 4. Format ────────────────────────────────────────────────────────────────
79
+ def format_example(ex):
80
+ msgs = ex.get("messages", [])
81
+ tools = ex.get("tools", [])
82
+ text = tokenizer.apply_chat_template(
83
+ msgs, tools=tools or None,
84
+ tokenize=False, add_generation_prompt=False,
85
+ )
86
+ return {"text": text}
87
+
88
+ print("\nπŸ”„ Formatting...")
89
+ ds = ds.map(format_example)
90
+
91
+ def tok_fn(examples):
92
+ return tokenizer(
93
+ examples["text"], truncation=True,
94
+ max_length=MAX_LENGTH, padding="max_length",
95
+ )
96
+
97
+ ds = ds.map(tok_fn, batched=True, remove_columns=ds.column_names)
98
+ ds = ds.train_test_split(test_size=0.1, seed=42)
99
+ print(f" Train: {len(ds['train'])} | Eval: {len(ds['test'])}")
100
+
101
+ # ── 5. Training ──────────────────────────────────────────────────────────────
102
+ print(f"\nπŸ‹οΈ Training ({EPOCHS} epochs, LR={LR}, batch={BATCH_SIZE}Γ—{GRAD_ACCUM})")
103
+ args = TrainingArguments(
104
+ output_dir=OUTPUT_DIR,
105
+ per_device_train_batch_size=BATCH_SIZE,
106
+ gradient_accumulation_steps=GRAD_ACCUM,
107
+ learning_rate=LR,
108
+ num_train_epochs=EPOCHS,
109
+ warmup_ratio=WARMUP_RATIO,
110
+ weight_decay=WEIGHT_DECAY,
111
+ fp16=True,
112
+ logging_steps=5,
113
+ save_strategy="no",
114
+ report_to="none",
115
+ remove_unused_columns=False,
116
+ ddp_find_unused_parameters=None,
117
+ optim="adamw_torch",
118
+ )
119
+
120
+ kw = dict(model=model, args=args, train_dataset=ds["train"],
121
+ eval_dataset=ds["test"],
122
+ data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False))
123
+ if _TF_MAJOR < 5:
124
+ kw["tokenizer"] = tokenizer
125
+
126
+ trainer = Trainer(**kw)
127
+ trainer.train()
128
+
129
+ # ── 6. Save ──────────────────────────────────────��───────────────────────────
130
+ print(f"\nπŸ’Ύ Saving adapter to {OUTPUT_DIR}")
131
+ model.save_pretrained(OUTPUT_DIR)
132
+ tokenizer.save_pretrained(OUTPUT_DIR)
133
+
134
+ # ── 7. Push ──────────────────────────────────────────────────────────────────
135
+ if PUSH_TO_HUB:
136
+ print(f"\n☁️ Pushing to {TARGET_REPO}...")
137
+ try:
138
+ from huggingface_hub import HfApi
139
+ HfApi().upload_folder(
140
+ repo_id=TARGET_REPO,
141
+ folder_path=OUTPUT_DIR,
142
+ repo_type="model",
143
+ commit_message=f"sakthai-lora-1.5b r=16 alpha=32 epoch={EPOCHS} v4-dataset",
144
+ )
145
+ print(f"βœ… Adapter at https://huggingface.co/{TARGET_REPO}")
146
+ except Exception as e:
147
+ print(f"❌ Push failed: {e}")
148
+ else:
149
+ print(f"\n⏭️ Push skipped. Adapter at {OUTPUT_DIR}")
150
+
151
+ print(f"""
152
+ {'='*50}
153
+ βœ… TRAINING COMPLETE
154
+ Base: {BASE_MODEL}
155
+ Dataset: {DATASET}
156
+ Adapter: {TARGET_REPO}
157
+ Epochs: {EPOCHS}
158
+ {'='*50}
159
+ """)