[自然言語処理] [大規模モデル] 非常に少ないリソースで大規模モデルのメソッドを微調整するための LoRA および BLOOM-LORA 実装コード

非常に少ないリソースで大規模モデルのメソッドを微調整する LoRA および BLOOM-LORA 実装コード

関連ブログ
[自然言語処理] [大規模モデル] ChatGLM-6B モデル構造のコード解析 (スタンドアロン版)
[自然言語処理] [大規模モデル] BLOOM モデル構造のソースコード解析 (スタンドアロン版)
[自然言語処理] [大規模モデル] 大規模モデル メソッドの超低リソース微調整 LoRA および BLOOM-LORA 実装コード
[自然言語処理] [大規模モデル] DeepMind の大規模モデル Gopher
[自然言語処理] [大規模モデル] Chinchilla: を備えた大規模言語モデル最適なトレーニングとコンピューティングの利用
[自然言語処理] [大規模モデル] 大規模言語モデル BLOOM 推論ツール テスト
[自然言語処理] [大規模モデル] GLM-130B: オープンソースのバイリンガル事前トレーニング済み言語モデル
[自然言語処理] [大規模モデル]モデル] 8- 大規模なトランスフォーマー用 ビット行列乗算の概要
[自然言語処理] [大規模モデル] BLOOM: 176B パラメーターとオープンアクセスを備えた多言語モデル
[自然言語処理] [大規模モデル] PaLM: Pathways に基づく大規模言語モデル
[自然言語処理] [chatGPT シリーズ] 大規模な言語モデルは自らを改善できる

1. LoRAの原理

LoRA は、非常に少ないリソースで大規模なモデルを微調整する方法であり、論文「LoRA: Low-Rank Adaptation of Large Language Models 」に由来しています。

1. 大規模モデルの微調整のジレンマ

モデルの規模が拡大し続けるにつれて、モデルはさまざまな機能を備えて「出現」します。特に大規模言語モデル (LLM) については、規模が拡大するにつれて、ゼロショットや常識推論などの機能が大幅に向上します。大規模モデルの微調整コストと展開コストは、小規模モデルに比べて非常に高くなります。たとえば、GPT-3 175B モデルの微調整には 1.2TB のビデオ メモリが必要です。さらに、複数のモデルがさまざまな下流タスクに合わせて微調整される場合、下流タスクごとにモデルの重みを保存する必要があり、これは非常にコストがかかります。シナリオによっては、さまざまなユーザーに合わせてさまざまなモデルを微調整する必要がある場合もあります。その場合、モデルの微調整と展開にかかるコストは許容できないものになります

したがって、大規模モデルの微調整と展開のコストをいかに削減するかが、大規模モデルの商用化の重要な部分となります

2. LoRA以前のアプローチ

LoRA 手法が提案される前にも、大規模モデルの微調整のジレンマを解決しようとする手法が数多くありました。主な方向は 2 つあります: (1) アダプター層の追加、(2) 何らかの形式の入力層のアクティブ化による。ただし、どちらのアプローチにも制限があります。

2.1 アダプター層により推論遅延が発生します

ここに画像の説明を挿入

簡単に言えば、アダプターは元のパラメーターを修正し、微調整のためにいくつかのパラメーターを追加します。上の図では、2 つのアダプターが元のトランス ブロックに追加され、1 つはマルチヘッド アテンションの後ろに、もう 1 つは FFN の後ろに追加されます。

明らかに、アダプターはモデルに追加のレイヤーを追加します。これにより、大規模なモデルは推論中により多くの GPU 通信を必要とし、モデルの並列性も制限されます。これらの問題により、モデル推論が遅くなります

2.2 プレフィックスチューニングの最適化は困難

ここに画像の説明を挿入

接頭辞チューニング手法は、言語モデルのコンテキスト内学習能力に着想を得ており、適切なコンテキストがある限り、言語モデルは自然言語タスクを適切に解決できます。ただし、特定のタスクの離散トークンのプレフィックスを見つけるには長い時間がかかります。プレフィックス チューニングでは、継続的な仮想トークンの埋め込みを使用して離散トークンを置き換えることが提案されています。

具体的には、トランスフォーマーの各レイヤーに対して、トレーニング可能な仮想トークンの埋め込みが文表現の前に挿入されます。自己回帰モデル (GPT シリーズ) の場合は、文の前に連続接頭辞を追加します (例: z = [ PREFIX ; x ; y ] z=[\text{PREFIX};x;y])。z=[プレフィックス;× ;y ]Encoder-Decoder モデル (T5) の場合、Ecoder と Decoder の前に連続プレフィックスを追加しますz = [ PREFIX ; x ∣ PREFIX ' ; y ] z=[\text{PREFIX};x|\text{PREFIX}';y ]z=[プレフィックス;x プレフィックス' ;y ]プレフィックスを追加するプロセスを上の図に示します

ただし、プレフィックスチューニングによって余分なパラメータが追加されることはありません。ただし、プレフィックス調整は最適化が難しく、ダウンストリーム タスクのシーケンス長が短くなります。

3. 問題の正式な定式化

用語と規約LoRA 原則の導入により、Transformer アーキテクチャが使用されます。したがって、いくつかの用語規則を最初にここで示します。Transformer レイヤーの入力および出力の次元はdmodel d_{model}です。dモデル_ _ _ _W q W_qを使用WqW k W_kWWvW_vWv W o W_o Wああセルフアテンション モジュールのクエリ/キー/値/出力射影行列を表します。WWW W 0 W_0 W0事前トレーニング済みモデルの重み行列Δ W \Delta Wを表します。ΔW は、フィッティング プロセス中のモデルの勾配更新を表します。rrrは LoRA モジュールのランクを表します。Adam をモデル オプティマイザーとして使用すると、Transformer MLP フィードフォワード層の次元はdffn = 4 × dmodel d_{ffn}=4\times d_{model} とdふふん_=4×dモデル_ _ _ _

問題ステートメントLoRA はトレーニングの目標とは関係ありませんが、言語モデリングの例を次に示します。事前にトレーニングされた自己回帰言語モデルP Φ ( y ∣ x ) P_{\Phi}(y|x) があるとします。PF( y x ) Φ \ファイΦはモデルパラメータです。目標は、言語モデルを要約や機械読解などの下流タスクに適応させることです。各ダウンストリーム タスクには、コンテキストとターゲットのサンプルのペアで構成されるトレーニング セットがあります。Z = { ( xi , yi ) } i = 1 , … , N \mathcal{Z}=\{(x_i,y_i)\}_{i = 1,\ドット,N}Z={( x私はy私は) }i = 1 , , N,其中 x i x_i バツ私は y i y_i y私はこれらはすべてトークンシーケンスです。たとえば、サマリー タスクの場合、xi x_iバツ私は記事の内容です、yi y_iy私はが概要です。

完全な微調整の過程で、モデルは事前にトレーニングされた重みΦ 0 \Phi_0を使用します。ファイ0モデルを初期化し、条件付き言語モデルを最大化してパラメーターΦ 0 + Δ Φ \Phi_0+\Delta\Phiを更新します。ファイ0+ΔΦ
max ⁡ Φ ∑ ( x , y ) ∈ Z ∑ t = 1 ∣ y ∣ log ⁡ ( P Φ ( yt ∣ x , y < t ) ) (1) \max_{\Phi}\sum_{(x, y)\in \mathcal{Z}}\sum_{t=1}^{|y|}\log (P_\Phi(y_t|x,y_{<t})) \tag{1}ファイマックス( x , y ) Zt = 1yログ( P _F( yx y< t))( 1 )
完全微調整の主な欠点: 下流タスクごとに、異なるパラメータ更新Δ Φ \Delta\PhiΔΦ、寸法∣ Δ Φ ∣ = ∣ Φ 0 ∣ |\Delta\Phi|=|\Phi_0|∣ΔΦ∣=Φ0したがって、事前トレーニングされたモデルが大きい場合、多くの独立した微調整されたモデル インスタンスを保存してデプロイすることは非常に困難です。

パラメータの効率を高めるために、LoRA は比較的非常に小さなパラメータΘ \Thetaを使用します。タスク関連パラメータの増分を表すΘ Δ Φ = Δ Φ ( Θ ) \Delta\Phi=\Delta\Phi(\Theta)DF=ΔΦ ( Θ ),使用∣ Θ ∣ ≪ ∣ Φ 0 ∣ |\Theta|\ll |\Phi_0|∣Θ∣Φ0Δ Φ \Delta\Phiを求めるΔΦのタスクはΘ \Thetaになりますデフォルトmax
⁡ Θ ∑ ( x , y ) ∈ Z ∑ t = 1 ∣ y ∣ log ⁡ ( p Φ 0 + Δ Φ ( Θ ) ( yt ∣ x , y < t ) ) (2) \max_{\ Theta} \sum_{(x,y)\in\mathcal{Z}}\sum_{t=1}^{|y|}\log(p_{\Phi_0+\Delta\Phi(\Theta)}(y_t| x, y_{<t})) \tag{2}Thマックス( x , y ) Zt = 1yログ( p _ファイ0+ ΔΦ ( Θ )( yx y< t))( 2 )
LoRA は低ランク表現を使用してΔ Φ \Delta\PhiΔΦ、計算効率とストレージ効率の両方を達成します。事前学習モデルが 175B GPT-3 の場合、学習可能なパラメータ∣ Θ ∣ |\Theta|∣Θ∣ は、∣ Φ 0 ∣ |\Phi_0|と同じくらい小さくすることができます。Φ0∣0.01 %の0.01\%0.01%

4.ロラ

ここに画像の説明を挿入

通常、ニューラル ネットワークには行列の乗算を実行する多数の高密度層が含まれており、これらの層は通常フルランクです。Adgajanyan et al.et al.の研究は、事前トレーニングされた言語モデルの「固有の次元性」が低いことを示しています。この研究に触発されて、重み更新も下流タスクへのモデル適応中に低い「固有ランク」を持つ必要があります。事前トレーニングの重み行列W 0 ∈ R d × k W_0\in\mathbb{R}^{d\times k} の場合W0Rd × kの場合、その更新は低ランク分解W 0 + Δ W = W 0 + BA W_0+\Delta W=W_0+BA でW0+ΔW _=W0+B AB ∈ R d × r , A ∈ R r × k B\in\mathbb{R}^{d\times r},A\in\mathbb{R}^{r\times k}BRd × rRr × kかつちr ≪ min ⁡ ( d , k ) r\ll\min(d,k)r( d ,k )トレーニング中、W 0 W_0W0フリーズされており、グラデーションの更新を受け入れません、AAABBBはトレーニング可能なパラメータです。W 0 W_0に注意してくださいW0 Δ W = B A \Delta W=BA ΔW _=Bと A の両方に同じ入力が乗算されます。h = W 0 xh=W_0xの場合h=W0xの場合、順伝播は次のようになります:
h = W 0 x + Δ W x = W 0 x + BA x (3) h=W_0x+\Delta Wx=W_0x+BAx \tag{3}h=W0バツ+ΔW × _=W0バツ+BAx _ _( 3 )

ペア行列AAAはランダムなガウス分布で初期化され、行列BBBは 0 で初期化されるため、Δ W = BA \Delta W=BAΔW _=B A はトレーニングの開始時には 0 です。α r \frac{\alpha}{r}を使用しますrあるΔ W x \Delta WxをスケールするΔ W x、ここでα \alphaαはrrより小さいrの定数Adam 最適化を使用する場合、適切なスケーリング初期化の後、α \alphaαは学習率の調整とほぼ同じです。

デプロイ時に、W = W 0 + BAW=W_0+BAを明示的に計算して保存しますW=W0+B A、通常どおり推論を実行します。W0W_0W0 B A BA B AはR d × k \mathbb{R}^{d\times k}ですRd × k別の下流タスクに切り替える必要がある場合は、 BA BAを減算することで切り替えることができます。B Aを復元するW 0 W_0W0、次に別のB ' A ' B' A'を追加しますBあ」'重要なのは、これによって追加の推論レイテンシが発生しないことが保証されることです。

5. LoRAをTransformerに適用

理論的には、LoRA は任意のニューラル ネットワークの重み行列に適用できるため、トレーニング可能なパラメーターの数が減ります。Transformer アーキテクチャのセルフアテンション モジュールには 4 つの重み行列があります: W q 、 W k 、 W v 、 W o W_q、W_k、W_v、W_oWqWWvWああ、および 2 つの MLP モデルの重み行列。W q W_qWq(或者 W k , W v W_k,W_v WWv) 次元としてdmodel × dmodel d_{model}\times d_{model}dモデル_ _ _ _×dモデル_ _ _ _単一の行列の。簡素化とパラメータ効率のため、この調査は下流タスクのアテンション重みの適応と MLP モジュールのフリーズに限定されています。

利点. 最も注目すべき利点は、ビデオ メモリとストレージ容量の削減です。Adam でトレーニングされた大規模な Transformer の場合、 if r ≪ dmodelr\ll d_{model}rdモデル_ _ _ _, 凍結されたパラメータのオプティマイザ状態を保存する必要がないため、VRAM 使用量が 2/3 削減されます。GPT-3 175B の場合、トレーニング中のメモリ消費量は 1.2TB から 350GB に削減されます。r = 4の場合r=4r=4また、クエリ マトリックスと値マトリックスのみを調整すると、チェックポイント サイズは 10,000 分の 1 (350 GB から 35 MB に) 減少します。もう 1 つの利点は、LoRA 重みを交換するだけで、導入時にタスクを低コストで切り替えることができることです。さらに、GPT-3 175B は、完全な微調整と比較して、大部分のパラメーターの勾配を計算する必要がないため、トレーニングが 25% 高速になります。

2. コード: BLOOM-LoRA を実装する

このセクションでは、LoRA を使用して大規模な言語モデルのブルームを微調整する方法を説明します。

注: peft パッケージはまだ急速な反復の過程にあり、その後のインターフェイスに大きな変更が加えられる可能性があり、いくつかのバグも存在する可能性があります。主要な依存関係パッケージのバージョン:

transformers==4.26.1
torch==1.13.1
deepspeed==0.8.2
peft==0.2.0

1. トレーニングコード

簡潔にするために、トレーニング コードが train.py にあると仮定します。

1.1 依存パッケージをインポートする

import os
import torch
import random
import datasets
import numpy as np

from tqdm import tqdm
from typing import Dict
from torch.utils.data import DataLoader
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    DataCollatorForSeq2Seq,
    TrainingArguments,
    Trainer
)
from peft import (
    LoraConfig,
    TaskType,
    get_peft_model,
    get_peft_model_state_dict,
    set_peft_model_state_dict
)

def set_random_seed(seed):
    if seed is not None and seed > 0:
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        torch.random.manual_seed(seed)
        torch.cuda.manual_seed(seed)
        torch.cuda.manual_seed_all(seed)
        torch.backends.cudnn.deterministic = True

set_random_seed(1234)

1.2 パラメータの設定

# LoRA参数
LORA_R = 8
LORA_ALPHA = 32
LORA_DROPOUT = 0.1
# 训练参数
EPOCHS=3
LEARNING_RATE=5e-5
OUTPUT_DIR="./checkpoints"
BATCH_SIZE=4 # 2
GRADIENT_ACCUMULATION_STEPS=3
# 其他参数
MODEL_PATH = "bigscience/bloomz-7b1-mt"
DATA_PATH = "./data/belle_open_source_1M.train.json"
MAX_LENGTH = 512
PATTERN = "{}\n{}"
DS_CONFIG = "ds_zero2_config.json"
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH) # 加载tokenizer

1.3 データのロード

dataset = datasets.load_dataset("json", data_files=DATA_PATH)
# print(dataset["train"][0])

1.4 トークン化

def tokenize(text: str, add_eos_token=True):
    result = tokenizer(
        text,
        truncation=True,
        max_length=MAX_LENGTH,
        padding=False,
        return_tensors=None)
    # 判断是否要添加eos_token
    if (result["input_ids"][-1] != tokenizer.eos_token_id
        and len(result["input_ids"]) < MAX_LENGTH
        and add_eos_token):
        result["input_ids"].append(tokenizer.eos_token_id)
        result["attention_mask"].append(1)
    result["labels"] = result["input_ids"].copy()
    return result

def preprocess(example: Dict, train_on_inputs: bool = False):
    prompt = example["input"]
    response = example["target"]
    text = PATTERN.format(prompt, response)
    tokenized_inp = tokenize(text)
    # 若train_on_inputs为False,则将label中与input相关的token替换为-100
    if not train_on_inputs:
        tokenized_prompt = tokenize(prompt,add_eos_token=False)
        prompt_tokens_len = len(tokenized_prompt["input_ids"])
        tokenized_inp["labels"] = [-100]*prompt_tokens_len + tokenized_inp["labels"][prompt_tokens_len:]
    return tokenized_inp

train_data = dataset["train"].shuffle().map(preprocess, remove_columns=["id", "input", "target"])
print(train_data[0])

1.5 照合_fn

# pad_to_multiple_of=8表示padding的长度是8的倍数
collate_fn = DataCollatorForSeq2Seq(tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True)

1.6 モデルのロード

device_map = {
    
    "": int(os.environ.get("LOCAL_RANK") or 0)}
# device_map指定模型加载的GPU;troch_dtype=torch.float16表示半精度加载模型
model = AutoModelForCausalLM.from_pretrained(MODEL_PATH, torch_dtype=torch.float16, device_map=device_map)

1.7 LoRA関連

# 转换模型
model = get_peft_model(model, lora_config)
model.config.use_cache = False
old_state_dict = model.state_dict
model.state_dict = (
    lambda self, *_, **__: get_peft_model_state_dict(self, old_state_dict())
).__get__(model, type(model))
# 打印模型中的可训练参数
model.print_trainable_parameters()

