Update benchmark to reflect those used in our latest results (#3967)

Update optimizer for GPT2 models exported from PyTorch 1.5.
Update benchmark to use GPT2 models without Past State inputs/outputs
Update bert_perf_test to allow setting omp_num_threads etc to test only one setting
This commit is contained in:
Tianlei Wu 2020-05-16 20:17:40 -07:00 committed by GitHub
parent 56700bec83
commit 07e9a4c164
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 271 additions and 167 deletions

View file

@ -15,7 +15,6 @@ logger = logging.getLogger(__name__)
class Gpt2OnnxModel(BertOnnxModel):
def __init(self, model, num_heads, hidden_size):
super().__init__(model, num_heads, hidden_size)
@ -28,7 +27,6 @@ class Gpt2OnnxModel(BertOnnxModel):
input_name_to_nodes = self.input_name_to_nodes()
output_name_to_node = self.output_name_to_node()
nodes_to_remove = []
attention_count = 0
for normalize_node in self.get_nodes_by_op_type("LayerNormalization"):
@ -65,10 +63,39 @@ class Gpt2OnnxModel(BertOnnxModel):
continue
qk_nodes = self.match_parent_path(matmul_qkv, ['Softmax', 'Sub', 'Mul', 'Div', 'MatMul'], [0, 0, 0, 0, 0])
if qk_nodes is None:
logger.debug("fuse_attention: failed to match qk path")
continue
(softmax_qk, sub_qk, mul_qk, div_qk, matmul_qk) = qk_nodes
if qk_nodes is not None:
(softmax_qk, sub_qk, mul_qk, div_qk, matmul_qk) = qk_nodes
mask_nodes = self.match_parent_path(
sub_qk,
['Mul', 'Sub', 'Slice', 'Slice', 'Unsqueeze', 'Sub', 'Squeeze', 'Slice', 'Shape', 'Div'],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0]) # yapf: disable
if mask_nodes is None:
logger.debug("fuse_attention: failed to match mask path")
continue
div_mask = mask_nodes[-1]
if div_qk != div_mask:
logger.debug("fuse_attention: skip since div_qk != div_mask")
continue
else:
# New pattern for gpt2 from PyTorch 1.5.0 and Transformers 2.9.0.
qk_nodes = self.match_parent_path(matmul_qkv, ['Softmax', 'Where', 'Div', 'MatMul'], [0, 0, 1, 0])
if qk_nodes is None:
logger.debug("fuse_attention: failed to match qk path")
continue
(softmax_qk, where_qk, div_qk, matmul_qk) = qk_nodes
mask_nodes = self.match_parent_path(
where_qk,
['Cast', 'Slice', 'Slice', 'Unsqueeze', 'Sub', 'Squeeze', 'Slice', 'Shape', 'Div'],
[ 0, 0, 0, 1, 0, 0, 0, 0, 0]) # yapf: disable
if mask_nodes is None:
logger.debug("fuse_attention: failed to match mask path")
continue
div_mask = mask_nodes[-1]
if div_qk != div_mask:
logger.debug("fuse_attention: skip since div_qk != div_mask")
continue
q_nodes = self.match_parent_path(matmul_qk, ['Transpose', 'Reshape', 'Split'], [0, 0, 0])
if q_nodes is None:
@ -88,31 +115,12 @@ class Gpt2OnnxModel(BertOnnxModel):
logger.debug("fuse_attention: skip since split_v != split_k")
continue
mask_nodes = self.match_parent_path(
sub_qk,
['Mul', 'Sub', 'Slice', 'Slice', 'Unsqueeze', 'Sub', 'Squeeze', 'Slice', 'Shape', 'Div'],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0]) # yapf: disable
if mask_nodes is None:
logger.debug("fuse_attention: failed to match mask path")
continue
(mul_mask, sub_mask, slice_mask, slice_mask_0, unsqueeze_mask, sub_mask, squeeze_mask, slice_mask_1,
shape_mask, div_mask) = mask_nodes
if div_qk != div_mask:
logger.debug("fuse_attention: skip since div_qk != div_mask")
continue
self.create_attention_node(gemm, gemm_qkv, layernorm_before_attention.output[0], reshape_qkv.output[0],
attention_count == 0)
nodes_to_remove.extend([reshape_qkv, transpose_qkv, matmul_qkv])
nodes_to_remove.extend(qk_nodes)
nodes_to_remove.extend(q_nodes)
nodes_to_remove.extend(k_nodes)
nodes_to_remove.extend(v_nodes)
nodes_to_remove.extend(mask_nodes)
# we rely on prune_graph() to clean old subgraph nodes:
# qk_nodes + q_nodes + k_nodes + v_nodes + mask_nodes + [reshape_qkv, transpose_qkv, matmul_qkv]
attention_count += 1
self.remove_nodes(nodes_to_remove)
self.prune_graph()
logger.info(f"Fused Attention count:{attention_count}")
@ -187,4 +195,4 @@ class Gpt2OnnxModel(BertOnnxModel):
reshape_count += 2
self.prune_graph()
logger.info(f"Remove Reshape count:{reshape_count}")
logger.info(f"postprocess: remove Reshape count:{reshape_count}")

