5️⃣Fine-tuning Phi-3-4k-instruct with DPO

Microsoft 새로운 SML Phi-3 발표

Microsoft에서 새로운 SLM(Small Language Model)인 Phi-3를 4K-Instruct와 128k-Instruct의 2개의 버전을 발표했습니다.

Phi-3-Mini-4K-Instruct는 고품질의 추론 밀도 속성에 중점을 두고 합성 데이터와 필터링된 공개 웹사이트 데이터를 모두 포함하는 Phi-3 데이터 세트로 학습된 38억 개의 파라미터를 가진 가벼운 최신 개방형 모델입니다. 이 모델은 Phi-3 제품군에 속하며, 지원 가능한 컨텍스트 길이(토큰 단위)는 4K와 128K의 두 가지 변형 버전인 미니 버전이 있습니다.이 모델은 명령어 추종 및 안전 조치를 위한 감독 미세 조정과 직접 선호도 최적화를 모두 통합하는 사후 훈련 프로세스를 거쳤습니다. 상식, 언어 이해, 수학, 코드, 긴 문맥 및 논리적 추론을 테스트하는 벤치마크에 대해 평가한 결과, Phi-3 Mini-4K-Instruct는 130억 개 미만의 파라미터를 가진 모델 중에서 강력한 최첨단 성능을 보여줍니다.

Phi-3의 활용법은 Phi-2와 동일하기 때문에 아래 글을 참고하세요. 다른점이 있다면 세부 Model Layer의 명칭이 약간 변경되었습니다.

Fine-tuning DPO(Direct Preference Optimization)

RLHF의 개념은 로봇 공학에서 오랫동안 사용되어 왔지만, OpenAI의 논문 '인간 선호도에서 언어 모델 미세 조정'에서 LLM에 대중화되었습니다. 이 논문에서 저자는 인간의 피드백을 근사화하도록 보상 모델을 훈련하는 프레임워크를 제시합니다. 그런 다음 이 보상 모델은 Proximal Policy Optimization (PPO) 알고리즘을 사용하여 미세 조정된 모델의 정책을 최적화하는 데 사용됩니다.

대규모 업데이트는 불안정하거나 최적의 솔루션이 아닌 결과를 초래할 수 있으므로 PPO의 핵심 개념은 정책에 대한 소규모의 점진적 업데이트를 중심으로 이루어집니다. 경험상 이 기술은 안타깝게도 여전히 불안정하고(손실 편차), 재현하기 어렵고(수많은 하이퍼파라미터, 임의의 시드에 민감함), 계산 비용이 많이 듭니다.바로 이 부분에서 Direct Preference Optimization(DPO)가 등장합니다. DPO는 작업을 분류 문제로 처리하여 제어를 단순화합니다. 구체적으로는 학습된 모델(또는 정책 모델)과 참조 모델이라고 하는 복사본이라는 두 가지 모델을 사용합니다. 학습하는 동안 목표는 학습된 모델이 참조 모델보다 선호하는 답변에 대해 더 높은 확률을 출력하도록 하는 것입니다. 반대로, 거부된 답변에 대해서는 더 낮은 확률을 출력하도록 하는 것도 목표입니다. 즉, 잘못된 답변에 대해서는 LLM에 불이익을 주고 좋은 답변에 대해서는 보상을 주는 것입니다.

DPO는 LLM 자체를 보상 모델로 사용하고 이진 교차 엔트로피 목표를 사용함으로써 광범위한 샘플링, 보상 모델 피팅 또는 복잡한 하이퍼파라미터 조정 없이도 모델의 출력을 사람의 선호도에 효율적으로 맞출 수 있습니다. 그 결과 더 안정적이고 효율적이며 계산 부담이 적은 프로세스를 구현할 수 있습니다.

Intel/orca_dpo_pairs 데이터 세트를 사용해 모델을 조정하고 성능을 개선할 것입니다. 이 새로운 모델을 Phi-3-loudai-dpo로 huggingface에 push 해 보겠습니다.

Fine-tune DPO: Phi-3-mini-4K-instruct

Setup Environment

%pip install -q datasets trl peft bitsandbytes sentencepiece wandb
import os
import gc
import torch

