206 lines
7.5 KiB
Python
206 lines
7.5 KiB
Python
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import os
|
|
|
|
import paddle
|
|
from paddleslim.nas.ofa import OFA, utils
|
|
from paddleslim.nas.ofa.convert_super import Convert, supernet
|
|
|
|
from paddlenlp.transformers import (
|
|
BertForSequenceClassification,
|
|
BertModel,
|
|
BertTokenizer,
|
|
)
|
|
|
|
MODEL_CLASSES = {
|
|
"bert": (BertForSequenceClassification, BertTokenizer),
|
|
}
|
|
|
|
|
|
def bert_forward(
|
|
self, input_ids, token_type_ids=None, position_ids=None, attention_mask=None, output_hidden_states=False
|
|
):
|
|
wtype = self.pooler.dense.fn.weight.dtype if hasattr(self.pooler.dense, "fn") else self.pooler.dense.weight.dtype
|
|
if attention_mask is None:
|
|
attention_mask = paddle.unsqueeze((input_ids == self.pad_token_id).astype(wtype) * -1e9, axis=[1, 2])
|
|
else:
|
|
if attention_mask.ndim == 2:
|
|
# attention_mask [batch_size, sequence_length] -> [batch_size, 1, 1, sequence_length]
|
|
attention_mask = attention_mask.unsqueeze(axis=[1, 2])
|
|
|
|
embedding_output = self.embeddings(input_ids=input_ids, position_ids=position_ids, token_type_ids=token_type_ids)
|
|
if output_hidden_states:
|
|
output = embedding_output
|
|
encoder_outputs = []
|
|
for mod in self.encoder.layers:
|
|
output = mod(output, src_mask=attention_mask)
|
|
encoder_outputs.append(output)
|
|
if self.encoder.norm is not None:
|
|
encoder_outputs[-1] = self.encoder.norm(encoder_outputs[-1])
|
|
pooled_output = self.pooler(encoder_outputs[-1])
|
|
else:
|
|
sequence_output = self.encoder(embedding_output, attention_mask)
|
|
pooled_output = self.pooler(sequence_output)
|
|
if output_hidden_states:
|
|
return encoder_outputs, pooled_output
|
|
else:
|
|
return sequence_output, pooled_output
|
|
|
|
|
|
BertModel.forward = bert_forward
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
# Required parameters
|
|
parser.add_argument(
|
|
"--model_type",
|
|
default=None,
|
|
type=str,
|
|
required=True,
|
|
help="Model type selected in the list: " + ", ".join(MODEL_CLASSES.keys()),
|
|
)
|
|
parser.add_argument(
|
|
"--model_name_or_path",
|
|
default=None,
|
|
type=str,
|
|
required=True,
|
|
help="Path to pre-trained model or shortcut name selected in the list: "
|
|
+ ", ".join(
|
|
sum([list(classes[-1].pretrained_init_configuration.keys()) for classes in MODEL_CLASSES.values()], [])
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
"--sub_model_output_dir",
|
|
default=None,
|
|
type=str,
|
|
required=True,
|
|
help="The output directory where the sub model predictions and checkpoints will be written.",
|
|
)
|
|
parser.add_argument(
|
|
"--static_sub_model",
|
|
default=None,
|
|
type=str,
|
|
help="The output directory where the sub static model will be written. If set to None, not export static model",
|
|
)
|
|
parser.add_argument(
|
|
"--max_seq_length",
|
|
default=128,
|
|
type=int,
|
|
help="The maximum total input sequence length after tokenization. Sequences longer "
|
|
"than this will be truncated, sequences shorter will be padded.",
|
|
)
|
|
parser.add_argument("--n_gpu", type=int, default=1, help="number of gpus to use, 0 for cpu.")
|
|
parser.add_argument("--width_mult", type=float, default=1.0, help="width mult you want to export")
|
|
parser.add_argument("--depth_mult", type=float, default=1.0, help="depth mult you want to export")
|
|
args = parser.parse_args()
|
|
return args
|
|
|
|
|
|
def export_static_model(model, model_path, max_seq_length):
|
|
input_shape = [
|
|
paddle.static.InputSpec(shape=[None, max_seq_length], dtype="int64"),
|
|
paddle.static.InputSpec(shape=[None, max_seq_length], dtype="int64"),
|
|
]
|
|
net = paddle.jit.to_static(model, input_spec=input_shape)
|
|
paddle.jit.save(net, model_path)
|
|
|
|
|
|
def do_train(args):
|
|
paddle.set_device("gpu" if args.n_gpu else "cpu")
|
|
args.model_type = args.model_type.lower()
|
|
model_class, tokenizer_class = MODEL_CLASSES[args.model_type]
|
|
config_path = os.path.join(args.model_name_or_path, "model_config.json")
|
|
cfg_dict = dict(json.loads(open(config_path).read()))
|
|
|
|
kept_layers_index = {}
|
|
if args.depth_mult < 1.0:
|
|
depth = round(cfg_dict["init_args"][0]["num_hidden_layers"] * args.depth_mult)
|
|
cfg_dict["init_args"][0]["num_hidden_layers"] = depth
|
|
for idx, i in enumerate(range(1, depth + 1)):
|
|
kept_layers_index[idx] = math.floor(i / args.depth_mult) - 1
|
|
|
|
os.rename(config_path, config_path + "_bak")
|
|
with open(config_path, "w", encoding="utf-8") as f:
|
|
f.write(json.dumps(cfg_dict, ensure_ascii=False))
|
|
|
|
num_labels = cfg_dict["num_classes"]
|
|
|
|
model = model_class.from_pretrained(args.model_name_or_path, num_classes=num_labels)
|
|
|
|
origin_model = model_class.from_pretrained(args.model_name_or_path, num_classes=num_labels)
|
|
|
|
os.rename(config_path + "_bak", config_path)
|
|
|
|
sp_config = supernet(expand_ratio=[1.0, args.width_mult])
|
|
model = Convert(sp_config).convert(model)
|
|
|
|
ofa_model = OFA(model)
|
|
|
|
sd = paddle.load(os.path.join(args.model_name_or_path, "model_state.pdparams"))
|
|
|
|
if len(kept_layers_index) == 0:
|
|
ofa_model.model.set_state_dict(sd)
|
|
else:
|
|
for name, params in ofa_model.model.named_parameters():
|
|
if "encoder" not in name:
|
|
params.set_value(sd[name])
|
|
else:
|
|
idx = int(name.strip().split(".")[3])
|
|
mapping_name = name.replace("." + str(idx) + ".", "." + str(kept_layers_index[idx]) + ".")
|
|
params.set_value(sd[mapping_name])
|
|
|
|
best_config = utils.dynabert_config(ofa_model, args.width_mult)
|
|
for name, sublayer in ofa_model.model.named_sublayers():
|
|
if isinstance(sublayer, paddle.nn.MultiHeadAttention):
|
|
sublayer.num_heads = int(args.width_mult * sublayer.num_heads)
|
|
|
|
ofa_model.export(
|
|
best_config,
|
|
input_shapes=[[1, args.max_seq_length], [1, args.max_seq_length]],
|
|
input_dtypes=["int64", "int64"],
|
|
origin_model=origin_model,
|
|
)
|
|
for name, sublayer in origin_model.named_sublayers():
|
|
if isinstance(sublayer, paddle.nn.MultiHeadAttention):
|
|
sublayer.num_heads = int(args.width_mult * sublayer.num_heads)
|
|
|
|
output_dir = os.path.join(args.sub_model_output_dir, "model_width_%.5f" % args.width_mult)
|
|
if not os.path.exists(output_dir):
|
|
os.makedirs(output_dir)
|
|
model_to_save = origin_model
|
|
model_to_save.save_pretrained(output_dir)
|
|
|
|
if args.static_sub_model is not None:
|
|
export_static_model(origin_model, args.static_sub_model, args.max_seq_length)
|
|
|
|
|
|
def print_arguments(args):
|
|
"""print arguments"""
|
|
print("----------- Configuration Arguments -----------")
|
|
for arg, value in sorted(vars(args).items()):
|
|
print("%s: %s" % (arg, value))
|
|
print("------------------------------------------------")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = parse_args()
|
|
print_arguments(args)
|
|
do_train(args)
|