├── DPO_train.sh ├── README.md ├── merge.py └── training_DPO.py /DPO_train.sh: -------------------------------------------------------------------------------- 1 | # 내가 훈련할 모델의 레포입니다. 2 | BASIS=MODEL_REPO 3 | 4 | # 내가 저장한 데이터의 repo입니다 5 | DATA=Your_DPO_Dataset 6 | 7 | # DPO가 진행된 후 저장되는 폴더의 명칭입니다. 8 | output_directory=save_folder_name 9 | 10 | # 훈련이 끝난 어댑터와, 기존의 SFT를 merge한 이후 저장되는 폴더 이름입니다. 11 | final_dir=final_merged_model 12 | 13 | 14 | 15 | # accelerate을 통해 분산학습을 시도하셔도 됩니다. 16 | python training_DPO.py \ 17 | --model_name_or_path $BASIS \ 18 | --output_dir $output_directory \ 19 | --datapath $DATA \ 20 | --num_epochs 1 \ 21 | --lora_r 32 \ 22 | --lora_alpha 16 \ 23 | --lora_dropout 0.01 \ 24 | --per_device_train_batch_size 64 \ 25 | --lr_scheduler_type 'cosine' \ 26 | --gradient_accumulation_steps 4 \ 27 | --eval_step 100 \ 28 | --max_prompt_length 8192 \ 29 | --max_length 8192 \ 30 | 31 | 32 | 33 | # 훈련이 끝나고, 훈련된 최종 adapter와 merge를 시도합니다. 34 | python merge.py \ 35 | --base_model_name_or_path $BASIS \ 36 | --peft_model_path $output_directory \ 37 | --output_dir $final_dir 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | # Easy DPO 5 | - DPO 코드를 한국어로 조금 더 편하고 편리하게 정할 수 있는 공유용 코드 레포입니다. 6 | 7 | - 요즘 LLM은 대부분 SFT 다음에 DPO로 allign을 잡아주게 됩니다. 8 | 9 | - trl 라이브러리의 기본 DPO예제를 더 편하게 이해하고 적용할 수 있도록 코드를 구성하였습니다. 10 | 11 | - 오픈소스 커뮤니티와 비기너들에게 유익한 정보를 공유할 수 있는 **Markr.AI팀**이 될 수 있도록 더 노력하겠습니다. 12 | 13 | # DATASET 💾 14 | ## 훈련데이터셋 📚 15 | 16 | - 훈련 Dataset은 기본적으로 두개의 pair가 구성이된 답변이 존재해야, 적절하게 훈련이 가능합니다. 17 | 18 | - 다음 표와 같이 데이터를 준비하시면 됩니다. 19 | 20 | 칼럼명: | question | response_j | response_k 21 | -- | -- | -- | -- 22 | **설명:** | **isntruction** | **선호답변** | **비선호 답변** 23 | 24 | 25 | 26 | # Train 27 | 28 | - 훈련 코드는 매우 간단하게 `sh`파일로 훈련이 가능하게 구성해 두었습니다. 적절한 인자를 넣어서 활용하시길 바랍니다. 29 | 30 | - sh 파일을 구성하시고, sh 파일을 구동하시면, 간편하게 훈련을 시킬 수 있도록 파일을 구성해 두었습니다. 31 | 32 | ```python 33 | sh DPO_train.sh 34 | ``` 35 | 36 | - sh 파일에 적절한 인자를 넣어주는게 중요합니다. 37 | 38 | - 라이브러리 버전은 차후 제가 확인 후 다시 한번 공유 드릴 수 있도록 하겠습니다. 39 | 40 | 41 | 42 | # Acknowledgement 43 | 44 | - 주식회사 마커의 LLM프로젝트를 학술적으로 연구하였으며, 학술적인 목적임을 알리는 바입니다. 45 | -------------------------------------------------------------------------------- /merge.py: -------------------------------------------------------------------------------- 1 | from transformers import AutoModelForCausalLM, AutoTokenizer 2 | from peft import PeftModel 3 | import torch 4 | from huggingface_hub import login 5 | import os 6 | import argparse 7 | 8 | def get_args(): 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument("--base_model_name_or_path", type=str) 11 | parser.add_argument("--token_id", type=str) 12 | parser.add_argument("--peft_model_path", type=str) 13 | parser.add_argument("--output_dir", type=str) 14 | parser.add_argument("--device", type=str, default="auto") 15 | 16 | return parser.parse_args() 17 | 18 | def main(): 19 | args = get_args() 20 | login(token=args.token_id) 21 | 22 | if args.device == 'auto': 23 | device_arg = { 'device_map': 'auto' } 24 | else: 25 | device_arg = { 'device_map': { "": args.device} } 26 | 27 | print(f"Loading base model: {args.base_model_name_or_path}") 28 | base_model = AutoModelForCausalLM.from_pretrained( 29 | args.base_model_name_or_path, 30 | load_in_8bit=True, 31 | return_dict=True, 32 | torch_dtype=torch.float16, 33 | **device_arg 34 | ) 35 | tokenizer = AutoTokenizer.from_pretrained(args.base_model_name_or_path) 36 | 37 | # 만약 토크나이저가 확장되도록 변한다면 바꾸어 주면 됨. 38 | # base_model.resize_token_embeddings(len(tokenizer)) 39 | 40 | 41 | print(f"Loading PEFT: {args.peft_model_path}") 42 | model = PeftModel.from_pretrained(base_model, args.peft_model_path, **device_arg) 43 | print(f"Running merge_and_unload") 44 | model = model.merge_and_unload() 45 | 46 | 47 | model.save_pretrained(f"{args.output_dir}") 48 | tokenizer.save_pretrained(f"{args.output_dir}") 49 | print(f"Model saved to {args.output_dir}") 50 | 51 | if __name__ == "__main__" : 52 | main() -------------------------------------------------------------------------------- /training_DPO.py: -------------------------------------------------------------------------------- 1 | """ 2 | @ Author: DopeorNope Lee(Seungyoo Lee) 3 | @ Description: training LLM using the DPO Method 4 | @ Date: 07.Aug.2023 5 | @ Last Update: 21.Jun.2024 6 | 7 | @ Notice: 8 | Unauthorized copying and sharing of code without the author's permission are prohibited. 9 | 10 | """ 11 | 12 | import os 13 | from dataclasses import dataclass, field 14 | from typing import Dict, Optional 15 | import torch 16 | from datasets import Dataset, load_dataset 17 | from peft import LoraConfig 18 | from transformers import AutoModelForCausalLM, AutoTokenizer, HfArgumentParser, TrainingArguments, BitsAndBytesConfig 19 | from accelerate import dispatch_model, infer_auto_device_map 20 | from accelerate.utils import get_balanced_memory 21 | from trl import DPOTrainer 22 | from huggingface_hub import login 23 | import argparse 24 | 25 | 26 | 27 | 28 | 29 | def get_args(): 30 | 31 | """ 32 | 이 함수는 각 DPO로 훈련시키기 위해 중요한 인수인자를 sh 파일로 부터 받아와서 33 | 34 | """ 35 | 36 | parser = argparse.ArgumentParser() 37 | 38 | parser.add_argument("--num_epochs", type=int, default=1) 39 | parser.add_argument("--beta", type=float, default=0.1) 40 | parser.add_argument("--datapath", type=str, default="") 41 | parser.add_argument("--model_name_or_path", type=str, default="") 42 | parser.add_argument("--learning_rate", type=float, default=1e-5) 43 | parser.add_argument("--lr_scheduler_type", type=str, default="linear") 44 | parser.add_argument("--warmup_steps",type=int,default=100) 45 | parser.add_argument("--token_id", type =str, default='',help='please enter yout huggingface token ID') 46 | parser.add_argument("--weight_decay",type=float,default=0.05) 47 | parser.add_argument("--optimizer_type", type=str, default="paged_adamw_32bit") 48 | parser.add_argument("--per_device_train_batch_size", type=int, default=32) 49 | parser.add_argument("--per_device_eval_batch_size", type=int, default=1) 50 | parser.add_argument("--gradient_accumulation_steps", type=int, default=4) 51 | parser.add_argument("--gradient_checkpointing", type=bool, default=True) 52 | parser.add_argument("--lora_alpha", type=float, default=16) 53 | parser.add_argument("--lora_dropout", type=float, default=0.05) 54 | parser.add_argument("--lora_r", type=int, default=16) 55 | parser.add_argument("--max_prompt_length", type=int, default=4096) 56 | parser.add_argument("--max_length", type=int, default=4096) 57 | parser.add_argument("--max_step", type=int, default=1000) 58 | parser.add_argument("--logging_steps", type=int, default=10) 59 | parser.add_argument("--save_steps", type=int, default=100) 60 | parser.add_argument("--eval_steps", type=int, default=100) 61 | parser.add_argument("--output_dir", type=str, default="./results") 62 | parser.add_argument("--log_freq", type=int, default=1) 63 | parser.add_argument("--sanity_check", type=bool, default=False) 64 | parser.add_argument("--report_to", type=str, default="wandb") 65 | parser.add_argument("--ignore_bias_buffers", type=bool, default=False) 66 | parser.add_argument("--lora_target_modules",type=list, default = ['embed_tokens', 'q_proj', 'k_proj', 'v_proj', 'gate_proj', 'down_proj', 'up_proj', 'lm_head'] ) 67 | 68 | return parser.parse_args() 69 | 70 | 71 | # pair 한 데이터셋을 준비하는 함수입니다. 72 | def paired_data_preparation( 73 | data_dir: str = "", #default 74 | sanity_check: bool = False, 75 | cache_dir: str = None, 76 | split_criteria: str = "train", 77 | num_proc: int=24, 78 | ) -> Dataset: 79 | """ 80 | 이 데이터셋은 이후 딕셔너리 형태로 변환되며 다음과 같은 형태로 prompt, chosen, reject로 담기게 됩니다. 81 | { 82 | 'prompt': List[str], 83 | 'chosen': List[str], 84 | 'rejected': List[str], 85 | } 86 | 87 | Prompt의 구조는 다음과 같이 담기게 됩니다(알파카 프롬프트): 88 | "###질문: " +