Transformer を用いた双方向エンコーダ表現(Bidirectional Encoder Representations from Transformers:BERT)は、事前学習済みの言語表現モデルです。BERT モデルは自然言語処理(Natural Language Processing:NLP)分野における画期的な進展を示し、多くの NLP タスクで最先端の結果を達成しています。ただし、BERT モデルは多数のパラメーターを持ち、計算コストが高いため、本番環境での最適化が強く求められます。本トピックでは、TensorFlow を使用して学習した BERT モデルを Blade で最適化する手順について説明します。
制限事項
本プロシージャを実行するには、以下の環境バージョンが必要です:
システム環境:Python 3.6 以降および CUDA 10.0 を搭載した Linux。
フレームワーク:TensorFlow 1.15。
推論最適化ツール:Blade 3.16.0 以降。
操作手順
Blade を使用して BERT モデルを最適化するには、以下の手順に従います:
モデルをダウンロードし、
tokenizersライブラリを使用してテストデータを準備します。blade.optimizeインターフェイスを呼び出してモデルを最適化し、最適化済みモデルを保存します。最適化前後の推論速度および結果をテストし、最適化レポートに記載された情報を検証します。
Blade ソフトウェア開発キット(SDK)を統合して、最適化済みモデルを読み込み、推論を実行します。
ステップ 1:事前準備
トークナイザーライブラリをインストールするには、以下のコマンドを実行します。
pip3 install tokenizersモデルをダウンロードし、指定したディレクトリに解凍します。
wget http://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/tutorials/bert_example/nlu_general_news_classification_base.tar.gz mkdir nlu_general_news_classification_base tar zxvf nlu_general_news_classification_base.tar.gz -C nlu_general_news_classification_baseTensorFlow が提供する
saved_model_cliコマンドを使用して、モデルの基本情報を表示します。saved_model_cli show --dir nlu_general_news_classification_base --all以下のような出力が返されます。
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs: signature_def['serving_default']: The given SavedModel SignatureDef contains the following input(s): inputs['input_ids'] tensor_info: dtype: DT_INT32 shape: (-1, -1) name: input_ids:0 inputs['input_mask'] tensor_info: dtype: DT_INT32 shape: (-1, -1) name: input_mask:0 inputs['segment_ids'] tensor_info: dtype: DT_INT32 shape: (-1, -1) name: segment_ids:0 The given SavedModel SignatureDef contains the following output(s): outputs['logits'] tensor_info: dtype: DT_FLOAT shape: (-1, 28) name: app/ez_dense/BiasAdd:0 outputs['predictions'] tensor_info: dtype: DT_INT32 shape: (-1) name: ArgMax:0 outputs['probabilities'] tensor_info: dtype: DT_FLOAT shape: (-1, 28) name: Softmax:0 Method name is: tensorflow/serving/predict出力から、ニューステキスト分類モデルには、
input_ids:0、input_mask:0、segment_ids:0の 3 つの入力テンソルがあることがわかります。また、logits、predictions、probabilitiesの 3 つの出力テンソルがあります。ArgMax:0はpredictionsの下位ノードであり、最終的な分類カテゴリ(つまり、主要な推論結果)を表します。tokenizersを呼び出してテストデータを準備します。from tokenizers import BertWordPieceTokenizer # モデルディレクトリ内の vocab.txt ファイルからトークナイザーを初期化します。 tokenizer = BertWordPieceTokenizer('./nlu_general_news_classification_base/vocab.txt') # 4 件のニュース記事を 1 つのバッチとしてエンコーディングします。 news = [ 'メキシコが感染者数 1,000 人超を受けて保健緊急事態を宣言。中国新聞網、3 月 31 日 — 総合報道によると、メキシコの新型コロナウイルス感染症(COVID-19)感染者数は 1,000 人を超えました。3 月 30 日、メキシコ政府は保健緊急事態を宣言し、パンデミックの拡大を抑えるための対策を強化しました。', '国家統計局のデータによると、8 月の中国製造業購買担当者景気指数(PMI)は 50.1 % で、しきい値を上回りましたが、前月比で 0.3 パーセントポイント低下しました。', 'UTC + 08:00、8 月 31 日 — 開催中の東京パラリンピック男子ブラインドサッカーのグループステージ最終戦で、中国代表チームはホスト国である日本代表チームを朱瑞明選手のハットトリックにより 2 対 0 で破り、2 勝 1 敗の成績で準決勝進出を決めました。', '8 月 30 日現在、「祝融号」火星探査車は火星表面に到着してから 100 日目を迎えました。この 100 日間で「祝融号」は着陸地点から南へ累積 1064 メートル移動し、6 基の科学観測機器を搭載して約 10 GB の生の科学データを取得しました。' ] tokenized = tokenizer.encode_batch(news) # シーケンス長を 128 にパディングします。 def pad(seq, seq_len, padding_val): return seq + [padding_val] * (seq_len - len(seq)) input_ids = [pad(tok.ids, 128, 0) for tok in tokenized] segment_ids = [pad(tok.type_ids, 128, 0) for tok in tokenized] input_mask = [ pad([1] * len(tok.ids), 128, 0) for tok in tokenized ] # 最終的なテストデータは TensorFlow FeedDict 形式です。 test_data = { "input_ids:0": input_ids, "segment_ids:0": segment_ids, "input_mask:0": input_mask, }モデルを読み込み、テストデータを使用して推論を実行します。
import tensorflow.compat.v1 as tf import json # ラベルマッピングファイルを読み込んで、出力整数に対応するカテゴリ名を取得します。 with open('./nlu_general_news_classification_base/label_mapping.json') as f: MAPPING = {v: k for k, v in json.load(f).items()} # モデルを読み込み、実行します。 cfg = tf.ConfigProto() cfg.gpu_options.allow_growth = True with tf.Session(config=cfg) as sess: tf.saved_model.loader.load(sess, ['serve'], './nlu_general_news_classification_base') result = sess.run('ArgMax:0', test_data) print([MAPPING[r] for r in result])以下のような推論結果が期待通りに返されます。
['国際', '金融', 'スポーツ', '科学技術']
ステップ 2:Blade を呼び出してモデルを最適化
blade.optimizeインターフェイスを呼び出してモデルを最適化します(以下に例を示します)。このインターフェイスの詳細については、「Python インターフェイスドキュメント」をご参照ください。import blade saved_model_dir = 'nlu_general_news_classification_base' optimized_model, _, report = blade.optimize( saved_model_dir, # モデルのパス。 'o1', # O1 損失なし最適化。 device_type='gpu', # GPU デバイス向けに最適化。 test_data=[test_data] # テストデータ。 )モデルを最適化する際は、以下の点にご注意ください:
blade.optimizeの最初の戻り値は最適化済みモデルです。そのデータ型は入力モデルと同じです。本例では入力が SavedModel のパスであるため、戻り値も最適化済み SavedModel のパスとなります。inputsおよびoutputsパラメーターを指定しないでください。Blade が自動的に入力および出力ノードを推定します。
最適化が完了したら、最適化レポートを出力します。
print("Report: {}".format(report))最適化レポートは、以下のような出力と似ています。
Report: { "software_context": [ { "software": "tensorflow", "version": "1.15.0" }, { "software": "cuda", "version": "10.0.0" } ], "hardware_context": { "device_type": "gpu", "microarchitecture": "T4" }, "user_config": "", "diagnosis": { "model": "nlu_general_news_classification_base", "test_data_source": "user provided", "shape_variation": "dynamic", "message": "", "test_data_info": "input_ids:0 shape: (4, 128) data type: int64\nsegment_ids:0 shape: (4, 128) data type: int64\ninput_mask:0 shape: (4, 128) data type: int64" }, "optimizations": [ { "name": "TfStripUnusedNodes", "status": "effective", "speedup": "na", "pre_run": "na", "post_run": "na" }, { "name": "TfStripDebugOps", "status": "effective", "speedup": "na", "pre_run": "na", "post_run": "na" }, { "name": "TfAutoMixedPrecisionGpu", "status": "effective", "speedup": "1.46", "pre_run": "35.04 ms", "post_run": "24.02 ms" }, { "name": "TfAicompilerGpu", "status": "effective", "speedup": "2.43", "pre_run": "23.99 ms", "post_run": "9.87 ms" } ], "overall": { "baseline": "35.01 ms", "optimized": "9.90 ms", "speedup": "3.54" }, "model_info": { "input_format": "saved_model" }, "compatibility_list": [ { "device_type": "gpu", "microarchitecture": "T4" } ], "model_sdk": {} }最適化レポートから、本例では
TfAutoMixedPrecisionGpuおよびTfAicompilerGpuの最適化が適用されたことがわかります。これらを組み合わせることで、推論時間は 35 ms から 9.9 ms へと 3.54 倍の高速化が達成されています。この結果は本例のみのものであり、実際の最適化効果はお客様の環境によって異なります。最適化レポート内の各フィールドの詳細については、「最適化レポート」をご参照ください。optimized_modelのパスを出力します。print("Optimized model: {}".format(optimized_model))システムからは、以下のような出力が返されます。
Optimized model: /root/nlu_general_news_classification_base_blade_opt_20210901141823/nlu_general_news_classification_base出力から、最適化済みモデルが新しいパスに保存されていることがわかります。
ステップ 3:パフォーマンスと正確性の検証
最適化後は、Python スクリプトを使用して最適化レポートの内容を検証できます。
benchmarkメソッドを定義します。このメソッドは、モデルを 10 回ウォームアップし、1,000 回実行して平均推論時間を算出します。import time def benchmark(model, test_data): tf.reset_default_graph() with tf.Session() as sess: sess.graph.as_default() tf.saved_model.loader.load(sess, ['serve'], model) # ウォームアップ。 for i in range(0, 10): result = sess.run('ArgMax:0', test_data) # ベンチマーク。 num_runs = 1000 start = time.time() for i in range(0, num_runs): result = sess.run('ArgMax:0', test_data) elapsed = time.time() - start rt_ms = elapsed / num_runs * 1000.0 # 結果を表示します。 print("Latency of model: {:.2f} ms.".format(rt_ms)) print("Predict result: {}".format([MAPPING[r] for r in result]))benchmarkメソッドを呼び出して、元のモデルを検証します。benchmark('nlu_general_news_classification_base', test_data)システムからは、以下のような出力が返されます。
Latency of model: 36.20 ms. Predict result: ['International', 'Finance', 'Sports', 'Science']結果より、推論時間が 36.20 ms であり、最適化レポートの「overall」セクションに記載された
"baseline": "35.01 ms"と一致していることが確認できます。予測結果['International', 'Finance', 'Sports', 'Science']も期待通りです。ここで得られた推論時間は本ケーススタディのみのものであり、お客様のモデルの実際の推論時間は異なる場合があります。benchmarkメソッドを呼び出して、最適化済みモデルを検証します。import os os.environ['TAO_COMPILATION_MODE_ASYNC'] = '0' benchmark(optimized_model, test_data)最適化レポートには AICompiler を使用した最適化が記載されています。AICompiler は非同期でコンパイルを行うため、コンパイル中は依然として元のモデルが推論に使用されます。正確なベンチマーク結果を得るためには、
TAO_COMPILATION_MODE_ASYNC=0環境変数を設定して、benchmarkを呼び出す前に同期コンパイルを強制してください。システムからは、以下のような出力が返されます。
Latency of model: 9.87 ms. Predict result: ['International', 'Finance', 'Sports', 'Science']結果より、推論時間が 9.87 ms であり、最適化レポートの「overall」セクションに記載された
"optimized": "9.90 ms"と一致していることが確認できます。予測結果['International', 'Finance', 'Sports', 'Science']も期待通りです。ここで得られた推論時間は本ケーススタディのみのものであり、お客様のモデルの実際の推論時間は異なる場合があります。
ステップ 4:最適化済みモデルの読み込みと実行
モデルの検証が完了したら、本番環境へのデプロイが可能です。Blade では、Python および C++ 向けのランタイム SDK が提供されており、これらを統合することでデプロイが可能です。C++ SDK の使用方法については、「SDK を使用した TensorFlow モデルの推論デプロイ」をご参照ください。本セクションでは、Python SDK を使用したデプロイ方法について説明します。
任意: 試用期間中は、認証失敗によるプログラムの予期せぬ終了を防ぐために、以下の環境変数設定を追加してください。
export BLADE_AUTH_USE_COUNTING=1PAI-Blade の利用認証を行います。
export BLADE_REGION=<region> export BLADE_TOKEN=<token>以下のパラメーターを、お客様のビジネス要件に応じて設定してください:
<region>:PAI-Blade を利用するリージョン。PAI-Blade ユーザーの DingTalk グループに参加すると、PAI-Blade の利用可能なリージョンを確認できます。DingTalk グループの QR コードについては、「PAI-Blade のインストール」をご参照ください。
<token>:PAI-Blade を利用するために必要な認証トークン。PAI-Blade ユーザーの DingTalk グループに参加すると、認証トークンを取得できます。DingTalk グループの QR コードについては、「PAI-Blade のインストール」をご参照ください。
最適化済みモデルを読み込み、実行します。
import blade.runtime.tensorflowの行を追加します。Blade の統合には、これ以外のコード変更は不要です。元の推論コードを変更する必要はありません。import tensorflow.compat.v1 as tf import blade.runtime.tensorflow # <your_optimized_model_path> を最適化済みモデルのパスに置き換えます。 savedmodel_dir = <your_optimized_model_path> # <your_infer_data> を推論用のデータに置き換えます。 infer_data = <your_infer_data> with tf.Session() as sess: sess.graph.as_default() tf.saved_model.loader.load(sess, ['serve'], savedmodel_dir) result = sess.run('ArgMax:0', infer_data)