pooling.cc 16.2 KB
Newer Older
1

2 3 4 5 6 7 8 9
/*!
 *  Copyright (c) 2017 by Contributors
 * \file pooling.cc
 * \brief Property def of pooling operators.
 */
#include <nnvm/op.h>
#include <nnvm/node.h>
#include <nnvm/op_attr_types.h>
10 11
#include <nnvm/compiler/op_attr_types.h>
#include <nnvm/compiler/util.h>
12
#include <nnvm/top/nn.h>
13
#include "nn_common.h"
14 15
#include "../op_common.h"
#include "../elemwise_op_common.h"
16
#include "topi/nn/pooling.h"
17 18 19

namespace nnvm {
namespace top {
20 21
using namespace tvm;
using namespace nnvm::compiler;
22

23
DMLC_REGISTER_PARAMETER(MaxPool2DParam);
24

25
template <typename T>
26 27 28
inline bool Pool2DInferShape(const nnvm::NodeAttrs& attrs,
                             std::vector<TShape>* in_shape,
                             std::vector<TShape>* out_shape) {
29
  const T& param = nnvm::get<T>(attrs.parsed);
30 31 32 33 34
  CHECK_EQ(in_shape->size(), 1U);
  CHECK_EQ(out_shape->size(), 1U);

  TShape dshape = (*in_shape)[0];
  if (dshape.ndim() ==  0) return false;
35 36 37 38 39 40 41 42 43 44 45 46

  CHECK_GE(dshape.ndim(), 2U)
    << "Pool2D only support input >= 2-D: input must have height and width";

  Layout layout(param.layout);
  CHECK(layout.contains('H') && layout.contains('W') &&
        !layout.contains('h') && !layout.contains('w'))
    << "Invalid layout " << layout
    << ". Pool2D layout must have H and W, which cannot be split";

  const auto hidx = layout.indexof('H');
  const auto widx = layout.indexof('W');
47

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
  dim_t pad_h, pad_w;
  if (param.padding.ndim() == 1) {
    pad_h = param.padding[0] * 2;
    pad_w = param.padding[0] * 2;
  } else if (param.padding.ndim() == 2) {
    // (top, left)
    pad_h = param.padding[0] * 2;
    pad_w = param.padding[1] * 2;
  } else if (param.padding.ndim() == 4) {
    // (top, left, bottom, right)
    pad_h = param.padding[0] + param.padding[2];
    pad_w = param.padding[1] + param.padding[3];
  } else {
    return false;
  }

64
  TShape oshape = dshape;
65
  CHECK(param.pool_size[0] <= dshape[hidx] + pad_h)
66
      << "pool size (" << param.pool_size[0] << ") exceeds input (" << dshape[hidx]
67 68
      << " padded to " << (dshape[hidx] + pad_h) << ")";
  CHECK(param.pool_size[1] <= dshape[widx] + pad_w)
69
      << "pool size (" << param.pool_size[1] << ") exceeds input (" << dshape[widx]
70
      << " padded to " << (dshape[widx] + pad_w) << ")";
71 72

  if (!param.ceil_mode) {
73
    oshape[hidx] = ((dshape[hidx] + pad_h - param.pool_size[0]) /
74
                    param.strides[0]) + 1;
75
    oshape[widx] = ((dshape[widx] + pad_w - param.pool_size[1]) /
76
                    param.strides[1]) + 1;
77
  } else {
78
    oshape[hidx] = ((dshape[hidx] + pad_h - param.pool_size[0] +
79
                    param.strides[0] - 1) / param.strides[0]) + 1;
80
    oshape[widx] = ((dshape[widx] + pad_w - param.pool_size[1] +
81
                    param.strides[1] - 1) / param.strides[1]) + 1;
82 83 84 85 86
  }
  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
  return true;
}

87
template <typename T>
88 89 90 91
inline bool Pool2DCorrectLayout(const NodeAttrs& attrs,
                                std::vector<Layout> *ilayouts,
                                const std::vector<Layout> *last_ilayouts,
                                std::vector<Layout> *olayouts) {
92
  const T &param = nnvm::get<T>(attrs.parsed);
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
  CHECK_EQ(ilayouts->size(), 1);
  CHECK_EQ(last_ilayouts->size(), 1);
  CHECK_EQ(olayouts->size(), 1);

  Layout input = (*ilayouts)[0];
  const Layout layout(param.layout);

  if (input.defined()) {
    CHECK(input.convertible(layout)) << "Invalid input layout " << input;
    if (input.indexof('W') != layout.indexof('W') ||
        input.indexof('H') != layout.indexof('H') ||
        input.contains('w') || input.contains('h')) {
      // as long as the index doesn't change for width and height
      // pool2d can keep the input layout.
      input = layout;
    }
  } else {
    input = layout;
  }

  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, input);
  NNVM_ASSIGN_LAYOUT(*olayouts, 0, input);

