
Llamma-65bなどの大規模言語モデルをQLoRAでファインチューニング
概要
QLoRAの本家とも言えるライブラリでファインチューニングしてみました
65Bモデルを48GB のVRAMで学習できたとの触れ込みです
multi gpuにも対応しているようです
先日、llmtuneというライブラリを試しましたが、本記事のライブラリが色々な意味で、上位互換な印象です。
インストール
miniconda環境で構築しました。
conda create -n gua
conda activate gua
conda install pip
レポジトリのクローン
git clone https://github.com/artidoro/qlora
pip
cd qlora
pip install -U -r requirements.txt
サンプルコードの実行
たくさんのサンプルコードがありました。
Jupyter-notebookでの 7bの実行
examples/guanaco_7B_demo_colab.ipynb を実行すればOKです。 (colabでも動きそうですね)
実行すると、web uiでchatができました。日本であまり話題にならないだけあって、日本語力の低さが際立ちます。


各種モデルのトレーニング
scriptsフォルダに色々とサンプルコードが入っていました。
oasst1というデータセットを学習するようです(約85k行)。
そのまま実行しても、errorが出ました。
AttributeError: /home/user/miniconda3/envs/gua/lib/python3.11/site-packages/bitsandbytes/libbitsandbytes_cpu.so: undefined symbol: cquantize_blockwise_fp16_nf4
GPUではなくcpuで動かそうとしていることが原因のようです。cuda toolkitをinstallすると解決できました。
conda install -c nvidia cuda-toolkit
例えば、7bをfine tuningする際は以下のコマンドになります
sh scripts/finetune_guanaco_7b.sh
RTX-3090でのgpu使用率は以下の感じです。メモリは余裕で余っています。
演算自体は、どちらか1枚のgpuで行っている印象です。

140W制限※で、学習に27時間ほどかかるようでした。
(※手持ちのマシンをフル出力で動かすと、放熱が不十分でプロセスがzombie化します)
65bのファインチューニング
実行コード
sh scripts/finetune_guanaco_65b.sh
学習開始まで、3分ほどかかった気がします。
RTX-3090 x2で動きました。
Falcontuneとかはmulti gpu非対応なので、ありがたい仕様です。

学習完了には230時間ほど、必要なようです。流石の65bです。
H100の購入を検討したくなる計算量です。

