Hugging Face Transformersでより高速なTensorFlowモデル
Hugging Face Transformersには、高速なTensorFlowモデルがあります
ここ数か月、Hugging FaceチームはTransformersのTensorFlowモデルの改良に取り組んできました。目標はより堅牢で高速なモデルを実現することです。最近の改良は主に次の2つの側面に焦点を当てています:
- 計算パフォーマンス:BERT、RoBERTa、ELECTRA、MPNetの計算時間を大幅に短縮するための改良が行われました。この計算パフォーマンスの向上は、グラフ/イージャーモード、TF Serving、CPU/GPU/TPUデバイスのすべての計算アスペクトで顕著に見られます。
- TensorFlow Serving:これらのTensorFlowモデルは、TensorFlow Servingを使用して展開することができ、推論においてこの計算パフォーマンスの向上を享受することができます。
計算パフォーマンス
計算パフォーマンスの向上を実証するために、v4.2.0のTensorFlow ServingとGoogleの公式実装との間でBERTのパフォーマンスを比較するベンチマークを実施しました。このベンチマークは、GPU V100上でシーケンス長128を使用して実行されました(時間はミリ秒単位で表示されます):
v4.2.0の現行のBertの実装は、Googleの実装よりも最大で約10%高速です。また、4.1.1リリースの実装よりも2倍高速です。
- PyTorch / XLA TPUsでのHugging Face
- Huggingface TransformersとRayを使用した検索増強生成
- シンプルな人々が派手なニューラルネットワークを構築するための簡単な考慮事項
TensorFlow Serving
前のセクションでは、Transformersの最新バージョンでブランドニューのBertモデルが計算パフォーマンスが劇的に向上したことを示しました。このセクションでは、製品環境で計算パフォーマンスの向上を活用するために、TensorFlow Servingを使用してBertモデルを展開する手順をステップバイステップで説明します。
TensorFlow Servingとは何ですか?
TensorFlow Servingは、モデルをサーバーに展開するタスクをこれまで以上に簡単にするTensorFlow Extended(TFX)が提供するツールの一部です。TensorFlow Servingには、HTTPリクエストを使用して呼び出すことができるAPIと、サーバー上で推論を実行するためにgRPCを使用するAPIの2つがあります。
SavedModelとは何ですか?
SavedModelには、ウェイトとアーキテクチャを含むスタンドアロンのTensorFlowモデルが含まれています。SavedModelは、モデルの元のソースを実行する必要がないため、Java、Go、C++、JavaScriptなどのSavedModelを読み込むバックエンドをサポートするすべてのバックエンドと共有または展開するために役立ちます。SavedModelの内部構造は次のように表されます:
savedmodel
/assets
-> モデルに必要なアセット(ある場合)
/variables
-> 重みを含むモデルのチェックポイントがここにあります
saved_model.pb -> モデルグラフを表すprotobufファイル
TensorFlow Servingのインストール方法
TensorFlow Servingのインストールと使用方法は3つあります:
- Dockerコンテナを介して
- aptパッケージを介して
- またはpipを使用します。
すべての既存のOSとの互換性を保ちながら、このチュートリアルではDockerを使用します。
SavedModelの作成方法
SavedModelはTensorFlow Servingが期待する形式です。Transformers v4.2.0以降、SavedModelの作成には以下の3つの追加機能があります:
- シーケンス長は実行ごとに自由に変更できます。
- すべてのモデル入力は推論に使用できます。
hidden states
またはattention
をoutput_hidden_states=True
またはoutput_attentions=True
で返す場合、それらは1つの出力にグループ化されます。
以下は、TensorFlow SavedModelとして保存されたTFBertForSequenceClassification
の入力と出力の表現です:
与えられたSavedModel SignatureDefには、次の入力が含まれています:
inputs['attention_mask'] tensor_info:
dtype: DT_INT32
shape: (-1, -1)
name: serving_default_attention_mask:0
inputs['input_ids'] tensor_info:
dtype: DT_INT32
shape: (-1, -1)
name: serving_default_input_ids:0
inputs['token_type_ids'] tensor_info:
dtype: DT_INT32
shape: (-1, -1)
name: serving_default_token_type_ids:0
与えられたSavedModel SignatureDefには、次の出力が含まれています:
outputs['attentions'] tensor_info:
dtype: DT_FLOAT
shape: (12, -1, 12, -1, -1)
name: StatefulPartitionedCall:0
outputs['logits'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 2)
name: StatefulPartitionedCall:1
メソッド名は:tensorflow/serving/predict
入力として input_ids
(トークンのID)ではなく、inputs_embeds
(トークンの埋め込み)を直接渡すには、モデルに新しいサービングシグネチャを持たせるために、モデルのサブクラスを作成する必要があります。次のコードの断片は、その方法を示しています:
from transformers import TFBertForSequenceClassification
import tensorflow as tf
# 新しいサービングシグネチャを定義するために、サブクラスを作成する
class MyOwnModel(TFBertForSequenceClassification):
# サービングメソッドに新しい input_signature を指定することで、新しいシグネチャを装飾する
# input_signature は、期待される入力の名前、データ型、形状を表す
@tf.function(input_signature=[{
"inputs_embeds": tf.TensorSpec((None, None, 768), tf.float32, name="inputs_embeds"),
"attention_mask": tf.TensorSpec((None, None), tf.int32, name="attention_mask"),
"token_type_ids": tf.TensorSpec((None, None), tf.int32, name="token_type_ids"),
}])
def serving(self, inputs):
# 入力を処理するためにモデルを呼び出す
output = self.call(inputs)
# フォーマットされた出力を返す
return self.serving_output(output)
# 新しいサービングメソッドを持つモデルのインスタンスを作成する
model = MyOwnModel.from_pretrained("bert-base-cased")
# saved_model=True を指定して保存すると、h5 の重みと一緒に SavedModel バージョンが作成されます
model.save_pretrained("my_model", saved_model=True)
サービングメソッドは、tf.function
デコレータの新しい input_signature
引数によって上書きする必要があります。この引数については、公式ドキュメントを参照してください。serving
メソッドは、TensorFlow Serving で展開されたときに SavedModel の振る舞いを定義するために使用されます。SavedModel は、新しい inputs_embeds
入力が含まれていることを確認してください:
与えられた SavedModel SignatureDef には、次の入力が含まれています:
inputs['attention_mask'] tensor_info:
dtype: DT_INT32
shape: (-1, -1)
name: serving_default_attention_mask:0
inputs['inputs_embeds'] tensor_info:
dtype: DT_FLOAT
shape: (-1, -1, 768)
name: serving_default_inputs_embeds:0
inputs['token_type_ids'] tensor_info:
dtype: DT_INT32
shape: (-1, -1)
name: serving_default_token_type_ids:0
与えられた SavedModel SignatureDef には、次の出力が含まれています:
outputs['attentions'] tensor_info:
dtype: DT_FLOAT
shape: (12, -1, 12, -1, -1)
name: StatefulPartitionedCall:0
outputs['logits'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 2)
name: StatefulPartitionedCall:1
メソッド名は: tensorflow/serving/predict
SavedModel の展開と使用方法
感情分類のための BERT モデルを展開して使用する手順を一つ一つ見ていきましょう。
ステップ 1
SavedModel を作成します。Transformers ライブラリを使用して、IMDB データセットでトレーニングされた nateraw/bert-base-uncased-imdb
という名前の PyTorch モデルをロードし、TensorFlow Keras モデルに変換することで、SavedModel を作成できます:
from transformers import TFBertForSequenceClassification
model = TFBertForSequenceClassification.from_pretrained("nateraw/bert-base-uncased-imdb", from_pt=True)
# saved_model パラメータは、h5 の重みと同時にモデルの SavedModel バージョンを作成するフラグです
model.save_pretrained("my_model", saved_model=True)
ステップ 2
SavedModel を使用した Docker コンテナを作成して実行します。まず、CPU 用の TensorFlow Serving Docker イメージを取得します(GPU の場合は、serving を serving:latest-gpu に置き換えてください):
docker pull tensorflow/serving
次に、serving_base という名前のデーモンとして serving イメージを実行します:
docker run -d --name serving_base tensorflow/serving
新しく作成した SavedModel を serving_base コンテナの models フォルダにコピーします:
docker cp my_model/saved_model serving_base:/models/bert
モデルを提供するコンテナをコミットして、MODEL_NAMEをモデルの名前(ここではbert
)に変更します。名前(bert
)はSavedModelに付ける名前に対応しています:
docker commit --change "ENV MODEL_NAME bert" serving_base my_bert_model
そして、デーモンとして実行されているserving_baseイメージを停止します。これ以上必要ありません:
docker kill serving_base
最後に、SavedModelをデーモンとして提供するためにイメージを実行し、コンテナ内のポート8501(REST API)と8500(gRPC API)をホストにマッピングし、コンテナの名前をbert
とします。
docker run -d -p 8501:8501 -p 8500:8500 --name bert my_bert_model
ステップ3
REST APIを介してモデルにクエリを送信します:
from transformers import BertTokenizerFast, BertConfig
import requests
import json
import numpy as np
sentence = "I love the new TensorFlow update in transformers."
# SavedModelの対応するトークナイザーをロードします
tokenizer = BertTokenizerFast.from_pretrained("nateraw/bert-base-uncased-imdb")
# SavedModelのモデル設定をロードします
config = BertConfig.from_pretrained("nateraw/bert-base-uncased-imdb")
# 文をトークナイズします
batch = tokenizer(sentence)
# バッチを正しい辞書形式に変換します
batch = dict(batch)
# 1つの要素からなるリストに例を追加します(バッチサイズに対応)
batch = [batch]
# REST APIに渡すための、例を宣言するキーinstancesを含むJSONを作成します
input_data = {"instances": batch}
# REST APIにクエリを送信します。パスはhttp://host:port/model_version/models_root_folder/model_name:methodに対応します
r = requests.post("http://localhost:8501/v1/models/bert:predict", data=json.dumps(input_data))
# JSONの結果をパースします。結果は「predictions」というルートキーを持つリストに含まれています。
# 1つの例しかないので、リストの最初の要素を取得します
result = json.loads(r.text)["predictions"][0]
# 返される結果は確率であり、正または負の値になります。そのため、絶対値を取得します
abs_scores = np.abs(result)
# 最大確率のインデックスを取得します
label_id = np.argmax(abs_scores)
# インデックスに対応する正しいLABELを表示します
print(config.id2label[label_id])
これにより、POSITIVEが返されるはずです。同じ結果を得るために、gRPC(Google Remote Procedure Call)APIを使用することもできます:
from transformers import BertTokenizerFast, BertConfig
import numpy as np
import tensorflow as tf
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
import grpc
sentence = "I love the new TensorFlow update in transformers."
tokenizer = BertTokenizerFast.from_pretrained("nateraw/bert-base-uncased-imdb")
config = BertConfig.from_pretrained("nateraw/bert-base-uncased-imdb")
# TensorFlowのテンソルとして出力されたバッチサイズ1の出力と共に文をトークナイズします。例:
# {
# 'input_ids': <tf.Tensor: shape=(1, 3), dtype=int32, numpy=array([[ 101, 19082, 102]])>,
# 'token_type_ids': <tf.Tensor: shape=(1, 3), dtype=int32, numpy=array([[0, 0, 0]])>,
# 'attention_mask': <tf.Tensor: shape=(1, 3), dtype=int32, numpy=array([[1, 1, 1]])>
# }
batch = tokenizer(sentence, return_tensors="tf")
# コンテナのgRPCポートに接続されるチャネルを作成します
channel = grpc.insecure_channel("localhost:8500")
# 予測のために使用されるスタブを作成します。このスタブはTFサーバーにgRPCリクエストを送信するために使用されます。
stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
# 予測のためのgRPCリクエストを作成します
request = predict_pb2.PredictRequest()
# モデルの名前を設定します(この場合はbert)
request.model_spec.name = "bert"
# gRPCクエリのフォーマットに使用されるシグネチャ名を設定します(デフォルト)
request.model_spec.signature_name = "serving_default"
# 入力_idsの入力をトークナイザーによって与えられた入力_idsから設定します
# tf.make_tensor_protoはTensorFlowテンソルをProtobufテンソルに変換します
request.inputs["input_ids"].CopyFrom(tf.make_tensor_proto(batch["input_ids"]))
# attention_maskも同様に設定します
request.inputs["attention_mask"].CopyFrom(tf.make_tensor_proto(batch["attention_mask"]))
# token_type_idsも同様に設定します
request.inputs["token_type_ids"].CopyFrom(tf.make_tensor_proto(batch["token_type_ids"]))
# gRPCリクエストをTFサーバーに送信します
result = stub.Predict(request)
# 出力は、唯一の出力である確率のリストであるProtobufです。
# 確率はfloatなので、リストは.float_valで浮動小数点数のNumPy配列に変換されます
output = result.outputs["logits"].float_val
# インデックスに対応する正しいLABELを表示します
print(config.id2label[np.argmax(np.abs(output))])
結論
transformersに適用された最新のTensorFlowモデルの更新のおかげで、TensorFlow Servingを使用してモデルを簡単に本番環境にデプロイすることができます。次に考えているステップの一つは、前処理部分をSavedModel内に直接統合することで、さらに簡単にすることです。
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