Repository: chainer/chainer

Issue:
Cupy fusion bug
(not sure if this is supposed to go in github.com/cupy/cupy yet or if this is still the right place)

This code works:
```python
@cupy.fuse()
def sigsbwd(gy, y1, y2):
    for y in (y1, y2):
        gy = gy * y * (1.0 - y)
    return gy
sigsbwd(gy, *ys) # ys is a list of 2 ndarrays
```
but this doesn't (the code generator makes two `v1` variables, one of which is a `&float` and the other a `float`, and nvcc throws an error):
```python
@cupy.fuse()
def sigsbwd(gy, *ys):
    for y in ys:
        gy = gy * y * (1.0 - y)
    return gy
sigsbwd(gy, *ys) # ys is a list of 2 ndarrays
```
I thought these were syntactically equivalent at the Python level, but I guess fusion is doing some fairly low-level Python manipulation and is tripped up by the varargs.


Discussion:

Resolving patch:
diff --git a/cupy/core/fusion.py b/cupy/core/fusion.py
--- a/cupy/core/fusion.py
+++ b/cupy/core/fusion.py
@@ -1,4 +1,3 @@
-import inspect
 import six
 from six.moves import builtins
 import string
@@ -489,8 +488,6 @@ def _get_fix_code(data_type, fixed_type, operation):
 
 
 def _get_fusion(func, nin, reduce, post_map, identity, input_types, name=None):
-    if nin is None:
-        nin = len(inspect.getargspec(func).args)
     in_vars = [_FusionVar(i, t) for i, t in enumerate(input_types)]
     mem = _FusionMem(in_vars)
     in_refs = [_FusionRef(_, mem) for _ in in_vars]
@@ -611,7 +608,11 @@ def is_cupy_data(a):
             types = [_.dtype for _ in args]
             key = tuple(types)
             if key not in self._memo:
-                f = _get_fusion(self.func, self.input_num, self.reduce,
+                if self.input_num is not None:
+                    nin = self.input_num
+                else:
+                    nin = len(args)
+                f = _get_fusion(self.func, nin, self.reduce,
                                 self.post_map, self.identity, types)
                 self._memo[key] = f
             f = self._memo[key]


Test patch:
diff --git a/tests/cupy_tests/core_tests/test_fusion.py b/tests/cupy_tests/core_tests/test_fusion.py
--- a/tests/cupy_tests/core_tests/test_fusion.py
+++ b/tests/cupy_tests/core_tests/test_fusion.py
@@ -543,8 +543,13 @@ def random_real(self, lower=-1000, higher=1000):
         return numpy.random.rand(10, 10) * (higher - lower) + lower
 
     def check(self, func, n, gen, *args):
+        self._check(func, n, gen, args, True)
+        self._check(func, n, gen, args, False)
 
-        @cupy.fuse(input_num=n)
+    def _check(self, func, n, gen, args, omit_nin):
+        nin = n if not omit_nin else None
+
+        @cupy.fuse(input_num=nin)
         def f(*x):
             return func(*x)
 
@@ -569,8 +574,13 @@ def f(*x):
             numpy.testing.assert_array_almost_equal(n, fc.get())
 
     def check_reduce(self, func, n, reduce_f, gen, *args):
+        self._check_reduce(func, n, reduce_f, gen, args, True)
+        self._check_reduce(func, n, reduce_f, gen, args, False)
+
+    def _check_reduce(self, func, n, reduce_f, gen, args, omit_nin):
+        nin = n if not omit_nin else None
 
-        @cupy.fuse(input_num=n, reduce=reduce_f)
+        @cupy.fuse(input_num=nin, reduce=reduce_f)
         def f(*x):
             return func(*x)