ONNXモデルの中間層出力を取得するアイデア

忘れないように自分用のメモ。

やりたいこと

  • ONNXモデルを使って推論をする時に最終出力ではなく中間ノードの出力がほしい

イデア

  • 2フェーズに分けて処理する
  • 第一フェーズでは中間ノードの出力にShapeノード+出力ノードを接続する
    • make_node('Shape', inputs=[middle_node.output[0]], output='shape1')
    • make_tensor_value_info('shape1', TensorProto.INT64, ['unknown'])
    • ValueInfoProtoのshapeは文字列もOKなので事前に値がわからなくてOK
    • Shapeの出力は1次元なのでValueInfoのdimは1固定にできる
    • たぶんこの状態で推論できるはず
    • 推論によって中間ノードのshapeが取得できる
  • 第二フェーズで中間ノードの出力にIdentityノード+出力ノードを接続する
    • make_node('Identity', inputs=[middle_node.output[0]], output='middle_output1')
    • make_tensor_value_info('middle_output1', TensorProto.FLOAT, shape1)
    • shape1は第一フェーズの出力
    • 第二フェーズの推論によって中間出力が得られる
    • データ型は自動取得する手段が思い浮かばない
    • IdentityノードではなくCastノードで強制キャストする手もありそう
    • 出力データ型がstring以外であればfloatへキャストとかでとりあえず取得できそう

あくまでアイデアなので、実際に実装して試してみたい。おそらくこのアイデアは複数の中間ノードにいっせいに実施できるはず。(outputに上限とかがなければ)

2020/02/15 追記
  • make_tensor_value_infoの引数shapeはNoneを指定できた
  • Noneにするとshape不一致で推論時エラーにならないので第一フェーズは不要になる
  • データ型は回避方法が見つからなかった
  • onnx.TensorProto.UNDEFINEDというのがあるが、指定しても推論時エラーになるだけだった
  • 事前にデータ型が不明な場合はCastノードを挟んで強制的に出力データ型を変更するしかなさそう。
    • 複素数型や文字列型にも対応できる汎用的な方法は見つかっていない

OpenCL/CUDAのスレッド間通信めも

CUDAとOpenCLでスレッド同士の通信でどうするんだっけ?と思って調べたことを自分用にメモ。

AMD(OpenCL)はshuffleがなさそう

  • retval = sub_group_broadcast(value, thread_id)がある
  • 特定の1つのthread_idからWavefront全体に値をわたせる
  • 隣のスレッドと値を交換するshuffleみたいなのは無さそう

参考(StackOverflow "Can we use shuffle() instruction for reg-to-reg data-exchange between items (threads) in WaveFront?")

CUDAはshuffleがある

  • __shfl_down()でスレッドIDの若番側へデータを送れる
  • __shfl_up()でスレッドIDの大きな方へデータを送れる
  • __shfl_xor()で2スレッド同士でデータ交換できる

参考("CSE 599 I Accelerated Computing - Programming GPUS Warp Shuffle and Warp Vote Instructions" 16/43ページ、17/43ページ、26/43ページ)

__shfl_xorを使えばbitonic sort実装できそう、と思っていたらすでにあった

  • facebook/fbcuda @ github
  • とはいえ、Warpサイズを超えるデータ数だとShared Memory使ったりする必要がありそう

あとついでにメモ

ROCm Docker内でfind_package(OpenCL)に失敗した

  • 使ったイメージはrocm2.9_ubuntu18.04_py3.6
  • cmake v3.6.3(古いなぁ・・・)
  • cmake AMDAPPSDKROOT=/opt/rocm/opencl ..だとエラー
    • exportしても同じ
  • CMakeLists.txtで直接上書きしたら(当然だけど)OK
cmake_minimum_required(VERSION 3.6)
set(OpenCL_INCLUDE_DIR /opt/rocm/opencl/include)
set(OpenCL_LIBRARY /opt/rocm/opencl/lib/x86_64)
find_package(OpenCL REQUIRED)

