Repository: chainer/chainer

Issue:
Double backprop exception
A simple double-backprop operation fails with an exception.
Tested on v3.0.0rc1 on Windows and Linux.

The forward pass runs successful, but the exception is thrown on the backward pass.

* Conditions
  - Chainer v3.0.0rc1
  - CuPy 1.0.3
  - Debian stretch/sid x86_64
  - CUDA v8.0, cuDNN 8.0 v6.0

* Code to reproduce (`problem.py`)

```python
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import optimizers

import numpy as np

print chainer.__version__

class GFunc(chainer.Chain):
    def __init__(self, d, dim_hidden=32):
        super(GFunc, self).__init__(
            l0 = L.Linear(d, dim_hidden),
            l1 = L.Linear(dim_hidden, dim_hidden),
            l2 = L.Linear(dim_hidden, d)
        )
        self.d = d

    def __call__(self, x):
        h1 = F.relu(self.l0(x))
        h2 = F.relu(self.l1(h1))
        h3 = self.l2(h2)

        return h3

class LDGEObjective(chainer.Chain):
    def __init__(self, G):
        super(LDGEObjective, self).__init__(
            G = G
        )

    def __call__(self, x):
        batchsize = x.shape[0]
        g = self.G(x)
        obj = F.sum(g*g)/batchsize

        # Compute gradient term
        gsum = F.sum(g)
        gx = chainer.grad((gsum,), (x,), enable_double_backprop=True)
        xgrad = gx[0]
        obj += 2.0*F.sum(xgrad)/batchsize

        return obj

G = GFunc(1)
ldge = LDGEObjective(G)

x = np.random.normal(size=(3,1)).astype(np.float32)
x = chainer.Variable(x)

obj = ldge(x)
print obj.data

obj.backward()
```

* Error messages, stack traces, or logs

```
$ python ./problem.py
3.0.0rc1
-0.602684617043
Traceback (most recent call last):
  File "./problem.py", line 54, in <module>
    obj.backward()
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/variable.py", line 852, in backward
    self._backward_main(retain_grad)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/variable.py", line 953, in _backward_main
    target_input_indexes, out_grad, in_grad)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/function_node.py", line 487, in backward_accumulate
    gxs = self.backward(target_input_indexes, grad_outputs)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/functions/connection/linear.py", line 53, in backward
    gW = linear(gy.T, x.T)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/functions/connection/linear.py", line 108, in linear
    y, = LinearFunction().apply(args)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/function_node.py", line 221, in apply
    outputs = self.forward(in_data)
  File "/mnt/nfs/users/senowozi/local/lib/python2.7/site-packages/chainer-3.0.0rc1-py2.7.egg/chainer/functions/connection/linear.py", line 37, in forward
    y = x.dot(W.T).astype(x.dtype, copy=False)
ValueError: On entry to SGEMV parameter number 8 had an illegal value
```



Discussion:

Resolving patch:
diff --git a/chainer/functions/connection/convolution_2d.py b/chainer/functions/connection/convolution_2d.py
--- a/chainer/functions/connection/convolution_2d.py
+++ b/chainer/functions/connection/convolution_2d.py
@@ -208,6 +208,14 @@ def forward_cpu(self, inputs):
         col = conv.im2col_cpu(
             x, self.kh, self.kw, self.sy, self.sx, self.ph, self.pw,
             cover_all=self.cover_all)
+
+        # NumPy raises an error when the array is not contiguous.
+        # See: https://github.com/chainer/chainer/issues/2744
+        # TODO(niboshi): Remove this code when NumPy is fixed.
+        if (not (gy.flags.c_contiguous or gy.flags.f_contiguous) and
+                1 in gy.shape):
+            gy = numpy.ascontiguousarray(gy)
+
         gW = numpy.tensordot(
             gy, col, ((0, 2, 3), (0, 4, 5))).astype(self.W_dtype, copy=False)
         return gW,
diff --git a/chainer/functions/connection/convolution_nd.py b/chainer/functions/connection/convolution_nd.py
--- a/chainer/functions/connection/convolution_nd.py
+++ b/chainer/functions/connection/convolution_nd.py
@@ -176,6 +176,15 @@ def _backward_xp(self, x, W, b, gy, xp):
         out_axes = (0,) + tuple(moves.range(2, ndim + 2))
         # (n, _, _, ..., _, out_1, out_2, ..., out_N)
         col_axes = (0,) + tuple(moves.range(ndim + 2, ndim * 2 + 2))
+
+        # NumPy raises an error when the array is not contiguous.
+        # See: https://github.com/chainer/chainer/issues/2744
+        # TODO(niboshi): Remove this code when NumPy is fixed.
+        if (xp is numpy and
+                not (gy.flags.c_contiguous or gy.flags.f_contiguous) and
+                1 in gy.shape):
+            gy = numpy.ascontiguousarray(gy)
+
         gW = xp.tensordot(gy, self.col, (out_axes, col_axes)).astype(
             W.dtype, copy=False)
 
