| 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()) |
|
|
|
|