大規模モデルの主要な微調整方法に関する一般的な理解: Prefix Tuning、P-Tuning V1/V2 から LoRA、QLoRA まで

序文

大規模なモデルを研究したことのある人なら誰でも、PEFT メソッドは少数の (追加の) モデル パラメーターのみを微調整し、プレフィックス チューニング、P チューニング V1/V2、 LoRA、QLoRA 実際、これらの微調整方法はオンラインで紹介されています 多くの記事/チュートリアルがあり、私もたくさん読みましたが、一目で実際に書かれた記事/チュートリアルはまだ少なく、ほとんどの記事/チュートリアルは記事やチュートリアルはほとんど無意味です

つまり、知識をわかりやすく書いたり、説明したりするのは簡単なことではありません。大規模モデルに関連する深度技術、これまでさまざまなモデルの微調整について書いてきましたが、これまでさまざまな微調整方法をまとめたことはなく、微調整方法は非常に重要なので、この記事で

効率的なパラメータ微調整の開発経緯 前編

1.1 Google のアダプター調整: トランスフォーマーに組み込まれており、元のパラメーターは変更されず、新しく追加されたアダプターのみが微調整されます。

2019年、Googleの研究者は論文「 NLPのためのパラメータ効率的な転移学習」でBERTのPEFT微調整方法を提案し、PEFT研究への序章を開きました。彼らは指摘する

  • 特定の下流タスクに直面した場合、フルチューニング (つまり、トレーニング前モデルのすべてのパラメーターが微調整される) を実行するのは非効率すぎます。
  • ただし、固定事前トレーニング モデルの一部のレイヤーが使用されている場合、下流のタスクに近いそれらのレイヤーのパラメーターを微調整するだけでは、より良い結果を達成することは困難です。

そこで彼らは、以下の図に示すようにアダプター構造を設計しました。

画像.png

  1. 上図の左側にあるように、Transformer 構造に埋め込まれており、学習中は元の学習前モデルのパラメーターは固定され、新しく追加されたアダプター構造のみが微調整されます。
  2. 上図の右側に示すように、トレーニングの効率を確保するために (つまり、より多くのパラメーターをできるだけ少なく導入するために)、アダプターを次のような構造として設計しました。 まず、ダウンプロジェクト層です。高次元の特徴を低レベルの次元の特徴にマッピングし、非線形層の後で、アッププロジェクト構造を使用して低次元の特徴を元の高次元の特徴にマッピングし、同時にスキップ接続します。構造は、最悪の場合、アイデンティティに変質する可能性があるように設計されています。

実験結果から、この方法は、パラメータ サイズを追加で 3.6% 増やすだけで (元の事前トレーニング済みモデルのパラメータ量と比較して)、フル微調整 (GLUE インデックスは 0.4% 以内) に近い効果を達成できます。 。

1.2 スタンフォードのプレフィックスチューニング

プレフィックス チューニング前の作業は、主に個別のテンプレートを手動で設計するか、個別のテンプレートを自動的に検索することでしたが、問題は、最終的なパフォーマンスが手動で設計したテンプレートの影響を特に受けやすいことです。単語の追加や単語の欠落、または位置の変更により、多くの問題を引き起こすため、この離散化されたトークンの検索結果は最適ではない可能性があります。

