はじめに
毎度おなじみ当方の LUT-Netは、ネットワークのパラメータをLUTのテーブル値として学習して回路にしてしまう為、外部SDRAMなどのメモリからパラメータをロードしながら演算する必要がありません。
今回はLUT-Net は置いておいて、「もし普通の積和演算を使うパーセプトロンモデルのパラメータも、演算時ロードではなく静的に固定パラメータとして合成したらどうなるか?」というのを考えてみました。
この場合、パラメータが固定されますので演算器の再利用は出来ませんが、乗算の係数が固定されているので乗算器を使わずに回路をシフトや可算で簡略化できるケースが出てきます。
例えば、入力値 x を 5 倍する場合、x * 5 を (x << 2) + x に分解すれば加算器一個で実現できます。
また、bit 数が少ない場合、演算結果を予めテーブル化しておくことも可能です。今回は主にテーブル化をメインに考えてみたいと思います。
BF16形式を眺めてみる
最近の深層学習でよく使われる形式の一例としてこちらにある BF16 形式を引用させてもらいます。
これはFP32形式の下位16bit を切り捨てたような大胆な形式ではありますが、なんと仮数部が7bitしかありません。
浮動小数点形式の乗算では、NaN や INF などの例外をおいておくと、符号部は XOR、指数部は可算、仮数部のみ乗算を行えばよいです。 この時、一番回路が大きくなるのが乗算部分で特にFP32などではDSPを利用するのがほぼ必須でしょう。ところが BF16のような小さな bit 幅で、かつ乗算の片方が固定係数の場合は話が変ってきそうです。 7bit であれば入力パターンは 128種類しかありませんので、128ワードの7bit ROM をテーブル引きすれば終わることになります。
ここで、Xilinx の 6入力 LUT は、64bit のメモリで構成されており、LUTRAM として使う事が出来るタイプが一定量組み込まれています。
つまり、LUTを2個使って 128bit のメモリが構成できれば、それを 7並列にすれば固定定数との乗算は実現できる可能性があります。
さっそく 7bit 128word のメモリを RTL 記述して合成してみます。
RAMの回路は既に昔書いたものがこちらにあるので、
ADDR_WIDTH = 7; DATA_WIDTH = 7; MODE = "READ_FIRST"
として、さらに en も 1 に固定して合成してみます。
結果は下記です。
LUT が 16 個使われたのみで、そのうち 14 個は LUTRAM として使われています(むしろ普通のLUT 2個が謎です)。
該当箇所は下記のようになっていました。
かなり思惑通りです。
ついでに、さらに精度を 1bit 捨てて 6bit の場合どうなるか、6bit 164word を試したところ下記のようになりました。
もはや入力が6bit な時点で、6入力LUTを単に置くだけと言うことになりますね。
まとめ
演算器を構成するのではなくROMを引くことで簡略化するような技法は逆数を求めるなどいろいろなシーンで利用できます。
BRAMを使う手もありますが、特に精度が低くていい場合はLUTを ROM にしてしまうのはなかなか効果的に思います。 特に Deep Learning の場合、学習済みのパラメータは推論時には変化しない固定定数となってしまうので、時として一緒に合成してしまうのは悪くない手に思えます。 リッチな汎用乗算器である DSP を温存して、一部でLUTを使った専用回路にしてしまうのも面白い使い方に思いました。
もっとも浮動小数点の場合は、乗算の後の加算で桁数を合わせるためのバレルシフタが大きかったりするのですけどね(汗)。
とはいえ、INT8 や INT4 なども Deep Learning には利用されているので、INT6 なんて用意すると FPGA 向けには効率が良かったりするかもしれません。 Deep Learning でも 非線形表現を用いてパラメータを圧縮する論文等はいくつかあったかと思いますが、特にテーブル化する場合、数値表現がリニアである必要はないのでFPGA向けに6bitの特殊な表現形式を定義するのも面白いかもしれません。
追記
UG574を見ると、CLB内の複数のLUTを繋げて、大きなマルチプレクサやシフトレジスタを作るためのセレクタがついていることが分かり、当然LUTRAMの結合にも使えるので下記のようになるようだ。 LUTはDSPやBRAMより配置自由度が高く、単独での速度も速いのでうまくテーブルとして使うと効果的なシーンは沢山ありそうだ。