diff --git a/chainer/functions/connection/linear.py b/chainer/functions/connection/linear.py
--- a/chainer/functions/connection/linear.py
+++ b/chainer/functions/connection/linear.py
@@ -1,3 +1,5 @@
+import numpy
+
 from chainer import function_node
 import chainer.functions
 from chainer.utils import type_check
@@ -34,6 +36,14 @@ def forward(self, inputs):
                              'type(W): {0}, type(x): {1}'
                              .format(type(W), type(x)))
 
+        # NumPy raises an error when the array is not contiguous.
+        # See: https://github.com/chainer/chainer/issues/2744
+        # TODO(niboshi): Remove this code when NumPy is fixed.
+        if (isinstance(x, numpy.ndarray) and
+                not (x.flags.c_contiguous or x.flags.f_contiguous) and
+                1 in x.shape):
+            x = numpy.ascontiguousarray(x)
+
         y = x.dot(W.T).astype(x.dtype, copy=False)
         if len(inputs) == 3:
             b = inputs[2]


Test patch:
diff --git a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
--- a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
+++ b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_2d.py
@@ -388,4 +388,25 @@ def _run_forward(self, x_data, W_data, b_data):
         return x, W, b, y
 
 
+class TestConvolution2DBackwardNoncontiguousGradOutputs(unittest.TestCase):
+    # NumPy raises an error when the inputs of dot operation are not
+    # contiguous. This test ensures this issue is correctly handled.
+    # (https://github.com/chainer/chainer/issues/2744)
+
+    # This test depdends on that backward() of F.sum generates
+    # a non-contiguous array.
+
+    def test_1(self):
+        n_batches = 2
+        in_channels = 3
+        out_channels = 1  # important
+        x_shape = (n_batches, in_channels, 10, 10)
+        w_shape = (out_channels, in_channels, 3, 3)
+        x = numpy.ones(x_shape, numpy.float32)
+        w = numpy.ones(w_shape, numpy.float32)
+        y = F.convolution_2d(x, chainer.Variable(w))
+        z = F.sum(y)
+        z.backward()
+
+
 testing.run_module(__name__, __file__)
diff --git a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_nd.py b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_nd.py
--- a/tests/chainer_tests/functions_tests/connection_tests/test_convolution_nd.py
+++ b/tests/chainer_tests/functions_tests/connection_tests/test_convolution_nd.py
@@ -267,4 +267,37 @@ def test_array_supplied_gpu(self):
                                   cuda.to_gpu(self.b_data))
 
 
+class TestConvolutionNDBackwardNoncontiguousGradOutputs(unittest.TestCase):
+    # NumPy raises an error when the inputs of dot operation are not
+    # contiguous. This test ensures this issue is correctly handled.
+    # (https://github.com/chainer/chainer/issues/2744)
+
+    # This test depdends on that backward() of F.sum generates
+    # a non-contiguous array.
+
+    def test_1(self):
+        n_batches = 2
+        in_channels = 3
+        out_channels = 1  # important
+        x_shape = (n_batches, in_channels, 4)
+        w_shape = (out_channels, in_channels, 3)
+        x = numpy.ones(x_shape, numpy.float32)
+        w = numpy.ones(w_shape, numpy.float32)
+        y = functions.convolution_nd(chainer.Variable(x), w)
+        z = functions.sum(y)
+        z.backward()
+
+    def test_2(self):
+        n_batches = 2
+        in_channels = 3
+        out_channels = 1  # important
+        x_shape = (n_batches, in_channels, 4)
+        w_shape = (out_channels, in_channels, 3)
+        x = numpy.ones(x_shape, numpy.float32)
+        w = numpy.ones(w_shape, numpy.float32)
+        y = functions.convolution_nd(x, chainer.Variable(w))
+        z = functions.sum(y)
+        z.backward()
+
+
 testing.run_module(__name__, __file__)
diff --git a/tests/chainer_tests/functions_tests/connection_tests/test_linear.py b/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
--- a/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
+++ b/tests/chainer_tests/functions_tests/connection_tests/test_linear.py
@@ -151,4 +151,25 @@ def test_double_backward_gpu_nobias(self):
             None)
 
 
+class TestLinearBackwardNoncontiguousGradOutputs(unittest.TestCase):
+    # NumPy raises an error when the inputs of dot operation are not
+    # contiguous. This test ensures this issue is correctly handled.
+    # (https://github.com/chainer/chainer/issues/2744)
+
+    # This test depdends on that backward() of F.sum generates
+    # a non-contiguous array.
+
+    def test_1(self):
+        n_batches = 1  # important
+        in_dims = (2, 2)
+        out_dim = 3
+        x_shape = (n_batches,) + in_dims
+        w_shape = (out_dim, numpy.prod(in_dims),)
+        x = numpy.ones(x_shape, numpy.float32)
+        w = numpy.ones(w_shape, numpy.float32)
+        y = functions.linear(chainer.Variable(x), w)
+        z = functions.sum(y)
+        z.backward()
+
+
 testing.run_module(__name__, __file__)