したがって、スタンフォード大学の研究者は、論文「Prefix-Tuning: Optimizing Continuous Prompts for Generation」を通じて、離散トークンの代わりに連続仮想トークン埋め込みを使用するプレフィックス チューニング手法を提案しました。これは、次のように、フルファインチューニングがすべてのパラメータを更新する方法とは異なります。図に示すように (図中のファインチューニングとプレフィックスチューニングの違いに注意してください)

  1. この方法は、トークンを入力する前に、タスク関連の仮想トークンのセクションを Prefix として構築することです。これは、複数の連続したトレーニング可能な「仮想トークン」埋め込みを挿入するのと同じです。これらの疑似トークンは、語彙内の実際の単語である必要はありません。ただし、調整可能な自由パラメータがいくつかあるだけです。」たとえば

    、テーブルからテキストへのタスクの場合、コンテキストは次のように バツ なります。 シリアル化されたフォームの場合、出力はy GPT を使用して生成されたフォームのテキスト説明です。 2; テキストの概要の場合、それは バツ元のテキスト yであり、要約であり、 BART を使用して生成されます
    \rightarrow  。自己回帰モデルの場合、文の前にプレフィックスを追加してz=[プレフィックス ;  バツ ;  や]
    これを取得します。固定 LM の場合 (GPT3 のインコンテキスト学習など)、
    \rightarrow  Encoder-Decoder モデルの場合、Encoder と Decoder の両方にプレフィックスが追加されています。これは、z=\left[プレフィックス ;  x \mid PREFIX^{\prime} ;  そうだね]
    Encoder 側でプレフィックスが追加されるためです。入力部分のエンコーディング (何を抽出するかをガイド バツ)、デコーダー側は後続のトークンの生成をガイドするプレフィックスを追加します ( y 次のトークン配布を操作することで の生成に影響します)

  2. 次に、Prefix 部分のパラメータのみがトレーニング中に更新され、Transformer の他のパラメータは固定されます。

この方法は実際にはプロンプトの構築と似ていますが、プロンプトは人為的に構築された「明示的な」プロンプトでパラメータを更新できないのに対し、プレフィックスは学習可能な「暗黙的な」プロンプトである点が異なります。

同時に、Prefix のパラメータを直接更新することによって引き起こされるトレーニングの不安定性を防ぐために、Prefix 層の前に MLP 構造を追加しました (Prefix を入力と MLP の組み合わせに分解した結果に相当し、より小さい)寸法)。Prefix のパラメータのみを保持します。

パート 2 P チューニング V1/V2

2.1 P チューニング V1

// 更新される

2.2 P-Tuning V2: 鍵はプレフィックスチューニングの導入にあります

// 更新される


第三部 LORA: 大規模言語モデルの低ランク適応

3.1 LoRAとは

「 LLaMA の解釈と微調整: Alpaca-LoRA/Vicuna/BELLE/中国語 LLaMA/Jiang Ziya/LLaMA 2」セクション 2.2.3 Alpaca-LoRA: コンシューマ グレードの GPU での「LLaMA ベースの Alpaca」の微調整などPEFT ライブラリ 上で述べたように、ニューラル ネットワーク モデルでは、通常、モデル パラメーターは行列の形式で表現されます。事前トレーニングされたモデルの場合、そのパラメーター行列にはすでに多くの有用な情報が含まれています。モデルを特定のタスクに適応させるには、これらのパラメーターを微調整する必要があります

