This is an automated email from the ASF dual-hosted git repository.

masahi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-tvm.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f7745e  [Relay][Frontend][Onnx] GRU Layer Support (#6020)
9f7745e is described below

commit 9f7745e7c265787d02af8a044a397ab788f9b6e0
Author: Josh Fromm <[email protected]>
AuthorDate: Sun Jul 12 05:26:02 2020 -0700

    [Relay][Frontend][Onnx] GRU Layer Support (#6020)
    
    * GRU debugging and testing added to onnx frontend.
    
    * All tests working and code formatted.
    
    * Fix lint issues.
    
    * Add a test case and changed RNN argument parsing.
    
    * Small refactor.
---
 python/tvm/relay/frontend/onnx.py          | 140 ++++++++++++-
 tests/python/frontend/onnx/test_forward.py | 311 ++++++++++++++++++++++-------
 2 files changed, 366 insertions(+), 85 deletions(-)

diff --git a/python/tvm/relay/frontend/onnx.py 
b/python/tvm/relay/frontend/onnx.py
index 224428b..1568c97 100644
--- a/python/tvm/relay/frontend/onnx.py
+++ b/python/tvm/relay/frontend/onnx.py
@@ -60,6 +60,8 @@ class onnx_input():
 
     def __getitem__(self, item):
         if isinstance(item, int):
+            if item > (len(self.input_keys) - 1):
+                return None
             return self.input_dict[self.input_keys[item]]
         if isinstance(item, str):
             if item not in self.input_keys:
@@ -1493,8 +1495,8 @@ class Expand(OnnxOpConverter):
         return _op.broadcast_to(inputs[0], shape=tuple(shape))
 
 
-class LSTM(OnnxOpConverter):
-    """ Operator converter for LSTM.
+class RNN(OnnxOpConverter):
+    """ Operator converter for RNNs such as LSTM and GRU.
     """
 
     @classmethod
@@ -1528,18 +1530,23 @@ class LSTM(OnnxOpConverter):
         ]
         return activation.decode("utf-8") in needs_beta
 
+
+class LSTM(RNN):
+    """Operator converter for LSTM
+    """
+
     @classmethod
     def _impl_v7(cls, inputs, attr, params):
         # Unpack inputs, note that if optional and not provided then value 
will be None.
         X = inputs[0]
         W = inputs[1]
         R = inputs[2]
-        B = inputs['B']
+        B = inputs[3]
         # Sequence length currently unused as it can be inferred from shapes.
         #sequence_lens = inputs['sequence_lens']
-        h_0 = inputs['initial_h']
-        c_0 = inputs['initial_c']
-        P = inputs['P']
+        h_0 = inputs[5]
+        c_0 = inputs[6]
+        P = inputs[7]
 
         num_directions = infer_shape(W)[0]
         W_dtype = infer_type(W).type_annotation.dtype
@@ -1577,7 +1584,8 @@ class LSTM(OnnxOpConverter):
         if 'activations' in attr:
             activations = attr['activations']
             if len(activations) != 3:
-                raise NotImplementedError("LSTM assumes 3 activation functions 
are provided")
+                raise NotImplementedError(
+                    "LSTM assumes 3 activation functions are provided")
             alpha_loc = 0
             alphas = attr.get('activation_alpha', [])
             if isinstance(alphas, float):
@@ -1591,10 +1599,12 @@ class LSTM(OnnxOpConverter):
                 alpha = None
                 beta = None
                 activation = activations[i]
-                if cls._activation_needs_alpha(activation) and len(alphas) > 
alpha_loc:
+                if cls._activation_needs_alpha(
+                        activation) and len(alphas) > alpha_loc:
                     alpha = alphas[alpha_loc]
                     alpha_loc += 1
-                if cls._activation_needs_beta(activation) and len(betas) > 
beta_loc:
+                if cls._activation_needs_beta(
+                        activation) and len(betas) > beta_loc:
                     beta = betas[beta_loc]
                     beta_loc += 1
                 acts.append(cls._activation_helper(activation, alpha, beta))
@@ -1638,6 +1648,117 @@ class LSTM(OnnxOpConverter):
         return _expr.TupleWrapper(_expr.Tuple((output, H_t, C_t)), 3)
 
 
+class GRU(RNN):
+    """Operator convert for GRU
+    """
+
+    @classmethod
+    def _impl_v7(cls, inputs, attr, params):
+        # Unpack inputs, note that if optional and not provided then value 
will be None.
+        X = inputs[0]
+        W = inputs[1]
+        R = inputs[2]
+        B = inputs[3]
+        # Sequence length currently unused as it can be inferred from shapes.
+        #sequence_lens = inputs['sequence_lens']
+        h_0 = inputs[5]
+        linear_before_reset = attr.get('linear_before_reset', 0)
+
+        num_directions = infer_shape(W)[0]
+        W_dtype = infer_type(W).type_annotation.dtype
+
+        if num_directions != 1:
+            raise NotImplementedError("Bidirectional GRUs not yet supported.")
+        # Remove num_directions axis from weights.
+        W = _op.squeeze(W, axis=[0])
+        R = _op.squeeze(R, axis=[0])
+        if B is not None:
+            B = _op.squeeze(B, axis=[0])
+
+        X_shape = infer_shape(X)
+        hidden_size = infer_shape(R)[-1]
+        batch_size = X_shape[1]
+
+        # Initialize state if not provided.
+        # Otherwise remove bidirectional axis.
+        if h_0 is None:
+            h_0 = _op.zeros((batch_size, hidden_size), W_dtype)
+        else:
+            h_0 = _op.squeeze(h_0, axis=[0])
+
+        H_t = h_0
+        h_list = []
+
+        if 'activations' in attr:
+            activations = attr['activations']
+            if len(activations) != 2:
+                raise NotImplementedError(
+                    "GRU assumes 2 activation functions are provided")
+            alpha_loc = 0
+            alphas = attr.get('activation_alpha', [])
+            if isinstance(alphas, float):
+                alphas = [alphas]
+            beta_loc = 0
+            betas = attr.get('activation_beta', [])
+            if isinstance(betas, float):
+                betas = [betas]
+            acts = []
+            for i in range(2):
+                alpha = None
+                beta = None
+                activation = activations[i]
+                if cls._activation_needs_alpha(
+                        activation) and len(alphas) > alpha_loc:
+                    alpha = alphas[alpha_loc]
+                    alpha_loc += 1
+                if cls._activation_needs_beta(
+                        activation) and len(betas) > beta_loc:
+                    beta = betas[beta_loc]
+                    beta_loc += 1
+                acts.append(cls._activation_helper(activation, alpha, beta))
+            f_act, g_act = acts
+        else:
+            f_act = _op.sigmoid
+            g_act = _op.tanh
+
+        X_steps = _op.split(X, indices_or_sections=X_shape[0], axis=0)
+        for step in X_steps:
+            step = _op.squeeze(step, axis=[0])
+            current = _op.nn.dense(step, W)
+            cz, cr, ch = _op.split(current, 3, axis=1)
+            rz, rr, rh = _op.split(R, 3, axis=0)
+            z = cz + _op.nn.dense(H_t, rz)
+            r = cr + _op.nn.dense(H_t, rr)
+            if B is not None:
+                WB, RB = _op.split(B, 2)
+                wbz, wbr, wbh = _op.split(WB, 3, axis=-1)
+                rbz, rbr, rbh = _op.split(RB, 3, axis=-1)
+                z += wbz + rbz
+                r += wbr + rbr
+                if linear_before_reset:
+                    h = ch + (r * (_op.nn.dense(H_t, rh) + rbh)) + wbh
+                else:
+                    h = ch + _op.nn.dense((r * H_t), rh) + wbh + rbh
+            else:
+                if linear_before_reset:
+                    h = ch + (r * (_op.nn.dense(H_t, rh)))
+                else:
+                    h = ch + _op.nn.dense((r * H_t), rh)
+
+            z = f_act(z)
+            r = f_act(r)
+            h = g_act(h)
+
+            H_t = ((_expr.const(1, dtype=W_dtype) - z) * h) + (z * H_t)
+            h_list.append(_op.expand_dims(H_t, axis=0))
+        # Concatenate outputs and add back in direction axis.
+        concatenated = _op.concatenate(h_list, 0)
+        output = _op.expand_dims(concatenated, axis=1)
+        H_t = _op.expand_dims(H_t, axis=0)
+
+        return _expr.TupleWrapper(_expr.Tuple((output, H_t)), 2)
+
+
 class Resize(OnnxOpConverter):
     """Operator converter for Resize
     """
@@ -1859,6 +1980,7 @@ def _get_convert_map(opset):
         'LRN': LRN.get_converter(opset),
         # Recurrent Layers
         'LSTM': LSTM.get_converter(opset),
+        'GRU': GRU.get_converter(opset),
 
         # defs/vision
         'MaxRoiPool': MaxRoiPool.get_converter(opset),
diff --git a/tests/python/frontend/onnx/test_forward.py 
b/tests/python/frontend/onnx/test_forward.py
index 5c472be..8654bf0 100644
--- a/tests/python/frontend/onnx/test_forward.py
+++ b/tests/python/frontend/onnx/test_forward.py
@@ -2573,19 +2573,30 @@ def test_lppool():
                   pads=None, out_shape=[1, 1, 16, 16, 16], 
auto_pad='SAME_UPPER')
 
 
-def verify_lstm(seq_length,
-                batch_size,
-                input_size,
-                hidden_size,
-                use_bias=False,
-                activations=None,
-                alphas=None,
-                betas=None,
-                use_initial_state=False,
-                use_peep=False):
-    x_np = np.random.uniform(size=(seq_length, batch_size, 
input_size)).astype('float32')
-    w_np = np.random.uniform(size=(1, 4 * hidden_size, 
input_size)).astype('float32')
-    r_np = np.random.uniform(size=(1, 4 * hidden_size, 
hidden_size)).astype('float32')
+def verify_rnn(seq_length,
+               batch_size,
+               input_size,
+               hidden_size,
+               rnn_type='LSTM',
+               use_bias=False,
+               activations=None,
+               alphas=None,
+               betas=None,
+               use_initial_state=False,
+               use_peep=False,
+               linear_before_reset=False):
+    if rnn_type == 'LSTM':
+        multiplier = 4
+    elif rnn_type == 'GRU':
+        multiplier = 3
+    else:
+        raise NotImplementedError("%s RNNs not yet supported." % rnn_type)
+    x_np = np.random.uniform(size=(seq_length, batch_size,
+                                   input_size)).astype('float32')
+    w_np = np.random.uniform(size=(1, multiplier * hidden_size,
+                                   input_size)).astype('float32')
+    r_np = np.random.uniform(size=(1, multiplier * hidden_size,
+                                   hidden_size)).astype('float32')
     input_names = ["X", "W", "R"]
     input_tensors = [
         helper.make_tensor_value_info("X", TensorProto.FLOAT, 
list(x_np.shape)),
@@ -2595,78 +2606,87 @@ def verify_lstm(seq_length,
     input_values = [x_np, w_np, r_np]
 
     if use_bias:
-        b_np = np.random.uniform(size=(1, 8 * hidden_size)).astype('float32')
+        b_np = np.random.uniform(size=(1, multiplier * 2 *
+                                       hidden_size)).astype('float32')
         input_names.append("B")
         input_tensors.append(
-            helper.make_tensor_value_info("B", TensorProto.FLOAT, [1, 8 * 
hidden_size]))
+            helper.make_tensor_value_info("B", TensorProto.FLOAT,
+                                          [1, multiplier * 2 * hidden_size]))
         input_values.append(b_np)
 
     if use_initial_state:
         assert use_bias == True, "Initial states must have bias specified."
         sequence_np = np.repeat(seq_length, batch_size).astype('int32')
         input_names.append("sequence_lens")
-        input_tensors.append(helper.make_tensor_value_info("sequence_lens", 
TensorProto.INT32, [batch_size]))
+        input_tensors.append(
+            helper.make_tensor_value_info("sequence_lens", TensorProto.INT32,
+                                          [batch_size]))
         input_values.append(sequence_np)
 
-        initial_h_np = np.random.uniform(size=(1, batch_size, 
hidden_size)).astype('float32')
+        initial_h_np = np.random.uniform(size=(1, batch_size,
+                                               hidden_size)).astype('float32')
         input_names.append("initial_h")
         input_tensors.append(
             helper.make_tensor_value_info("initial_h", TensorProto.FLOAT,
                                           [1, batch_size, hidden_size]))
         input_values.append(initial_h_np)
 
-        initial_c_np = np.random.uniform(size=(1, batch_size, 
hidden_size)).astype('float32')
-        input_names.append("initial_c")
-        input_tensors.append(
-            helper.make_tensor_value_info("initial_c", TensorProto.FLOAT,
-                                          [1, batch_size, hidden_size]))
-        input_values.append(initial_c_np)
+        if rnn_type == 'LSTM':
+            initial_c_np = np.random.uniform(
+                size=(1, batch_size, hidden_size)).astype('float32')
+            input_names.append("initial_c")
+            input_tensors.append(
+                helper.make_tensor_value_info("initial_c", TensorProto.FLOAT,
+                                              [1, batch_size, hidden_size]))
+            input_values.append(initial_c_np)
 
-    if use_peep:
+    if use_peep and rnn_type == 'LSTM':
         assert use_initial_state == True, "Peepholes require initial state to 
be specified."
         p_np = np.random.uniform(size=(1, 3 * hidden_size)).astype('float32')
         input_names.append("P")
         input_tensors.append(
-            helper.make_tensor_value_info("P", TensorProto.FLOAT, [1, 3 * 
hidden_size]))
+            helper.make_tensor_value_info("P", TensorProto.FLOAT,
+                                          [1, 3 * hidden_size]))
         input_values.append(p_np)
 
     Y_shape = [seq_length, 1, batch_size, hidden_size]
     Y_h_shape = [1, batch_size, hidden_size]
-    Y_c_shape = [1, batch_size, hidden_size]
-
-    if activations is None:
-        lstm_node = helper.make_node(
-            'LSTM', inputs=input_names, outputs=["Y", "Y_h", "Y_c"], 
hidden_size=hidden_size)
-    elif alphas is None:
-        lstm_node = helper.make_node(
-            'LSTM',
-            inputs=input_names,
-            outputs=["Y", "Y_h", "Y_c"],
-            hidden_size=hidden_size,
-            activations=activations)
-    else:
-        lstm_node = helper.make_node(
-            'LSTM',
-            inputs=input_names,
-            outputs=["Y", "Y_h", "Y_c"],
-            hidden_size=hidden_size,
-            activations=activations,
-            activation_alpha=alphas,
-            activation_beta=betas)
-
-    graph = helper.make_graph([lstm_node],
-                              "lstm_test",
+    outputs = ["Y", "Y_h"]
+    graph_outputs = [
+        helper.make_tensor_value_info("Y", TensorProto.FLOAT, list(Y_shape)),
+        helper.make_tensor_value_info("Y_h", TensorProto.FLOAT, 
list(Y_h_shape))
+    ]
+    output_shapes = [Y_shape, Y_h_shape]
+
+    if rnn_type == 'LSTM':
+        Y_c_shape = [1, batch_size, hidden_size]
+        outputs.append("Y_c")
+        graph_outputs.append(
+            helper.make_tensor_value_info("Y_c", TensorProto.FLOAT,
+                                          list(Y_c_shape)))
+        output_shapes.append(Y_c_shape)
+
+    rnn_node = helper.make_node(
+        rnn_type, inputs=input_names, outputs=outputs, hidden_size=hidden_size)
+    if activations is not None:
+        activations_attr = helper.make_attribute('activations', activations)
+        rnn_node.attribute.append(activations_attr)
+    if alphas is not None:
+        alphas_attr = helper.make_attribute('activation_alpha', alphas)
+        rnn_node.attribute.append(alphas_attr)
+    if betas is not None:
+        betas_attr = helper.make_attribute('activation_beta', betas)
+        rnn_node.attribute.append(betas_attr)
+    if linear_before_reset and rnn_type == 'GRU':
+        lbr_attr = helper.make_attribute('linear_before_reset', 1)
+        rnn_node.attribute.append(lbr_attr)
+
+    graph = helper.make_graph([rnn_node],
+                              "rnn_test",
                               inputs=input_tensors,
-                              outputs=[
-                                  helper.make_tensor_value_info("Y", 
TensorProto.FLOAT,
-                                                                list(Y_shape)),
-                                  helper.make_tensor_value_info("Y_h", 
TensorProto.FLOAT,
-                                                                
list(Y_h_shape)),
-                                  helper.make_tensor_value_info("Y_c", 
TensorProto.FLOAT,
-                                                                
list(Y_c_shape))
-                              ])
+                              outputs=graph_outputs)
 
-    model = helper.make_model(graph, producer_name='lstm_test')
+    model = helper.make_model(graph, producer_name='rnn_test')
 
     for target, ctx in ctx_list():
         onnx_out = get_onnxruntime_output(model, input_values, 'float32')
@@ -2674,37 +2694,75 @@ def verify_lstm(seq_length,
             model,
             input_values,
             target,
-            ctx, [Y_shape, Y_h_shape, Y_c_shape],
-            output_dtype=['float32', 'float32', 'float32'])
+            ctx,
+            output_shapes,
+            output_dtype=['float32'] * len(output_shapes))
         for o_out, t_out in zip(onnx_out, tvm_out):
             tvm.testing.assert_allclose(o_out, t_out, rtol=5e-3, atol=5e-3)
 
 
 def test_lstm():
     # No bias.
-    verify_lstm(seq_length=2, batch_size=1, input_size=16, hidden_size=32, 
use_bias=False)
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=False,
+        rnn_type='LSTM')
     # large batch.
-    verify_lstm(seq_length=4, batch_size=8, input_size=16, hidden_size=32, 
use_bias=True)
+    verify_rnn(
+        seq_length=4,
+        batch_size=8,
+        input_size=16,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='LSTM')
     # Non power of two.
-    verify_lstm(seq_length=3, batch_size=3, input_size=16, hidden_size=40, 
use_bias=True)
+    verify_rnn(
+        seq_length=3,
+        batch_size=3,
+        input_size=16,
+        hidden_size=40,
+        use_bias=True,
+        rnn_type='LSTM')
     # Long sequence.
-    verify_lstm(seq_length=8, batch_size=1, input_size=16, hidden_size=32, 
use_bias=True)
+    verify_rnn(
+        seq_length=8,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='LSTM')
     # Large hidden.
-    verify_lstm(seq_length=2, batch_size=1, input_size=16, hidden_size=128, 
use_bias=True)
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=128,
+        use_bias=True,
+        rnn_type='LSTM')
     # Large input.
-    verify_lstm(seq_length=2, batch_size=1, input_size=64, hidden_size=32, 
use_bias=True)
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=64,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='LSTM')
 
     # Different activation testing.
     # Default value hardsigmoid.
-    verify_lstm(
+    verify_rnn(
         seq_length=2,
         batch_size=1,
         input_size=16,
         hidden_size=32,
         use_bias=False,
-        activations=['HardSigmoid', 'Tanh', 'Tanh'])
+        activations=['HardSigmoid', 'Tanh', 'Tanh'],
+        rnn_type='LSTM')
     # Multiple parameterized activations.
-    verify_lstm(
+    verify_rnn(
         seq_length=2,
         batch_size=1,
         input_size=16,
@@ -2712,9 +2770,10 @@ def test_lstm():
         use_bias=False,
         activations=['HardSigmoid', 'LeakyRelu', 'Tanh'],
         alphas=[2.0, 0.5],
-        betas=[.3])
+        betas=[.3],
+        rnn_type='LSTM')
     # All parameterized with new Affine activation.
-    verify_lstm(
+    verify_rnn(
         seq_length=2,
         batch_size=1,
         input_size=16,
@@ -2722,24 +2781,123 @@ def test_lstm():
         use_bias=False,
         activations=['HardSigmoid', 'LeakyRelu', 'Affine'],
         alphas=[2.0, 0.5, 0.8],
-        betas=[.3, 0.1])
+        betas=[.3, 0.1],
+        rnn_type='LSTM')
 
     # Testing with initial state and peepholes
-    verify_lstm(
+    verify_rnn(
         seq_length=2,
         batch_size=1,
         input_size=16,
         hidden_size=32,
         use_bias=True,
-        use_initial_state=True)
-    verify_lstm(
+        use_initial_state=True,
+        rnn_type='LSTM')
+
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=True,
+        use_initial_state=True,
+        use_peep=True,
+        rnn_type='LSTM')
+
+
+def test_gru():
+    # No bias.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=False,
+        rnn_type='GRU')
+    # large batch.
+    verify_rnn(
+        seq_length=4,
+        batch_size=8,
+        input_size=16,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='GRU',
+        linear_before_reset=True)
+    # Non power of two.
+    verify_rnn(
+        seq_length=3,
+        batch_size=3,
+        input_size=16,
+        hidden_size=40,
+        use_bias=True,
+        rnn_type='GRU')
+    # Long sequence.
+    verify_rnn(
+        seq_length=8,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='GRU')
+    # Large hidden.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=128,
+        use_bias=True,
+        rnn_type='GRU')
+    # Large input.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=64,
+        hidden_size=32,
+        use_bias=True,
+        rnn_type='GRU')
+
+    # Different activation testing.
+    # Default value hardsigmoid.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=False,
+        activations=['HardSigmoid', 'Softsign'],
+        rnn_type='GRU')
+    # Multiple parameterized activations.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=False,
+        activations=['HardSigmoid', 'LeakyRelu'],
+        alphas=[2.0, 0.5],
+        betas=[.3],
+        rnn_type='GRU')
+    # All parameterized with new Affine activation.
+    verify_rnn(
+        seq_length=2,
+        batch_size=1,
+        input_size=16,
+        hidden_size=32,
+        use_bias=False,
+        activations=['HardSigmoid', 'Affine'],
+        alphas=[2.0, 0.8],
+        betas=[.3, 0.1],
+        rnn_type='GRU')
+
+    # Testing with initial state
+    verify_rnn(
         seq_length=2,
         batch_size=1,
         input_size=16,
         hidden_size=32,
         use_bias=True,
         use_initial_state=True,
-        use_peep=True)
+        rnn_type='GRU')
 
 
 def test_resize():
@@ -2992,6 +3150,7 @@ if __name__ == '__main__':
     test_pooling()
     test_lppool()
     test_lstm()
+    test_gru()
     test_resize()
     test_nonzero()
     test_topk()

Reply via email to