Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export GenAi-PA results to CSV file #497

Merged
merged 8 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 76 additions & 12 deletions src/c++/perf_analyzer/genai-pa/genai_pa/llm_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import contextlib
import csv
import io
import json
from dataclasses import dataclass
Expand All @@ -47,6 +48,26 @@
class Metrics:
"""A base class for all the metrics class that contains common metrics."""

metric_labels = [
"time_to_first_token",
"inter_token_latency",
"request_latency",
"output_token_throughput",
"request_throughput",
"num_output_token",
]

time_fields = [
"inter_token_latency",
"time_to_first_token",
"request_latency",
]

throughput_fields = [
"request_throughput",
"output_token_throughput",
]

def __init__(
self,
request_throughputs: list[float] = [],
Expand Down Expand Up @@ -152,38 +173,81 @@ def __repr__(self):
attr_strs = ",".join([f"{k}={v}" for k, v in self.__dict__.items()])
return f"Statistics({attr_strs})"

def _is_throughput_field(self, field: str):
return field in Metrics.throughput_fields

def _is_time_field(self, field: str):
time_fields = [
"inter_token_latency",
"time_to_first_token",
"end_to_end_latency",
]
return field in time_fields
return field in Metrics.time_fields

def pretty_print(self):
table = Table(title="PA LLM Metrics")

table.add_column("Statistic", justify="right", style="cyan", no_wrap=True)
stats = ["avg", "min", "max", "p99", "p95", "p90", "p75", "p50", "p25"]
stats = ["avg", "min", "max", "p99", "p90", "p75"]
nv-hwoo marked this conversation as resolved.
Show resolved Hide resolved
for stat in stats:
table.add_column(stat, justify="right", style="green")

metrics = ["inter_token_latency", "time_to_first_token"]
for metric in metrics:
for metric in Metrics.metric_labels:
formatted_metric = metric.replace("_", " ").capitalize()
is_time_field = self._is_time_field(metric)
if is_time_field:
if self._is_time_field(metric):
formatted_metric += " (ns)"
elif self._is_throughput_field(metric):
formatted_metric += " (per sec)"

row_values = [formatted_metric]

for stat in stats:
value = self.__dict__.get(f"{stat}_{metric}", -1)
row_values.append("{:,.0f}".format(value))
table.add_row(*row_values)

# Without streaming, there is no inter-token latency available.
if metric == "inter_token_latency":
if all(value == -1 for value in row_values[1:]):
continue
else:
table.add_row(*row_values)

console = Console()
console.print(table)

def export_to_csv(self, csv_filename: str):
header = [
"Statistic",
"avg",
"min",
"max",
"p99",
"p95",
"p90",
"p75",
"p50",
"p25",
]

with open(csv_filename, mode="w", newline="") as csvfile:
csv_writer = csv.writer(csvfile)
csv_writer.writerow(header)

for metric in Metrics.metric_labels:
formatted_metric = metric
if self._is_time_field(metric):
formatted_metric += "(ns)"
elif self._is_throughput_field(metric):
formatted_metric += "(per sec)"

row_values = [formatted_metric]

for stat in header[1:]:
value = self.__dict__.get(f"{stat}_{metric}", -1)
row_values.append(f"{value:.0f}")

# Without streaming, there is no inter-token latency available.
if metric == "inter_token_latency":
if all(value == -1 for value in row_values[1:]):
continue
else:
csv_writer.writerow(row_values)


class ProfileDataParser:
"""Base profile data parser class that reads the profile data JSON file to
Expand Down
4 changes: 4 additions & 0 deletions src/c++/perf_analyzer/genai-pa/genai_pa/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ def report_output(metrics: LLMProfileDataParser, args):
"Neither concurrency_range nor request_rate_range was found in args when reporting metrics"
)
stats = metrics.get_statistics(infer_mode, int(load_level))
export_csv_name = args.profile_export_file.with_name(
args.profile_export_file.stem + "_genai_pa" + args.profile_export_file.suffix
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be .csv not .json

)
stats.export_to_csv(export_csv_name)
stats.pretty_print()


Expand Down
8 changes: 5 additions & 3 deletions src/c++/perf_analyzer/genai-pa/genai_pa/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,11 @@ def _add_profile_args(parser):
"--profile-export-file",
type=Path,
default="profile_export.json",
help="Specifies the path where the profile export will be "
"generated. By default, the profile export will not be "
"generated.",
help="Specifies the path where the perf_analyzer profile export will be "
"generated. By default, the profile export will be to profile_export.json. "
"The GenAi-PA file will be exported to <profile_export_file>_genai_pa.<file_extension>. "
"For example, if the profile export file is profile_export.json, the GenAi-PA file will be "
"exported to profile_export_genai_pa.json.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

csv file right? not json?

)
load_management_group.add_argument(
"--request-rate",
Expand Down
Loading