  return true;
}

119 120 121 122 123 124 125 126
NNVM_REGISTER_OP(max_pool2d)
.describe(R"code(Max pooling operation for one dimensional data.

- **data**: This depends on the `layout` parameter. Input is 4D array of shape
            (batch_size, channels, height, width) if `layout` is `NCHW`.
- **out**: This depends on the `layout` parameter. Output is 4D array of shape
           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
           out_height and out_width are calculated as::
127

128 129 130 131 132 133 134
               out_height = floor((height+padding[0]+padding[2]-pool_size[0])/strides[0])+1
               out_width = floor((width+padding[1]+padding[3]-pool_size[1])/strides[1])+1

           where padding will be an expanded array based on number of values passed as::
               one int : all sides same padding used.
               two int : bottom, right use same as top and left.
               four int: padding width in the order of (top, left, bottom, right).
135

136 137 138 139 140
           When `ceil_mode` is `True`, ceil will be used instead of floor in this
           equation.

)code" NNVM_ADD_FILELINE)
.add_argument("data", "4D Tensor", "Input data.")
141 142 143
.add_arguments(MaxPool2DParam::__FIELDS__())
.set_attr_parser(ParamParser<MaxPool2DParam>)
.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MaxPool2DParam>)
144 145
.set_num_outputs(1)
.set_num_inputs(1)
146
.set_attr<FInferShape>("FInferShape", Pool2DInferShape<MaxPool2DParam>)
147
.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
148
.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout<MaxPool2DParam>)
149 150 151
.set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                         const Array<Tensor>& inputs,
                                         const Array<Tensor>& out_info) {
152
  const MaxPool2DParam& param = nnvm::get<MaxPool2DParam>(attrs.parsed);
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
  auto pool_size = ShapeToArray(param.pool_size);
  auto strides = ShapeToArray(param.strides);
  auto padding = ShapeToArray(param.padding);
  auto ceil_mode = param.ceil_mode;

  Layout layout(param.layout);
  CHECK(layout.convertible(Layout("NCHW")))
    << "max_pool2d currently only supports layouts that are convertible from NCHW";
  CHECK_EQ(layout.indexof('h'), -1) << "max_pool2d does not support input split on height";
  CHECK_EQ(layout.indexof('w'), -1) << "max_pool2d does not support input split on width";

  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
    << "Pool2D only support 4-D input (e.g., NCHW)"
    << " or 5-D input (last dimension is a split of channel)";

168 169 170 171 172 173 174 175 176
  if (param.padding.ndim() == 1) {
    padding.push_back(padding[0]);
    padding.push_back(padding[0]);
    padding.push_back(padding[0]);
  } else if (param.padding.ndim() == 2) {
    padding.push_back(padding[0]);
    padding.push_back(padding[1]);
  }

177 178 179
  return Array<Tensor>{
    topi::nn::pool(inputs[0], pool_size, strides, padding,
                   topi::nn::kMaxPool, ceil_mode, layout.name())};
180
})
181 182 183 184 185 186 187
.set_attr<FGradient>(
  "FGradient", [](const NodePtr& n,
                  const std::vector<NodeEntry>& ograds) {
    return MakeGradNode("_max_pool2d_grad", n,
                        {ograds[0], n->inputs[0], NodeEntry{n, 0, 0}},
                        n->attrs.dict);
})
188 189
.set_support_level(2);

190 191 192 193 194 195 196 197 198
NNVM_REGISTER_OP(_max_pool2d_grad)
  .describe(R"code(Max pooling 2D grad.

)code" NNVM_ADD_FILELINE)
.add_argument("ograd", "4D Tensor", "Output grad.")
.add_argument("input", "4D Tensor", "Input data of max_pool2d grad.")
.add_argument("output", "4D Tensor", "Output data of max_pool2d grad.")
.set_num_inputs(3)
.set_num_outputs(1)
199 200
.set_attr_parser(ParamParser<MaxPool2DParam>)
.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<MaxPool2DParam>)
201 202 203 204
.set_attr<FInferShape>("FInferShape", AssignOutputAttr<TShape, 1, 0>)
.set_attr<FInferType>("FInferType", ElemwiseType<3, 1>)
.set_attr<TIsBackward>("TIsBackward", true);

205
DMLC_REGISTER_PARAMETER(AvgPool2DParam);
206 207 208 209 210 211 212 213 214

