このチュートリアルでは、UnslothとGoogle Colabを使って無料でLlama-3をファインチューニングし、独自のチャットボットを作成する方法を段階的に説明します。作成したチャットボットは、Ollamaを使ってローカルコンピュータ上か、Google Colabの無料GPUインスタンス上で実行できます。
- 完全なガイド(画像付き)はこちら: https://docs.unsloth.ai/tutorials/how-to-finetune-llama-3-and-export-to-ollama
- このガイドで使用しているColabノートブック: https://colab.research.google.com/drive/1WZDi7APtQ9VsvOrQSSC5DDtxq159j8iZ?usp=sharing
Unslothは、ファインチューニングしたモデルをOllamaに自動的にエクスポートし、Modelfileも自動生成します。
Unsloth Github: https://github.com/unslothai/unsloth
Unslothとは?
Unslothは、Llama-3、Mistral、Phi-3、GemmaなどのLLMのファインチューニングを2倍高速化し、メモリ使用量を70%削減します。しかも、精度の低下はありません。Unslothを無料で使うには、無料GPUを提供するインターフェースであるGoogle Colabを利用します。
- Ollama:
- Llama-3 Alpaca (このチュートリアルで使用するノートブック):
- CSV/Excel Ollamaガイド:
ノートブックを使用するには、Googleアカウントにログインする必要があります。
Ollamaとは?
Ollamaは、自分のコンピュータから簡単かつ迅速に言語モデルを実行できるようにするツールです。Llama-3のような言語モデルをバックグラウンドで実行するプログラムを起動します。言語モデルに質問したい場合は、Ollamaにリクエストを送信するだけで、すぐに結果が返ってきます。このチュートリアルでは、Ollamaを推論エンジンとして使用します。
Unslothのインストール
Colabノートブックを使用したことがない場合は、ノートブック自体について簡単に説明します。
- 再生ボタン: 各「セル」にあります。このボタンをクリックすると、そのセルのコードが実行されます。セルをスキップしたり、順番を無視して実行したりしないでください。エラーが発生した場合は、実行されなかったセルを再実行するだけです。再生ボタンをクリックする代わりに、CTRL + ENTERを押すこともできます。
- ランタイムボタン: 上部のツールバーにあります。このボタンを使用して「すべて実行」をクリックすると、ノートブック全体を一度に実行できます。
最初のインストールセルは次のようになります。括弧内の再生ボタンをクリックしてください。オープンソースのGithubパッケージを取得し、他のパッケージをインストールします。
これは、ファインチューニングが実際に機能するためには、複数の列を1つの大きなプロンプトに「マージ」する必要があることを意味します。
例えば、有名なタイタニック号のデータセットには多くの列があります。乗客の年齢、客室クラス、料金などに基づいて、その乗客が生存したか死亡したかを予測するのが課題でした。これをそのままChatGPTに渡すことはできません。むしろ、この情報を1つの大きなプロンプトに「マージ」する必要があります。
例えば、その乗客に関するすべての情報を含む「マージ」された単一のプロンプトでChatGPTに質問し、乗客が死亡したか生存したかを推測または予測するよう依頼できます。
他のファインチューニングライブラリでは、ファインチューニングのために、すべての列を1つのプロンプトにマージしてデータセットを手動で準備する必要があります。Unslothでは、これを一度に行うto_sharegpt
という関数を用意しています。
タイタニック号のファインチューニングノートブックにアクセスしたり、CSVまたはExcelファイルをアップロードしたりするには、https://colab.research.google.com/drive/1VYkncZMfGFkeCEgN2IzbZIKEDkyQuJAS?usp=sharing にアクセスしてください。
from unsloth import to_sharegpt
dataset = to_sharegpt(
dataset,
merged_prompt="{instruction}[[\nYour input is:\n{input}]]",
output_column_name="output",
conversation_extension=3, # より長い会話を処理するには、これを増やしてください
)
これは少し複雑ですが、多くのカスタマイズが可能です。いくつかのポイントを説明します:
- すべての列名は中括弧
{}
で囲む必要があります。これらは、実際のCSV/Excelファイルの列名です。 - オプションのテキストコンポーネントは、
[[]]
で囲む必要があります。例えば、「input」列が空の場合、マージ関数はテキストを表示せず、これをスキップします。これは、値が不足しているデータセットに役立ちます。 output_column_name
に出力またはターゲット/予測列を選択します。Alpacaデータセットの場合、これはoutput
になります。
例えば、タイタニック号のデータセットでは、以下のように各列/テキストをオプションにすることで、大きなマージされたプロンプト形式を作成できます。
データが不足しているデータセットが次のように表されるとします:
Embarked Age Fare
S 23
18 7.25
この場合、次のような結果は避けたいでしょう:
The passenger embarked from S. Their age is 23. Their fare is EMPTY.
The passenger embarked from EMPTY. Their age is 18. Their fare is $7.25.
代わりに、[[]]
を使用して列をオプションで囲むことで、この情報を完全に除外できます:
[[The passenger embarked from S.]] [[Their age is 23.]] [[Their fare is EMPTY.]]
[[The passenger embarked from EMPTY.]] [[Their age is 18.]] [[Their fare is $7.25.]]
これにより、以下のようになります:
The passenger embarked from S. Their age is 23.
Their age is 18. Their fare is $7.25.
複数ターンの会話
Alpacaデータセットはシングルターンであることに注意が必要です。一方、ChatGPTの使用はインタラクティブで、複数ターンの会話が可能です。ファインチューニングされた言語モデルが、ChatGPTのように複数ターンの会話をどのように行うかを学習させたいと考えています。
そこで、conversation_extension
パラメータを導入しました。これはシングルターンのデータセットからランダムな行を選択し、それらを1つの会話にマージします。例えば、3に設定すると、3つの行をランダムに選択して1つにマージします。長すぎるとトレーニングが遅くなる可能性がありますが、チャットボットと最終的なファインチューニングの質が大幅に向上する可能性があります。
次に、output_column_name
を予測/出力列に設定します。Alpacaデータセットの場合、これはoutput
列になります。
from unsloth import standardize_sharegpt
dataset = standardize_sharegpt(dataset)
次に、standardize_sharegpt
関数を使用して、データセットをファインチューニングに適した形式に変換します。これは必ず呼び出してください。
カスタマイズ可能なチャットテンプレート
これで、ファインチューニング自体に使用するチャットテンプレートを指定できます。非常に有名なAlpacaの形式を以下に示します:
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
しかし、ChatGPTスタイルのファインチューニングでは1つのプロンプトしか必要ないと前述しました。Unslothを使用してすべてのデータセット列を1つに正常にマージしたので、1つの入力列(指示)と1つの出力でチャットテンプレートを作成できます。
chat_template = """Below are some instructions that describe some tasks. Write responses that appropriately completes each request.
### Instruction:
{INPUT}
### Response:
{OUTPUT}"""
したがって、カスタムの指示を書くなど、自由にテンプレートをカスタマイズできます。指示には{INPUT}
フィールド、モデルの出力フィールドには{OUTPUT}
フィールドを配置する必要があります。
または、Llama-3テンプレート自体を使用することもできます(これは、Llama-3のinstructバージョンを使用する場合にのみ機能します):
chat_template = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{SYSTEM}<|eot_id|><|start_header_id|>user<|end_header_id|>
{INPUT}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
{OUTPUT}<|eot_id|>"""
実際には、ChatGPTのようにシステムプロンプトをカスタマイズするのに役立つ、オプションの{SYSTEM}
フィールドも使用できます。
また、CSVとExcelのアップロードを含むこのColabノートブックhttps://colab.research.google.com/drive/1VYkncZMfGFkeCEgN2IzbZIKEDkyQuJAS?usp=sharing の、乗客が死亡したか生存したかを予測する必要があったタイタニック号の予測タスクでは、次のようになります:
chat_template = """{SYSTEM}
USER: {INPUT}
ASSISTANT: {OUTPUT}"""
from unsloth import apply_chat_template
dataset = apply_chat_template(
dataset,
tokenizer=tokenizer,
chat_template=chat_template,
# default_system_message="You are a helpful assistant", << [オプション]
)
モデルのトレーニング
より長いステップでファインチューニングしたり、大きなバッチサイズでトレーニングしたりしない限り、以下の設定を編集しないことをお勧めします。
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=max_seq_length,
dataset_num_proc=2,
packing=False, # 短いシーケンスの場合、トレーニングを5倍高速化できます
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=5,
max_steps=60,
# num_train_epochs = 1,
learning_rate=2e-4,
fp16=not is_bfloat16_supported(),
bf16=is_bfloat16_supported(),
logging_steps=1,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
output_dir="outputs",
),
)
上記のパラメータを変更することは通常はお勧めしませんが、いくつか詳しく説明します:
- `per_device_train_batch_size = 2`: GPUのメモリをより多く活用したい場合は、バッチサイズを増やします。また、トレーニングをよりスムーズにし、オーバーフィットを防ぐためにも、これを増やします。ただし、パディングの問題によりトレーニングが実際に遅くなる可能性があるため、通常はお勧めしません。代わりに、`gradient_accumulation_steps`を増やすことをお勧めします。
- `gradient_accumulation_steps = 4`: 上記のバッチサイズを増やすことと同じ効果がありますが、メモリ消費量には影響しません。通常、トレーニング損失曲線をよりスムーズにしたい場合は、これを増やすことをお勧めします。
- `max_steps = 60`: トレーニングを高速化するために、ステップを60に設定しました。数時間かかる可能性のある完全なトレーニング実行の場合は、代わりに`max_steps`をコメントアウトし、`num_train_epochs = 1`に置き換えます。1に設定すると、データセット全体を1回完全にパスします。通常は1〜3パスを推奨します。それ以上は推奨しません。それ以上パスするとオーバーフィットの可能性が高くなります。
- `learning_rate = 2e-4`: ファインチューニングプロセスを遅くしたい場合は学習率を下げますが、より高い精度の結果に収束する可能性があります。通常は、試してみる値として、`2e-4`、`1e-4`、`5e-5`、`2e-5`を推奨します。
トレーニング中にいくつかの数値のログが表示されます。これがトレーニング損失です。目標は、これをできるだけ0.5に近づけるようにパラメータを設定することです。ファインチューニングが1、0.8、または0.5に達しない場合は、いくつかの数値を調整する必要があるかもしれません。損失が0になる場合も良い兆候ではありません。
trainer_stats = trainer.train()
推論/モデルの実行
トレーニングプロセスが完了したら、モデルを実行してみましょう。以下のコードで、黄色で下線付きの部分を編集できます。実際、複数ターンのチャットボットを作成したので、過去にいくつかの会話をしていたかのようにモデルを呼び出すことができます。
FastLanguageModel.for_inference(model) # ネイティブで2倍高速な推論を有効にします
messages = [ # 以下を変更してください!
{"role": "user", "content": "フィボナッチ数列を続けてください。入力は1、1、2、3、5、8です。"},
]
input_ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="pt",
).to("cuda")
from transformers import TextStreamer
text_streamer = TextStreamer(tokenizer, skip_prompt=True)
_ = model.generate(
input_ids, streamer=text_streamer, max_new_tokens=128, pad_token_id=tokenizer.eos_token_id
)
Unsloth自体はネイティブで2倍高速な推論を提供するため、`FastLanguageModel.for_inference(model)`を呼び出すことを忘れないでください。モデルにより長い応答を出力させたい場合は、`max_new_tokens = 128`を`256`や`1024`などのより大きな数値に設定します。結果が出るまでに時間がかかることに注意してください。
モデルの保存
これで、ファインチューニングされたモデルを、LoRAアダプターと呼ばれる小さな100MBのファイルとして保存できます。モデルをアップロードしたい場合は、代わりにHugging Faceハブにプッシュすることもできます。<https://huggingface.co/settings/tokens> からHugging Faceトークンを取得し、トークンを追加することを忘れないでください。
model.save_pretrained("lora_model") # ローカルに保存
tokenizer.save_pretrained("lora_model")
# model.push_to_hub("your_name/lora_model", token = "...") # オンラインに保存
# tokenizer.push_to_hub("your_name/lora_model", token = "...") # オンラインに保存
モデルを保存したら、Unslothを使用してモデル自体を再度実行できます。`FastLanguageModel`を再度使用して、推論のために呼び出します。
if False:
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="lora_model", # トレーニングに使用したモデル
max_seq_length=max_seq_length,
dtype=dtype,
load_in_4bit=load_in_4bit,
)
FastLanguageModel.for_inference(model) # ネイティブで2倍高速な推論を有効にします
pass
messages = [ # 以下を変更してください!
{
"role": "user",
"content": "シーケンスに関する特別なことを説明してください。入力は1、1、2、3、5、8です。",
},
]
input_ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="pt",
).to("cuda")
from transformers import TextStreamer
text_streamer = TextStreamer(tokenizer, skip_prompt=True)
_ = model.generate(
input_ids, streamer=text_streamer, max_new_tokens=128, pad_token_id=tokenizer.eos_token_id
)
Ollamaへのエクスポート
最後に、ファインチューニングしたモデルをOllama自体にエクスポートできます。まず、ColabノートブックにOllamaをインストールする必要があります。
!curl -fsSL https://ollama.com/install.sh | sh
次に、作成したファインチューニング済みモデルをllama.cppのGGUFフォーマットにエクスポートします。
# 8ビットQ8_0に保存
if True:
model.save_pretrained_gguf("model", tokenizer)
# https://huggingface.co/settings/tokens にアクセスしてトークンを取得してください。
# また、hfを自分のユーザー名に変更してください。
if False:
model.push_to_hub_gguf("hf/model", tokenizer, token="")
# 16ビットGGUFに保存
if False:
model.save_pretrained_gguf("model", tokenizer, quantization_method="f16")
if False:
model.push_to_hub_gguf("hf/model", tokenizer, quantization_method="f16", token="")
# q4_k_m GGUFに保存
if False:
model.save_pretrained_gguf("model", tokenizer, quantization_method="q4_k_m")
if False:
model.push_to_hub_gguf("hf/model", tokenizer, quantization_method="q4_k_m", token="")
# 複数のGGUFオプションに保存 - 複数必要な場合ははるかに高速です!
if False:
model.push_to_hub_gguf(
"hf/model", # hfを自分のユーザー名に変更してください。
tokenizer,
quantization_method=["q4_k_m", "q8_0", "q5_k_m"],
token="", # https://huggingface.co/settings/tokens からトークンを取得してください
)
すべての行をTrueに変更するのではなく、1行だけTrueに変更してください。そうしないと、非常に長い時間がかかります。通常は最初の行をTrueに設定することをお勧めします。これにより、ファインチューニングされたモデルをQ8_0形式(8ビット量子化)にすばやくエクスポートできます。また、`q4_k_m`など、量子化メソッドのリスト全体にエクスポートすることもできます。
GGUFの詳細については、<https://github.com/ggerganov/llama.cpp> をご覧ください。
また、GGUFへのエクスポート方法について、手動での手順も記載しています: <https://github.com/unslothai/unsloth/wiki#manually-saving-to-gguf>
以下のような長いテキストのリストが表示されます - 5〜10分ほどお待ちください。
Loading checkpoint shards: 100%|██████████| 4/4 [00:00<00:00, 7.86it/s]
Merging 4 shards of model.safetensors: 100%|██████████| 4/4 [00:14<00:00, 3.61s/it]
Saving model to model/ggml-model-q8_0.gguf
そして最後に、以下のように表示されます。
GGUF file size: 3.89 GB
次に、Ollama自体をバックグラウンドで実行する必要があります。Colabは非同期呼び出しを好まないため、`subprocess`を使用しますが、通常は端末/コマンドプロンプトで`ollama serve`を実行するだけです。
import subprocess
subprocess.Popen(["ollama", "serve"])
import time
time.sleep(3) # Ollamaがロードされるまで数秒間待ちます。
Modelfileの自動作成
Unslothが提供する工夫は、Ollamaが必要とするModelfileを自動的に作成することです。これは、設定のリストであり、ファインチューニングプロセスで使用したチャットテンプレートも含まれています。生成されたModelfileは、以下のように出力できます。
print(tokenizer._ollama_modelfile)
次に、Modelfileを使用して、Ollamaと互換性のあるモデルを作成するようにOllamaに指示します。
!ollama create unsloth_model -f ./model/Modelfile
Ollamaでの推論
独自のローカルマシン/無料のColabノートブックのバックグラウンドで実行されているOllamaサーバー自体を呼び出したい場合は、モデルを呼び出して推論を行うことができます。以下のコマンドの内容は編集可能です。
!curl http://localhost:11434/api/chat -d '{ \
"model": "unsloth_model", \
"messages": [ \
{ "role": "user", "content": "フィボナッチ数列を続けてください: 1, 1, 2, 3, 5, 8," } \
] \
}'
インタラクティブなChatGPTスタイル
ファインチューニングされたモデルをChatGPTのように実際に実行するには、もう少し作業が必要です。まず、左側のサイドバーにある端末アイコンをクリックすると、端末がポップアップ表示されます。
次に、Enterキーを2回押して、端末ウィンドウの一部の奇妙な出力を削除する必要がある場合があります。数秒待ってから、「ollama run unsloth_model」と入力し、Enterキーを押します。
そして最後に、実際のChatGPTのように、ファインチューニングされたモデルと対話できます。CTRL + Dを押してシステムを終了し、Enterキーを押してチャットボットと会話します。
まとめ
これで完了です。Unslothを使用して、言語モデルを正常にファインチューニングし、Ollamaにエクスポートしました。しかも、2倍高速で、VRAM使用量は70%も削減されています。そして、これらはすべて、Google Colabノートブックで無料で実現できます。
報酬モデリングの実行方法、継続的な事前トレーニングの実行方法、vLLMまたはGGUFへのエクスポート方法、テキスト補完の実行方法、またはファインチューニングのヒントとコツの詳細については、Githubをご覧ください。
ここまで読んでいただき、ありがとうございました。
コメント