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

Replace Argparse with ConfigCommand #266

Merged
merged 36 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fd6fbc6
Ported over logic infer logic from parser
nv-braf Jan 29, 2025
f1c9fd0
Added unit testing for inferred input/endpoint logic
nv-braf Jan 29, 2025
8b1e38a
Completed porting of all inferred and checked fields from parser
nv-braf Jan 29, 2025
21e0fca
Adding missing goodput parsing
nv-braf Jan 29, 2025
6edd605
Initial changes to get profile working
nv-braf Jan 30, 2025
8a7c0c6
CLI unit tests passing
nv-braf Jan 31, 2025
442a515
Fixing CLI tests that I missed
nv-braf Jan 31, 2025
cc1930e
Telemetry tests passing
nv-braf Jan 31, 2025
bbf4fc8
Test artifacts passing
nv-braf Jan 31, 2025
0cb56ca
Telemetry data collector tests passing
nv-braf Jan 31, 2025
505adf0
Passing common unit tests
nv-braf Jan 31, 2025
b7493af
Fixing console exporter unit tests
nv-braf Jan 31, 2025
7fc8903
Fixing CSV exporter unit tests
nv-braf Jan 31, 2025
ebe6688
Partial for for JSON exporter unit tests
nv-braf Jan 31, 2025
8684791
Adding library function to convert BaseConfig into a JSON readable di…
nv-braf Jan 31, 2025
6fd0e30
JSON Exporter unit tests passing
nv-braf Feb 1, 2025
4fc8338
Fixing Tokenizer unit tests
nv-braf Feb 1, 2025
32a2f6c
PA Config unit tests passing
nv-braf Feb 1, 2025
2e5d996
Fixing Results and RunConfig unit tests
nv-braf Feb 1, 2025
1ea7221
All unit tests passing
nv-braf Feb 1, 2025
0f5cc07
Fixing codeql issues
nv-braf Feb 3, 2025
f1bf717
Moving generation of artifact directory into PA Config generator class
nv-braf Feb 3, 2025
5774787
Fixing exporters to use PA config
nv-braf Feb 3, 2025
e7b702f
Progress on analyze. Need to add checkpoint support to base config cl…
nv-braf Feb 3, 2025
de70186
Fixed GAP config generator. All unit tests passing
nv-braf Feb 3, 2025
3750fa2
Getting analyze working with CLI
nv-braf Feb 4, 2025
a55ee3b
Analyze working with config file
nv-braf Feb 4, 2025
82ef958
fixing codeql
nv-braf Feb 4, 2025
e2d9c36
Fixed PA config to work with multiple sweep parameters
nv-braf Feb 4, 2025
2a2778e
Fixing message when config found in checkpoint
nv-braf Feb 4, 2025
aa01418
Refactoring path method
nv-braf Feb 5, 2025
59edd20
Removing commented out lines
nv-braf Feb 5, 2025
b16676a
Fixing issue around specifing url/server metrics url
nv-braf Feb 5, 2025
5103616
Changes based on Elias' PR
nv-braf Feb 11, 2025
9093f86
Fixing codeql issue
nv-braf Feb 12, 2025
a752b3e
Missing check for None
nv-braf Feb 12, 2025
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
326 changes: 126 additions & 200 deletions genai-perf/genai_perf/config/generate/perf_analyzer_config.py

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions genai-perf/genai_perf/config/input/base_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
# limitations under the License.

from copy import deepcopy
from enum import Enum
from pathlib import PosixPath

from genai_perf.config.input.config_defaults import Range
from genai_perf.config.input.config_field import ConfigField


Expand Down Expand Up @@ -44,6 +47,32 @@

return self._fields[name]

def to_json(self):
config_dict = {}
for key, value in self._values.items():
if isinstance(value, BaseConfig):
config_dict[key] = value.to_json()
else:
config_dict[key] = self._get_legal_json_value(value)

return config_dict

def _get_legal_json_value(self, value):
if isinstance(value, Enum):
return value.name.lower()
elif isinstance(value, PosixPath):
return str(value)
elif hasattr(value, "__dict__"):
return value.__dict__()
elif isinstance(value, dict):
config_dict = {}
for k, v in value.items():
config_dict[k] = self._get_legal_json_value(v)

return config_dict
else:
return value

def __setattr__(self, name, value):
# This prevents recursion failure in __init__
if name == "_fields" or name == "_values" or name == "_children":
Expand Down
2 changes: 1 addition & 1 deletion genai-perf/genai_perf/config/input/config_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ConfigAnalyze(BaseConfig):
Describes the configuration for the analyze subcommand
"""

def __init__(self):
def __init__(self) -> None:
super().__init__()
self.sweep_parameters: Any = ConfigField(
default=AnalyzeDefaults.SWEEP_PARAMETER, choices=all_parameters
Expand Down
108 changes: 103 additions & 5 deletions genai-perf/genai_perf/config/input/config_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
# limitations under the License.

from enum import Enum, auto
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, TypeAlias, Union

import genai_perf.logging as logging
from genai_perf.config.input.base_config import BaseConfig
from genai_perf.config.input.config_analyze import ConfigAnalyze
from genai_perf.config.input.config_defaults import (
Expand All @@ -28,18 +30,20 @@
from genai_perf.config.input.config_output import ConfigOutput
from genai_perf.config.input.config_perf_analyzer import ConfigPerfAnalyzer
from genai_perf.config.input.config_tokenizer import ConfigTokenizer
from genai_perf.inputs.input_constants import ModelSelectionStrategy, OutputFormat
from genai_perf.inputs.input_constants import OutputFormat
from genai_perf.types import CheckpointObject, ModelName


class Subcommand(Enum):
COMPARE = auto()
PROFILE = auto()
ANALYZE = auto()
COMPARE = "compare"
PROFILE = "profile"
ANALYZE = "analyze"


ConfigRangeOrList: TypeAlias = Optional[Union[Range, List[int]]]

logger = logging.getLogger(__name__)


class ConfigCommand(BaseConfig):
"""
Expand All @@ -66,7 +70,7 @@ def __init__(self, user_config: Optional[Dict[str, Any]] = None):
# Top-Level Parsing Methods
###########################################################################
def _parse_yaml(self, user_config: Optional[Dict[str, Any]] = None) -> None:
if user_config is None:
if not user_config:
return