NNVM_REGISTER_OP(avg_pool2d)
.describe(R"code(Average pooling operation for one dimensional data.

- **data**: This depends on the `layout` parameter. Input is 4D array of shape
            (batch_size, channels, height, width) if `layout` is `NCHW`.
- **out**: This depends on the `layout` parameter. Output is 4D array of shape
           (batch_size, channels, out_height, out_width)  if `layout` is `NCHW`.
           out_height and out_width are calculated as::
215

216 217 218 219 220 221 222
               out_height = floor((height+padding[0]+padding[2]-pool_size[0])/strides[0])+1
               out_width = floor((width+padding[1]+padding[3]-pool_size[1])/strides[1])+1

           where padding will be an expanded array based on number of values passed as::
               one int : all sides same padding used.
               two int : bottom, right use same as top and left.
               four int: padding width in the order of (top, left, bottom, right).
223

224 225 226 227 228
           When `ceil_mode` is `True`, ceil will be used instead of floor in this
           equation.

)code" NNVM_ADD_FILELINE)
.add_argument("data", "4D Tensor", "Input data.")
229 230 231 232
.add_arguments(AvgPool2DParam::__FIELDS__())
.set_attr_parser(ParamParser<AvgPool2DParam>)
.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<AvgPool2DParam>)
.set_attr<FInferShape>("FInferShape", Pool2DInferShape<AvgPool2DParam>)
233
.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
234
.set_attr<FCorrectLayout>("FCorrectLayout", Pool2DCorrectLayout<AvgPool2DParam>)
235 236 237
.set_attr<FTVMCompute>("FTVMCompute", [](const NodeAttrs& attrs,
                                         const Array<Tensor>& inputs,
                                         const Array<Tensor>& out_info) {
238
  const AvgPool2DParam& param = nnvm::get<AvgPool2DParam>(attrs.parsed);
239 240 241 242
  auto pool_size = ShapeToArray(param.pool_size);
  auto strides = ShapeToArray(param.strides);
  auto padding = ShapeToArray(param.padding);
  auto ceil_mode = param.ceil_mode;
243
  auto count_include_pad = param.count_include_pad;
244 245 246 247 248 249 250 251 252 253 254

  Layout layout(param.layout);
  CHECK(layout.convertible(Layout("NCHW")))
    << "avg_pool2d currently only supports layouts that are convertible from NCHW";
  CHECK_EQ(layout.indexof('h'), -1) << "avg_pool2d does not support input split on height";
  CHECK_EQ(layout.indexof('w'), -1) << "avg_pool2d does not support input split on width";

  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
    << "Pool2D only support 4-D input (e.g., NCHW)"
    << " or 5-D input (last dimension is a split of channel)";

255 256 257 258 259 260 261 262 263
  if (param.padding.ndim() == 1) {
    padding.push_back(padding[0]);
    padding.push_back(padding[0]);
    padding.push_back(padding[0]);
  } else if (param.padding.ndim() == 2) {
    padding.push_back(padding[0]);
    padding.push_back(padding[1]);
  }

264 265
  return Array<Tensor>{
    topi::nn::pool(inputs[0], pool_size, strides, padding,
266
                   topi::nn::kAvgPool, ceil_mode, layout.name(), count_include_pad)};
267
})
268 269
.set_num_outputs(1)
.set_num_inputs(1)
270 271 272
.set_support_level(2);


273
DMLC_REGISTER_PARAMETER(GlobalPool2DParam);
274 275 276 277

inline bool GlobalPool2DInferShape(const nnvm::NodeAttrs& attrs,
                                   std::vector<TShape>* in_shape,
                                   std::vector<TShape>* out_shape) {
278
  static const Layout kNCHW("NCHW");
279
  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
280 281
  CHECK_EQ(in_shape->size(), 1U);
  CHECK_EQ(out_shape->size(), 1U);
282

283 284
  TShape dshape = (*in_shape)[0];
  if (dshape.ndim() ==  0) return false;
285 286 287 288 289 290 291 292 293 294 295 296 297

  CHECK_GE(dshape.ndim(), 2U)
    << "Pool2D only support input >= 2-D: input must have height and width";

  Layout layout(param.layout);
  CHECK(layout.contains('H') && layout.contains('W') &&
        !layout.contains('h') && !layout.contains('w'))
    << "Invalid layout " << layout
    << ". Pool2D layout must have H and W, which cannot be split";

  const auto hidx = layout.indexof('H');
  const auto widx = layout.indexof('W');

298
  TShape oshape = dshape;
299
  oshape[hidx] = oshape[widx] = 1;
300 301 302 303
  NNVM_ASSIGN_OUTPUT_SHAPE(attrs, *out_shape, 0, oshape);
  return true;
}

