この記事では、Hugging FaceのSFTTrainerを使ったLlama3のファインチューニング(QLoRA)を紹介します。
SFTTrainerを使うことで、少ないコードでファインチューニングの実装ができます。
SFTTrainerでLlama3のファインチューニング(QLoRA)
SFTTrainerとは
この記事では、HuggingFaceのSFTTrainerを使って、Llama3のモデルにQLoRAファインチューニングをします。
SFTTrainerは、教師ありファインチューニング(Supervised Fine-tuning)で学習するためのライブラリです。
Transformersライブラリに統合されており、少ないコードでファインチューニングの実装ができます。
QLoRA
QLoRAは、LoRAと量子化(Quantization)の2つの要素をもつファインチューニングの手法です。
LoRAは、モデルのパラメータを低ランク行列で近似することで、更新するパラメータ数を大幅に減らし計算量を削減しています。
量子化とは、モデルの精度を下げる代わりに、GPUメモリを大幅に節約する技術になります。
Llama3の使い方・性能・商用利用ついては、別記事で解説しています。
Unslothを使ったファインチューニングは、別記事で解説しています。
PyTorchのTorchtuneを使ったファインチューニングは、別記事で解説しています。
使用するデータセット
モデルの学習にはデータセット「bbz662bbz/databricks-dolly-15k-ja-ojousama」を使用します。
15,000以上の指示と応答で構成された日本語データセットです。
応答が「お嬢様」の口調になっていることが特徴です。
事前準備
必要なスペック・実行環境
Llama3のQLoRAファインチューニングでは、大容量のGPUメモリを必要とします。
この記事では、GPUメモリ80GBを搭載したNVIDIA A100 80GBのインスタンスを使用しています。
実行環境の詳細は以下のとおりです。
- GPU:NVIDIA A100 80GB
- GPUメモリ(VRAM):80GB
- OS:Ubuntu22.04
- Docker
Llama3のモデル利用申請
Llama3のモデルを使うにあたって、利用申請が必要になります。
以下の記事で利用申請の方法を紹介しています。
Dockerで環境構築
Dockerを使用してLlama3の環境構築をしていきます。
Dockerの使い方は以下の記事をご覧ください。
Dockerfileにインストールするパッケージを記述します。
- CUDA:12.1
- Python:3.10
- PyTorch:2.2.2
- transformers:4.41.2
- bitsandbytes
- ninja
- packaging
- accelerate
- datasets
- bitsandbytes
- evaluate
- trl
- peft
- wheel
- flash-attn
- JupyterLab
- huggingface_hub[cli]
- wandb
Ubuntuのコマンドラインから、Dockerfileを作成します。
mkdir llama3_sftqlora
cd llama3_sftqlora
nano Dockerfile
次の記述をコピーしてDockerfileに貼り付けます。
# ベースイメージ(CUDA)の指定
FROM nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04
# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y python3-pip python3-venv git nano
# 作業ディレクトリを設定
WORKDIR /app
# アプリケーションコードをコピー
COPY . /app
# Python仮想環境の作成
RUN python3 -m venv /app/.venv
# 仮想環境をアクティベートするコマンドを.bashrcに追加
RUN echo "source /app/.venv/bin/activate" >> /root/.bashrc
# JupyterLab,HuggingFaceHub,WandBのインストール
RUN /app/.venv/bin/pip install Jupyter \
jupyterlab \
huggingface_hub[cli] \
wandb
# PyTorchのインストール
RUN /app/.venv/bin/pip install torch==2.2.2 torchvision==0.17.2 torchaudio==2.2.2 --index-url https://download.pytorch.org/whl/cu121
# Transformer関連のインストール
RUN /app/.venv/bin/pip install \
transformers==4.41.2 \
ninja \
packaging \
accelerate \
datasets \
bitsandbytes \
evaluate \
trl \
peft \
wheel
# Flash attentionをインストール
RUN /app/.venv/bin/pip install flash-attn --no-build-isolation
# コンテナの起動時にbashを実行
CMD ["/bin/bash"]
docker-compose.ymlファイルを使ってDockerコンテナの設定をします。
docker-compose.ymlファイルを作成します。
nano docker-compose.yml
次の記述をコピーしてdocker-compose.ymlに貼り付けます。
services:
llama3_sftqlora:
build:
context: .
dockerfile: Dockerfile
image: llama3_sftqlora
runtime: nvidia
container_name: llama3_sftqlora
ports:
- "8888:8888"
volumes:
- .:/app/llama3_sftqlora
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
bash -c '/app/.venv/bin/jupyter lab --ip="*" --port=8888 --NotebookApp.token="" --NotebookApp.password="" --no-browser --allow-root'
Dockerfileからビルドしてコンテナを起動します。
docker compose up
Dockerの起動後にブラウザの検索窓に”localhost:8888″を入力すると、Jupyter Labをブラウザで表示できます。
localhost:8888
Llama3ファインチューニングの実装
Dokcerコンテナ上で起動したJupyter Labを使って、Llama3のファインチューニングを実装していきます。
Jupyter Labのコードセルで以下のコマンドを実行します。
必要なライブラリをインポートします。
import torch
from torch import cuda, bfloat16
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
BitsAndBytesConfig,
HfArgumentParser,
TrainingArguments,
pipeline,
logging
)
from datasets import load_dataset
from peft import LoraConfig, PeftModel
from trl import SFTTrainer
from huggingface_hub import login
import wandb
モデルをダウンロードするために、HuggingFaceにログインします。
token = "*************"
login(token)
HuggingFaceでアクセストークンを発行する方法は以下の記事で解説しています。
学習ログの管理をするために、WandBにログインします。(WandBを使用しない場合は省略してください。)
API_KEY = "*************"
wandb.login(key=API_KEY)
WandbでAPIキーを発行する方法を以下の記事で解説しています。
モデルとトークナイザーのダウンロードをして読み込みます。
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16
)
model_id="meta-llama/Meta-Llama-3-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(
model_id,
trust_remote_code=True,
token=token,
quantization_config=bnb_config,
device_map='auto',
torch_dtype=torch.bfloat16,
attn_implementation="flash_attention_2"
)
tokenizer = AutoTokenizer.from_pretrained(
model_id,
padding_side="right",
add_eos_token=True
)
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id
BitsAndBytesConfig
4bit量子化の定義をしています。モデルの精度を下げて、GPUメモリの節約ができます。
meta-llama/Meta-Llama-3-8B-Instruct
llama3 8B Instructモデルを指定しています。
AutoModelForCausalLM.from_pretrained
モデルの読み込む設定をしています。
torch.bfloat16
Bfloat16は、FP32と同じ数値範囲を持ちながら、GPUメモリを節約でき、計算速度も向上します。
attn_implementation
Transformerの処理を効率化して学習を高速するライブラリを指定しています。
AutoTokenizer.from_pretrained
トークナイザーの読み込み設定をしています。
ファインチューニングをする前のモデルでテキスト生成のテストをしてみます。
messages = [
{"role": "system", "content": "あなたは日本語で回答するアシスタントです。"},
{"role": "user", "content": "GDPとは何ですか?"},
]
input_ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="pt"
).to(model.device)
terminators = [
tokenizer.eos_token_id,
tokenizer.convert_tokens_to_ids("<|eot_id|>")
]
outputs = model.generate(
input_ids,
max_new_tokens=256,
eos_token_id=terminators,
do_sample=True,
temperature=0.6,
top_p=0.9,
)
response = outputs[0][input_ids.shape[-1]:]
print(tokenizer.decode(response, skip_special_tokens=True))
GDPとは何ですか?
GDP(Gross Domestic Product)は、国の経済活動の総計を示す指標です。国のGDPは、一定の期間(例えば1年)に生じた全ての商品やサービスの生産額、また輸出入の差額を加算して算出されます。
GDPには、以下の3つの要素が含まれます。
Consumption(消費):国民が購入した商品やサービスの額
Investment(投資):企業や政府が行った投資の額
Government spending(政府支出):政府が行った支出の額
Net exports(輸出入の差額):輸出額と輸入額の差額
GDPは、国の経済の規模や成長率を示す指標として、国際的に比較されることが多いです。また、GDPを使用して、国の経済状況を分析し、政策決定に役立つ情報を得ることができます。
モデルの学習に使用するデータセット「bbz662bbz/databricks-dolly-15k-ja-ojousama」を読み込みます。
dataset = load_dataset("bbz662bbz/databricks-dolly-15k-ja-ojousama", split="train")
データセットから「instruction(指示)」と「output(応答)」を抽出して、チャットテンプレートに変換して、データセットを更新します。
def formatting_func(example):
messages = [
{'role': "system",'content': "あなたは日本語で回答するアシスタントです。"},
{'role': "user",'content': example["instruction"]},
{'role': "assistant",'content': example["output"]}
]
return tokenizer.apply_chat_template(messages, tokenize=False)
def update_dataset(example):
example["text"] = formatting_func(example)
for field in ["index", "category", "instruction", "input", "output"]:
example.pop(field, None)
return example
dataset = dataset.map(update_dataset)
print(dataset[293]["text"])
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
あなたは日本語で回答するアシスタントです。<|eot_id|><|start_header_id|>user<|end_header_id|>
石けんはどうやって作るの?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
ワタクシ思いますの、 石けんは、天然またはオーガニックのオイル、水酸化ナトリウム、蒸留水を混ぜて作られますわー。 石けんを作るときは、オリーブオイルやココナッツオイルのような軽い油と重い油の組み合わせを混ぜ、水酸化ナトリウムと蒸留水と混ぜ合わせる必要がありますわー。 この混合物をよく混ぜた後、石けん型に流し込んで何週間もかけて固めますわー。 鹸化と呼ばれるこのプロセスは、水酸化ナトリウムが混合物から蒸発するのにかかる時間であり、石鹸が石鹸型の中で硬化する原因となりますわー。 石鹸を作る際に、ラベンダーやグレープフルーツのようなエッセンシャルオイルを加えて、石鹸に良い香りをつけるという方法もありますわー。 これは、鹸化の前に追加する必要がありますわー。ξ゚⊿゚)ξ<|eot_id|>
LoRAのパラメータ設定をします。
peft_config = LoraConfig(
r=8,
lora_alpha=16,
lora_dropout=0.05,
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj","gate_proj", "up_proj", "down_proj",],
bias="none",
task_type="CAUSAL_LM",
modules_to_save=["embed_tokens"],
)
target_modules = [“q_proj”,…
LoRAを適用する対象のトランスフォーマの層(Target modules)を指定します。
すべての線形層にLoRAを対象にすることでモデルの適応品質が向上すると言われています。
r=8
rはファインチューニングの過程で学習される低ランク行列のサイズを表します。
rを大きくするとモデルの適応品質が向上する傾向がありますが、必ずしも直線的な関係ではありません。
rの値が大きくなると更新されるパラメータが増えるため、メモリ使用量が増加します。
lora_alpha:16
LoRaスケーリングのAlphaパラメータは、学習した重みをスケーリングします。
多くの文献では、Alphaを調整可能なパラメータとして扱っておらず、Alphaを16に固定してます。
学習パラメータを設定しています。
training_arguments = TrainingArguments(
output_dir="./results",
bf16=True,
per_device_train_batch_size=4,
gradient_accumulation_steps=16,
num_train_epochs=3,
optim="adamw_torch_fused",
learning_rate=2e-4,
lr_scheduler_type="cosine",
weight_decay=0.01,
warmup_steps=100,
logging_steps=10,
group_by_length=True,
report_to="wandb"
)
group_by_length=True
同じ長さのシーケンスをまとめてバッチ化して、メモリを節約しています。
per_device_train_batch_size
一度に処理するバッチのサイズを指定します。GPUメモリが不足する場合は、値を小さくします。
gradient_accumulation_steps
勾配累積。この値を大きくすることで、擬似的にミニバッチのサイズを大きくすることができます。
report_to=”wandb”
WandBにログを出力します。WandBを使用しない場合は、コメントアウトしてください。
SFTTrainerを使って教師ありファインチューニング(Supervised Fine-tuning)を実行します。
new_model="llama-3-8b-ft"
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
peft_config=peft_config,
args=training_arguments,
max_seq_length=1024,
packing=True,
)
wandb.init(project="llama3_sftqlora")
trainer.train()
trainer.model.save_pretrained(new_model)
SFTTrainer(…
事前に設定したモデルやトークナイザー、データセット、LoRAパラメータ、学習パラメータ等をSFTTrainerに渡しています。
wandb.init(…
WandBの記録を開始します。WandBと連携しない場合はコメントアウトしてください。
WandBに保存したファインチューニング実行中のメトリクスを確認します。
Loss(損失)は最初の60ステップで急速に減少し、その後は緩やかに小さくなり収束しています。
GPUメモリは、常に88%(70GB)を使用しています。
ファインチューニング後のモデルでテキスト生成
ファインチューニング後のモデルでテキスト生成をしていきます。
ファインチューニング後のモデルを読み込みます。
torch.cuda.empty_cache()
ft_model = AutoModelForCausalLM.from_pretrained(
new_model,
device_map='auto',
torch_dtype=torch.bfloat16,
attn_implementation="flash_attention_2"
)
tokenizer = AutoTokenizer.from_pretrained(
model_id
)
if tokenizer.pad_token_id is None:
tokenizer.pad_token_id = tokenizer.eos_token_id
torch.cuda.empty_cache()
テキスト生成を実行する前に、ファインチューニングで使用していたGPUメモリをリセットしています。
AutoModelForCausalLM.from_pretrained(new_model,…
ファインチューニング後のモデルを読み込んでいます。
“GDPとは何ですか?”というプロンプトを実行します。
messages = [
{"role": "system", "content": "あなたは日本語で回答するアシスタントです。"},
{"role": "user", "content": "GDPとは何ですか?"},
]
input_ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="pt"
).to(ft_model.device)
terminators = [
tokenizer.eos_token_id,
tokenizer.convert_tokens_to_ids("<|eot_id|>")
]
outputs = ft_model.generate(
input_ids,
max_new_tokens=256,
eos_token_id=terminators,
do_sample=True,
temperature=0.6,
top_p=0.9,
)
response = outputs[0][input_ids.shape[-1]:]
print(tokenizer.decode(response, skip_special_tokens=True))
GDPとは何ですか?
ワタクシ思いますの、 国民総生産(GDP)は、国民の総消費、投資、輸出、輸入を合計した額なのですわー。
GDPは、国民経済の規模を測る最も一般的な指標で、各国の経済の大きさを比較するために使用されますわー。
GDPは、1年間の経済活動を測るものであり、経済の成長を測るために使用されますわー。ξ゚⊿゚)ξ
生成AI・LLMのコストでお困りなら
GPUのスペック不足で生成AIの開発が思うように進まないことはありませんか?
そんなときには、高性能なGPUをリーズナブルな価格で使えるGPUクラウドサービスがおすすめです!
GPUSOROBANは、生成AI・LLM向けの高速GPUを業界最安級の料金で使用することができます。
インターネット環境さえあれば、クラウド環境のGPUサーバーをすぐに利用可能です。
大規模な設備投資の必要がなく、煩雑なサーバー管理からも解放されます。