Skip to content

Commit

Permalink
Merge pull request #619 from shinh/ceil-mode
Browse files Browse the repository at this point in the history
Use ceil_mode of MaxPool
  • Loading branch information
shinh authored Aug 27, 2019
2 parents dddbfc9 + 3fbb1fc commit 743fa7c
Show file tree
Hide file tree
Showing 10 changed files with 20 additions and 170 deletions.
2 changes: 1 addition & 1 deletion chainer_compiler/ch2o/funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def call_impl(self, env, x, ksize, stride, pad, cover_all, return_indices):
'MaxPool',
inputs=[x.to_tensor(env).name],
kernel_shape=kernel_shape,
chainer_cover_all=cover_all.to_bool(),
ceil_mode=int(cover_all.to_bool()),
**kwargs)


Expand Down
8 changes: 4 additions & 4 deletions chainer_compiler/elichika/functions_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def get_onnx_dtype(dtype):
class BaseConverter(object):
def __init__(self):
self.expected_args = ()

def parse_args(self, onnx_graph, node):
assert hasattr(self, 'expected_args'), 'BaseConverter subclass must have `expected_args`'
parser = oc.NodeParse()
Expand All @@ -52,7 +52,7 @@ def parse_args(self, onnx_graph, node):
def __call__(self, onnx_graph, node):
raise NotImplementedError


class ConverterChainerMathMisc(BaseConverter):
def __init__(self, operator, arg_name = 'x'):
self.arg_name = arg_name
Expand Down Expand Up @@ -284,7 +284,7 @@ def __call__(self, onnx_graph, node):
str(node.lineprop),
min=parser.get('x_min'),
max=parser.get('x_max'))

class ConverterSum(BaseConverter):
def __init__(self):
self.expected_args = (
Expand Down Expand Up @@ -482,7 +482,7 @@ def __call__(self, onnx_graph, node):
[parser.get('x')],
node.outputs,
name=str(node.lineprop),
chainer_cover_all=parser.get('cover_all'),
ceil_mode=int(parser.get('cover_all')),
**kwargs)


