Repository: pytorch/pytorch

Issue:
ONNX export fails for Pooling modules with kernel_size as tuple
When exporting a model like 
```
model = nn.Sequential(
    nn.Conv2d(1, n1, kernel_size=3, stride=1, padding=1),
    nn.Tanh(),
    nn.MaxPool2d(kernel_size=(1, 2))
```
to ONNX, export fails with
```
TypeError: is_(): incompatible function arguments. The following argument types are supported:
    1. (self: torch._C.Node, arg0: unicode, arg1: List[int]) -> torch._C.Node

Invoked with: %32 : UNKNOWN_TYPE = MaxPool(%31), uses = [];
, 'kernel_shape', [(1, 2), (1, 2)]
```

The error doesn't show up when passing `kernel_size` as a scalar.

This commit https://github.com/pytorch/pytorch/commit/3b5a5a6f9caaf0e545331bb8d15750c6acbb5e1b
is responsible, we need to manage both cases, scalar or tuple. And probably check that this is taken care of in other modules.

@soumith I can take care of this.


Discussion:
That diagnosis sounds spot on. Thank you!

Resolving patch:
diff --git a/torch/nn/_functions/thnn/pooling.py b/torch/nn/_functions/thnn/pooling.py
--- a/torch/nn/_functions/thnn/pooling.py
+++ b/torch/nn/_functions/thnn/pooling.py
@@ -15,10 +15,10 @@ def symbolic(g, input, kernel_size, stride=None, padding=0, dilation=1,
             raise RuntimeError("ceil_mode not supported in MaxPool1d")
         stride = stride or kernel_size
         n = g.appendNode(g.create("MaxPool", [input])
-                          .is_("kernel_shape", [kernel_size])
-                          .is_("pads", [padding] * 2)
-                          .is_("dilations", [dilation])
-                          .is_("strides", [stride]))
+                          .is_("kernel_shape", _single(kernel_size))
+                          .is_("pads", _single(padding))
+                          .is_("dilations", _single(dilation))
+                          .is_("strides", _single(stride)))
         return (n, None)
 
     @staticmethod
@@ -99,10 +99,10 @@ def symbolic(g, input, kernel_size, stride=None, padding=0, dilation=1,
         if ceil_mode:
             raise RuntimeError("ceil_mode not supported in MaxPool2d")
         n = g.appendNode(g.create("MaxPool", [input])
-                          .is_("kernel_shape", [kernel_size] * 2)
-                          .is_("pads", [padding] * 4)
-                          .is_("dilations", [dilation] * 2)
-                          .is_("strides", [stride] * 2))
+                          .is_("kernel_shape", _pair(kernel_size))
+                          .is_("pads", _pair(padding))
+                          .is_("dilations", _pair(dilation))
+                          .is_("strides", _pair(stride)))
         return (n, None)
 
     @staticmethod
@@ -169,6 +169,18 @@ def backward(ctx, ggI, _ggIndices=None):
 
 class MaxPool3d(Function):
 
+    @staticmethod
+    def symbolic(g, input, kernel_size, stride=None, padding=0, dilation=1,
+                 ceil_mode=False):
+        if ceil_mode:
+            raise RuntimeError("ceil_mode not supported in MaxPool3d")
+        n = g.appendNode(g.create("MaxPool", [input])
+                          .is_("kernel_shape", _triple(kernel_size))
+                          .is_("pads", _triple(padding))
+                          .is_("dilations", _triple(dilation))
+                          .is_("strides", _triple(stride)))
+        return (n, None)
+
     @staticmethod
     def forward(ctx, input, kernel_size, stride=None, padding=0, dilation=1,
                 ceil_mode=False):


Test patch:
diff --git a/test/expect/TestONNX.test_maxpool.expect b/test/expect/TestONNX.test_maxpool.expect
--- a/test/expect/TestONNX.test_maxpool.expect
+++ b/test/expect/TestONNX.test_maxpool.expect
@@ -9,7 +9,6 @@ node {
   attribute {
     name: "pads"
     ints: 0
-    ints: 0
   }
   attribute {
     name: "dilations"