import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, BitsAndBytesConfig
from datasets import load_dataset
from peft import LoraConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training
from trl import DPOTrainer
import bitsandbytes as bnb
import wandb

Model, Dataset & Tokenizer

hf_token = "<Your-Huggingface-Token" # hugginface.co
wb_token = "Your-wandb-Token" # wandb.ai/
wandb.login(key=wb_token)

# Load model & new fine model name
model_name = "microsoft/Phi-3-mini-4k-instruct"
new_model = "phi-3-mini-4k-dpo-loudai"
Failed to detect the name of this notebook, you can set it manually with the WANDB_NOTEBOOK_NAME environment variable to enable code saving.
wandb: W&B API key is configured. Use `wandb login --relogin` to force relogin
wandb: WARNING If you're specifying your api key in code, ensure this code is not shared publicly.
wandb: WARNING Consider setting the WANDB_API_KEY environment variable, or running `wandb login` from the command line.
wandb: Appending key for api.wandb.ai to your netrc file: /home/kubwa/.netrc

ChatML은 서로 다른 역할(시스템, 사용자, 어시스턴트)을 정의하고 이를 구분하기 위해 특수 토큰(<|im_start|> 및 <|im_end|>)을 추가합니다. 또한 DPOTrainer는 prompt, chosen, rejected 라는 세 개의 컬럼이 있는 특정 형식도 요구합니다. 데이터 세트에는 system, question, chatgpt, llama2-13b-chat 라는 네 개의 컬럼이 포함되어 있습니다. 'system'과 'question' 열을 프롬프트 열에 연결하기만 하면 됩니다. 또한 chatgpt 열을 'chosen'으로, llama2-13b-chat 열을 'reject'로 매핑하겠습니다. 데이터 세트의 형식을 안정적으로 지정하기 위해 이미 ChatML을 사용하는 토큰화 도구의 apply_chat_template() 함수를 사용하겠습니다.

def chatml_format(example):
    # Format system
    if len(example['system']) > 0:
        message = {"role": "system", "content": example['system']}
        system = tokenizer.apply_chat_template([message], tokenize=False)
    else:
        system = ""

    # Format instruction
    message = {"role": "user", "content": example['question']}
    prompt = tokenizer.apply_chat_template([message], tokenize=False, add_generation_prompt=True)

    # Format chosen answer
    chosen = example['chosen'] + "<|im_end|>\n"

    # Format rejected answer
    rejected = example['rejected'] + "<|im_end|>\n"

    return {
        "prompt": system + prompt,
        "chosen": chosen,
        "rejected": rejected,
    }

# Load dataset
dataset = load_dataset("Intel/orca_dpo_pairs")['train']

# Save columns
original_columns = dataset.column_names

# Tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "left"

# Format dataset
dataset = dataset.map(
    chatml_format,
    remove_columns=original_columns
)

# Print sample
dataset[1]
Downloading readme:   0%|          | 0.00/196 [00:00<?, ?B/s]

Downloading data:   0%|          | 0.00/36.3M [00:00<?, ?B/s]

Generating train split:   0%|          | 0/12859 [00:00<?, ? examples/s]

Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.

Map:   0%|          | 0/12859 [00:00<?, ? examples/s]





{'chosen': 'Midsummer House is a moderately priced Chinese restaurant with a 3/5 customer rating, located near All Bar One.<|im_end|>\n',
 'rejected': ' Sure! Here\'s a sentence that describes all the data you provided:\n\n"Midsummer House is a moderately priced Chinese restaurant with a customer rating of 3 out of 5, located near All Bar One, offering a variety of delicious dishes."<|im_end|>\n',
 'prompt': '<s><|system|>\nYou are an AI assistant. You will be given a task. You must generate a detailed and long answer.<|end|>\n<s><|user|>\nGenerate an approximately fifteen-word sentence that describes all this data: Midsummer House eatType restaurant; Midsummer House food Chinese; Midsummer House priceRange moderate; Midsummer House customer rating 3 out of 5; Midsummer House near All Bar One<|end|>\n<|assistant|>\n'}

LoRA Configuration

모델을 학습시키기 위한 LoRA 구성을 정의합니다. 순위 값을 lora_alpha와 같도록 설정했습니다.

