NVIDIA DALIを使ってみた(DALI単体編)

NVIDIAからjpegなどの画像をGPU上でデコードするライブラリがリリースされました。

'18/7/8時点はver.0.1.1なので正式リリースではないのですが、せっかくなので触ってみた感触をレポートします。

DALIとTensorFlowを組み合わせて使う方法はこちらを参照してください。

f:id:xterm256color:20180708231613j:plain

目次

NVIDIA DALIを使って得する人

Deep Learningで画像認識を解いている人。特に画像のデコードとかdata augmentationがボトルネックとなってGPUを活かしきれていない人はDALIを使うと幸せになる(かも)しれません。

ちなみに対応しているDeep Learningフレームワーク

  • MXNet
  • pyTorch
  • TensorFlow

となっています。

環境

インストール方法

pip install --extra-index-url https://developer.download.nvidia.com/compute/redist nvidia-dali

使い方

NVIDIA DALIのコード内のサンプル画像を使うため、GitHubからcloneしておきます。

git clone https://github.com/NVIDIA/DALI.git
mkdir dali-example
cd dali-example

まずはPipelineクラスを定義しましょう。

※注意点:ops.FileReader()にfile_rootで与える第一引数は画像の2階層上ディレクトリ(images/dog/dog_n.jpgの場合はimages)のパスを渡します。

image_dir = "../dali/examples/images/"
batch_size = 8

class nvJPEGPipeline(Pipeline):
    def __init__(self, batch_size, num_threads, device_id):
        super(nvJPEGPipeline, self).__init__(batch_size, num_threads, device_id, seed=12)
        self.input = ops.FileReader(file_root=image_dir, random_shuffle=True, initial_fill=21)
        self.decode = ops.nvJPEGDecoder(device="mixed", output_type=types.RGB)

    def define_graph(self):
        jpegs, labels = self.input()
        images = self.decode(jpegs)
        # images are on the GPU
        return images, labels

つぎにPipelineをインスタンス化→build()run()という順で呼び出します。するとrun()の返り値としてimagesとlabelsが得られます。

pipe = nvJPEGPipeline(batch_size, 1, 0)
pipe.build()
pipe_out = pipe.run()
images, labels = pipe_out

imagesの内容は以下の関数で画面に表示できます。

import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt

def show_images(image_batch):
    columns = 4
    rows = (batch_size + 1) // (columns)
    fig = plt.figure(figsize=(32, (32 // columns) * rows))
    gs = gridspec.GridSpec(rows, columns)
    for j in range(rows*columns):
        plt.subplot(gs[j])
        plt.axis("off")
        plt.imshow(image_batch.at(j))

    plt.show()

show_images(images.asCPU())

f:id:xterm256color:20180709010917p:plain

かわいい

CPU/GPUデコードの速度比較

GPUでデコードする場合、CPUでデコードする場合の比較です。比較用に作成したスクリプトこちらにあります。

上がCPUデコード、下がGPUデコードです。

バッチサイズ64の場合

read 21 files from 2 directories
Speed: 2807.4778410571894 imgs/s
read 21 files from 2 directories
Speed: 4956.1491537075135 imgs/s

バッチサイズ8の場合

read 21 files from 2 directories
Speed: 2202.594687067823 imgs/s
read 21 files from 2 directories
Speed: 3669.342854389418 imgs/s

バッチサイズ1の場合

read 21 files from 2 directories
Speed: 462.7281609674059 imgs/s
read 21 files from 2 directories
Speed: 561.744339581996 imgs/s

バッチサイズが大きいほどGPU側が有利なようですね。バッチサイズ64の時は1.76倍になっています。

NVIDIA DALIのデータを扱うときの注意点

NVIDIA DALIを扱う時は以下の2点を意識しましょう。

  • データの実体がGPU/CPUのどちらに置かれているのか
  • Dense Tensor なのか

例えば上のサンプルコードではimages, labelsはそれぞれTensorListGPU, TensorListCPUという型です。下のコードを実行すると

print("Images type is: " + str(type(images)))
print("Labels type is: " + str(type(labels)))
Images type is: <class 'nvidia.dali.backend_impl.TensorListGPU'>
Labels type is: <class 'nvidia.dali.backend_impl.TensorListCPU'>

こんなメッセージが出てきます。読んだ通り、画像はGPUでラベルはCPUで保持してる型だとわかります。

print("Images is_dense_tensor: " + str(images.is_dense_tensor()))
print("Labels is_dense_tensor: " + str(labels.is_dense_tensor()))
Images is_dense_tensor: False
Labels is_dense_tensor: True

またis_dense_tensor()の返り値がTrueの場合のみ、TensorListを numpy.ndarray に変換できます。 つまり np.array(labels.as_tensor()) はOKだけどnp.array(images.as_tensor())はNGです。無理して変換しようとするとこんなエラーが出てしまいます。

[/opt/dali/dali/pipeline/data/tensor.h:188] Assert on "tl->IsDenseTensor()" failed: All tensors in the input TensorList must have the same shape and be densely packed.

imagesをnumpy.ndarrayとして扱いたい場合は、あらかじめCPU側に移して画像を1枚ずつ取り出す必要があります。

おそらく複数の画像が別のサイズの場合に対処するための仕様だと思われます。

# GPUからCPUに実体を移動
images_on_cpu = images.asCPU()
# 0枚目の画像を取り出し
images_tensor = images_on_cpu.at(0)
# numpy.ndarrayに変換
images_ndarray = nd.array(images_tensor)

print(images_ndarray)
[[[231 234 239]
  [231 234 239]
...(省略)

まとめ

GPUデコードにすると1.76倍にスピードアップ。なかなかいい数字ではないでしょうか。

DALIとTensorFlowと組み合わせて使う方法はこちらでレポートしています。

参考URL