/usr/local/share/cmake-3.6/Modules/FindOpenCL.cmakeはこうなってた。AMDAPPSDKROOTを見に行くように見えるのだが・・・

find_path(OpenCL_INCLUDE_DIR
  NAMES
    CL/cl.h OpenCL/cl.h
  PATHS
    ENV "PROGRAMFILES(X86)"
    ENV AMDAPPSDKROOT
    ENV INTELOCLSDKROOT
    ENV NVSDKCOMPUTE_ROOT
    ENV CUDA_PATH
    ENV ATISTREAMSDKROOT
  PATH_SUFFIXES
    include
    OpenCL/common/inc
    "AMD APP/include")

YUY2のRAW画像を保存してPythonでRGB化

やったこと

  • YUY2出力可能なUSBカメラをLinuxに接続
  • ffmpegでRAW画像キャプチャしてファイル保存
  • Pythonからnumpyで読み込み+OpenCVでRGBへ変換
    • 正確にはBGRにしてファイル保存

キャプチャ(コマンドライン

$ ffmpeg -f v4l2 -input_format yuyv422 -video_size 800x600 -framerate 30 -i /dev/video0 -vcodec rawvideo -pix_fmt yuyv422 -f image2 sample-%04d.png
  • 素数とフレームレートの数値はffmpeg -f v4l2 -list_formats all -i /dev/video0で確認
  • Windowsはたぶん-f dshow-i "カメラデバイス名"にしたらいける予感(動作未確認)

Python

import cv2
import numpy as np
from pathlib import Path


_IMG_SHAPE = (600, 800, 2) # 縦幅, 横幅, 8bit2つ


# カレントディレクトリの.rawファイルをRGB変換してjpgで保存
for p in Path('.').glob('*.raw'):
    yuy2 = np.fromfile(str(p), dtype=np.uint8).reshape(_IMG_SHAPE) # YUY2のRAWファイルを8bitずつ読み出し+[600][800][2]に変形
    bgr = cv2.cvtColor(yuy2, cv2.COLOR_YUV2BGR_YUY2) # YUY2からBGRへ変換
    cv2.imwrite(str(p.with_suffix('.jpg')), bgr) # 拡張子をjpgにしてファイル保存

補足

  • YUY2?
    • YUVフォーマットのうち、UV要素を2ピクセルにつき1ピクセル分の情報に間引いた?フォーマットっぽい
    • データの並びはYUYVYUYV・・・と並ぶっぽい
      • YUYVで2ピクセル分。YUで8bitが2つ、YVで8bitが2つ
    • YUYV422と同じ?(少なくともffmpegは同じ扱いをしてるように見える)
  • Pythonで縦横逆っぽくない?
    • たぶんメモリ上で1次元配列っぽく並んでる
    • YUYV・・・が横方向に並んで、1行が終わると次の行、と並んでいる
    • 上記を3次元で確認すると[縦方向, 横方向, 2]になる

KerasでLeakyReLUを使った

完全に自分用のメモ。KerasでLeakyReLUを使おうとしたら怒られたので正しい(?)書き方をメモしておく。

環境

警告を食らったコード

import plaidml.keras
plaidml.keras.install_backend()

from keras.models import Sequential
from keras.layers.core import Dense
from keras.layers import LeakyReLU

def make_mlp():
	model = Sequential()
	model.add(Dense(256, activation=LeakyReLU(alpha=0.01), input_shape=(3,), kernel_initializer='he_normal'))
	model.add(Dense(3, activation='linear'))
	model.compile(optimizer='adam', loss='mean_squared_error', metrics=['mse'])
	return model

警告メッセージ

keras\plaidml-env\lib\site-packages\keras\activations.py:197: UserWarning: Do not pass a layer instance (such as LeakyReLU) as the activation argument of another layer. Instead, advanced activation layers should be used just like any other layer in a model.
identifier=identifier.__class__.__name__))

修正後のコード

def make_mlp():
	model = Sequential()
-	model.add(Dense(256, activation=LeakyReLU(alpha=0.01), input_shape=(3,), kernel_initializer='he_normal'))
+	model.add(Dense(256, input_shape=(3,), kernel_initializer='he_normal'))
+	model.add(LeakyReLU(alpha=0.01))
	model.add(Dense(3, activation='linear'))

