banner
Nagi-ovo

Nagi-ovo

Breezing
github

微調の道

Screenshot 2024-03-16 at 15.10.16

なぜ微調整が必要か#

LLM を選択して NLP タスクを完了するには、どのように始めればよいのでしょうか?
下の図から、どの操作が現在のタスクに適しているかをよく理解できます:

Screenshot 2024-03-15 at 10.17.08

時間と大量のデータがある場合は、モデルを再訓練することができます。一定量のデータがあれば、事前訓練されたモデルを微調整できます。データが少ない場合は、最良の選択肢は「文脈内学習」であり、RAG のような文脈学習です。

もちろん、ここでは主に微調整の部分を研究します。微調整により、モデルを再訓練することなく、元の性能よりも優れた結果を得ることができます。

どうやって微調整するか#

ご存知のように、GPU(ビデオメモリ)は、私たち一般のプレイヤーが LLM を使用する際のボトルネックです。ほとんどの人は RTX シリーズのようなコンシューマ向け GPU しか購入できないため、この 16GB のビデオメモリを利用して微調整する賢い方法を見つける必要があります。

Screenshot 2024-03-15 at 10.32.09

微調整のボトルネック#

llama 7B のような中程度のパラメータ量のモデルを訓練する際、モデルの元のパラメータを保存するために約 28GB の VRAM が必要になる可能性があります(これは後でどのように推定されるかを説明します)。さらに、訓練プロセス中の勾配を保存するために同量のビデオメモリが必要で、通常はパラメータ量の 2 倍の量をオプティマイザの状態を追跡するために必要です。

計算してみましょう:

28+28+2×2816=9628 + 28 + 2 \times 28 - 16 = 96

この 96GB のビデオメモリを誰が補ってくれるのでしょうか?

問題解決#

半精度#

最初のステップは、モデル自体をロードすることです。7B モデルでは、各パラメータの単位は 32 ビット浮動小数点数です。
1 バイトは 8 ビットなので、32 ビットは 4 バイト(4B)を必要とします。70 億、つまり 7B は、必要なストレージサイズは7,000,000,000×4B=28,000,000,000B28GB7,000,000,000 \times 4B = 28,000,000,000B\approx28GBです。
1GB=210MB=210×210KB=230B=1,073,741,824B1GB = 2^{10}MB = 2^{10} \times 2^{10}KB = 2^{30}B = 1,073,741,824B

ここでは 28-16=12GB を超えているため、モデルのパラメータをより小さな形式にパッケージ化する方法を考える必要があります。非常に自然な考えは、パラメータの単位を変更することです。16 ビットまたは 8 ビットの浮動小数点数に変更できるでしょうか(それぞれ 2B、1B のストレージスペースに対応します)。F16 に変更すれば、この部分のビデオメモリの必要量を半分にできます。トレードオフとして、対応する浮動小数点数の精度や表現範囲が低下し、勾配爆発や勾配消失などの問題が発生する可能性があります。Google はこれに対して bfloat16(brain float、脳浮動小数点)を提案しました。その核心的な目的は、IEEE 規格に比べて(指数部:5 ビットから 8 ビット)、比較的広い数値範囲を保持しつつ(分数部:10 ビットから 7 ビット)、ハードウェア実装を簡素化する浮動小数点フォーマットを提供することです。これにより、過度な精度を犠牲にすることなく、深層学習モデルの訓練と推論プロセスを加速します。

16 ビット浮動小数点数を選択すると、ビデオメモリの必要量が半分になり、1 枚のカードで十分になります:

Screenshot 2024-03-15 at 11.09.21

量子化#

ここで神経ネットワークの訓練プロセスについて簡単に説明します:

入力内容に対してフォワードパス(前向き伝播)を行い、すなわち活性化し、結果を予測目標と比較します。予測と実際の目標の間の差(損失)に基づいて、損失関数の各パラメータに対する勾配(偏導関数)を計算し、BP(逆伝播)に使用します。最適化アルゴリズム(SGD、すなわち確率的勾配降下法など)を選択してパラメータを更新し、複数回の反復後にモデルを得ます。

モデル内の勾配は通常、元のモデル内のパラメータと同じデータ型を持ち、各パラメータには対応する勾配があります。したがって、オプティマイザを考慮しない場合、2 倍のパラメータ量のビデオメモリが必要です。

一般的には量子化(Quantized)手法が採用され、8 ビット浮動小数点数を選択できます。

Pasted image 20240315221418

図はNvidia ブログからの引用です。

量子化プロセスでは、データの表現範囲が圧縮され、データが圧縮されて集中し、各パラメータ間の差が小さくなります。これにより、大量の情報が失われる可能性があります。新しい表現範囲を超える異常値をカットすることで、これらの極端な値によって引き起こされる量子化誤差を減少させることができます。

int8 量子化を選択すると、モデルパラメータと勾配に必要なメモリを 14GB に削減できます:

Screenshot 2024-03-15 at 11.23.52

LoRA#

これまで多くの努力をしてきましたが、オプティマイザが重要な部分です。

業界で人気のある Adam オプティマイザは非常に効果的ですが、かなりのメモリを消費します。その理由は以下の通りです:

Adam オプティマイザは、各イテレーションでパラメータ $\theta$ を更新し、使用する更新式は以下の通りです(数学を深く理解する必要はありません):

  1. 勾配の一次モーメント推定(すなわち勾配の平均)と二次モーメント推定(すなわち勾配の非中心化分散)を計算します。