Expand Down
6 changes: 4 additions & 2 deletions compiler/chxvm/simple_node_emitter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,10 @@ void EmitSimpleNode(const Node& node, const ValueIdManager& id_manager, ChxVMPro
CHECK_EQ(3UL, node.outputs().size());
CHECK(node.output(1)->IsNull());
}
EMIT(MaxPool, out(0), oout(2), in(0), node.kernel_shape(), strides(), pads(), node.chainer_cover_all(), auto_pad());
EMIT(MaxPool, out(0), oout(2), in(0), node.kernel_shape(), strides(), pads(), node.ceil_mode(), auto_pad());
} else if (node.op_type() == Node::kChainerMaxPoolGrad) {
CHECK_EQ("NOTSET", node.auto_pad()) << "auto_pad is not supported for MaxPool";
EMIT(MaxPoolGrad, out(0), in(0), in(1), node.kernel_shape(), node.chainer_cover_all());
EMIT(MaxPoolGrad, out(0), in(0), in(1), node.kernel_shape(), node.ceil_mode());
} else if (node.op_type() == Node::kChainerROIMaxPool2D) {
EMIT(ROIMaxPool2D, out(0), in(0), in(1), in(2), node.output_shape(), node.spatial_scale());
} else if (node.op_type() == Node::kChainerROIAveragePool2D) {
Expand All @@ -348,9 +348,11 @@ void EmitSimpleNode(const Node& node, const ValueIdManager& id_manager, ChxVMPro
} else if (node.op_type() == Node::kAveragePool) {
CHECK_EQ("NOTSET", node.auto_pad()) << "auto_pad is not supported for AveragePool";
CHECK_EQ(1UL, node.inputs().size());
CHECK_EQ(0, node.ceil_mode()) << "ceil_mode for AveragePool is not supported yet";
EMIT(AveragePool, out(0), oout(1), in(0), node.kernel_shape(), strides(), pads(), node.count_include_pad());
} else if (node.op_type() == Node::kChainerAveragePoolGrad) {
CHECK_EQ("NOTSET", node.auto_pad()) << "auto_pad is not supported for AveragePool";
CHECK_EQ(0, node.ceil_mode()) << "ceil_mode for AveragePool is not supported yet";
EMIT(AveragePoolGrad, out(0), in(0), in(1), node.kernel_shape(), node.count_include_pad());
} else if (node.op_type() == Node::kChainerPadBatchSize) {
EMIT(PadBatchSize, out(0), in(0), node.size());
Expand Down
154 changes: 0 additions & 154 deletions compiler/custom_onnx_ops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,159 +92,6 @@ ONNX_CHAINER_OPERATOR_SET_SCHEMA(

namespace {

// From onnx/onnx/defs/nn/defs.cc
void convPoolTypeAndShapeInference(InferenceContext& ctx, bool use_dilation, bool require_kernel_shape) {
propagateElemTypeFromInputToOutput(ctx, 0, 0);
if (ctx.getNumOutputs() > 1) {
// MaxPool with two outputs case.
auto output_type = ctx.getOutputType(1);
if (output_type->value_case() == TypeProto::kTensorType || output_type->value_case() == TypeProto::VALUE_NOT_SET) {
output_type->mutable_tensor_type()->set_elem_type(TensorProto::INT64);
}
}

// we need the first input shape for this inference.
if (!hasNInputShapes(ctx, 1)) {
return;
}

// if kernel shape is an input (and not attribute)
// we need the shape of the second input.
if (!require_kernel_shape && !hasNInputShapes(ctx, 2)) {
return;
}

// don't bother with legacy auto_pad for now
if (ctx.getAttribute("auto_pad")) {
return;
}

auto input_shape = ctx.getInputType(0)->tensor_type().shape();
if (input_shape.dim_size() < 2) {
fail_shape_inference("Input tensor must have atleast 2 dimensions");
}

// first dim is the batch axis and the next is the number of channels.
size_t n_input_dims = static_cast<size_t>(input_shape.dim_size() - 2);

// Pooling operations don't support dilation, only Conv. For
// simplicity of the code, we just treat them as having all-1s
// dilation.
std::vector<int64_t> dilations;
if (use_dilation && getRepeatedAttribute(ctx, "dilations", dilations)) {
if (dilations.size() != n_input_dims) {
fail_shape_inference("Attribute dilations has incorrect size");
}
} else {
dilations.assign(n_input_dims, 1);
}

std::vector<int64_t> pads;
if (getRepeatedAttribute(ctx, "pads", pads)) {
if (pads.size() != n_input_dims * 2) {
fail_shape_inference("Attribute pads has incorrect size");
}
} else {
pads.assign(n_input_dims * 2, 0);
}

std::vector<int64_t> strides;
if (getRepeatedAttribute(ctx, "strides", strides)) {
if (strides.size() != n_input_dims) {
fail_shape_inference("Attribute strides has incorrect size");
}
} else {
strides.assign(n_input_dims, 1);
}

std::vector<int64_t> kernel_shape;
if (getRepeatedAttribute(ctx, "kernel_shape", kernel_shape)) {
if (kernel_shape.size() != n_input_dims) {
fail_shape_inference("Attribute kernel_shape has incorrect size");
}
} else if (require_kernel_shape) {
fail_shape_inference("Attribute kernel_shape must be specified");
} else {
auto second_input_shape = ctx.getInputType(1)->tensor_type().shape();
for (int i = 2; i < second_input_shape.dim_size(); ++i) {
if (!second_input_shape.dim(i).has_dim_value()) {
return;
}
kernel_shape.push_back(second_input_shape.dim(i).dim_value());
}
}

auto output_shape = ctx.getOutputType(0)->mutable_tensor_type()->mutable_shape();

if (require_kernel_shape) {
// add the first two dimensions from the input.
*output_shape->add_dim() = input_shape.dim(0);
*output_shape->add_dim() = input_shape.dim(1);
} else {
*output_shape->add_dim() = input_shape.dim(0);
auto& second_input_shape = getInputShape(ctx, 1);
if (second_input_shape.dim_size() < 1) {
fail_shape_inference("Second input tensor has wrong dimension");
}
*output_shape->add_dim() = second_input_shape.dim(0);
}

// EDIT(hamaji): Check if `chainer_cover_all` is set.
const bool cover_all = getAttribute(ctx, "chainer_cover_all", 0);

int kernel_shape_size = static_cast<int>(kernel_shape.size());
for (int i = 0; i < kernel_shape_size; ++i) {
auto newdim = output_shape->add_dim();
if (!input_shape.dim(2 + i).has_dim_value()) {
continue;
}
// how big is the input, including padding
int64_t effective_input_size = input_shape.dim(2 + i).dim_value();
effective_input_size += pads[i];
effective_input_size += pads[i + kernel_shape_size];

int64_t effective_kernel_size = kernel_shape[i];
// accounting for dilation, how big is the kernel in this dimension
effective_kernel_size = (effective_kernel_size - 1) * dilations[i] + 1;

// how many times we can move the kernel from it's initial position, based
// on the stride
int64_t strided_kernel_positions = (effective_input_size - effective_kernel_size) / strides[i];
// EDIT(hamaji): Adjustment for `chainer_cover_all`.
if (cover_all && (effective_input_size - effective_kernel_size) % strides[i]) {
++strided_kernel_positions;
}

// add in the initial position
newdim->set_dim_value(1 + strided_kernel_positions);
}

if (ctx.getNumOutputs() > 1) {
// MaxPool with two outputs case.
auto second_output_shape = ctx.getOutputType(1)->mutable_tensor_type()->mutable_shape();
second_output_shape->CopyFrom(*output_shape);
}
}

} // namespace

ONNX_WORKAROUND_OPERATOR_SET_SCHEMA(
MaxPool,
11,
OpSchema()
.SetDoc("TBD")
.Input(0, "X", "Input tensor", "T")
.Output(0, "Y", "Output tensor", "T")
.Output(1, "Indices", "Indices tensor", "I", OpSchema::Optional)
.TypeConstraint(
"T",
{"tensor(float)", "tensor(float16)", "tensor(double)"},
"Constrain input and output types to signed numeric tensors.")
.TypeConstraint("I", {"tensor(int64)"}, "Constrain index tensor to int64")
.TypeAndShapeInferenceFunction([](InferenceContext& ctx) { convPoolTypeAndShapeInference(ctx, false, true); }));

namespace {

void InferROI(InferenceContext& ctx) {
propagateElemTypeFromInputToOutput(ctx, 0, 0);

Expand Down Expand Up @@ -612,7 +459,6 @@ class Custom_OpSet_Onnx_ver9 {
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Chainer, 9, ChainerSoftmaxCrossEntropy)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Chainer, 9, ChainerSelectItem)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Expand)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 11, MaxPool)>());
fn(GetOpSchema<ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 9, Split)>());
}
};
Expand Down
2 changes: 1 addition & 1 deletion compiler/fusion_ngraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void FuseNGraphOperations(Graph* graph) {
return false;
}
} else if (node.op_type() == Node::kMaxPool) {
if (node.chainer_cover_all()) {
if (node.ceil_mode()) {
return false;
}
} else if (
Expand Down
7 changes: 4 additions & 3 deletions compiler/gen_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ def __init__(self, op_type, num_inputs, num_outputs, domain='', **kwargs):
kernel_shape=Required([int]),
pads=[int],
storage_order=0,
strides=[int])
strides=[int],
ceil_mode=0)
# Extension: the third output is for backward context.
NodeDef('MaxPool', 1, (1, 2, 3), chainer_cover_all=False, **pool_attrs)
NodeDef('MaxPool', 1, (1, 2, 3), **pool_attrs)
# Extension: the second output is for backward context.
NodeDef('AveragePool', 1, (1, 2), count_include_pad=False, **pool_attrs)
NodeDef('GlobalMaxPool', 1, 1)
Expand Down Expand Up @@ -266,7 +267,7 @@ def __init__(self, op_type, num_inputs, num_outputs, domain='', **kwargs):
# For experimental ops.
NodeDef('ChainerDoSomething', None, None, function_name=Required(str))

NodeDef('ChainerMaxPoolGrad', 2, 1, chainer_cover_all=False, **pool_attrs)
NodeDef('ChainerMaxPoolGrad', 2, 1, **pool_attrs)
NodeDef('ChainerAveragePoolGrad', 2, 1, count_include_pad=False, **pool_attrs)
NodeDef('ChainerResizeGrad', 2, 1)
NodeDef('ChainerBatchNormalizationGrad', 2, 3)
Expand Down
2 changes: 1 addition & 1 deletion compiler/gradient_ops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ void MaxPoolGradFn(GradientOpContext* gc) {
->set_pads(node->pads())
->set_storage_order(node->storage_order())
->set_strides(node->strides())
->set_chainer_cover_all(node->chainer_cover_all());
->set_ceil_mode(node->ceil_mode());
}

void AveragePoolGradFn(GradientOpContext* gc) {
Expand Down
2 changes: 1 addition & 1 deletion compiler/simplifier.cc
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ bool ReplaceMaxPool(Graph* graph, Node* node) {
Value* padded = PadForPool(&gb, node, -std::numeric_limits<double>::infinity());
gb.Op(Node::kMaxPool, {padded}, node->output(0))
->producer()
->set_chainer_cover_all(node->chainer_cover_all())
->set_ceil_mode(node->ceil_mode())
->set_auto_pad(node->auto_pad())
->set_kernel_shape(node->kernel_shape())
->set_storage_order(node->storage_order())
Expand Down
5 changes: 2 additions & 3 deletions scripts/gen_extra_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,6 @@ def gen_incomplete_transpose_test(test_name):


def gen_maxpool_cover_all_test(test_name):
# A custom attribute for Chainer/ChainerX's `cover_all` parameter.
gb = onnx_script.GraphBuilder(test_name)

input = np.random.random((1, 3, 7, 7))
Expand All @@ -889,14 +888,14 @@ def gen_maxpool_cover_all_test(test_name):
outputs=['not_cover_all']),
F.max_pooling_2d(input, ksize=3, stride=2, cover_all=False))
gb.output(gb.MaxPool([input_v], kernel_shape=[3, 3], strides=[2, 2],
chainer_cover_all=True,
ceil_mode=1,
outputs=['cover_all']),
F.max_pooling_2d(input, ksize=3, stride=2, cover_all=True))
gb.output(gb.MaxPool([dynamic_v], kernel_shape=[3, 3], strides=[2, 2],
outputs=['not_cover_all_dynamic']),
F.max_pooling_2d(input, ksize=3, stride=2, cover_all=False))
gb.output(gb.MaxPool([dynamic_v], kernel_shape=[3, 3], strides=[2, 2],
chainer_cover_all=True,
ceil_mode=1,
outputs=['cover_all_dynamic']),
F.max_pooling_2d(input, ksize=3, stride=2, cover_all=True))

Expand Down
2 changes: 2 additions & 0 deletions scripts/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,11 @@
TestCase(NODE_TEST, 'test_constant_pad'),
# TODO(hamaji): auto_pad is not supported.
TestCase(NODE_TEST, 'test_maxpool_1d_default', fail=fail_1d_conv_pool),
TestCase(NODE_TEST, 'test_maxpool_2d_ceil'),
TestCase(NODE_TEST, 'test_maxpool_2d_default'),
TestCase(NODE_TEST, 'test_maxpool_2d_pads'),
TestCase(NODE_TEST, 'test_maxpool_2d_precomputed_pads'),
TestCase(NODE_TEST, 'test_maxpool_2d_precomputed_same_upper'),
TestCase(NODE_TEST, 'test_maxpool_2d_precomputed_strides'),
TestCase(NODE_TEST, 'test_maxpool_2d_strides'),
TestCase(NODE_TEST, 'test_maxpool_3d_default'),
Expand Down

0 comments on commit 743fa7c

Please sign in to comment.