オリジナルデータのqlora
自作のdatasetで試してみます。
まずは、適当にデータセットを作ります。
何件かのデータを、databricks-dolly-15k-jaからデータを引っ張ってきました
test.json
[
{
"input": "ヴァージン・オーストラリア航空はいつから運航を開始したのですか?ヴァージン・オーストラリア航空(Virgin Australia Airlines Pty Ltd)はオーストラリアを拠点とするヴァージン・ブランドを冠する最大の船団規模を持つ航空会社です。2000年8月31日に、ヴァージン・ブルー空港として、2機の航空機、1つの空路を運行してサービスを開始しました。2001年9月のアンセット・オーストラリア空港の崩壊後、オーストラリアの国内市場で急速に地位を確立しました。その後はブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長しました。",
"output": "ヴァージン・オーストラリア航空は、2000年8月31日にヴァージン・ブルー航空として、2機の航空機で単一路線の運航を開始しました。"
},
{
"input": "魚の種類はどっち?イコクエイラクブカとロープ",
"output": "イコクエイラクブカ"
},
{
"input": "ラクダはなぜ水なしで長く生きられるのか?",
"output": "ラクダは、長時間にわたってエネルギーと水分で満たされた状態を保つために、腰の脂肪を利用しています。"
},
{
"input": "アリスの両親には3人の娘がいる:エイミー、ジェシー、そして三女の名前は?",
"output": "三女の名前はアリス"
},
{
"input": "小森田友明はいつ生まれたの?小森田は1981年7月10日、熊本県に生まれる。高校卒業後、2000年にJ1リーグのアビスパ福岡に入団。2001年にMFとしてデビューするも、出番は少なく、2001年シーズン終了後にJ2リーグに降格する。2002年、J2の大分トリニータに移籍。守備的MFとしてレギュラーに定着し、2002年に優勝、2003年に昇格を果たす。2005年まで多くの試合に出場した。2005年9月、J2のモンテディオ山形に移籍。2006年、J2のヴィッセル神戸へ移籍。守備的ミッドフィルダーとしてレギュラーになったものの、夏には徐々に出番が少なくなっていった。2007年、地元に本拠地を置く日本フットボールリーグのロッソ熊本(後のロアッソ熊本)へ移籍。レギュラーとして活躍し、2008年にはクラブはJ2に昇格した。その後は出場機会は限られたものの、多くの試合に出場した。2010年、インドネシアに渡り、ペルセラ・ラモンガンに移籍。2010年7月、日本に戻り、J2のギラヴァンツ北九州に入団。2012年に引退するまで、守備的ミッドフィルダーやセンターバックとして多くの試合に出場した。",
"output": "小森田友明は1981年7月10日に生まれました。"
}
]
evalの設定にちょっと躓いたので、evalなしで実行します。
(eval関係のcommandが残ってます)
dataset_path=dataset/test.json
eval_dataset_size=1
python qlora.py \
--model_name_or_path huggyllama/llama-7b \
--output_dir ./output/test \
--logging_steps 10 \
--save_strategy steps \
--data_seed 42 \
--save_steps 10 \
--save_total_limit 40 \
--evaluation_strategy steps \
--eval_dataset_size $eval_dataset_size\
--max_eval_samples 1000 \
--per_device_eval_batch_size 1 \
--max_new_tokens 32 \
--dataloader_num_workers 3 \
--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 \
--bf16 \
--bits 4 \
--warmup_ratio 0.03 \
--lr_scheduler_type constant \
--gradient_checkpointing \
--dataset $dataset_path \
--dataset_format input-output \
--source_max_len 16 \
--target_max_len 512 \
--per_device_train_batch_size 1 \
--gradient_accumulation_steps 16 \
--max_steps 1875 \
--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
max_steps=1875のままで動かしてみます。
今回はデータ数が少ないこともあり、すぐにlossがゼロになったので、途中で止めました。

Loraモデルの動作確認
jupyterで動かします。
まずは、LoRA前の応答を見てみます。
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
#load base model
model_id = "huggyllama/llama-7b"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, quantization_config=bnb_config, device_map={"":0})
model.eval()
text = "#Q: ラクダはなぜ水なしで長く生きられるのか? #A"
device = "cuda:0"
inputs = tokenizer(text, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=256)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
回答はこちら
#Q: ラクダはなぜ水なしで長く生きられるのか?
#A: ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことができる。 そのため、ラクダは、水を含む物質を含むことが
※ベースがllammaで、instructionの学習をしていないこと、さらに日本語をちゃんと勉強していないようなので、おかしな回答になっています
QLoRAの推論
# apply peft model
peft_name = "guanaco/qlora/output/test/checkpoint-50"
model = PeftModel.from_pretrained(
model,
peft_name,
device_map={"":0}
)
model.eval()
text = "#Q: ラクダはなぜ水なしで長く生きられるのか? #A"
device = "cuda:0"
inputs = tokenizer(text, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=256)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
回答はこちら
#Q: ラクダはなぜ水なしで長く生きられるのか?
#A: ラクダは、2016年7月31日に生まれました。
意味不明ですが、学習データセットの「 "output": "小森田友明は1981年7月10日に生まれました。"」に引っ張られたのだと思います。
学習自体は成功しているものと思われます。
まとめ
QLoRAで65bレベルのLLMを、24GB x2 のVRAMなどで学習可能でした
一般家庭用のコンピュータで65bレベルをチューニング・推論できるのは素晴らしいことだと思います
llamaはあまりinstructionされていなかったり、日本語が微妙なので、fine tuningしたいデータの量、質によっては、予めinstruciton済みのモデルをfine tuningした方が良いかもしれません