見出し画像

calm2-7b-chatをファインチューニング(QLoRA)してキャラBOTを作る。

 CyberAgentが2023/11/2に発表した日本語LLM「calm2-7b-chat」。
 商用利用可能かつ32kトークンの入力に対応、ということで簡単なFew-Shotで簡単にキャラBOTができるので個人的に楽しんでいました。
 しかし、一つ問題が。

会話の履歴が長くなるとVRAM15GBでは足らなくなる。

 小手先の手段で会話履歴を保持させながら動かしていると、Google ColabのT4ではVRAM不足に陥ります。VRAMをあげようとするとA100 GPUを選択するしかなく、ただ遊んだりするだけでそれはコストが高いよなぁ⋯⋯と思ってQLoRAでファインチューニングすることにしました。

前提

 途中まで読んで何やねん! と思われたくないので先に書いておきますと。

  • モデルの保存にGoogle Driveを使うと無料枠では足りないのでストレージプラン契約が必要

  • Colab上でcalm2-7b-chatをQLoRAチューニングするにはPRO以上の契約必要

 calm2-7b-chatをチューニングする際にconfigファイルを触る必要があるので、Driveに保存する必要があります。またアダプターの容量も数GBになるので無料の15GBでは足りなくなります。
 そしてDriveに保存したモデルを読み込む際、一旦システムメモリに呼び出してからVRAMに移しますが、T4ノーマルでは読み込み中に落ちてしまうので有料プランでしか選択できないハイメモリにする必要があるからです。
※Hugging FaceからColab環境に直接ロードして遊ぶだけならT4ノーマルでいけます。

チューニングの段取り

 Configを見るとcalm2-7b-chatはLlaMaタイプなので、npakaさんの記事の通りに環境を準備し、qlora.pyの中身を変更します(一部今回の内容に必須ではないものがありますが、環境を合わせる為にも全部やっちゃってください)。

https://note.com/npaka/n/na7c63117511

 ライブラリやpyファイルの編集が終わったらgit cloneやHugging Faceからの直接DLでcalm2-7b-chatのモデルファイルをDrive上に保存します。

 次にtokenizer_config.jsonを開いて編集します。

{
  "add_prefix_space": false,
  "added_tokens_decoder": {
    "0": {
      "content": "<|endoftext|>",
      "lstrip": false,
      "normalized": false,
      "rstrip": false,
      "single_word": false,
      "special": true
    },
    "1": {
      "content": "<|padding|>",
      "lstrip": false,
      "normalized": false,
      "rstrip": false,
      "single_word": false,
      "special": true
    }
  },
  "bos_token": "<|endoftext|>",
  "clean_up_tokenization_spaces": true,
  "eos_token": "<|endoftext|>",
  "model_max_length": 1000000000000000019884624838656,
  "pad_token": "<|padding|>",
  "tokenizer_class": "GPTNeoXTokenizerFast",
  "unk_token": "<|endoftext|>"
}

 tokenizer_classを「GPTNeoXTokenizer」から「GPTNeoXTokenizerFast」に変更してください。これはqloraではGPTNeoXTokenizerを扱えず、GPTNeoXTokenizerFastで代用可能な為です。

 次は学習に必要なデータセットをjsonファイルで用意します。

[
  {
    "input": "",
    "instruction": "今日の天気は?",
    "output": "あなたが手に持っているものは何? 携帯で調べればすぐ分かることよ。",
    "category": "open_qa",
    "index": 1
  },
  {
    "input": "",
    "instruction": "疲れたから癒やして。",
    "output": "私に癒やしを求めているなんてどうかしているわね。",
    "category": "brainstorming",
    "index": 2
  },
  {
    "input": "AI(人工知能)とは、Artificial Intelligence(アーティフィシャル・インテリジェンス)の略です。人間の知能を模倣するコンピューターやシステムを指し、人間の知的能力を模倣する技術を意味します。",
    "instruction": "AIとは何?",
    "output": "そんなことも知らないの? Artificial Intelligenceの頭字語で人工知能のことよ。そのぐらい自分で調べたら?",
    "category": "closed_qa",
    "index": 3
  }
]

 以上のようなAlpaca書式でjsonファイルを用意します。

 次に学習のフェーズです。

