Skip to content

Commit af09cf8

Browse files
nv-hwoomc-nv
authored andcommitted
Generate plot configurations for plot manager (#632)
* Introduce PlotConfig and PlotConfigParser class * Port preprocessing steps and introduce ProfileRunData * Create plot configs for default plots * fix minor bug * Fix comment * Implement parse method in PlotConfigParser * refactor * fix test * Add test * Address feedback * Handle custom endpoint
1 parent ed2c237 commit af09cf8

File tree

10 files changed

+465
-31
lines changed

10 files changed

+465
-31
lines changed

src/c++/perf_analyzer/genai-perf/genai_perf/llm_metrics.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,24 @@
2828

2929
import csv
3030
import json
31+
from enum import Enum, auto
3132
from itertools import pairwise
33+
from pathlib import Path
3234
from typing import List
3335

3436
import numpy as np
3537
import pandas as pd
3638
from genai_perf.constants import DEFAULT_ARTIFACT_DIR
37-
from genai_perf.llm_inputs.llm_inputs import OutputFormat
3839
from genai_perf.tokenizer import Tokenizer
3940
from genai_perf.utils import load_json, remove_sse_prefix
4041
from rich.console import Console
4142
from rich.table import Table
4243

43-
_OPENAI_CHAT_COMPLETIONS = OutputFormat.OPENAI_CHAT_COMPLETIONS
44-
_OPENAI_COMPLETIONS = OutputFormat.OPENAI_COMPLETIONS
44+
45+
class ResponseFormat(Enum):
46+
OPENAI_CHAT_COMPLETIONS = auto()
47+
OPENAI_COMPLETIONS = auto()
48+
TRITON = auto()
4549

4650

4751
class Metrics:
@@ -401,10 +405,36 @@ class ProfileDataParser:
401405
extract core metrics and calculate various performance statistics.
402406
"""
403407

404-
def __init__(self, filename: str) -> None:
408+
def __init__(self, filename: Path) -> None:
405409
data = load_json(filename)
410+
self._get_profile_metadata(data)
406411
self._parse_profile_data(data)
407412

413+
def _get_profile_metadata(self, data: dict) -> None:
414+
self._service_kind = data["service_kind"]
415+
if self._service_kind == "openai":
416+
if data["endpoint"] == "v1/chat/completions":
417+
self._response_format = ResponseFormat.OPENAI_CHAT_COMPLETIONS
418+
elif data["endpoint"] == "v1/completions":
419+
self._response_format = ResponseFormat.OPENAI_COMPLETIONS
420+
else:
421+
# TPA-66: add PA metadata to handle this case
422+
# When endpoint field is either empty or custom endpoint, fall
423+
# back to parsing the response to extract the response format.
424+
request = data["experiments"][0]["requests"][0]
425+
response = request["response_outputs"][0]["response"]
426+
if "chat.completion" in response:
427+
self._response_format = ResponseFormat.OPENAI_CHAT_COMPLETIONS
428+
elif "text_completion" in response:
429+
self._response_format = ResponseFormat.OPENAI_COMPLETIONS
430+
else:
431+
raise RuntimeError("Unknown OpenAI response format.")
432+
433+
elif self._service_kind == "triton":
434+
self._response_format = ResponseFormat.TRITON
435+
else:
436+
raise ValueError(f"Unknown service kind: {self._service_kind}")
437+
408438
def _parse_profile_data(self, data: dict) -> None:
409439
"""Parse through the entire profile data to collect statistics."""
410440
self._profile_results = {}
@@ -429,6 +459,10 @@ def get_statistics(self, infer_mode: str, load_level: str) -> Statistics:
429459
raise KeyError(f"Profile with {infer_mode}={load_level} does not exist.")
430460
return self._profile_results[(infer_mode, load_level)]
431461

462+
def get_profile_load_info(self) -> list[tuple[str, str]]:
463+
"""Return available (infer_mode, load_level) tuple keys."""
464+
return [k for k, _ in self._profile_results.items()]
465+
432466

433467
class LLMProfileDataParser(ProfileDataParser):
434468
"""A class that calculates and aggregates all the LLM performance statistics
@@ -447,7 +481,6 @@ class LLMProfileDataParser(ProfileDataParser):
447481
>>> tokenizer = AutoTokenizer.from_pretrained("gpt2")
448482
>>> pd = LLMProfileDataParser(
449483
>>> filename="profile_export.json",
450-
>>> service_kind="triton",
451484
>>> tokenizer=tokenizer,
452485
>>> )
453486
>>> stats = pd.get_statistics(infer_mode="concurrency", level=10)
@@ -458,14 +491,10 @@ class LLMProfileDataParser(ProfileDataParser):
458491

459492
def __init__(
460493
self,
461-
filename: str,
462-
service_kind: str,
463-
output_format: OutputFormat,
494+
filename: Path,
464495
tokenizer: Tokenizer,
465496
) -> None:
466497
self._tokenizer = tokenizer
467-
self._service_kind = service_kind
468-
self._output_format = output_format
469498
super().__init__(filename)
470499

471500
def _parse_requests(self, requests: dict) -> LLMMetrics:
@@ -591,9 +620,9 @@ def _tokenize_triton_request_input(self, req_inputs: dict) -> list[int]:
591620
def _tokenize_openai_request_input(self, req_inputs: dict) -> list[int]:
592621
"""Tokenize the OpenAI request input texts."""
593622
payload = json.loads(req_inputs["payload"])
594-
if self._output_format == _OPENAI_CHAT_COMPLETIONS:
623+
if self._response_format == ResponseFormat.OPENAI_CHAT_COMPLETIONS:
595624
input_text = payload["messages"][0]["content"]
596-
elif self._output_format == _OPENAI_COMPLETIONS:
625+
elif self._response_format == ResponseFormat.OPENAI_COMPLETIONS:
597626
input_text = payload["prompt"]
598627
else:
599628
raise ValueError(

src/c++/perf_analyzer/genai-perf/genai_perf/main.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from genai_perf.exceptions import GenAIPerfException
3939
from genai_perf.llm_inputs.llm_inputs import LlmInputs
4040
from genai_perf.llm_metrics import LLMProfileDataParser, Statistics
41+
from genai_perf.plots.plot_config_parser import PlotConfigParser
4142
from genai_perf.plots.plot_manager import PlotManager
4243
from genai_perf.tokenizer import Tokenizer, get_tokenizer
4344

@@ -87,8 +88,6 @@ def generate_inputs(args: Namespace, tokenizer: Tokenizer) -> None:
8788
def calculate_metrics(args: Namespace, tokenizer: Tokenizer) -> LLMProfileDataParser:
8889
return LLMProfileDataParser(
8990
filename=args.profile_export_file,
90-
service_kind=args.service_kind,
91-
output_format=args.output_format,
9291
tokenizer=tokenizer,
9392
)
9493

@@ -111,12 +110,18 @@ def report_output(data_parser: LLMProfileDataParser, args: Namespace) -> None:
111110
stats.export_parquet(DEFAULT_PARQUET_FILE)
112111
stats.pretty_print()
113112
if args.generate_plots:
114-
create_plots(stats)
113+
create_plots(args.profile_export_file)
114+
115115

116+
def create_plots(filename: Path) -> None:
117+
output_dir = Path(f"{DEFAULT_ARTIFACT_DIR}/plots")
118+
PlotConfigParser.create_init_yaml_config([filename], output_dir)
119+
config_parser = PlotConfigParser(output_dir / "config.yaml")
120+
plot_configs = config_parser.generate_configs()
116121

117-
def create_plots(stats: Statistics) -> None:
118-
plot_manager = PlotManager(stats)
119-
plot_manager.create_default_plots()
122+
# TODO (harshini): plug-in configs to plot manager
123+
# plot_manager = PlotManager(stats)
124+
# plot_manager.create_default_plots()
120125

121126

122127
def finalize(profile_export_file: Path):

src/c++/perf_analyzer/genai-perf/genai_perf/parser.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import genai_perf.utils as utils
3333
from genai_perf.constants import CNN_DAILY_MAIL, OPEN_ORCA
3434
from genai_perf.llm_inputs.llm_inputs import LlmInputs, OutputFormat, PromptSource
35+
from genai_perf.plots.plot_config_parser import PlotConfigParser
36+
from genai_perf.plots.plot_manager import PlotManager
3537
from genai_perf.tokenizer import DEFAULT_TOKENIZER
3638

3739
from . import __version__
@@ -488,8 +490,17 @@ def profile_handler(args, extra_args):
488490

489491

490492
def compare_handler(args: argparse.Namespace):
491-
# TMA-1893: parse yaml file
492-
pass
493+
"""Handles `compare` subcommand workflow."""
494+
if args.files:
495+
PlotConfigParser.create_init_yaml_config(args.files, Path("."))
496+
args.config = Path("config.yaml")
497+
498+
config_parser = PlotConfigParser(args.config)
499+
plot_configs = config_parser.generate_configs()
500+
501+
# TODO (harshini): plug-in configs to PlotManager
502+
# plot_manager = PlotManager(plot_configs)
503+
# plot_manager.generate_plots()
493504

494505

495506
### Entrypoint ###
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions
6+
# are met:
7+
# * Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
# * Redistributions in binary form must reproduce the above copyright
10+
# notice, this list of conditions and the following disclaimer in the
11+
# documentation and/or other materials provided with the distribution.
12+
# * Neither the name of NVIDIA CORPORATION nor the names of its
13+
# contributors may be used to endorse or promote products derived
14+
# from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
17+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24+
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env python3
2+
# Copyright 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
#
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions
6+
# are met:
7+
# * Redistributions of source code must retain the above copyright
8+
# notice, this list of conditions and the following disclaimer.
9+
# * Redistributions in binary form must reproduce the above copyright
10+
# notice, this list of conditions and the following disclaimer in the
11+
# documentation and/or other materials provided with the distribution.
12+
# * Neither the name of NVIDIA CORPORATION nor the names of its
13+
# contributors may be used to endorse or promote products derived
14+
# from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
17+
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23+
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24+
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
28+
from collections.abc import Sequence
29+
from dataclasses import dataclass
30+
from enum import Enum, auto
31+
from pathlib import Path
32+
33+
34+
class PlotType(Enum):
35+
SCATTER = auto()
36+
BOX = auto()
37+
HEATMAP = auto()
38+
39+
40+
@dataclass
41+
class ProfileRunData:
42+
name: str
43+
x_metric: Sequence[int | float]
44+
y_metric: Sequence[int | float]
45+
46+
47+
@dataclass
48+
class PlotConfig:
49+
title: str
50+
data: list[ProfileRunData]
51+
x_label: str
52+
y_label: str
53+
type: PlotType
54+
output: Path

0 commit comments

Comments
 (0)