# LoRA configuration
peft_config = LoraConfig(
    r=16,
    lora_alpha=16,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=['k_proj', 'gate_proj', 'v_proj', 'up_proj', 'q_proj', 'o_proj', 'down_proj']
)

Model Fine-tune

이제 DPO로 Fine-tuning 하려는 모델을 로드할 준비가 되었습니다. 이 경우 fine-tune 모델과 reference 모델이라는 두 가지 모델이 필요합니다. 이는 대부분 가독성을 위한 것으로, 참조 모델이 제공되지 않은 경우 DPOTrainer 객체가 자동으로 참조 모델을 생성하기 때문입니다.

Model이 로딩되면서 질문 입력창이 나오면 Yes/No에서 'y'를 순차적으로 입력해주시면 됩니다.

# Model to fine-tune
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    load_in_4bit=True
)
model.config.use_cache = False

# Reference model
ref_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,
    load_in_4bit=True
)
The repository for microsoft/Phi-3-mini-4k-instruct contains custom code which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/microsoft/Phi-3-mini-4k-instruct.
You can avoid this prompt in future by passing the argument `trust_remote_code=True`.

Do you wish to run the custom code? [y/N]  y

A new version of the following files was downloaded from https://huggingface.co/microsoft/Phi-3-mini-4k-instruct:
- configuration_phi3.py
. Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.

The repository for microsoft/Phi-3-mini-4k-instruct contains custom code which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/microsoft/Phi-3-mini-4k-instruct.
You can avoid this prompt in future by passing the argument `trust_remote_code=True`.

Do you wish to run the custom code? [y/N]  y

A new version of the following files was downloaded from https://huggingface.co/microsoft/Phi-3-mini-4k-instruct:
- modeling_phi3.py
. Make sure to double-check they do not contain any added malicious code. To avoid downloading new versions of the code file, you can pin a revision.
`flash-attention` package not found, consider installing for better performance: No module named 'flash_attn'.
Current `flash-attenton` does not support `window_size`. Either upgrade or use `attn_implementation='eager'`.
The `load_in_4bit` and `load_in_8bit` arguments are deprecated and will be removed in the future versions. Please, pass a `BitsAndBytesConfig` object in `quantization_config` argument instead.
`low_cpu_mem_usage` was None, now set to True since model is quantized.

Downloading shards:   0%|          | 0/2 [00:00<?, ?it/s]

Loading checkpoint shards:   0%|          | 0/2 [00:00<?, ?it/s]

The repository for microsoft/Phi-3-mini-4k-instruct contains custom code which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/microsoft/Phi-3-mini-4k-instruct.
You can avoid this prompt in future by passing the argument `trust_remote_code=True`.

Do you wish to run the custom code? [y/N]  y
The repository for microsoft/Phi-3-mini-4k-instruct contains custom code which must be executed to correctly load the model. You can inspect the repository content at https://hf.co/microsoft/Phi-3-mini-4k-instruct.
You can avoid this prompt in future by passing the argument `trust_remote_code=True`.

Do you wish to run the custom code? [y/N]  y

`flash-attention` package not found, consider installing for better performance: No module named 'flash_attn'.
Current `flash-attenton` does not support `window_size`. Either upgrade or use `attn_implementation='eager'`.
The `load_in_4bit` and `load_in_8bit` arguments are deprecated and will be removed in the future versions. Please, pass a `BitsAndBytesConfig` object in `quantization_config` argument instead.
`low_cpu_mem_usage` was None, now set to True since model is quantized.

Loading checkpoint shards:   0%|          | 0/2 [00:00<?, ?it/s]

DPO Training

TrainingArgumentsDPOTrainer에 모든 하이퍼파라미터를 제공하는 것으로 구성됩니다:

# Training arguments
training_args = TrainingArguments(
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    gradient_checkpointing=True,
    learning_rate=5e-5,
    lr_scheduler_type="cosine",
    max_steps=200,
    save_strategy="no",
    logging_steps=1,
    output_dir=new_model,
    optim="paged_adamw_32bit",
    warmup_steps=100,
    bf16=False,
    report_to="wandb",
)

