Repository: chainer/chainer

Issue:
`MultiprocessParallelIUpdater` does not pass raw arrays to the loss function
Chainer v2 introduced a change that raw arrays are passed to the loss function by the updater:
http://docs.chainer.org/en/stable/upgrade.html#updater-and-evaluator-pass-raw-data-arrays-to-the-loss-function

`MultiprocessParallelUpdater` does not follow this change, which is inconsistent:
https://github.com/chainer/chainer/blob/master/chainer/training/updaters/multiprocess_parallel_updater.py#L236


Discussion:

Resolving patch:
diff --git a/chainer/training/updaters/multiprocess_parallel_updater.py b/chainer/training/updaters/multiprocess_parallel_updater.py
--- a/chainer/training/updaters/multiprocess_parallel_updater.py
+++ b/chainer/training/updaters/multiprocess_parallel_updater.py
@@ -7,7 +7,6 @@
 from chainer.dataset import convert
 from chainer import reporter
 from chainer.training import updater
-from chainer import variable
 
 try:
     from cupy.cuda import nccl
@@ -233,15 +232,11 @@ def finalize(self):
 
 def _calc_loss(model, in_arrays):
     if isinstance(in_arrays, tuple):
-        in_vars = tuple(variable.Variable(x) for x in in_arrays)
-        return model(*in_vars)
+        return model(*in_arrays)
     elif isinstance(in_arrays, dict):
-        in_vars = {key: variable.Variable(x)
-                   for key, x in six.iteritems(in_arrays)}
-        return model(**in_vars)
+        return model(**in_arrays)
     else:
-        in_vars = variable.Variable(in_arrays)
-        return model(in_vars)
+        return model(in_arrays)
 
 
 def size_num_grads(link):


Test patch:
diff --git a/tests/chainer_tests/training_tests/updaters_tests/test_multiprocess_parallel_updater.py b/tests/chainer_tests/training_tests/updaters_tests/test_multiprocess_parallel_updater.py
--- a/tests/chainer_tests/training_tests/updaters_tests/test_multiprocess_parallel_updater.py
+++ b/tests/chainer_tests/training_tests/updaters_tests/test_multiprocess_parallel_updater.py
@@ -116,4 +116,60 @@ def test_gather_params_raise_on_cpu(self):
             mpu.gather_params(model)
 
 
+class SimpleNetRawArray(chainer.Chain):
+
+    def __init__(self, testcase):
+        super(SimpleNetRawArray, self).__init__(
+            conv=chainer.links.Convolution2D(2, 2, 3),
+            fc=chainer.links.Linear(18, 2),
+        )
+        self.train = True
+        self.call_called = 0
+        self.testcase = testcase
+
+    def clear(self):
+        self.loss = None
+        self.accuracy = None
+
+    def __call__(self, x, t):
+        self.testcase.assertNotIsInstance(x, chainer.Variable)
+        self.testcase.assertNotIsInstance(t, chainer.Variable)
+
+        self.call_called += 1
+
+        h = chainer.functions.relu(self.conv(x))
+        y = self.fc(h)
+
+        self.loss = chainer.functions.softmax_cross_entropy(y, t)
+        self.accuracy = chainer.functions.accuracy(y, t)
+
+        return self.loss
+
+
+class TestRawArray(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    @attr.gpu
+    def test_update_uses_raw_array(self):
+        if mpu.MultiprocessParallelUpdater.available():
+            model = SimpleNetRawArray(self)
+            dataset = [((numpy.ones((2, 5, 5)) * i).astype(numpy.float32),
+                        numpy.int32(0)) for i in range(100)]
+
+            batch_size = 5
+            devices = (1,)
+            iters = [chainer.iterators.SerialIterator(i, batch_size) for i in
+                     chainer.datasets.split_dataset_n_random(
+                         dataset, len(devices))]
+            optimizer = chainer.optimizers.SGD(lr=1.0)
+            optimizer.setup(model)
+            updater = mpu.MultiprocessParallelUpdater(
+                iters, optimizer, devices=devices)
+            updater.update()
+
+            self.assertEqual(model.call_called, 1)
+
+
 testing.run_module(__name__, __file__)