Mistral-finetune - Mistralモデルをファインチューニングする
(github.com/mistralai)mistral-finetuneは、Mistralモデルをメモリ効率よく高性能にファインチューニングするための軽量コードベースであり、現在このリポジトリはアーカイブされていて、もはや積極的にはメンテナンスされていない- 学習方式は、ほとんどの重みを固定し、低ランク行列摂動の形をした追加重みの1〜2%だけを学習するLoRAに基づいている
- 最大効率のためにA100またはH100 GPUの利用が推奨されており、コードはマルチGPU単一ノード学習に最適化されているが、7Bのような小さなモデルは単一GPUでも可能
- 対応モデルには 7B、Mixtral 8x7B、Mixtral 8x22B、Mistral-Nemo 12B、Mistral Large v2 123B Instruct が含まれ、Mistral-Nemo と Large v2 にはそれぞれシーケンス長と学習率に関する制約がある
- データは
jsonl形式と厳格なスキーマに従う必要があり、学習前にutils.validate_dataで形式検証と学習時間の推定を行う手順が重要
プロジェクトの状態と目的
mistral-finetuneリポジトリは Archived 状態で、これ以上積極的にはメンテナンスされていない- コミュニティの需要がある、またはファインチューニングのエコシステムに価値を加えられると判断された場合、今後新しいライブラリや大規模アップデートが出る可能性がある
- 目的は、Mistralモデルをファインチューニングするためのシンプルでガイド付きの入口を提供すること
- このコードベースは、特にデータ形式についてかなり明確な方針を持っており、複数のモデルアーキテクチャやハードウェア種別を包括する汎用ツールを目指してはいない
- より汎用的なアプローチとしては torchtune のようなプロジェクトを参照できる
ファインチューニング方式とハードウェア推奨事項
mistral-finetuneは LoRA に基づいている- モデル重みの大半は固定される
- 低ランク行列摂動形式の追加重みの1〜2%だけを学習する
- 最大効率のためにA100またはH100 GPUの利用が推奨される
- コードはマルチGPU単一ノード学習環境に最適化されている
- 7Bのような小さなモデルであれば単一GPUでも十分
最近の互換モデル更新
- 2024年8月13日から Mistral Large v2 が
mistral-finetuneと互換になった- 123B Instruct チェックポイントをダウンロードし、
model_id_or_pathをそのチェックポイントのディレクトリに設定する必要がある - モデルサイズが大きいため、ファインチューニングにははるかに多くのメモリが必要
- 現時点では
seq_lenを 8192以下 に設定する必要がある - 他モデルより低い学習率が推奨され、ほとんどの場合
lr=1e-6がうまく機能するとされている
- 123B Instruct チェックポイントをダウンロードし、
- 2024年7月19日から Mistral Nemo が
mistral-finetuneと互換になった- 12B Base または Instruct モデルをダウンロードし、
model_id_or_pathをチェックポイントディレクトリに設定する必要がある - Tekkenizer をサポートする
mistral-commonバージョンが必要で、pip install --upgrade mistral-commonで>=1.3.1をインストールする必要がある - 語彙サイズが大きいため、CE loss のピークメモリ要件が増加し、現時点ではより多くのメモリが必要
- 現時点では
seq_lenを 16384以下 に設定する必要がある - 7B v3 と同様のハイパーパラメータを使うことが推奨されている
- 12B Base または Instruct モデルをダウンロードし、
インストールとモデルのダウンロード
- 開始手順はリポジトリのクローンと依存関係のインストールで構成される
git clone https://github.com/mistralai/mistral-finetune.gitpip install -r requirements.txt
- 公式Mistralモデルのファインチューニングが推奨されており、README は以下のモデルのダウンロードリンクとチェックサムを提供している
- 7B Base:
0663b293810d7571dad25dae2f2a5806 - 7B Instruct v3:
80b71fcb6416085bcb4efad86dfb4d52 - 8x7B Base: Hugging Face リンク
- 8x7B Instruct:
8e2d3930145dc43d3084396f49d38a3f - 8x22 Instruct:
471a02a6902706a2f1e44a693813855b - 8x22B Base:
a2fa75117174f87d1197e3a4eb50371a - 12B Instruct (Mistral-Nemo):
296fbdf911cb88e6f0be74cd04827fe7 - 12 Base (Mistral-Nemo):
c5d079ac4b55fc1ae35f51f0a3c0eb83 - 123B Instruct (Large v2):
fc602155f9e39151fba81fcaab2fa7c4
- 7B Base:
- 8x7B Base V1 と 8x7B Instruct V1 は、ファインチューニング前にv3 tokenizerを使い、語彙サイズを 32768 に拡張する必要がある
- ダウンロードしたモデルフォルダのパスは、学習 YAML の
model_id_or_pathに絶対パスで指定する必要がある
データ形式の要件
- すべてのデータファイルは jsonl 形式でなければならない
- 事前学習データは
"text"キーにプレーンテキストを格納する - Instruction データは
"messages"キーに会話のリストを格納する- 各項目は
"content"と"role"キーを含む "role"は"user"、"assistant"、"system"のいずれか- 損失は
"role" == "assistant"の場合にのみ計算される - assistant メッセージに
"weight": 0を指定すると、そのメッセージの学習を除外できる
- 各項目は
- 関数呼び出しデータも
"messages"キーに会話のリストを格納する- 各項目は
"role"と"content"または"tool_calls"キーを含む "role"は"user"、"assistant"、"system"、"tool"のいずれか- 損失は
"role" == "assistant"の場合にのみ計算される "tool_calls"の"id"と"tool_call_id"は、ちょうど9文字長のランダム文字列である必要がある- README では、これをデータ準備スクリプトで自動生成する方法を推奨している
- 各項目は
データ検証とサンプルワークフロー
- 学習開始前に utils.validate_data でデータ形式を検証し、学習時間を推定する必要がある
- Instruction の例では Ultachat_200k の一部を使用する
- Pandas で parquet データを読み込む
- 学習95%、評価5%に分割する
jsonlとして保存するexample/7B.yamlのdata.instruct_dataとdata.eval_instruct_dataにパスを指定する
- 検証過程で、一部の会話が
userロールで終わっている問題が見つかることがある- assistant メッセージのみ学習するため、最後の
userメッセージは不要な処理対象になる - utils.reformat_data.py でデータを修正できる
- assistant メッセージのみ学習するため、最後の
- 修正後に再検証すると、データトークン数、学習トークン数、epoch 数、
max_steps、推定時間などの要約が出力される - README の例では
max_steps=500だとデータセットを約5周し、8xH100 クラスターで約30分かかる設定で、max_steps=300を推奨している
関数呼び出しファインチューニングの例
- 関数呼び出しの例では Glaive function calling dataset を使用する
- データは Pandas で読み込み、学習95%・評価5%に分けたうえで
jsonlとして保存する - 元のデータセットは要求される関数呼び出し形式に従っていないため、再フォーマットが必要
"from"を"user"に置き換える必要がある- 不要な
"\n"文字を削除する必要がある
- utils.reformat_data_glaive.py を使うと、大半のサンプルを正しい形式にできる
- あらゆる種類のデータセットで動作する再フォーマットスクリプトを書くことは不可能なので、要求形式に従っていないデータセットには別途再フォーマットスクリプトが必要になる場合がある
utils.validate_data --create_correctedを使うと、残ったエラーを取り除いて.correctedデータセットを生成できる
学習の実行と結果例
- データ検証後に学習を開始できる
- より高速な学習のために
max_stepsを 300 に設定する構成が推奨される run_dirは実験フォルダとして設定する必要があり、任意でwandb.projectを指定して Weights & Biases のロギングを利用できる- 学習の実行には
torchrunを使い、--nproc-per-nodeは利用可能な GPU 数に設定する必要がある - UltraChat の学習は 8xH100 ノードで約 30分 かかり、得られた重みは MT Bench スコア約 6.3 を出せる
- Glaive の学習は 8xH100 ノードで約 1時間 かかり、得られた重みは関数呼び出しで良好に動作すると案内されている
学習設定の主要項目
model_id_or_path: 学習開始に使う事前学習モデル、またはローカルモデルディレクトリのパスrun_dir: チェックポイントとメトリクスを保存するディレクトリseq_len: 学習シーケンス長で、サンプルは効率のためseq_len長に合わせてパッキングされるbatch_size: GPU あたりの学習サンプル数- 全体の有効トークンバッチサイズは
num_gpus x batch_size x seq_len
- 全体の有効トークンバッチサイズは
max_steps: 総学習反復回数- 学習中に見る総トークン数は
max_steps x num_gpus x batch_size x seq_len
- 学習中に見る総トークン数は
optim.lr: オプティマイザの初期学習率optim.weight_decay: 重み減衰で、README では 0.1 を維持 することを推奨しているoptim.pct_start: PyTorchOneCycleLRの warm-up フェーズ比率lora.rank: LoRA アダプタのサイズで、64以下 が推奨されるseed: 初期化とデータシャッフル・サンプリングの再現性のための乱数シードdata.instruct_data: instruction 学習データのパス- 単一の
jsonlファイル、jsonlディレクトリ、または重み付きの複数データソースを指定できる
- 単一の
data.data: 任意の追加事前学習データのパスdata.eval_instruct_data: 任意の評価用 instruction データのパスeval_freq,no_eval,ckpt_freq: 評価・中間評価・チェックポイント保存の周期を制御するsave_adapters: LoRA チェックポイントのみ保存するか、LoRA をベースモデルにマージして完全なモデルとして保存するかを決めるsave_adapters=Falseは、単一プロセスで完全なモデルを保存できる十分な CPU と GPU メモリが必要で、通常は 7B モデルでのみ可能
推論と Weights & Biases
- 学習済みモデルの推論には mistral-inference の利用が推奨される
pip install mistral_inferenceでインストールできるmistral-chat実行時に--lora_pathに保存済みlora.safetensorsのパスを指定すると、LoRA 重みを利用できる- Weights and Biases サポートが含まれており、学習メトリクスと実験を監視できる
pip install wandbでインストールする- API キーは
WANDB_API_KEY環境変数で渡す方式が推奨される - セキュリティ上、API キーは YAML 設定からは読み込まない
- 学習損失、評価損失、学習率などが wandb プロジェクトダッシュボードに記録・可視化される
- 詳しい使い方は Weights and Biases documentation を参照できる
モデル拡張と FAQ
- v3 tokenizer と互換性のある Mistral モデルのみファインチューニングできる
- 互換モデルの語彙サイズは 32768 である必要があり、32000 ではない
- 語彙サイズ 32000 の旧モデルは
utils.extend_model_vocabで 32768 まで拡張できる - MoE モデルのファインチューニングでは、性能のばらつきがより大きく現れる
- 異なる seed で同じ MoE ファインチューニングを複数回実行し、最も性能の良い結果を選ぶ方法が提案されている
- dense モデルでは、このような大きな分散は観測されていない
- 学習に使われたトークン数は、utils.validate_data.py に YAML 学習ファイルを入力して確認できる
- CUDA out-of-memory エラーが発生した場合は、GPU あたりのバッチサイズを減らせる
- バッチサイズは
seq_len x batch_size batch_sizeを 1 に設定し、seq_lenを下げる方法が提案されている
- バッチサイズは
- ライブラリは Apache 2.0 License で提供される
- このライブラリやモデルを、第三者の知的財産権を含む権利を侵害・悪用・違反する形で使用してはならない
1件のコメント
Hacker Newsのコメント
モデルの進歩がこれほど速いのに、ファインチューニングにはまだ価値があるのだろうか? 実際の活用事例が気になる。
たとえば Bloomberg は昨年、金融データで GPT-3.5級の LLM を学習させたが、ほどなくして GPT-4-8k がほぼすべての金融タスクでそれを上回った。
結局、私たちは高品質な評価データと、新しいモデルへ簡単に乗り換えられるアーキテクチャに注力するようになった。
LLM はこうしたアノテーションを見たことがなく、非英語圏向け LLM は企業の最優先事項でもなく、データプライバシーのためオフライン優先のモデルしか使えない。
こういう状況では、汎用言語モデルをファインチューニングするのが非常にうまくはまる。
定型メッセージでファインチューニングしておけば、モデルが自動的にその形式を生成するので、毎回のプロンプトで出力形式を説明するためのトークンをかなり節約できる。
ただし、ファインチューニングはその差をかなり埋めてくれる。
狭い領域ではあるが、プログラミングの大半も同じだ。汎用 LLM を自分のデータ寄りにしたいという目的なら、ファインチューニングはあまり関係ない可能性が高い。
しかし、非常に具体的でありながら曖昧な問題を解こうとしていて、LLM がその一部しか解決してくれないなら、ファインチューニングが最善である可能性が高い。
アプリにツールとやり取りするカスタム関数が多いなら、コンテキストトークンを使うよりファインチューニングを好むかもしれない。
これをやるにはどんな GPU が必要だろう? 3060 Ti のノートPC版、i9、RAM 16GB がある。
AWS や GCP の割り当てはなく、Paperspace の話は聞いたことがあるが、進行中の顧客プロジェクトで Mistral モデルの一部を使う予定なので、Mistral のファインチューニングを早く始めたい。
ゲーミング GPU は 300W の発熱を問題なく処理できるが、ノートPCの GPU がそんなことをしたら溶けてしまうだろうし、おそらく 100W 前後に制限される。
放熱性能は速度に直接比例する。
しかもデスクトップなら、より高速な GPU へのアップグレードや複数 GPU の利用も可能だ。
ただし、特にマルチ GPU 構成はうるさく、部屋ひとつがすぐ暖まるほど熱を出す。
今後数年間で GPU をフルロードで回す時間が 10% を超えないなら、クラウドのほうが安くつく可能性が高い。
モデルごとのハードウェア要件が整理されており、VRAM とシステムメモリを選んで使えるモデルを絞り込める。
うちの会社では、そこの RTX4000 で Mistral と Llama 3 をファインチューニングしてきた。
RAM が 20GB しかないので少し制約はあるが、より大きな入力トークン数には量子化レベルを下げる方法が役立った。
今では時間単位のレンタルも提供している。
今、会社で使っているが、かなり良い結果が出ている。
よくある LLM の活用事例ごとに、どのツールが事実上の標準になるのかは非常に興味深い。
エコシステムがあまりに分裂していて、ほとんどのツールは聞いたことすらない感じだ。
数日前に Microsoft の Olive を見たが、まったく初めて知るツールだった。
すでに多くのオープンソース LLM が「実用になる」レベルに達している今、その周辺開発を容易にすることが重要だ。
とりわけ、ユーザーであり開発者でもある人たちが、非公開データ、つまりモデルの事前学習に含まれていないデータを活用できるようにする必要がある。
リポジトリには大規模モデル向けに最適化されていて A100/H100 が必要だと書かれているが、それでもこれは大型モデルより小型モデルに対してより役立つのではないかと感じる。
「作れば人が来る」は、「ツールを提供すれば人が作る」へと拡張できる。
重み付けの部分が興味深い。
HuggingFace の SFTTrainer は、望めば完成部分だけを学習させられるが、人間にはそれが自然に見えても、LLM は一般に入力全体を予測するよう学習したほうがうまくいく。
この方式なら両方の長所を得られる。
3090 や 4090 を 2 枚使って、より大きな派生モデルを学習できるよう最適化できるだろうか?
いくつかの選択肢を扱う出発点はここにある: https://huggingface.co/blog/trl-peft
自分のWhatsApp チャットモデルはどうやって学習させればよいのだろう?
自分の WhatsApp メッセージでモデルを学習させたいのか? 目的は何か? 自分のように書かせたいのか、それとも RAG ベースの質疑応答をしたいのかによって変わってくる。