You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@singa.apache.org by wa...@apache.org on 2020/03/31 14:03:57 UTC

[singa] branch dev updated: upgrade sonnx for nlp and cv models

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

wangwei pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/singa.git


The following commit(s) were added to refs/heads/dev by this push:
     new 02b5190  upgrade sonnx for nlp and cv models
     new 7af810f  Merge pull request #638 from joddiy/SINGA-500_v2
02b5190 is described below

commit 02b519035669e6c042a9153a73a221469911cfbc
Author: joddiy <jo...@qq.com>
AuthorDate: Wed Mar 25 14:04:42 2020 +0800

    upgrade sonnx for nlp and cv models
---
 examples/onnx/arcface.py             |  122 +++
 examples/onnx/bert/bert-squad.py     |  169 ++++
 examples/onnx/bert/inputs.json       |   27 +
 examples/onnx/bert/run_onnx_squad.py |  507 ++++++++++
 examples/onnx/bert/tokenization.py   |  399 ++++++++
 examples/onnx/fer_emotion.py         |  111 +++
 examples/onnx/mnist.py               |  320 +++++++
 examples/onnx/mobilenet.py           |  116 +++
 examples/onnx/resnet18.py            |  115 +++
 examples/onnx/tiny_yolov2.py         |  167 ++++
 examples/onnx/utils.py               |   72 ++
 examples/onnx/vgg16.py               |  114 +++
 python/singa/autograd.py             |  141 ++-
 python/singa/sonnx.py                | 1719 +++++++++++++++++++++++-----------
 python/singa/utils.py                |   40 +
 test/python/test_onnx.py             |  259 ++++-
 test/python/test_onnx_backend.py     | 1053 ++++++++++++++++++---
 test/python/test_operation.py        |   43 +-
 18 files changed, 4784 insertions(+), 710 deletions(-)

