onnxruntime/onnxruntime/python/tools/tensorrt/perf/post.py
Yifan Li 3170a48e60
[EP Perf] Add tag to indicate which TRT parser is using (#19784)
### Description
* Add tag to distinguish if TRT `builtin` or `oss` parser is being used
* `oss` tag will be inserted with onnx-tensorrt commit id, to indicate
which version oss parser is
### Validate
DB entry before/after this PR 
(during test, `builtin` or `oss_{commit_id}` tag was inserted in the
database entries):

### Motivation and Context
To distinguish perf results using builtin/oss parser in the database,
this parser tag is needed.
In future, results using different parsers will be listed in different
Perf Dashboard pages.
2024-03-08 10:24:36 -08:00

521 lines
19 KiB
Python

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# --------------------------------------------------------------------------
import argparse
import datetime
import os
import sys
import pandas as pd
from azure.kusto.data import KustoConnectionStringBuilder
from azure.kusto.data.data_format import DataFormat
from azure.kusto.ingest import IngestionProperties, QueuedIngestClient, ReportLevel
from perf_utils import (
avg_ending,
cpu,
cuda,
cuda_fp16,
fail_name,
group_title,
latency_name,
latency_over_time_name,
memory_ending,
memory_name,
memory_over_time_name,
model_title,
op_metrics_name,
ort_provider_list,
provider_list,
second,
session_name,
session_over_time_name,
specs_name,
standalone_trt,
standalone_trt_fp16,
status_name,
status_over_time_name,
table_headers,
trt,
trt_fp16,
)
def parse_arguments():
"""
Parses command-line arguments and returns an object with each argument as a field.
:return: An object whose fields represent the parsed command-line arguments.
"""
parser = argparse.ArgumentParser()
parser.add_argument("-r", "--report_folder", help="Path to the local file report", required=True)
parser.add_argument("-c", "--commit_hash", help="Commit hash", required=True)
parser.add_argument("-u", "--report_url", help="Report Url", required=True)
parser.add_argument("-t", "--trt_version", help="Tensorrt Version", required=True)
parser.add_argument("-b", "--branch", help="Branch", required=True)
parser.add_argument("--kusto_conn", help="Kusto connection URL", required=True)
parser.add_argument("--database", help="Database name", required=True)
parser.add_argument("--use_tensorrt_oss_parser", help="Use TensorRT OSS parser", required=False)
parser.add_argument(
"-d",
"--commit_datetime",
help="Commit datetime in Python's datetime ISO 8601 format",
required=True,
type=datetime.datetime.fromisoformat,
)
return parser.parse_args()
def adjust_columns(table, columns, db_columns, model_group):
"""
Utility function that replaces column names in an in-memory table with the appropriate database column names.
Additionly, this function adds a model group column to all rows in the table.
:param table: The Pandas table to adjust.
:param columns: A list of existing column names to rename.
:param db_columns: A list of databse columns names to use.
:param model_group: The model group to append as a column.
:return: The updated table.
"""
table = table[columns]
table = table.set_axis(db_columns, axis=1)
table = table.assign(Group=model_group)
return table
def get_latency_over_time(report_url, latency_table):
"""
Returns a new Pandas table with data that tracks the latency of model/EP inference runs over time.
:param report_url: The URL of the Azure pipeline run/report which produced this latency data.
:param latency_table: The Pandas table containing per model/EP latencies with the schema:
| Model | ORT-CPUFp32 | ORT-CUDAFp32 | ... | Group | ...
=====================================================================
| resnet.. | 43.61 | 4.18 | ... | onnx-zoo-models | ...
:return: A new table in which the EPs are not hardcoded as columns. Ex:
| Model | Group | Ep | Latency | ...
===========================================================
| resnet.. | onnx-zoo-models | ORT-CPUFp32 | 43.61 | ...
| resnet.. | onnx-zoo-models | ORT-CUDAFp32 | 4.18 | ...
"""
over_time = latency_table.melt(id_vars=[model_title, group_title], var_name="Ep", value_name="Latency")
over_time = over_time.assign(ReportUrl=report_url)
over_time = over_time[
[
model_title,
group_title,
"Ep",
"Latency",
"ReportUrl",
]
]
over_time.fillna("", inplace=True)
return over_time
def get_failures(fail, model_group):
"""
Returns a new Pandas table with data that tracks failed model/EP inference runs.
:param fail: The Pandas table containing raw failure data imported from a CSV file.
:param model_group: The model group namespace to append as a column.
:return: The updated table.
"""
fail_columns = fail.keys()
fail_db_columns = [model_title, "Ep", "ErrorType", "ErrorMessage"]
fail = adjust_columns(fail, fail_columns, fail_db_columns, model_group)
return fail
def get_memory(memory, model_group):
"""
Returns a new Pandas table with data that tracks peak memory usage per model/EP.
:param memory: The Pandas table containing raw memory usage data imported from a CSV file.
:param model_group: The model group namespace to append as a column.
:return: The updated table.
"""
memory_columns = [model_title]
for provider in provider_list:
if cpu not in provider:
memory_columns.append(provider + memory_ending)
memory_db_columns = [
model_title,
cuda,
trt,
standalone_trt,
cuda_fp16,
trt_fp16,
standalone_trt_fp16,
]
memory = adjust_columns(memory, memory_columns, memory_db_columns, model_group)
return memory
def get_memory_over_time(memory_table):
"""
Returns a new Pandas table with data that tracks the peak memory usage of model/EP inference runs over time.
:param memory_table: The Pandas table containing per model/EP memory usage with the schema:
| Model | ORT-CUDAFp16 | ORT-CUDAFp32 | ... | Group | ...
======================================================================
| resnet.. | 685 | 873 | ... | onnx-zoo-models | ...
:return: A new table in which the EPs are not hardcoded as columns. Ex:
| Model | Group | Ep | MemUsage | ...
============================================================
| resnet.. | onnx-zoo-models | ORT-CUDAFp16 | 685 | ...
| resnet.. | onnx-zoo-models | ORT-CUDAFp32 | 873 | ...
"""
over_time = memory_table.melt(id_vars=[model_title, group_title], var_name="Ep", value_name="MemUsage")
over_time = over_time[
[
model_title,
group_title,
"Ep",
"MemUsage",
]
]
over_time.fillna("", inplace=True)
return over_time
def get_session_over_time(session_table):
"""
Returns a new Pandas table with data that tracks the session creation times of model/EP combinations over time.
:param session_table: The Pandas table containing per model/EP session creation times with the schema:
| Model | ORT-CUDAFp16 | ... | ORT-CUDAFp16_second | Group | ...
=============================================================================
| resnet.. | 1.99 | ... | 0.92 | onnx-zoo-models | ...
:return: A new table in which the EPs are not hardcoded as columns. Ex:
| Model | Group | Ep | SessionCreationTime | SessionCreationTime_second | ...
====================================================================================================
| resnet.. | onnx-zoo-models | ORT-CUDAFp16 | 1.99 | 0.92 | ...
"""
ep_names = [cpu, cuda_fp16, cuda, trt_fp16, trt]
over_time_1 = session_table.melt(
id_vars=[model_title, group_title], value_vars=ep_names, var_name="Ep", value_name="SessionCreationTime"
)
over_time_2 = session_table.melt(
id_vars=[model_title, group_title],
value_vars=[ep + "_second" for ep in ep_names],
value_name="SessionCreationTime_second",
)
over_time = over_time_1.merge(over_time_2[["SessionCreationTime_second"]], left_index=True, right_index=True)
over_time = over_time[
[
model_title,
group_title,
"Ep",
"SessionCreationTime",
"SessionCreationTime_second",
]
]
over_time.fillna("", inplace=True)
return over_time
def get_status_over_time(status_table):
"""
Returns a new Pandas table with data that tracks the compatibility of model/EP combinations over time.
:param status_table: The Pandas table containing per model/EP compatibility ('Pass' or 'Fail') data with the schema:
| Model | ORT-CUDAFp16 | ORT-CUDAFp32 | ... | Group | ...
===========================================================================
| FasterRCNN-10 | Fail | Pass | ... | onnx-zoo-models | ...
:return: A new table in which the EPs are not hardcoded as columns. Ex:
| Model | Group | Ep | Pass | ...
=============================================================
| FasterRCNN-10 | onnx-zoo-models | ORT-CUDAFp16 | 0 | ...
| FasterRCNN-10 | onnx-zoo-models | ORT-CUDAFp32 | 1 | ...
"""
over_time = status_table.melt(id_vars=[model_title, group_title], var_name="Ep", value_name="Pass")
over_time["Pass"] = over_time["Pass"].map(lambda s: 1 if s == "Pass" else 0)
over_time = over_time[
[
model_title,
group_title,
"Ep",
"Pass",
]
]
return over_time
def get_latency(latency, model_group):
"""
Returns a new Pandas table with data that tracks inference run latency per model/EP.
:param latency: The Pandas table containing raw latency data imported from a CSV file.
:param model_group: The model group namespace to append as a column.
:return: The updated table.
"""
latency_columns = [model_title]
for provider in provider_list:
latency_columns.append(provider + avg_ending)
latency_db_columns = table_headers
latency = adjust_columns(latency, latency_columns, latency_db_columns, model_group)
return latency
def get_status(status, model_group):
"""
Returns a new Pandas table with data that tracks whether an EP can successfully run a particular model.
:param status: The Pandas table containing raw model/EP status data imported from a CSV file.
:param model_group: The model group namespace to append as a column.
:return: The updated table.
"""
status_columns = status.keys()
status_db_columns = table_headers
status = adjust_columns(status, status_columns, status_db_columns, model_group)
return status
def get_specs(specs, branch, commit_hash, commit_datetime):
"""
Returns a new Pandas table with data that tracks the configuration/specs/versions of the hardware and software
used to gather benchmarking data.
:param specs: The Pandas table containing raw specs data imported from a CSV file.
:param branch: The name of the git branch corresponding to the version of ORT used to gather data.
:param commit_hash: The short git commit hash corresponding to the version of ORT used to gather data.
:param commit_datetime: The git commit datetime corresponding to the version of ORT used to gather data.
:return: The updated table.
"""
init_id = int(specs.tail(1).get(".", 0)) + 1
specs_additional = pd.DataFrame(
{
".": [init_id, init_id + 1, init_id + 2],
"Spec": ["Branch", "CommitId", "CommitTime"],
"Version": [branch, commit_hash, str(commit_datetime)],
}
)
return pd.concat([specs, specs_additional], ignore_index=True)
def get_session(session, model_group):
"""
Returns a new Pandas table with data that tracks the ORT session creation time for each model/EP combination.
:param session: The Pandas table containing raw model/EP session timing data imported from a CSV file.
:param model_group: The model group namespace to append as a column.
:return: The updated table.
"""
session_columns = session.keys()
session_db_columns = [model_title, *ort_provider_list] + [p + second for p in ort_provider_list]
session = adjust_columns(session, session_columns, session_db_columns, model_group)
return session
def write_table(
ingest_client, database_name, table, table_name, upload_time, identifier, branch, commit_id, commit_date
):
"""
Uploads the provided table to the database. This function also appends the upload time and unique run identifier
to the table.
:param ingest_client: An instance of QueuedIngestClient used to initiate data ingestion.
:param table: The Pandas table to ingest.
:param table_name: The name of the table in the database.
:param upload_time: A datetime object denoting the data's upload time.
:param identifier: An identifier that associates the uploaded data with an ORT commit/date/branch.
"""
if table.empty:
return
# Add upload time and identifier columns to data table.
table = table.assign(UploadTime=str(upload_time))
table = table.assign(Identifier=identifier)
table = table.assign(Branch=branch)
table = table.assign(CommitId=commit_id)
table = table.assign(CommitDate=str(commit_date))
ingestion_props = IngestionProperties(
database=database_name,
table=table_name,
data_format=DataFormat.CSV,
report_level=ReportLevel.FailuresAndSuccesses,
)
# append rows
ingest_client.ingest_from_dataframe(table, ingestion_properties=ingestion_props)
def get_identifier(commit_datetime, commit_hash, trt_version, branch, use_tensorrt_oss_parser):
"""
Returns an identifier that associates uploaded data with an ORT commit/date/branch and a TensorRT version.
:param commit_datetime: The datetime of the ORT commit used to run the benchmarks.
:param commit_hash: The hash of the ORT commit used to run the benchmarks.
:param trt_version: The TensorRT version used to run the benchmarks.
:param branch: The name of the ORT branch used to run the benchmarks.
:return: A string identifier.
"""
date = str(commit_datetime.date()) # extract date only
if use_tensorrt_oss_parser:
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.abspath(os.path.join(current_dir, "../../../../.."))
deps_txt_path = os.path.join(root_dir, "cmake", "deps.txt")
commit_head = ""
with open(deps_txt_path) as file:
for line in file:
parts = line.split(";")
if parts[0] == "onnx_tensorrt":
url = parts[1]
commit = url.split("/")[-1]
commit_head = commit[:6]
break
parser = f"oss_{commit_head}"
else:
parser = "builtin"
return "_".join([date, commit_hash, trt_version, parser, branch])
def main():
"""
Entry point of this script. Uploads data produced by benchmarking scripts to the database.
"""
args = parse_arguments()
# connect to database
kcsb_ingest = KustoConnectionStringBuilder.with_az_cli_authentication(args.kusto_conn)
ingest_client = QueuedIngestClient(kcsb_ingest)
identifier = get_identifier(
args.commit_datetime, args.commit_hash, args.trt_version, args.branch, args.use_tensorrt_oss_parser
)
upload_time = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0)
try:
result_file = args.report_folder
folders = os.listdir(result_file)
os.chdir(result_file)
tables = [
fail_name,
memory_name,
memory_over_time_name,
latency_name,
latency_over_time_name,
status_name,
status_over_time_name,
specs_name,
session_name,
session_over_time_name,
op_metrics_name,
]
table_results = {}
for table_name in tables:
table_results[table_name] = pd.DataFrame()
for model_group in folders:
os.chdir(model_group)
csv_filenames = os.listdir()
for csv in csv_filenames:
table = pd.read_csv(csv)
if session_name in csv:
table_results[session_name] = pd.concat(
[table_results[session_name], get_session(table, model_group)], ignore_index=True
)
elif specs_name in csv:
table_results[specs_name] = pd.concat(
[
table_results[specs_name],
get_specs(table, args.branch, args.commit_hash, args.commit_datetime),
],
ignore_index=True,
)
elif fail_name in csv:
table_results[fail_name] = pd.concat(
[table_results[fail_name], get_failures(table, model_group)],
ignore_index=True,
)
elif latency_name in csv:
table_results[memory_name] = pd.concat(
[table_results[memory_name], get_memory(table, model_group)],
ignore_index=True,
)
table_results[latency_name] = pd.concat(
[table_results[latency_name], get_latency(table, model_group)],
ignore_index=True,
)
elif status_name in csv:
table_results[status_name] = pd.concat(
[table_results[status_name], get_status(table, model_group)], ignore_index=True
)
elif op_metrics_name in csv:
table = table.assign(Group=model_group)
table_results[op_metrics_name] = pd.concat(
[table_results[op_metrics_name], table], ignore_index=True
)
os.chdir(result_file)
if not table_results[memory_name].empty:
table_results[memory_over_time_name] = get_memory_over_time(table_results[memory_name])
if not table_results[latency_name].empty:
table_results[latency_over_time_name] = get_latency_over_time(args.report_url, table_results[latency_name])
if not table_results[session_name].empty:
table_results[session_over_time_name] = get_session_over_time(table_results[session_name])
if not table_results[status_name].empty:
table_results[status_over_time_name] = get_status_over_time(table_results[status_name])
for table in tables:
print("writing " + table + " to database")
db_table_name = "ep_model_" + table
write_table(
ingest_client,
args.database,
table_results[table],
db_table_name,
upload_time,
identifier,
args.branch,
args.commit_hash,
args.commit_datetime,
)
except BaseException as e:
print(str(e))
sys.exit(1)
if __name__ == "__main__":
main()