View file

@ -13,11 +13,11 @@
# 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.
""" Benchmarking the inference of pretrained transformer models
""" Benchmarking the inference of pretrained transformer models.
PyTorch/TorchScript benchmark is based on https://github.com/huggingface/transformers/blob/master/examples/benchmarks.py.
One difference is that random input_ids is generated in this benchmark.
This will automatically export a pretrained model to ONNX, and do optimization (optional)
For onnxruntime, this script will convert a pretrained model to ONNX, and optimize it when -o parameter is used.
Example commands:
Export all models to ONNX, optimize and validate them:
@ -48,24 +48,25 @@ from packaging import version
logger = logging.getLogger('')
DEFAULT_MODELS = ["bert-base-cased", "distilbert-base-uncased", "roberta-base", "gpt2"]
# List of pretrained models: https://huggingface.co/transformers/pretrained_models.html
# Pretrained model name to a tuple of input names, opset_version and optimization model type
# Pretrained model name to a tuple of input names, opset_version, use_external_data_format and optimization model type
MODELS = {
"bert-base-cased": (["input_ids", "attention_mask", "token_type_ids"], 11, "bert"),
"distilbert-base-uncased": (["input_ids", "attention_mask"], 11, "bert"),
"roberta-base": (["input_ids", "attention_mask"], 11, "bert"),
"bert-base-cased": (["input_ids", "attention_mask", "token_type_ids"], 11, False, "bert"),
"distilbert-base-uncased": (["input_ids", "attention_mask"], 11, False, "bert"),
"roberta-base": (["input_ids", "attention_mask"], 11, False, "bert"),
# No past state inputs for GPT models. You might set enable_cache=False in modeling_gpt2.py (need installing transformers from source) to disable past state output.
"gpt2": (["input_ids"], 11, "gpt2"),
"distilgpt2": (["input_ids"], 11, "gpt2"),
"openai-gpt": (["input_ids"], 11, "gpt2"),
# No past state inputs for GPT models.
"gpt2": (["input_ids"], 11, False, "gpt2"), # no past state inputs & outputs
"distilgpt2": (["input_ids"], 11, False, "gpt2"), # no past state inputs & outputs
"openai-gpt": (["input_ids"], 11, False, "gpt2"), # no past state inputs
# Models uses Einsum, which need opset version 12 and PyTorch 1.5.0 or above.
# Currently OnnxRuntime lacks cuda op for Einsum. GPU inference will be very slow.
"albert-base-v2": (["input_ids"], 12, "bert"),
"xlnet-base-cased": (["input_ids"], 12, "bert"),
"albert-base-v2": (["input_ids"], 12, False, "bert"),
"xlnet-base-cased": (["input_ids"], 12, False, "bert"),
# This model is very large. Need use_external_data_format=True to export it.
"xlm-mlm-en-2048": (["input_ids"], 11, True, "bert"),
}
cpu_count = psutil.cpu_count(logical=True)
@ -73,11 +74,23 @@ cpu_count = psutil.cpu_count(logical=True)
if "OMP_NUM_THREADS" not in os.environ:
os.environ["OMP_NUM_THREADS"] = str(cpu_count)
from transformers import (AutoConfig, AutoTokenizer, is_torch_available)
import torch
from transformers import (AutoConfig, AutoTokenizer, AutoModel, GPT2Model)
if is_torch_available():
import torch
from transformers import AutoModel
# use_cache is True by default in GPT2Model. Here we wrap a class to disable past state output.
class GPT2ModelNoPastState(GPT2Model):
def __init__(self, config):
super().__init__(config)
def forward(self, input_ids):
return super().forward(input_ids, use_cache=False)
def load_pretrained_model(model_name, config, cache_dir):
if model_name in ["gpt2", "distilgpt2"]:
return GPT2ModelNoPastState.from_pretrained(model_name, config=config, cache_dir=cache_dir)
return AutoModel.from_pretrained(model_name, config=config, cache_dir=cache_dir)
def create_onnxruntime_session(onnx_model_path, use_gpu):
@ -176,16 +189,22 @@ def validate_onnx_model(onnx_model_filename, example_inputs, example_outputs_fla
optimize_model_statistics = {}
def optimize_onnx_model(onnx_model_filename, model_type, num_attention_heads, hidden_size, fp16):
def optimize_onnx_model(onnx_model_filename, model_type, num_attention_heads, hidden_size, fp16, overwrite):
optimized_model_filename = onnx_model_filename.replace(".onnx", "_fp16.onnx" if fp16 else "_fp32.onnx")
if not os.path.exists(optimized_model_filename):
if overwrite or not os.path.exists(optimized_model_filename):
from optimizer import optimize_model
from BertOnnxModel import BertOptimizationOptions
optimization_options = BertOptimizationOptions(model_type)
if fp16:
optimization_options.enable_gelu_approximation = True
# Use onnxruntime to optimize model, which will be saved to *_ort_cpu.onnx
opt_model = optimize_model(onnx_model_filename,
model_type,
num_heads=num_attention_heads,
hidden_size=hidden_size,
opt_level=99,
optimization_options=optimization_options,
only_onnxruntime=True)
optimize_model_statistics[onnx_model_filename] = opt_model.get_fused_operator_statistics()
@ -194,7 +213,7 @@ def optimize_onnx_model(onnx_model_filename, model_type, num_attention_heads, hi
model_type,
num_heads=num_attention_heads,
hidden_size=hidden_size,
opt_level=0)
opt_level=99)
optimize_model_statistics[optimized_model_filename] = opt_model.get_fused_operator_statistics()
if fp16:
@ -205,9 +224,9 @@ def optimize_onnx_model(onnx_model_filename, model_type, num_attention_heads, hi
return optimized_model_filename
def export_onnx_model(model_name, cache_dir, input_names, fp16, optimize_onnx, validate_onnx):
def export_onnx_model(model_name, cache_dir, input_names, fp16, optimize_onnx, validate_onnx, overwrite):
config = AutoConfig.from_pretrained(model_name, cache_dir=cache_dir)
model = AutoModel.from_pretrained(model_name, config=config, cache_dir=cache_dir)
model = load_pretrained_model(model_name, config=config, cache_dir=cache_dir)
model.cpu()
tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
@ -223,7 +242,7 @@ def export_onnx_model(model_name, cache_dir, input_names, fp16, optimize_onnx, v
example_outputs_flatten = update_flatten_list(example_outputs_flatten, [])
onnx_model_filename = "{}_{}.onnx".format(model_name, str(len(input_names)))
if not os.path.exists(onnx_model_filename):
if overwrite or not os.path.exists(onnx_model_filename):
logger.info("Exporting ONNX model to {}".format(onnx_model_filename))
dynamic_axes, output_names = build_dynamic_axes(example_inputs, example_outputs_flatten)
@ -236,7 +255,8 @@ def export_onnx_model(model_name, cache_dir, input_names, fp16, optimize_onnx, v
example_outputs=example_outputs,
dynamic_axes=dynamic_axes,
do_constant_folding=True,
opset_version=MODELS[model_name][1])
opset_version=MODELS[model_name][1],
use_external_data_format=MODELS[model_name][2])
else:
logger.info(f"Skip export since model existed: {onnx_model_filename}")
@ -245,9 +265,9 @@ def export_onnx_model(model_name, cache_dir, input_names, fp16, optimize_onnx, v
is_valid_onnx_model = validate_onnx_model(onnx_model_filename, example_inputs, example_outputs_flatten)
if optimize_onnx or fp16:
model_type = MODELS[model_name][2]
model_type = MODELS[model_name][3]
onnx_model_filename = optimize_onnx_model(onnx_model_filename, model_type, config.num_attention_heads,
config.hidden_size, fp16)
config.hidden_size, fp16, overwrite)
if validate_onnx:
is_valid_onnx_model = validate_onnx_model(onnx_model_filename, example_inputs, example_outputs_flatten)
@ -272,7 +292,7 @@ def get_latency_result(runtimes, batch_size):
def run_onnxruntime(use_gpu, model_names, fp16, batch_sizes, sequence_lengths, repeat_times, input_counts,
optimize_onnx, validate_onnx, cache_dir, verbose):
optimize_onnx, validate_onnx, cache_dir, verbose, overwrite):
import onnxruntime
results = []
@ -295,7 +315,7 @@ def run_onnxruntime(use_gpu, model_names, fp16, batch_sizes, sequence_lengths, r
with torch.no_grad():
onnx_model_file, is_valid_onnx_model, vocab_size, max_sequence_length = export_onnx_model(
model_name, cache_dir, input_names, fp16, optimize_onnx, validate_onnx)
model_name, cache_dir, input_names, fp16, optimize_onnx, validate_onnx, overwrite)
if not is_valid_onnx_model:
continue
@ -347,19 +367,21 @@ def run_pytorch(use_gpu, model_names, fp16, batch_sizes, sequence_lengths, repea
for model_name in model_names:
config = AutoConfig.from_pretrained(model_name, torchscript=torchscript, cache_dir=cache_dir)
model = AutoModel.from_pretrained(model_name, config=config, cache_dir=cache_dir)
model = load_pretrained_model(model_name, config=config, cache_dir=cache_dir)
tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
max_input_size = tokenizer.max_model_input_sizes[model_name]
logger.debug(f"Model {model}")
logger.debug(f"Number of parameters {model.num_parameters()}")
if fp16:
model.half()
device = torch.device("cuda:0" if use_gpu else "cpu")
model.to(device)
for batch_size in batch_sizes:
if batch_size <= 0:
continue
if fp16:
model.half()
device = torch.device("cuda:0" if use_gpu else "cpu")
model.to(device)
for sequence_length in sequence_lengths:
if max_input_size is not None and sequence_length > max_input_size:
@ -372,13 +394,8 @@ def run_pytorch(use_gpu, model_names, fp16, batch_sizes, sequence_lengths, repea
dtype=torch.long,
device=device)
try:
if torchscript:
logger.debug("Tracing model with input shape {}".format(input_ids.shape))
inference = torch.jit.trace(model, input_ids)
inference(input_ids)
else:
inference = model
inference(input_ids)
inference = torch.jit.trace(model, input_ids) if torchscript else model
inference(input_ids)
runtimes = timeit.repeat(lambda: inference(input_ids), repeat=repeat_times, number=1)
@ -452,11 +469,15 @@ def output_summary(results, csv_filename, args):
def output_fusion_statistics(optimize_model_statistics, csv_filename):
from transformers import __version__ as transformers_version
with open(csv_filename, mode="a", newline='') as csv_file:
column_names = ["model_filename"] + list(next(iter(optimize_model_statistics.values())).keys())
column_names = ["model_filename", "transformers", "torch"] + list(
next(iter(optimize_model_statistics.values())).keys())
csv_writer = csv.DictWriter(csv_file, fieldnames=column_names)
csv_writer.writeheader()
for key in optimize_model_statistics.keys():
optimize_model_statistics[key]["transformers"] = transformers_version
optimize_model_statistics[key]["torch"] = torch.__version__
optimize_model_statistics[key]["model_filename"] = key
csv_writer.writerow(optimize_model_statistics[key])
logger.info(f"Fusion statistics is saved to csv file: {csv_filename}")
@ -496,6 +517,8 @@ def parse_arguments():
parser.add_argument("--verbose", required=False, action="store_true", help="Print more information")
parser.add_argument("--overwrite", required=False, action="store_true", help="Overwrite existing models")
parser.add_argument("-o",
"--optimize_onnx",
required=False,
@ -543,7 +566,7 @@ def setup_logger(verbose):
coloredlogs.install(level='DEBUG', fmt='[%(filename)s:%(lineno)s - %(funcName)20s()] %(message)s')
else:
coloredlogs.install(fmt='%(message)s')
logging.getLogger("transformers").setLevel(logging.ERROR)
logging.getLogger("transformers").setLevel(logging.WARNING)
def main():
@ -569,10 +592,6 @@ def main():
results = []
if enable_torch or enable_torchscript:
if not is_torch_available():
logger.error("Trying to run a PyTorch benchmark but PyTorch was not found in the environment.")
return
if args.input_counts != [1]:
logger.warning("--input_counts is not implemented for torch or torchscript engine.")
@ -588,7 +607,7 @@ def main():
try:
results += run_onnxruntime(args.use_gpu, args.models, args.fp16, args.batch_sizes, args.sequence_lengths,
args.test_times, args.input_counts, args.optimize_onnx, args.validate_onnx,
args.cache_dir, args.verbose)
args.cache_dir, args.verbose, args.overwrite)
except:
logger.error(f"Exception", exc_info=True)

View file

@ -26,6 +26,62 @@ from datetime import datetime
import multiprocessing
from bert_test_data import get_bert_inputs, generate_test_data
from dataclasses import dataclass
@dataclass
class TestSetting:
batch_size: int
sequence_length: int
test_cases: int
test_times: int
contiguous: bool
use_gpu: bool
warmup: bool
omp_num_threads: int
omp_wait_policy: str
intra_op_num_threads: int
seed: int
verbose: bool
contiguous: bool
inclusive: bool
extra_latency: float = 0
def get_setting(self) -> str:
return f"batch_size={self.batch_size},sequence_length={self.sequence_length},test_cases={self.test_cases},test_times={self.test_times},contiguous={self.contiguous},use_gpu={self.use_gpu},warmup={self.warmup}"
def check(self, intra_op_threads, omp_threads, omp_policy) -> bool:
if intra_op_threads is None:
if self.intra_op_num_threads > 0:
return False
else:
assert intra_op_threads > 0
if not (self.intra_op_num_threads is None or self.intra_op_num_threads == intra_op_threads):
return False
if omp_threads is None:
if self.omp_num_threads > 0:
return False
else:
assert omp_threads > 0
if not (self.omp_num_threads is None or self.omp_num_threads == omp_threads):
return False
if self.omp_wait_policy is not None:
if omp_policy != self.omp_wait_policy:
return False
return True
@dataclass
class ModelSetting:
model_path: str
input_ids: str
segment_ids: str
input_mask: str
opt_level: int
def create_session(model_path, use_gpu, intra_op_num_threads, graph_optimization_level=None):
# Import onnxruntime shall be after OpenMP environment variable setting.
@ -43,7 +99,7 @@ def create_session(model_path, use_gpu, intra_op_num_threads, graph_optimization
session = onnxruntime.InferenceSession(model_path)
else:
execution_providers = ['CPUExecutionProvider'
] if not use_gpu else ['CUDAExecutionProvider', 'CPUExecutionProvider']
] if not use_gpu else ['CUDAExecutionProvider', 'CPUExecutionProvider']
sess_options = onnxruntime.SessionOptions()
sess_options.execution_mode = onnxruntime.ExecutionMode.ORT_SEQUENTIAL
@ -113,7 +169,7 @@ def to_string(model_path, session, test_setting):
'GraphOptimizationLevel.ORT_', '')
option += ",OMP_NUM_THREADS={}".format(os.environ["OMP_NUM_THREADS"] if "OMP_NUM_THREADS" in os.environ else "")
option += ",OMP_WAIT_POLICY={}".format(os.environ["OMP_WAIT_POLICY"] if "OMP_WAIT_POLICY" in os.environ else "")
option += ",{}".format(test_setting)
option += ",{}".format(test_setting.get_setting())
return option
@ -128,23 +184,20 @@ def setup_openmp_environ(omp_num_threads, omp_wait_policy):
if "OMP_WAIT_POLICY" in os.environ:
del os.environ["OMP_WAIT_POLICY"]
else:
assert omp_wait_policy in ["ACTIVE", "PASSIVE"]
assert omp_wait_policy in ["ACTIVE", "PASSIVE"], f"{omp_wait_policy} is not a valid policy"
os.environ["OMP_WAIT_POLICY"] = omp_wait_policy
def run_one_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, intra_op_num_threads, omp_num_threads, omp_wait_policy, no_warmup, opt_level,
extra_latency):
def run_one_test(model_setting, test_setting, perf_results, all_inputs, intra_op_num_threads, omp_num_threads,
omp_wait_policy):
# Environment variable shall be set before import onnxruntime.
setup_openmp_environ(omp_num_threads, omp_wait_policy)
test_setting = "batch_size={},sequence_length={},test_cases={},test_times={},contiguous={},use_gpu={},warmup={}".format(
batch_size, sequence_length, test_cases, test_times, contiguous, use_gpu, not no_warmup)
session = create_session(model_path, use_gpu, intra_op_num_threads, opt_level)
session = create_session(model_setting.model_path, test_setting.use_gpu, intra_op_num_threads,
model_setting.opt_level)
output_names = [output.name for output in session.get_outputs()]
key = to_string(model_path, session, test_setting)
key = to_string(model_setting.model_path, session, test_setting)
if key in perf_results:
print("skip duplicated test:", key)
return
@ -152,12 +205,12 @@ def run_one_test(perf_results, model_path, all_inputs, batch_size, sequence_leng
print("Running test:", key)
all_latency_list = []
for i in range(test_times):
results, latency_list = onnxruntime_inference(session, all_inputs, output_names, not no_warmup)
for i in range(test_setting.test_times):
results, latency_list = onnxruntime_inference(session, all_inputs, output_names, test_setting.warmup)
all_latency_list.extend(latency_list)
# latency in miliseconds
latency_ms = np.array(all_latency_list) * 1000 + extra_latency
latency_ms = np.array(all_latency_list) * 1000 + test_setting.extra_latency
average_latency = statistics.mean(latency_ms)
latency_50 = np.percentile(latency_ms, 50)
@ -165,7 +218,7 @@ def run_one_test(perf_results, model_path, all_inputs, batch_size, sequence_leng
latency_90 = np.percentile(latency_ms, 90)
latency_95 = np.percentile(latency_ms, 95)
latency_99 = np.percentile(latency_ms, 99)
throughput = batch_size * (1000.0 / average_latency)
throughput = test_setting.batch_size * (1000.0 / average_latency)
perf_results[key] = (average_latency, latency_50, latency_75, latency_90, latency_95, latency_99, throughput)
@ -173,48 +226,67 @@ def run_one_test(perf_results, model_path, all_inputs, batch_size, sequence_leng
format(throughput, '.2f')))
def launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, intra_op_num_threads, omp_num_threads, omp_wait_policy, no_warmup, opt_level,
extra_latency):
def launch_test(model_setting, test_setting, perf_results, all_inputs, intra_op_num_threads, omp_num_threads,
omp_wait_policy):
if not test_setting.check(intra_op_num_threads, omp_num_threads, omp_wait_policy):
return
process = multiprocessing.Process(target=run_one_test,
args=(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu,
test_cases, test_times, contiguous, intra_op_num_threads, omp_num_threads,
omp_wait_policy, no_warmup, opt_level, extra_latency))
args=(model_setting, test_setting, perf_results, all_inputs, intra_op_num_threads,
omp_num_threads, omp_wait_policy))
process.start()
process.join()
def run_perf_tests(perf_results, model_path, batch_size, sequence_length, use_gpu, test_cases, test_times, contiguous,
all_inputs, test_all, no_warmup, opt_level, extra_latency):
def run_perf_tests(model_setting, test_setting, perf_results, test_all, all_inputs):
cpu_count = psutil.cpu_count(logical=False)
logical_cores = psutil.cpu_count(logical=True)
# Test a setting without any setting as baseline 1.
launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, None, None, None, no_warmup, opt_level, extra_latency)
candidate_threads = list(set([1, logical_cores, cpu_count]))
if not use_gpu:
if (test_setting.intra_op_num_threads is not None) or (test_setting.omp_num_threads is not None):
if test_setting.intra_op_num_threads is not None:
intra_op_threads = [test_setting.intra_op_num_threads]
else:
intra_op_threads = [None] + candidate_threads
if test_setting.omp_num_threads is not None:
omp_threads = [test_setting.omp_num_threads]
else:
omp_threads = [None] + candidate_threads
if test_setting.omp_wait_policy is not None:
omp_policies = [test_setting.omp_wait_policy]
else:
omp_policies = [None, 'PASSIVE', 'ACTIVE']
for it in intra_op_threads:
for ot in omp_threads:
for op in omp_policies:
launch_test(model_setting, test_setting, perf_results, all_inputs, it, ot, op)
return
# Test a setting without any setting as baseline 1.
launch_test(model_setting, test_setting, perf_results, all_inputs, None, None, None)
if not test_setting.use_gpu:
# For CPU: intra_op_num_threads = 1, omp_num_threads=None, omp_wait_policy=None
# Another setting without environment variable as baseline 2.
launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, 1, None, None, no_warmup, opt_level, extra_latency)
launch_test(model_setting, test_setting, perf_results, all_inputs, 1, None, None)
else:
# For GPU, we test two more settings by default:
# (1) intra_op_num_threads = 1, omp_num_threads=cpu_count, omp_wait_policy=PASSIVE
# (2) intra_op_num_threads = logical_cores, omp_num_threads=1, omp_wait_policy=ACTIVE
launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, 1, cpu_count, 'PASSIVE', no_warmup, opt_level, extra_latency)
launch_test(model_setting, test_setting, perf_results, all_inputs, 1, cpu_count, 'PASSIVE')
launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases, test_times,
contiguous, logical_cores, 1, 'ACTIVE', no_warmup, opt_level, extra_latency)
launch_test(model_setting, test_setting, perf_results, all_inputs, logical_cores, 1, 'ACTIVE')
# GPU latency is not sensitive to these settings. No need to test many combinations.
# Skip remaining settings for GPU without --all flag.
if use_gpu and not test_all:
if test_setting.use_gpu and not test_all:
return
candidates = list(set([1, logical_cores, cpu_count]))
for intra_op_num_threads in candidates:
for omp_num_threads in candidates:
# skip settings that are very slow
@ -234,66 +306,33 @@ def run_perf_tests(perf_results, model_path, batch_size, sequence_length, use_gp
continue
for omp_wait_policy in ['ACTIVE', 'PASSIVE']:
launch_test(perf_results, model_path, all_inputs, batch_size, sequence_length, use_gpu, test_cases,
test_times, contiguous, intra_op_num_threads, omp_num_threads, omp_wait_policy, no_warmup,
opt_level, extra_latency)
launch_test(model_setting, test_setting, perf_results, all_inputs, intra_op_num_threads,
omp_num_threads, omp_wait_policy)
def run_performance(perf_results, model_path, batch_size, sequence_length, use_gpu, test_cases, test_times, seed,
verbose, inclusive, test_all, no_warmup, opt_level, input_ids_name, segment_ids_name,
input_mask_name):
input_ids, segment_ids, input_mask = get_bert_inputs(model_path, input_ids_name, segment_ids_name, input_mask_name)
def run_performance(model_setting, test_setting, perf_results, test_all):
input_ids, segment_ids, input_mask = get_bert_inputs(model_setting.model_path, model_setting.input_ids,
model_setting.segment_ids, model_setting.input_mask)
# Do not generate random mask for performance test.
print(f"Generating {test_cases} samples for batch_size={batch_size} sequence_length={sequence_length}")
all_inputs = generate_test_data(batch_size,
sequence_length,
test_cases,
seed,
verbose,
print(
f"Generating {test_setting.test_cases} samples for batch_size={test_setting.batch_size} sequence_length={test_setting.sequence_length}"
)
all_inputs = generate_test_data(test_setting.batch_size,
test_setting.sequence_length,
test_setting.test_cases,
test_setting.seed,
test_setting.verbose,
input_ids,
segment_ids,
input_mask,
random_mask_length=False)
if test_setting.contiguous:
all_inputs, contiguous_latency = get_contiguous_inputs(all_inputs)
print("Extra latency for converting inputs to contiguous: {} ms".format(format(contiguous_latency, '.2f')))
test_setting.extra_latency = contiguous_latency if test_setting.inclusive else 0
contiguous = False
run_perf_tests(perf_results,
model_path,
batch_size,
sequence_length,
use_gpu,
test_cases,
test_times,
contiguous,
all_inputs,
test_all,
no_warmup,
opt_level,
extra_latency=0)
# only test contiguous array when the --all flag is set.
if not test_all:
return
# Convert inputs to contiguous array, which could improve inference performance
all_inputs, contiguous_latency = get_contiguous_inputs(all_inputs)
print("Extra latency for converting inputs to contiguous: {} ms".format(format(contiguous_latency, '.2f')))
contiguous = True
run_perf_tests(perf_results,
model_path,
batch_size,
sequence_length,
use_gpu,
test_cases,
test_times,
contiguous,
all_inputs,
test_all,
no_warmup,
opt_level,
extra_latency=contiguous_latency if inclusive else 0)
run_perf_tests(model_setting, test_setting, perf_results, test_all, all_inputs)
def parse_arguments():
@ -345,6 +384,28 @@ def parse_arguments():
parser.add_argument('--all', required=False, action='store_true', help="test all candidate settings")
parser.set_defaults(all=False)
parser.add_argument('--omp_num_threads',
required=False,
type=int,
default=None,
help=">0, set OMP_NUM_THREADS value. 0, do not set")
parser.add_argument('--intra_op_num_threads',
required=False,
type=int,
default=None,
help=">=0, set intra_op_num_threads")
parser.add_argument('--omp_wait_policy',
required=False,
type=str,
default=None,
choices=['ACTIVE', 'PASSIVE'],
help="OMP_WAIT_POLICY")
parser.add_argument('--contiguous', required=False, type=bool, default=False, help="contiguous input")
parser.set_defaults(contiguous=False)
parser.add_argument('--no_warmup', required=False, action='store_true', help="do not use one sample for warm-up.")
parser.set_defaults(no_warmup=False)
@ -369,10 +430,26 @@ def main():
if not min(batch_size_set) >= 1 and max(batch_size_set) <= 128:
raise Exception("batch_size not in range [1, 128]")
model_setting = ModelSetting(args.model, args.input_ids, args.segment_ids, args.input_mask, args.opt_level)
for batch_size in batch_size_set:
run_performance(perf_results, args.model, batch_size, args.sequence_length, args.use_gpu, args.samples,
args.test_times, args.seed, args.verbose, args.inclusive, args.all, args.no_warmup,
args.opt_level, args.input_ids, args.segment_ids, args.input_mask)
test_setting = TestSetting(
batch_size,
args.sequence_length,
args.samples,
args.test_times,
None, #contiguous
args.use_gpu,
not args.no_warmup,
args.omp_num_threads,
args.omp_wait_policy,
args.intra_op_num_threads,
args.seed,
args.verbose,
args.contiguous,
args.inclusive)
print("test setting", test_setting)
run_performance(model_setting, test_setting, perf_results, args.all)
# Sort the results so that the first one has smallest latency.
sorted_results = sorted(perf_results.items(), reverse=False, key=lambda x: x[1])