| """ |
| scripts/train_sft.py |
| |
| SFT warm-up training script. |
| Run: python scripts/train_sft.py --config configs/sft.yaml |
| |
| HALLUCINATION NOTE: |
| - SFTConfig field 'max_length' (not 'max_seq_length') verified in trl 1.4.0. |
| - SFTConfig field 'dataset_text_field' verified in trl 1.4.0. |
| - gradient_checkpointing_kwargs passed via SFTConfig directly. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import logging |
| import os |
| import sys |
|
|
| import yaml |
| import wandb |
| from datasets import Dataset |
| from peft import LoraConfig, TaskType |
| from transformers import AutoTokenizer |
| from trl import SFTConfig, SFTTrainer |
|
|
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
|
|
| from src.data.dataset import build_sft_dataset |
| from src.model.loader import load_base_model, load_tokenizer, make_lora_config |
|
|
| logging.basicConfig( |
| level=logging.INFO, |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", |
| ) |
| logger = logging.getLogger(__name__) |
|
|
|
|
| def parse_args(): |
| parser = argparse.ArgumentParser(description="SFT warm-up for Phi-3-Mini") |
| parser.add_argument("--config", default="configs/sft.yaml") |
| parser.add_argument("--dry-run", action="store_true", |
| help="Load data and model but don't train (sanity check)") |
| return parser.parse_args() |
|
|
|
|
| def main(): |
| args = parse_args() |
|
|
| with open(args.config) as f: |
| cfg = yaml.safe_load(f) |
|
|
| |
| wandb.init( |
| project=cfg["wandb"]["project"], |
| name=cfg["training"]["run_name"], |
| config=cfg, |
| ) |
|
|
| |
| logger.info("Loading datasets...") |
| train_ds, val_ds = build_sft_dataset( |
| gsm8k_fraction=cfg["data"]["gsm8k_fraction"], |
| aqua_fraction=cfg["data"]["aqua_fraction"], |
| val_size=cfg["data"]["val_size"], |
| ) |
| logger.info("Train: %d examples, Val: %d examples", len(train_ds), len(val_ds)) |
|
|
| if args.dry_run: |
| logger.info("Dry run: printing 2 training examples then exiting.") |
| for i in range(min(2, len(train_ds))): |
| print(f"\n--- Example {i} ---") |
| print(train_ds[i]["text"][:500]) |
| return |
|
|
| |
| logger.info("Loading model: %s", cfg["model"]["base_id"]) |
| model = load_base_model( |
| model_id=cfg["model"]["base_id"], |
| torch_dtype=cfg["model"]["torch_dtype"], |
| attn_implementation=cfg["model"]["attn_implementation"], |
| ) |
| tok = load_tokenizer(cfg["model"]["base_id"]) |
|
|
| lora_cfg = make_lora_config( |
| r=cfg["lora"]["r"], |
| lora_alpha=cfg["lora"]["lora_alpha"], |
| lora_dropout=cfg["lora"]["lora_dropout"], |
| bias=cfg["lora"]["bias"], |
| target_modules=cfg["lora"]["target_modules"], |
| ) |
|
|
| |
| |
| t = cfg["training"] |
| sft_cfg = SFTConfig( |
| output_dir=t["output_dir"], |
| num_train_epochs=t["num_train_epochs"], |
| per_device_train_batch_size=t["per_device_train_batch_size"], |
| gradient_accumulation_steps=t["gradient_accumulation_steps"], |
| learning_rate=t["learning_rate"], |
| lr_scheduler_type=t["lr_scheduler_type"], |
| warmup_ratio=t["warmup_ratio"], |
| bf16=t["bf16"], |
| gradient_checkpointing=t["gradient_checkpointing"], |
| logging_steps=t["logging_steps"], |
| save_strategy=t["save_strategy"], |
| eval_strategy=t["eval_strategy"], |
| eval_steps=t["eval_steps"], |
| save_total_limit=t["save_total_limit"], |
| report_to=t["report_to"], |
| run_name=t["run_name"], |
| |
| dataset_text_field="text", |
| max_length=cfg["data"]["max_seq_length"], |
| packing=False, |
| ) |
|
|
| |
| trainer = SFTTrainer( |
| model=model, |
| args=sft_cfg, |
| train_dataset=train_ds, |
| eval_dataset=val_ds, |
| processing_class=tok, |
| peft_config=lora_cfg, |
| ) |
|
|
| logger.info("Starting SFT training...") |
| trainer.train() |
|
|
| logger.info("Saving final SFT checkpoint to %s", t["output_dir"]) |
| trainer.save_model(t["output_dir"]) |
| tok.save_pretrained(t["output_dir"]) |
|
|
| wandb.finish() |
| logger.info("SFT training complete.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |