transformers、accelerate、bitsandbytesを使用した大規模トランスフォーマーの8ビット行列乗算へのやさしい入門
transformers、accelerate、bitsandbytesを使用した8ビット行列乗算の大規模トランスフォーマー入門
導入
言語モデルはますます大きくなっています。この執筆時点では、PaLMは540Bのパラメータを持ち、OPT、GPT-3、およびBLOOMは約176Bのパラメータを持ち、さらに大きなモデルに向かっています。以下は、いくつかの最近の言語モデルのサイズを示した図です。
したがって、これらのモデルは簡単にアクセス可能なデバイス上で実行するのが難しいです。例えば、BLOOM-176Bで推論を行うためには、8つの80GBのA100 GPU(各約15,000ドル)が必要です。BLOOM-176Bを微調整するには、これらのGPUが72台必要です!PaLMのようなさらに大きなモデルでは、さらに多くのリソースが必要です。
これらの巨大なモデルは多くのGPUで実行する必要があるため、モデルの性能を維持しながらこれらの要件を削減する方法を見つける必要があります。モデルサイズを縮小するためのさまざまな技術が開発されており、量子化や蒸留などの技術があります。
BLOOM-176Bのトレーニングを完了した後、HuggingFaceとBigScienceでは、この大きなモデルをより少ないGPUで簡単に実行できるようにする方法を探していました。BigScienceコミュニティを通じて、大規模モデルの予測パフォーマンスを低下させずに大規模モデルのメモリフットプリントを2倍に減らすInt8推論の研究について知らされました。すぐにこの研究に協力し始め、Hugging Faceのtransformersに完全に統合することで終了しました。このブログ記事では、Hugging FaceモデルのLLM.int8()統合を提供し、詳細を以下で説明します。研究についてもっと読みたい場合は、論文「LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale」を読んでください。
この記事では、この量子化技術の高レベルの概要を提供し、transformers
ライブラリへの統合の難しさを概説し、このパートナーシップの長期的な目標を立てます。
ここでは、なぜ大きなモデルが多くのメモリを使用するのか、BLOOMが350GBになる理由について、少しずつ基本的な前提を説明します。
機械学習で使用される一般的なデータ型
まず、機械学習の文脈では「精度」とも呼ばれる異なる浮動小数点データ型の基本的な理解から始めます。
モデルのサイズは、そのパラメータの数とその精度によって決まります。一般的には、float32、float16、またはbfloat16のいずれかのデータ型が使用されます(以下の画像は、https://blogs.nvidia.com/blog/2020/05/14/tensorfloat-32-precision-format/から引用されています)。
Float32(FP32)は、標準化されたIEEE 32ビット浮動小数点表現を表します。このデータ型では、幅広い浮動小数点数を表現することが可能です。FP32では、8ビットが「指数」に、23ビットが「仮数」に、1ビットが数値の符号に予約されています。さらに、ほとんどのハードウェアはFP32の操作と命令をサポートしています。
浮動小数点16ビット(FP16)のデータ型では、5ビットが指数に、10ビットが仮数に予約されています。これにより、FP16数の表現可能な範囲はFP32よりもはるかに低くなります。これにより、FP16数はオーバーフロー(非常に大きな数を表現しようとする)やアンダーフロー(非常に小さな数を表現する)のリスクにさらされます。
例えば、10k * 10k
を行うと、100M
になりますが、これはFP16で表現することはできません。なぜなら、最大の数値は64k
であり、NaN
(Not a Number)の結果になるためです。そして、ニューラルネットワークのような連続的な計算がある場合、以前の作業が破壊されます。通常、この問題を克服するために損失スケーリングが使用されますが、常にうまく機能しないことがあります。
これらの制約を避けるために、新しい形式であるbfloat16(BF16)が作成されました。BF16では、8ビットが指数(FP32と同じ)に、7ビットが小数部に予約されます。
これは、BF16ではFP32と同じ動的範囲を保持できることを意味します。ただし、FP16に比べて3ビットの精度が低下します。非常に大きな数値には問題ありませんが、ここでは精度はFP16よりも悪いです。
Ampereアーキテクチャでは、NVIDIAはTensorFloat-32(TF32)精度形式も導入しました。これは、BF16の動的範囲とFP16の精度を組み合わせて、19ビットのみを使用します。現在は特定の操作中にのみ内部で使用されています。
機械学習の用語では、FP32は完全精度(4バイト)と呼ばれ、BF16とFP16は半精度(2バイト)と呼ばれます。さらに、int8(INT8)データ型は、2^8個の異なる値([0、255]または[-128、127]の範囲内)を格納できる8ビットの表現で構成されています。
理想的には、トレーニングと推論はFP32で行うべきですが、FP16/BF16よりも2倍遅くなります。そのため、トレーニングの速度を向上させるために、重みは正確な「メインの重み」参照としてFP32で保持され、順方向および逆方向の計算はFP16/BF16で行われます。FP16/BF16の勾配は、その後、FP32メインの重みを更新するために使用されます。
トレーニング中は、メインの重みは常にFP32で格納されますが、実際には、半精度の重みは推論中にFP32と同等の品質を提供する場合があります。モデルに複数の勾配更新がある場合にのみ正確なモデルの参照が必要です。これは、半精度の重みを使用し、同じ結果を得るために半分のGPUを使用できることを意味します。
モデルのサイズをバイト単位で計算するには、パラメータの数に選択した精度のサイズをバイト単位で乗算します。たとえば、BLOOM-176Bモデルのbfloat16バージョンを使用する場合、176*10**9 x 2バイト = 352GB
となります!先に述べたように、これは数台のGPUに収まるのはかなりの難題です。
しかし、異なるデータ型を使用してそれらの重みをより少ないメモリで保存できるとしたらどうでしょうか?量子化と呼ばれる手法が深層学習で広く使用されています。
モデルの量子化の概要
実験的に、4バイトのFP32精度の代わりに2バイトのBF16/FP16半精度を使用すると、ほぼ同じ推論結果が得られ、モデルのサイズが半分になります。さらに削減できれば素晴らしいですが、低い精度では推論品質が劇的に低下し始めます。
それを解決するために、8ビットの量子化を導入します。この方法では、モデルサイズの1/4しか必要としない四分精度が使用されます!ただし、これは単にビットの半分を削除するだけではありません。
量子化は、実質的にはデータ型を別のデータ型に「丸める」ことによって行われます。たとえば、あるデータ型の範囲が0..9で、別のデータ型の範囲が0..4である場合、最初のデータ型の値「4」は、2番目のデータ型では「2」に丸められます。ただし、最初のデータ型に値「3」がある場合、それは2番目のデータ型の1と2の間に位置しているため、通常は「2」に丸められます。これは、最初のデータ型の値「4」と「3」が、2番目のデータ型では同じ値「2」を持つことを示しています。これは、量子化が情報の損失を引き起こすノイズの多いプロセスであり、一種の非可逆圧縮です。
最も一般的な8ビットの量子化技術は、ゼロポイント量子化と絶対最大値(absmax)量子化です。ゼロポイント量子化とabsmax量子化は、浮動小数点値をよりコンパクトなint8(1バイト)値にマッピングします。まず、これらの方法では、入力を量子化定数でスケーリングして正規化します。
たとえば、ゼロポイント量子化では、範囲が-1.0〜1.0であり、範囲-127〜127に量子化する場合、スケーリングには127の係数を使用し、それを8ビット精度に丸める必要があります。元の値を取得するには、int8の値を同じ量子化係数で除算する必要があります。たとえば、値0.3は、0.3*127 = 38.1
にスケーリングされます。丸めにより、値38が得られます。これを逆にすると、38/127=0.2992
が得られます。この例では、量子化エラーは0.008です。これらの非常に小さなエラーは、モデルのレイヤーを通過し伝播すると蓄積し、パフォーマンスの低下につながります。
(画像は、このブログ記事から引用されました)
では、absmax量子化の詳細を見てみましょう。absmax量子化におけるfp16数とその対応するint8数のマッピングを計算するには、まずテンソルの絶対最大値で割り、次にデータ型の合計範囲で乗算する必要があります。
たとえば、[1.2、-0.5、-4.3、1.2、-3.1、0.8、2.4、5.4]
というベクトルにabsmax量子化を適用したいとします。それを絶対最大値である5.4
で抽出します。int8は[-127、127]
の範囲を持っているので、127を5.4
で割るとスケーリングファクターとして23.5
が得られます。したがって、元のベクトルにそれを乗算すると量子化されたベクトル[28、-12、-101、28、-73、19、56、127]
が得られます。
最新の情報を取得するためには、int8数を量子化因子で完全精度で除算するだけで十分ですが、上記の結果は「四捨五入」されているため、一部の精度が失われます。
unsigned int8の場合、最小値を引き、絶対最大値でスケーリングします。これはゼロポイント量子化と似ています。これは最小値と最大値のスケーリングと似ていますが、後者は値のスケールを維持し、値「0」が量子化エラーなしで常に整数で表されるようにします。
これらのトリックは、行ごとのまたはベクトルごとの量子化など、さまざまな方法で組み合わせることができます。行列の乗算でより正確な結果を得るためには、行ごとのまたはベクトルごとの量子化が使用されます。行列の乗算をA*B=Cとして、テンソルごとに絶対最大値で正規化する通常の量子化の代わりに、行ごとのAおよび列ごとのBの絶対最大値を求めます。次に、これらのベクトルでAとBを正規化します。そして、A*Bを計算してCを得ます。最後に、FP16の値に戻すために、AとBの絶対最大値ベクトルの外積を計算して非正規化します。この技術の詳細については、LLM.int8()の論文やTimのブログの量子化と新興特徴に関する記事で詳しく説明されています。
これらの基本的な技術により、Deep Learningモデルを量子化することができますが、大規模なモデルでは通常、精度が低下します。Hugging Face TransformersとAccelerateライブラリに統合されたLLM.int8()の実装は、BLOOMなどの176Bパラメータを持つ大規模なモデルでもパフォーマンスが低下しない初めての技術です。
LLM.int8()の優れた概要:大規模言語モデルのゼロ劣化行列乗算
LLM.int8()では、大規模なモデルでは従来の量子化が失敗する理由を理解するために、トランスフォーマーのスケール依存性の新興特性を理解することが重要であることを示しています。パフォーマンスの低下は、次のセクションで説明する外れ値フィーチャーによって引き起こされると説明しています。LLM.int8()アルゴリズム自体は次のように説明できます。
本質的には、LLM.int8()は行列乗算の計算を次の3つのステップで完了しようとします:
- 入力の隠れ状態から、列ごとに外れ値(つまり、ある閾値よりも大きい値)を抽出します。
- 外れ値をFP16で、非外れ値をint8で行列乗算します。
- 非外れ値の結果を非量子化し、外れ値と非外れ値の結果を合算して、FP16の完全な結果を得ます。
これらのステップは、次のアニメーションで要約されます:
外れ値フィーチャーの重要性
一部の数字のグローバル分布の範囲外の値は、一般的に外れ値と呼ばれます。外れ値の検出は広く使用され、現在の文献でカバーされており、フィーチャーの分布に関する事前の知識を持つことは外れ値検出のタスクに役立ちます。具体的には、トランスフォーマーベースのモデルの場合、>6Bパラメータのモデルでは、クラシックな量子化が失敗することを観察しています。大規模な外れ値フィーチャーは、より小さなモデルにも存在しますが、トランスフォーマーの各レイヤーに存在する非常に系統的なパターンから、これらの外れ値は常に出現するという特定の閾値を観察しています。これらの現象の詳細については、LLM.int8()の論文や新興特徴のブログ記事をご覧ください。
先述の通り、8ビットの精度は非常に制約されているため、いくつかの大きな値を持つベクトルを量子化すると、非常に誤った結果が得られる可能性があります。さらに、トランスフォーマベースのアーキテクチャには、すべての要素をリンクする組み込み特性があるため、これらのエラーは複数のレイヤーを跨いで伝播するにつれて複合される傾向があります。そのため、このような極端な外れ値での効率的な量子化を容易にするために、混合精度分解が開発されました。次に、これについて説明します。
MatMulの内部
隠れた状態が計算された後、カスタムのしきい値を使用して外れ値を抽出し、上記で説明したように行列を2つの部分に分解します。この方法では、このような方法で6以上の大きさのすべての外れ値を抽出すると、完全な推論パフォーマンスが回復します。外れ値部分はfp16で行われるため、クラシックな行列の乗算です。一方、8ビットの行列の乗算は、重みと隠れた状態をベクトルごとに量子化することによって行われます。つまり、隠れた状態の場合は行ごとの量子化であり、重み行列の場合は列ごとの量子化です。このステップの後、結果はデクォンタイズされ、半精度で返されて最初の行列の乗算に追加されます。
0の劣化とは何ですか?
この方法のパフォーマンス劣化を適切に評価するにはどうすればよいですか? 8ビットモデルを使用すると、生成においてどれだけの品質を失うのでしょうか?
lm-eval-harnessを使用して、8ビットモデルとネイティブモデルを使用したいくつかの一般的なベンチマークを実行し、結果を報告しました。
OPT-175Bの場合:
BLOOM-176の場合:
これらのモデルでは、BLOOM-int8を除き、すべてのメトリックの絶対差が標準誤差以下であるため、0のパフォーマンス劣化を観察しました(BLOOM-int8はlambadaでネイティブモデルよりもわずかに優れています)。最先端のアプローチとの詳細なパフォーマンス評価については、論文をご覧ください。
ネイティブモデルよりも速いですか?
LLM.int8()メソッドの主な目的は、パフォーマンスの劣化なしに大規模なモデルをよりアクセス可能にすることです。ただし、その場合のメソッドが非常に遅い場合、そのメソッドはあまり役に立ちません。したがって、複数のモデルの生成速度をベンチマークしました。BLOOM-176BはLLM.int8()を使用した場合、fp16バージョンと比較して約15%から23%遅くなりますが、それでも非常に受け入れられる速度です。T5-3BやT5-11Bなどの小さなモデルでは、より大きな遅延が見られました。これらの小さなモデルの推論時間を312 msから173 ms(T5-3Bの場合)および45 msから25 ms(T5-11Bの場合)に短縮するために、私たちは努力しました。さらに、問題も既に特定されており、今後のリリースでは小さなモデルに対してLLM.int8()がさらに高速化される可能性があります。現時点では、現在の数字は以下の表にあります。
3つのモデルはBLOOM-176B、T5-11B、T5-3Bです。
Hugging Face transformers
の統合のニュアンス
次に、Hugging Face transformers
の統合の具体的な内容について説明しましょう。使用方法と、設定を行う際に遭遇する可能性のある一般的な問題について見ていきましょう。
使用方法
このブログ投稿で説明されているすべての魔法の責任を負うモジュールは、Linear8bitLt
という名前で、bitsandbytes
ライブラリから簡単にインポートできます。これは、クラシックなtorch.nn
モジュールから派生したものであり、以下に説明するコードを使用して、簡単にアーキテクチャに使用および展開できます。
以下は、次のようなユースケースのステップバイステップの例です:小さなモデルをbitsandbytes
を使用してint8に変換したいとします。
- まず、以下の正しいインポートが必要です!
import torch
import torch.nn as nn
import bitsandbytes as bnb
from bnb.nn import Linear8bitLt
- 次に、独自のモデルを定義できます。注意点として、チェックポイントや任意の精度のモデルを8ビット(FP16、BF16、FP32)に変換することはできますが、現時点では、モデルの入力はFP16である必要があります。Int8モジュールが機能するためには。したがって、ここではモデルをfp16モデルとして扱います。
fp16_model = nn.Sequential(
nn.Linear(64, 64),
nn.Linear(64, 64)
)
- お気に入りのデータセットとタスクでモデルをトレーニングしたとしましょう! さあ、モデルを保存する時間です:
[... モデルをトレーニングする ...]
torch.save(fp16_model.state_dict(), "model.pt")
- 今、
state_dict
が保存されたので、int8モデルを定義しましょう:
int8_model = nn.Sequential(
Linear8bitLt(64, 64, has_fp16_weights=False),
Linear8bitLt(64, 64, has_fp16_weights=False)
)
ここで非常に重要なのは、フラグhas_fp16_weights
を追加することです。デフォルトでは、これは混合Int8/FP16精度でトレーニングに使用されるため、True
に設定されています。ただし、メモリ効率の良い推論に興味があり、has_fp16_weights=False
を使用する必要があります。
- これで、8ビットのモデルをロードする時間です!
int8_model.load_state_dict(torch.load("model.pt"))
int8_model = int8_model.to(0) # ここで量子化が行われます
量子化ステップは、モデルがGPUに設定された後の2行目で行われます。 .to
関数を呼び出す前にint8_model[0].weight
を印刷すると、次のようになります:
int8_model[0].weight
Parameter containing:
tensor([[ 0.0031, -0.0438, 0.0494, ..., -0.0046, -0.0410, 0.0436],
[-0.1013, 0.0394, 0.0787, ..., 0.0986, 0.0595, 0.0162],
[-0.0859, -0.1227, -0.1209, ..., 0.1158, 0.0186, -0.0530],
...,
[ 0.0804, 0.0725, 0.0638, ..., -0.0487, -0.0524, -0.1076],
[-0.0200, -0.0406, 0.0663, ..., 0.0123, 0.0551, -0.0121],
[-0.0041, 0.0865, -0.0013, ..., -0.0427, -0.0764, 0.1189]],
dtype=torch.float16)
一方、2行目の呼び出しの後にそれを印刷すると:
int8_model[0].weight
Parameter containing:
tensor([[ 3, -47, 54, ..., -5, -44, 47],
[-104, 40, 81, ..., 101, 61, 17],
[ -89, -127, -125, ..., 120, 19, -55],
...,
[ 82, 74, 65, ..., -49, -53, -109],
[ -21, -42, 68, ..., 13, 57, -12],
[ -4, 88, -1, ..., -43, -78, 121]],
device='cuda:0', dtype=torch.int8, requires_grad=True)
重みの値は「切り捨て」られていることがわかります。また、値は[-127, 127]の間に分布しているようです。また、fp16で外れ値のMatMulを実行するためにFP16の重みを取得する方法についても疑問に思うかもしれません。簡単に次のようにできます:
(int8_model[0].weight.CB * int8_model[0].weight.SCB) / 127
そして、次のようになります:
tensor([[ 0.0028, -0.0459, 0.0522, ..., -0.0049, -0.0428, 0.0462],
[-0.0960, 0.0391, 0.0782, ..., 0.0994, 0.0593, 0.0167],
[-0.0822, -0.1240, -0.1207, ..., 0.1181, 0.0185, -0.0541],
...,
[ 0.0757, 0.0723, 0.0628, ..., -0.0482, -0.0516, -0.1072],
[-0.0194, -0.0410, 0.0657, ..., 0.0128, 0.0554, -0.0118],
[-0.0037, 0.0859, -0.0010, ..., -0.0423, -0.0759, 0.1190]],
device='cuda:0')
元のFP16の値にかなり近いです(上の2つの出力)!
- 今、モデルを安全に推論することができます。入力が正しいGPU上にあり、FP16であることを確認してください:
input_ = torch.randn((1, 64), dtype=torch.float16)
hidden_states = int8_model(input_.to(torch.device('cuda', 0)))
完全な最小限のコードの例をチェックしてください!
ちなみに、これらのモジュールはnn.Linear
モジュールとは異なり、パラメータはbnb.nn.Int8Params
クラスから来るため、若干異なることに注意してください。後で見るように、これは私たちの旅においてさらなる障害を示しました!
さあ、それをtransformers
ライブラリに統合する方法を理解する時が来ました!
accelerate
が必要なものです
大きなモデルを扱う場合、accelerate
ライブラリには多くの便利なユーティリティが含まれています。init_empty_weights
メソッドは特に役立ちます。このメソッドを使って、どんなサイズのモデルでもメモリを割り当てずに初期化することができます。
import torch.nn as nn
from accelerate import init_empty_weights
with init_empty_weights():
model = nn.Sequential([nn.Linear(100000, 100000) for _ in range(1000)]) # これは約0 RAMを使用します!
初期化されたモデルは、PyTorchのmeta
デバイスに配置されます。これは、メモリを割り当てずに形状とデータ型を表現するための基礎メカニズムです。すごいでしょう?
最初に、この関数は.from_pretrained
関数の内部で呼び出され、すべてのパラメータをtorch.nn.Parameter
にオーバーライドします。これは私たちの要件に合わないため、上述したようにInt8Params
クラスを保持したい場合には適しません。以下のPRで次のように修正しました:
module._parameters[name] = nn.Parameter(module._parameters[name].to(torch.device("meta")))
を
param_cls = type(module._parameters[name])
kwargs = module._parameters[name].__dict__
module._parameters[name] = param_cls(module._parameters[name].to(torch.device("meta")), **kwargs)
これが修正されたので、このコンテキストマネージャを簡単に活用して、カスタム関数を使用してすべてのnn.Linear
モジュールをbnb.nn.Linear8bitLt
に置き換えることができます。メモリのコストなしで!
def replace_8bit_linear(model, threshold=6.0, module_to_not_convert="lm_head"):
for name, module in model.named_children():
if len(list(module.children())) > 0:
replace_8bit_linear(module, threshold, module_to_not_convert)
if isinstance(module, nn.Linear) and name != module_to_not_convert:
with init_empty_weights():
model._modules[name] = bnb.nn.Linear8bitLt(
module.in_features,
module.out_features,
module.bias is not None,
has_fp16_weights=False,
threshold=threshold,
)
return model
この関数は、与えられたモデルのすべてのnn.Linear
レイヤーを再帰的に置換し、meta
デバイスで初期化されたLinear8bitLt
モジュールに置き換えます。has_fp16_weights
属性は、量子化統計と一緒にint8
で重みを直接ロードするためにFalse
に設定する必要があります。
また、いくつかのモジュール(ここではlm_head
)の置換を破棄します。これにより、より正確かつ安定した結果を得るために、最新のモジュールをネイティブの精度で保持したいからです。
しかし、まだ終わりではありません!上記の関数はinit_empty_weights
コンテキストマネージャの下で実行されるため、新しいモデルはまだmeta
デバイス上にあります。このコンテキストマネージャで初期化されたモデルでは、accelerate
が各モジュールのパラメータを手動でロードし、正しいデバイスに移動します。bitsandbytes
では、Linear8bitLt
モジュールのデバイスの設定が重要なステップです(興味がある場合は、ここでコードの断片を確認できます)。これは、おもちゃのスクリプトで見たようにです。
ここでは、2回呼び出すと量子化ステップが失敗します。 accelerate
の set_module_tensor_to_device
関数(set_module_8bit_tensor_to_device
と呼ばれる)の実装を考え出さなければなりませんでした。これにより、2回呼び出さないようにします。以下のセクションで詳しく説明しましょう!
accelerate
でデバイスを設定する際に非常に注意が必要です
ここでは、accelerate
ライブラリと非常に微妙なバランスを取りました!モデルをロードし、正しいデバイスに設定した後でも、場合によってはまだ set_module_tensor_to_device
を呼び出してモデルをすべてのデバイスにフックする必要があります。これは accelerate
の dispatch_model
関数内で行われます。これは複数回 .to
を呼び出す可能性があり、これを避けたいと考えています。私たちが望んでいたことを達成するためには、2つのプルリクエストが必要でした!最初のプルリクエストはいくつかのテストを破壊しましたが、このプルリクエストではすべて修正することができました!
すべてをまとめる
したがって、究極のレシピは次のとおりです:
- 正しいモジュールを持つ
meta
デバイスでモデルを初期化します - パラメータを正しいGPUデバイスに1つずつ設定し、この手順を二度実行しないようにします!
- 新しいキーワード引数をすべての適切な場所に追加し、いくつかの素敵なドキュメントを追加します
- 非常に詳細なテストを追加します!詳細については、こちらのテストをチェックしてください。これは非常に簡単に聞こえるかもしれませんが、私たちは一緒に多くの困難なデバッグセッションを経験しました。これには、CUDAカーネルを含むことが多かったです!
言いたいことはすべて言いましたが、この統合の冒険は非常に楽しかったです。さまざまなライブラリでの深いダイビングや「手術」を行い、すべてを整列させて動作させることができました!
さて、この統合をどのように活用し、transformers
で成功裏に使用するかを見てみましょう!
transformers
での使用方法
ハードウェアの要件
8ビットテンソルコアはCPUではサポートされていません。bitsandbytesは8ビットテンソルコアをサポートするハードウェアで実行できます。これにはTuringおよびAmpere GPU(RTX 20、RTX 30、A40-A100、T4+)が含まれます。たとえば、Google ColabのGPUは通常、NVIDIA T4 GPUです。彼らの最新世代のGPUは8ビットテンソルコアをサポートしています。私たちのデモはGoogle Colabを基にしているので、以下でチェックしてください!
インストール方法
以下のコマンドを使用して、ライブラリの最新バージョンをインストールし、以下のコマンドを実行して試してみてください(python>=3.8を使用していることを確認してください)
pip install accelerate
pip install bitsandbytes
pip install git+https://github.com/huggingface/transformers.git
デモの例 – Google ColabでT5 11Bを実行する
8ビットモデルをBLOOM-3Bモデルで実行するためのGoogle Colabデモをチェックしてください!
以下はT5-11Bを実行するデモです。T5-11BモデルのチェックポイントはFP32で、メモリを42GB使用し、Google Colabには収まりません。8ビットモジュールを使用すると、11GBしか使用せず、簡単に収まります:
または、BLOOM-3Bのデモです:
改善の余地
このアプローチは、私たちの意見では非常に大きなモデルへのアクセスを大幅に向上させます。パフォーマンスの低下なしに、以前はアクセスできなかったモデルにアクセスできるようにします。大きなモデルには既に対応しており、さらなる改善の余地があります!
より小さいモデルの高速推論速度
ベンチマークセクションで見たように、小さなモデル(<=6Bパラメータ)のランタイム速度をほぼ2倍に改善することができました。ただし、BLOOM-176Bのような大きなモデルの推論速度は堅牢ですが、小さなモデルにはまだ改善の余地があります。問題点は既に特定されており、fp16と同じパフォーマンスを回復したり、わずかな高速化を得ることができるでしょう。これらの変更は、今後数週間以内に統合される予定です。
Kepler GPUs(GTX 1080など)への対応
過去4年間のすべてのGPUに対応していますが、GTX 1080などの一部の古いGPUはまだ広く使用されています。これらのGPUにはInt8テンソルコアはありませんが、Int8ベクトルユニット(一種の「弱い」テンソルコア)があります。そのため、これらのGPUでもInt8の高速化が可能です。ただし、高速推論のためには完全に異なるソフトウェアスタックが必要です。私たちはKepler GPUへのサポートを統合し、LLM.int8()の機能をより広く利用できるようにする予定ですが、その複雑さのために時間がかかるでしょう。
Hub上で8ビットステート辞書を保存する
8ビットステート辞書は現在、ハブにプッシュされた後に直接8ビットモデルにロードすることはできません。これは、モデルが計算する統計情報(weight.CB
およびweight.SCB
)が現在のところステート辞書に格納されず、またLinear8bitLt
モジュールがこの機能をサポートしていないためです。それを保存し、ハブにプッシュできる能力があると、よりアクセスしやすくなる可能性があると考えています。
CPUのサポート
CPUデバイスは8ビットコアをサポートしていません。このモジュールをCPU上で実行することは、使いやすさとアクセシビリティを大幅に向上させる可能性があります。
他のモダリティのスケーリングアップ
現在、言語モデルが非常に大規模なモデルを主導しています。この手法を非常に大規模なビジョン、音声、およびマルチモーダルモデルに活用することは、これらのモデルがよりアクセスしやすくなる来年以降において興味深いことです。
クレジット
この記事の可読性を向上させるために貢献してくれた、およびtransformers
の統合手順に貢献した以下の方々に大きな感謝を申し上げます(アルファベット順):JustHeuristic(Yozh)、Michael Benayoun、Stas Bekman、Steven Liu、Sylvain Gugger、Tim Dettmers
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles