Skip to content

Commit 5bf39f2

Browse files
Josh Frommtrevor-m
authored andcommitted
[Relay][ONNX] 1-D global and adaptive pooling. (apache#7906)
* 1D adaptive pooling added and tested. * Apply formatting. * Add onnx integration and tests. * Busted by lint.
1 parent 9216cf8 commit 5bf39f2

File tree

10 files changed

+495
-37
lines changed

10 files changed

+495
-37
lines changed

include/tvm/relay/attrs/nn.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,22 @@ struct GlobalPool2DAttrs : public tvm::AttrsNode<GlobalPool2DAttrs> {
760760
}
761761
};
762762

763-
/*! \brief Attributes for adaptive pool operator */
763+
/*! \brief Attributes for 1d adaptive pool operator */
764+
struct AdaptivePool1DAttrs : public tvm::AttrsNode<AdaptivePool1DAttrs> {
765+
Array<IndexExpr> output_size;
766+
std::string layout;
767+
768+
TVM_DECLARE_ATTRS(AdaptivePool1DAttrs, "relay.attrs.AdaptivePool1DAttrs") {
769+
TVM_ATTR_FIELD(output_size).set_default(Array<IndexExpr>({})).describe("Output width.");
770+
TVM_ATTR_FIELD(layout).set_default("NCW").describe(
771+
"Dimension ordering of input data. Can be 'NCW', 'NWC', etc."
772+
"'N', 'C', 'W' stands for batch, channel, and width"
773+
"dimensions respectively. Pooling is applied on the"
774+
"'W' dimension.");
775+
}
776+
};
777+
778+
/*! \brief Attributes for 2d adaptive pool operator */
764779
struct AdaptivePool2DAttrs : public tvm::AttrsNode<AdaptivePool2DAttrs> {
765780
Array<IndexExpr> output_size;
766781
std::string layout;
@@ -777,6 +792,7 @@ struct AdaptivePool2DAttrs : public tvm::AttrsNode<AdaptivePool2DAttrs> {
777792
}
778793
};
779794