!python qlora.py \
    --model_name "../calm2-7b-chat" \
    --output_dir "./output/test_peft" \
    --dataset "./output.json" \
    --max_steps 1000 \
    --use_auth \
    --logging_steps 10 \
    --save_strategy steps \
    --data_seed 42 \
    --save_steps 200 \
    --save_total_limit 40 \
    --max_new_tokens 32 \
    --dataloader_num_workers 1 \
    --group_by_length \
    --logging_strategy steps \
    --remove_unused_columns False \
    --do_train \
    --lora_r 64 \
    --lora_alpha 16 \
    --lora_modules all \
    --double_quant \
    --quant_type nf4 \
    --fp16 \
    --bits 4 \
    --warmup_ratio 0.03 \
    --lr_scheduler_type constant \
    --gradient_checkpointing \
    --source_max_len 16 \
    --target_max_len 512 \
    --per_device_train_batch_size 1 \
    --gradient_accumulation_steps 16 \
    --eval_steps 187 \
    --learning_rate 0.0002 \
    --adam_beta2 0.999 \
    --max_grad_norm 0.3 \
    --lora_dropout 0.1 \
    --weight_decay 0.0 \
    --seed 0 \
    --load_in_4bit \
    --use_peft \
    --batch_size 16 \
    --gradient_accumulation_steps 2

 ポイントはmodel_name "../calm2-7b-chat"でDrive上のフォルダを参照している点です。データセットも自分で用意したjsonファイルのパスが合っているか確認してください。
 パラメータは試行錯誤中です。row=80ほどのデータセットだとT4ハイメモリ環境で学習に25分ほどかかりました。

 次にモデルのロードです。

# Drive内のモデル読み込み
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

# トークナイザーとモデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(
    "../calm2-7b-chat"
)
model = AutoModelForCausalLM.from_pretrained(
    "../calm2-7b-chat",
    quantization_config=BitsAndBytesConfig(
        load_in_4bit=True,
        bnb_4bit_use_double_quant=True,
        bnb_4bit_quant_type="nf4",
        bnb_4bit_compute_dtype=torch.bfloat16
    ),
    device_map={"":0}
)

 ここでもDrive上に保存したモデルを呼び出しています。
 次にQLoRAアダプタの読み込みです。

# QLoRAアダプタの読み込み
model = PeftModel.from_pretrained(
    model,
    "./output/test_peft/checkpoint-1000/adapter_model/",
    device_map={"":0}
)
model.eval()

 最後です。モデルに推論させてみます。

# プロンプトの準備
chat_template = ""
prompt = "俺のことどう思う?"
chat_template = chat_template + "\nUSER: " + prompt + "\nASSISTANT: "

# 推論の実行
inputs = tokenizer(chat_template, return_tensors="pt").to("cuda:0")
with torch.no_grad():
    outputs = model.generate(
        **inputs,
        top_k=500,
        top_p=0.95,
        do_sample=True,
        temperature=0.9,
        max_new_tokens=100
        )
temp = tokenizer.decode(outputs[0], skip_special_tokens=True)
lenght = len(temp) - len(chat_template)
chat_template = chat_template + temp[-lenght:]
print(temp[-lenght:])
chat_templateの初期化を別のコードブロックに移動して実行すると会話履歴を残すことができる。

プロンプト:俺のことどう思う?
モデルの回答:それを知る為に、質問は具体的にしなければならないわ。

 ちょっと想定した答えとは違いましたが、狙った性格の通り冷たいお嬢様風の回答が得られました。

 以上です。

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