EmiliaLee commited on
Commit
b02cae0
·
verified ·
1 Parent(s): 5a563c9

Upload step2_finetune.py

Browse files
Files changed (1) hide show
  1. step2_finetune.py +134 -0
step2_finetune.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Step 2: Fine-tune dense retriever with PQ training data.
2
+
3
+ Uses sentence-transformers MultipleNegativesRankingLoss with hard negatives.
4
+
5
+ Usage:
6
+ uv run python experiments/exp_021_enrichment_distillation/scripts/step2_finetune.py \
7
+ --domain wands --epochs 1 --batch_size 32
8
+
9
+ # Quick test run
10
+ uv run python experiments/exp_021_enrichment_distillation/scripts/step2_finetune.py \
11
+ --domain wands --epochs 1 --batch_size 32 --max_pairs 5000
12
+ """
13
+
14
+ import argparse
15
+ import json
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ import torch
20
+ from datasets import Dataset as HFDataset
21
+ from sentence_transformers import (
22
+ SentenceTransformer,
23
+ SentenceTransformerTrainer,
24
+ SentenceTransformerTrainingArguments,
25
+ )
26
+ from sentence_transformers.losses import MultipleNegativesRankingLoss
27
+
28
+ DATA_DIR = Path(__file__).parent.parent / "data"
29
+ MODEL_DIR = Path(__file__).parent.parent / "models"
30
+
31
+
32
+ def load_pairs(path: Path, max_pairs: int | None = None) -> list[dict]:
33
+ pairs = []
34
+ with open(path) as f:
35
+ for line in f:
36
+ if line.strip():
37
+ d = json.loads(line)
38
+ if d["negatives"]:
39
+ pairs.append(d)
40
+ if max_pairs and len(pairs) >= max_pairs:
41
+ break
42
+ return pairs
43
+
44
+
45
+ def main() -> None:
46
+ parser = argparse.ArgumentParser()
47
+ parser.add_argument("--domain", required=True)
48
+ parser.add_argument("--model_name", default="BAAI/bge-m3")
49
+ parser.add_argument("--epochs", type=int, default=1)
50
+ parser.add_argument("--batch_size", type=int, default=32)
51
+ parser.add_argument("--lr", type=float, default=2e-5)
52
+ parser.add_argument("--warmup_ratio", type=float, default=0.1)
53
+ parser.add_argument("--max_pairs", type=int, default=None,
54
+ help="Limit training pairs (for quick test)")
55
+ parser.add_argument("--seed", type=int, default=42)
56
+ args = parser.parse_args()
57
+
58
+ device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
59
+ print(f"Device: {device}")
60
+
61
+ # Load training data
62
+ train_path = DATA_DIR / f"{args.domain}_pq_train.jsonl"
63
+ print(f"Loading training data from {train_path}...")
64
+ pairs = load_pairs(train_path, args.max_pairs)
65
+ print(f" {len(pairs)} training pairs")
66
+
67
+ # Split: 95% train, 5% eval
68
+ split_idx = int(len(pairs) * 0.95)
69
+ train_pairs = pairs[:split_idx]
70
+ eval_pairs = pairs[split_idx:]
71
+ print(f" Train: {len(train_pairs)}, Eval: {len(eval_pairs)}")
72
+
73
+ train_dataset = HFDataset.from_dict({
74
+ "anchor": [p["query"] for p in train_pairs],
75
+ "positive": [p["positive"] for p in train_pairs],
76
+ "negative": [p["negatives"][0] if p["negatives"] else "" for p in train_pairs],
77
+ })
78
+ eval_dataset = HFDataset.from_dict({
79
+ "anchor": [p["query"] for p in eval_pairs],
80
+ "positive": [p["positive"] for p in eval_pairs],
81
+ "negative": [p["negatives"][0] if p["negatives"] else "" for p in eval_pairs],
82
+ })
83
+
84
+ # Load model
85
+ print(f"Loading {args.model_name}...")
86
+ model = SentenceTransformer(args.model_name, device=device)
87
+
88
+ # Loss
89
+ loss = MultipleNegativesRankingLoss(model)
90
+
91
+ # Output dir
92
+ output_dir = MODEL_DIR / f"{args.domain}_pq_finetuned"
93
+ output_dir.mkdir(parents=True, exist_ok=True)
94
+
95
+ # Training args
96
+ training_args = SentenceTransformerTrainingArguments(
97
+ output_dir=str(output_dir),
98
+ num_train_epochs=args.epochs,
99
+ per_device_train_batch_size=args.batch_size,
100
+ per_device_eval_batch_size=args.batch_size,
101
+ learning_rate=args.lr,
102
+ warmup_steps=args.warmup_ratio,
103
+ fp16=False,
104
+ bf16=(device == "cuda"),
105
+ eval_strategy="steps",
106
+ eval_steps=500,
107
+ save_strategy="epoch",
108
+ save_total_limit=2,
109
+ logging_steps=100,
110
+ seed=args.seed,
111
+ dataloader_num_workers=4 if device == "cuda" else 0,
112
+ report_to="none",
113
+ )
114
+
115
+ # Train
116
+ trainer = SentenceTransformerTrainer(
117
+ model=model,
118
+ args=training_args,
119
+ train_dataset=train_dataset,
120
+ eval_dataset=eval_dataset,
121
+ loss=loss,
122
+ )
123
+
124
+ print(f"Training... (epochs={args.epochs}, batch_size={args.batch_size}, lr={args.lr})")
125
+ trainer.train()
126
+
127
+ # Save final model
128
+ final_path = MODEL_DIR / f"{args.domain}_pq_finetuned_final"
129
+ model.save(str(final_path))
130
+ print(f"\nModel saved → {final_path}")
131
+
132
+
133
+ if __name__ == "__main__":
134
+ main()