Update bert optimization script for SQuAD model exported by keras2onnx (#3163)

Update script to make it work on fine-tuned bert model exported by keras2onnx
This commit is contained in:
Tianlei Wu 2020-03-10 12:57:49 -07:00 committed by GitHub
parent 876d0c5430
commit 51a8c82908
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 571 additions and 53 deletions

View file

@ -685,7 +685,8 @@ class BertOnnxModel(OnnxModel):
break
if normalize_node is None:
logger.info("Failed to find embedding layer")
if len(self.get_nodes_by_op_type("EmbedLayerNormalization")) == 0:
logger.info("Failed to find embedding layer")
return
# Here we assume the order of embedding is word_embedding +

View file

@ -18,6 +18,41 @@ class BertOnnxModelKeras(BertOnnxModelTF):
def __init(self, model, num_heads, hidden_size, sequence_length, input_int32, float16, gpu_only):
super().__init__(model, model, num_heads, hidden_size, sequence_length, input_int32, float16, gpu_only)
def match_mask_path(self, add_or_sub_before_softmax):
mask_nodes = self.match_parent_path(add_or_sub_before_softmax,
['Mul', 'Sub', 'Reshape', 'Cast'],
[ 1, None, 1, 0])
if mask_nodes is not None:
return mask_nodes
mask_nodes = self.match_parent_path(add_or_sub_before_softmax,
['Mul', 'Sub', 'Cast', 'Slice', 'Unsqueeze'],
[ 1, 1, 1, 0, 0])
if mask_nodes is not None:
return mask_nodes
mask_nodes = self.match_parent_path(add_or_sub_before_softmax,
['Mul', 'Sub', 'Cast', 'Unsqueeze', 'Unsqueeze'],
[ 1, None, 1, 0, 0])
return mask_nodes
def check_attention_input(self, matmul_q, matmul_k, matmul_v, parent, output_name_to_node):
reshape_nodes = []
for x in [matmul_q, matmul_k, matmul_v]:
root_input = x.input[0]
root_node = output_name_to_node[root_input]
if root_node == parent:
continue
if root_node.op_type == 'Reshape' and root_node.input[0] == parent.output[0]:
reshape_nodes.append(root_node)
continue
logger.debug(f"Check attention input failed:{root_input}, {parent.output[0]}")
return False, []
return True, reshape_nodes
def fuse_attention(self):
input_name_to_nodes = self.input_name_to_nodes()
output_name_to_node = self.output_name_to_node()
@ -71,8 +106,10 @@ class BertOnnxModelKeras(BertOnnxModelTF):
else:
qk_nodes = self.match_parent_path(matmul_qkv, ['Softmax', 'Add', 'Mul', 'MatMul'], [0, 0, 0, None])
if qk_nodes is None:
logger.debug("Failed to match qk path")
continue
qk_nodes = self.match_parent_path(matmul_qkv, ['Softmax', 'Add', 'Div', 'MatMul'], [0, 0, 0, None])
if qk_nodes is None:
logger.debug("Failed to match qk path")
continue
(softmax_qk, add_qk, mul_qk, matmul_qk) = qk_nodes
q_nodes = self.match_parent_path(matmul_qk,
@ -94,28 +131,16 @@ class BertOnnxModelKeras(BertOnnxModelTF):
(transpose_k, reshape_k, add_k, extra_reshape_3, matmul_k) = k_nodes
mask_nodes = self.match_parent_path(qk_nodes[1],
['Mul', 'Sub', 'Reshape', 'Cast'],
[ 1, None, 1, 0])
mask_nodes = self.match_mask_path(qk_nodes[1])
if mask_nodes is None:
mask_nodes = self.match_parent_path(qk_nodes[1],
['Mul', 'Sub', 'Cast', 'Slice', 'Unsqueeze'],
[ 1, 1, 1, 0, 0])
if mask_nodes is None:
logger.debug("Failed to match mask path")
continue
(mul_mask, sub_mask, cast_mask, slice_mask, unsqueeze_mask) = mask_nodes
else:
(mul_mask, sub_mask, reshape_mask, cast_mask) = mask_nodes
if not self.has_constant_input(sub_mask, 1):
logger.debug("Failed to match mask path")
continue
if not self.has_constant_input(mask_nodes[1], 1):
logger.debug("Sub node expected to have an input with constant value 1.0.")
continue
root_input = matmul_v.input[0]
root_node = output_name_to_node[root_input]
is_same_root = (root_node == parent or (root_node.op_type == 'Reshape' and root_node.input[0] == parent.output[0]))
if matmul_q.input[0] == root_input and matmul_v.input[0] == root_input and is_same_root:
is_same_root, reshape_nodes = self.check_attention_input(matmul_q, matmul_k, matmul_v, parent, output_name_to_node)
if is_same_root:
mask_index = self.process_mask(mask_nodes[-1].input[0])
logger.debug("Create an Attention node.")
self.create_attention_node(mask_index, matmul_q, matmul_k, matmul_v, add_q, add_k, add_v, parent.output[0], reshape_qkv.output[0])
@ -125,10 +150,8 @@ class BertOnnxModelKeras(BertOnnxModelTF):
nodes_to_remove.extend(k_nodes)
nodes_to_remove.extend(v_nodes)
nodes_to_remove.extend(mask_nodes)
if root_node.op_type == 'Reshape':
nodes_to_remove.append(root_node)
nodes_to_remove.extend(reshape_nodes)
attention_count += 1
nodes_to_remove.append(extra_reshape_0)
self.replace_node_input(add, extra_reshape_0.output[0], matmul.output[0])
else:
@ -138,35 +161,46 @@ class BertOnnxModelKeras(BertOnnxModelTF):
self.update_graph()
logger.info(f"Fused Attention count:{attention_count}")
def preprocess(self):
self.process_embedding()
self.fuse_mask()
self.skip_reshape()
def skip_reshape(self):
input_name_to_nodes = self.input_name_to_nodes()
output_name_to_node = self.output_name_to_node()
nodes_to_remove = []
attention_count = 0
count = 0
reshape_nodes = self.get_nodes_by_op_type("Reshape")
for reshape_node in reshape_nodes:
parent = self.get_parent(reshape_node, 0)
if parent is None or parent.op_type == "Reshape":
reshape_node.input[0] = parent.input[0]
count += 1
if count > 0:
logger.info(f"Skip consequent Reshape count: {count}")
def fuse_embedding(self, node, output_name_to_node):
assert node.op_type == 'LayerNormalization'
pos_embed_path2 = self.match_parent_path(
logger.debug(f"start fusing embedding from node with output={node.output[0]}...")
word_embed_path = self.match_parent_path(
node,
['Add', 'Add', 'Gather'],
[ 0, 0, 0],
output_name_to_node)
if pos_embed_path2 is None:
logger.debug("failed to match pos_embed_path")
return False
skip_node, add_node, gather_node = pos_embed_path2
pos_initializer = self.get_initializer(add_node.input[1])
if pos_initializer is None:
return False
temp = numpy_helper.to_array(pos_initializer)
if len(temp.shape) == 3 and temp.shape[0] == 1:
tensor = numpy_helper.from_array(temp.reshape((temp.shape[1],temp.shape[2])), "position_embedding")
self.add_initializer(tensor)
logger.info("Found position embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape[1:]))
position_embedding = "position_embedding"
else:
logger.info("Failed to find position embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape))
if word_embed_path is None:
logger.debug("failed to match word_embed_path")
return False
skip_node, add_node, gather_node = word_embed_path
word_initializer = self.get_initializer(gather_node.input[0])
if word_initializer is None:
logger.debug("failed to get word initializer")
return False
temp = numpy_helper.to_array(word_initializer)
@ -177,12 +211,50 @@ class BertOnnxModelKeras(BertOnnxModelTF):
logger.info("Failed to find word embedding. name:{}, shape:{}".format(word_initializer.name, temp.shape))
return False
pos_initializer = self.get_initializer(add_node.input[1])
if pos_initializer is not None:
temp = numpy_helper.to_array(pos_initializer)
if len(temp.shape) == 3 and temp.shape[0] == 1:
tensor = numpy_helper.from_array(temp.reshape((temp.shape[1],temp.shape[2])), "position_embedding")
self.add_initializer(tensor)
logger.info("Found position embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape[1:]))
position_embedding = "position_embedding"
else:
logger.info("Failed to find position embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape))
return False
else:
pos_embed_path = self.match_parent_path(
add_node,
['Gather', 'Slice'],
[1, 1],
output_name_to_node)
if pos_embed_path is None:
logger.debug("failed to match pos_embed_path")
return False
pos_gather, pos_slice = pos_embed_path
pos_initializer = self.get_initializer(pos_gather.input[0])
if pos_initializer is None:
logger.debug("failed to get pos initializer")
return False
temp = numpy_helper.to_array(pos_initializer)
if len(temp.shape) == 2:
logger.info("Found word embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape))
position_embedding = pos_initializer.name
else:
logger.info("Failed to find position embedding. name:{}, shape:{}".format(pos_initializer.name, temp.shape))
return False
gather = self.get_parent(skip_node, 1, output_name_to_node)
if gather is None or gather.op_type != "Gather":
logger.debug("failed to get gather")
return False
segment_initializer = self.get_initializer(gather.input[0])
if segment_initializer is None:
logger.debug("failed to get segment initializer")
return False
temp = numpy_helper.to_array(segment_initializer)
@ -207,7 +279,60 @@ class BertOnnxModelKeras(BertOnnxModelTF):
if node.op_type == 'LayerNormalization':
if self.fuse_embedding(node, output_name_to_node):
return
break
def fuse_mask(self):
nodes_to_remove = []
for node in self.nodes():
if node.op_type == 'Mul' and self.has_constant_input(node, -10000):
mask_path = self.match_parent_path(
node,
['Sub', 'Cast', 'Slice', 'Unsqueeze'],
[ 0, 1, 0, 0])
if mask_path is None:
continue
sub_node, cast_node, slice_node, unsqueeze_node = mask_path
mask_input_name = next(iter(self.mask_indice))
if unsqueeze_node.input[0] != mask_input_name:
print("Cast input {} is not mask input{}".format(unsqueeze_node.input[0], mask_input_name))
continue
unsqueeze_added_1 = onnx.helper.make_node(
'Unsqueeze',
inputs=[mask_input_name],
outputs=['mask_fuse_unsqueeze1_output'],
name='Mask_UnSqueeze_1',
axes=[1])
unsqueeze_added_2 = onnx.helper.make_node(
'Unsqueeze',
inputs=['mask_fuse_unsqueeze1_output'],
outputs=['mask_fuse_unsqueeze2_output'],
name='Mask_UnSqueeze_2',
axes=[2])
#self.replace_node_input(cast_node, cast_node.input[0], 'mask_fuse_unsqueeze2_output')
cast_node_2 = onnx.helper.make_node(
'Cast',
inputs=['mask_fuse_unsqueeze2_output'],
outputs=['mask_fuse_cast_output'])
cast_node_2.attribute.extend([onnx.helper.make_attribute("to", 1)])
self.replace_node_input(sub_node, sub_node.input[1], 'mask_fuse_cast_output')
nodes_to_remove.extend([slice_node, unsqueeze_node, cast_node])
self.add_node(unsqueeze_added_1)
self.add_node(unsqueeze_added_2)
self.add_node(cast_node_2)
self.remove_nodes(nodes_to_remove)
# Prune graph is done after removing nodes to remove island nodes.
if len(nodes_to_remove) > 0:
self.prune_graph()
logger.info("Fused mask" if len(nodes_to_remove) > 0 else "Failed to fuse mask")
def remove_extra_reshape(self):
skiplayernorm_nodes = self.get_nodes_by_op_type("SkipLayerNormalization")
reshape_removed = 0
@ -228,7 +353,7 @@ class BertOnnxModelKeras(BertOnnxModelTF):
self.remove_node(reshape_1)
reshape_removed += 3
logger.info(f"Remove {reshape_removed} Reshape nodes.")
return reshape_removed
def remove_extra_reshape_2(self):
skiplayernorm_nodes = self.get_nodes_by_op_type("SkipLayerNormalization")
@ -257,9 +382,97 @@ class BertOnnxModelKeras(BertOnnxModelTF):
reshape_removed += 4
logger.info(f"Remove {reshape_removed} Reshape nodes.")
return reshape_removed
def postprocess(self):
self.remove_extra_reshape()
self.remove_extra_reshape_2()
reshape_removed = self.remove_extra_reshape() + self.remove_extra_reshape_2()
logger.info(f"Remove {reshape_removed} Reshape nodes.")
self.prune_graph()
"""
Fuse Gelu with Erf into one node:
+------------------------------------------+
| |
| v
[root] --> Div -----> Erf --> Add --> Mul -->Mul
(B=1.4142...) (A=1) (A=0.5)
Note that constant input for Add and Mul could be first or second input: like either A=0.5 or B=0.5 is fine.
"""
def fuse_gelu_with_elf(self, gelu_op_name):
input_name_to_nodes = self.input_name_to_nodes()
output_name_to_node = self.output_name_to_node()
nodes_to_remove = []
nodes_to_add = []
for node in self.get_nodes_by_op_type('Erf'):
erf_node = node
if erf_node.output[0] not in input_name_to_nodes:
continue
children = input_name_to_nodes[erf_node.output[0]]
if len(children) != 1 or children[0].op_type != 'Add':
continue
add_after_erf = children[0]
if not self.has_constant_input(add_after_erf, 1):
continue
if add_after_erf.output[0] not in input_name_to_nodes:
continue
children = input_name_to_nodes[add_after_erf.output[0]]
if len(children) != 1 or children[0].op_type != 'Mul':
continue
mul_after_erf = children[0]
if not self.has_constant_input(mul_after_erf, 0.5):
continue
if mul_after_erf.output[0] not in input_name_to_nodes:
continue
children = input_name_to_nodes[mul_after_erf.output[0]]
if len(children) != 1 or children[0].op_type != 'Mul':
continue
mul = children[0]
div = self.match_parent(erf_node, 'Div', 0, output_name_to_node)
if div is None:
continue
sqrt_node = None
if self.find_constant_input(div, 1.4142, delta=0.001) != 1:
sqrt_node = self.match_parent(div, 'Sqrt', 1, output_name_to_node)
if sqrt_node is None:
continue
if not self.has_constant_input(sqrt_node, 2.0):
continue
root_node = self.get_parent(div, 0, output_name_to_node)
if root_node is None:
continue
if root_node.output[0] not in mul.input:
continue
subgraph_nodes = [div, erf_node, add_after_erf, mul_after_erf, mul]
if sqrt_node:
subgraph_nodes.append(sqrt_node)
if not self.is_safe_to_fuse_nodes(subgraph_nodes, [mul.output[0]], input_name_to_nodes, output_name_to_node):
continue
nodes_to_remove.extend(subgraph_nodes)
gelu_node = onnx.helper.make_node(gelu_op_name,
inputs=[root_node.output[0]],
outputs=[mul.output[0]])
gelu_node.domain = "com.microsoft"
nodes_to_add.append(gelu_node)
self.remove_nodes(nodes_to_remove)
self.add_nodes(nodes_to_add)
if len(nodes_to_add) > 0:
logger.info("Fused {} count:{}".format('FastGelu (approximation)' if gelu_op_name == 'FastGelu' else 'Gelu', len(nodes_to_add)))
else:
super().fuse_gelu_with_elf(gelu_op_name)

View file

@ -119,3 +119,4 @@ python bert_perf_test.py --model optimized_model_gpu.onnx --batch_size 1 --seque
```
After test is finished, a file like perf_results_CPU_B1_S128_<date_time>.txt or perf_results_GPU_B1_S128_<date_time>.txt will be output to the model directory.

View file

@ -92,10 +92,10 @@ def parse_arguments():
help="Model type selected in the list: " + ", ".join(MODEL_CLASSES.keys()))
parser.add_argument('--num_heads', required=False, type=int, default=12,
help="number of attention heads")
help="number of attention heads. 12 for bert-base model and 16 for bert-large")
parser.add_argument('--hidden_size', required=False, type=int, default=768,
help="bert model hidden size. 768 for base model and 1024 for large")
help="bert model hidden size. 768 for bert-base model and 1024 for bert-large")
parser.add_argument('--sequence_length', required=False, type=int, default=128,
help="max sequence length")

View file

@ -208,4 +208,4 @@ def main():
print("Test summary is saved to", summary_file)
if __name__ == "__main__":
main()
main()

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python
# coding: utf-8
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
@ -23,7 +23,8 @@ BERT_TEST_MODELS = {
"bert_pytorch_0": 'test_data\\bert_squad_pytorch1.4_opset11\\BertForQuestionAnswering_0.onnx',
"bert_pytorch_1": 'test_data\\bert_squad_pytorch1.4_opset11\\BertForQuestionAnswering_1.onnx',
"bert_squad_pytorch1.4_opset10_fp32": 'test_data\\bert_squad_pytorch1.4_opset10_fp32\\BertForQuestionAnswering.onnx',
"bert_keras_0": 'test_data\\bert_mrpc_tensorflow2.1_opset10\\TFBertForSequenceClassification_1.onnx'
"bert_keras_0": 'test_data\\bert_mrpc_tensorflow2.1_opset10\\TFBertForSequenceClassification_1.onnx',
"bert_keras_squad": 'test_data\\bert_squad_tensorflow2.1_keras2onnx_opset11\\TFBertForQuestionAnswering.onnx'
}
class TestBertOptimization(unittest.TestCase):
@ -181,5 +182,14 @@ class TestBertOptimization(unittest.TestCase):
}
self.verify_node_count(bert_model, expected_node_count)
def test_keras_squad_model_cpu(self):
input = BERT_TEST_MODELS['bert_keras_squad']
bert_model = optimize_model(input, 'bert_keras', gpu_only=False,
num_heads=2, hidden_size=8, sequence_length=7,
input_int32=False, float16=False)
self.assertTrue(bert_model.is_fully_optimized())
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,291 @@
#-------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
#--------------------------------------------------------------------------
"""
Convert a Bert large model exported by Keras2Onnx to a tiny model for test purpose.
The input model is generated like the following (need install keras2onnx from source):
import numpy
import keras2onnx
from transformers import (TFBertForQuestionAnswering, BertTokenizer)
tokenizer = BertTokenizer.from_pretrained'bert-large-uncased-whole-word-masking-finetuned-squad', do_lower_case=True, cache_dir=cache_dir)
model = TFBertForQuestionAnswering.from_pretrained(model_name_or_path, cache_dir=cache_dir)
question, text = "What is ONNX Runtime?", "ONNX Runtime is a performance-focused inference engine for ONNX models."
inputs = tokenizer.encode_plus(question, text, add_special_tokens=True, return_tensors='tf')
output_model_path = os.path.join(output_dir, 'keras_{}.onnx'.format(model_name_or_path))
if not os.path.exists(output_model_path):
model.predict(inputs)
onnx_model = keras2onnx.convert_keras(model, model.name)
keras2onnx.save_model(onnx_model, output_model_path)
"""
import onnx
import onnx.utils
import sys
import argparse
import numpy as np
from onnx import ModelProto, TensorProto, numpy_helper
from bert_model_optimization import OnnxModel
import os
import onnxruntime
import random
from pathlib import Path
import timeit
DICT_SIZE = 20
SEQ_LEN = 7
""" This class creates a tiny bert model for test purpose. """
class TinyBertOnnxModel(OnnxModel):
def __init__(self, model, verbose):
super(TinyBertOnnxModel, self).__init__(model, verbose)
self.resize_model()
def resize_weight(self, initializer_name, target_shape):
weight = self.get_initializer(initializer_name)
w = numpy_helper.to_array(weight)
target_w = w
if len(target_shape) == 1:
target_w = w[:target_shape[0]]
elif len(target_shape) == 2:
target_w = w[:target_shape[0],:target_shape[1]]
elif len(target_shape) == 3:
target_w = w[:target_shape[0], :target_shape[1], :target_shape[2]]
else:
print("at most 3 dimensions")
tensor = onnx.helper.make_tensor(
name=initializer_name + '_resize',
data_type=TensorProto.FLOAT,
dims=target_shape,
vals=target_w.flatten().tolist())
return tensor
def resize_model(self):
graph = self.model.graph
initializers = graph.initializer
# parameters of input base model.
old_parameters = {
"seq_len" : 26,
"hidden_size": 1024,
"num_heads": 16,
"size_per_head": 64,
"word_dict_size": [28996, 30522], # list of supported dictionary size.
"max_word_position": 512
}
# parameters of output tiny model.
new_parameters = {
"seq_len" : SEQ_LEN,
"hidden_size": 8,
"num_heads": 2,
"size_per_head": 4,
"word_dict_size": DICT_SIZE,
"max_word_position": 10
}
for input in graph.input:
if (input.type.tensor_type.shape.dim[1].dim_value == old_parameters["seq_len"]):
print("input", input.name, input.type.tensor_type.shape)
input.type.tensor_type.shape.dim[1].dim_value = new_parameters["seq_len"]
print("=>", input.type.tensor_type.shape)
reshapes = {}
for initializer in initializers:
tensor = numpy_helper.to_array(initializer)
dtype = np.float32 if initializer.data_type == 1 else np.int32
if len(tensor.shape) == 1 and tensor.shape[0] == 1:
if tensor == old_parameters["num_heads"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["num_heads"], "=>[", new_parameters["num_heads"], "]")
initializer.CopyFrom(numpy_helper.from_array(np.asarray([new_parameters["num_heads"]], dtype=dtype), initializer.name))
elif tensor == old_parameters["seq_len"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["seq_len"], "=>[", new_parameters["seq_len"], "]")
initializer.CopyFrom(numpy_helper.from_array(np.asarray([new_parameters["seq_len"]], dtype=dtype), initializer.name))
elif tensor == old_parameters["size_per_head"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["size_per_head"], "=>[", new_parameters["size_per_head"], "]")
initializer.CopyFrom(numpy_helper.from_array(np.asarray([new_parameters["size_per_head"]], dtype=dtype), initializer.name))
elif tensor == old_parameters["hidden_size"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["hidden_size"], "=>[", new_parameters["hidden_size"], "]")
initializer.CopyFrom(numpy_helper.from_array(np.asarray([new_parameters["hidden_size"]], dtype=dtype), initializer.name))
elif tensor == 4 * old_parameters["hidden_size"]:
print("initializer type={}".format(initializer.data_type), initializer.name, 4 * old_parameters["hidden_size"], "=>[", 4 * new_parameters["hidden_size"], "]")
initializer.CopyFrom(numpy_helper.from_array(np.asarray([4 * new_parameters["hidden_size"]], dtype=dtype), initializer.name))
elif len(tensor.shape) == 0:
if tensor == old_parameters["num_heads"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["num_heads"], "=>", new_parameters["num_heads"])
initializer.CopyFrom(numpy_helper.from_array(np.asarray(new_parameters["num_heads"], dtype=dtype), initializer.name))
elif tensor == old_parameters["seq_len"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["seq_len"], "=>", new_parameters["seq_len"])
initializer.CopyFrom(numpy_helper.from_array(np.asarray(new_parameters["seq_len"], dtype=dtype), initializer.name))
elif tensor == old_parameters["size_per_head"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["size_per_head"], "=>", new_parameters["size_per_head"])
initializer.CopyFrom(numpy_helper.from_array(np.asarray(new_parameters["size_per_head"], dtype=dtype), initializer.name))
elif tensor == old_parameters["hidden_size"]:
print("initializer type={}".format(initializer.data_type), initializer.name, old_parameters["hidden_size"], "=>", new_parameters["hidden_size"])
initializer.CopyFrom(numpy_helper.from_array(np.asarray(new_parameters["hidden_size"], dtype=dtype), initializer.name))
elif tensor == 4 * old_parameters["hidden_size"]:
print("initializer type={}".format(initializer.data_type), initializer.name, 4 * old_parameters["hidden_size"], "=>", 4 * new_parameters["hidden_size"])
initializer.CopyFrom(numpy_helper.from_array(np.asarray(4 * new_parameters["hidden_size"], dtype=dtype), initializer.name))
elif tensor == 1.0 / np.sqrt(old_parameters["size_per_head"]):
print("initializer type={}".format(initializer.data_type), initializer.name, 1.0 / np.sqrt(old_parameters["size_per_head"]), "=>", 1.0 / np.sqrt(new_parameters["size_per_head"]))
initializer.CopyFrom(numpy_helper.from_array(np.asarray(1.0 / np.sqrt(new_parameters["size_per_head"]), dtype=dtype), initializer.name))
new_shape=[]
shape_changed = False
for dim in tensor.shape:
if (dim == old_parameters["hidden_size"]):
new_shape.append(new_parameters["hidden_size"])
shape_changed = True
elif (dim == 4 * old_parameters["hidden_size"]):
new_shape.append(4 * new_parameters["hidden_size"])
shape_changed = True
elif (dim in old_parameters["word_dict_size"]):
new_shape.append(new_parameters["word_dict_size"])
shape_changed = True
elif (dim == old_parameters["max_word_position"]):
new_shape.append(new_parameters["max_word_position"])
shape_changed = True
else:
new_shape.append(dim)
if shape_changed:
reshapes[initializer.name] = new_shape
print("initializer", initializer.name, tensor.shape, "=>", new_shape)
for initializer_name in reshapes:
self.replace_input_of_all_nodes(initializer_name, initializer_name + '_resize')
tensor = self.resize_weight(initializer_name, reshapes[initializer_name])
self.model.graph.initializer.extend([tensor])
self.use_dynamic_axes()
def use_dynamic_axes(self, dynamic_batch_dim='batch_size', seq_len=7):
"""
Update input and output shape to use dynamic axes.
"""
dynamic_batch_inputs = {}
for input in self.model.graph.input:
dim_proto = input.type.tensor_type.shape.dim[0]
dim_proto.dim_param = dynamic_batch_dim
dim_proto = input.type.tensor_type.shape.dim[1]
dim_proto.dim_value = seq_len
for output in self.model.graph.output:
dim_proto = output.type.tensor_type.shape.dim[0]
dim_proto.dim_param = dynamic_batch_dim
dim_proto = output.type.tensor_type.shape.dim[1]
dim_proto.dim_value = seq_len
def generate_test_data(onnx_file, output_path, batch_size, sequence_length, use_cpu = True, input_tensor_only=False, dictionary_size=DICT_SIZE, test_cases=3):
input_data_type = np.int32
for test_case in range(test_cases):
input_1 = np.random.randint(dictionary_size, size=(batch_size, sequence_length), dtype=input_data_type)
tensor_1 = numpy_helper.from_array(input_1, 'input_ids')
actual_seq_len = random.randint(sequence_length - 3, sequence_length)
input_2 = np.zeros((batch_size, sequence_length), dtype=input_data_type)
temp = np.ones((batch_size, actual_seq_len), dtype=input_data_type)
input_2[:temp.shape[0],:temp.shape[1]]=temp
tensor_2 = numpy_helper.from_array(input_2, 'attention_mask')
input_3 = np.zeros((batch_size, sequence_length), dtype=input_data_type)
tensor_3 = numpy_helper.from_array(input_3, 'token_type_ids')
path = os.path.join(output_path, 'test_data_set_' + str(test_case))
try:
os.mkdir(path)
except OSError:
print ("Creation of the directory %s failed" % path)
else:
print ("Successfully created the directory %s " % path)
if input_tensor_only:
return
sess_options = onnxruntime.SessionOptions()
sess_options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_DISABLE_ALL
sess = onnxruntime.InferenceSession(onnx_file, sess_options, providers=['CPUExecutionProvider'])
input1_name = sess.get_inputs()[0].name
output_names = [output.name for output in sess.get_outputs()]
inputs = {'input_ids': input_1, 'attention_mask': input_2, 'token_type_ids': input_3}
print ("inputs", inputs)
result=sess.run(output_names, inputs)
with open(os.path.join(path, 'input_{}.pb'.format(0)), 'wb') as f:
f.write(tensor_1.SerializeToString())
with open(os.path.join(path, 'input_{}.pb'.format(1)), 'wb') as f:
f.write(tensor_2.SerializeToString())
with open(os.path.join(path, 'input_{}.pb'.format(2)), 'wb') as f:
f.write(tensor_3.SerializeToString())
for i, output_name in enumerate(output_names):
tensor_result = numpy_helper.from_array(np.asarray(result[i]).reshape((batch_size, sequence_length)), output_names[i])
with open(os.path.join(path, 'output_{}.pb'.format(i)), 'wb') as f:
f.write(tensor_result.SerializeToString())
start_time = timeit.default_timer()
sess_options.graph_optimization_level = onnxruntime.GraphOptimizationLevel.ORT_ENABLE_EXTENDED
path_prefix = onnx_file[:-5] #remove .onnx suffix
if use_cpu:
sess_options.optimized_model_filepath = path_prefix + "_optimized_cpu.onnx"
else:
sess_options.optimized_model_filepath = path_prefix + "_optimized_gpu.onnx"
session = onnxruntime.InferenceSession(onnx_file, sess_options)
if use_cpu:
session.set_providers(['CPUExecutionProvider']) # use cpu
else:
if 'CUDAExecutionProvider' not in session.get_providers():
print("Warning: GPU not found")
continue
outputs = session.run(None, inputs)
evalTime = timeit.default_timer() - start_time
if outputs[0].tolist() != result[0].tolist():
print("Error: not same result after optimization. use_cpu={}, no_opt_output={}, opt_output={}".format(use_cpu, result[0].tolist(), outputs[1].tolist()))
print("** Evaluation done in total {} secs".format(evalTime))
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--input', required=True, type=str)
parser.add_argument('--output', required=True, type=str)
parser.add_argument('--float16', required=False, action='store_true')
parser.set_defaults(float16=False)
args = parser.parse_args()
model = ModelProto()
with open(args.input, "rb") as f:
model.ParseFromString(f.read())
bert_model = TinyBertOnnxModel(model, False)
if args.float16:
bert_model.convert_model_float32_to_float16()
bert_model.update_graph()
bert_model.remove_unused_constant()
print("opset verion", bert_model.model.opset_import[0].version)
with open(args.output, "wb") as out:
out.write(bert_model.model.SerializeToString())
p = Path(args.output)
data_path = p.parent
batch_size = 1
sequence_length = SEQ_LEN
generate_test_data(args.output, data_path, batch_size, sequence_length, use_cpu=not args.float16)
if __name__ == "__main__":
main()

View file

@ -0,0 +1 @@
Boutput_1J23= {<7B>=?є=N<><4E>=(,<2C>=\<5C><>=`<60><><

View file

@ -0,0 +1 @@
Boutput_2JÁŸ¼+2ª¼5à³¼`Çß¼…¹½2RÕ¼o‡;