1.8 トレーニングパラメータ

args = TrainingArguments(
    output_dir=OUTPUT_DIR, # checkpoint的存储目录
    per_device_train_batch_size=BATCH_SIZE, # 单设备上的batch size
    gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS, # 梯度累加的step数
    warmup_steps=100,
    num_train_epochs=EPOCHS,
    learning_rate=LEARNING_RATE,
    fp16=True, # 使用混合精度训练
    logging_steps=50,
    evaluation_strategy="no", # 不进行评估
    save_strategy="steps",
    save_steps=2000, # 保存checkpoint的step数
    save_total_limit=5, # 最多保存5个checkpoint
    deepspeed=DS_CONFIG
)

1.9 モデルのトレーニング

trainer = Trainer(
    model=model,
    train_dataset=train_data,
    eval_dataset=None,
    args=args,
    data_collator=collate_fn
)
trainer.train()
model.save_pretrained("best_model")

2. DeepSpeed設定ファイル

DeepSpeed 構成ファイルの名前は ds_zero2_config.json です。

{
    
    
  "train_micro_batch_size_per_gpu": "auto",
  "gradient_accumulation_steps": "auto",
  "steps_per_print": 50,
  "gradient_clipping": 1.0,
  "zero_optimization": {
    
    
    "stage": 2,
    "offload_optimizer": {
    
    
            "device": "cpu"
    },
    "contiguous_gradients": true,
    "overlap_comm": true
  },
  "zero_allow_untested_optimizer": true,
  "fp16": {
    
    
    "enabled": true,
    "loss_scale": 0,
    "loss_scale_window": 1000,
    "hysteresis": 2,
    "min_loss_scale": 1
  },
  "optimizer": {
    
    
    "type": "Adam",
    "params": {
    
    
      "lr": "auto",
      "betas": "auto",
      "eps": "auto",
      "weight_decay": "auto"
    }
  },
  "activation_checkpointing": {
    
    
    "partition_activations": true,
    "contiguous_memory_optimization": true
  },
  "wall_clock_breakdown": false
}

3.スタート

deepspeed --include=localhost:0,1,2,3 train.py

4. 推論

推論ファイル名は inference.py です

import torch
  
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

BASE_MODEL = "bigscience/bloomz-7b1-mt"
LORA_WEIGHTS = "best_model"
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
model = AutoModelForCausalLM.from_pretrained(
        BASE_MODEL,
        torch_dtype=torch.float16, # 加载半精度
        device_map={
    
    "":0}, # 指定GPU 0
    )
model.eval()
# 加载LoRA权重
model = PeftModel.from_pretrained(model, LORA_WEIGHTS, torch_dtype=torch.float16)
model.half()
prompt = ""
inp = tokenizer(prompt, max_length=512, return_tensors="pt").to("cuda")
outputs = model.generate(input_ids=inp["input_ids"], max_new_tokens=256)
print(tokenizer.decode(outputs[0]))

参考文献

https://arxiv.org/pdf/2106.09685.pdf

https://zhuanlan.zhihu.com/p/615235322

https://github.com/tloen/alpaca-lora/blob/main/finetune.py

https://github.com/huggingface/peft/blob/main/examples/conditional_generation/peft_lora_seq2seq_accelerate_ds_zero3_offload.py

おすすめ

転載: blog.csdn.net/bqw18744018044/article/details/130163540
おすすめ