LoRA の中心となるアイデアは、これらのパラメーター マトリックスを低ランクの方法で調整することです。数学的には、低ランクとは、論文「LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS」に示されているように、2 つの小さな行列を乗算することで行列を近似できることを意味します。

  1. ターゲット層の選択: まず、事前トレーニングされたニューラル ネットワーク モデルで LoRA を適用するターゲット層を選択します。これらの層は通常、セルフ アテンション メカニズムのクエリ Q 行列やキー K 行列など、タスク固有のものです。
  2. マッピング行列と逆マッピング行列を初期化します: ターゲット層に対して 2 つの小さな行列 A と B を作成します。A は
    \rightarrow  マッピング行列です (通常はランダムなガウス分布で初期化されます。もちろん、Microsoft のディープスピードなど実際のコードが実装されるとき)チャットは LoRA を使用しており、最初は 0 行列が空間を占有するために使用され、その後 ReLU 活性化関数による kaiming 一様分布初期化が呼び出されます。これは、本来の定義で使用される正規分布初期化とは異なりますが、 LoRA、両方の初期化方法が機能します。詳細については、以下のディープスピード チャットを参照してください。 コード )、次元は次元削減
    \rightarrow  B は逆マッピング行列 (0 行列で初期化)、次元は次元増加
    このうち、サイズはマトリックスの順位はLoRAの順位(ランク)とアルファ値によって決まります
  3. パラメータ変換: ターゲット レイヤーの元のパラメータ行列 W を、マッピング行列 A と逆マッピング行列 B を介して変換します。計算式は次のとおりです。W' = W + A * Bここで、W' は変換されたパラメータ行列です。
  4. わ」モデルの微調整:ターゲット層の元のパラメータ行列を新しいパラメータ行列に置き換えてWから、タスク固有のトレーニング データに基づいてモデルを微調整します。
  5. 勾配の更新: 微調整プロセスでは、マッピング行列 A および逆マッピング行列 B に関する損失関数の勾配を計算し、最適化アルゴリズム (Adam、SGD など) を使用して A およびB. 更新プロセス中、元のパラメータ
    行列 W は変更されないことに注意してください。平たく言えば、元の PLM のパラメータはトレーニング中に固定され、次元削減行列 A と次元強化行列 B のみがトレーニングされます。
  6. 繰り返し更新: トレーニングの各バッチで、所定のトレーニング エポックに達するか収束条件が満たされるまで、ステップ 3 ~ 5 を繰り返します。

要約すると、LoRA の詳細な手順には、ターゲット レイヤの選択、マッピング行列と逆マッピング行列の初期化、パラメータ変換の実行、およびモデルの微調整が含まれます。微調整プロセスでは、モデルはマッピング行列 U と逆マッピング行列 V を更新することで特定のタスクの知識を学習し、それによってこのタスクにおけるモデルのパフォーマンスが向上します。

3.2 Microsoft DeepSpeed-Chat での LoRA 微調整の実装

この LoRA の応用範囲は非常に広く、例えば後に Microsoft が立ち上げた DeepSpeed-Chat もこの方式を採用しています。

DeepSpeed-Chat の実装では、LoRA の下位ディメンション lora_dim が設定されている場合 (lora_dim=128 など)、LoRA トレーニングが有効であるとみなされ、元のモデルの名前には「deoder.layers」が含まれます。線形レイヤーが LoRA レイヤーに変更される場合、具体的な操作は次のとおりです。

  1. 元の構造の重みパラメータを固定します。
  2. 2 つの新しい線形層 lora_right_weight と lora_left_weight (上図の次元削減行列 A と次元強化行列 B にそれぞれ対応 )が導入され、最初に lora_dim まで次元削減を実現し、次に次元を元の次元に増やすことができます。
  3. LoRA 層は主に 2 つの分岐パスを実装します。1 つの分岐は重みパラメータが固定された元の構造で、もう 1 つの分岐は次元削減と次元増加のために新しく導入された線形層グループです。
# applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py
# 判断是否启用LoRA模式
if args.lora_dim > 0:
"""
如果启用,则对名称中含有“decoder.layers.”且为线性层的结构部分引入LoRA旁路(实现先降维后升维的2个线性层),
这类结构基本都是attention、信息交互用的inner线性层,
这类结构的Weight参数将被冻结,转而优化LoRA旁路的参数。
"""
    args.lora_module_name = "decoder.layers."
    model = convert_linear_layer_to_lora(model, args.lora_module_name,
                                         args.lora_dim)