mt=β1mt1+(1β1)gtm_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t

vt=β2vt1+(1β2)gt2v_t = \beta_2 \cdot v_{t-1} + (1 - \beta_2) \cdot g_t^2

ここで、gtg_tは時間ステップttの勾配で、β1\beta_1β2\beta_2は減衰率で、通常は 1 に近い値を取ります。これはKarpathy の Batch-Norm チュートリアルの第 3 節で紹介されている指数移動平均に対応します。

  1. mtm_tvtv_tの偏差を修正し、初期偏差を 0 に修正します。

m^t=mt1β1t\hat{m}_t = \frac{m_t}{1 - \beta_1^t}

v^t=vt1β2t\hat{v}_t = \frac{v_t}{1 - \beta_2^t}

  1. 修正された一次モーメント推定と二次モーメント推定を使用してパラメータを更新します:

θt+1=θtηv^t+ϵm^t\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \cdot \hat{m}_t

ここで、η\etaは学習率で、ϵ\epsilonは数値の安定性を保つために追加された非常に小さな定数です。

このプロセスは、モデルのパラメータが収束するか、特定の停止条件に達するまで、各時間ステップで繰り返されます。

1.のモーメントベクトル(動量ベクトル)と分散ベクトルは、それぞれ 7B のパラメータを持ち、これは前述の 2 倍のパラメータ量が必要な理由です。

ここでの解決策は LoRA(Low-Rank Adaptation、低ランク適応)です:

Screenshot 2024-03-15 at 11.37.11

この技術は、訓練可能なパラメータの数を減少させ、モデルの重みの占有スペースを減少させ、訓練速度を加速する効果があります。このシナリオでは、LoRA はオプティマイザおよび勾配追跡のために必要なパラメータ量を大幅に減少させ、訓練プロセス中に必要なビデオメモリを減少させました。

LoRA の背後にある重要な考え方は、llama2 のような大規模モデルを微調整する際、すべてのパラメータを微調整する必要はない(すなわち全パラメータ微調整)ということです。通常、他のパラメータや層に比べて重要なパラメータがいくつかあります。例えば、注意メカニズムを担当し、シーケンス内のどのトークンが他のトークンと関連しているか、またその関連方法を決定する部分がより重要です。LoRA はこれらの特定のパラメータを取り出し、低ランク行列を注入し、その後の訓練や伝播、パラメータの更新時に変更されるのはこの補助的な低ランク行列だけです。

Screenshot 2024-03-16 at 17.20.56

LoRA の R 超パラメータ、すなわちランクは調整可能です。しかし、一般的には実践の中で、LoRA が選択する特定のパラメータは全体の 10% 以下を占めることが多いです。

Screenshot 2024-03-15 at 11.54.44

LoRA パラメータには精度の高い fp16 を選択し、オプティマイザの状態の単位は fp32 です。したがって、ここでのメモリ占有量はパラメータ量の 4 倍です。

しかし、ここにはもう一つの問題があります。それは活性化部分です。活性化のフォワードパスプロセス中のオーバーヘッドは、神経ネットワーク内で最大の層のサイズ $\times$ バッチサイズ(一次更新で何サンプルを処理するか)であり、これが 5GB のメモリを占有する可能性があり、依然として予算を超えています。

QLoRA#

では、4 ビット量子化を使用できるでしょうか?
これが QLoRA という論文が提案したアイデアで、paged atom(ページ化された原子)最適化技術を通じて、必要に応じてオプティマイザの状態のページングメモリを CPU に移動させ、訓練中のピークの影響を減少させることを目的としています:

Screenshot 2024-03-15 at 11.54.07

そのために、新しい単位nf4(ノーマル浮動小数点 4)が導入されました。

Screenshot 2024-03-16 at 15.12.09

これにより、さらにビデオメモリを節約できます:

Screenshot 2024-03-15 at 12.11.07

勾配累積#

最後の問題はバッチサイズの選択です。もし一度に非常に少ないサンプルを更新することを選択すると、訓練プロセス中の分散が非常に大きくなります。極端な場合は完全な SGD(確率的勾配降下法)になります。したがって、一般的には大きく滑らかで小さく急激な間のスイートスポットを選択します。これが一般的に 23、64、128 をバッチサイズとして選択する理由です。

しかし、現在は一度に 1 つのサンプルしか読み込めないため、Gradient Accumulation 技術が導入されました。

その重要な考え方は、追加のメモリオーバーヘッドを増やすことなく、より大きなバッチサイズを使用した訓練効果を得ることです。

Pasted image 20240315122335

ここでの操作は:

  1. バッチ処理:大きなバッチデータを複数の小さなバッチに分割します(これらの小さなバッチのサイズは利用可能なメモリリソースに基づいて決定されます)。各小バッチに対して:

    • フォワードパスを実行して損失を計算します。
    • 逆伝播を実行して現在の小バッチの勾配を計算しますが、モデルパラメータを即座に更新しません
  2. 勾配累積:各小バッチで計算された勾配を以前の勾配に累積しますが、即座にそれらを使用してパラメータを更新しません。

  3. パラメータ更新:すべての小バッチデータを処理し、十分な数の勾配を累積した後、累積された勾配を使用してモデルのパラメータを一度に更新します。

微調整実践: Mistral 7B#

QLoRA

16GB VRAM

Mixtral 8x7B (MoE)#

ハードウェア要件:>=65GB VRAM

ご覧いただきありがとうございます。微調整実践部分をできるだけ早く更新します~

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。