from __future__ import annotations import argparse from pathlib import Path import torch from datasets import load_dataset from peft import LoraConfig from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments from trl import SFTTrainer def format_messages(example: dict, tokenizer) -> str: messages = example["messages"] if hasattr(tokenizer, "apply_chat_template") and tokenizer.chat_template: return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=False) chunks = [] for msg in messages: role = msg["role"] content = msg["content"] chunks.append(f"<|im_start|>{role}\n{content}<|im_end|>") return "\n".join(chunks) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--model_id", default="openbmb/MiniCPM5-1B") parser.add_argument("--train_file", type=Path, required=True) parser.add_argument("--val_file", type=Path, required=True) parser.add_argument("--output_dir", type=Path, required=True) parser.add_argument("--max_seq_length", type=int, default=2048) parser.add_argument("--epochs", type=float, default=2.0) parser.add_argument("--learning_rate", type=float, default=2e-4) parser.add_argument("--batch_size", type=int, default=2) parser.add_argument("--grad_accum", type=int, default=8) parser.add_argument("--lora_r", type=int, default=16) parser.add_argument("--lora_alpha", type=int, default=32) args = parser.parse_args() dataset = load_dataset( "json", data_files={ "train": str(args.train_file), "validation": str(args.val_file), }, ) tokenizer = AutoTokenizer.from_pretrained(args.model_id, trust_remote_code=True) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, ) model = AutoModelForCausalLM.from_pretrained( args.model_id, quantization_config=quant_config, device_map="auto", trust_remote_code=True, ) model.config.use_cache = False peft_config = LoraConfig( r=args.lora_r, lora_alpha=args.lora_alpha, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", ], ) 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, lr_scheduler_type="cosine", warmup_ratio=0.05, logging_steps=10, eval_strategy="steps", eval_steps=100, save_steps=100, save_total_limit=3, bf16=True, optim="paged_adamw_8bit", report_to="none", gradient_checkpointing=True, ) def formatting_func(example): return format_messages(example, tokenizer) trainer = SFTTrainer( model=model, processing_class=tokenizer, train_dataset=dataset["train"], eval_dataset=dataset["validation"], peft_config=peft_config, formatting_func=formatting_func, max_seq_length=args.max_seq_length, args=training_args, ) trainer.train() trainer.save_model(str(args.output_dir)) tokenizer.save_pretrained(str(args.output_dir)) print(f"Saved LoRA adapter to {args.output_dir}") return 0 if __name__ == "__main__": raise SystemExit(main())