Skip to content

Commit 70cb0da

Browse files
Add Support for Templating to GAP (#283)
* Adding support for templating to GAP * Potential fix for code scanning alert no. 159: Unused import Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Fixing isort issue created by autofix * Refactoring template creation * Refactoring parser * Adding missing verbose template comment * Reformatting sweep template comment --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 6a38035 commit 70cb0da

15 files changed

+438
-83
lines changed

genai-perf/genai_perf/checkpoint/checkpoint.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# Copyright 2024-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -47,6 +47,8 @@ def __post_init__(self):
4747
# Read/Write Methods
4848
###########################################################################
4949
def create_checkpoint_object(self) -> None:
50+
os.makedirs(self.config.output.checkpoint_directory, exist_ok=True)
51+
5052
state_dict = {"Results": self.results.create_checkpoint_object()}
5153

5254
checkpoint_file_path = self._create_checkpoint_file_path()

genai-perf/genai_perf/config/input/base_config.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import copy
1616
from enum import Enum
1717
from pathlib import PosixPath
18+
from textwrap import indent
19+
from typing import Any, Dict
1820

1921
from genai_perf.config.input.config_field import ConfigField
2022

@@ -40,13 +42,16 @@ def __init__(self):
4042
# This exists just to make looking up values when debugging easier
4143
self._values = {}
4244

43-
def get_field(self, name):
45+
###########################################################################
46+
# Top-Level Methods
47+
###########################################################################
48+
def get_field(self, name) -> ConfigField:
4449
if name not in self._fields:
4550
raise ValueError(f"{name} not found in ConfigFields")
4651

4752
return self._fields[name]
4853

49-
def to_json_dict(self):
54+
def to_json_dict(self) -> Dict[str, Any]:
5055
config_dict = {}
5156
for key, value in self._values.items():
5257
if isinstance(value, BaseConfig):
@@ -56,7 +61,7 @@ def to_json_dict(self):
5661

5762
return config_dict
5863

59-
def _get_legal_json_value(self, value):
64+
def _get_legal_json_value(self, value: Any) -> Any:
6065
if isinstance(value, Enum):
6166
return value.name.lower()
6267
elif isinstance(value, PosixPath):
@@ -82,6 +87,75 @@ def _get_legal_json_value(self, value):
8287
else:
8388
raise ValueError(f"Value {value} is not a legal JSON value")
8489

90+
###########################################################################
91+
# Template Creation Methods
92+
###########################################################################
93+
def create_template(self, header: str, level: int = 1, verbose=False) -> str:
94+
indention = " " * level
95+
96+
template = self._add_header_to_template(header, indention)
97+
template += self._add_fields_to_template(indention, verbose)
98+
template += "\n"
99+
template += self._add_children_to_template(level, verbose)
100+
101+
return template
102+
103+
def _add_header_to_template(self, header: str, indention: str) -> str:
104+
template = ""
105+
if header:
106+
template = indent(f"{header}:\n", indention)
107+
return template
108+
109+
def _add_fields_to_template(self, indention: str, verbose: bool) -> str:
110+
template = ""
111+
for name, field in self._fields.items():
112+
template_comment = self._get_template_comment(field, verbose)
113+
template += self._create_template_from_comment(template_comment, indention)
114+
template += self._add_field_to_template(field, name, indention)
115+
116+
if verbose and field.verbose_template_comment:
117+
template += "\n"
118+
119+
return template
120+
121+
def _add_children_to_template(self, level: int, verbose: bool) -> str:
122+
template = ""
123+
for name, child in self._children.items():
124+
template += child.create_template(
125+
header=name, level=level + 1, verbose=verbose
126+
)
127+
128+
return template
129+
130+
def _get_template_comment(self, field: ConfigField, verbose: bool) -> str:
131+
if verbose and field.verbose_template_comment:
132+
return field.verbose_template_comment
133+
else:
134+
return field.template_comment if field.template_comment else ""
135+
136+
def _create_template_from_comment(self, comment: str, indention: str) -> str:
137+
template = ""
138+
if comment:
139+
comment_lines = comment.split("\n")
140+
for comment_line in comment_lines:
141+
template += indent(f" # {comment_line}\n", indention)
142+
143+
return template
144+
145+
def _add_field_to_template(
146+
self, field: ConfigField, name: str, indention: str
147+
) -> str:
148+
template = ""
149+
if field.add_to_template:
150+
template = indent(
151+
f" {name}: {self._get_legal_json_value(self.__getattr__(name))}\n",
152+
indention,
153+
)
154+
return template
155+
156+
###########################################################################
157+
# Dunder Methods
158+
###########################################################################
85159
def __setattr__(self, name, value):
86160
# This prevents recursion failure in __init__
87161
if name == "_fields" or name == "_values" or name == "_children":

genai-perf/genai_perf/config/input/config_analyze.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,29 @@ class ConfigAnalyze(BaseConfig):
3131

3232
def __init__(self) -> None:
3333
super().__init__()
34+
# yapf: disable
35+
sweep_parameter_template_comment = \
36+
(f"Uncomment the lines below to enable the analyze subcommand\n"
37+
f"For further details see analyze.md sweep_parameters:\n"
38+
f" concurrency:\n"
39+
f" start: {AnalyzeDefaults.MIN_CONCURRENCY}\n"
40+
f" stop: {AnalyzeDefaults.MAX_CONCURRENCY}")
41+
# yapf: enable
42+
3443
self.sweep_parameters: Any = ConfigField(
35-
default=AnalyzeDefaults.SWEEP_PARAMETER, choices=all_parameters
44+
default=AnalyzeDefaults.SWEEP_PARAMETER,
45+
choices=all_parameters,
46+
add_to_template=False,
47+
template_comment=sweep_parameter_template_comment,
3648
)
3749

50+
###########################################################################
51+
# Parsing Methods
52+
###########################################################################
3853
def parse(self, analyze: Dict[str, Any]) -> None:
54+
if not analyze:
55+
return
56+
3957
sweep_parameters: Dict[str, Any] = {}
4058
for sweep_type, range_dict in analyze.items():
4159
if (

genai-perf/genai_perf/config/input/config_command.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Subcommand(Enum):
3838
COMPARE = "compare"
3939
PROFILE = "profile"
4040
ANALYZE = "analyze"
41+
TEMPLATE = "create-template"
4142

4243

4344
ConfigRangeOrList: TypeAlias = Optional[Union[Range, List[int]]]
@@ -54,7 +55,10 @@ def __init__(self, user_config: Optional[Dict[str, Any]] = None):
5455
super().__init__()
5556

5657
self.model_names: Any = ConfigField(
57-
default=TopLevelDefaults.MODEL_NAME, required=True
58+
default=TopLevelDefaults.MODEL_NAME,
59+
required=True,
60+
add_to_template=True,
61+
verbose_template_comment="The name of the model(s) to benchmark.",
5862
)
5963

6064
self.analyze = ConfigAnalyze()
@@ -195,6 +199,12 @@ def _process_stimulus(self) -> List[str]:
195199
else:
196200
return []
197201

202+
###########################################################################
203+
# Template Creation Methods
204+
###########################################################################
205+
def make_template(self) -> str:
206+
return self.create_template(header="", level=0, verbose=self.verbose)
207+
198208
###########################################################################
199209
# Utility Methods
200210
###########################################################################

genai-perf/genai_perf/config/input/config_defaults.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from dataclasses import dataclass, field
1717

1818
from genai_perf.inputs.input_constants import ModelSelectionStrategy, OutputFormat
19+
from genai_perf.inputs.retrievers.synthetic_image_generator import ImageFormat
1920

2021

2122
def default_field(obj):
@@ -62,8 +63,8 @@ class EndPointDefaults:
6263
TYPE = ""
6364
SERVICE_KIND = "triton"
6465
STREAMING = False
65-
SERVER_METRICS_URL = None
66-
URL = ""
66+
SERVER_METRICS_URL = ["http://localhost:8002/metrics"]
67+
URL = "localhost:8001"
6768

6869

6970
@dataclass(frozen=True)
@@ -82,7 +83,7 @@ class ImageDefaults:
8283
WIDTH_STDDEV = 0
8384
HEIGHT_MEAN = 100
8485
HEIGHT_STDDEV = 0
85-
FORMAT = None
86+
FORMAT = ImageFormat.PNG
8687

8788

8889
@dataclass(frozen=True)
@@ -113,18 +114,18 @@ class RequestCountDefaults:
113114
@dataclass(frozen=True)
114115
class InputDefaults:
115116
BATCH_SIZE = 1
116-
EXTRA = None
117-
GOODPUT = None
118-
HEADER = None
119-
FILE = None
117+
EXTRA = ""
118+
GOODPUT = ""
119+
HEADER = ""
120+
FILE = ""
120121
NUM_DATASET_ENTRIES = 100
121122
RANDOM_SEED = 0
122123

123124

124125
@dataclass(frozen=True)
125126
class OutputDefaults:
126127
ARTIFACT_DIRECTORY = "./artifacts"
127-
CHECKPOINT_DIRECTORY = "./"
128+
CHECKPOINT_DIRECTORY = "./checkpoint"
128129
PROFILE_EXPORT_FILE = "profile_export.json"
129130
GENERATE_PLOTS = False
130131

@@ -134,3 +135,8 @@ class TokenizerDefaults:
134135
NAME = "hf-internal-testing/llama-tokenizer"
135136
REVISION = "main"
136137
TRUST_REMOTE_CODE = False
138+
139+
140+
@dataclass(frozen=True)
141+
class TemplateDefaults:
142+
FILENAME = "genai_perf_config.yaml"

genai-perf/genai_perf/config/input/config_endpoint.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,46 @@ def __init__(self) -> None:
3232
self.model_selection_strategy: Any = ConfigField(
3333
default=EndPointDefaults.MODEL_SELECTION_STRATEGY,
3434
choices=ModelSelectionStrategy,
35+
verbose_template_comment="When multiple model are specified, this is how a specific model should be assigned to a prompt.\
36+
\nround_robin: nth prompt in the list gets assigned to n-mod len(models).\
37+
\nrandom: assignment is uniformly random",
3538
)
3639
self.backend: Any = ConfigField(
37-
default=EndPointDefaults.BACKEND, choices=OutputFormat
40+
default=EndPointDefaults.BACKEND,
41+
choices=OutputFormat,
42+
verbose_template_comment="When using the \"triton\" service-kind, this is the backend of the model.\
43+
\nFor the TENSORRT-LLM backend,you currently must set 'exclude_input_in_output' to true\
44+
\nin the model config to not echo the input tokens",
45+
)
46+
47+
self.custom: Any = ConfigField(
48+
default=EndPointDefaults.CUSTOM,
49+
verbose_template_comment="Set a custom endpoint that differs from the OpenAI defaults.",
3850
)
39-
self.custom: Any = ConfigField(default=EndPointDefaults.CUSTOM)
4051
self.type: Any = ConfigField(
4152
default=EndPointDefaults.TYPE,
4253
choices=list(endpoint_type_map.keys()),
54+
verbose_template_comment="The type to send requests to on the server.",
4355
)
4456
self.service_kind: Any = ConfigField(
4557
default=EndPointDefaults.SERVICE_KIND,
4658
choices=["triton", "openai", "tensorrtllm_engine"],
59+
verbose_template_comment='The kind of service Perf Analyzer will generate load for.\
60+
\nIn order to use "openai", you must specify an api via the "type" field',
61+
)
62+
self.streaming: Any = ConfigField(
63+
default=EndPointDefaults.STREAMING,
64+
verbose_template_comment="An option to enable the use of the streaming API.",
4765
)
48-
self.streaming: Any = ConfigField(default=EndPointDefaults.STREAMING)
4966
self.server_metrics_urls: Any = ConfigField(
50-
default=EndPointDefaults.SERVER_METRICS_URL
67+
default=EndPointDefaults.SERVER_METRICS_URL,
68+
verbose_template_comment='The list of Triton server metrics URLs.\
69+
\nThese are used for Telemetry metric reporting with the "triton" service-kind.',
70+
)
71+
self.url: Any = ConfigField(
72+
default=EndPointDefaults.URL,
73+
verbose_template_comment="URL of the endpoint to target for benchmarking.",
5174
)
52-
self.url: Any = ConfigField(default=EndPointDefaults.URL)
5375

5476
def parse(self, endpoint: Dict[str, Any]) -> None:
5577
for key, value in endpoint.items():

genai-perf/genai_perf/config/input/config_field.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(
3838
required: bool = False,
3939
add_to_template: bool = True,
4040
template_comment: Optional[str] = None,
41+
verbose_template_comment: Optional[str] = None,
4142
value: Optional[Any] = None,
4243
bounds: Optional[Dict[str, Any]] = None,
4344
choices: Optional[Any] = None,
@@ -46,6 +47,7 @@ def __init__(
4647
self.required = required
4748
self.add_to_template = add_to_template
4849
self.template_comment = template_comment
50+
self.verbose_template_comment = verbose_template_comment
4951
self.bounds = bounds
5052
self.choices = choices
5153
self.is_set_by_user = False

0 commit comments

Comments
 (0)