# Create DPO trainer
dpo_trainer = DPOTrainer(
    model,
    ref_model,
    args=training_args,
    train_dataset=dataset,
    tokenizer=tokenizer,
    peft_config=peft_config,
    beta=0.1,
    max_prompt_length=1024,
    max_length=1536,
    force_use_ref_model=True
    
)

# Fine-tune model with DPO
dpo_trainer.train()
Map:   0%|          | 0/12859 [00:00<?, ? examples/s]

Tracking run with wandb version 0.16.6

Weight & Bias에 Project에 저장된 Model Complexity plot 입니다.

train/rewards/chosen과 train/rewards/rejected plot은 학습된 모델과 참조 모델이 출력한 로그 확률의 평균 차이에 해당합니다. 시간이 지남에 따라 학습된 모델이 선호하는 답을 학습함에 따라 이 두 그래프가 갈라지는 것은 당연한 일입니다. train/rewards plot은 이 두 플롯 간의 차이도 보여줍니다. 마지막으로, train/reward/accuracies plot은 선호하는 답변의 선택 빈도를 보여줍니다. 학습된 모델은 빠르게 완벽한 정확도 점수에 도달하는데, 이는 좋은 신호이지만 선호하는 답변과 거부된 답변의 차이가 너무 분명하다는 것을 의미할 수도 있습니다.

Merge & Push Model

이제 학습이 완료되었으므로 어댑터를 원래 모델과 병합할 수 있습니다. 다음으로 병합된 모델과 토큰화를 저장한 후 'Huggingface Hub에 phi-3-mini-4k-dpo-loudai 새로운 모델명으로 푸시합니다.

# Save artifacts
dpo_trainer.model.save_pretrained("final_checkpoint")
tokenizer.save_pretrained("final_checkpoint")

# Flush memory
del dpo_trainer, model, ref_model
gc.collect()
torch.cuda.empty_cache()

# Reload model in FP16 (instead of NF4)
base_model = AutoModelForCausalLM.from_pretrained(
    model_name,
    return_dict=True,
    torch_dtype=torch.float16,
)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Merge base model with the adapter
model = PeftModel.from_pretrained(base_model, "final_checkpoint")
model = model.merge_and_unload()

# Save model and tokenizer
model.save_pretrained(new_model)
tokenizer.save_pretrained(new_model)

# Push them to the HF Hub
model.push_to_hub(new_model, use_temp_dir=False, token=hf_token)
tokenizer.push_to_hub(new_model, use_temp_dir=False, token=hf_token)

Inference

마지막으로 DPO fine-tune 모델에 Prompt를 날려 생성하는 Inference를 해봅니다.

# Format prompt
message = [
    {"role": "system", "content": "너는 유용한 어시스턴트 챗봇입니다."},
    {"role": "user", "content": "LLM이란 무엇인지 알려줄래?"}
]
tokenizer = AutoTokenizer.from_pretrained(new_model)
prompt = tokenizer.apply_chat_template(message, add_generation_prompt=True, tokenize=False)

# Create pipeline
pipeline = transformers.pipeline(
    "text-generation",
    model=new_model,
    tokenizer=tokenizer
)

# Generate text
sequences = pipeline(
    prompt,
    do_sample=True,
    temperature=0.7,
    top_p=0.9,
    num_return_sequences=1,
    max_length=200,
)
print(sequences[0]['generated_text'])
대규모 언어 모델은 방대한 양의 텍스트 데이터로 학습된 일종의 인공 지능(AI) 시스템입니다.
이러한 모델은 인간의 언어를 이해하고 생성하도록 설계되어 텍스트 생성, 언어 번역, 질문 답변 등 다양한
자연어 처리 작업을 수행할 수 있습니다. 
대규모 언어 모델은 일반적으로 순환 신경망(RNN) 또는 트랜스포머와 같은 딥러닝 기술을 사용하여 데이터의
패턴과 관계를 학습함으로써 일관성 있고 맥락에 맞는 응답을 생성할 수 있습니다. 
이러한 모델은 학습되는 매개변수의 수와 데이터의 양 측면에서 그 규모가 복잡한 언어 구조를 이해하고 생성하는
능력에 중요한 역할을 합니다.

Last updated