見出し画像

Google Colab で PEFT による大規模言語モデルのファインチューニングを試す

「Google Colab」で 「PEFT」による大規模言語モデルのファインチューニングを試したので、まとめました。

1. PEFT

「PEFT」(Parameter-Efficient Fine-Tuning)は、モデルの全体のファインチューニングなしに、事前学習済みの言語モデルをさまざまな下流タスクに適応させることができるパッケージです。

大規模言語モデルのファインチューニングは、多くの場合、法外なコストがかかりますが、「PEFT」は少数のパラメータのみをファインチューニングするため、計算コストとストレージ コストが大幅に削減でき、さらには、完全なファインチューニングに匹敵するパフォーマンスを実現します。

現在サポートしている手法は、次の4つです。

LoRA
Prefix Tuning
P-Tuning
Prompt Tuning

2. Colabでの実行

Google Colabでの実行手順は、次のとおりです。

(1) 新規のColabのノートブックを開き、メニュー「編集 → ノートブックの設定」で「GPU」を選択。

(2) パッケージのインストール。

# パッケージのインストール
!pip install -q bitsandbytes datasets accelerate loralib
!pip install -q git+https://github.com/huggingface/transformers.git@main git+https://github.com/huggingface/peft.git

(3) モデルの読み込み。
「OPT-6.7B」を読み込みます。重みは半精度 (float16)で約13GB、8bitで読み込むと約7GBのメモリが必要になります。

# モデルの読み込み
import os
os.environ["CUDA_VISIBLE_DEVICES"]="0"
import torch
import torch.nn as nn
import bitsandbytes as bnb
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
    "facebook/opt-6.7b", 
    load_in_8bit=True, 
    device_map='auto',
)

tokenizer = AutoTokenizer.from_pretrained("facebook/opt-6.7b")

(4) 8bitモデルに後処理を適用。
学習を有効にするためには、8bitモデルに後処理を適用する必要があります。すべてのレイヤーをフリーズし、レイヤーノルムをfloat32にキャストして安定させます。 同じ理由で、float32の最後のレイヤーの出力もキャストします。

for param in model.parameters():
  param.requires_grad = False  # モデルをフリーズ
  if param.ndim == 1:
    # 安定のためにレイヤーノルムをfp32にキャスト
    param.data = param.data.to(torch.float32)

model.gradient_checkpointing_enable()
model.enable_input_require_grads()

class CastOutputToFloat(nn.Sequential):
  def forward(self, x): return super().forward(x).to(torch.float32)
model.lm_head = CastOutputToFloat(model.lm_head)

(5) PeftModelを読み込む。
configでLoRAを使用することを指定します。

def print_trainable_parameters(model):
    """
    モデル内の学習可能なパラメータ数を出力
    """
    trainable_params = 0
    all_param = 0
    for _, param in model.named_parameters():
        all_param += param.numel()
        if param.requires_grad:
            trainable_params += param.numel()
    print(
        f"trainable params: {trainable_params} || all params: {all_param} || trainable%: {100 * trainable_params / all_param}"
    )


from peft import LoraConfig, get_peft_model 

config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = get_peft_model(model, config)
print_trainable_parameters(model)
trainable params: 8388608 || all params: 6666862592 || trainable%: 0.12582542214183376

(6) 学習の実行。
学習データは「Abirate/english_quotes」(偉人の名言集)を使用します。
40分ほどかかりました。

import transformers
from datasets import load_dataset
data = load_dataset("Abirate/english_quotes")
data = data.map(lambda samples: tokenizer(samples['quote']), batched=True)

trainer = transformers.Trainer(
    model=model, 
    train_dataset=data['train'],
    args=transformers.TrainingArguments(
        per_device_train_batch_size=4, 
        gradient_accumulation_steps=4,
        warmup_steps=100, 
        max_steps=200, 
        learning_rate=2e-4, 
        fp16=True,
        logging_steps=1, 
        output_dir='outputs'
    ),
    data_collator=transformers.DataCollatorForLanguageModeling(tokenizer, mlm=False)
)
model.config.use_cache = False  # 警告を黙らせます。 推論のために再度有効にしてください。
trainer.train()

(7) 推論の実行。
"Two things are infinite: "に続く言葉を推論します。

batch = tokenizer("Two things are infinite: ", return_tensors='pt')

with torch.cuda.amp.autocast():
  output_tokens = model.generate(**batch, max_new_tokens=50)

print('\n\n', tokenizer.decode(output_tokens[0], skip_special_tokens=True))
Two things are infinite:  the universe and human stupidity, and I'm not sure about the former.  - Albert Einstein
I'm not sure about the latter.  - Albert Einstein

無事、データセットにあるアインシュタインの名言がほぼ復元されました。
(元データは“Two things are infinite: the universe and human stupidity; and I'm not sure about the universe.”)

関連



いいなと思ったら応援しよう!