Batchサイズの変更

複数の推論リクエストをグループ化してバッチ処理する事で、高いスループットを実現する事が可能です。グループ化された各推論リクエストに対して必要となるパラーメータは外部メモリへのアクセス無しで再利用されます。 Neuronコア Batchingによる最適化を行うためにはコンパイル時にバッチサイズを指定する必要があります。

Batchingにはレイテンシのコストを払うことになるため、最適なバッチサイズ(最適なレイテンシとスループットの組み合わせ)はユーザによるトライアルエラーにより決定されます。

Step 1. コンパイル用 Python スクリプトを作成

以下の内容でcompile_resnet50_batch.py というファイル名の Python スクリプトを作成します。

import shutil
import tensorflow.neuron as tfn

model_dir = 'resnet50'

for batch_size in [1, 2, 4, 8, 16]:

# Prepare export directory (old one removed)
    compiled_model_dir = 'resnet50_neuron_batch' + str(batch_size)
    shutil.rmtree(compiled_model_dir, ignore_errors=True)

# Compile using Neuron
    tfn.saved_model.compile(model_dir, compiled_model_dir, batch_size=batch_size, dynamic_batch_size=True)

Step 2. コンパイル用スクリプトを実行

コンパイルスクリプトを実行します。ここでは batch サイズ、1, 2, 4, 8, 16のモデルを用意します。inf1.2xlarge では計~8分程度かかります。

python compile_resnet50_batch.py

Step 3. 推論実行 Python スクリプトを作成

以下の内容でinfer_resnet50_batch.py というファイル名の推論実行 Python スクリプトを作成します。章4.1、章4.2で使用したCPU向けモデル、本章Step 2で用意した各Batchサイズ毎のコンパイル済みモデルを利用します。

import os
import time
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing import image
from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input, decode_predictions
from concurrent import futures

# added for utilizing 4 neuron cores
os.environ['NEURON_RT_VISIBLE_CORES'] = '0-3'

# Load model
model_dir = 'resnet50'
predictor_cpu = tf.contrib.predictor.from_saved_model(model_dir)

# measure the performance per batch size
for batch_size in [1, 2, 4, 8, 16]:
    USER_BATCH_SIZE = batch_size
    print("batch_size: {}, USER_BATCH_SIZE: {}". format(batch_size, USER_BATCH_SIZE))

# Load model
    compiled_model_dir = 'resnet50_neuron_batch' + str(batch_size)
    predictor_inferentia = tf.contrib.predictor.from_saved_model(compiled_model_dir)

# Create input from image
    img_sgl = image.load_img('kitten_small.jpg', target_size=(224, 224))
    img_arr = image.img_to_array(img_sgl)
    img_arr2 = np.expand_dims(img_arr, axis=0)
    img_arr3 = preprocess_input(np.repeat(img_arr2, USER_BATCH_SIZE, axis=0))

    model_feed_dict={'input': img_arr3}

# warmup
    infa_rslts = predictor_cpu(model_feed_dict) 
    infa_rslts = predictor_inferentia(model_feed_dict) 

    num_loops = 100
    num_inferences = num_loops * USER_BATCH_SIZE

# Run inference on CPUs, Display results
    start = time.time()
    with futures.ThreadPoolExecutor(8) as exe:
        fut_list = []
        for i in range (1, num_loops + 1):
            fut = exe.submit(predictor_cpu, model_feed_dict)
            fut_list.append(fut)
        for fut in fut_list:
            infa_rslts = fut.result()
    elapsed_time = time.time() - start

    print('By CPU         - num_inferences:{:>6}[images], elapsed_time:{:6.2f}[sec], Throughput:{:8.2f}[images/sec]'.format(num_inferences, elapsed_time, num_inferences / elapsed_time))

# Run inference on Neuron Cores, Display results
    start = time.time()
    with futures.ThreadPoolExecutor(8) as exe:
        fut_list = []
        for i in range (1, num_loops + 1):
            fut = exe.submit(predictor_inferentia, model_feed_dict)
            fut_list.append(fut)
        for fut in fut_list:
            infa_rslts = fut.result()
    elapsed_time = time.time() - start

    print('By Neuron Core - num_inferences:{:>6}[images], elapsed_time:{:6.2f}[sec], Throughput:{:8.2f}[images/sec]'.format(num_inferences, elapsed_time, num_inferences / elapsed_time))

Step 4. 推論スクリプトを実行

推論実行スクリプトinfer_resnet50_batch.pyを実行します。

python infer_resnet50_batch.py

出力結果から、Batchサイズを大きくした場合、CPU上での推論スループットは一定であるのに対し、Neuronコア上での推論スループットは大きく向上している事が確認できます。

batch_size: 1, USER_BATCH_SIZE: 1
By CPU         - num_inferences:   100[images], elapsed_time:  3.50[sec], Throughput:   28.55[images/sec]
By Neuron Core - num_inferences:   100[images], elapsed_time:  0.13[sec], Throughput:  753.22[images/sec]
batch_size: 2, USER_BATCH_SIZE: 2
By CPU         - num_inferences:   200[images], elapsed_time:  6.45[sec], Throughput:   31.02[images/sec]
By Neuron Core - num_inferences:   200[images], elapsed_time:  0.17[sec], Throughput: 1176.86[images/sec]
batch_size: 4, USER_BATCH_SIZE: 4
By CPU         - num_inferences:   400[images], elapsed_time: 12.12[sec], Throughput:   33.00[images/sec]
By Neuron Core - num_inferences:   400[images], elapsed_time:  0.29[sec], Throughput: 1388.48[images/sec]
batch_size: 8, USER_BATCH_SIZE: 8
By CPU         - num_inferences:   800[images], elapsed_time: 23.82[sec], Throughput:   33.58[images/sec]
By Neuron Core - num_inferences:   800[images], elapsed_time:  0.70[sec], Throughput: 1141.48[images/sec]
batch_size: 16, USER_BATCH_SIZE: 16
By CPU         - num_inferences:  1600[images], elapsed_time: 48.71[sec], Throughput:   32.85[images/sec]
By Neuron Core - num_inferences:  1600[images], elapsed_time:  1.28[sec], Throughput: 1253.75[images/sec]

スクリプトinfer_resnet50_batch.py中、batch_sizeはコンパイル時に指定したバッチサイズ、USER_BATCH_SIZEはランタイム時のバッチサイズを示しています。 スループットを最大化するため、ランタイム時のバッチサイズは、コンパイル時に指定したバッチサイズの倍数を指定して下さい。 バッチサイズについての詳細はこちらをご確認下さい。