解説(のような何か)

Dense()の引数activationにLeakyReLUのインスタンスをわたすとダメらしい。他に試したこととしてmodel.add()でActivation()をわたして引数にLeakyReLUのインスタンスをわたしてみたが、同じ警告メッセージが表示された。
警告メッセージの英文がよくわからなかったけど、どうやら「直接LeakyReLUのインスタンスをadd()せよ」ってことらしい。

XavierにTensorFlowをインストール。ついでにjupyter notebookも入れる

Jetson AGX XavierにTensorFlowとJupyter notebookをインストールしたのでメモ。
基本的に公式サイト*1とフォーラム*2に書いてある通り。

前提

JetPack-4.0インストール済みの状態から始める

インストール

# 熱くなるのでファン全開で冷やす
ubuntu@jetson-0423018055036:~$ sudo ./jetson_clocks.sh
# コンパイルが重いので本気を出させる
ubuntu@jetson-0423018055036:~$ sudo nvpmodel -m 0

# pipを入れる。依存パッケージでpython3-devも入る
ubuntu@jetson-0423018055036:~$ sudo apt-get install python3-pip

# venv入れて環境を作る
ubuntu@jetson-0423018055036:~$ sudo apt-get install python3-venv
ubuntu@jetson-0423018055036:~$ python3 --version
Python 3.6.6
ubuntu@jetson-0423018055036:~$ python3 -m venv py366env
ubuntu@jetson-0423018055036:~$ source py366env/bin/activate
(py366env) ubuntu@jetson-0423018055036:~$ pip install -U pip

# TensorFlowを入れる
(py366env) ubuntu@jetson-0423018055036:~$ pip install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp40 tensorflow-gpu

# matplotlibとkerasの依存パッケージを入れる
(py366env) ubuntu@jetson-0423018055036:~$ deactivate
# freetypeを入れるときにlibpng-devも一緒に入る
ubuntu@jetson-0423018055036:~$ sudo apt-get install libfreetype6-dev
ubuntu@jetson-0423018055036:~$ sudo apt-get install libhdf5-dev
# KerasというよりSciPyの依存パッケージ
ubuntu@jetson-0423018055036:~$ sudo apt-get install liblapacke-dev
ubuntu@jetson-0423018055036:~$ sudo apt-get install gfortran
ubuntu@jetson-0423018055036:~$ source py366env/bin/activate               

# matplotlibとKeras、Jupyterを入れる
(py366env) ubuntu@jetson-0423018055036:~$ pip install matplotlib
(py366env) ubuntu@jetson-0423018055036:~$ pip install -U keras==2.2.0
(py366env) ubuntu@jetson-0423018055036:~$ pip install jupyter

[2018/10/14 追記]
liblapack-devは「static version」らしい。ヘッダファイル類は「e」付きのliblapacke-devが正解っぽい。

Jupyterの初期設定

以前の記事*3とほぼ同じ。

差分は

c.NotebookApp.ip = '0.0.0.0'

と設定するところ。「*」だとエラーになる。参考*4

あとnotebookのアクセス先URLはXavierのホスト名でアクセスできるみたい。自分の環境だと以下のアドレスだった。
http://jetson-0423018055036:8888/


importできたので大丈夫そう。

Jetson AGX Xavier買いました

Jetson AGX Xavier、早期注文が可能になったのでさっそく買ってみた。不在だった荷物の引き取りに手間取ったので今日受け取ったけど、9/11に到着したみたい。
[2018/09/15 02:15追記]なんかGTC Japan以降、名称に「AGX」がつくようになったらしい。

JetPackのファイルダウンロード中なので所感とかそういうのを書いておこうと思う。

工場出荷の状態

Ubuntuはインストール済みの状態だった。ただ、起動させるとCUIが立ち上がってSSHで接続できない状態だった。