304 305 306 307
inline bool GlobalPool2DCorrectLayout(const NodeAttrs& attrs,
                                      std::vector<Layout> *ilayouts,
                                      const std::vector<Layout> *last_ilayouts,
                                      std::vector<Layout> *olayouts) {
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
  const GlobalPool2DParam &param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
  CHECK_EQ(ilayouts->size(), 1);
  CHECK_EQ(last_ilayouts->size(), 1);
  CHECK_EQ(olayouts->size(), 1);

  Layout input = (*ilayouts)[0];
  const Layout layout(param.layout);

  if (input.defined()) {
    CHECK(input.convertible(layout)) << "Invalid input layout " << input;
    if (input.indexof('W') != layout.indexof('W') ||
        input.indexof('H') != layout.indexof('H') ||
        input.contains('w') || input.contains('h')) {
      // as long as the index doesn't change for width and height
      // pool2d can keep the input layout.
      input = layout;
    }
  } else {
    input = layout;
  }

  NNVM_ASSIGN_LAYOUT(*ilayouts, 0, input);
  NNVM_ASSIGN_LAYOUT(*olayouts, 0, input);

  return true;
}

335 336 337 338 339 340 341 342 343 344
NNVM_REGISTER_OP(global_max_pool2d)
.describe(R"code(Global max pooling operation for 2D data.

- **data**: This depends on the `layout` parameter. Input is 4D array of shape
            (batch_size, channels, height, width) if `layout` is `NCHW`.
- **out**: This depends on the `layout` parameter. Output is 4D array of shape
           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.

)code" NNVM_ADD_FILELINE)
.add_argument("data", "4D Tensor", "Input data.")
345 346
.add_arguments(GlobalPool2DParam::__FIELDS__())
.set_attr_parser(ParamParser<GlobalPool2DParam>)
347
.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
348 349
.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
350
.set_attr<FCorrectLayout>("FCorrectLayout", GlobalPool2DCorrectLayout)
351 352 353 354
.set_attr<FTVMCompute>(
  "FTVMCompute", [](const NodeAttrs& attrs,
                    const Array<Tensor>& inputs,
                    const Array<Tensor>& out_info) {
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
  Layout layout(param.layout);
  CHECK(layout.convertible(Layout("NCHW")))
    << "global_max_pool2d currently only supports layouts that are convertible from NCHW";
  CHECK_EQ(layout.indexof('h'), -1)
    << "global_max_pool2d does not support input split on height";
  CHECK_EQ(layout.indexof('w'), -1)
    << "global_max_pool2d does not support input split on width";

  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
    << "Pool2D only support 4-D input (e.g., NCHW)"
    << " or 5-D input (last dimension is a split of channel)";

  return Array<Tensor>{
    topi::nn::global_pool(inputs[0], topi::nn::kMaxPool, layout.name()) };
370
})
371 372
.set_num_outputs(1)
.set_num_inputs(1)
373 374 375 376 377 378 379 380 381 382 383 384 385
.set_support_level(2);


NNVM_REGISTER_OP(global_avg_pool2d)
.describe(R"code(Global average pooling operation for 2D data.

- **data**: This depends on the `layout` parameter. Input is 4D array of shape
            (batch_size, channels, height, width) if `layout` is `NCHW`.
- **out**: This depends on the `layout` parameter. Output is 4D array of shape
           (batch_size, channels, 1, 1)  if `layout` is `NCHW`.

)code" NNVM_ADD_FILELINE)
.add_argument("data", "4D Tensor", "Input data.")
386 387
.add_arguments(GlobalPool2DParam::__FIELDS__())
.set_attr_parser(ParamParser<GlobalPool2DParam>)
388
.set_attr<FGetAttrDict>("FGetAttrDict", ParamGetAttrDict<GlobalPool2DParam>)
389 390
.set_attr<FInferShape>("FInferShape", GlobalPool2DInferShape)
.set_attr<FInferType>("FInferType", ElemwiseType<1, 1>)
391
.set_attr<FCorrectLayout>("FCorrectLayout", GlobalPool2DCorrectLayout)
392 393 394 395
.set_attr<FTVMCompute>(
  "FTVMCompute", [](const NodeAttrs& attrs,
                    const Array<Tensor>& inputs,
                    const Array<Tensor>& out_info) {
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
  const GlobalPool2DParam& param = nnvm::get<GlobalPool2DParam>(attrs.parsed);
  Layout layout(param.layout);
  CHECK(layout.convertible(Layout("NCHW")))
    << "global_avg_pool2d currently only supports layouts that are convertible from NCHW";
  CHECK_EQ(layout.indexof('h'), -1)
    << "global_avg_pool2d does not support input split on height";
  CHECK_EQ(layout.indexof('w'), -1)
    << "global_avg_pool2d does not support input split on width";

  CHECK(inputs[0].ndim() == 4U || inputs[0].ndim() == 5U)
    << "Pool2D only support 4-D input (e.g., NCHW)"
    << " or 5-D input (last dimension is a split of channel)";

  return Array<Tensor>{
    topi::nn::global_pool(inputs[0], topi::nn::kAvgPool, layout.name()) };
411
})
412 413
.set_num_outputs(1)
.set_num_inputs(1)
414 415 416 417
.set_support_level(2);

}  // namespace top
}  // namespace nnvm