Secured / training /train_lora.py
gowtham0992's picture
Ship MiniCPM LoRA v3
cc862d5
Raw
History Blame Contribute Delete
6.41 kB
from __future__ import annotations
import argparse
import json
from pathlib import Path
import torch
from peft import LoraConfig, TaskType, get_peft_model
from torch.utils.data import Dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments
class ChatJsonlDataset(Dataset):
def __init__(self, path: Path, tokenizer, max_length: int) -> None:
self.rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self) -> int:
return len(self.rows)
def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
row = self.rows[index]
messages = row["messages"]
prompt_messages = messages[:-1]
assistant_message = messages[-1]
prompt = self.tokenizer.apply_chat_template(
prompt_messages,
tokenize=False,
add_generation_prompt=True,
)
full_text = prompt + assistant_message["content"] + self.tokenizer.eos_token
full = self.tokenizer(
full_text,
truncation=True,
max_length=self.max_length,
padding=False,
return_tensors="pt",
)
prompt_tokens = self.tokenizer(
prompt,
truncation=True,
max_length=self.max_length,
padding=False,
return_tensors="pt",
)
input_ids = full["input_ids"][0]
attention_mask = full["attention_mask"][0]
labels = input_ids.clone()
labels[: prompt_tokens["input_ids"].shape[-1]] = -100
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels,
}
class DataCollator:
def __init__(self, tokenizer) -> None:
self.tokenizer = tokenizer
def __call__(self, features: list[dict[str, torch.Tensor]]) -> dict[str, torch.Tensor]:
max_len = max(feature["input_ids"].shape[0] for feature in features)
batch = {"input_ids": [], "attention_mask": [], "labels": []}
for feature in features:
pad_len = max_len - feature["input_ids"].shape[0]
batch["input_ids"].append(
torch.nn.functional.pad(feature["input_ids"], (0, pad_len), value=self.tokenizer.pad_token_id)
)
batch["attention_mask"].append(torch.nn.functional.pad(feature["attention_mask"], (0, pad_len), value=0))
batch["labels"].append(torch.nn.functional.pad(feature["labels"], (0, pad_len), value=-100))
return {key: torch.stack(value) for key, value in batch.items()}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="LoRA fine-tune MiniCPM for Jawbreaker JSON scam analysis.")
parser.add_argument("--model-id", default="openbmb/MiniCPM4.1-8B")
parser.add_argument("--train-file", type=Path, default=Path("training/data/train.jsonl"))
parser.add_argument("--dev-file", type=Path, default=Path("training/data/dev.jsonl"))
parser.add_argument("--output-dir", type=Path, default=Path("training/output/jawbreaker-minicpm-lora"))
parser.add_argument("--max-length", type=int, default=768)
parser.add_argument("--epochs", type=float, default=1.0)
parser.add_argument("--batch-size", type=int, default=1)
parser.add_argument("--grad-accum", type=int, default=16)
parser.add_argument("--learning-rate", type=float, default=2e-4)
parser.add_argument("--warmup-ratio", type=float, default=0.0)
parser.add_argument("--weight-decay", type=float, default=0.0)
parser.add_argument("--lr-scheduler-type", default="linear")
parser.add_argument("--lora-r", type=int, default=16)
parser.add_argument("--lora-alpha", type=int, default=32)
parser.add_argument("--lora-dropout", type=float, default=0.05)
parser.add_argument("--trust-remote-code", action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--push-to-hub", action="store_true")
parser.add_argument("--hub-model-id", default=None)
return parser.parse_args()
def main() -> None:
args = parse_args()
tokenizer = AutoTokenizer.from_pretrained(args.model_id, trust_remote_code=args.trust_remote_code)
if tokenizer.pad_token_id is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
args.model_id,
dtype="auto",
device_map="auto",
trust_remote_code=args.trust_remote_code,
)
model.config.use_cache = False
lora_config = LoraConfig(
r=args.lora_r,
lora_alpha=args.lora_alpha,
lora_dropout=args.lora_dropout,
bias="none",
task_type=TaskType.CAUSAL_LM,
target_modules=[
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"gate_proj",
"up_proj",
"down_proj",
],
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
train_dataset = ChatJsonlDataset(args.train_file, tokenizer, args.max_length)
eval_dataset = ChatJsonlDataset(args.dev_file, tokenizer, args.max_length)
training_args = TrainingArguments(
output_dir=str(args.output_dir),
num_train_epochs=args.epochs,
per_device_train_batch_size=args.batch_size,
per_device_eval_batch_size=args.batch_size,
gradient_accumulation_steps=args.grad_accum,
learning_rate=args.learning_rate,
warmup_ratio=args.warmup_ratio,
weight_decay=args.weight_decay,
lr_scheduler_type=args.lr_scheduler_type,
logging_steps=10,
eval_strategy="steps",
eval_steps=50,
save_steps=100,
save_total_limit=2,
bf16=torch.cuda.is_available(),
report_to="none",
push_to_hub=args.push_to_hub,
hub_model_id=args.hub_model_id,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=DataCollator(tokenizer),
)
trainer.train()
trainer.save_model(str(args.output_dir))
tokenizer.save_pretrained(str(args.output_dir))
if args.push_to_hub:
trainer.push_to_hub()
if __name__ == "__main__":
main()