for key, value in user_config.items():
Expand All @@ -89,6 +93,11 @@ def _parse_yaml(self, user_config: Optional[Dict[str, Any]] = None) -> None:
f"User Config: {key} is not a valid top-level parameter"
)

self._infer_settings()
self._check_for_illegal_combinations()
self._set_artifact_directory()
self._set_profile_export_file()

def _parse_model_names(self, model_names: str) -> None:
if type(model_names) is str:
self.model_names = [model_names]
Expand All @@ -97,6 +106,95 @@ def _parse_model_names(self, model_names: str) -> None:
else:
raise ValueError("User Config: model_names must be a string or list")

###########################################################################
# Infer Methods
###########################################################################
def _infer_settings(self) -> None:
self.endpoint.infer_settings(model_name=self.model_names[0])
self.input.infer_settings()

###########################################################################
# Illegal Combination Methods
###########################################################################
def _check_for_illegal_combinations(self) -> None:
self._check_output_tokens_and_service_kind()
self._check_output_format_and_generate_plots()

self.endpoint.check_for_illegal_combinations()

def _check_output_tokens_and_service_kind(self) -> None:
if self.endpoint.service_kind not in ["triton", "tensorrtllm_engine"]:
if self.input.output_tokens.get_field("deterministic").is_set_by_user:
raise ValueError(
"User Config: input.output_tokens.deterministic is only supported with Triton or TensorRT-LLM Engine service kinds"
)

def _check_output_format_and_generate_plots(self) -> None:
if self.endpoint.output_format in [
OutputFormat.IMAGE_RETRIEVAL,
OutputFormat.NVCLIP,
OutputFormat.OPENAI_EMBEDDINGS,
OutputFormat.RANKINGS,
]:
if self.output.generate_plots:
raise ValueError(
"User Config: generate_plots is not supported with the {self.endpoint.output_format} output format"
)

###########################################################################
# Set Path Methods
###########################################################################
def _set_artifact_directory(self) -> None:
if not self.output.get_field("artifact_directory").is_set_by_user:
model_name = self.model_names[0]

# Preprocess Huggingface model names that include '/' in their model name.
if (model_name is not None) and ("/" in model_name):
filtered_name = "_".join(model_name.split("/"))
logger.info(
f"Model name '{model_name}' cannot be used to create artifact "
f"directory. Instead, '{filtered_name}' will be used."
)
name = [f"{filtered_name}"]
else:
name = [f"{model_name}"]

if self.endpoint.service_kind == "openai":
name += [f"{self.endpoint.service_kind}-{self.endpoint.type}"]
elif self.endpoint.service_kind == "triton":
name += [
f"{self.endpoint.service_kind}-{self.endpoint.backend.to_lowercase()}"
]
elif self.endpoint.service_kind == "tensorrtllm_engine":
name += [f"{self.endpoint.service_kind}"]
else:
raise ValueError(
f"Unknown service kind '{self.endpoint.service_kind}'."
)

if "concurrency" in self.perf_analyzer.stimulus:
concurrency = self.perf_analyzer.stimulus["concurrency"]
name += [f"concurrency{concurrency}"]
elif "request_rate" in self.perf_analyzer.stimulus:
request_rate = self.perf_analyzer.stimulus["request_rate"]
name += [f"request_rate{request_rate}"]

self.output.artifact_directory = self.output.artifact_directory / Path(
"-".join(name)
)

def _set_profile_export_file(self) -> None:
if self.output.get_field("profile_export_file").is_set_by_user:
if Path(self.output.profile_export_file).parent != Path(""):
raise ValueError(
"Please use artifact_directory option to define intermediary paths to "
"the profile_export_file."
)

self.output.profile_export_file = (
self.output.artifact_directory / self.output.profile_export_file
)

###########################################################################
# Utility Methods
###########################################################################
Expand Down
11 changes: 7 additions & 4 deletions genai-perf/genai_perf/config/input/config_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class Range:
min: int
max: int

def __dict__(self):
return {"min": self.min, "max": self.max}


@dataclass(frozen=True)
class TopLevelDefaults:
Expand Down Expand Up @@ -59,17 +62,17 @@ class EndPointDefaults:
TYPE = ""
SERVICE_KIND = "triton"
STREAMING = False
SERVER_METRICS_URL = ""
URL = ""
SERVER_METRICS_URL = ["http://localhost:8002/metrics"]
URL = "http://localhost:8001"


@dataclass(frozen=True)
class PerfAnalyzerDefaults:
PATH = "./perf_analyzer"
PATH = "perf_analyzer"
VERBOSE = False
STIMULUS = {"concurrency": 1}
STABILITY_PERCENTAGE = 999
MEASUREMENT_INTERVAL = 10000
SKIP_ARGS = False


@dataclass(frozen=True)
Expand Down
Loading
Loading