795+
/*! \brief Attributes for 3d adaptive pool operator */
780796
struct AdaptivePool3DAttrs : public tvm::AttrsNode<AdaptivePool3DAttrs> {
781797
Array<IndexExpr> output_size;
782798
std::string layout;

include/tvm/topi/nn/pooling.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,21 @@ inline Tensor adaptive_pool3d(const Tensor& x, const Array<PrimExpr>& output_siz
613613
return adaptive_pool_impl(x, output_size, pool_type, {depth_axis, height_axis, width_axis});
614614
}
615615

616+
/*!
617+
* \brief Adaptively perform pooling on one dimensional data.
618+
* See the two dimensional version above for details.
619+
* \param x The input tensor
620+
* \param output_size Vector of one int: {output_width}
621+
* \param pool_type The type of pooling operator
622+
* \param layout The input layout. The default is "NCW".
623+
*/
624+
inline Tensor adaptive_pool1d(const Tensor& x, const Array<PrimExpr>& output_size,
625+
PoolType pool_type, const std::string& layout = "NCW") {
626+
int width_axis = -1;
627+
ICHECK(find_width(layout, &width_axis)) << "Unsupported layout " << layout;
628+
return adaptive_pool_impl(x, output_size, pool_type, {width_axis});
629+
}
630+
616631
/*!
617632
* \brief Perform global pooling on height and width dimension of data.
618633
* It decides the height and width dimension according to the layout string,

python/tvm/relay/frontend/onnx.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,42 @@ def _impl_v1(cls, inputs, attr, params):
501501
return out
502502

503503

504+
class GlobalAveragePool(OnnxOpConverter):
505+
"""Operator converter for GlobalAveragePool"""
506+
507+
@classmethod
508+
def _impl_v1(cls, inputs, attr, params):
509+
rank = len(infer_shape(inputs[0]))
510+
if rank == 3:
511+
return _op.nn.global_avg_pool1d(inputs[0])
512+
if rank == 4:
513+
return _op.nn.global_avg_pool2d(inputs[0])
514+
if rank == 5:
515+
return _op.nn.global_avg_pool3d(inputs[0])
516+
raise NotImplementedError(
517+
"Global average pooling is only implemented for 1D, 2D, and 3D kernels, got %dD."
518+
% (rank - 2),
519+
)
520+
521+
522+
class GlobalMaxPool(OnnxOpConverter):
523+
"""Operator converter for GlobalMaxPool"""
524+
525+
@classmethod
526+
def _impl_v1(cls, inputs, attr, params):
527+
rank = len(infer_shape(inputs[0]))
528+
if rank == 3:
529+
return _op.nn.global_max_pool1d(inputs[0])
530+
if rank == 4:
531+
return _op.nn.global_max_pool2d(inputs[0])
532+
if rank == 5:
533+
return _op.nn.global_max_pool3d(inputs[0])
534+
raise NotImplementedError(
535+
"Global max pooling is only implemented for 1D, 2D, and 3D kernels, got %dD."
536+
% (rank - 2),
537+
)
538+
539+
504540
class Div(Elemwise):
505541
"""Operator converter for Divide."""
506542

@@ -2775,8 +2811,8 @@ def _get_convert_map(opset):
27752811
"MaxUnpool": MaxUnpool.get_converter(opset),
27762812
"Conv": Conv.get_converter(opset),
27772813
"ConvTranspose": ConvTranspose.get_converter(opset),
2778-
"GlobalAveragePool": Renamer("global_avg_pool2d"),
2779-
"GlobalMaxPool": Renamer("global_max_pool2d"),
2814+
"GlobalAveragePool": GlobalAveragePool.get_converter(opset),
2815+
"GlobalMaxPool": GlobalMaxPool.get_converter(opset),
27802816
"BatchNormalization": BatchNorm.get_converter(opset),
27812817
"InstanceNormalization": InstanceNorm.get_converter(opset),
27822818
# 'LpNormalization'

python/tvm/relay/op/nn/_nn.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,16 @@ def compute_contrib_conv3d_winograd_weight_transform(attrs, inputs, out_dtype):
504504
reg.register_pattern("nn.avg_pool2d_grad", OpPattern.OUT_ELEMWISE_FUSABLE)
505505

506506

507+
# adaptive_max_pool1d
508+
reg.register_schedule("nn.adaptive_max_pool1d", strategy.schedule_adaptive_pool)
509+
reg.register_pattern("nn.adaptive_max_pool1d", OpPattern.OUT_ELEMWISE_FUSABLE)
510+
511+
512+
# adaptive_avg_pool1d
513+
reg.register_schedule("nn.adaptive_avg_pool1d", strategy.schedule_adaptive_pool)
514+
reg.register_pattern("nn.adaptive_avg_pool1d", OpPattern.OUT_ELEMWISE_FUSABLE)
515+
516+
507517
# global_max_pool2d
508518
reg.register_schedule("nn.global_max_pool2d", strategy.schedule_adaptive_pool)
509519
reg.register_pattern("nn.global_max_pool2d", OpPattern.OUT_ELEMWISE_FUSABLE)

python/tvm/relay/op/nn/nn.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2964,6 +2964,94 @@ def space_to_depth(data, block_size, layout="NCHW"):
29642964
return _make.space_to_depth(data, block_size, layout)
29652965

29662966

2967+
def adaptive_max_pool1d(data, output_size=None, layout="NCW"):
2968+
r"""1D adaptive max pooling operator. This operator is experimental.
2969+
2970+
This operator takes data as input and does 1D max value calculation
2971+
across each window represented by W.
2972+
2973+
2974+
In the default case, where the data_layout is `NCW`
2975+
a data Tensor with shape `(batch_size, in_channels, width)`,
2976+
to produce an output Tensor with shape
2977+
(batch_size, in_channels, output_width).
2978+
2979+
The pooling kernel and stride sizes are automatically chosen for
2980+
desired output sizes.
2981+
2982+
For output_size:
2983+
If this argument is not provided, input height and width will be used
2984+
as output height and width.
2985+
2986+
If a single integer is provided for output_size, the output size is
2987+
(N x C x output_size) for any input (NCW).
2988+
2989+
Parameters
2990+
----------
2991+
data : tvm.relay.Expr
2992+
The input data to the operator.
2993+
2994+
output_size : tuple of int. optional
2995+
Output height and width.
2996+
2997+
layout : str, optional
2998+
Layout of the input.
2999+
3000+
Returns
3001+
-------
3002+
result : tvm.relay.Expr
3003+
The computed result.
3004+
"""
3005+
output_size = [] or output_size
3006+
if isinstance(output_size, int):
3007+
output_size = [output_size]
3008+
return _make.adaptive_max_pool1d(data, output_size, layout)
3009+
3010+
3011+
def adaptive_avg_pool1d(data, output_size=None, layout="NCW"):
3012+
r"""1D adaptive average pooling operator. This operator is experimental.
3013+
3014+
This operator takes data as input and does 1D average value calculation
3015+
across each window represented by W.
3016+
3017+
3018+
In the default case, where the data_layout is `NCW`
3019+
a data Tensor with shape `(batch_size, in_channels, width)`,
3020+
to produce an output Tensor with shape
3021+
(batch_size, in_channels, output_width).
3022+
3023+
The pooling kernel and stride sizes are automatically chosen for
3024+
desired output sizes.
3025+
3026+
For output_size:
3027+
If this argument is not provided, input height and width will be used
3028+
as output width.
3029+
3030+
If a single integer is provided for output_size, the output size is
3031+
(N x C x output_size) for any input (NCW).
3032+
3033+
Parameters
3034+
----------
3035+
data : tvm.relay.Expr
3036+
The input data to the operator.
3037+
3038+
output_size : tuple of int. optional
3039+
Output height and width.
3040+
3041+
layout : str, optional
3042+
Layout of the input.
3043+
3044+
Returns
3045+
-------
3046+
result : tvm.relay.Expr
3047+
The computed result.
3048+
"""
3049+
output_size = [] or output_size
3050+
if isinstance(output_size, int):
3051+
output_size = [output_size]
3052+
return _make.adaptive_avg_pool1d(data, output_size, layout)
3053+
3054+
29673055
def adaptive_max_pool2d(data, output_size=None, layout="NCHW"):
29683056
r"""2D adaptive max pooling operator. This operator is experimental.
29693057
@@ -3142,6 +3230,71 @@ def adaptive_avg_pool3d(data, output_size=None, layout="NCDHW"):
31423230
return _make.adaptive_avg_pool3d(data, output_size, layout)
31433231

31443232

3233+
def global_max_pool1d(data, layout="NCW"):
3234+
r"""1D global maximum pooling operator.
3235+
3236+
This operator takes data as input and does 1D max value calculation
3237+
across each window represented by W.
3238+
3239+
In the default case, where the data_layout is `NCW`
3240+
a data Tensor with shape `(batch_size, in_channels, width)`,
3241+
to produce an output Tensor with the following rule:
3242+
3243+
with data of shape (b, c, w)
3244+
.. math::
3245+
3246+
\mbox{out}(b, c, 1) = \max_{n=0, \ldots, w} \mbox{data}(b, c, n)
3247+
3248+
Parameters
3249+
----------
3250+
data : tvm.relay.Expr
3251+
The input data to the operator.
3252+
3253+
layout : str, optional
3254+
Layout of the input.
3255+
3256+
Returns
3257+
-------
3258+
result : tvm.relay.Expr
3259+
The computed result.
3260+
"""
3261+
output_size = [1]
3262+
return _make.adaptive_max_pool1d(data, output_size, layout)
3263+
3264+
3265+
def global_avg_pool1d(data, layout="NCW"):
3266+
r"""1D global average pooling operator.
3267+
3268+
This operator takes data as input and does 1D average value calculation
3269+
across each window represented by W.
3270+
3271+
In the default case, where the data_layout is `NCW`
3272+
a data Tensor with shape `(batch_size, in_channels, width)`,
3273+
to produce an output Tensor with the following rule:
3274+
3275+
with data of shape (b, c, w)
3276+
3277+
.. math::
3278+
3279+
\mbox{out}(b, c, 1) = \frac{1}{w} \sum_{n=0}^{w-1} \mbox{data}(b, c, n)
3280+
3281+
Parameters
3282+
----------
3283+
data : tvm.relay.Expr
3284+
The input data to the operator.
3285+
3286+
layout : str, optional
3287+
Layout of the input.
3288+
3289+
Returns
3290+
-------
3291+
result : tvm.relay.Expr
3292+
The computed result.
3293+
"""
3294+
output_size = [1]
3295+
return _make.adaptive_avg_pool1d(data, output_size, layout)
3296+
3297+
31453298
def global_max_pool3d(data, layout="NCDHW"):
31463299
r"""3D global maximum pooling operator.
31473300

python/tvm/topi/testing/adaptive_pool_python.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ def _end_index(index, odim, idim):
2727
return int(np.ceil((index + 1) * idim / odim))
2828

2929

30+
def _pool1d(in_size, out_size, np_data, np_op):
31+
out = np.zeros(out_size).astype(np_data.dtype)
32+
ow = out_size[0]
33+
for l in range(ow):
34+
l_start = _start_index(l, ow, in_size[0])
35+
l_end = _end_index(l, ow, in_size[0])
36+
l_sl = slice(l_start, l_end)
37+
out[l] = np_op(np_data[l_sl])
38+
return out
39+
40+
3041
def _pool2d(in_size, out_size, np_data, np_op):
3142
out = np.zeros(out_size).astype(np_data.dtype)
3243
oh, ow = out_size
@@ -61,8 +72,8 @@ def _pool3d(in_size, out_size, np_data, np_op):
6172
return out
6273

6374

64-
def adaptive_pool_nchw(np_data, out_size, pool_op, np_op):
65-
""" The reference function for adaptive pool, nchw layout """
75+
def adaptive_pool_channel_first(np_data, out_size, pool_op, np_op):
76+
""" The reference function for adaptive pool, channel first layout """
6677
ishape = np_data.shape
6778
n, c = ishape[:2]
6879
oshape = (n, c) + out_size
@@ -75,16 +86,18 @@ def adaptive_pool_nchw(np_data, out_size, pool_op, np_op):
7586
return np_out
7687

7788

78-
def adaptive_pool_nhwc(np_data, out_size, pool_op, np_op):
79-
""" The reference function for adaptive pool, nhwc layout """
89+
def adaptive_pool_channel_last(np_data, out_size, pool_op, np_op):
90+
""" The reference function for adaptive pool, channel last layout """
8091
ishape = np_data.shape
8192
n, c = ishape[0], ishape[-1]
8293
oshape = (n,) + out_size + (c,)
8394
np_out = np.zeros(oshape).astype(np_data.dtype)
8495

8596
for i in range(n):
8697
for j in range(c):
87-
if len(out_size) == 2:
98+
if len(out_size) == 1:
99+
np_out[i, :, j] = pool_op(ishape[1:-1], out_size, np_data[i, :, j], np_op)
100+
elif len(out_size) == 2:
88101
np_out[i, :, :, j] = pool_op(ishape[1:-1], out_size, np_data[i, :, :, j], np_op)
89102
else:
90103
np_out[i, :, :, :, j] = pool_op(
@@ -96,16 +109,20 @@ def adaptive_pool_nhwc(np_data, out_size, pool_op, np_op):
96109

97110
def adaptive_pool(np_data, out_size, pool_type, layout):
98111
""" The reference function for adaptive pool, for 2d and 3d """
99-
if len(out_size) == 2:
112+
if isinstance(out_size, int):
113+
out_size = (out_size,)
114+
if len(out_size) == 1:
115+
pool_op = _pool1d
116+
elif len(out_size) == 2:
100117
pool_op = _pool2d
101118
else:
102119
assert len(out_size) == 3
103120
pool_op = _pool3d
104121

105122
np_op = np.mean if pool_type == "avg" else np.max
106123

107-
if layout in ["NCHW", "NCDHW"]:
108-
return adaptive_pool_nchw(np_data, out_size, pool_op, np_op)
124+
if layout in ["NCW", "NCHW", "NCDHW"]:
125+
return adaptive_pool_channel_first(np_data, out_size, pool_op, np_op)
109126

110-
assert layout in ["NHWC", "NDHWC"]
111-
return adaptive_pool_nhwc(np_data, out_size, pool_op, np_op)
127+
assert layout in ["NWC", "NHWC", "NDHWC"]
128+
return adaptive_pool_channel_last(np_data, out_size, pool_op, np_op)

0 commit comments

Comments
 (0)