Hugging Face Transformersでより高速なTensorFlowモデル

Hugging Face Transformersには、高速なTensorFlowモデルがあります

ここ数か月、Hugging FaceチームはTransformersのTensorFlowモデルの改良に取り組んできました。目標はより堅牢で高速なモデルを実現することです。最近の改良は主に次の2つの側面に焦点を当てています:

  1. 計算パフォーマンス:BERT、RoBERTa、ELECTRA、MPNetの計算時間を大幅に短縮するための改良が行われました。この計算パフォーマンスの向上は、グラフ/イージャーモード、TF Serving、CPU/GPU/TPUデバイスのすべての計算アスペクトで顕著に見られます。
  2. 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倍高速です。

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つの追加機能があります:

  1. シーケンス長は実行ごとに自由に変更できます。
  2. すべてのモデル入力は推論に使用できます。
  3. hidden statesまたはattentionoutput_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!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

人工知能

「Zenの共同創設者兼CTO、イオン・アレクサンドル・セカラ氏によるインタビューシリーズ」

創業者兼CTOであるIon-Alexandru Secaraは、Zen(PostureHealth Inc.)の開発を牽引しており、画期的な姿勢矯正ソフトウェア...

AIニュース

Q&A:ブラジルの政治、アマゾンの人権、AIについてのGabriela Sá Pessoaの見解

ブラジルの社会正義のジャーナリストは、MIT国際研究センターのフェローです

AIテクノロジー

「LXTのテクノロジーバイスプレジデント、アムル・ヌール・エルディン - インタビューシリーズ」

アムル・ヌール・エルディンは、LXTのテクノロジー担当副社長ですアムルは、自動音声認識(ASR)の文脈での音声/音響処理と機...

機械学習

「機械学習 vs AI vs ディープラーニング vs ニューラルネットワーク:違いは何ですか?」

テクノロジーの急速な進化は、ビジネスが効率化のために洗練されたアルゴリズムにますます頼ることで、私たちの日常生活を形...

人工知能

「ナレ・ヴァンダニャン、Ntropyの共同創設者兼CEO- インタビューシリーズ」

Ntropyの共同創設者兼CEOであるナレ・ヴァンダニアンは、開発者が100ミリ秒未満で超人的な精度で金融取引を解析することを可...

データサイエンス

「David Smith、TheVentureCityの最高データオフィサー- インタビューシリーズ」

デビッド・スミス(別名「デビッド・データ」)は、TheVentureCityのチーフデータオフィサーであり、ソフトウェア駆動型のス...