diff --git a/examples/onnx/arcface.py b/examples/onnx/arcface.py
new file mode 100644
index 0000000..933e1b1
--- /dev/null
+++ b/examples/onnx/arcface.py
@@ -0,0 +1,122 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import os
+import numpy as np
+from PIL import Image
+from sklearn import preprocessing
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
+
+def preprocess(img):
+    w, h = img.size
+    img = img.crop((0, (h - w) // 2, w, h - (h - w) // 2))
+    img = img.resize((112, 112))
+    img = np.array(img).astype(np.float32)
+    img = np.rollaxis(img, 2, 0)
+    img = np.expand_dims(img, axis=0)
+    return img
+
+
+def get_image():
+    # download image
+    img1 = Image.open(
+        check_exist_or_download(
+            'https://angus-doc.readthedocs.io/en/latest/_images/aurelien.jpg'))
+    img2 = Image.open(
+        check_exist_or_download(
+            'https://angus-doc.readthedocs.io/en/latest/_images/gwenn.jpg'))
+    return img1, img2
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+if __name__ == "__main__":
+
+    download_dir = '/tmp'
+    url = 'https://s3.amazonaws.com/onnx-model-zoo/arcface/resnet100/resnet100.tar.gz'
+    model_path = os.path.join(download_dir, 'resnet100', 'resnet100.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 2)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test dataset
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(
+    #     os.path.join('/tmp', 'resnet100', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference demo
+    logging.info("preprocessing...")
+    img1, img2 = get_image()
+    img1 = preprocess(img1)
+    img2 = preprocess(img2)
+
+    x_batch = tensor.Tensor(device=dev,
+                            data=np.concatenate((img1, img2), axis=0))
+    logging.info("model running...")
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    embedding = tensor.to_numpy(y)
+    embedding = preprocessing.normalize(embedding)
+    embedding1 = embedding[0]
+    embedding2 = embedding[1]
+
+    # Compute squared distance between embeddings
+    dist = np.sum(np.square(embedding1 - embedding2))
+    # Compute cosine similarity between embedddings
+    sim = np.dot(embedding1, embedding2.T)
+    # logging.info predictions
+    logging.info('Distance = %f' % (dist))
+    logging.info('Similarity = %f' % (sim))
diff --git a/examples/onnx/bert/bert-squad.py b/examples/onnx/bert/bert-squad.py
new file mode 100644
index 0000000..e4a8488
--- /dev/null
+++ b/examples/onnx/bert/bert-squad.py
@@ -0,0 +1,169 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import zipfile
+import numpy as np
+import json
+
+from singa import device
+from singa import tensor
+from singa import sonnx
+from singa import autograd
+import onnx
+import tokenization
+from run_onnx_squad import read_squad_examples, convert_examples_to_features, RawResult, write_predictions
+
+import sys
+sys.path.append(os.path.dirname(__file__) + '/..')
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(message)s')
+
+max_answer_length = 30
+max_seq_length = 256
+doc_stride = 128
+max_query_length = 64
+n_best_size = 20
+batch_size = 1
+
+
+def load_vocab():
+    url = 'https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip'
+    download_dir = '/tmp/'
+    filename = os.path.join(download_dir, 'uncased_L-12_H-768_A-12', '.',
+                            'vocab.txt')
+    with zipfile.ZipFile(check_exist_or_download(url), 'r') as z:
+        z.extractall(path=download_dir)
+    return filename
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+
+    def forward(self, x):
+        return sg_ir.run(x)
+
+
+def preprocess():
+    vocab_file = load_vocab()
+    tokenizer = tokenization.FullTokenizer(vocab_file=vocab_file,
+                                           do_lower_case=True)
+    predict_file = os.path.join(os.path.dirname(__file__), 'inputs.json')
+    # print content
+    with open(predict_file) as json_file:
+        test_data = json.load(json_file)
+        print("The input is:", json.dumps(test_data, indent=2))
+
+    eval_examples = read_squad_examples(input_file=predict_file)
+
+    # Use convert_examples_to_features method from run_onnx_squad to get parameters from the input
+    input_ids, input_mask, segment_ids, extra_data = convert_examples_to_features(
+        eval_examples, tokenizer, max_seq_length, doc_stride, max_query_length)
+    return input_ids, input_mask, segment_ids, extra_data, eval_examples
+
+
+def postprocess(eval_examples, extra_data, all_results):
+    output_dir = 'predictions'
+    os.makedirs(output_dir, exist_ok=True)
+    output_prediction_file = os.path.join(output_dir, "predictions.json")
+    output_nbest_file = os.path.join(output_dir, "nbest_predictions.json")
+    write_predictions(eval_examples, extra_data, all_results, n_best_size,
+                      max_answer_length, True, output_prediction_file,
+                      output_nbest_file)
+
+    # print results
+    with open(output_prediction_file) as json_file:
+        test_data = json.load(json_file)
+        print("The result is:", json.dumps(test_data, indent=2))
+
+
+if __name__ == "__main__":
+
+    url = 'https://media.githubusercontent.com/media/onnx/models/master/text/machine_comprehension/bert-squad/model/bertsquad-10.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'download_sample_10',
+                              'bertsquad10.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, batch_size)
+    dev = device.create_cuda_gpu()
+    autograd.training = False
+
+    # inference
+    logging.info("preprocessing...")
+    input_ids, input_mask, segment_ids, extra_data, eval_examples = preprocess()
+
+    sg_ir = None
+    n = len(input_ids)
+    bs = batch_size
+    all_results = []
+
+    tmp_dict = {}
+    for idx in range(0, n):
+        logging.info("starting infer sample {}...".format(idx))
+        item = eval_examples[idx]
+        inputs = [
+            np.array([item.qas_id], dtype=np.int32),
+            segment_ids[idx:idx + bs].astype(np.int32),
+            input_mask[idx:idx + bs].astype(np.int32),
+            input_ids[idx:idx + bs].astype(np.int32),
+        ]
+
+        if sg_ir is None:
+            # prepare the model
+            logging.info("model is none, prepare model...")
+            sg_ir = sonnx.prepare(onnx_model,
+                                  device=dev,
+                                  init_inputs=inputs,
+                                  keep_initializers_as_inputs=False)
+            model = Infer(sg_ir)
+
+        x_batch = []
+        for inp in inputs:
+            tmp_tensor = tensor.from_numpy(inp)
+            tmp_tensor.to_device(dev)
+            x_batch.append(tmp_tensor)
+
+        logging.info("model running for sample {}...".format(idx))
+        outputs = model.forward(x_batch)
+
+        logging.info("hanlde the result of sample {}...".format(idx))
+        result = []
+        for outp in outputs:
+            result.append(tensor.to_numpy(outp))
+
+        in_batch = result[1].shape[0]
+        start_logits = [float(x) for x in result[1][0].flat]
+        end_logits = [float(x) for x in result[0][0].flat]
+        for i in range(0, in_batch):
+            unique_id = len(all_results)
+            all_results.append(
+                RawResult(unique_id=unique_id,
+                          start_logits=start_logits,
+                          end_logits=end_logits))
+    # postprocessing
+    logging.info("postprocessing...")
+    postprocess(eval_examples, extra_data, all_results)
\ No newline at end of file
diff --git a/examples/onnx/bert/inputs.json b/examples/onnx/bert/inputs.json
new file mode 100644
index 0000000..b9e313b
--- /dev/null
+++ b/examples/onnx/bert/inputs.json
@@ -0,0 +1,27 @@
+{
+  "version": "1.4",
+  "data": [
+    {
+      "paragraphs": [
+        {
+          "context": "In its early years, the new convention center failed to meet attendance and revenue expectations.[12] By 2002, many Silicon Valley businesses were choosing the much larger Moscone Center in San Francisco over the San Jose Convention Center due to the latter's limited space. A ballot measure to finance an expansion via a hotel tax failed to reach the required two-thirds majority to pass. In June 2005, Team San Jose built the South Hall, a $6.77 million, blue and whit [...]
+          "qas": [
+            {
+              "question": "where is the businesses choosing to go?",
+              "id": "1"
+            },
+            {
+              "question": "how may votes did the ballot measure need?",
+              "id": "2"
+            },
+            {
+              "question": "By what year many Silicon Valley businesses were choosing the Moscone Center?",
+              "id": "3"
+            }
+          ]
+        }
+      ],
+      "title": "Conference Center"
+    }
+  ]
+}
diff --git a/examples/onnx/bert/run_onnx_squad.py b/examples/onnx/bert/run_onnx_squad.py
new file mode 100644
index 0000000..f9a60da
--- /dev/null
+++ b/examples/onnx/bert/run_onnx_squad.py
@@ -0,0 +1,507 @@
+# Copyright 2018 The Google AI Language Team Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+Inference for squad/bert using onnx.
+
+This is going to do the samem as 'python run_squad.py --do_predict=True ...' using a squad/bert model
+that was converted to onnx. Lots of code was taken from run_squad.py.
+You run it with:
+
+
+python onnx_squad.py --model $SQUAD_MODEL/squad.onnx \
+                     --vocab_file $BERT_BASE_DIR/uncased_L-12_H-768_A-12/vocab.txt
+                     --predict_file $SQUAD_DATA/dev-v1.1.json \
+                     --bert_config_file $BERT_BASE_DIR/uncased_L-12_H-768_A-12/bert_config.json \
+                     --output /tmp/
+"""
+
+import collections
+import json
+import math
+
+import numpy as np
+import six
+import tokenization
+
+RawResult = collections.namedtuple("RawResult",
+                                   ["unique_id", "start_logits", "end_logits"])
+
+Feature = collections.namedtuple("Feature", [
+    "unique_id", "tokens", "example_index", "token_to_orig_map",
+    "token_is_max_context"
+])
+
+
+class SquadExample(object):
+    """A single training/test example for simple sequence classification."""
+
+    def __init__(self,
+                 qas_id,
+                 question_text,
+                 doc_tokens,
+                 orig_answer_text=None,
+                 start_position=None,
+                 end_position=None):
+        self.qas_id = qas_id
+        self.question_text = question_text
+        self.doc_tokens = doc_tokens
+        self.orig_answer_text = orig_answer_text
+        self.start_position = start_position
+        self.end_position = end_position
+
+    def __str__(self):
+        return self.__repr__()
+
+    def __repr__(self):
+        s = []
+        s.append("qas_id: %s" % (tokenization.printable_text(self.qas_id)))
+        s.append("question_text: %s" %
+                 (tokenization.printable_text(self.question_text)))
+        s.append("doc_tokens: [%s]" % (" ".join(self.doc_tokens)))
+        if self.start_position:
+            s.append("start_position: %d" % (self.start_position))
+        if self.start_position:
+            s.append("end_position: %d" % (self.end_position))
+        return ", ".join(s)
+
+
+def _check_is_max_context(doc_spans, cur_span_index, position):
+    """Check if this is the 'max context' doc span for the token."""
+
+    # Because of the sliding window approach taken to scoring documents, a single
+    # token can appear in multiple documents. E.g.
+    #  Doc: the man went to the store and bought a gallon of milk
+    #  Span A: the man went to the
+    #  Span B: to the store and bought
+    #  Span C: and bought a gallon of
+    #  ...
+    #
+    # Now the word 'bought' will have two scores from spans B and C. We only
+    # want to consider the score with "maximum context", which we define as
+    # the *minimum* of its left and right context (the *sum* of left and
+    # right context will always be the same, of course).
+    #
+    # In the example the maximum context for 'bought' would be span C since
+    # it has 1 left context and 3 right context, while span B has 4 left context
+    # and 0 right context.
+    best_score = None
+    best_span_index = None
+    for (span_index, doc_span) in enumerate(doc_spans):
+        end = doc_span.start + doc_span.length - 1
+        if position < doc_span.start:
+            continue
+        if position > end:
+            continue
+        num_left_context = position - doc_span.start
+        num_right_context = end - position
+        score = min(num_left_context,
+                    num_right_context) + 0.01 * doc_span.length
+        if best_score is None or score > best_score:
+            best_score = score
+            best_span_index = span_index
+
+    return cur_span_index == best_span_index
+
+
+def convert_examples_to_features(examples, tokenizer, max_seq_length,
+                                 doc_stride, max_query_length):
+    """Loads a data file into a list of `InputBatch`s."""
+
+    res_input_ids = []
+    res_input_mask = []
+    res_segment_ids = []
+    extra = []
+    unique_id = 0
+
+    for (example_index, example) in enumerate(examples):
+        query_tokens = tokenizer.tokenize(example.question_text)
+
+        if len(query_tokens) > max_query_length:
+            query_tokens = query_tokens[0:max_query_length]
+
+        tok_to_orig_index = []
+        orig_to_tok_index = []
+        all_doc_tokens = []
+        for (i, token) in enumerate(example.doc_tokens):
+            orig_to_tok_index.append(len(all_doc_tokens))
+            sub_tokens = tokenizer.tokenize(token)
+            for sub_token in sub_tokens:
+                tok_to_orig_index.append(i)
+                all_doc_tokens.append(sub_token)
+
+        # The -3 accounts for [CLS], [SEP] and [SEP]
+        max_tokens_for_doc = max_seq_length - len(query_tokens) - 3
+
+        # We can have documents that are longer than the maximum sequence length.
+        # To deal with this we do a sliding window approach, where we take chunks
+        # of the up to our max length with a stride of `doc_stride`.
+        _DocSpan = collections.namedtuple("DocSpan", ["start", "length"])
+        doc_spans = []
+        start_offset = 0
+        while start_offset < len(all_doc_tokens):
+            length = len(all_doc_tokens) - start_offset
+            if length > max_tokens_for_doc:
+                length = max_tokens_for_doc
+            doc_spans.append(_DocSpan(start=start_offset, length=length))
+            if start_offset + length == len(all_doc_tokens):
+                break
+            start_offset += min(length, doc_stride)
+
+        for (doc_span_index, doc_span) in enumerate(doc_spans):
+            tokens = []
+            token_to_orig_map = {}
+            token_is_max_context = {}
+            segment_ids = []
+            tokens.append("[CLS]")
+            segment_ids.append(0)
+            for token in query_tokens:
+                tokens.append(token)
+                segment_ids.append(0)
+            tokens.append("[SEP]")
+            segment_ids.append(0)
+
+            for i in range(doc_span.length):
+                split_token_index = doc_span.start + i
+                token_to_orig_map[len(
+                    tokens)] = tok_to_orig_index[split_token_index]
+
+                is_max_context = _check_is_max_context(doc_spans,
+                                                       doc_span_index,
+                                                       split_token_index)
+                token_is_max_context[len(tokens)] = is_max_context
+                tokens.append(all_doc_tokens[split_token_index])
+                segment_ids.append(1)
+            tokens.append("[SEP]")
+            segment_ids.append(1)
+
+            input_ids = tokenizer.convert_tokens_to_ids(tokens)
+
+            # The mask has 1 for real tokens and 0 for padding tokens. Only real
+            # tokens are attended to.
+            input_mask = [1] * len(input_ids)
+
+            # Zero-pad up to the sequence length.
+            while len(input_ids) < max_seq_length:
+                input_ids.append(0)
+                input_mask.append(0)
+                segment_ids.append(0)
+            res_input_ids.append(np.array(input_ids, dtype=np.int64))
+            res_input_mask.append(np.array(input_mask, dtype=np.int64))
+            res_segment_ids.append(np.array(segment_ids, dtype=np.int64))
+            feature = Feature(unique_id=unique_id,
+                              tokens=tokens,
+                              example_index=example_index,
+                              token_to_orig_map=token_to_orig_map,
+                              token_is_max_context=token_is_max_context)
+            extra.append(feature)
+            unique_id += 1
+    return np.array(res_input_ids), np.array(res_input_mask), np.array(
+        res_segment_ids), extra
+
+
+def read_squad_examples(input_file):
+    """Read a SQuAD json file into a list of SquadExample."""
+    with open(input_file, "r") as f:
+        input_data = json.load(f)["data"]
+
+    def is_whitespace(c):
+        if c == " " or c == "\t" or c == "\r" or c == "\n" or ord(c) == 0x202F:
+            return True
+        return False
+
+    examples = []
+    for idx, entry in enumerate(input_data):
+        for paragraph in entry["paragraphs"]:
+            paragraph_text = paragraph["context"]
+            doc_tokens = []
+            char_to_word_offset = []
+            prev_is_whitespace = True
+            for c in paragraph_text:
+                if is_whitespace(c):
+                    prev_is_whitespace = True
+                else:
+                    if prev_is_whitespace:
+                        doc_tokens.append(c)
+                    else:
+                        doc_tokens[-1] += c
+                    prev_is_whitespace = False
+                char_to_word_offset.append(len(doc_tokens) - 1)
+
+            for qa in paragraph["qas"]:
+                qas_id = qa["id"]
+                question_text = qa["question"]
+                start_position = None
+                end_position = None
+                orig_answer_text = None
+                example = SquadExample(qas_id=qas_id,
+                                       question_text=question_text,
+                                       doc_tokens=doc_tokens,
+                                       orig_answer_text=orig_answer_text,
+                                       start_position=start_position,
+                                       end_position=end_position)
+                examples.append(example)
+    return examples
+
+
+def write_predictions(all_examples, all_features, all_results, n_best_size,
+                      max_answer_length, do_lower_case, output_prediction_file,
+                      output_nbest_file):
+    """Write final predictions to the json file."""
+    example_index_to_features = collections.defaultdict(list)
+    for feature in all_features:
+        example_index_to_features[feature.example_index].append(feature)
+
+    unique_id_to_result = {}
+    for result in all_results:
+        unique_id_to_result[result.unique_id] = result
+
+    _PrelimPrediction = collections.namedtuple(  # pylint: disable=invalid-name
+        "PrelimPrediction", [
+            "feature_index", "start_index", "end_index", "start_logit",
+            "end_logit"
+        ])
+
+    all_predictions = collections.OrderedDict()
+    all_nbest_json = collections.OrderedDict()
+    for (example_index, example) in enumerate(all_examples):
+        features = example_index_to_features[example_index]
+        prelim_predictions = []
+        for (feature_index, feature) in enumerate(features):
+            if not feature.unique_id in unique_id_to_result:
+                print("feature not in unique_Id", feature.unique_id)
+                continue
+            result = unique_id_to_result[feature.unique_id]
+
+            start_indexes = _get_best_indexes(result.start_logits, n_best_size)
+            end_indexes = _get_best_indexes(result.end_logits, n_best_size)
+            for start_index in start_indexes:
+                for end_index in end_indexes:
+                    # We could hypothetically create invalid predictions, e.g., predict
+                    # that the start of the span is in the question. We throw out all
+                    # invalid predictions.
+                    if start_index >= len(feature.tokens):
+                        continue
+                    if end_index >= len(feature.tokens):
+                        continue
+                    if start_index not in feature.token_to_orig_map:
+                        continue
+                    if end_index not in feature.token_to_orig_map:
+                        continue
+                    if not feature.token_is_max_context.get(start_index, False):
+                        continue
+                    if end_index < start_index:
+                        continue
+                    length = end_index - start_index + 1
+                    if length > max_answer_length:
+                        continue
+                    prelim_predictions.append(
+                        _PrelimPrediction(
+                            feature_index=feature_index,
+                            start_index=start_index,
+                            end_index=end_index,
+                            start_logit=result.start_logits[start_index],
+                            end_logit=result.end_logits[end_index]))
+
+        prelim_predictions = sorted(prelim_predictions,
+                                    key=lambda x: (x.start_logit + x.end_logit),
+                                    reverse=True)
+
+        _NbestPrediction = collections.namedtuple(  # pylint: disable=invalid-name
+            "NbestPrediction", ["text", "start_logit", "end_logit"])
+
+        seen_predictions = {}
+        nbest = []
+        for pred in prelim_predictions:
+            if len(nbest) >= n_best_size:
+                break
+            feature = features[pred.feature_index]
+
+            tok_tokens = feature.tokens[pred.start_index:(pred.end_index + 1)]
+            orig_doc_start = feature.token_to_orig_map[pred.start_index]
+            orig_doc_end = feature.token_to_orig_map[pred.end_index]
+            orig_tokens = example.doc_tokens[orig_doc_start:(orig_doc_end + 1)]
+            tok_text = " ".join(tok_tokens)
+
+            # De-tokenize WordPieces that have been split off.
+            tok_text = tok_text.replace(" ##", "")
+            tok_text = tok_text.replace("##", "")
+
+            # Clean whitespace
+            tok_text = tok_text.strip()
+            tok_text = " ".join(tok_text.split())
+            orig_text = " ".join(orig_tokens)
+
+            final_text = get_final_text(tok_text, orig_text, do_lower_case)
+            if final_text in seen_predictions:
+                continue
+
+            seen_predictions[final_text] = True
+            nbest.append(
+                _NbestPrediction(text=final_text,
+                                 start_logit=pred.start_logit,
+                                 end_logit=pred.end_logit))
+
+        # In very rare edge cases we could have no valid predictions. So we
+        # just create a nonce prediction in this case to avoid failure.
+        if not nbest:
+            nbest.append(
+                _NbestPrediction(text="empty", start_logit=0.0, end_logit=0.0))
+
+        assert len(nbest) >= 1
+
+        total_scores = []
+        for entry in nbest:
+            total_scores.append(entry.start_logit + entry.end_logit)
+
+        probs = _compute_softmax(total_scores)
+
+        nbest_json = []
+        for (i, entry) in enumerate(nbest):
+            output = collections.OrderedDict()
+            output["text"] = entry.text
+            output["probability"] = probs[i]
+            output["start_logit"] = float(entry.start_logit)
+            output["end_logit"] = float(entry.end_logit)
+            nbest_json.append(output)
+
+        all_predictions[example.qas_id] = nbest_json[0]["text"]
+        all_nbest_json[example.qas_id] = nbest_json
+
+    with open(output_prediction_file, "w") as writer:
+        writer.write(json.dumps(all_predictions, indent=4) + "\n")
+
+    with open(output_nbest_file, "w") as writer:
+        writer.write(json.dumps(all_nbest_json, indent=4) + "\n")
+
+
+def get_final_text(pred_text, orig_text, do_lower_case):
+    """Project the tokenized prediction back to the original text."""
+
+    # When we created the data, we kept track of the alignment between original
+    # (whitespace tokenized) tokens and our WordPiece tokenized tokens. So
+    # now `orig_text` contains the span of our original text corresponding to the
+    # span that we predicted.
+    #
+    # However, `orig_text` may contain extra characters that we don't want in
+    # our prediction.
+    #
+    # For example, let's say:
+    #   pred_text = steve smith
+    #   orig_text = Steve Smith's
+    #
+    # We don't want to return `orig_text` because it contains the extra "'s".
+    #
+    # We don't want to return `pred_text` because it's already been normalized
+    # (the SQuAD eval script also does punctuation stripping/lower casing but
+    # our tokenizer does additional normalization like stripping accent
+    # characters).
+    #
+    # What we really want to return is "Steve Smith".
+    #
+    # Therefore, we have to apply a semi-complicated alignment heruistic between
+    # `pred_text` and `orig_text` to get a character-to-charcter alignment. This
+    # can fail in certain cases in which case we just return `orig_text`.
+
+    def _strip_spaces(text):
+        ns_chars = []
+        ns_to_s_map = collections.OrderedDict()
+        for (i, c) in enumerate(text):
+            if c == " ":
+                continue
+            ns_to_s_map[len(ns_chars)] = i
+            ns_chars.append(c)
+        ns_text = "".join(ns_chars)
+        return (ns_text, ns_to_s_map)
+
+    # We first tokenize `orig_text`, strip whitespace from the result
+    # and `pred_text`, and check if they are the same length. If they are
+    # NOT the same length, the heuristic has failed. If they are the same
+    # length, we assume the characters are one-to-one aligned.
+    tokenizer = tokenization.BasicTokenizer(do_lower_case=do_lower_case)
+
+    tok_text = " ".join(tokenizer.tokenize(orig_text))
+
+    start_position = tok_text.find(pred_text)
+    if start_position == -1:
+        return orig_text
+    end_position = start_position + len(pred_text) - 1
+
+    (orig_ns_text, orig_ns_to_s_map) = _strip_spaces(orig_text)
+    (tok_ns_text, tok_ns_to_s_map) = _strip_spaces(tok_text)
+
+    if len(orig_ns_text) != len(tok_ns_text):
+        return orig_text
+
+    # We then project the characters in `pred_text` back to `orig_text` using
+    # the character-to-character alignment.
+    tok_s_to_ns_map = {}
+    for (i, tok_index) in six.iteritems(tok_ns_to_s_map):
+        tok_s_to_ns_map[tok_index] = i
+
+    orig_start_position = None
+    if start_position in tok_s_to_ns_map:
+        ns_start_position = tok_s_to_ns_map[start_position]
+        if ns_start_position in orig_ns_to_s_map:
+            orig_start_position = orig_ns_to_s_map[ns_start_position]
+
+    if orig_start_position is None:
+        return orig_text
+
+    orig_end_position = None
+    if end_position in tok_s_to_ns_map:
+        ns_end_position = tok_s_to_ns_map[end_position]
+        if ns_end_position in orig_ns_to_s_map:
+            orig_end_position = orig_ns_to_s_map[ns_end_position]
+
+    if orig_end_position is None:
+        return orig_text
+
+    output_text = orig_text[orig_start_position:(orig_end_position + 1)]
+    return output_text
+
+
+def _get_best_indexes(logits, n_best_size):
+    """Get the n-best logits from a list."""
+    index_and_score = sorted(enumerate(logits),
+                             key=lambda x: x[1],
+                             reverse=True)
+    best_indexes = []
+    for i in range(len(index_and_score)):
+        if i >= n_best_size:
+            break
+        best_indexes.append(index_and_score[i][0])
+    return best_indexes
+
+
+def _compute_softmax(scores):
+    """Compute softmax probability over raw logits."""
+    if not scores:
+        return []
+
+    max_score = None
+    for score in scores:
+        if max_score is None or score > max_score:
+            max_score = score
+
+    exp_scores = []
+    total_sum = 0.0
+    for score in scores:
+        x = math.exp(score - max_score)
+        exp_scores.append(x)
+        total_sum += x
+
+    probs = []
+    for score in exp_scores:
+        probs.append(score / total_sum)
+    return probs
\ No newline at end of file
diff --git a/examples/onnx/bert/tokenization.py b/examples/onnx/bert/tokenization.py
new file mode 100644
index 0000000..4dd0a31
--- /dev/null
+++ b/examples/onnx/bert/tokenization.py
@@ -0,0 +1,399 @@
+# coding=utf-8
+# Copyright 2018 The Google AI Language Team Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tokenization classes."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import collections
+import re
+import unicodedata
+import six
+
+
+def validate_case_matches_checkpoint(do_lower_case, init_checkpoint):
+  """Checks whether the casing config is consistent with the checkpoint name."""
+
+  # The casing has to be passed in by the user and there is no explicit check
+  # as to whether it matches the checkpoint. The casing information probably
+  # should have been stored in the bert_config.json file, but it's not, so
+  # we have to heuristically detect it to validate.
+
+  if not init_checkpoint:
+    return
+
+  m = re.match("^.*?([A-Za-z0-9_-]+)/bert_model.ckpt", init_checkpoint)
+  if m is None:
+    return
+
+  model_name = m.group(1)
+
+  lower_models = [
+      "uncased_L-24_H-1024_A-16", "uncased_L-12_H-768_A-12",
+      "multilingual_L-12_H-768_A-12", "chinese_L-12_H-768_A-12"
+  ]
+
+  cased_models = [
+      "cased_L-12_H-768_A-12", "cased_L-24_H-1024_A-16",
+      "multi_cased_L-12_H-768_A-12"
+  ]
+
+  is_bad_config = False
+  if model_name in lower_models and not do_lower_case:
+    is_bad_config = True
+    actual_flag = "False"
+    case_name = "lowercased"
+    opposite_flag = "True"
+
+  if model_name in cased_models and do_lower_case:
+    is_bad_config = True
+    actual_flag = "True"
+    case_name = "cased"
+    opposite_flag = "False"
+
+  if is_bad_config:
+    raise ValueError(
+        "You passed in `--do_lower_case=%s` with `--init_checkpoint=%s`. "
+        "However, `%s` seems to be a %s model, so you "
+        "should pass in `--do_lower_case=%s` so that the fine-tuning matches "
+        "how the model was pre-training. If this error is wrong, please "
+        "just comment out this check." % (actual_flag, init_checkpoint,
+                                          model_name, case_name, opposite_flag))
+
+
+def convert_to_unicode(text):
+  """Converts `text` to Unicode (if it's not already), assuming utf-8 input."""
+  if six.PY3:
+    if isinstance(text, str):
+      return text
+    elif isinstance(text, bytes):
+      return text.decode("utf-8", "ignore")
+    else:
+      raise ValueError("Unsupported string type: %s" % (type(text)))
+  elif six.PY2:
+    if isinstance(text, str):
+      return text.decode("utf-8", "ignore")
+    elif isinstance(text, unicode):
+      return text
+    else:
+      raise ValueError("Unsupported string type: %s" % (type(text)))
+  else:
+    raise ValueError("Not running on Python2 or Python 3?")
+
+
+def printable_text(text):
+  """Returns text encoded in a way suitable for print or `tf.logging`."""
+
+  # These functions want `str` for both Python2 and Python3, but in one case
+  # it's a Unicode string and in the other it's a byte string.
+  if six.PY3:
+    if isinstance(text, str):
+      return text
+    elif isinstance(text, bytes):
+      return text.decode("utf-8", "ignore")
+    else:
+      raise ValueError("Unsupported string type: %s" % (type(text)))
+  elif six.PY2:
+    if isinstance(text, str):
+      return text
+    elif isinstance(text, unicode):
+      return text.encode("utf-8")
+    else:
+      raise ValueError("Unsupported string type: %s" % (type(text)))
+  else:
+    raise ValueError("Not running on Python2 or Python 3?")
+
+
+def load_vocab(vocab_file):
+  """Loads a vocabulary file into a dictionary."""
+  vocab = collections.OrderedDict()
+  index = 0
+  with open(vocab_file, "rb") as reader:
+    while True:
+      token = reader.readline()
+      token = token.decode("utf-8", "ignore")
+      if not token:
+        break
+      token = token.strip()
+      vocab[token] = index
+      index += 1
+  return vocab
+
+
+def convert_by_vocab(vocab, items):
+  """Converts a sequence of [tokens|ids] using the vocab."""
+  output = []
+  for item in items:
+    output.append(vocab[item])
+  return output
+
+
+def convert_tokens_to_ids(vocab, tokens):
+  return convert_by_vocab(vocab, tokens)
+
+
+def convert_ids_to_tokens(inv_vocab, ids):
+  return convert_by_vocab(inv_vocab, ids)
+
+
+def whitespace_tokenize(text):
+  """Runs basic whitespace cleaning and splitting on a piece of text."""
+  text = text.strip()
+  if not text:
+    return []
+  tokens = text.split()
+  return tokens
+
+
+class FullTokenizer(object):
+  """Runs end-to-end tokenziation."""
+
+  def __init__(self, vocab_file, do_lower_case=True):
+    self.vocab = load_vocab(vocab_file)
+    self.inv_vocab = {v: k for k, v in self.vocab.items()}
+    self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case)
+    self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab)
+
+  def tokenize(self, text):
+    split_tokens = []
+    for token in self.basic_tokenizer.tokenize(text):
+      for sub_token in self.wordpiece_tokenizer.tokenize(token):
+        split_tokens.append(sub_token)
+
+    return split_tokens
+
+  def convert_tokens_to_ids(self, tokens):
+    return convert_by_vocab(self.vocab, tokens)
+
+  def convert_ids_to_tokens(self, ids):
+    return convert_by_vocab(self.inv_vocab, ids)
+
+
+class BasicTokenizer(object):
+  """Runs basic tokenization (punctuation splitting, lower casing, etc.)."""
+
+  def __init__(self, do_lower_case=True):
+    """Constructs a BasicTokenizer.
+
+    Args:
+      do_lower_case: Whether to lower case the input.
+    """
+    self.do_lower_case = do_lower_case
+
+  def tokenize(self, text):
+    """Tokenizes a piece of text."""
+    text = convert_to_unicode(text)
+    text = self._clean_text(text)
+
+    # This was added on November 1st, 2018 for the multilingual and Chinese
+    # models. This is also applied to the English models now, but it doesn't
+    # matter since the English models were not trained on any Chinese data
+    # and generally don't have any Chinese data in them (there are Chinese
+    # characters in the vocabulary because Wikipedia does have some Chinese
+    # words in the English Wikipedia.).
+    text = self._tokenize_chinese_chars(text)
+
+    orig_tokens = whitespace_tokenize(text)
+    split_tokens = []
+    for token in orig_tokens:
+      if self.do_lower_case:
+        token = token.lower()
+        token = self._run_strip_accents(token)
+      split_tokens.extend(self._run_split_on_punc(token))
+
+    output_tokens = whitespace_tokenize(" ".join(split_tokens))
+    return output_tokens
+
+  def _run_strip_accents(self, text):
+    """Strips accents from a piece of text."""
+    text = unicodedata.normalize("NFD", text)
+    output = []
+    for char in text:
+      cat = unicodedata.category(char)
+      if cat == "Mn":
+        continue
+      output.append(char)
+    return "".join(output)
+
+  def _run_split_on_punc(self, text):
+    """Splits punctuation on a piece of text."""
+    chars = list(text)
+    i = 0
+    start_new_word = True
+    output = []
+    while i < len(chars):
+      char = chars[i]
+      if _is_punctuation(char):
+        output.append([char])
+        start_new_word = True
+      else:
+        if start_new_word:
+          output.append([])
+        start_new_word = False
+        output[-1].append(char)
+      i += 1
+
+    return ["".join(x) for x in output]
+
+  def _tokenize_chinese_chars(self, text):
+    """Adds whitespace around any CJK character."""
+    output = []
+    for char in text:
+      cp = ord(char)
+      if self._is_chinese_char(cp):
+        output.append(" ")
+        output.append(char)
+        output.append(" ")
+      else:
+        output.append(char)
+    return "".join(output)
+
+  def _is_chinese_char(self, cp):
+    """Checks whether CP is the codepoint of a CJK character."""
+    # This defines a "chinese character" as anything in the CJK Unicode block:
+    #   https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
+    #
+    # Note that the CJK Unicode block is NOT all Japanese and Korean characters,
+    # despite its name. The modern Korean Hangul alphabet is a different block,
+    # as is Japanese Hiragana and Katakana. Those alphabets are used to write
+    # space-separated words, so they are not treated specially and handled
+    # like the all of the other languages.
+    if ((cp >= 0x4E00 and cp <= 0x9FFF) or  #
+        (cp >= 0x3400 and cp <= 0x4DBF) or  #
+        (cp >= 0x20000 and cp <= 0x2A6DF) or  #
+        (cp >= 0x2A700 and cp <= 0x2B73F) or  #
+        (cp >= 0x2B740 and cp <= 0x2B81F) or  #
+        (cp >= 0x2B820 and cp <= 0x2CEAF) or
+        (cp >= 0xF900 and cp <= 0xFAFF) or  #
+        (cp >= 0x2F800 and cp <= 0x2FA1F)):  #
+      return True
+
+    return False
+
+  def _clean_text(self, text):
+    """Performs invalid character removal and whitespace cleanup on text."""
+    output = []
+    for char in text:
+      cp = ord(char)
+      if cp == 0 or cp == 0xfffd or _is_control(char):
+        continue
+      if _is_whitespace(char):
+        output.append(" ")
+      else:
+        output.append(char)
+    return "".join(output)
+
+
+class WordpieceTokenizer(object):
+  """Runs WordPiece tokenziation."""
+
+  def __init__(self, vocab, unk_token="[UNK]", max_input_chars_per_word=200):
+    self.vocab = vocab
+    self.unk_token = unk_token
+    self.max_input_chars_per_word = max_input_chars_per_word
+
+  def tokenize(self, text):
+    """Tokenizes a piece of text into its word pieces.
+
+    This uses a greedy longest-match-first algorithm to perform tokenization
+    using the given vocabulary.
+
+    For example:
+      input = "unaffable"
+      output = ["un", "##aff", "##able"]
+
+    Args:
+      text: A single token or whitespace separated tokens. This should have
+        already been passed through `BasicTokenizer.
+
+    Returns:
+      A list of wordpiece tokens.
+    """
+
+    text = convert_to_unicode(text)
+
+    output_tokens = []
+    for token in whitespace_tokenize(text):
+      chars = list(token)
+      if len(chars) > self.max_input_chars_per_word:
+        output_tokens.append(self.unk_token)
+        continue
+
+      is_bad = False
+      start = 0
+      sub_tokens = []
+      while start < len(chars):
+        end = len(chars)
+        cur_substr = None
+        while start < end:
+          substr = "".join(chars[start:end])
+          if start > 0:
+            substr = "##" + substr
+          if substr in self.vocab:
+            cur_substr = substr
+            break
+          end -= 1
+        if cur_substr is None:
+          is_bad = True
+          break
+        sub_tokens.append(cur_substr)
+        start = end
+
+      if is_bad:
+        output_tokens.append(self.unk_token)
+      else:
+        output_tokens.extend(sub_tokens)
+    return output_tokens
+
+
+def _is_whitespace(char):
+  """Checks whether `chars` is a whitespace character."""
+  # \t, \n, and \r are technically contorl characters but we treat them
+  # as whitespace since they are generally considered as such.
+  if char == " " or char == "\t" or char == "\n" or char == "\r":
+    return True
+  cat = unicodedata.category(char)
+  if cat == "Zs":
+    return True
+  return False
+
+
+def _is_control(char):
+  """Checks whether `chars` is a control character."""
+  # These are technically control characters but we count them as whitespace
+  # characters.
+  if char == "\t" or char == "\n" or char == "\r":
+    return False
+  cat = unicodedata.category(char)
+  if cat in ("Cc", "Cf"):
+    return True
+  return False
+
+
+def _is_punctuation(char):
+  """Checks whether `chars` is a punctuation character."""
+  cp = ord(char)
+  # We treat all non-letter/number ASCII as punctuation.
+  # Characters such as "^", "$", and "`" are not in the Unicode
+  # Punctuation class but we treat them as punctuation anyways, for
+  # consistency.
+  if ((cp >= 33 and cp <= 47) or (cp >= 58 and cp <= 64) or
+      (cp >= 91 and cp <= 96) or (cp >= 123 and cp <= 126)):
+    return True
+  cat = unicodedata.category(char)
+  if cat.startswith("P"):
+    return True
+  return False
diff --git a/examples/onnx/fer_emotion.py b/examples/onnx/fer_emotion.py
new file mode 100644
index 0000000..adbadaf
--- /dev/null
+++ b/examples/onnx/fer_emotion.py
@@ -0,0 +1,111 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import numpy as np
+from PIL import Image
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
+
+def preprocess(img):
+    input_shape = (1, 1, 64, 64)
+    img = img.resize((64, 64), Image.ANTIALIAS)
+    img_data = np.array(img).astype(np.float32)
+    img_data = np.resize(img_data, input_shape)
+    return img_data
+
+
+def get_image_labe():
+    labels = [
+        'neutral', 'happiness', 'surprise', 'sadness', 'anger', 'disgust',
+        'fear', 'contempt'
+    ]
+    # download image
+    image_url = 'https://microsoft.github.io/onnxjs-demo/img/fear.8d1417fa.jpg'
+    img = Image.open(check_exist_or_download(image_url))
+
+    return img, labels
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+if __name__ == "__main__":
+
+    url = 'https://onnxzoo.blob.core.windows.net/models/opset_8/emotion_ferplus/emotion_ferplus.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'emotion_ferplus', 'model.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 1)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test 
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(os.path.join('/tmp', 'emotion_ferplus', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference
+    logging.info("preprocessing...")
+    img, labels = get_image_labe()
+    img = preprocess(img)
+
+    x_batch = tensor.Tensor(device=dev, data=img)
+
+    logging.info("model running...")
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    y = tensor.softmax(y)
+    scores = tensor.to_numpy(y)
+    scores = np.squeeze(scores)
+    a = np.argsort(scores)[::-1]
+    for i in a[0:5]:
+        logging.info('class=%s ; probability=%f' % (labels[i], scores[i]))
diff --git a/examples/onnx/mnist.py b/examples/onnx/mnist.py
new file mode 100644
index 0000000..d4f7d2f
--- /dev/null
+++ b/examples/onnx/mnist.py
@@ -0,0 +1,320 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import gzip
+import numpy as np
+import codecs
+
+
+from singa import device
+from singa import tensor
+from singa import opt
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(message)s')
+
+def load_dataset():
+    train_x_url = 'http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz'
+    train_y_url = 'http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz'
+    valid_x_url = 'http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz'
+    valid_y_url = 'http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz'
+    train_x = read_image_file(check_exist_or_download(train_x_url)).astype(
+        np.float32)
+    train_y = read_label_file(check_exist_or_download(train_y_url)).astype(
+        np.float32)
+    valid_x = read_image_file(check_exist_or_download(valid_x_url)).astype(
+        np.float32)
+    valid_y = read_label_file(check_exist_or_download(valid_y_url)).astype(
+        np.float32)
+    return train_x, train_y, valid_x, valid_y
+
+
+def read_label_file(path):
+    with gzip.open(path, 'rb') as f:
+        data = f.read()
+        assert get_int(data[:4]) == 2049
+        length = get_int(data[4:8])
+        parsed = np.frombuffer(data, dtype=np.uint8, offset=8).reshape((length))
+        return parsed
+
+
+def get_int(b):
+    return int(codecs.encode(b, 'hex'), 16)
+
+
+def read_image_file(path):
+    with gzip.open(path, 'rb') as f:
+        data = f.read()
+        assert get_int(data[:4]) == 2051
+        length = get_int(data[4:8])
+        num_rows = get_int(data[8:12])
+        num_cols = get_int(data[12:16])
+        parsed = np.frombuffer(data, dtype=np.uint8, offset=16).reshape(
+            (length, 1, num_rows, num_cols))
+        return parsed
+
+
+def to_categorical(y, num_classes):
+    y = np.array(y, dtype="int")
+    n = y.shape[0]
+    categorical = np.zeros((n, num_classes))
+    categorical[np.arange(n), y] = 1
+    categorical = categorical.astype(np.float32)
+    return categorical
+
+
+class CNN:
+
+    def __init__(self):
+        self.conv1 = autograd.Conv2d(1, 20, 5, padding=0)
+        self.conv2 = autograd.Conv2d(20, 50, 5, padding=0)
+        self.linear1 = autograd.Linear(4 * 4 * 50, 500, bias=False)
+        self.linear2 = autograd.Linear(500, 10, bias=False)
+        self.pooling1 = autograd.MaxPool2d(2, 2, padding=0)
+        self.pooling2 = autograd.MaxPool2d(2, 2, padding=0)
+
+    def forward(self, x):
+        y = self.conv1(x)
+        y = autograd.relu(y)
+        y = self.pooling1(y)
+        y = self.conv2(y)
+        y = autograd.relu(y)
+        y = self.pooling2(y)
+        y = autograd.flatten(y)
+        y = self.linear1(y)
+        y = autograd.relu(y)
+        y = self.linear2(y)
+        return y
+
+
+def accuracy(pred, target):
+    y = np.argmax(pred, axis=1)
+    t = np.argmax(target, axis=1)
+    a = y == t
+    return np.array(a, "int").sum() / float(len(t))
+
+
+def train(model,
+          x,
+          y,
+          epochs=1,
+          batch_size=64,
+          dev=device.get_default_device()):
+    batch_number = x.shape[0] // batch_size
+
+    for i in range(epochs):
+        for b in range(batch_number):
+            l_idx = b * batch_size
+            r_idx = (b + 1) * batch_size
+
+            x_batch = tensor.Tensor(device=dev, data=x[l_idx:r_idx])
+            target_batch = tensor.Tensor(device=dev, data=y[l_idx:r_idx])
+
+            output_batch = model.forward(x_batch)
+
+            loss = autograd.softmax_cross_entropy(output_batch, target_batch)
+            accuracy_rate = accuracy(tensor.to_numpy(output_batch),
+                                     tensor.to_numpy(target_batch))
+
+            sgd = opt.SGD(lr=0.001)
+            for p, gp in autograd.backward(loss):
+                sgd.update(p, gp)
+            sgd.step()
+
+            if b % 1e2 == 0:
+                logging.info("acc %6.2f loss, %6.2f" %
+                      (accuracy_rate, tensor.to_numpy(loss)[0]))
+    logging.info("training completed")
+    return x_batch, output_batch
+
+
+def make_onnx(x, y):
+    return sonnx.to_onnx([x], [y])
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+def re_train(sg_ir,
+             x,
+             y,
+             epochs=1,
+             batch_size=64,
+             dev=device.get_default_device()):
+    batch_number = x.shape[0] // batch_size
+
+    new_model = Infer(sg_ir)
+
+    for i in range(epochs):
+        for b in range(batch_number):
+            l_idx = b * batch_size
+            r_idx = (b + 1) * batch_size
+
+            x_batch = tensor.Tensor(device=dev, data=x[l_idx:r_idx])
+            target_batch = tensor.Tensor(device=dev, data=y[l_idx:r_idx])
+
+            output_batch = new_model.forward(x_batch)
+
+            loss = autograd.softmax_cross_entropy(output_batch, target_batch)
+            accuracy_rate = accuracy(tensor.to_numpy(output_batch),
+                                     tensor.to_numpy(target_batch))
+
+            sgd = opt.SGD(lr=0.01)
+            for p, gp in autograd.backward(loss):
+                sgd.update(p, gp)
+            sgd.step()
+
+            if b % 1e2 == 0:
+                logging.info("acc %6.2f loss, %6.2f" %
+                      (accuracy_rate, tensor.to_numpy(loss)[0]))
+    logging.info("re-training completed")
+    return new_model
+
+
+class Trans:
+
+    def __init__(self, sg_ir, last_layers):
+        self.sg_ir = sg_ir
+        self.last_layers = last_layers
+        self.append_linear1 = autograd.Linear(500, 128, bias=False)
+        self.append_linear2 = autograd.Linear(128, 32, bias=False)
+        self.append_linear3 = autograd.Linear(32, 10, bias=False)
+
+    def forward(self, x):
+        y = sg_ir.run([x], last_layers=self.last_layers)[0]
+        y = self.append_linear1(y)
+        y = autograd.relu(y)
+        y = self.append_linear2(y)
+        y = autograd.relu(y)
+        y = self.append_linear3(y)
+        y = autograd.relu(y)
+        return y
+
+
+def transfer_learning(sg_ir,
+                      x,
+                      y,
+                      epochs=1,
+                      batch_size=64,
+                      dev=device.get_default_device()):
+    batch_number = x.shape[0] // batch_size
+
+    trans_model = Trans(sg_ir, -1)
+
+    for i in range(epochs):
+        for b in range(batch_number):
+            l_idx = b * batch_size
+            r_idx = (b + 1) * batch_size
+
+            x_batch = tensor.Tensor(device=dev, data=x[l_idx:r_idx])
+            target_batch = tensor.Tensor(device=dev, data=y[l_idx:r_idx])
+            output_batch = trans_model.forward(x_batch)
+
+            loss = autograd.softmax_cross_entropy(output_batch, target_batch)
+            accuracy_rate = accuracy(tensor.to_numpy(output_batch),
+                                     tensor.to_numpy(target_batch))
+
+            sgd = opt.SGD(lr=0.07)
+            for p, gp in autograd.backward(loss):
+                sgd.update(p, gp)
+            sgd.step()
+
+            if b % 1e2 == 0:
+                logging.info("acc %6.2f loss, %6.2f" %
+                      (accuracy_rate, tensor.to_numpy(loss)[0]))
+    logging.info("transfer-learning completed")
+    return trans_model
+
+
+def test(model, x, y, batch_size=64, dev=device.get_default_device()):
+    batch_number = x.shape[0] // batch_size
+
+    result = 0
+    for b in range(batch_number):
+        l_idx = b * batch_size
+        r_idx = (b + 1) * batch_size
+
+        x_batch = tensor.Tensor(device=dev, data=x[l_idx:r_idx])
+        target_batch = tensor.Tensor(device=dev, data=y[l_idx:r_idx])
+
+        output_batch = model.forward(x_batch)
+        result += accuracy(tensor.to_numpy(output_batch),
+                           tensor.to_numpy(target_batch))
+
+    logging.info("testing acc %6.2f" % (result / batch_number))
+
+
+if __name__ == "__main__":
+    # create device
+    dev = device.create_cuda_gpu()
+    #dev = device.get_default_device()
+    # create model
+    model = CNN()
+    # load data
+    train_x, train_y, valid_x, valid_y = load_dataset()
+    # normalization
+    train_x = train_x / 255
+    valid_x = valid_x / 255
+    train_y = to_categorical(train_y, 10)
+    valid_y = to_categorical(valid_y, 10)
+    # do training
+    autograd.training = True
+    x, y = train(model, train_x, train_y, dev=dev)
+    onnx_model = make_onnx(x, y)
+    # logging.info('The model is:\n{}'.format(onnx_model))
+
+    # Save the ONNX model
+    model_path = os.path.join('/', 'tmp', 'mnist.onnx')
+    onnx.save(onnx_model, model_path)
+    logging.info('The model is saved.')
+
+    # load the ONNX model
+    onnx_model = onnx.load(model_path)
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+
+    # inference
+    autograd.training = False
+    logging.info('The inference result is:')
+    test(Infer(sg_ir), valid_x, valid_y, dev=dev)
+
+    # re-training
+    autograd.training = True
+    new_model = re_train(sg_ir, train_x, train_y, dev=dev)
+    autograd.training = False
+    test(new_model, valid_x, valid_y, dev=dev)
+
+    # transfer-learning
+    autograd.training = True
+    new_model = transfer_learning(sg_ir, train_x, train_y, dev=dev)
+    autograd.training = False
+    test(new_model, valid_x, valid_y, dev=dev)
\ No newline at end of file
diff --git a/examples/onnx/mobilenet.py b/examples/onnx/mobilenet.py
new file mode 100644
index 0000000..e9fd90c
--- /dev/null
+++ b/examples/onnx/mobilenet.py
@@ -0,0 +1,116 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import numpy as np
+from PIL import Image
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
+
+def preprocess(img):
+    img = img.resize((256, 256))
+    img = img.crop((16, 16, 240, 240))
+    img = np.array(img).astype(np.float32) / 255.
+    img = np.rollaxis(img, 2, 0)
+    for channel, mean, std in zip(range(3), [0.485, 0.456, 0.406],
+                                  [0.229, 0.224, 0.225]):
+        img[channel, :, :] -= mean
+        img[channel, :, :] /= std
+    img = np.expand_dims(img, axis=0)
+    return img
+
+
+def get_image_labe():
+    # download label
+    label_url = 'https://s3.amazonaws.com/onnx-model-zoo/synset.txt'
+    with open(check_exist_or_download(label_url), 'r') as f:
+        labels = [l.rstrip() for l in f]
+
+    # download image
+    image_url = 'https://s3.amazonaws.com/model-server/inputs/kitten.jpg'
+    img = Image.open(check_exist_or_download(image_url))
+    return img, labels
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+if __name__ == "__main__":
+
+    url = 'https://s3.amazonaws.com/onnx-model-zoo/mobilenet/mobilenetv2-1.0/mobilenetv2-1.0.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'mobilenetv2-1.0',
+                              'mobilenetv2-1.0.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 1)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test dataset
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(os.path.join('/tmp', 'mobilenetv2-1.0', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference
+    logging.info("preprocessing...")
+    img, labels = get_image_labe()
+    img = preprocess(img)
+
+    logging.info("model running...")
+    x_batch = tensor.Tensor(device=dev, data=img)
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    y = tensor.softmax(y)
+    scores = tensor.to_numpy(y)
+    scores = np.squeeze(scores)
+    a = np.argsort(scores)[::-1]
+    for i in a[0:5]:
+        logging.info('class=%s ; probability=%f' % (labels[i], scores[i]))
diff --git a/examples/onnx/resnet18.py b/examples/onnx/resnet18.py
new file mode 100644
index 0000000..c0ef13a
--- /dev/null
+++ b/examples/onnx/resnet18.py
@@ -0,0 +1,115 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import numpy as np
+from PIL import Image
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
+
+def preprocess(img):
+    img = img.resize((256, 256))
+    img = img.crop((16, 16, 240, 240))
+    img = np.array(img).astype(np.float32) / 255.
+    img = np.rollaxis(img, 2, 0)
+    for channel, mean, std in zip(range(3), [0.485, 0.456, 0.406],
+                                  [0.229, 0.224, 0.225]):
+        img[channel, :, :] -= mean
+        img[channel, :, :] /= std
+    img = np.expand_dims(img, axis=0)
+    return img
+
+
+def get_image_labe():
+    # download label
+    label_url = 'https://s3.amazonaws.com/onnx-model-zoo/synset.txt'
+    with open(check_exist_or_download(label_url), 'r') as f:
+        labels = [l.rstrip() for l in f]
+
+    # download image
+    image_url = 'https://s3.amazonaws.com/model-server/inputs/kitten.jpg'
+    img = Image.open(check_exist_or_download(image_url))
+    return img, labels
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+if __name__ == "__main__":
+
+    url = 'https://s3.amazonaws.com/onnx-model-zoo/resnet/resnet18v1/resnet18v1.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'resnet18v1', 'resnet18v1.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 1)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test 
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(os.path.join('/tmp', 'resnet18v1', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference
+    logging.info("preprocessing...")
+    img, labels = get_image_labe()
+    img = preprocess(img)
+
+    logging.info("model running...")
+    x_batch = tensor.Tensor(device=dev, data=img)
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    y = tensor.softmax(y)
+    scores = tensor.to_numpy(y)
+    scores = np.squeeze(scores)
+    a = np.argsort(scores)[::-1]
+    for i in a[0:5]:
+        logging.info('class=%s ; probability=%f' % (labels[i], scores[i]))
\ No newline at end of file
diff --git a/examples/onnx/tiny_yolov2.py b/examples/onnx/tiny_yolov2.py
new file mode 100644
index 0000000..8aff769
--- /dev/null
+++ b/examples/onnx/tiny_yolov2.py
@@ -0,0 +1,167 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import numpy as np
+from PIL import Image, ImageDraw
+
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(message)s')
+
+
+def preprocess(img):
+    img = np.array(img).astype(np.float32)
+    img = np.rollaxis(img, 2, 0)
+    img = np.expand_dims(img, axis=0)
+    return img
+
+
+def get_image():
+    image_url = 'https://raw.githubusercontent.com/simo23/tinyYOLOv2/master/person.jpg'
+    img = Image.open(check_exist_or_download(image_url))
+    img = img.resize((416, 416))
+    return img
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+def postprcess(out):
+    numClasses = 20
+    anchors = [1.08, 1.19, 3.42, 4.41, 6.63, 11.38, 9.42, 5.11, 16.62, 10.52]
+
+    def sigmoid(x, derivative=False):
+        return x * (1 - x) if derivative else 1 / (1 + np.exp(-x))
+
+    def softmax(x):
+        scoreMatExp = np.exp(np.asarray(x))
+        return scoreMatExp / scoreMatExp.sum(0)
+
+    clut = [(0, 0, 0), (255, 0, 0), (255, 0, 255), (0, 0, 255), (0, 255, 0),
+            (0, 255, 128), (128, 255, 0), (128, 128, 0), (0, 128, 255),
+            (128, 0, 128), (255, 0, 128), (128, 0, 255), (255, 128, 128),
+            (128, 255, 128), (255, 255, 0), (255, 128, 128), (128, 128, 255),
+            (255, 128, 128), (128, 255, 128)]
+    label = [
+        "aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat",
+        "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person",
+        "pottedplant", "sheep", "sofa", "train", "tvmonitor"
+    ]
+
+    img = get_image()
+    draw = ImageDraw.Draw(img)
+
+    for cy in range(13):
+        for cx in range(13):
+            for b in range(5):
+                channel = b * (numClasses + 5)
+                tx = out[channel][cy][cx]
+                ty = out[channel + 1][cy][cx]
+                tw = out[channel + 2][cy][cx]
+                th = out[channel + 3][cy][cx]
+                tc = out[channel + 4][cy][cx]
+                x = (float(cx) + sigmoid(tx)) * 32
+                y = (float(cy) + sigmoid(ty)) * 32
+
+                w = np.exp(tw) * 32 * anchors[2 * b]
+                h = np.exp(th) * 32 * anchors[2 * b + 1]
+
+                confidence = sigmoid(tc)
+
+                classes = np.zeros(numClasses)
+                for c in range(0, numClasses):
+                    classes[c] = out[channel + 5 + c][cy][cx]
+
+                classes = softmax(classes)
+                detectedClass = classes.argmax()
+                if 0.5 < classes[detectedClass] * confidence:
+                    color = clut[detectedClass]
+                    x = x - w / 2
+                    y = y - h / 2
+                    draw.line((x, y, x + w, y), fill=color)
+                    draw.line((x, y, x, y + h), fill=color)
+                    draw.line((x + w, y, x + w, y + h), fill=color)
+                    draw.line((x, y + h, x + w, y + h), fill=color)
+                    draw.text((x, y), label[detectedClass], fill=color)
+                    logging.info("bounding box: (%.2f, %.2f, %.2f, %.2f)" %
+                                 (x, y, x + w, y + h))
+                    logging.info('class=%s ; probability=%f' %
+                                 (label[detectedClass],
+                                  classes[detectedClass] * confidence))
+    img.save("result.png")
+
+
+if __name__ == "__main__":
+
+    url = 'https://onnxzoo.blob.core.windows.net/models/opset_8/tiny_yolov2/tiny_yolov2.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'tiny_yolov2', 'Model.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 1)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test dataset
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(os.path.join('/tmp', 'tiny_yolov2', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference
+    logging.info("preprocessing...")
+    img = get_image()
+    img = preprocess(img)
+
+    logging.info("model running...")
+    x_batch = tensor.Tensor(device=dev, data=img)
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    out = tensor.to_numpy(y)[0]
+    postprcess(out)
diff --git a/examples/onnx/utils.py b/examples/onnx/utils.py
new file mode 100644
index 0000000..aff4492
--- /dev/null
+++ b/examples/onnx/utils.py
@@ -0,0 +1,72 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import urllib.request
+import tarfile
+import glob
+import onnx
+from onnx import numpy_helper
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(message)s')
+
+def download_model(url):
+    download_dir = '/tmp/'
+    with tarfile.open(check_exist_or_download(url), 'r') as t:
+        t.extractall(path=download_dir)
+
+
+def load_dataset(test_data_dir):
+    # load inputs
+    inputs = []
+    inputs_num = len(glob.glob(os.path.join(test_data_dir, 'input_*.pb')))
+    for i in range(inputs_num):
+        input_file = os.path.join(test_data_dir, 'input_{}.pb'.format(i))
+        onnx_tensor = onnx.TensorProto()
+        with open(input_file, 'rb') as f:
+            onnx_tensor.ParseFromString(f.read())
+        inputs.append(numpy_helper.to_array(onnx_tensor))
+
+    # load reference outputs
+    ref_outputs = []
+    ref_outputs_num = len(glob.glob(os.path.join(test_data_dir, 'output_*.pb')))
+    for i in range(ref_outputs_num):
+        output_file = os.path.join(test_data_dir, 'output_{}.pb'.format(i))
+        onnx_tensor = onnx.TensorProto()
+        with open(output_file, 'rb') as f:
+            onnx_tensor.ParseFromString(f.read())
+        ref_outputs.append(numpy_helper.to_array(onnx_tensor))
+    return inputs, ref_outputs
+
+
+def check_exist_or_download(url):
+    download_dir = '/tmp/'
+    name = url.rsplit('/', 1)[-1]
+    filename = os.path.join(download_dir, name)
+    if not os.path.isfile(filename):
+        logging.info("Downloading %s" % url)
+        urllib.request.urlretrieve(url, filename)
+    return filename
+
+
+def update_batch_size(onnx_model, batch_size):
+    model_input = onnx_model.graph.input[0]
+    model_input.type.tensor_type.shape.dim[0].dim_value = batch_size
+    model_output = onnx_model.graph.output[0]
+    model_output.type.tensor_type.shape.dim[0].dim_value = batch_size
+    return onnx_model
diff --git a/examples/onnx/vgg16.py b/examples/onnx/vgg16.py
new file mode 100644
index 0000000..d97b025
--- /dev/null
+++ b/examples/onnx/vgg16.py
@@ -0,0 +1,114 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under th
+
+import os
+import numpy as np
+from PIL import Image
+
+from singa import device
+from singa import tensor
+from singa import autograd
+from singa import sonnx
+import onnx
+from utils import download_model, update_batch_size, check_exist_or_download
+
+import logging
+logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(message)s')
+
+def preprocess(img):
+    img = img.resize((256, 256))
+    img = img.crop((16, 16, 240, 240))
+    img = np.array(img).astype(np.float32) / 255.
+    img = np.rollaxis(img, 2, 0)
+    for channel, mean, std in zip(range(3), [0.485, 0.456, 0.406],
+                                  [0.229, 0.224, 0.225]):
+        img[channel, :, :] -= mean
+        img[channel, :, :] /= std
+    img = np.expand_dims(img, axis=0)
+    return img
+
+
+def get_image_labe():
+    # download label
+    label_url = 'https://s3.amazonaws.com/onnx-model-zoo/synset.txt'
+    with open(check_exist_or_download(label_url), 'r') as f:
+        labels = [l.rstrip() for l in f]
+
+    # download image
+    image_url = 'https://s3.amazonaws.com/model-server/inputs/kitten.jpg'
+    img = Image.open(check_exist_or_download(image_url))
+    return img, labels
+
+
+class Infer:
+
+    def __init__(self, sg_ir):
+        self.sg_ir = sg_ir
+        for idx, tens in sg_ir.tensor_map.items():
+            # allow the tensors to be updated
+            tens.requires_grad = True
+            tens.stores_grad = True
+            sg_ir.tensor_map[idx] = tens
+
+    def forward(self, x):
+        return sg_ir.run([x])[0]
+
+
+if __name__ == "__main__":
+    url = 'https://s3.amazonaws.com/onnx-model-zoo/vgg/vgg16/vgg16.tar.gz'
+    download_dir = '/tmp/'
+    model_path = os.path.join(download_dir, 'vgg16', 'vgg16.onnx')
+
+    logging.info("onnx load model...")
+    download_model(url)
+    onnx_model = onnx.load(model_path)
+
+    # set batch size
+    onnx_model = update_batch_size(onnx_model, 1)
+
+    # prepare the model
+    logging.info("prepare model...")
+    dev = device.create_cuda_gpu()
+    sg_ir = sonnx.prepare(onnx_model, device=dev)
+    autograd.training = False
+    model = Infer(sg_ir)
+
+    # verifty the test 
+    # from utils import load_dataset
+    # inputs, ref_outputs = load_dataset(os.path.join('/tmp', 'vgg16', 'test_data_set_0'))
+    # x_batch = tensor.Tensor(device=dev, data=inputs[0])
+    # outputs = model.forward(x_batch)
+    # for ref_o, o in zip(ref_outputs, outputs):
+    #     np.testing.assert_almost_equal(ref_o, tensor.to_numpy(o), 4)
+
+    # inference
+    logging.info("preprocessing...")
+    img, labels = get_image_labe()
+    img = preprocess(img)
+
+    logging.info("model running...")
+    x_batch = tensor.Tensor(device=dev, data=img)
+    y = model.forward(x_batch)
+
+    logging.info("postprocessing...")
+    y = tensor.softmax(y)
+    scores = tensor.to_numpy(y)
+    scores = np.squeeze(scores)
+    a = np.argsort(scores)[::-1]
+    for i in a[0:5]:
+        logging.info('class=%s ; probability=%f' % (labels[i], scores[i]))
diff --git a/python/singa/autograd.py b/python/singa/autograd.py
index 8f64eb8..17ce07e 100644
--- a/python/singa/autograd.py
+++ b/python/singa/autograd.py
@@ -638,11 +638,7 @@ class Reshape(Operation):
 
     def __init__(self, shape):
         super(Reshape, self).__init__()
-        if isinstance(shape, tensor.Tensor):
-            self.shape = np.asarray(tensor.to_numpy(shape).astype(
-                np.int32)).tolist()
-        else:
-            self.shape = list(shape)
+        self.shape = list(shape)
 
     def forward(self, x):
         self._shape = x.shape()
@@ -656,7 +652,6 @@ class Reshape(Operation):
         # handle the shape with -1
         hidden_shape = int(np.prod(self._shape) // np.abs(np.prod(shape)))
         self.cache = [s if s != -1 else hidden_shape for s in shape]
-
         return singa.Reshape(x, self.cache)
 
     def backward(self, dy):
@@ -1431,7 +1426,6 @@ class Conv2d(Layer):
         self.pad_mode = pad_mode
 
     def __call__(self, x):
-
         assert x.shape[1] == self.in_channels, "in_channels mismatched"
 
         # if same pad mode, re-compute the padding
@@ -1689,10 +1683,8 @@ class _Pooling2d(Operation):
             y = singa.GpuPoolingForward(self.handle, x)
         else:
             y = singa.CpuPoolingForward(self.handle, x)
-
         if training:
             self.cache = (x, y)
-
         return y
 
     def backward(self, dy):
@@ -2243,11 +2235,21 @@ class Mul(Operation):
         super(Mul, self).__init__()
 
     def forward(self, a, b):
-        res = singa.__mul__(a, b)
+        # todo we cannot support mul op for int tensors
+        _a, _b = a, b
+        dtype0 = _a.data_type()
+        dtype1 = _b.data_type()
+        if dtype0 == singa.kInt or dtype1 == singa.kInt:
+            _a = a.AsType(singa.kFloat32)
+            _b = b.AsType(singa.kFloat32)
+            res = singa.__mul__(_a, _b)
+            res = res.AsType(singa.kInt)
+        else:
+            res = singa.__mul__(_a, _b)
         if training:
-            self.input = (a, b)
-            self.shape0 = list(a.shape())
-            self.shape1 = list(b.shape())
+            self.input = (_a, _b)
+            self.shape0 = list(_a.shape())
+            self.shape1 = list(_b.shape())
             self.shape3 = list(res.shape())
         return res
 
@@ -2275,6 +2277,9 @@ class Unsqueeze(Operation):
     def forward(self, x):
         self.cache = x.shape()
         cur = list(self.cache)
+        # todo, need optimize after we have scalar tensor
+        if len(self.cache) == 1 and self.axis == [0]:
+            return x
         for i in self.axis:
             cur.insert(i, 1)
         return singa.Reshape(x, cur)
@@ -2542,7 +2547,7 @@ def exp(a):
 class LeakyRelu(Operation):
 
     def __init__(self, a):
-        super().__init__(self)
+        super(LeakyRelu, self).__init__()
         self.a = a
 
     def forward(self, x):
@@ -2840,14 +2845,18 @@ class Squeeze(Operation):
         if (self.axis == []):
             newshape = list(filter(lambda i: i != 1, self.cache))
         else:
-            for i in self.axis:
+            for id, i in enumerate(self.axis):
                 assert i < len(self.cache)
+                self.axis[id] = i % len(self.cache)
                 assert self.cache[
                     i] == 1, "the length of axis {} is {}, which should be 1".format(
                         i, self.cache[i])
             for ind, v in enumerate(self.cache):
                 if ind not in self.axis:
                     newshape.append(v)
+        # todo, need optimize after we have scalar tensor
+        if newshape == []:
+            return x
         return singa.Reshape(x, newshape)
 
     def backward(self, dy):
@@ -3556,6 +3565,8 @@ class Slice(Operation):
             self.steps = [1] * len(x_shape)  # steps = None
         for idx, axis in enumerate(self.axes):
             start, end, step = self.starts[idx], self.ends[idx], self.steps[idx]
+            if end > x_shape[axis]:
+                end = x_shape[axis]
             self.cache.append((axis, x_shape[axis], start, end, step))
             xs = []
             for step_idx in range(x_shape[axis])[start:end:step]:
@@ -3658,7 +3669,7 @@ def ceil(x):
 
 class Split(Operation):
 
-    def __init__(self, axis, parts):
+    def __init__(self, axis, parts, num_output=None):
         """
         Init a Split, Split a tensor into a list of tensors, along the specified 'axis'. 
         Args:
@@ -3667,10 +3678,15 @@ class Split(Operation):
         Args:
             parts: list of ints, length of each output, which can be specified using argument 'parts'. 
             Otherwise, the tensor is parts to equal sized parts.
+        Args:
+            num_output: once parts is none, the tensor is split to equal sized parts for each output.
         """
         super(Split, self).__init__()
         self.axis = axis
         self.parts = parts
+        self.num_output = num_output
+        if self.parts is None:
+            assert self.num_output is not None, "For (parts, num_output), it at least requires one."
 
     def forward(self, x):
         """
@@ -3680,6 +3696,10 @@ class Split(Operation):
         Returns:
             the output CTensor.
         """
+        x_shape = list(x.shape())
+        self.axis  = self.axis % len(x_shape)
+        if self.parts is None:
+            self.parts = [x_shape[self.axis]//self.num_output] * self.num_output
         xs = []
         _s = 0
         for _l in self.parts:
@@ -3700,7 +3720,7 @@ class Split(Operation):
         return dy
 
 
-def split(x, axis, parts):
+def split(x, axis, parts, num_output=None):
     """
     Init a Split, Split a tensor into a list of tensors, along the specified 'axis'. 
     Args:
@@ -3711,10 +3731,12 @@ def split(x, axis, parts):
     Args:
         parts: list of ints, length of each output, which can be specified using argument 'parts'. 
         Otherwise, the tensor is split to equal sized parts.
+    Args:
+        num_output: once parts is none, the tensor is split to equal sized parts for each output.
     Returns:
         the output CTensor.
     """
-    return Split(axis, parts)(x)
+    return Split(axis, parts, num_output)(x)
 
 
 class Gather(Operation):
@@ -3931,7 +3953,7 @@ class NonZero(Operation):
             the output CTensor.
         """
         y = tensor.to_numpy(tensor.from_raw_tensor(x))
-        y = np.array((np.nonzero(y)))
+        y = np.array((np.nonzero(y))).astype(np.int32)
         y = tensor.from_numpy(y)
         y.to_device(x.device())
         return y.data
@@ -3980,9 +4002,10 @@ class Cast(Operation):
             the output CTensor.
         """
         if x.data_type() != self.to:
-            x.AsType(self.to)
+            x = x.AsType(self.to)
         return x
 
+
     def backward(self, dy):
         """
         backward of Cast
@@ -4006,3 +4029,81 @@ def cast(x, to):
         the output CTensor.
     """
     return Cast(to)(x)[0]
+
+
+class OneHot(Operation):
+
+    def __init__(self, axis, depth, values):
+        """
+        Produces a one-hot tensor based on inputs. 
+        Args:
+            axis: Axis along which one-hot representation in added. Default: axis=-1. 
+            axis=-1 means that the additional dimension will be inserted as the innermost/last dimension in the output tensor.
+        Args:
+            values: Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], 
+            where 'on_value' is the value used for filling locations specified in 'indices' input tensor, 
+            and 'off_value' is the value used for filling locations other than those specified in 'indices' input tensor.
+        """
+        super(OneHot, self).__init__()
+        self.axis = axis
+        self.depth = depth
+        self.values = values
+
+    def forward(self, indices):
+        """
+        forward of OneHot
+        ! borrow from onnx
+        Args:
+            indices: Scalar specifying the number of classes in one-hot tensor. 
+            This is also the size of the one-hot dimension (specified by 'axis' attribute) added on in the output tensor. 
+            The values in the 'indices' input tensor are expected to be in the range [-depth, depth-1]. 
+            In case 'depth' is of non-integer type, it will be casted to int64 before use.
+        Returns:
+            the output CTensor.
+        """
+        values = tensor.to_numpy(tensor.from_raw_tensor(indices))
+        rank = len(values.shape)
+        depth_range = np.arange(self.depth)
+        if self.axis < 0:
+            self.axis += (rank + 1)
+        ls = values.shape[0:self.axis]
+        rs = values.shape[self.axis:rank]
+        targets = np.reshape(depth_range, (1,) * len(ls) + depth_range.shape + (1,) * len(rs))
+        values = np.reshape(np.mod(values, self.depth), ls + (1,) + rs)
+        np_tensor = np.asarray(targets == values, dtype=np.float32)
+        np_tensor = np_tensor * (self.values[1] - self.values[0]) + self.values[0]
+        tmp_tensor = tensor.from_numpy(np_tensor)
+        tmp_tensor.to_device(indices.device())
+        return tmp_tensor.data
+
+
+    def backward(self, dy):
+        """
+        backward of OneHot
+        Args:f
+            dy: CTensor, gradient tensor.
+        Returns:
+            the gradient tensor over input tensor.
+        """
+        assert False, ('no gradient for backward function')
+
+
+def onehot(axis, indices, depth, values):
+    """
+    Produces a one-hot tensor based on inputs. 
+    Args:
+        axis: Axis along which one-hot representation in added. Default: axis=-1. 
+        axis=-1 means that the additional dimension will be inserted as the innermost/last dimension in the output tensor.
+    Args:
+        indices: Scalar specifying the number of classes in one-hot tensor. 
+        This is also the size of the one-hot dimension (specified by 'axis' attribute) added on in the output tensor. 
+        The values in the 'indices' input tensor are expected to be in the range [-depth, depth-1]. 
+        In case 'depth' is of non-integer type, it will be casted to int64 before use.
+    Args:
+        values: Rank 1 tensor containing exactly two elements, in the format [off_value, on_value], 
+        where 'on_value' is the value used for filling locations specified in 'indices' input tensor, 
+        and 'off_value' is the value used for filling locations other than those specified in 'indices' input tensor.
+    Returns:
+        the output CTensor.
+    """
+    return OneHot(axis, depth, values)(indices)[0]
diff --git a/python/singa/sonnx.py b/python/singa/sonnx.py
index 86dbec7..fa87485 100755
--- a/python/singa/sonnx.py
+++ b/python/singa/sonnx.py
@@ -30,111 +30,11 @@ import warnings
 from . import singa_wrap as singa
 from . import autograd
 from . import tensor
+from singa import utils
 
 import collections
-deque = collections.deque
-
-
-def postorderRecursive(root, root_t):
-    """
-    return a list by the topological ordering (postorder of Depth-first search)
-    Args:
-        root: singa operator
-    Args:
-        root_t: tensor
-    Returns: 
-        deque[int]
-    """
-
-    def recursive(root, yid, root_t, res):
-        if root:
-            for srcop, yid, y, _ in root.src:
-                recursive(srcop, yid, y, res)
-            res.append((root, yid, root_t))
-
-    res = deque([])
-    recursive(root, None, root_t, res)
-    return res
-
-
-def force_unicode(s):
-    """
-    return string of a bytes
-    ! borrow from onnx
-    Args:
-        s: string or bytes
-    Returns: 
-        string
-    """
-    try:
-        return s.decode('utf-8')
-    except AttributeError:
-        return s
-
-
-def get_pad_shape(auto_pad, input_spatial_shape, kernel_spatial_shape,
-                  strides_spatial, output_spatial_shape):
-    """
-    return padding shape of conv2d or pooling,
-    ! borrow from onnx
-    Args:
-        auto_pad: string
-    Args:
-        input_spatial_shape: list[int]
-    Args:
-        kernel_spatial_shape: list[int]
-    Args:
-        strides_spatial: list[int]
-    Args:
-        output_spatial_shape: list[int]
-    Returns: 
-        list[int]
-    """
-    pad_shape = [0] * len(input_spatial_shape)
-    if auto_pad in ('SAME_UPPER', 'SAME_LOWER'):
-        for i in range(len(input_spatial_shape)):
-            pad_shape[i] = (output_spatial_shape[i] - 1) * strides_spatial[i] + \
-                kernel_spatial_shape[i] - input_spatial_shape[i]
-            if (pad_shape[i] % 2) == 0:
-                pad_shape[i] = pad_shape[i] // 2
-    elif auto_pad == 'VALID':
-        pass
-    if pad_shape[0] != pad_shape[1]:
-        # once the padding is odd, it means we must add extra padding at one end of the input
-        raise ValueError("Not implemented two directional padding")
-    return pad_shape
-
-
-def get_output_shape(auto_pad, input_spatial_shape, kernel_spatial_shape,
-                     strides_spatial):
-    """
-    return output shape of conv2d or pooling,
-    ! borrow from onnx
-    Args:
-        auto_pad: string
-    Args:
-        input_spatial_shape: list[int]
-    Args:
-        kernel_spatial_shape: list[int]
-    Args:
-        strides_spatial: list[int]
-    Returns: 
-        list[int]
-    """
-    out_shape = [0] * len(input_spatial_shape)
-    if auto_pad in ('SAME_UPPER', 'SAME_LOWER'):
-        for i in range(len(input_spatial_shape)):
-            out_shape[i] = int(
-                np.ceil(
-                    float(input_spatial_shape[i]) / float(strides_spatial[i])))
-    elif auto_pad == 'VALID':
-        for i in range(len(input_spatial_shape)):
-            out_shape[i] = int(
-                np.ceil(
-                    float(input_spatial_shape[i] -
-                          (kernel_spatial_shape[i] - 1)) /
-                    float(strides_spatial[i])))
-    return out_shape
+OrderedDict = collections.OrderedDict
+namedtuple = collections.namedtuple
 
 
 class SingaFrontend(object):
@@ -151,7 +51,6 @@ class SingaFrontend(object):
     _rename_operators = {
         '_Conv2d': 'Conv',
         'ReLU': 'Relu',
-        'Dummy': 'Constant',
         'MaxPool2d': 'MaxPool',
         'AvgPool2d': 'AveragePool',
         'SoftMax': 'Softmax',
@@ -179,7 +78,7 @@ class SingaFrontend(object):
         'atanh': 'Atanh',
         'SeLU': 'Selu',
         'Elu': 'Elu',
-        'Equal': 'Equal',
+        'Equal': 'equal',
         'Less': 'Less',
         'Sign': 'Sign',
         'Div': 'Div',
@@ -206,7 +105,22 @@ class SingaFrontend(object):
         'Not': 'Not',
         'Negative': 'Neg',
         'Reciprocal': 'Reciprocal',
-        'GlobalAveragePool' : 'GlobalAveragePool'
+        'ConstantOfShape': 'ConstantOfShape',
+        'Dropout': 'Dropout',
+        'ReduceSum': 'ReduceSum',
+        'ReduceMean': 'ReduceMean',
+        'LeakyRelu': 'LeakyRelu',
+        'GlobalAveragePool': 'GlobalAveragePool',
+        'Squeeze': 'Squeeze',
+        'Unsqueeze': 'Unsqueeze',
+        'Slice': 'Slice',
+        'Ceil': 'Ceil',
+        'Split': 'Split',
+        'Gather': 'Gather',
+        'Tile': 'Tile',
+        'NonZero': 'NonZero',
+        'Cast': 'Cast',
+        'OneHot': 'OneHot',
     }
 
     # this dict indicates the operators that need extra handle
@@ -214,7 +128,6 @@ class SingaFrontend(object):
     _special_operators = {
         '_Conv2d': '_create_conv_pool',
         '_Pooling2d': '_create_conv_pool',
-        'Dummy': '_create_dummy',
         '_BatchNorm2d': '_create_batchnorm',
         'Concat': '_create_concat',
         'Flatten': '_create_flatten',
@@ -226,6 +139,18 @@ class SingaFrontend(object):
         'HardSigmoid': '_create_hardsigmoid',
         'Clip': '_create_clip',
         'Transpose': '_create_transpose',
+        'ConstantOfShape': '_create_constantOfShape',
+        'Dropout': '_create_dropout',
+        'ReduceSum': '_create_reduceOp',
+        'ReduceMean': '_create_reduceOp',
+        'Squeeze': '_create_squeeze',
+        'Unsqueeze': '_create_squeeze',
+        'Slice': '_create_slice',
+        'Split': '_create_split',
+        'Gather': '_create_gather',
+        'Tile': '_create_tile',
+        'Cast': '_create_cast',
+        'OneHot': '_create_onehot',
     }
 
     # operators with bool output
@@ -238,12 +163,209 @@ class SingaFrontend(object):
         'Or': TensorProto.BOOL,
         'Xor': TensorProto.BOOL,
         'Shape': TensorProto.INT64,
+        'NonZero': TensorProto.INT64,
     }
 
     # some ops(such as batchnorm) has inputs we cannot handle directly,
     # so we record these items firstly so that we can handle then
     # at other place.
-    _unhandled_operators = {}
+    _unhandled_operators = {
+        "_BatchNorm2d": "_special_handle_batchnorm",
+        "Reshape": "_special_handle_reshape",
+        "Clip": "_special_handle_clip",
+        "Slice": "_special_handle_slice",
+        "Gather": "_special_handle_gather",
+        "Tile": "_special_handle_tile",
+        "OneHot": "_special_handle_onehot",
+    }
+
+    @classmethod
+    def _create_onehot(cls, op, op_t):
+        """
+        get a onnx node from singa onthot
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+        # axis, indices, depth, values
+        node.attribute.extend([
+            helper.make_attribute('axis', op.axis),
+        ])
+        for attr in ['depth', 'values']:
+            node.input.append(op.name + ":" + attr)
+        return node
+
+    @classmethod
+    def _create_cast(cls, op, op_t):
+        """
+        get a onnx node from singa cast
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        map_dict = {
+            tensor.float32: TensorProto.FLOAT,  # FLOAT to float32
+            tensor.int32: TensorProto.INT32,  # INT32 to int32
+        }
+        node.attribute.extend([
+            helper.make_attribute('to', map_dict[op.to]),
+        ])
+        return node
+
+    @classmethod
+    def _create_tile(cls, op, op_t):
+        """
+        get a onnx node from singa tile
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.input.append(op.name + ":repeats")
+        return node
+
+    @classmethod
+    def _create_gather(cls, op, op_t):
+        """
+        get a onnx node from singa gather
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.attribute.extend([
+            helper.make_attribute('axis', op.axis),
+        ])
+        node.input.append(op.name + ":indices")
+        return node
+
+    @classmethod
+    def _create_split(cls, op, op_t):
+        """
+        get a onnx node from singa split
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.attribute.extend([
+            helper.make_attribute('axis', op.axis),
+            helper.make_attribute('split', op.parts),
+        ])
+        return node
+
+    @classmethod
+    def _create_slice(cls, op, op_t):
+        """
+        get a onnx node from singa slice
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+        for attr in ['starts', 'ends', 'axes', 'steps']:
+            node.input.append(op.name + ":" + attr)
+        return node
+
+    @classmethod
+    def _create_squeeze(cls, op, op_t):
+        """
+        get a onnx node from singa squeeze and unsqueeze
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.attribute.extend([
+            helper.make_attribute('axes', list(op.axis)),
+        ])
+        return node
+
+    @classmethod
+    def _create_reduceOp(cls, op, op_t):
+        """
+        get a onnx node from singa ReduceSum, ReduceMean, ReduceMax, ReduceMin, etc.
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.attribute.extend([
+            helper.make_attribute('axes', list(op.axes)),
+            helper.make_attribute('keepdims', op.keepdims),
+        ])
+        return node
+
+    @classmethod
+    def _create_dropout(cls, op, op_t):
+        """
+        get a onnx node from singa Dropout operator
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+
+        node.attribute.extend([
+            helper.make_attribute('ratio', op.ratio),
+        ])
+        return node
+
+    @classmethod
+    def _create_constantOfShape(cls, op, op_t):
+        """
+        get a onnx node from singa ConstantOfShape operator
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+        tensor_type = onnx.TensorProto.FLOAT if isinstance(
+            op.value, float) else onnx.TensorProto.INT32
+        tensor_value = onnx.helper.make_tensor("value", tensor_type, [1],
+                                               [op.value])
+        node.attribute.extend([
+            helper.make_attribute('value', tensor_value),
+        ])
+        return node
 
     @classmethod
     def _create_transpose(cls, op, op_t):
@@ -274,35 +396,16 @@ class SingaFrontend(object):
         Returns: 
             the onnx node
         """
-
-        nodes = []
-        clip_node = cls._common_singa_tensor_to_onnx_node(op, op_t)
-
-        # firstly we add the max and min
-        for tmp_name in ['min', 'max']:
-            node_name = op.name + ":" + tmp_name
-            # moidfy the input of clip
-            clip_node.input.append(node_name)
-
-            # node = NodeProto()
-            # node.name = node_name
-            # node.op_type = cls._rename_operators.get("Dummy", "Dummy")
-            # node.output.extend([node_name])
-
-            # node.attribute.extend([helper.make_attribute(
-            #     'value', helper.make_tensor(
-            #         name=node_name,
-            #         data_type=TensorProto.FLOAT,
-            #         dims=[1],
-            #         vals=[getattr(op,tmp_name)],
-            #     )
-            # )])
-            # nodes.append(node)
-
-        # then we add the clip op itself
-        nodes.append(clip_node)
-
-        return nodes
+        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
+        if op.min is not None:
+            node.input.append(op.name + ":min")
+        else:
+            node.input.append("")
+        if op.max is not None:
+            node.input.append(op.name + ":max")
+        else:
+            node.input.append("")
+        return node
 
     @classmethod
     def _create_hardsigmoid(cls, op, op_t):
@@ -497,11 +600,12 @@ class SingaFrontend(object):
 
         k = [op.handle.kernel_h, op.handle.kernel_w]
         s = [op.handle.stride_h, op.handle.stride_w]
+        oddp = op.odd_padding
         p = [
-            op.handle.pad_h,
-            op.handle.pad_w,
-            op.handle.pad_w,
-            op.handle.pad_h,
+            op.handle.pad_h + oddp[0],
+            op.handle.pad_w + oddp[1],
+            op.handle.pad_w + oddp[2],
+            op.handle.pad_h + oddp[3],
         ]
 
         node.attribute.extend([
@@ -513,6 +617,7 @@ class SingaFrontend(object):
             node.op_type = cls._rename_operators.get('_Conv2d')
             node.attribute.extend([
                 helper.make_attribute('group', op.handle.group),
+                helper.make_attribute('auto_pad', 'NOTSET'),
             ])
 
         elif op.handle.is_max_pooling:
@@ -522,334 +627,769 @@ class SingaFrontend(object):
         return node
 
     @classmethod
-    def _create_dummy(cls, op, op_t):
+    def _get_singa_op_inputs_outputs(cls, op):
         """
-        get a onnx node from singa dummy (constant)
+        get inputs and outputs from a given operator
         Args:
             op: a given operator
-        Args:
-            op_t: the tensor of the operator
         Returns: 
-            the onnx node
+            inputs and outputs of the op
         """
-        node = cls._common_singa_tensor_to_onnx_node(op, op_t)
-        node.attribute.extend([
-            helper.make_attribute(
-                'value',
-                helper.make_tensor(
-                    name=op.name,
-                    data_type=TensorProto.FLOAT,
-                    dims=op_t.shape,
-                    vals=tensor.to_numpy(op_t).flatten().astype(float),
-                ))
-        ])
-        del node.input[:]
-        return node
+        outputs = [op.output_name(idx) for _, idx in op.y_id2idx.items()]
+        inputs = [
+            srcop.output_name(srcop.y_id2idx[yid])
+            for (srcop, yid, _, _) in op.src
+        ]
+        return inputs, outputs
 
     @classmethod
-    def _common_singa_tensor_to_onnx_node(cls, op, op_t):
+    def _get_singa_op_type(cls, op):
         """
-        get a onnx node from a singa operator, prepare its type, inputs and outputs
+        get the operator type from a given operator
         Args:
             op: a given operator
-        Args:
-            op: the tensor of the operator
-        Returns: the onnx node
+        Returns: 
+            operator type
         """
-        node_def = NodeProto()
-        node_def.name = op.name
-
-        optype = cls._get_singa_op_type(op)
-        node_def.op_type = cls._rename_operators.get(optype, optype)
-
-        inputs, outputs = cls._get_singa_op_inputs_outputs(op)
-        node_def.input.extend(inputs)
-        node_def.output.extend(outputs)
+        return type(op).__name__
 
-        return node_def
+    @classmethod
+    def _special_handle_batchnorm(cls, op, X, W):
+        """
+        hanlde the special operators
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            onnx tensor list
+        """
+        # for singa, x, scale, bias is input
+        # and mean and var is attribute
+        # so we add the mean and var to W
+        tensor_list = []
+        append_inputs = {"mean": op.running_mean, "var": op.running_var}
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            append_input = tensor.to_numpy(tensor.from_raw_tensor(append_input))
+            tensor_list.append(numpy_helper.from_array(append_input, node_name))
+        return tensor_list
 
     @classmethod
-    def singa_op_to_onnx_node(cls, op, op_t):
+    def _special_handle_reshape(cls, op, X, W):
         """
-        get a onnx node from singa operator
+        hanlde the special operators
         Args:
             op: a given operator
         Args:
             op_t: the tensor of the operator
         Returns: 
-            the onnx node
+            onnx tensor list
         """
-        optype = cls._get_singa_op_type(op)
-        # wether the operator needs special handler
-        if optype in cls._special_operators:
-            translator = getattr(cls, cls._special_operators[optype])
-        else:
-            translator = cls._common_singa_tensor_to_onnx_node
-        nodes = translator(op, op_t)
-        if not isinstance(nodes, collections.Iterable):
-            nodes = [nodes]
-        nodes = [node for node in nodes if node is not None]
-        return nodes
+        node_name = op.name + ":shape"
+        return [
+            numpy_helper.from_array(np.array(op.shape, dtype=np.int64),
+                                    node_name)
+        ]
 
     @classmethod
-    def singa_to_onnx_graph(cls, inputs, y, model_name="sonnx"):
+    def _special_handle_clip(cls, op, X, W):
         """
-        get onnx model from singa computational graph
+        hanlde the special operators
         Args:
-            inputs: a list of input tensors (each is initialized with a name)
+            op: a given operator
         Args:
-            y: a list of tensors, usually the outputs of the graph
+            op_t: the tensor of the operator
         Returns: 
-            the onnx model
+            onnx tensor list
         """
-        assert len(y) == 1  # assume there is only one output
-        y = y[0]
+        tensor_list = []
+        # clip add min and max
+        append_inputs = {"min": op.min, "max": op.max}
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            tensor_list.append(
+                helper.make_tensor(node_name, TensorProto.FLOAT, [],
+                                   [append_input]))
+        return tensor_list
 
-        graph_def = GraphProto()
-        graph_def.name = model_name
-        topol = postorderRecursive(y.creator, y)
-        # since tensor's name might change
-        # we record its id
-        input_tensors = {id(x): x for x in inputs}
-        # print(input_tensors)
-        X = []
-        optype = cls._get_singa_op_type(y.creator)
-        y_dtype = TensorProto.FLOAT
-        if optype in cls._bool_operators:
-            y_dtype = cls._bool_operators[optype]
-        Y = [helper.make_tensor_value_info(y.name, y_dtype, y.shape)]
-        for op, yid, op_t in topol:
-            optype = cls._get_singa_op_type(op)
-            # print(op.name, cls._get_singa_op_type(op), op_t, optype, yid)
-            if yid in input_tensors and optype == 'Dummy':
-                # find the input by its id
-                op_t = input_tensors[yid]
-                dtype = TensorProto.FLOAT
-                if op_t.dtype == tensor.int32:
-                    dtype = TensorProto.INT32
-                X.append(
-                    helper.make_tensor_value_info(op.name, dtype, op_t.shape))
-            # because the inputs of batchnorm and reshape are differnet with onnx
-            # we need to add these inputs into onnx model mannully
-            elif yid in input_tensors and optype == '_BatchNorm2d':
-                # batchnorm add scale, bias, mean, var as inputs
-                running_values = {
-                    "mean": op.running_mean,
-                    "var": op.running_var
-                }
-                for tmp_name, running_value in running_values.items():
-                    node_name = op.name + ":" + tmp_name
-                    tmp_device = running_value.device()
-                    running_value.ToHost()
-                    np_running_value = running_value.GetFloatValue(
-                        int(running_value.Size()))
-                    running_value.ToDevice(tmp_device)
-                    X.append(
-                        helper.make_tensor_value_info(node_name,
-                                                      TensorProto.FLOAT,
-                                                      np_running_value.shape))
-                graph_def.node.extend(cls.singa_op_to_onnx_node(op, op_t))
-            elif yid in input_tensors and optype == 'Reshape':
-                # reshape add shape
-                node_name = op.name + ":shape"
-                X.append(
-                    helper.make_tensor_value_info(node_name, TensorProto.FLOAT,
-                                                  [len(op.shape)]))
-                graph_def.node.extend(cls.singa_op_to_onnx_node(op, op_t))
-            elif yid in input_tensors and optype == 'Clip':
-                # Clip add min and max
-                node_name = op.name + ":min"
-                X.append(
-                    helper.make_tensor_value_info(node_name, TensorProto.FLOAT,
-                                                  [1]))
-                node_name = op.name + ":max"
-                X.append(
-                    helper.make_tensor_value_info(node_name, TensorProto.FLOAT,
-                                                  [1]))
-                graph_def.node.extend(cls.singa_op_to_onnx_node(op, op_t))
-            else:
-                graph_def.node.extend(cls.singa_op_to_onnx_node(op, op_t))
+    @classmethod
+    def _special_handle_slice(cls, op, X, W):
+        """
+        hanlde the special operators
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            onnx tensor list
+        """
+        tensor_list = []
+        # slice add starts, ends, axes, steps
+        append_inputs = {
+            "starts": op.starts,
+            "ends": op.ends,
+            "axes": op.axes,
+            "steps": op.steps,
+        }
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            tensor_list.append(
+                numpy_helper.from_array(np.array(append_input), node_name))
+        return tensor_list
 
-        graph_def.input.extend(X)
-        graph_def.output.extend(Y)
+    @classmethod
+    def _special_handle_gather(cls, op, X, W):
+        """
+        hanlde the special operators
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            onnx tensor list
+        """
+        tensor_list = []
+        append_inputs = {
+            "indices": op.indices,
+        }
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            tensor_list.append(
+                numpy_helper.from_array(np.array(append_input), node_name))
+        return tensor_list
+
+    @classmethod
+    def _special_handle_tile(cls, op, X, W):
+        """
+        hanlde the special operators
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            onnx tensor list
+        """
+        tensor_list = []
+        append_inputs = {
+            "repeats": op.repeats,
+        }
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            tensor_list.append(
+                numpy_helper.from_array(np.array(append_input), node_name))
+        return tensor_list
+
+    @classmethod
+    def _special_handle_onehot(cls, op, X, W):
+        """
+        hanlde the special operators
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            onnx tensor list
+        """
+        tensor_list = []
+        append_inputs = {
+            "depth": op.depth,
+            "values": op.values,
+        }
+        for tmp_name, append_input in append_inputs.items():
+            node_name = op.name + ":" + tmp_name
+            tensor_list.append(
+                numpy_helper.from_array(np.array(append_input), node_name))
+        return tensor_list
+
+    @classmethod
+    def handle_special_ops(cls, op, X, W):
+        """
+        hanlde the special operators, 
+        because the inputs of batchnorm and reshape are differnet with onnx
+        we need to add these inputs into onnx model mannully
+        Args:
+            op: a given operator
+        Args:
+            X: onnx input list
+        Args:
+            X: onnx weight list
+        Returns: the onnx node
+        """
+        optype = cls._get_singa_op_type(op)
+        translator = getattr(cls, cls._unhandled_operators[optype])
+        tensor_list = translator(op, X, W)
+        for tensor in tensor_list:
+            X.append(
+                helper.make_tensor_value_info(tensor.name, tensor.data_type,
+                                              tensor.dims))
+            W.append(tensor)
+        # return X, W
+
+    @classmethod
+    def _common_singa_tensor_to_onnx_node(cls, op, op_t):
+        """
+        get a onnx node from a singa operator, prepare its type, inputs and outputs
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: the onnx node
+        """
+        node_def = NodeProto()
+        node_def.name = op.name
+
+        optype = cls._get_singa_op_type(op)
+        node_def.op_type = cls._rename_operators.get(optype, optype)
+
+        inputs, outputs = cls._get_singa_op_inputs_outputs(op)
+        node_def.input.extend(inputs)
+        node_def.output.extend(outputs)
+
+        return node_def
+
+    @classmethod
+    def singa_op_to_onnx_node(cls, op, op_t):
+        """
+        get a onnx node from singa operator
+        Args:
+            op: a given operator
+        Args:
+            op_t: the tensor of the operator
+        Returns: 
+            the onnx node
+        """
+        optype = cls._get_singa_op_type(op)
+        # wether the operator needs special handler
+        if optype in cls._special_operators:
+            translator = getattr(cls, cls._special_operators[optype])
+        else:
+            translator = cls._common_singa_tensor_to_onnx_node
+        nodes = translator(op, op_t)
+        if not isinstance(nodes, collections.Iterable):
+            nodes = [nodes]
+        nodes = [node for node in nodes if node is not None]
+        return nodes
+
+    @classmethod
+    def singa_to_onnx_graph(cls, inputs, y, model_name="sonnx"):
+        """
+        get onnx model from singa computational graph
+        Args:
+            inputs: a list of input tensors (each is initialized with a name)
+        Args:
+            y: a list of tensors, usually the outputs of the graph
+        Returns: 
+            the onnx model
+        """
+        assert len(
+            y
+        ) == 1, "Not support multiple output now."  # assume there is only one output
+        y = y[0]
+
+        graph_def = GraphProto()
+        graph_def.name = model_name
+        topol, ws, ins = utils.post_order_recursive(y.creator, y)
+
+        # prepare the input
+        X = []
+        for op_name, op_t in ins.items():
+            op_t = inputs.pop(0)
+            dtype = TensorProto.INT32 if op_t.dtype == tensor.int32 else TensorProto.FLOAT
+            X.append(helper.make_tensor_value_info(op_name, dtype, op_t.shape))
+
+        # prepare the output
+        y_optype = cls._get_singa_op_type(y.creator)
+        if y_optype in cls._bool_operators:
+            y_dtype = cls._bool_operators[y_optype]
+        elif y.dtype == tensor.int32:
+            y_dtype = TensorProto.INT32
+        else:
+            y_dtype = TensorProto.FLOAT
+        Y = [helper.make_tensor_value_info(y.name, y_dtype, y.shape)]
+
+        # prepare the weight
+        W = []
+        for op_name, op_t in ws.items():
+            dtype = TensorProto.INT32 if op_t.dtype == tensor.int32 else TensorProto.FLOAT
+            wt = tensor.to_numpy(op_t)
+            wt = numpy_helper.from_array(wt)
+            wt.name = op_name
+            W.append(wt)
+            X.append(helper.make_tensor_value_info(op_name, dtype, op_t.shape))
+
+        # iterate the node graph
+        for op_name, op in topol.items():
+            optype = cls._get_singa_op_type(op)
+            if optype in cls._unhandled_operators:
+                cls.handle_special_ops(op, X, W)
+            graph_def.node.extend(cls.singa_op_to_onnx_node(op, op_t))
+
+        graph_def.input.extend(X)
+        graph_def.output.extend(Y)
+        graph_def.initializer.extend(W)
         return graph_def
 
     @classmethod
-    def singa_to_onnx_model(cls, inputs, y, model_name="sonnx"):
+    def singa_to_onnx_model(cls, inputs, y, model_name="sonnx"):
+        """
+        get onnx model from singa computational graph
+        Args:
+            inputs: a list of input tensors (each is initialized with a name)
+        Args:
+            y: a list of tensors, usually the outputs of the graph
+        Returns: 
+            the onnx model
+        """
+        opset_id = OperatorSetIdProto()
+        opset_id.version = cls._target_opset_version
+        model = helper.make_model(cls.singa_to_onnx_graph(inputs,
+                                                          y,
+                                                          model_name="sonnx"),
+                                  producer_name='sonnx',
+                                  opset_imports=[opset_id])
+        model = optimizer.optimize(model)
+        checker.check_model(model)
+        return model
+
+
+class OnnxNode(object):
+    """
+    Reimplementation of NodeProto from ONNX, but in a form
+    more convenient to work with from Python.
+    """
+
+    def __init__(self, node):
+        self.name = str(node.name)
+        self.op_type = str(node.op_type)
+        self.attrs = OnnxAttributes.from_onnx(node.attribute)
+        # there may some inputs which we regard as attribute, so we mark them there
+        self.consumed_inputs = list()
+        self.inputs = list(node.input)
+        self.outputs = list(node.output)
+
+    def getattr(self, key, default=None):
+        return self.attrs[key] if key in self.attrs else default
+
+
+class OnnxAttributes(dict):
+    """
+    This is a more convenient way to work with ONNX attributes
+    that is not the protobuf representation.
+    """
+
+    @staticmethod
+    def from_onnx(args):
+        d = OnnxAttributes()
+        for arg in args:
+            d[arg.name] = helper.get_attribute_value(arg)
+        return d
+
+
+class SingaBackend(Backend):
+
+    # This number indicates the onnx operator set version
+    _known_opset_version = 11
+
+    # beceuase singa's operators are different from onnx.
+    # we define a dict for the name projection
+    _rename_operators = {
+        'Relu': 'relu',
+        'Softmax': 'SoftMax',
+        'Sigmoid': 'sigmoid',
+        'Add': 'add',
+        'MatMul': 'matmul',
+        'Conv': '_Conv2d',
+        'MaxPool': '_Pooling2d',
+        'AveragePool': '_Pooling2d',
+        'BatchNormalization': 'batchnorm_2d',
+        'Concat': 'Concat',
+        'Flatten': 'Flatten',
+        'Gemm': 'Gemm',
+        'Reshape': 'Reshape',
+        'Sum': 'sum',
+        'Cos': 'cos',
+        'Cosh': 'cosh',
+        'Sin': 'sin',
+        'Sinh': 'sinh',
+        'Tan': 'tan',
+        'Tanh': 'tanh',
+        'Acos': 'acos',
+        'Acosh': 'acosh',
+        'Asin': 'asin',
+        'Asinh': 'asinh',
+        'Atan': 'atan',
+        'Atanh': 'atanh',
+        'Selu': 'SeLU',
+        'Elu': 'Elu',
+        'Equal': 'equal',
+        'Less': 'less',
+        'Sign': 'sign',
+        'Div': 'div',
+        'Sub': 'sub',
+        'Sqrt': 'sqrt',
+        'Log': 'log',
+        'Greater': 'greater',
+        'HardSigmoid': 'HardSigmoid',
+        'Identity': 'identity',
+        'Softplus': 'softplus',
+        'Softsign': 'softsign',
+        'Mean': 'mean',
+        'Pow': 'pow',
+        'Clip': 'Clip',
+        'PRelu': 'prelu',
+        'Mul': 'mul',
+        'Transpose': 'Transpose',
+        'Max': 'max',
+        'Min': 'min',
+        'Shape': 'shape',
+        'And': '_and',
+        'Or': '_or',
+        'Xor': '_xor',
+        'Not': '_not',
+        'Neg': 'negative',
+        'Reciprocal': 'reciprocal',
+        'ConstantOfShape': 'ConstantOfShape',
+        'Dropout': 'Dropout',
+        'ReduceSum': 'ReduceSum',
+        'ReduceMean': 'ReduceMean',
+        'LeakyRelu': 'LeakyRelu',
+        'GlobalAveragePool': 'GlobalAveragePool',
+        'Squeeze': 'Squeeze',
+        'Unsqueeze': 'Unsqueeze',
+        'Slice': 'Slice',
+        'Ceil': 'Ceil',
+        'Split': 'Split',
+        'Gather': 'Gather',
+        'Tile': 'Tile',
+        'NonZero': 'nonzero',
+        'Cast': 'Cast',
+        'OneHot': 'OneHot',
+    }
+
+    # this dict indicates the operators that need extra handle
+    # each indicates a function name
+    _special_operators = {
+        'Conv': '_create_conv',
+        'MaxPool': '_create_max_avg_pool',
+        'AveragePool': '_create_max_avg_pool',
+        'BatchNormalization': '_create_batchnorm',
+        'Concat': '_create_concat',
+        'Flatten': '_create_flatten',
+        'Gemm': '_create_gemm',
+        'Reshape': '_create_reshape',
+        'Softmax': '_create_softmax',
+        'Selu': '_create_selu',
+        'Elu': '_create_elu',
+        'HardSigmoid': '_create_hardsigmoid',
+        'Clip': '_create_clip',
+        'Transpose': '_create_transpose',
+        'ConstantOfShape': '_create_constantOfShape',
+        'Dropout': '_create_dropout',
+        'ReduceSum': '_create_reduceOp',
+        'ReduceMean': '_create_reduceOp',
+        'LeakyRelu': '_create_leakyrelu',
+        'GlobalAveragePool': '_create_globalaveragepool',
+        'Squeeze': '_create_squeeze',
+        'Unsqueeze': '_create_squeeze',
+        'Slice': '_create_slice',
+        'Split': '_create_split',
+        'Gather': '_create_gather',
+        'Tile': '_create_tile',
+        'Cast': '_create_cast',
+        'OneHot': '_create_onehot',
+        'Constant': "_create_constant"
+    }
+
+    @classmethod
+    def _create_constant(cls, onnx_node, inputs, opset_version):
+        """
+        parse onnx constatn node to weights
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        tmp_tensor = onnx_node.getattr('value')
+        np_dtype = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[tmp_tensor.data_type]
+        np_tensor = np.frombuffer(tmp_tensor.raw_data, dtype=np_dtype)
+        if np_tensor.dtype == "int64":
+            np_tensor = np_tensor.astype(np.int32)
+        # todo, we cannot support scalar tensor
+        if np.ndim(np_tensor) == 0:
+            np_tensor = np.array(np_tensor, ndmin=1)
+        return None, np_tensor
+
+    @classmethod
+    def _create_onehot(cls, onnx_node, inputs, opset_version):
+        """
+        get the OneHot operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        axis = onnx_node.getattr("axis", -1)
+        # we move several inputs to singa's attribuates
+        # and mark them so we don't use them when we run this operator
+        depth = tensor.to_numpy(inputs.pop(1)).astype(np.int32)
+        value = tensor.to_numpy(inputs.pop(1))
+        onnx_node.consumed_inputs.extend(onnx_node.inputs[1:])
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(axis, depth, value)
+
+    @classmethod
+    def _create_cast(cls, onnx_node, inputs, opset_version):
+        """
+        get the Cast operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        to = onnx_node.getattr("to")
+        # singa only supports float32 and int32
+        map_dict = {
+            TensorProto.FLOAT: tensor.float32,  # FLOAT to float32
+            TensorProto.UINT8: None,  # UINT8
+            TensorProto.INT8: tensor.int32,  # INT8 to int32
+            TensorProto.UINT16: None,  # UINT16
+            TensorProto.INT16: tensor.int32,  # INT16 to int32
+            TensorProto.INT32: tensor.int32,  # INT32 to int32
+            TensorProto.INT64: tensor.int32,  # INT64 to int32
+            TensorProto.STRING: None,  # stirng
+            TensorProto.BOOL: None,  # bool
+        }
+        to = map_dict[to]
+        assert to != None, "not support cast type: {}".format(to)
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(to)
+
+    @classmethod
+    def _create_tile(cls, onnx_node, inputs, opset_version):
+        """
+        get the Tile operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        # we move several inputs to singa's attribuates
+        # and mark them so we don't use them when we run this operator
+        repeats = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        onnx_node.consumed_inputs.append(onnx_node.inputs[1])
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(repeats)
+
+    @classmethod
+    def _create_gather(cls, onnx_node, inputs, opset_version):
+        """
+        get the Gather operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        axis = onnx_node.getattr("axis", 0)
+        # we move several inputs to singa's attribuates
+        # and mark them so we don't use them when we run this operator
+        indices = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        onnx_node.consumed_inputs.append(onnx_node.inputs[1])
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(axis, indices)
+
+    @classmethod
+    def _create_split(cls, onnx_node, inputs, opset_version):
+        """
+        get the Split operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        axis = onnx_node.getattr("axis", 0)
+        split = onnx_node.getattr("split", None)
+        num_output = len(onnx_node.outputs)
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(axis, split, num_output)
+
+    @classmethod
+    def _create_slice(cls, onnx_node, inputs, opset_version):
+        """
+        get the Slice operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        # we move several inputs to singa's attribuates
+        # and mark them so we don't use them when we run this operator
+        starts = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        ends = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        # sometime onnx may ignore these two inputs, axes and step
+        if len(inputs) >= 2 and onnx_node.inputs[3] != '':
+            axes = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        else:
+            axes = None
+        steps = tensor.to_numpy(inputs.pop(1)).astype(
+            np.int32).tolist() if len(inputs) >= 2 else None
+        onnx_node.consumed_inputs.extend(onnx_node.inputs[1:])
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(starts, ends, axes, steps)
+
+    @classmethod
+    def _create_squeeze(cls, onnx_node, inputs, opset_version):
+        """
+        get the Squeeze and Unsqueeze operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        axes = onnx_node.getattr("axes")
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(axes)
+
+    @classmethod
+    def _create_globalaveragepool(cls, onnx_node, inputs, opset_version):
         """
-        get onnx model from singa computational graph
+        get the GlobalAveragePool operator from onnx node
         Args:
-            inputs: a list of input tensors (each is initialized with a name)
+            onnx_node: a given onnx node
         Args:
-            y: a list of tensors, usually the outputs of the graph
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
         Returns: 
-            the onnx model
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
         """
-        opset_id = OperatorSetIdProto()
-        opset_id.version = cls._target_opset_version
-        model = helper.make_model(cls.singa_to_onnx_graph(inputs,
-                                                          y,
-                                                          model_name="sonnx"),
-                                  producer_name='sonnx',
-                                  opset_imports=[opset_id])
-        # print('The model is:\n{}'.format(model))
-        model = optimizer.optimize(model)
-        checker.check_model(model)
-        return model
+        data_format = onnx_node.getattr("data_format", 'channels_first')
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(data_format)
 
     @classmethod
-    def _get_singa_op_inputs_outputs(cls, op):
+    def _create_leakyrelu(cls, onnx_node, inputs, opset_version):
         """
-        get inputs and outputs from a given operator
+        get the LeakyRelu operator from onnx node
         Args:
-            op: a given operator
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
         Returns: 
-            inputs and outputs of the op
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
         """
-        outputs = [op.output_name(idx) for yid, idx in op.y_id2idx.items()]
-        inputs = [
-            srcop.output_name(srcop.y_id2idx[yid])
-            for (srcop, yid, _, _) in op.src
-        ]
-        return inputs, outputs
+        alpha = onnx_node.getattr("alpha", 0.01)
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(alpha)
 
     @classmethod
-    def _get_singa_op_type(cls, op):
+    def _create_reduceOp(cls, onnx_node, inputs, opset_version):
         """
-        get the operator type from a given operator
+        get the ReduceSum, ReduceMean, ReduceMax, ReduceMin, etc, operator from onnx node
         Args:
-            op: a given operator
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
         Returns: 
-            operator type
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
         """
-        return type(op).__name__
-
-
-class OnnxNode(object):
-    """
-    Reimplementation of NodeProto from ONNX, but in a form
-    more convenient to work with from Python.
-    We may temporarily edit these nodes to get them into Caffe2 form,
-    before actually translating into the Caffe2 protobuf, since this
-    is easier than decomposing everything, and putting it back together
-    when we're ready.
-    """
-
-    def __init__(self, node):
-        self.name = str(node.name)
-        self.op_type = str(node.op_type)
-        self.attrs = OnnxAttributes.from_onnx(node.attribute)
-        self.inputs = list(node.input)
-        self.outputs = list(node.output)
-
-    def getattr(self, key, default=None):
-        return self.attrs[key] if key in self.attrs else default
-
-
-class OnnxAttributes(dict):
-    """
-    This is a more convenient way to work with ONNX/Caffe2 attributes
-    that is not the protobuf representation.
-    """
-
-    @staticmethod
-    def from_onnx(args):
-        d = OnnxAttributes()
-        for arg in args:
-            d[arg.name] = helper.get_attribute_value(arg)
-        return d
-
-
-class SingaBackend(Backend):
-
-    # This number indicates the onnx operator set version
-    _known_opset_version = 11
-
-    # beceuase singa's operators are different from onnx.
-    # we define a dict for the name projection
-    _rename_operators = {
-        'Relu': 'relu',
-        'Softmax': 'SoftMax',
-        'Sigmoid': 'sigmoid',
-        'Add': 'add',
-        'MatMul': 'Matmul',
-        'Conv': 'conv2d',
-        'MaxPool': 'pooling_2d',
-        'AveragePool': 'pooling_2d',
-        'BatchNormalization': 'batchnorm_2d',
-        'Concat': 'Concat',
-        'Flatten': 'Flatten',
-        'Gemm': 'Gemm',
-        'Reshape': 'reshape',
-        'Sum': 'sum',
-        'Cos': 'cos',
-        'Cosh': 'cosh',
-        'Sin': 'sin',
-        'Sinh': 'sinh',
-        'Tan': 'tan',
-        'Tanh': 'tanh',
-        'Acos': 'acos',
-        'Acosh': 'acosh',
-        'Asin': 'asin',
-        'Asinh': 'asinh',
-        'Atan': 'atan',
-        'Atanh': 'atanh',
-        'Selu': 'SeLU',
-        'Elu': 'Elu',
-        'Equal': 'equal',
-        'Less': 'less',
-        'Sign': 'sign',
-        'Div': 'div',
-        'Sub': 'sub',
-        'Sqrt': 'sqrt',
-        'Log': 'log',
-        'Greater': 'greater',
-        'HardSigmoid': 'HardSigmoid',
-        'Identity': 'identity',
-        'Softplus': 'softplus',
-        'Softsign': 'softsign',
-        'Mean': 'mean',
-        'Pow': 'pow',
-        'Clip': 'clip',
-        'PRelu': 'prelu',
-        'Mul': 'mul',
-        'Transpose': 'Transpose',
-        'Max': 'max',
-        'Min': 'min',
-        'Shape': 'shape',
-        'And': '_and',
-        'Or': '_or',
-        'Xor': '_xor',
-        'Not': '_not',
-        'Neg': 'negative',
-        'Reciprocal': 'reciprocal',
-        'GlobalAveragePool' : 'globalaveragepool'
-    }
+        axes = onnx_node.getattr("axes", None)
+        keepdims = onnx_node.getattr("keepdims", 1)
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(axes, keepdims)
 
-    # this dict indicates the operators that need extra handle
-    # each indicates a function name
-    _special_operators = {
-        'Conv': '_create_conv',
-        'MaxPool': '_create_max_avg_pool',
-        'AveragePool': '_create_max_avg_pool',
-        'BatchNormalization': '_create_batchnorm',
-        'Concat': '_create_concat',
-        'MatMul': '_create_matmul',
-        'Flatten': '_create_flatten',
-        'Gemm': '_create_gemm',
-        'Reshape': '_create_reshape',
-        'Softmax': '_create_softmax',
-        'Selu': '_create_selu',
-        'Elu': '_create_elu',
-        'HardSigmoid': '_create_hardsigmoid',
-        'Clip': '_create_clip',
-        'Transpose': '_create_transpose',
-    }
+    @classmethod
+    def _create_dropout(cls, onnx_node, inputs, opset_version):
+        """
+        get the Dropout operator from onnx node
+        Args:
+            onnx_node: a given onnx node
+        Args:
+            inputs: the input tensor
+        Args:
+            opset_version: the opset version
+        Returns: 
+            handle, the handle of singa operator
+        Returns: 
+            forward, the autograd of singa operator
+        """
+        ratio = onnx_node.getattr("ratio", 0)
+        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
+                                                       opset_version)
+        return _, forward(ratio)
 
     @classmethod
-    def _create_transpose(cls, onnx_node, inputs, opset_version):
+    def _create_constantOfShape(cls, onnx_node, inputs, opset_version):
         """
-        get the Transpose operator from onnx node
+        get the ConstantOfShape operator from onnx node
         Args:
             onnx_node: a given onnx node
         Args:
@@ -861,16 +1401,17 @@ class SingaBackend(Backend):
         Returns: 
             forward, the autograd of singa operator
         """
-        shape = inputs[0].shape
-        perm = onnx_node.getattr("perm", list(range(len(shape) - 1, -1, -1)))
+        value = onnx_node.getattr("value", 0)
+        if isinstance(value, onnx.TensorProto):
+            value = numpy_helper.to_array(value)[0].item()
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return _, forward(perm)
+        return _, forward(value)
 
     @classmethod
-    def _create_clip(cls, onnx_node, inputs, opset_version):
+    def _create_transpose(cls, onnx_node, inputs, opset_version):
         """
-        get the clip operator from onnx node
+        get the Transpose operator from onnx node
         Args:
             onnx_node: a given onnx node
         Args:
@@ -882,14 +1423,16 @@ class SingaBackend(Backend):
         Returns: 
             forward, the autograd of singa operator
         """
+        shape = inputs[0].shape
+        perm = onnx_node.getattr("perm", list(range(len(shape) - 1, -1, -1)))
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return _, forward
+        return _, forward(perm)
 
     @classmethod
-    def _create_hardsigmoid(cls, onnx_node, inputs, opset_version):
+    def _create_clip(cls, onnx_node, inputs, opset_version):
         """
-        get the HardSigmoid operator from onnx node
+        get the clip operator from onnx node
         Args:
             onnx_node: a given onnx node
         Args:
@@ -901,16 +1444,24 @@ class SingaBackend(Backend):
         Returns: 
             forward, the autograd of singa operator
         """
-        alpha = onnx_node.getattr("alpha", 0.2)
-        beta = onnx_node.getattr("beta", 0.5)
+        # sometime onnx may ignore these two inputs, min or max or both
+        if len(inputs) >= 2 and onnx_node.inputs[1] != '':
+            min_v = tensor.to_numpy(inputs.pop(1)).tolist()[0]
+        else:
+            min_v = None
+        if len(inputs) >= 2 and onnx_node.inputs[2] != '':
+            max_v = tensor.to_numpy(inputs.pop(1)).tolist()[0]
+        else:
+            max_v = None
+        onnx_node.consumed_inputs.extend(onnx_node.inputs[1:])
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return _, forward(alpha, beta)
+        return _, forward(min_v, max_v)
 
     @classmethod
-    def _create_equal(cls, onnx_node, inputs, opset_version):
+    def _create_hardsigmoid(cls, onnx_node, inputs, opset_version):
         """
-        get the equal operator from onnx node
+        get the HardSigmoid operator from onnx node
         Args:
             onnx_node: a given onnx node
         Args:
@@ -922,9 +1473,11 @@ class SingaBackend(Backend):
         Returns: 
             forward, the autograd of singa operator
         """
+        alpha = onnx_node.getattr("alpha", 0.2)
+        beta = onnx_node.getattr("beta", 0.5)
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return _, forward()
+        return _, forward(alpha, beta)
 
     @classmethod
     def _create_elu(cls, onnx_node, inputs, opset_version):
@@ -982,9 +1535,11 @@ class SingaBackend(Backend):
         Returns: 
             the autograd of singa operator
         """
+        shape = tensor.to_numpy(inputs.pop(1)).astype(np.int32).tolist()
+        onnx_node.consumed_inputs.append(onnx_node.inputs[1])
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return _, forward
+        return _, forward(shape)
 
     @classmethod
     def _create_conv(cls, onnx_node, inputs, opset_version):
@@ -1002,24 +1557,27 @@ class SingaBackend(Backend):
             forward, the autograd of singa operator
         """
         kernel = tuple(onnx_node.attrs["kernel_shape"])
-        # todo: we only support the padding with tuple
+        padding = tuple(
+            onnx_node.attrs["pads"]) if "pads" in onnx_node.attrs else (0, 0)
         stride = tuple(onnx_node.getattr('strides', (1, 1)))
-        padding = tuple(onnx_node.attrs["pads"][0:2]) if "pads" in onnx_node.attrs else (0, 0)
+        # default the odd_padding is 0, once there are same pad mode, we modify it
+        # for odd_padding, please refer the autegrade.py
+        odd_padding = (0, 0, 0, 0)
         if "auto_pad" in onnx_node.attrs:
-            auto_pad = force_unicode(onnx_node.attrs['auto_pad'])
-            out_shape = get_output_shape(auto_pad, inputs[0].shape[2:], kernel, stride)
-            padding = get_pad_shape(auto_pad, inputs[0].shape[2:], kernel, stride, out_shape)
-        dilation = onnx_node.getattr('dilations', 1)
-        group = onnx_node.getattr('group', 1)
+            auto_pad = utils.force_unicode(onnx_node.attrs['auto_pad'])
+            if auto_pad in ('SAME_UPPER', 'SAME_LOWER'):
+                padding, odd_padding = utils.get_padding_shape(
+                    auto_pad, inputs[0].shape[2:], kernel, stride)
 
         # not support dilation
-
+        dilation = onnx_node.getattr('dilations', 1)
         if dilation != 1 and list(dilation) != [1, 1]:
             raise ValueError("Not implemented yet for dilation")
+        group = onnx_node.getattr('group', 1)
 
-        # only support 2d
-        if len(kernel) != 2:
-            raise ValueError("Not implemented yet for 2d")
+        # only support 1d or 2d
+        if len(kernel) > 2:
+            raise ValueError("Only implemented for 1d or 2d")
 
         bias = len(inputs) == 3
         x = inputs[0]
@@ -1043,7 +1601,7 @@ class SingaBackend(Backend):
 
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return handle, forward
+        return _, forward(handle, odd_padding)
 
     @classmethod
     def _create_max_avg_pool(cls, onnx_node, inputs, opset_version):
@@ -1061,17 +1619,17 @@ class SingaBackend(Backend):
             forward, the autograd of singa operator
         """
         kernel = tuple(onnx_node.attrs["kernel_shape"])
-        # todo: we only support the padding with tuple
         padding = tuple(
-            onnx_node.attrs["pads"][0:2]) if "pads" in onnx_node.attrs else (0,
-                                                                             0)
+            onnx_node.attrs["pads"]) if "pads" in onnx_node.attrs else (0, 0)
         stride = tuple(onnx_node.getattr('strides', (1, 1)))
+        # default the odd_padding is 0, once there are same pad mode, we modify it
+        # for odd_padding, please refer the autegrade.py
+        odd_padding = (0, 0, 0, 0)
         if "auto_pad" in onnx_node.attrs:
-            auto_pad = force_unicode(onnx_node.attrs['auto_pad'])
-            out_shape = get_output_shape(auto_pad, inputs[0].shape[2:], kernel,
-                                         stride)
-            padding = get_pad_shape(auto_pad, inputs[0].shape[2:], kernel,
-                                    stride, out_shape)
+            auto_pad = utils.force_unicode(onnx_node.attrs['auto_pad'])
+            if auto_pad in ('SAME_UPPER', 'SAME_LOWER'):
+                padding, odd_padding = utils.get_padding_shape(
+                    auto_pad, inputs[0].shape[2:], kernel, stride)
 
         # not support count_include_pad and auto_pad
         if "count_include_pad" in onnx_node.attrs or "ceil_mode" in onnx_node.attrs:
@@ -1093,7 +1651,7 @@ class SingaBackend(Backend):
 
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
-        return handle, forward
+        return _, forward(handle, odd_padding)
 
     @classmethod
     def _create_batchnorm(cls, onnx_node, inputs, opset_version):
@@ -1156,12 +1714,8 @@ class SingaBackend(Backend):
         """
         factor = onnx_node.getattr('axis', 1)
         if factor < 0:
-            factor = len(inputs[0].shape
-                        ) + factor  # in order to support the negative axis
-        # alpha = onnx_node.attrs["alpha"]
-        # beta = onnx_node.attrs["beta"]
-        # transA = False if onnx_node.attrs["transA"] == 0 else True
-        # transB = False if onnx_node.attrs["transB"] == 0 else True
+            # in order to support the negative axis
+            factor = len(inputs[0].shape) + factor
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
         return None, forward(axis=factor)
@@ -1210,33 +1764,14 @@ class SingaBackend(Backend):
         """
         factor = onnx_node.getattr('axis', 1)
         if factor < 0:
-            factor = len(inputs[0].shape
-                        ) + factor  # in order to support the negative axis
+            # in order to support the negative axis
+            factor = len(inputs[0].shape) + factor
 
         _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
                                                        opset_version)
         return None, forward(start_axis=factor)
 
     @classmethod
-    def _create_matmul(cls, onnx_node, inputs, opset_version):
-        """
-        get the matmul operator from onnx node
-        Args:
-            onnx_node: a given onnx node
-        Args:
-            inputs: the input tensor
-        Args:
-            opset_version: the opset version
-        Returns: 
-            the handle of singa operator
-        Returns: 
-            the autograd of singa operator
-        """
-        _, forward = cls._common_onnx_node_to_singa_op(onnx_node, inputs,
-                                                       opset_version)
-        return None, forward()
-
-    @classmethod
     def _common_onnx_node_to_singa_op(cls, onnx_node, inputs, opset_version):
         """
         get a common singa operator(only autograd) from a onnx node
@@ -1253,12 +1788,16 @@ class SingaBackend(Backend):
             a list of SingaOps('name', 'op', 'handle', 'forward')
         """
         onnx_op_type = onnx_node.op_type
-        autograd_op = getattr(
-            autograd, cls._rename_operators.get(onnx_op_type, onnx_op_type))
+        assert onnx_op_type in cls._rename_operators, "not support operator: {}".format(
+            onnx_op_type)
+        autograd_op = getattr(autograd, cls._rename_operators[onnx_op_type])
         return None, autograd_op
 
     @classmethod
-    def _onnx_node_to_singa_op(cls, onnx_node, inputs, opset_version):
+    def _onnx_node_to_singa_op(cls,
+                               onnx_node,
+                               inputs,
+                               opset_version=_known_opset_version):
         """
         get a singa operator(handle and autograd) from a onnx node
         Args:
@@ -1298,10 +1837,19 @@ class SingaBackend(Backend):
             inputs), "{}: expected {} but got {}".format(
                 onnx_node.op_type, len(valid_inputs), len(inputs))
 
-        inputs = [inputs[x] for x in valid_inputs]
-        handle, forward = cls._onnx_node_to_singa_op(onnx_node, inputs,
+        tmp_inputs = [inputs[x] for x in onnx_node.inputs if x != ""]
+        handle, forward = cls._onnx_node_to_singa_op(onnx_node, tmp_inputs,
                                                      opset_version)
-        return cls._run_node(onnx_node, inputs, handle, forward, opset_version)
+        # only give the inputs it needs
+        # consumed_inputs are the inputs marked as attributes
+        # so we remove it here
+        tmp_inputs = [
+            inputs[x]
+            for x in onnx_node.inputs
+            if x not in onnx_node.consumed_inputs
+        ]
+        return cls._run_node(onnx_node, tmp_inputs, handle, forward,
+                             opset_version)
 
     @classmethod
     def _run_node(cls,
@@ -1323,42 +1871,78 @@ class SingaBackend(Backend):
         Returns: 
             list, the output of the
         """
-        # since reshape acutally only needs one input tensor
-        # but onnx regard its shape as another tensor, we need to ommit it
         outputs = forward(*inputs) if handle is None else forward(
             handle, *inputs)
         if not isinstance(outputs, collections.Iterable):
             outputs = [outputs]
-        outputs_dict = collections.OrderedDict()
+        outputs_dict = OrderedDict()
         for (key, val) in zip(onnx_node.outputs, outputs):
             outputs_dict[key] = val
         return outputs_dict
 
     @classmethod
-    def _onnx_node_to_singa_tensor(cls, node_infos, tensor_map, device):
+    def _init_graph_parameter(cls, graph, init_inputs, device):
         """
         init the singa tensor from onnx infos
         Args:
-            node_infos: a given onnx model
+            graph: a given onnx graph
         Args:
-            tensor_map: the tensor map
+            init_inputs: a list of inputs, which used to init the operators
         Args:
             device: the used device
+        Returns:
+            a dict of tensors
         """
-        for x in node_infos:
-            x_shape = tuple(
-                dim.dim_value for dim in x.type.tensor_type.shape.dim)
-            tmp_tensor = tensor.from_numpy(
-                np.random.randn(*x_shape).astype(np.float32))
+        tensor_map = {}
+        # due to https://github.com/onnx/onnx/issues/2417
+        # sometimes, input contains all initializer's info
+        # sometimes, may not
+        all_inputs = OrderedDict()
+        for t in graph.input:
+            all_inputs[t.name] = t
+        # so we refresh the input by the initializer
+        for t in graph.initializer:
+            all_inputs[t.name] = t
+        initializers = {t.name for t in graph.initializer}
+        inp_idx = 0
+        for name, x in all_inputs.items():
+            if name in initializers:
+                # if it has initializer, we use its value as the input
+                np_tensor = numpy_helper.to_array(x)
+                if np_tensor.dtype == "int64":
+                    np_tensor = np_tensor.astype(np.int32)
+                # todo, we cannot support scalar tensor
+                if np.ndim(np_tensor) == 0:
+                    np_tensor = np.array(np_tensor, ndmin=1)
+            else:
+                # if not, means it's a input rather than a inner weight
+                # so if the user gives values, we use these values
+                # if not, we just use the shape of input gived by onnx to init a random value
+                # HOWEVER, the random value may not be correct for some inputs, such as gather which needs indices
+                # so if have operators, the user must give inputs
+                x_shape = tuple(
+                    dim.dim_value for dim in x.type.tensor_type.shape.dim)
+                if init_inputs is not None:
+                    np_tensor = init_inputs[inp_idx]
+                    inp_idx += 1
+                else:
+                    np_tensor = np.random.randn(*x_shape).astype(np.float32)
+            tmp_tensor = tensor.from_numpy(np_tensor)
             tmp_tensor.to_device(device)
+            # todo, for backward
+            tmp_tensor.stores_grad = (name in initializers)
             tensor_map[x.name] = tmp_tensor
+        return tensor_map
 
     @classmethod
-    def _onnx_model_to_singa_net(cls, onnx_model, device, opset_version):
+    def _onnx_model_to_singa_net(cls, model, init_inputs, device,
+                                 opset_version):
         """
         get all intermediate tensors and operators from onnx model
         Args:
-            onnx_model: a given onnx model
+            model: a given onnx model
+        Args:
+            init_inputs: a list of inputs, which used to init the operators
         Args:
             device: the used device
         Args:
@@ -1368,40 +1952,37 @@ class SingaBackend(Backend):
         Returns:
             a list of SingaOps('name', 'op', 'handle', 'forward')
         """
-        #  runs model checker, optimizer, shape inference engine
-        optimized_model = onnx.utils.polish_model(onnx_model)
-        # print('The model is:\n{}'.format(optimized_model))
-        # this tensor_nap contains all tensors, including outputs of each op
-        tensor_map = {}
-        # this weights only contains the tensors which have stored the gradients
-        weights = {}
+        # init all tensor input and weight as a tensor map
+        tensor_map = cls._init_graph_parameter(model.graph, init_inputs, device)
+        # only weights tensor
+        weights = {x.name: tensor_map[x.name] for x in model.graph.initializer}
+        # the parsed operators queue
         singa_ops = []
-        singa_op = collections.namedtuple('SingaOps',
-                                          ['name', 'op', 'handle', 'forward'])
-        # init the input, output, and intermidate nodes as singa tensors
-        cls._onnx_node_to_singa_tensor(optimized_model.graph.input, tensor_map,
-                                       device)
-        cls._onnx_node_to_singa_tensor(optimized_model.graph.output, tensor_map,
-                                       device)
-        cls._onnx_node_to_singa_tensor(optimized_model.graph.value_info,
-                                       tensor_map, device)
-        # convert constant nodes to tensor, other nodes to handler
-        for node in optimized_model.graph.node:
+        singa_op = namedtuple('SingaOps', ['name', 'op', 'handle', 'forward'])
+        for node in model.graph.node:
             node = OnnxNode(node)
-            if node.op_type == "Constant":
-                requires_grad, stores_grad = False, False
-                tmp_tensor = tensor.Tensor(
-                    device=device,
-                    data=numpy_helper.to_array(node.attrs['value']),
-                    requires_grad=requires_grad,
-                    stores_grad=stores_grad,
-                )
-                tensor_map[node.name] = tmp_tensor
-                weights[node.name] = tmp_tensor
+            # only give the inputs it needs
+            # consumed_inputs are the inputs marked as attributes
+            # so we remove it here
+            inputs = [
+                tensor_map[x]
+                for x in node.inputs
+                if x not in node.consumed_inputs
+            ]
+            handle, forward = cls._onnx_node_to_singa_op(
+                node, inputs, opset_version)
+            # if it is Constant, we hanlde it as a weight
+            # otherwise, we run it and add its output into map for being used by later operators
+            if node.op_type == 'Constant':
+                tmp_tensor = tensor.from_numpy(forward)
+                tmp_tensor.to_device(device)
+                tmp_name = node.outputs.pop(0)
+                weights[tmp_name] = tmp_tensor
+                tensor_map[tmp_name] = tmp_tensor
             else:
-                inputs = [tensor_map[x].clone() for x in node.inputs]
-                handle, forward = cls._onnx_node_to_singa_op(
-                    node, inputs, opset_version)
+                outputs = cls._run_node(node, inputs, handle, forward)
+                for key, val in outputs.items():
+                    tensor_map[key] = val
                 singa_ops.extend([singa_op(node.name, node, handle, forward)])
         return weights, singa_ops
 
@@ -1410,17 +1991,28 @@ class SingaBackend(Backend):
         """
         get the batch norm operator from onnx node
         Args:
-            onnx_node: a given onnx node
-        Args:
-            tensor_map: the input tensor
+            model: a given onnx node
         Args:
             device: the used device
-        Args:
-            opset_version: the opset version
         Returns: 
             a list of output values
         """
         super(SingaBackend, cls).prepare(model, device, **kwargs)
+        # when parsing graph, we use the shape of input gived by onnx to init a random value
+        # HOWEVER, the random value may not be correct for some inputs, such as gather which needs indices
+        # so if have operators, the user must give inputs
+        init_inputs = kwargs.get("init_inputs", None)
+        # whether initializers are moved into inputs, due to https://github.com/onnx/onnx/issues/2417
+        # sometimes, input contains all initializer's info, sometimes, may not
+        cls.keep_initializers_as_inputs = kwargs.get(
+            'keep_initializers_as_inputs', True)
+        # optimize and infer the shape of the model
+        try:
+            model = onnx.utils.polish_model(model)
+        except IndexError as err:
+            # due to https://github.com/onnx/onnx/issues/2417
+            model = onnx.shape_inference.infer_shapes(model)
+
         # check the opset version and ir version
         opset_version = None
         for imp in model.opset_import:
@@ -1439,14 +2031,19 @@ class SingaBackend(Backend):
                 )
             else:
                 opset_version = 1
-        tensor_map, singa_ops = cls._onnx_model_to_singa_net(
-            model, device, opset_version)
-        return SingaRep(model, tensor_map, singa_ops)
+        weights, singa_ops = cls._onnx_model_to_singa_net(
+            model, init_inputs, device, opset_version)
+        return SingaRep(model, weights, singa_ops,
+                        cls.keep_initializers_as_inputs)
 
 
 class SingaRep(BackendRep):
 
-    def __init__(self, model, tensor_map, singa_ops):
+    def __init__(self,
+                 model,
+                 weights,
+                 singa_ops,
+                 keep_initializers_as_inputs=True):
         """
         SingaRep provides the intermediate representation of Singa,
         the user can run the forward of the singa model by run func,
@@ -1455,13 +2052,14 @@ class SingaRep(BackendRep):
         Args:
             model: a given operator
         Args:
-            tensor_map: the tensor of the operator
+            weights: the tensor of weights
         Args:
             singa_ops: the tensor of the operator
         """
         super(SingaRep, self).__init__()
         self.model = model
-        self.tensor_map = tensor_map
+        self.tensor_map = weights
+        self.keep_initializers_as_inputs = keep_initializers_as_inputs
         # this each item of singa_ops is: ('name', 'op', 'handle', 'forward')
         # the name is a string, op is OnnxNode,
         # handle is Singa handle to store the tensor into singa operator
@@ -1476,27 +2074,49 @@ class SingaRep(BackendRep):
         Returns: 
             the onnx node
         """
+        graph = self.model.graph
         # last_layers means we run this model until the last #N layers
         last_layers = kwargs.get('last_layers', len(self.singa_ops))
+        if last_layers != len(self.singa_ops):
+            final_outputs = self.singa_ops[last_layers-1].op.outputs
+        else:
+            final_outputs =  [outp.name for outp in graph.output]
         # whether return all outputs
         all_outputs = kwargs.get('all_outputs', False)
         # get a specific op by its name
         op_name = kwargs.get('op_name', None)
+        # record the tensor we added from input
+        tmp_tensor_map = {name: val for name, val in self.tensor_map.items()}
 
         # the dict will be returned
-        ret_outputs = collections.OrderedDict()
-        if len(self.model.graph.input) != len(inputs):
-            raise RuntimeError(
-                "The length of graph input is different from the tensor input: %d, %d"
-                % (len(self.model.graph.input), len(inputs)))
+        ret_outputs = OrderedDict()
+        if self.keep_initializers_as_inputs:
+            require_input_len = len(graph.input) - len(graph.initializer)
+            actual_input_len = len(inputs)
+        else:
+            require_input_len = len(graph.input)
+            actual_input_len = len(inputs)
+        assert require_input_len == actual_input_len, "The length of graph input is different from the tensor input: %d, %d" % (
+            require_input_len, actual_input_len)
         # run the handle by the order of the list(the list is Topological Sorting)
-        for x, val in zip(self.model.graph.input, inputs):
-            self.tensor_map[x.name] = val
+        for inp in graph.input:
+            if inp.name not in tmp_tensor_map:
+                tmp_tensor_map[inp.name] = inputs.pop(0)
+
         for _, op, handle, forward in self.singa_ops[:last_layers]:
-            inputs = [self.tensor_map[x] for x in op.inputs]
+            if len(op.consumed_inputs) != 0:
+                # because if op has consumed_inputs, it means it moved some inputs into attributes
+                # so when running, we should update these attributes
+                handle, forward = get_op(op,
+                                         [tmp_tensor_map[x] for x in op.inputs])
+            inputs = [
+                tmp_tensor_map[x]
+                for x in op.inputs
+                if x not in op.consumed_inputs
+            ]
             outputs = _run_node(op, inputs, handle, forward)
             for key, val in outputs.items():
-                self.tensor_map[key] = val
+                tmp_tensor_map[key] = val
                 ret_outputs[key] = val
 
         if op_name is not None:
@@ -1512,12 +2132,13 @@ class SingaRep(BackendRep):
         if all_outputs:
             return ret_outputs
         else:
-            return list(outputs.values())
+            return [ret_outputs[outp] for outp in final_outputs]
 
 
 run_node = SingaBackend.run_node
 _run_node = SingaBackend._run_node
 prepare = SingaBackend.prepare
+get_op = SingaBackend._onnx_node_to_singa_op
 to_onnx = SingaFrontend.singa_to_onnx_model
 save = onnx.save
 load = onnx.load
diff --git a/python/singa/utils.py b/python/singa/utils.py
index 8c38f6c..78c9f2c 100644
--- a/python/singa/utils.py
+++ b/python/singa/utils.py
@@ -18,10 +18,13 @@
 import sys
 import math
 import numpy as np
+import collections
 
 from singa import tensor
 from . import singa_wrap as singa
 
+OrderedDict = collections.OrderedDict
+
 
 def update_progress(progress, info):
     """Display progress bar and user info.
@@ -231,3 +234,40 @@ def force_unicode(s):
         return s.decode('utf-8')
     except AttributeError:
         return s
+
+
+def post_order_recursive(root, root_t):
+    """
+    return a list by the topological ordering (postorder of Depth-first search)
+    Args:
+        root: singa operator
+    Args:
+        root_t: tensor
+    Returns: 
+        deque[int]
+    """
+
+    def recursive(root, yid, root_t, nodes, weights, inputs):
+        if root:
+            # srcop: operator for a input of root
+            # yid: id(output of this operator)
+            # y: output of this operator
+            for srcop, yid, y, _ in root.src:
+                recursive(srcop, yid, y, nodes, weights, inputs)
+
+            if type(root).__name__ == 'Dummy':
+                if root_t != None:
+                    # constant within a node: weight
+                    weights[root.name] = root_t
+                else:
+                    # constant outside a node: input
+                    inputs[root.name] = root_t
+            else:
+                nodes[root.name] = root
+
+    nodes = OrderedDict()
+    weights = OrderedDict()
+    inputs = OrderedDict()
+
+    recursive(root, None, root_t, nodes, weights, inputs)
+    return nodes, weights, inputs
diff --git a/test/python/test_onnx.py b/test/python/test_onnx.py
index 59d8440..18afa0b 100644
--- a/test/python/test_onnx.py
+++ b/test/python/test_onnx.py
@@ -37,8 +37,19 @@ import numpy as np
 autograd.training = True
 
 
+def _tuple_to_string(t):
+    lt = [str(x) for x in t]
+    return '(' + ', '.join(lt) + ')'
+
+
 class TestPythonOnnx(unittest.TestCase):
 
+    def check_shape(self, actual, expect):
+        self.assertEqual(
+            actual, expect, 'shape mismatch, actual shape is %s'
+            ' exepcted is %s' %
+            (_tuple_to_string(actual), _tuple_to_string(expect)))
+
     def test_conv2d(self):
         x = tensor.Tensor(shape=(2, 3, 3, 3), device=gpu_dev)
         x.gaussian(0.0, 1.0)
@@ -235,7 +246,7 @@ class TestPythonOnnx(unittest.TestCase):
 
         # backend
         sg_ir = sonnx.prepare(model, device=gpu_dev)
-        y_t = sg_ir.run([x, s, bias, mean, var])
+        y_t = sg_ir.run([x, s, bias]) # mean and var has been stored in graph
 
         np.testing.assert_array_almost_equal(tensor.to_numpy(y),
                                              tensor.to_numpy(y_t[0]),
@@ -299,7 +310,7 @@ class TestPythonOnnx(unittest.TestCase):
 
         # backend
         sg_ir = sonnx.prepare(model, device=gpu_dev)
-        y_t = sg_ir.run([x, (2, 3)])
+        y_t = sg_ir.run([x]) # shape has been stored in graph
 
         np.testing.assert_array_almost_equal(tensor.to_numpy(y),
                                              tensor.to_numpy(y_t[0]),
@@ -889,7 +900,7 @@ class TestPythonOnnx(unittest.TestCase):
 
         # backend
         sg_ir = sonnx.prepare(model, device=gpu_dev)
-        y_t = sg_ir.run([x, min, max])
+        y_t = sg_ir.run([x]) # min, max has been stored in model
 
         np.testing.assert_array_almost_equal(tensor.to_numpy(y),
                                              tensor.to_numpy(y_t[0]),
@@ -1165,6 +1176,230 @@ class TestPythonOnnx(unittest.TestCase):
                                              tensor.to_numpy(y_t[0]),
                                              decimal=5)
 
+    def test_constantOfShape(self):
+        X = np.array([4, 3, 2]).astype(np.int64)
+        x = tensor.from_numpy(X)
+        x.to_device(cpu_dev)
+
+        y = autograd.constant_of_shape(x, 1.)
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev, init_inputs=[X])
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y),
+                                             tensor.to_numpy(y_t[0]),
+                                             decimal=5)
+
+    def test_dropout(self):
+        X = np.random.randn(3, 4, 5).astype(np.float32)
+
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.dropout(x, 0.5)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        self.check_shape(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_reduceSum(self):
+        X = np.random.randn(3, 4, 5).astype(np.float32)
+
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.reduce_sum(x, None, 1)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_reduceMean(self):
+        X = np.random.randn(3, 4, 5).astype(np.float32)
+
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.reduce_mean(x, None, 1)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_squeeze(self):
+        X = np.random.randn(3, 1, 2, 1, 1)
+
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.squeeze(x, [1, 3, 4])
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_unsqueeze(self):
+        X = np.random.randn(3, 2)
+        
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.unsqueeze(x, [2, 4, 5])
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_slice(self):
+        X = np.random.randn(20, 10, 5).astype(np.float32)
+        starts, ends, axes, steps = [0, 0], [3, 10], [0, 1], [1, 1]
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.slice(x, starts, ends, axes, steps)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    # todo, we don't support muli outputs
+    # def test_split(self):
+    #     X = np.array([1., 2., 3., 4., 5., 6.]).astype(np.float32)
+    #     x = tensor.from_numpy(X)
+    #     x.to_device(gpu_dev)
+    #     y = autograd.split(x, 0, (2, 4))
+
+    #     # frontend
+    #     model = sonnx.to_onnx([x], [*y])
+    #     # print('The model is:\n{}'.format(model))
+
+    #     # backend
+    #     sg_ir = sonnx.prepare(model, device=gpu_dev)
+    #     y_t = sg_ir.run([x])[0]
+
+    #     np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t).shape)
+
+    def test_gather(self):
+        X = np.array([0, 1, 2]).astype(np.float32)
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.gather(x, 0, [0, 1, 3])
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_tile(self):
+        X = np.array([0, 1, 2]).astype(np.float32)
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.tile(x, [2, 2])
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_nonzero(self):
+        X = np.array([[1, 0], [1, 1]]).astype(np.float32)
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.nonzero(x)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_cast(self):
+        X = np.array([[1, 0], [1, 1]]).astype(np.float32)
+        x = tensor.from_numpy(X)
+        x.to_device(gpu_dev)
+        y = autograd.cast(x, tensor.int32)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        np.testing.assert_array_almost_equal(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
+    def test_onehot(self):
+        axisValue = 1
+        on_value = 3
+        off_value = 1
+        output_type = np.float32
+        indices = np.array([[1, 9], [2, 4]], dtype=np.float32)
+        depth = np.array([10], dtype=np.float32)
+        values = np.array([off_value, on_value], dtype=output_type)
+
+        x = tensor.from_numpy(indices)
+        x.to_device(gpu_dev)
+        y = autograd.onehot(axisValue, x, depth, values)
+
+        # frontend
+        model = sonnx.to_onnx([x], [y])
+        # print('The model is:\n{}'.format(model))
+
+        # backend
+        sg_ir = sonnx.prepare(model, device=gpu_dev)
+        y_t = sg_ir.run([x])
+
+        self.check_shape(tensor.to_numpy(y).shape, tensor.to_numpy(y_t[0]).shape)
+
     def test_inference(self):
         x = tensor.Tensor(shape=(2, 3, 3, 3), device=gpu_dev)
         x.gaussian(0.0, 1.0)
@@ -1252,24 +1487,6 @@ class TestPythonOnnx(unittest.TestCase):
             sgd.update(p, gp)
         sgd.step()
 
-    def test_globalaveragepool(self):
-        X = np.array([[[
-            [1, 2, 3],
-            [4, 5, 6],
-            [7, 8, 9],
-        ]]]).astype(np.float32)
-
-        x = tensor.from_numpy(X)
-        x.to_device(gpu_dev)
-        y = autograd.globalaveragepool(x)
-
-        # frontend
-        model = sonnx.to_onnx([x], [y])
-        # backend
-        sg_ir = sonnx.prepare(model, device=gpu_dev)
-        y_t = sg_ir.run([x])
-
-        np.testing.assert_array_almost_equal(tensor.to_numpy(y), tensor.to_numpy(y_t[0]), decimal=5)
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/test_onnx_backend.py b/test/python/test_onnx_backend.py
index 3d7427b..a1dc1f0 100644
--- a/test/python/test_onnx_backend.py
+++ b/test/python/test_onnx_backend.py
@@ -37,29 +37,31 @@ import itertools
 
 autograd.training = True
 
-_default_opset_version = 10
+_default_opset_version = 11
 
 
-def expect(node, inputs, outputs, name, opset_version=_default_opset_version):
+def expect(node,
+           inputs,
+           outputs,
+           name,
+           opset_version=_default_opset_version,
+           decimal=5):
     onnx_node = sonnx.OnnxNode(node)
     input_tensors = {}
     input_labels = [x for x in onnx_node.inputs if x != ""]
     # prepare input tensors
     for key, val in zip(input_labels, inputs):
-        if node.op_type=="Clip" and key in ("min", "max"):
-            input_tensors[key] = val.item()
-        else:
-            # very important! must be float
-            if not isinstance(val, np.ndarray) or len(val.shape) == 0:
-                val = np.array([val])
-            x = tensor.from_numpy(val.astype(np.float32))
-            x.to_device(gpu_dev)
-            input_tensors[key] = x
+        # very important! must be float
+        if not isinstance(val, np.ndarray) or len(val.shape) == 0:
+            val = np.array([val])
+        x = tensor.from_numpy(val.astype(np.float32))
+        x.to_device(gpu_dev)
+        input_tensors[key] = x
     outputs_dict = sonnx.run_node(onnx_node, input_tensors, opset_version)
     for out1, out2 in zip(outputs, outputs_dict.values()):
         np.testing.assert_array_almost_equal(out1,
                                              tensor.to_numpy(out2),
-                                             decimal=5)
+                                             decimal=decimal)
 
 
 class TestPythonOnnxBackend(unittest.TestCase):
@@ -886,7 +888,7 @@ class TestPythonOnnxBackend(unittest.TestCase):
 
         x = np.random.randn(3, 4, 5).astype(np.float32)
         y = np.tan(x)
-        expect(node, inputs=[x], outputs=[y], name='test_tan')
+        expect(node, inputs=[x], outputs=[y], name='test_tan', decimal=3)
 
     def test_Tanh(self):  # type: () -> None
         node = onnx.helper.make_node(
@@ -1851,13 +1853,17 @@ class TestPythonOnnxBackend(unittest.TestCase):
         x = np.array([1, 2, 3]).astype(np.float32)
         y = np.array([4, 5, 6]).astype(np.float32)  # todo, not exactly same
         z = np.power(x, y)  # expected output [1., 32., 729.]
-        expect(node, inputs=[x, y], outputs=[z], name='test_pow_example')
+        expect(node,
+               inputs=[x, y],
+               outputs=[z],
+               name='test_pow_example',
+               decimal=3)
 
         x = np.arange(24).reshape(2, 3, 4).astype(
             np.float32)  # todo, cannot too big here
         y = np.random.randn(2, 3, 4).astype(np.float32)
         z = np.power(x, y)
-        expect(node, inputs=[x, y], outputs=[z], name='test_pow')
+        expect(node, inputs=[x, y], outputs=[z], name='test_pow', decimal=3)
 
     def test_pow_broadcast(self):  # type: () -> None
         node = onnx.helper.make_node(
@@ -1869,7 +1875,11 @@ class TestPythonOnnxBackend(unittest.TestCase):
         x = np.array([1, 2, 3]).astype(np.float32)
         y = np.array(2).astype(np.float32)
         z = np.power(x, y)  # expected output [1., 4., 9.]
-        expect(node, inputs=[x, y], outputs=[z], name='test_pow_bcast_scalar')
+        expect(node,
+               inputs=[x, y],
+               outputs=[z],
+               name='test_pow_bcast_scalar',
+               decimal=3)
 
         node = onnx.helper.make_node(
             'Pow',
@@ -1880,7 +1890,11 @@ class TestPythonOnnxBackend(unittest.TestCase):
         y = np.array([1, 2, 3]).astype(np.float32)
         # expected output [[1, 4, 27], [4, 25, 216]]
         z = np.power(x, y).astype(np.float32)
-        expect(node, inputs=[x, y], outputs=[z], name='test_pow_bcast_array')
+        expect(node,
+               inputs=[x, y],
+               outputs=[z],
+               name='test_pow_bcast_array',
+               decimal=3)
 
     def test_clip(self):
         node = onnx.helper.make_node(
@@ -2031,160 +2045,974 @@ class TestPythonOnnxBackend(unittest.TestCase):
         y = np.random.randn(5).astype(np.float32)
         z = x * y
         expect(node, inputs=[x, y], outputs=[z], name='test_mul_bcast')
-        
+
     def test_gemm_default_zero_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'])
         a = np.random.ranf([3, 5]).astype(np.float32)
         b = np.random.ranf([5, 4]).astype(np.float32)
         c = np.zeros([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_default_zero_bias')
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_default_zero_bias')
 
     def test_gemm_default_no_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm', inputs=['a', 'b'], outputs=['y'])
         a = np.random.ranf([2, 10]).astype(np.float32)
         b = np.random.ranf([10, 3]).astype(np.float32)
         y = gemm_reference_implementation(a, b)
-        expect(node, inputs=[a, b], outputs=[y],
-                name='test_gemm_default_no_bias')
+        expect(node,
+               inputs=[a, b],
+               outputs=[y],
+               name='test_gemm_default_no_bias')
 
     def test_gemm_default_scalar_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'])
         a = np.random.ranf([2, 3]).astype(np.float32)
         b = np.random.ranf([3, 4]).astype(np.float32)
         c = np.array(3.14).astype(np.float32)
         y = gemm_reference_implementation(a, b, c)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_default_scalar_bias')
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_default_scalar_bias')
 
     def test_gemm_default_single_elem_vector_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'])
         a = np.random.ranf([3, 7]).astype(np.float32)
         b = np.random.ranf([7, 3]).astype(np.float32)
         c = np.random.ranf([1]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_default_single_elem_vector_bias')
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_default_single_elem_vector_bias')
 
     def test_gemm_default_vector_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'])
         a = np.random.ranf([2, 7]).astype(np.float32)
         b = np.random.ranf([7, 4]).astype(np.float32)
         c = np.random.ranf([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_default_vector_bias')
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_default_vector_bias')
 
     def test_gemm_default_matrix_bias(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y']
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'])
         a = np.random.ranf([3, 6]).astype(np.float32)
         b = np.random.ranf([6, 4]).astype(np.float32)
         c = np.random.ranf([3, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_default_matrix_bias')
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_default_matrix_bias')
 
     def test_gemm_transposeA(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y'],
-            transA=1
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'],
+                                     transA=1)
         a = np.random.ranf([6, 3]).astype(np.float32)
         b = np.random.ranf([6, 4]).astype(np.float32)
         c = np.zeros([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c, transA=1)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_transposeA')
+        expect(node, inputs=[a, b, c], outputs=[y], name='test_gemm_transposeA')
 
     def test_gemm_transposeB(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y'],
-            transB=1
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'],
+                                     transB=1)
         a = np.random.ranf([3, 6]).astype(np.float32)
         b = np.random.ranf([4, 6]).astype(np.float32)
         c = np.zeros([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c, transB=1)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_transposeB')
+        expect(node, inputs=[a, b, c], outputs=[y], name='test_gemm_transposeB')
 
     def test_gemm_alpha(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y'],
-            alpha=0.5
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'],
+                                     alpha=0.5)
         a = np.random.ranf([3, 5]).astype(np.float32)
         b = np.random.ranf([5, 4]).astype(np.float32)
         c = np.zeros([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c, alpha=0.5)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_alpha')
+        expect(node, inputs=[a, b, c], outputs=[y], name='test_gemm_alpha')
 
     def test_gemm_beta(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y'],
-            beta=0.5
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'],
+                                     beta=0.5)
         a = np.random.ranf([2, 7]).astype(np.float32)
         b = np.random.ranf([7, 4]).astype(np.float32)
         c = np.random.ranf([1, 4]).astype(np.float32)
         y = gemm_reference_implementation(a, b, c, beta=0.5)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_beta')
+        expect(node, inputs=[a, b, c], outputs=[y], name='test_gemm_beta')
 
     def test_gemm_all_attributes(self):
-        node = onnx.helper.make_node(
-            'Gemm',
-            inputs=['a', 'b', 'c'],
-            outputs=['y'],
-            alpha=0.25,
-            beta=0.35,
-            transA=1,
-            transB=1
-        )
+        node = onnx.helper.make_node('Gemm',
+                                     inputs=['a', 'b', 'c'],
+                                     outputs=['y'],
+                                     alpha=0.25,
+                                     beta=0.35,
+                                     transA=1,
+                                     transB=1)
         a = np.random.ranf([4, 3]).astype(np.float32)
         b = np.random.ranf([5, 4]).astype(np.float32)
         c = np.random.ranf([1, 5]).astype(np.float32)
-        y = gemm_reference_implementation(a, b, c, transA=1, transB=1, alpha=0.25, beta=0.35)
-        expect(node, inputs=[a, b, c], outputs=[y],
-                name='test_gemm_all_attributes')
+        y = gemm_reference_implementation(a,
+                                          b,
+                                          c,
+                                          transA=1,
+                                          transB=1,
+                                          alpha=0.25,
+                                          beta=0.35)
+        expect(node,
+               inputs=[a, b, c],
+               outputs=[y],
+               name='test_gemm_all_attributes')
+
+    def test_constantOfShape_float_ones(self):
+            x = np.array([4, 3, 2]).astype(np.int64)
+            tensor_value = onnx.helper.make_tensor("value", onnx.TensorProto.FLOAT,
+                                                [1], [1])
+            node = onnx.helper.make_node(
+                'ConstantOfShape',
+                inputs=['x'],
+                outputs=['y'],
+                value=tensor_value,
+            )
+
+            y = np.ones(x, dtype=np.float32)
+            expect(node,
+                inputs=[x],
+                outputs=[y],
+                name='test_constantofshape_float_ones')
+
+    def test_constantOfShape_int32_zeros(self):
+        x = np.array([10, 6]).astype(np.int64)
+        tensor_value = onnx.helper.make_tensor("value", onnx.TensorProto.INT32,
+                                               [1], [0])
+        node = onnx.helper.make_node(
+            'ConstantOfShape',
+            inputs=['x'],
+            outputs=['y'],
+            value=tensor_value,
+        )
+        y = np.zeros(x, dtype=np.int32)
+        expect(node,
+               inputs=[x],
+               outputs=[y],
+               name='test_constantofshape_int_zeros')
+
+    # cannot support yet
+    # def test_int32_shape_zero(self):
+    #     x = np.array([0, ]).astype(np.int64)
+    #     tensor_value = onnx.helper.make_tensor("value", onnx.TensorProto.INT32,
+    #                                            [1], [0])
+    #     node = onnx.helper.make_node(
+    #         'ConstantOfShape',
+    #         inputs=['x'],
+    #         outputs=['y'],
+    #         value=tensor_value,
+    #     )
+    #     y = np.zeros(x, dtype=np.int32)
+    #     expect(node, inputs=[x], outputs=[y],
+    #            name='test_constantofshape_int_shape_zero')
+
+    def test_reduce_sum_do_not_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [1]
+        keepdims = 0
+
+        node = onnx.helper.make_node('ReduceSum',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
+            dtype=np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+        #print(reduced)
+        #[[4., 6.]
+        # [12., 14.]
+        # [20., 22.]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_do_not_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_do_not_keepdims_random')
+
+    def test_reduce_sum_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [1]
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceSum',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
+            dtype=np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+        #print(reduced)
+        #[[[4., 6.]]
+        # [[12., 14.]]
+        # [[20., 22.]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_keepdims_random')
+
+    def test_reduce_sum_default_axes_keepdims(self):
+        shape = [3, 2, 2]
+        axes = None
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceSum',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
+            dtype=np.float32)
+        reduced = np.sum(data, axis=axes, keepdims=keepdims == 1)
+        #print(reduced)
+        #[[[78.]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_default_axes_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.sum(data, axis=axes, keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_default_axes_keepdims_random')
+
+    def test_reduce_sum_negative_axes_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [-2]
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceSum',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
+            dtype=np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+        # print(reduced)
+        #[[[4., 6.]]
+        # [[12., 14.]]
+        # [[20., 22.]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_negative_axes_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.sum(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_sum_negative_axes_keepdims_random')
+
+    def test_reduce_mean_do_not_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [1]
+        keepdims = 0
+
+        node = onnx.helper.make_node('ReduceMean',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[5, 1], [20, 2]], [[30, 1], [40, 2]], [[55, 1], [60, 2]]],
+            dtype=np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+        #print(reduced)
+        #[[12.5, 1.5]
+        # [35., 1.5]
+        # [57.5, 1.5]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_do_not_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_do_not_keepdims_random')
+
+    def test_reduce_mean_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [1]
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceMean',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[5, 1], [20, 2]], [[30, 1], [40, 2]], [[55, 1], [60, 2]]],
+            dtype=np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+        #print(reduced)
+        #[[[12.5, 1.5]]
+        # [[35., 1.5]]
+        # [[57.5, 1.5]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_keepdims_random')
+
+    def test_reduce_mean_default_axes_keepdims(self):
+        shape = [3, 2, 2]
+        axes = None
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceMean',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[5, 1], [20, 2]], [[30, 1], [40, 2]], [[55, 1], [60, 2]]],
+            dtype=np.float32)
+        reduced = np.mean(data, axis=axes, keepdims=keepdims == 1)
+        #print(reduced)
+        #[[[18.25]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_default_axes_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.mean(data, axis=axes, keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_default_axes_keepdims_random')
+
+    def test_reduce_mean_negative_axes_keepdims(self):
+        shape = [3, 2, 2]
+        axes = [-2]
+        keepdims = 1
+
+        node = onnx.helper.make_node('ReduceMean',
+                                     inputs=['data'],
+                                     outputs=['reduced'],
+                                     axes=axes,
+                                     keepdims=keepdims)
+
+        data = np.array(
+            [[[5, 1], [20, 2]], [[30, 1], [40, 2]], [[55, 1], [60, 2]]],
+            dtype=np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+        # print(reduced)
+        # [[[12.5, 1.5]]
+        # [[35., 1.5]]
+        # [[57.5, 1.5]]]
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_negative_axes_keepdims_example')
+
+        np.random.seed(0)
+        data = np.random.uniform(-10, 10, shape).astype(np.float32)
+        reduced = np.mean(data, axis=tuple(axes), keepdims=keepdims == 1)
+
+        expect(node,
+               inputs=[data],
+               outputs=[reduced],
+               name='test_reduce_mean_negative_axes_keepdims_random')
+
+    def test_squeeze(self):
+        node = onnx.helper.make_node(
+            'Squeeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[0],
+        )
+        x = np.random.randn(1, 3, 4, 5).astype(np.float32)
+        y = np.squeeze(x, axis=0)
+
+        expect(node, inputs=[x], outputs=[y], name='test_squeeze')
+
+    def test_squeeze_negative_axes(self):
+        node = onnx.helper.make_node(
+            'Squeeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[-2],
+        )
+        x = np.random.randn(1, 3, 1, 5).astype(np.float32)
+        y = np.squeeze(x, axis=-2)
+        expect(node, inputs=[x], outputs=[y], name='test_squeeze_negative_axes')
+
+    def test_unsqueeze_one_axis(self):
+        x = np.random.randn(3, 4, 5).astype(np.float32)
+
+        for i in range(x.ndim):
+            node = onnx.helper.make_node(
+                'Unsqueeze',
+                inputs=['x'],
+                outputs=['y'],
+                axes=[i],
+            )
+            y = np.expand_dims(x, axis=i)
+
+            expect(node,
+                   inputs=[x],
+                   outputs=[y],
+                   name='test_unsqueeze_axis_' + str(i))
+
+    def test_unsqueeze_two_axes(self):
+        x = np.random.randn(3, 4, 5).astype(np.float32)
+
+        node = onnx.helper.make_node(
+            'Unsqueeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[1, 4],
+        )
+        y = np.expand_dims(x, axis=1)
+        y = np.expand_dims(y, axis=4)
+
+        expect(node, inputs=[x], outputs=[y], name='test_unsqueeze_two_axes')
+
+    def test_unsqueeze_three_axes(self):
+        x = np.random.randn(3, 4, 5).astype(np.float32)
+
+        node = onnx.helper.make_node(
+            'Unsqueeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[2, 4, 5],
+        )
+        y = np.expand_dims(x, axis=2)
+        y = np.expand_dims(y, axis=4)
+        y = np.expand_dims(y, axis=5)
+
+        expect(node, inputs=[x], outputs=[y], name='test_unsqueeze_three_axes')
+
+    def test_unsqueeze_unsorted_axes(self):
+        x = np.random.randn(3, 4, 5).astype(np.float32)
+
+        node = onnx.helper.make_node(
+            'Unsqueeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[5, 4, 2],
+        )
+        y = np.expand_dims(x, axis=2)
+        y = np.expand_dims(y, axis=4)
+        y = np.expand_dims(y, axis=5)
+
+        expect(node,
+               inputs=[x],
+               outputs=[y],
+               name='test_unsqueeze_unsorted_axes')
+
+    def test_unsqueeze_negative_axes(self):
+        node = onnx.helper.make_node(
+            'Unsqueeze',
+            inputs=['x'],
+            outputs=['y'],
+            axes=[-2],
+        )
+        x = np.random.randn(1, 3, 1, 5).astype(np.float32)
+        y = np.expand_dims(x, axis=-2)
+        expect(node,
+               inputs=[x],
+               outputs=[y],
+               name='test_unsqueeze_negative_axes')
+
+    def test_slice(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes', 'steps'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        y = x[0:3, 0:10]
+        starts = np.array([0, 0], dtype=np.int64)
+        ends = np.array([3, 10], dtype=np.int64)
+        axes = np.array([0, 1], dtype=np.int64)
+        steps = np.array([1, 1], dtype=np.int64)
+
+        expect(node, inputs=[x, starts, ends, axes, steps], outputs=[y],
+               name='test_slice')
+
+    def test_slice_neg(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes', 'steps'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([0], dtype=np.int64)
+        ends = np.array([-1], dtype=np.int64)
+        axes = np.array([1], dtype=np.int64)
+        steps = np.array([1], dtype=np.int64)
+        y = x[:, 0:-1]
+
+        expect(node, inputs=[x, starts, ends, axes, steps], outputs=[y],
+               name='test_slice_neg')
+
+    # not support empty tensor
+    # def test_slice_start_out_of_bounds(self):
+    #     node = onnx.helper.make_node(
+    #         'Slice',
+    #         inputs=['x', 'starts', 'ends', 'axes', 'steps'],
+    #         outputs=['y'],
+    #     )
+
+    #     x = np.random.randn(20, 10, 5).astype(np.float32)
+    #     starts = np.array([1000], dtype=np.int64)
+    #     ends = np.array([1000], dtype=np.int64)
+    #     axes = np.array([1], dtype=np.int64)
+    #     steps = np.array([1], dtype=np.int64)
+    #     y = x[:, 1000:1000]
+
+    #     expect(node, inputs=[x, starts, ends, axes, steps], outputs=[y],
+    #            name='test_slice_start_out_of_bounds')
+
+    def test_slice_end_out_of_bounds(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes', 'steps'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([1], dtype=np.int64)
+        ends = np.array([1000], dtype=np.int64)
+        axes = np.array([1], dtype=np.int64)
+        steps = np.array([1], dtype=np.int64)
+        y = x[:, 1:1000]
+
+        expect(node, inputs=[x, starts, ends, axes, steps], outputs=[y],
+               name='test_slice_end_out_of_bounds')
+
+    def test_slice_default_axes(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([0, 0, 3], dtype=np.int64)
+        ends = np.array([20, 10, 4], dtype=np.int64)
+        y = x[:, :, 3:4]
+
+        expect(node, inputs=[x, starts, ends], outputs=[y],
+               name='test_slice_default_axes')
+
+    def test_slice_default_steps(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([0, 0, 3], dtype=np.int64)
+        ends = np.array([20, 10, 4], dtype=np.int64)
+        axes = np.array([0, 1, 2], dtype=np.int64)
+        y = x[:, :, 3:4]
+
+        expect(node, inputs=[x, starts, ends, axes], outputs=[y],
+               name='test_slice_default_steps')
+
+    def test_slice_neg_steps(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes', 'steps'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([20, 10, 4], dtype=np.int64)
+        ends = np.array([0, 0, 1], dtype=np.int64)
+        axes = np.array([0, 1, 2], dtype=np.int64)
+        steps = np.array([-1, -3, -2])
+        y = x[20:0:-1, 10:0:-3, 4:1:-2]
+
+        expect(node, inputs=[x, starts, ends, axes, steps], outputs=[y],
+               name='test_slice_neg_steps')
+
+    def test_slice_negative_axes(self):
+        node = onnx.helper.make_node(
+            'Slice',
+            inputs=['x', 'starts', 'ends', 'axes'],
+            outputs=['y'],
+        )
+
+        x = np.random.randn(20, 10, 5).astype(np.float32)
+        starts = np.array([0, 0, 3], dtype=np.int64)
+        ends = np.array([20, 10, 4], dtype=np.int64)
+        axes = np.array([0, -2, -1], dtype=np.int64)
+        y = x[:, :, 3:4]
+
+        expect(node, inputs=[x, starts, ends, axes], outputs=[y],
+               name='test_slice_negative_axes')
+
+    def test_split_1d(self):
+        input = np.array([1., 2., 3., 4., 5., 6.]).astype(np.float32)
+
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2', 'output_3'],
+            axis=0
+        )
+
+        expected_outputs = [np.array([1., 2.]).astype(np.float32), np.array([3., 4.]).astype(np.float32), np.array([5., 6.]).astype(np.float32)]
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_equal_parts_1d')
+
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2'],
+            axis=0,
+            split=[2, 4]
+        )
+
+        expected_outputs = [np.array([1., 2.]).astype(np.float32), np.array([3., 4., 5., 6.]).astype(np.float32)]
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_variable_parts_1d')
+
+    def test_split_2d(self):
+        input = np.array([[1., 2., 3., 4., 5., 6.],
+                          [7., 8., 9., 10., 11., 12.]]).astype(np.float32)
+
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2'],
+            axis=1
+        )
+
+        expected_outputs = [np.array([[1., 2., 3.], [7., 8., 9.]]).astype(np.float32),
+                            np.array([[4., 5., 6.], [10., 11., 12.]]).astype(np.float32)]
+
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_equal_parts_2d')
+
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2'],
+            axis=1,
+            split=[2, 4]
+        )
+
+        expected_outputs = [np.array([[1., 2.], [7., 8.]]).astype(np.float32),
+                            np.array([[3., 4., 5., 6.], [9., 10., 11., 12.]]).astype(np.float32)]
+
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_variable_parts_2d')
+
+    def test_split_default_values(self):
+        input = np.array([1., 2., 3., 4., 5., 6.]).astype(np.float32)
+
+        # If axis is not specified, split is applied on default axis 0
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2', 'output_3']
+        )
+
+        expected_outputs = [np.array([1., 2.]).astype(np.float32), np.array([3., 4.]).astype(np.float32), np.array([5., 6.]).astype(np.float32)]
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_equal_parts_default_axis')
 
+        node = onnx.helper.make_node(
+            'Split',
+            inputs=['input'],
+            outputs=['output_1', 'output_2'],
+            split=[2, 4]
+        )
+
+        expected_outputs = [np.array([1., 2.]).astype(np.float32), np.array([3., 4., 5., 6.]).astype(np.float32)]
+        expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_variable_parts_default_axis')
+
+    # not support empty tensor
+    # def test_split_zero_size_splits(self):
+    #     input = np.array([]).astype(np.float32)
+
+    #     # Split emtpy tensor to tensors of size zero
+    #     node = onnx.helper.make_node(
+    #         'Split',
+    #         inputs=['input'],
+    #         outputs=['output_1', 'output_2', 'output_3'],
+    #         split=[0, 0, 0]
+    #     )
+
+    #     expected_outputs = [np.array([]).astype(np.float32), np.array([]).astype(np.float32), np.array([]).astype(np.float32)]
+    #     expect(node, inputs=[input], outputs=[y for y in expected_outputs], name='test_split_zero_size_splits')
+
+    def test_gather_0(self):
+        node = onnx.helper.make_node(
+            'Gather',
+            inputs=['data', 'indices'],
+            outputs=['y'],
+            axis=0,
+        )
+        data = np.random.randn(5, 4, 3, 2).astype(np.float32)
+        indices = np.array([0, 1, 3])
+        y = np.take(data, indices, axis=0)
 
-def gemm_reference_implementation(A, B, C=None, alpha=1., beta=1., transA=0,
-                                transB=0):  # type: (np.ndarray, np.ndarray, Optional[np.ndarray], float, float, int, int) -> np.ndarray
+        expect(node, inputs=[data, indices.astype(np.int64)], outputs=[y],
+               name='test_gather_0')
+
+    def test_gather_1(self):
+        node = onnx.helper.make_node(
+            'Gather',
+            inputs=['data', 'indices'],
+            outputs=['y'],
+            axis=1,
+        )
+        data = np.random.randn(5, 4, 3, 2).astype(np.float32)
+        indices = np.array([0, 1, 3])
+        y = np.take(data, indices, axis=1)
+
+        expect(node, inputs=[data, indices.astype(np.int64)], outputs=[y],
+               name='test_gather_1')
+
+    def test_gather_negative_indices(self):
+        node = onnx.helper.make_node(
+            'Gather',
+            inputs=['data', 'indices'],
+            outputs=['y'],
+            axis=0,
+        )
+        data = np.arange(10).astype(np.float32)
+        indices = np.array([0, -9, -10])
+        y = np.take(data, indices, axis=0)
+
+        expect(node, inputs=[data, indices.astype(np.int64)], outputs=[y],
+               name='test_gather_negative_indices')
+
+    def test_tile(self):
+        node = onnx.helper.make_node(
+            'Tile',
+            inputs=['x', 'y'],
+            outputs=['z']
+        )
+
+        x = np.random.rand(2, 3, 4, 5).astype(np.float32)
+
+        repeats = np.random.randint(low=1, high=10, size=(np.ndim(x),)).astype(np.int64)
+
+        z = np.tile(x, repeats)
+
+        expect(node,
+               inputs=[x, repeats],
+               outputs=[z],
+               name='test_tile')
+
+    def test_tile_precomputed(self):
+        node = onnx.helper.make_node(
+            'Tile',
+            inputs=['x', 'y'],
+            outputs=['z']
+        )
+
+        x = np.array([
+            [0, 1],
+            [2, 3]
+        ], dtype=np.float32)
+
+        repeats = np.array([2, 2], dtype=np.int64)
+
+        z = np.array([
+            [0, 1, 0, 1],
+            [2, 3, 2, 3],
+            [0, 1, 0, 1],
+            [2, 3, 2, 3]
+        ], dtype=np.float32)
+
+        expect(node,
+               inputs=[x, repeats],
+               outputs=[z],
+               name='test_tile_precomputed')
+
+    def test_onehot_without_axis(self):
+        on_value = 5
+        off_value = 2
+        output_type = np.int32
+        node = onnx.helper.make_node('OneHot',
+                                     inputs=['indices', 'depth', 'values'],
+                                     outputs=['y'])
+        indices = np.array([0, 7, 8], dtype=np.int64)
+        depth = np.float32(12)
+        values = np.array([off_value, on_value], dtype=output_type)
+        y = one_hot(indices, depth, dtype=output_type)
+        y = y * (on_value - off_value) + off_value
+        expect(node,
+               inputs=[indices, depth, values],
+               outputs=[y],
+               name='test_onehot_without_axis')
+
+    def test_onehot_with_axis(self):
+        axisValue = 1
+        on_value = 3
+        off_value = 1
+        output_type = np.float32
+        node = onnx.helper.make_node('OneHot',
+                                     inputs=['indices', 'depth', 'values'],
+                                     outputs=['y'],
+                                     axis=axisValue)
+        indices = np.array([[1, 9], [2, 4]], dtype=np.float32)
+        depth = np.array([10], dtype=np.float32)
+        values = np.array([off_value, on_value], dtype=output_type)
+        y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
+        y = y * (on_value - off_value) + off_value
+        expect(node,
+               inputs=[indices, depth, values],
+               outputs=[y],
+               name='test_onehot_with_axis')
+
+    def test_onehot_with_negative_indices(self):
+        axisValue = 1
+        on_value = 3
+        off_value = 1
+        output_type = np.float32
+        node = onnx.helper.make_node('OneHot',
+                                     inputs=['indices', 'depth', 'values'],
+                                     outputs=['y'],
+                                     axis=axisValue)
+        indices = np.array([0, -7, -8], dtype=np.int64)
+
+        depth = np.array([10], dtype=np.float32)
+        values = np.array([off_value, on_value], dtype=output_type)
+        y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
+        y = y * (on_value - off_value) + off_value
+        expect(node,
+               inputs=[indices, depth, values],
+               outputs=[y],
+               name='test_onehot_negative_indices')
+
+    def test_onehot_with_negative_axis(self):
+        axisValue = -2
+        on_value = 3
+        off_value = 1
+        output_type = np.float32
+        node = onnx.helper.make_node('OneHot',
+                                     inputs=['indices', 'depth', 'values'],
+                                     outputs=['y'],
+                                     axis=axisValue)
+        indices = np.array([[1, 9], [2, 4]], dtype=np.float32)
+        depth = np.array([10], dtype=np.float32)
+        values = np.array([off_value, on_value], dtype=output_type)
+        y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
+        y = y * (on_value - off_value) + off_value
+        expect(node,
+               inputs=[indices, depth, values],
+               outputs=[y],
+               name='test_onehot_with_negative_axis')
+
+
+def one_hot(indices, depth, axis=-1, dtype=np.float32):  # type: ignore
+    ''' Compute one hot from indices at a specific axis '''
+    values = np.asarray(indices)
+    rank = len(values.shape)
+    depth_range = np.arange(depth)
+    if axis < 0:
+        axis += (rank + 1)
+    ls = values.shape[0:axis]
+    rs = values.shape[axis:rank]
+    targets = np.reshape(depth_range,
+                         (1,) * len(ls) + depth_range.shape + (1,) * len(rs))
+    values = np.reshape(np.mod(values, depth), ls + (1,) + rs)
+    return np.asarray(targets == values, dtype=dtype)
+
+
+def gemm_reference_implementation(
+    A,
+    B,
+    C=None,
+    alpha=1.,
+    beta=1.,
+    transA=0,
+    transB=0
+):  # type: (np.ndarray, np.ndarray, Optional[np.ndarray], float, float, int, int) -> np.ndarray
     A = A if transA == 0 else A.T
     B = B if transB == 0 else B.T
     C = C if C is not None else np.array(0)
@@ -2280,19 +3108,6 @@ def pool(
             y[shape] = f(window_vals[np.where(~np.isnan(window_vals))])
     return y.astype(np.float32)
 
-    def test_globalaveragepool(self):
-        node = onnx.helper.make_node(
-            'GlobalAveragePool',
-            inputs=['x'],
-            outputs=['y'],
-        )
-        x = np.array([[[
-            [1, 2, 3],
-            [4, 5, 6],
-            [7, 8, 9],
-        ]]]).astype(np.float32)
-        y = np.array([[[[5]]]]).astype(np.float32)
-        expect(node, inputs=[x], outputs=[y], name='test_globalaveragepool_precomputed')
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/test_operation.py b/test/python/test_operation.py
index 4a17282..be3cf2b 100755
--- a/test/python/test_operation.py
+++ b/test/python/test_operation.py
@@ -3645,6 +3645,7 @@ class TestPythonOperation(unittest.TestCase):
     def test_globalaveragepool_gpu(self):
         self.globalaveragepool_channel_first(gpu_dev)
         self.globalaveragepool_channel_last(gpu_dev)
+
     def constantOfShape_test(self, dev):
         # float_ones
         X = np.array([4, 3, 2]).astype(np.int64)
@@ -3959,7 +3960,9 @@ class TestPythonOperation(unittest.TestCase):
             x.to_device(dev)
 
             result = autograd.cast(x, t3)
-            np.testing.assert_array_almost_equal(tensor.to_numpy(result),
+            result_np = tensor.to_numpy(result)
+            assert result_np.dtype == y.dtype, "type %s != %s." % (result_np.dtype, y.dtype)
+            np.testing.assert_array_almost_equal(result_np,
                                                     y,
                                                     decimal=5)
 
@@ -3969,5 +3972,43 @@ class TestPythonOperation(unittest.TestCase):
     def test_cast_gpu(self):
         self.cast_test(gpu_dev)
 
+    def onehot_test(self, dev):
+        def one_hot(indices, depth, axis=-1, dtype=np.float32):  # type: ignore
+            ''' Compute one hot from indices at a specific axis '''
+            values = np.asarray(indices)
+            rank = len(values.shape)
+            depth_range = np.arange(depth)
+            if axis < 0:
+                axis += (rank + 1)
+            ls = values.shape[0:axis]
+            rs = values.shape[axis:rank]
+            targets = np.reshape(depth_range, (1,) * len(ls) + depth_range.shape + (1,) * len(rs))
+            values = np.reshape(np.mod(values, depth), ls + (1,) + rs)
+            return np.asarray(targets == values, dtype=dtype)
+
+        axisValue = 1
+        on_value = 3
+        off_value = 1
+        output_type = np.float32
+        indices = np.array([[1, 9], [2, 4]], dtype=np.float32)
+        depth = np.array([10], dtype=np.float32)
+        values = np.array([off_value, on_value], dtype=output_type)
+        y = one_hot(indices, depth, axis=axisValue, dtype=output_type)
+        y = y * (on_value - off_value) + off_value
+
+        x = tensor.from_numpy(indices)
+        x.to_device(dev)
+
+        result = autograd.onehot(axisValue, x, depth, values)
+        np.testing.assert_array_almost_equal(tensor.to_numpy(result),
+                                                y,
+                                                decimal=5)
+
+    def test_onehot_cpu(self):
+        self.onehot_test(cpu_dev)
+
+    def test_onehot_gpu(self):
+        self.onehot_test(gpu_dev)
+    
 if __name__ == '__main__':
     unittest.main()