# applications/DeepSpeed-Chat/training/utils/module/lora.py
def convert_linear_layer_to_lora(model,
                                 part_module_name,
                                 lora_dim=0,
                                 lora_scaling=1,
                                 lora_droppout=0):
    """
	将名称中带有"decoder.layers."的线性层转换为lora层
	"""
	"""取出模型中参数名含有decoder.layers.的线性层"""
    repalce_name = []
    for name, module in model.named_modules():
        if isinstance(module, nn.Linear) and part_module_name in name:
            repalce_name.append(name)

    for name in repalce_name:
    	"""recursive_getattr实现了从model中根据属性名取出对应原始结构"""
        module = recursive_getattr(model, name)

        """纳入原始结构的参数,实例化lora层"""
        tmp = LinearLayer_LoRA(
            module.weight, lora_dim, lora_scaling, lora_droppout,
            module.bias).to(module.weight.device).to(module.weight.dtype)

        """recursive_getattr实现了将model对应属性的结构换成lora层实例"""
        recursive_setattr(model, name, tmp)
    return model
    
# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):
	"""具体的lora层"""
	def __init__(...):
		...
		"""此处的weight和bias即为原始结构中的参数"""
		self.weight = weight
		self.bias = bias
		···

		"""冻结weight部分的参数"""
		self.weight.requires_grad = False
		···
		self.lora_right_weight = nn.Parameter(torch.zeros(columns, lora_dim))
	    self.lora_left_weight = nn.Parameter(torch.zeros(lora_dim, rows))
        ...

	    """初始化LoRA线性层的参数"""
	    self.reset_parameters()

    # 调用reset_parameters(self)做初始化
    def reset_parameters(self):
    	# 降维矩阵与LoRA原始定义所用的(0,\sigma^2)正态分布初始化不同,而是使用的kaiming均匀分布初始化
    	# kaiming服从均匀分布U(-\sqrt{1/in_feature}, +\sqrt{1/in_feature})
        # f_i是矩阵的输入维度,就是nn.Linear(in_features, out_features)中的in_features
        # 对应上面代码中的columns,而这个columns相当于基座模型的hidden_size
        nn.init.kaiming_uniform_(self.lora_right_weight, a=math.sqrt(5))

        # 升维矩阵使用全0初始化
        nn.init.zeros_(self.lora_left_weight)

    def forward(self, input):
    	"""LoRA的正向传播"""
    	···
    	else:
            # F.linear(input, self.weight, self.bias)是使用给定的权重self.weight和偏差self.bias对输入数据input进行线性变换
            # 这个操作等价于input @ self.weight.t() + self.bias,其中@表示矩阵乘法,.t()表示矩阵转置
	    	return F.linear(input, self.weight, self.bias) 
                    # 1,self.lora_dropout(input)对输入进行了随机的dropout操作,这是一种正则化手段
                    # 2,对结果进行两次线性变换,一次是@ self.lora_right_weight,然后是@ self.lora_left_weight
                    # 3,乘法部分* self.lora_scaling是对加号后面部分的结果进行缩放
	    			+ (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling

このコードの最後の部分をさらに分析します

# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):
	"""具体的lora层"""
	···
    def forward(self, input):
    	"""LoRA的正向传播"""
    	···
    	else:
	    	return F.linear(
	                input, self.weight,
	                self.bias) + (self.lora_dropout(input) @ self.lora_right_weight
	                              @ self.lora_left_weight) * self.lora_scaling

通常の部分の前方伝播はトランスフォーマーによって定義されますが、LoRA 部分の前方伝播は LinearLayer_LoRA(nn.Module) の forward() によって定義されます。つまり、「LoRA 層の 2 つのブランチの結果は次のとおりです」 「合計」、以下の図に示すように、「出典: LoRA」、トレーニング中と同等、より小さい重み行列 (下図の A と B) が分離されますが、トレーニングが完了すると、重みを 1 つにマージできます。新しい重み行列 "

 コードに反映されるのは

F.linear(input, self.weight, self.bias) + (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling

プラス記号の左側は元の構造ブランチ、プラス記号の右側は新しいブランチ、self.lora_right_weight self.lora_left_weight は新しく導入された 2 つの線形レイヤーのパラメーターです。

3.3 Huggingface の PEFT ライブラリによる LoRA、Prefix Tuning、および P-Tuning のカプセル化

Huggingface によって立ち上げられたPEFT ( Parameter-Efficient Fine-Tuning、効率的なパラメータ微調整の意味)ライブラリも LoRA メソッドをカプセル化しています。PEFT ライブラリは、すべてを微調整することなく、事前トレーニングされた言語モデルをさまざまな下流タスクに効率的に適応させることができます。モデルのパラメータ、つまり少数の (追加の) モデル パラメータのみを微調整することで、計算とストレージのコストが大幅に削減されます。

モデル 完全な微調整 PEFT-LoRA PyTorch CPU オフロードを備えた PEFT-LoRA DeepSpeed
bigscience/T0_3B (3B パラメータ) 47.14GB GPU / 2.96GB CPU 14.4GB GPU / 2.96GB CPU 9.8GB GPU / 17.8GB CPU
bigscience/mt0-xxl (12B パラメータ) OOM GPU 56GB GPU / 3GB CPU 22GB GPU / 52GB CPU
bigscience/bloomz-7b1 (7B パラメータ) OOM GPU 32GB GPU / 3.8GB CPU 18.1GB GPU / 35GB CPU

また、PEFT ライブラリ ( peft/src/peft/peft_model.py メイン Huggingface/peft GitHub ) は、次の一般的なメソッドをサポートしています。

  1. LoRA の場合、PEFT による LoRA の実装は、peft/src/peft/tuners/lora.py at main huggingface/peft GitHubで確認できます。たとえば、重みをマージするコード (および上記の DSC による LoRA 重みのマージの実装)本質は一貫している)
    def merge(self): 
        # 检查当前激活的适配器是否在lora_A的键中,如果不在则终止函数
        if self.active_adapter not in self.lora_A.keys():  
            return  
    
        if self.merged:  
            warnings.warn("Already merged. Nothing to do.")
            return  
    
        # 如果激活适配器的r值大于0,表示有可以合并的权重
        if self.r[self.active_adapter] > 0: 
            # 在当前的权重上加上计算得到的新权重
            self.weight.data += (  
                # 转置运算
                transpose(  
                    # 通过矩阵乘法计算新的权重
                    self.lora_B[self.active_adapter].weight @ self.lora_A[self.active_adapter].weight, 
     
                    # 这是转置运算的维度参数
                    self.fan_in_fan_out,  
                )
    
                # 然后将计算得到的权重乘以对应的缩放因子
                * self.scaling[self.active_adapter]  
            )
            self.merged = True
  2. プレフィックス チューニング: プレフィックス チューニング: 生成のための連続プロンプトの最適化、  P-Tuning v2: プロンプト チューニングは、スケールやタスク全体で普遍的に微調整に匹敵する可能性があります
  3. P チューニング:  GPT も理解する
  4. プロンプト チューニング: パラメーター効率の高いプロンプト チューニングのためのスケールの力

パート IV QLoRA

// 更新される

参考文献と推奨書籍

  1. アダプター チューニングに関する Google の論文「NLP のためのパラメーター効率的な転移学習」
  2. 世界に大規模モデルのチューニングを簡単に - PEFT テクノロジーの紹介
  3. PEFT: 低リソースのハードウェア上で 10 億規模のモデルをパラメータ効率よく微調整
  4. 継続的なプロンプト: プレフィックス -チューニング
  5. LLaMA の解釈と微調整: Alpaca-LoRA/Vicuna/BELLE/中国語 LLaMA/Jiang Ziya/LLaMA 2
  6. P-Tuning v2 は小型モデルのパフォーマンスを大幅に向上させ、NER も迅速にチューニングできます
  7. P チューニング: 言語モデルの可能性を解放するテンプレートを自動的に構築します。
  8. プロンプトチューニング - 新しい微調整パラダイムの詳細な解釈
  9. ..

おすすめ

転載: blog.csdn.net/v_JULY_v/article/details/132116949