Add ONNX Training Post-Passes to Front-End - Cont (#4041)

* Add ONNX postpasses

* add flag + add bert test from onnx file

* address PR comments

* fix typo

* fix rebase

* address comments

* Fix test failures

* add new pass for expand for new pt version, add comments

* fix rebase

Co-authored-by: lahaidar <lahaidar@OrtDevTest2v100.af05slrtruoetgaxwwjv5nsq5e.px.internal.cloudapp.net>
This commit is contained in:
Bowen Bao 2020-06-15 10:33:26 -07:00 committed by GitHub
parent 0b5bbb16b8
commit b08771f00e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 704 additions and 24 deletions

View file

@ -727,7 +727,7 @@ class TestOrtTrainer(unittest.TestCase):
model_desc = ModelDescription([input_desc, label_desc], [loss_desc, output_desc])
def loss_fn(x, label):
return F.nll_loss(F.log_softmax(x, dim=1), label)
def get_lr_this_step(global_step):
learningRate = 0.02
return torch.tensor([learningRate])

View file

@ -8,14 +8,15 @@ from onnx import helper
import torch
import torch.nn
import torch.onnx
import onnxruntime as ort
import onnxruntime.capi.postprocess as postprocess
from distutils.version import LooseVersion
import warnings
import onnxruntime as ort
from .checkpointing_utils import list_checkpoint_files, get_checkpoint_name, CombineZeroCheckpoint
import onnxruntime.capi.pt_patch
DEFAULT_OPSET_VERSION = 10
DEFAULT_OPSET_VERSION = 12
class IODescription():
def __init__(self, name, shape, dtype=None, num_classes=None):
@ -204,7 +205,7 @@ def wrap_for_input_match(model, loss_fn, input_names):
if len(sig_loss.parameters) != 2:
raise RuntimeError("loss function should take two arguments - predict and label.")
# label shall be the second input to loss_fn.
# label shall be the second input to loss_fn.
ordered_list_keys = [*ordered_list_keys, list(sig_loss.parameters.keys())[1]]
class model_loss_cls(torch.nn.Module):
@ -274,7 +275,7 @@ def wrap_for_input_match(model, loss_fn, input_names):
return model
def convert_model_loss_fn_to_onnx(model, loss_fn, model_desc, device, inputs, opset_version=DEFAULT_OPSET_VERSION):
def convert_model_loss_fn_to_onnx(model, loss_fn, model_desc, device, inputs, opset_version=DEFAULT_OPSET_VERSION, _enable_internal_postprocess=True):
# example: {input0:{0:'batch'}, input1:{0:'batch'}}
dynamic_axes = {}
for input in model_desc.inputs_:
@ -323,17 +324,20 @@ def convert_model_loss_fn_to_onnx(model, loss_fn, model_desc, device, inputs, op
# Other export options to use(this is for backward compatibility).
other_export_options = {}
other_export_options['training'] = True
# This option was added after 1.4 release.
if LooseVersion(torch.__version__) > LooseVersion('1.4.0'):
other_export_options['enable_onnx_checker'] = False
# This option was added after 1.6 release.
if LooseVersion(torch.__version__) >= LooseVersion('1.6.0'):
other_export_options['training'] = torch.onnx.TrainingMode.TRAINING
torch.onnx._export(model, tuple(sample_inputs), f,
input_names=input_names,
output_names=output_names,
opset_version=opset_version,
dynamic_axes=dynamic_axes,
training=True,
_retain_param_name=True,
example_outputs=tuple(sample_outputs),
do_constant_folding=False,
@ -360,7 +364,8 @@ def convert_model_loss_fn_to_onnx(model, loss_fn, model_desc, device, inputs, op
"Initializer names do not match between PyTorch model and ONNX model, " \
"please report a bug to ONNX Runtime."
onnx_model = FuseSofmaxNLLToSoftmaxCE(onnx_model)
if _enable_internal_postprocess:
onnx_model = postprocess.run_postprocess(onnx_model)
return onnx_model
@ -518,10 +523,11 @@ def load_checkpoint(model, checkpoint_dir, checkpoint_prefix="ORT_checkpoint", s
class ORTTrainer():
def __init__(self, model, loss_fn, model_desc, training_optimizer_name, map_optimizer_attributes,
learning_rate_description, device, gradient_accumulation_steps=1, postprocess_model=None,
learning_rate_description, device, gradient_accumulation_steps=1,
world_rank=0, world_size=1, use_mixed_precision=False, allreduce_post_accumulation=False,
global_step=0, get_lr_this_step=None, loss_scaler=None, partition_optimizer=False,
enable_grad_norm_clip=True, frozen_weights=[], _opset_version=DEFAULT_OPSET_VERSION):
enable_grad_norm_clip=True, frozen_weights=[], _opset_version=DEFAULT_OPSET_VERSION,
_enable_internal_postprocess=True, _extra_postprocess=None):
super(ORTTrainer, self).__init__()
"""
Initialize ORTTrainer.
@ -586,6 +592,10 @@ class ORTTrainer():
Defaults to True.
frozen_weights: list of model parameters to be frozen (not trained).
Defaults to [].
_enable_internal_postprocess: whether to run or not the internal postprocesses.
Defaults to True
_extra_postprocess: a callable to postprocess the ONNX model that is converted from PyTorch.
Defaults to None
"""
warnings.warn('DISCLAIMER: This is an early version of an experimental training API and it is subject to change. DO NOT create production applications with it')
self.is_train = True
@ -621,7 +631,7 @@ class ORTTrainer():
# we use self.global_step_ to count optimizations being performed.
# it is used to calculate learning rate if self.get_lr_this_step_ is provided.
self.global_step_ = global_step
self.post_process_model_fn_ = postprocess_model
self._extra_postprocess = _extra_postprocess
self.get_lr_this_step_ = get_lr_this_step
self.loss_scaler_ = loss_scaler
@ -636,6 +646,7 @@ class ORTTrainer():
self.frozen_weights_ = frozen_weights
self.opset_version_ = _opset_version
self.state_dict_ = None
self._enable_internal_postprocess = _enable_internal_postprocess
# use this special string to workaround a corner case that external loss_scale is passed into train_step as kwargs.
# see prepare_input_and_fetches for more details.
@ -647,6 +658,12 @@ class ORTTrainer():
if self.onnx_model_ is None:
return
if self._enable_internal_postprocess:
self._onnx_model_ = postprocess.run_postprocess(self.onnx_model_)
if self._extra_postprocess:
self._extra_postprocess(self.onnx_model_)
self._verify_fully_optimized_model(self.onnx_model_)
self.session, self.train_io_binding, self.eval_io_binding, self.output_name, _, self.output_types = \
create_ort_training_session_with_optimizer(
@ -702,10 +719,10 @@ class ORTTrainer():
torch_buffers = list(dict(self.torch_model_.named_buffers()).keys())
self.frozen_weights_ = self.frozen_weights_ + torch_buffers
self.onnx_model_ = convert_model_loss_fn_to_onnx(
self.torch_model_, self.loss_fn_, self.model_desc_, torch.device('cpu'), inputs, opset_version=self.opset_version_)
self.torch_model_, self.loss_fn_, self.model_desc_, torch.device('cpu'), inputs, opset_version=self.opset_version_, _enable_internal_postprocess=self._enable_internal_postprocess)
if self.post_process_model_fn_:
self.post_process_model_fn_(self.onnx_model_)
if self._extra_postprocess:
self._extra_postprocess(self.onnx_model_)
self._init_session()

View file

@ -0,0 +1,455 @@
import sys
import os.path
from onnx import *
import onnx
import numpy as np
import struct
from onnx import helper
from onnx import numpy_helper
def run_postprocess(model):
# this post pass is not required for pytorch >= 1.5
# where add_node_name in torch.onnx.export is default to True
model = add_name(model)
# this post pass is not required for pytorch > 1.6
model = fuse_softmaxNLL_to_softmaxCE(model)
model = layer_norm_transform(model)
model = fix_expand_shape(model)
model = fix_expand_shape_pt_1_5(model)
return model
def find_input_node(model, arg):
result = []
for node in model.graph.node:
for output in node.output:
if output == arg:
result.append(node)
return result[0] if len(result)== 1 else None
def find_output_node(model, arg):
result = []
for node in model.graph.node:
for input in node.input:
if input == arg:
result.append(node)
return result[0] if len(result) == 1 else result
def add_name(model):
i = 0
for node in model.graph.node:
node.name = '%s_%d' %(node.op_type, i)
i += 1
return model
# Expand Shape PostProcess
def fix_expand_shape(model):
expand_nodes = [n for n in model.graph.node if n.op_type == 'Expand']
model_inputs_names = [i.name for i in model.graph.input]
for expand_node in expand_nodes:
shape = find_input_node(model, expand_node.input[1])
if shape.op_type == 'Shape':
# an expand subgraph
# Input Input2
# | |
# | Shape
# | |
# |__ __|
# | |
# Expand
# |
# output
#
# Only if Input2 is one of the model inputs, assign Input2's shape to output of expand.
shape_input_name = shape.input[0]
if shape_input_name in model_inputs_names:
index = model_inputs_names.index(shape_input_name)
expand_out = model.graph.value_info.add()
expand_out.name = expand_node.output[0]
expand_out.type.CopyFrom(model.graph.input[index].type)
return model
def fix_expand_shape_pt_1_5(model):
# expand subgraph
# Constant
# +
# ConstantOfShape
# | + |
# | + |
# (Reshape subgraph) Mul |
# |___ _________| |
# + | | |
# + Equal |
# +++++|++++++++++++++|++
# |____________ | +
# | | +
# (subgraph) Where
# | |
# |_____ ___________|
# | |
# Expand
# |
# output
#
# where the Reshape subgraph is
#
# Input
# | |
# | |___________________
# | |
# Shape Constant Shape Constant
# | ______| | ______|
# | | | |
# Gather Gather
# | |
# Unsqueeze Unsqueeze
# | |
# | ..Number of dims.. |
# | _________________|
# |...|
# Concat Constant
# | |
# |______ __________________|
# | |
# Reshape
# |
# output
#
# This pass will copy Input's shape to the output of Expand.
expand_nodes = [n for n in model.graph.node if n.op_type == 'Expand']
model_inputs_names = [i.name for i in model.graph.input]
for expand_node in expand_nodes:
n_where = find_input_node(model, expand_node.input[1])
if n_where.op_type != 'Where':
continue
n_equal = find_input_node(model, n_where.input[0])
n_cos = find_input_node(model, n_where.input[1])
n_reshape = find_input_node(model, n_where.input[2])
if n_equal.op_type != 'Equal' or n_cos.op_type != 'ConstantOfShape' or n_reshape.op_type != 'Reshape':
continue
n_reshape_e = find_input_node(model, n_equal.input[0])
n_mul = find_input_node(model, n_equal.input[1])
if n_reshape_e != n_reshape or n_mul.op_type != 'Mul':
continue
n_cos_m = find_input_node(model, n_mul.input[0])
n_constant = find_input_node(model, n_mul.input[1])
if n_cos_m != n_cos or n_constant.op_type != 'Constant':
continue
n_concat = find_input_node(model, n_reshape.input[0])
n_constant_r = find_input_node(model, n_reshape.input[1])
if n_concat.op_type != 'Concat' or n_constant_r.op_type != 'Constant':
continue
n_input_candidates = []
for concat_in in n_concat.input:
n_unsqueeze = find_input_node(model, concat_in)
if n_unsqueeze.op_type != 'Unsqueeze':
break
n_gather = find_input_node(model, n_unsqueeze.input[0])
if n_gather.op_type != 'Gather':
break
n_shape = find_input_node(model, n_gather.input[0])
n_constant_g = find_input_node(model, n_gather.input[1])
if n_shape.op_type != 'Shape' or n_constant_g.op_type != 'Constant':
break
n_input = n_shape.input[0]
if not n_input in model_inputs_names:
break
n_input_candidates.append(n_input)
if not n_input_candidates or not all(elem == n_input_candidates[0] for elem in n_input_candidates):
continue
index = model_inputs_names.index(n_input_candidates[0])
expand_out = model.graph.value_info.add()
expand_out.name = expand_node.output[0]
expand_out.type.CopyFrom(model.graph.input[index].type)
return model
# LayerNorm PostProcess
def find_nodes(graph, op_type):
nodes = []
for node in graph.node:
if node.op_type == op_type:
nodes.append(node)
return nodes
def is_type(node, op_type):
if node is None or isinstance(node, list):
return False
return node.op_type == op_type
def add_const(model, name, output, t_value = None, f_value = None):
const_node = model.graph.node.add()
const_node.op_type = 'Constant'
const_node.name = name
const_node.output.extend([output])
attr = const_node.attribute.add()
attr.name = 'value'
if t_value is not None:
attr.type = 4
attr.t.CopyFrom(t_value)
else:
attr.type = 1
attr.f = f_value
return const_node
def layer_norm_transform(model):
# Converting below subgraph
#
# input
# |
# ReduceMean
# __|_____
# | |
# Sub Sub
# | |
# | (optional) Cast
# | |
# | Pow
# | |
# | (optional) Cast
# | |
# | ReduceMean
# | |
# | Add
# | |
# |__ __Sqrt
# | |
# Div (weight)
# | |
# | _____|
# | |
# Mul (bias)
# | |
# | _____|
# | |
# Add
# |
# output
#
# to the below subgraph
#
# input (weight) (bias)
# | | |
# | _______| |
# | | ________________|
# | | |
# LayerNormalization
# |
# output
graph = model.graph
nodes_ReduceMean = find_nodes(graph, "ReduceMean")
id = 0
layer_norm_nodes = []
remove_nodes = []
for reduce_mean in nodes_ReduceMean:
# check that reduce_mean output is Sub
sub = find_output_node(model, reduce_mean.output[0])
if not is_type(sub, "Sub"):
continue
# check that sub output[0] is Div and output[1] is Pow
pow, div = find_output_node(model, sub.output[0])
if is_type(pow, "Cast"):
# During an update in PyTorch, Cast nodes are inserted between Sub and Pow.
remove_nodes += [pow]
pow = find_output_node(model, pow.output[0])
if not is_type(pow, "Pow"):
continue
cast_pow = find_input_node(model, pow.input[1])
if not is_type(cast_pow, "Cast"):
continue
remove_nodes += [cast_pow]
if not is_type(div, "Div") or not is_type(pow, "Pow"):
continue
# check that pow ouput is ReduceMean
reduce_mean2 = find_output_node(model, pow.output[0])
if not is_type(reduce_mean2, "ReduceMean"):
continue
# check that reduce_mean2 output is Add
add = find_output_node(model, reduce_mean2.output[0])
if not is_type(add, "Add"):
continue
# check that add output is Sqrt
sqrt = find_output_node(model, add.output[0])
if not is_type(sqrt, "Sqrt"):
continue
# check that sqrt output is div
if div != find_output_node(model, sqrt.output[0]):
continue
# check if div output is Mul
optional_mul = find_output_node(model, div.output[0])
if not is_type(optional_mul, "Mul"):
optional_mul = None
continue # default bias and weight not supported
# check if mul output is Add
if optional_mul is not None:
optional_add = find_output_node(model, optional_mul.output[0])
else:
optional_add = find_output_node(model, div.output[0])
if not is_type(optional_add, "Add"):
optional_add = None
continue # default bias and weight not supported
# add nodes to remove_nodes
remove_nodes.extend([reduce_mean, sub, div, pow, reduce_mean2, add, sqrt])
# create LayerNorm node
layer_norm_input = []
layer_norm_output = []
layer_norm_input.append(reduce_mean.input[0])
if optional_mul is not None:
remove_nodes.append(optional_mul)
weight = optional_mul.input[1]
layer_norm_input.append(weight)
if optional_add is not None:
remove_nodes.append(optional_add)
bias = optional_add.input[1]
layer_norm_input.append(bias)
if optional_add is not None:
layer_norm_output.append(optional_add.output[0])
elif optional_mul is not None:
layer_norm_output.append(optional_mul.output[0])
else:
layer_norm_output.append(div.output[0])
layer_norm_output.append('saved_mean_' + str(id))
layer_norm_output.append('saved_inv_std_var_' + str(id))
epsilon_node = find_input_node(model, add.input[1])
epsilon = epsilon_node.attribute[0].t.raw_data
epsilon = struct.unpack('f', epsilon)[0]
layer_norm = helper.make_node("LayerNormalization",
layer_norm_input,
layer_norm_output,
"LayerNormalization_" + str(id),
None,
axis = reduce_mean.attribute[0].ints[0],
epsilon = epsilon)
layer_norm_nodes.append(layer_norm)
id += 1
# remove orphan constant nodes
for constant in graph.node:
if constant.op_type == "Constant" and constant not in remove_nodes:
is_orphan = True
for out_name in constant.output:
out = find_output_node(model, out_name)
if out not in remove_nodes:
is_orphan = False
if is_orphan:
remove_nodes.append(constant)
all_nodes = []
for node in graph.node:
if node not in remove_nodes:
all_nodes.append(node)
for node in layer_norm_nodes:
all_nodes.append(node)
graph.ClearField("node")
graph.node.extend(all_nodes)
return model
# Fuse SoftmaxCrossEntropy
def fuse_softmaxNLL_to_softmaxCE(onnx_model):
# Converting below subgraph
#
# (subgraph)
# |
# LogSoftmax (target) (optional weight)
# | | |
# nll_loss/NegativeLogLikelihoodLoss
# |
# output
#
# to the following
#
# (subgraph) (target) (optional weight)
# | | _____|
# | | |
# SparseSoftmaxCrossEntropy
# |
# output
nll_count = 0
while True:
nll_count = nll_count + 1
nll_loss_node = None
nll_loss_node_index = 0
for nll_loss_node_index, node in enumerate(onnx_model.graph.node):
if node.op_type == "nll_loss" or node.op_type == "NegativeLogLikelihoodLoss":
nll_loss_node = node
break
if nll_loss_node is None:
break
softmax_node = None
softmax_node_index = 0
label_input_name = None
weight_input_name = None
for softmax_node_index, node in enumerate(onnx_model.graph.node):
if node.op_type == "LogSoftmax":
# has to be connected to nll_loss
if len(nll_loss_node.input) > 2:
weight_input_name = nll_loss_node.input[2]
if node.output[0] == nll_loss_node.input[0]:
softmax_node = node
label_input_name = nll_loss_node.input[1]
break
elif node.output[0] == nll_loss_node.input[1]:
softmax_node = node
label_input_name = nll_loss_node.input[0]
break
else:
if softmax_node is not None:
break
if softmax_node is None:
break
# delete nll_loss and LogSoftmax nodes in order
if nll_loss_node_index < softmax_node_index:
del onnx_model.graph.node[softmax_node_index]
del onnx_model.graph.node[nll_loss_node_index]
else:
del onnx_model.graph.node[nll_loss_node_index]
del onnx_model.graph.node[softmax_node_index]
probability_output_name = softmax_node.output[0]
node = onnx_model.graph.node.add()
inputs = [softmax_node.input[0], label_input_name, weight_input_name] if weight_input_name else [softmax_node.input[0], label_input_name]
node.CopyFrom(onnx.helper.make_node("SparseSoftmaxCrossEntropy", inputs,
[nll_loss_node.output[0], probability_output_name],
"nll_loss_node_" + str(nll_count)))
return onnx_model

View file

@ -0,0 +1,214 @@
import unittest
import pytest
import sys
import os
import copy
from numpy.testing import assert_allclose, assert_array_equal
import onnx
import torch
import torch.nn as nn
import torch.nn.functional as F
from orttraining_test_utils import map_optimizer_attributes
from orttraining_test_transformers import BertModelTest, BertForPreTraining
from orttraining_test_data_loader import create_ort_test_dataloader
from orttraining_test_bert_postprocess import postprocess_model
import onnxruntime
from onnxruntime.capi.ort_trainer import ORTTrainer, IODescription, ModelDescription, LossScaler, generate_sample
torch.manual_seed(1)
onnxruntime.set_seed(1)
class Test_PostPasses(unittest.TestCase):
def get_onnx_model(self, model, model_desc, inputs, device,
_enable_internal_postprocess=True, _extra_postprocess=None):
lr_desc = IODescription('Learning_Rate', [1,], torch.float32)
model = ORTTrainer(model,
None,
model_desc,
"LambOptimizer",
map_optimizer_attributes,
lr_desc,
device,
world_rank=0,
world_size=1,
_opset_version=12,
_enable_internal_postprocess=_enable_internal_postprocess,
_extra_postprocess=_extra_postprocess)
train_output = model.train_step(*inputs)
return model.onnx_model_
def count_all_nodes(self, model):
return len(model.graph.node)
def count_nodes(self, model, node_type):
count = 0
for node in model.graph.node:
if node.op_type == node_type:
count += 1
return count
def find_nodes(self, model, node_type):
nodes = []
for node in model.graph.node:
if node.op_type == node_type:
nodes.append(node)
return nodes
def get_name(self, name):
if os.path.exists(name):
return name
rel = os.path.join("testdata", name)
if os.path.exists(rel):
return rel
this = os.path.dirname(__file__)
data = os.path.join(this, "..", "..", "..", "..", "onnxruntime", "test", "testdata")
res = os.path.join(data, name)
if os.path.exists(res):
return res
raise FileNotFoundError("Unable to find '{0}' or '{1}' or '{2}'".format(name, rel, res))
def test_layer_norm(self):
class LayerNormNet(nn.Module):
def __init__(self, target):
super(LayerNormNet, self).__init__()
self.ln_1 = nn.LayerNorm(10)
self.loss = nn.CrossEntropyLoss()
self.target = target
def forward(self, x):
output1 = self.ln_1(x)
loss = self.loss(output1, self.target)
return loss, output1
device = torch.device("cpu")
target = torch.ones(20, 10, 10, dtype=torch.int64).to(device)
model = LayerNormNet(target)
input = torch.randn(20, 5, 10, 10, dtype=torch.float32).to(device)
input_desc = IODescription('input', [], "float32")
output0_desc = IODescription('output0', [], "float32")
output1_desc = IODescription('output1', [20, 5, 10, 10], "float32")
model_desc = ModelDescription([input_desc], [output0_desc, output1_desc])
learning_rate = torch.tensor([1.0000000e+00]).to(device)
input_args=[input, learning_rate]
onnx_model = self.get_onnx_model(model, model_desc, input_args, device)
count_layer_norm = self.count_nodes(onnx_model, "LayerNormalization")
count_nodes = self.count_all_nodes(onnx_model)
assert count_layer_norm == 1
assert count_nodes == 3
def test_expand(self):
class ExpandNet(nn.Module):
def __init__(self, target):
super(ExpandNet, self).__init__()
self.loss = nn.CrossEntropyLoss()
self.target = target
self.linear = torch.nn.Linear(2, 2)
def forward(self, x, x1):
output = x.expand_as(x1)
output = self.linear(output)
output = output + output
loss = self.loss(output, self.target)
return loss, output
device = torch.device("cpu")
target = torch.ones(5, 5, 2, dtype=torch.int64).to(device)
model = ExpandNet(target).to(device)
x = torch.randn(5, 3, 1, 2, dtype=torch.float32).to(device)
x1 = torch.randn(5, 3, 5, 2, dtype=torch.float32).to(device)
input0_desc = IODescription('x', [5, 3, 1, 2], "float32")
input1_desc = IODescription('x1', [5, 3, 5, 2], "float32")
output0_desc = IODescription('output0', [], "float32")
output1_desc = IODescription('output1', [5, 3, 5, 2], "float32")
model_desc = ModelDescription([input0_desc, input1_desc], [output0_desc, output1_desc])
learning_rate = torch.tensor([1.0000000e+00]).to(device)
input_args = [x, x1, learning_rate]
onnx_model = self.get_onnx_model(model, model_desc, input_args, device)
# check that expand output has shape
expand_nodes = self.find_nodes(onnx_model, "Expand")
assert len(expand_nodes) == 1
model_info = onnx_model.graph.value_info
assert model_info[0].name == expand_nodes[0].output[0]
assert model_info[0].type == onnx_model.graph.input[1].type
def test_bert(self):
device = torch.device("cpu")
model_tester = BertModelTest.BertModelTester(self)
config, input_ids, token_type_ids, input_mask, sequence_labels, token_labels, choice_labels = model_tester.prepare_config_and_inputs()
model = BertForPreTraining(config=config)
model.eval()
loss, prediction_scores, seq_relationship_score = model(input_ids,
attention_mask=input_mask,
token_type_ids=token_type_ids,
masked_lm_labels=token_labels,
next_sentence_label=sequence_labels)
model_desc = ModelDescription([model_tester.input_ids_desc,
model_tester.attention_mask_desc,
model_tester.token_type_ids_desc,
model_tester.masked_lm_labels_desc,
model_tester.next_sentence_label_desc],
[model_tester.loss_desc,
model_tester.prediction_scores_desc,
model_tester.seq_relationship_scores_desc])
from collections import namedtuple
MyArgs = namedtuple("MyArgs",
"local_rank world_size max_steps learning_rate warmup_proportion batch_size seq_len")
args = MyArgs(local_rank=0,
world_size=1,
max_steps=100,
learning_rate=0.00001,
warmup_proportion=0.01,
batch_size=13,
seq_len=7)
dataloader = create_ort_test_dataloader(model_desc.inputs_,
args.batch_size,
args.seq_len,
device)
learning_rate = torch.tensor(1.0e+0, dtype=torch.float32).to(device)
for b in dataloader:
batch = b
break
learning_rate = torch.tensor([1.00e+00]).to(device)
inputs = batch + [learning_rate,]
onnx_model = self.get_onnx_model(model, model_desc, inputs, device, _extra_postprocess=postprocess_model)
self._bert_helper(onnx_model)
def _bert_helper(self, onnx_model):
# count layer_norm
count_layer_norm = self.count_nodes(onnx_model, "LayerNormalization")
assert count_layer_norm == 12
# get expand node and check output shape
expand_nodes = self.find_nodes(onnx_model, "Expand")
assert len(expand_nodes) == 1
model_info = onnx_model.graph.value_info
assert model_info[0].name == expand_nodes[0].output[0]
assert model_info[0].type == onnx_model.graph.input[0].type
if __name__ == '__main__':
unittest.main(module=__name__, buffer=True)

View file

@ -3,10 +3,3 @@ from orttraining_test_layer_norm_transform import layer_norm_transform
def postprocess_model(model):
add_name(model)
# remove transpose node if its input is a 2d weight which only feeds to the node
fix_transpose(model)
add_expand_shape(model)
layer_norm_transform(model)

View file

@ -19,7 +19,7 @@ def warmup_linear(x, warmup=0.002):
if x < warmup:
return x/warmup
return max((x - 1. )/ (warmup - 1.), 0.)
def warmup_poly(x, warmup=0.002, degree=0.5):
if x < warmup:
return x/warmup
@ -49,15 +49,16 @@ def map_optimizer_attributes(name):
return {"alpha": 0.9, "beta": 0.999, "lambda": 0.01, "epsilon": 1e-6}
def run_test(model, model_desc, device, args, gradient_accumulation_steps, fp16,
allreduce_post_accumulation, get_lr_this_step, use_internal_get_lr_this_step, loss_scaler, use_internal_loss_scaler,
allreduce_post_accumulation, get_lr_this_step, use_internal_get_lr_this_step, loss_scaler, use_internal_loss_scaler,
batch_args_option):
dataloader = create_ort_test_dataloader(model_desc.inputs_, args.batch_size, args.seq_len, device)
model = ORTTrainer(model, None, model_desc, "LambOptimizer",
map_optimizer_attributes=map_optimizer_attributes,
learning_rate_description=IODescription('Learning_Rate', [1,], torch.float32),
device=device, postprocess_model=postprocess_model,
gradient_accumulation_steps=gradient_accumulation_steps,
device=device,
_enable_internal_postprocess=True,
gradient_accumulation_steps=gradient_accumulation_steps,
# BertLAMB default initial settings: b1=0.9, b2=0.999, e=1e-6
world_rank=args.local_rank, world_size=args.world_size,
use_mixed_precision=fp16,