実行時のカスタムレイヤの実装 - DigitalMediaProfessionals/dv-sdk GitHub Wiki
まず初めに、実行時によばれるカスタムレイヤのコールバック関数を実装してください。
ネットワーク変換ツールでは、各種カスタムレイヤのためのコールバック関数やデータ構造のプロトタイプがヘッダーファイル内に生成されます。さらに、各カスタムレイヤに対しパラメータとレイヤがソースファイルの中に生成されます。
カスタムレイヤのデータ構造やコールバック関数は次のようになっている。
struct custom_param_type1
{
...
};
void custom_callback_type1(fpga_layer &layer, void *custom_param);
...
そして、ソースファイルの中に生成される各層のパラメータは下記のようである。
void CSSDFacePerson::Layer_XXX()
{
static custom_param_type1 custom_param = {
...
};
struct fpga_layer& layer = get_layer(XXX);
layer.type = LT_CUSTOM;
...
layer.input_dim[0] = XXX;
layer.input_dim[1] = XXX;
layer.input_dim[2] = XXX;
layer.input_dim_size = 3;
layer.output_dim[0] = XXX;
layer.output_dim[1] = XXX;
layer.output_dim[2] = XXX;
layer.output_dim_size = 3;
layer.is_output = false;
layer.is_f32_output = true;
layer.is_input_hw_layout = true;
layer.custom_proc_ptr = &custom_callback_type1;
layer.custom_param = &custom_param;
}//end of Layer_XXX
実行時にこの層が実行されたとき、FPGA ネットワークユーティリティクラスはコールバック関数 custom_callback_type1()
を呼び出し、パラメータを渡します。
コールバック関数内ではlayer
引数とcustom_param
引数により層の情報が取得できます。
コールバック関数の実装に当たり、入力バッファからの読み込みと出力バッファへの書き込みのために下記の関数が提供される。
void get_layer_input(fpga_layer& layer, std::vector<float>& layer_input, uint8_t *io_ptr);
void put_layer_output(fpga_layer& layer, std::vector<float>& layer_output, uint8_t *io_ptr, bool is_output_hw_layout = false);
get_layer_input()
はlayer
への入力データlayer_input
に読み込みます。
put_layer_output()
はlayer_output
の出力データをlayer
に書き込みます。
is_output_hw_layout
がtrue
ならば、この関数は出力の値を16 ビット浮動小数点数にし、バッファをハードウェアレイアウトに従って並べる。
もし出力がほかのハードウェアの畳み込み層や全結合層への入力バッファとして使われるなら、is_output_hw_layout
はtrue
でなくてはならない。
ネットワーク変換ツールはPriorBox のパラメータとコールバック関数のプロトタイプを次のようにヘッダファイルに生成します。
struct custom_param_PriorBox
{
int img_size[2];
float min_size;
float max_size;
float aspect_ratios[6];
float variances[4];
bool clip;
};
void custom_callback_PriorBox(fpga_layer &layer, void *custom_param);
これらの層の設定パラメータはソースファイルに、次のように生成されます。
//Layer_64: Custom Layer
// ->: conv5_mbox_priorbox
void CSSDFacePerson::Layer_64()
{
static custom_param_PriorBox custom_param = {
{ 300, 300, },
10.0,
30.0,
{ 1.0, 1.0, 2, 0.5, 3, 0.3333333333333333, },
{ 0.1, 0.1, 0.2, 0.2, },
true,
};
struct fpga_layer& layer = get_layer(64);
layer.type = LT_CUSTOM;
...
layer.input_dim[0] = 38;
layer.input_dim[1] = 38;
layer.input_dim[2] = 256;
layer.input_dim_size = 3;
layer.output_dim[0] = 8664;
layer.output_dim[1] = 8;
layer.output_dim_size = 2;
layer.is_output = false;
layer.is_f32_output = true;
layer.is_input_hw_layout = true;
layer.custom_proc_ptr = &custom_callback_PriorBox;
layer.custom_param = &custom_param;
}//end of Layer_64
ご覧の通り、パラメータとそのデータ構造は自動的に変換ツールにより生成されます。
ユーザに必要なことは、custom_callback_PriorBox()
関数を実装することです。
inline float box_clip(float x) {
if (x < 0.f)
return 0.f;
else if (x > 1.f)
return 1.f;
else
return x;
}
void custom_callback_PriorBox(fpga_layer &layer, void *param) {
custom_param_PriorBox *box_param = reinterpret_cast<custom_param_PriorBox*>(param);
vector<float> boxes_v(layer.output_dim[0] * layer.output_dim[1]);
prior_box_t box;
int box_count = 0;
// Box widths and heights
vector<float> box_widths_v;
vector<float> box_heights_v;
for (auto ar : box_param->aspect_ratios) {
if ((ar == 1.0) && box_widths_v.empty()) {
float sz = 0.5 * box_param->min_size;
box_widths_v.push_back(sz);
box_heights_v.push_back(sz);
} else if ((ar == 1.0) && (box_widths_v.size() > 0)) {
float sz = 0.5 * sqrt(box_param->min_size * box_param->max_size);
box_widths_v.push_back(sz);
box_heights_v.push_back(sz);
} else if (ar != 1.0) {
box_widths_v.push_back(0.5 * box_param->min_size * sqrt(ar));
box_heights_v.push_back(0.5 * box_param->min_size / sqrt(ar));
}
}
// Grid
int num_variances = sizeof(box_param->variances) / sizeof(box_param->variances[0]);
assert((num_variances == 1 || num_variances == 4) && "Number of variances must be either 1 or 4.");
if (num_variances == 1) {
box.xv = box_param->variances[0];
box.yv = box_param->variances[0];
box.wv = box_param->variances[0];
box.hv = box_param->variances[0];
} else {
box.xv = box_param->variances[0];
box.yv = box_param->variances[1];
box.wv = box_param->variances[2];
box.hv = box_param->variances[3];
}
float step_x = float(box_param->img_size[0]) / float(layer.input_dim[0]);
float step_y = float(box_param->img_size[1]) / float(layer.input_dim[1]);
for (int y = 0; y < layer.input_dim[1]; y++) {
float center_y = step_y * (float(y) + 0.5);
for (int x = 0; x < layer.input_dim[0]; x++) {
float center_x = step_x * (float(x) + 0.5);
for (unsigned int p = 0; p < box_widths_v.size(); p++) {
box.x0 = (center_x - box_widths_v[p]) / float(box_param->img_size[0]);
box.y0 = (center_y - box_heights_v[p]) / float(box_param->img_size[1]);
box.x1 = (center_x + box_widths_v[p]) / float(box_param->img_size[0]);
box.y1 = (center_y + box_heights_v[p]) / float(box_param->img_size[1]);
if (box_param->clip) {
box.x0 = box_clip(box.x0);
box.y0 = box_clip(box.y0);
box.x1 = box_clip(box.x1);
box.y1 = box_clip(box.y1);
}
memcpy(&boxes_v[box_count * 8], &box, sizeof(box));
box_count++;
}
}
}
put_layer_output(layer, boxes_v);
}
注: 上記の実装ではget_layer_input()
関数は呼ばれません。
これはPriorBox 層はその層のパラメータのみにしか依存しないからです。
これが問題なのであれば、最適化のためにこの層を最初の一回のみしか実行されないようにすることもできます。