finllm-foundry / src /training /preference.py
finpy1789's picture
Upload folder using huggingface_hub
b9592cc verified
Raw
History Blame Contribute Delete
4.08 kB
"""Preference alignment: DPO or ORPO on top of (or instead of) the SFT adapter.
python -m src.training.preference --method orpo --config configs/train_general.yaml
- ORPO: no reference model needed; can even replace the SFT stage (cheapest).
- DPO: run *after* SFT; TRL handles the frozen reference model implicitly when
training a PEFT adapter (the base weights are the reference).
Dataset must have prompt/chosen/rejected columns (conversational format works —
TRL extracts prompt/chosen/rejected from message lists).
Note: `pref_dataset` in the config defaults to a *general* preference set as a
placeholder. For real finance alignment, build pairs by sampling two model
answers per finance prompt and labeling the better one (human or strong-model
judge), then point pref_dataset at that repo.
"""
import argparse
import torch
import yaml
from datasets import load_dataset
from peft import LoraConfig, PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from trl import DPOConfig, DPOTrainer, ORPOConfig, ORPOTrainer
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--config", required=True)
ap.add_argument("--method", choices=["dpo", "orpo"], default="orpo")
ap.add_argument("--sft-adapter", default=None,
help="path/repo of the SFT adapter to continue from (default: config output_dir for dpo)")
args = ap.parse_args()
with open(args.config) as f:
cfg = yaml.safe_load(f)
bf16 = torch.cuda.is_bf16_supported()
dtype = torch.bfloat16 if bf16 else torch.float16
model = AutoModelForCausalLM.from_pretrained(
cfg["base_model"],
dtype=dtype,
device_map="auto",
quantization_config=BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=dtype,
),
)
model.config.use_cache = False
# DPO continues from the SFT adapter; ORPO may start fresh from the base.
adapter = args.sft_adapter or (cfg["output_dir"] if args.method == "dpo" else None)
peft_config = None
if adapter:
model = PeftModel.from_pretrained(model, adapter, is_trainable=True)
print(f"[info] continuing from adapter: {adapter}")
else:
peft_config = LoraConfig(
r=cfg["lora_r"],
lora_alpha=cfg["lora_alpha"],
lora_dropout=cfg["lora_dropout"],
target_modules=cfg["target_modules"],
bias="none",
task_type="CAUSAL_LM",
)
tokenizer = AutoTokenizer.from_pretrained(cfg["base_model"])
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
pref_data = load_dataset(cfg["pref_dataset"], split="train")
out_dir = f"{cfg['output_dir']}-{args.method}"
common = dict(
output_dir=out_dir,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
learning_rate=float(cfg["pref_learning_rate"]),
max_steps=cfg["pref_max_steps"],
lr_scheduler_type="cosine",
warmup_ratio=0.05,
bf16=bf16,
fp16=not bf16,
gradient_checkpointing=True,
gradient_checkpointing_kwargs={"use_reentrant": False},
logging_steps=10,
save_steps=100,
report_to="none",
beta=cfg["pref_beta"],
)
if args.method == "dpo":
trainer = DPOTrainer(
model=model,
args=DPOConfig(**common),
train_dataset=pref_data,
processing_class=tokenizer,
peft_config=peft_config,
)
else:
trainer = ORPOTrainer(
model=model,
args=ORPOConfig(**common),
train_dataset=pref_data,
processing_class=tokenizer,
peft_config=peft_config,
)
trainer.train()
trainer.save_model(out_dir)
tokenizer.save_pretrained(out_dir)
print(f"[done] {args.method} adapter saved to {out_dir}")
if __name__ == "__main__":
main()