実行時のカスタムレイヤの実装 - 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_layouttrue ならば、この関数は出力の値を16 ビット浮動小数点数にし、バッファをハードウェアレイアウトに従って並べる。 もし出力がほかのハードウェアの畳み込み層や全結合層への入力バッファとして使われるなら、is_output_hw_layouttrue でなくてはならない。

例:PriorBox 層の実行時の実装

ネットワーク変換ツールは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 層はその層のパラメータのみにしか依存しないからです。 これが問題なのであれば、最適化のためにこの層を最初の一回のみしか実行されないようにすることもできます。

⚠️ **GitHub.com Fallback** ⚠️