HDMIでディスプレイをつなぎ、付属の変換ケーブルでUSBキーボードをつないで作業した。
/home/nvidia/NVIDIA-INSTALLER/README.txtに書かれている通りにインストーラを実行してJetsonを再起動するとGUIモードで起動してSSHもつながった。

$ cd NVIDIA-INSTALLER
$ sudo ./installer.sh

ただ、この状態だとCUDA Toolkitとかサンプルアプリとかが入ってない状態なので遊べない。結局JetPackを入れる感じになりそう。

確認できたツール

  • nvpmodel
  • jetson_clocks.sh
  • tegrastats

は入ってた。jetson_clocks.shをたたいておかないと、何もしてなくてもヒートシンクが熱い。

nvpmodelのモード

/etc/nvpmodel.confの設定が書かれているっぽい。デフォルトはID=3「MODE_30W_ALL」ってモードでCPUは8コアともonlineだけど4つだけ1.2GHz設定、GPUは最大900MHz、EMC(メモリのバス?)は1.6GHz。DLAとPVAのクロックも設定されるっぽい。
tegrastatsを打つとこんな感じ。

RAM 2100/15820MB (lfb 3000x4MB) CPU [0%@1190,0%@1190,0%@1190,0%@1190,0%@1190,0%@1190,0%@1190,0%@1190] EMC_FREQ 0%@1600 GR3D_FREQ 0%@905 APE 150 MTS fg 0% bg 0% AO@33C GPU@32.5C Tboard@34C Tdiode@35.75C AUX@31.5C CPU@34C thermal@32.55C PMIC@100C GPU 619/619 CPU 309/309 SOC 1548/1548 CV 0/0 VDDRQ 0/0 SYS5V 1851/1851

CPUのクロック設定が4つしか入ってないのはなぜだろう。。。

一番本気っぽい設定はID=0「MAXN」でクロック設定が全部「-1」になってる。

MAXN is the NONE power model to release all constraints

ってことなので「特に制限しないモード」ってことだと思う。

cpuinfo

今気づいたけど、v8lってv8.1ってこと?XavierのCPUってv8.2じゃなかったっけ???
[2018/09/15 02:15追記]よく見たら「いち」じゃなくて「える」ですね。kernelソースを見たら取りうる値がv8bかv8lってことなのでlittle endianってことかな。

nvidia@jetson-0423018055036:~$ cat /proc/cpuinfo
processor       : 0
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 1
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 2
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 3
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 4
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 5
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 6
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

processor       : 7
model name      : ARMv8 Processor rev 0 (v8l)
BogoMIPS        : 62.50
Features        : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant     : 0x0
CPU part        : 0x004
CPU revision    : 0
MTS version     : 42272872

とりあえず今日はここまで。(ダウンロードエラーで止まっとる・・・)

Fancy Indexingが使えるリストを実装してみた

自分用のメモ。Pythonでlistにデータを入れてnumpyみたいにインデックスをリストでわたして取り出したいと思ったので作ってみた。

実装はこちら。

from collections import UserList
#class FancyIndexingList(list): 2020/02/03修正。UserListを継承すべき
class FancyIndexingList(UserList):
	def __getitem__(self, arg):
		if not isinstance(arg, list) and not isinstance(arg, tuple):
			return super().__getitem__(arg)

		indices = arg
		result = [self[i] for i in indices]
		return FancyIndexingList(result)

UserListを継承して__getitem__で引数がリストとタプルの時だけ自分で処理して他は全部親クラスに任せる。
Fancy Indexingは__getitem__の引数がそのままインデックスのリストになっているので、インデックス引きして新しいリストを作って返すだけ。

使うときはこんな感じ。

>>> sample = FancyIndexingList(range(10))
>>> sample
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sample[2]
2
>>> sample[ [4, 2, 0] ]
[4, 2, 0]


参考。

>>> class IndexingTest:
...     def __getitem__(self, arg):
...             print(type(arg))
...
>>> i = IndexingTest()
>>> i[1]
<class 'int'>
>>> i[1, 2]
<class 'tuple'>
>>> i[ [0, 1] ]
<class 'list'>
>>> i[0:10:2]
<class 'slice'>