triton-model-analyzer 1.48.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- model_analyzer/__init__.py +15 -0
- model_analyzer/analyzer.py +448 -0
- model_analyzer/cli/__init__.py +15 -0
- model_analyzer/cli/cli.py +193 -0
- model_analyzer/config/__init__.py +15 -0
- model_analyzer/config/generate/__init__.py +15 -0
- model_analyzer/config/generate/automatic_model_config_generator.py +164 -0
- model_analyzer/config/generate/base_model_config_generator.py +352 -0
- model_analyzer/config/generate/brute_plus_binary_parameter_search_run_config_generator.py +164 -0
- model_analyzer/config/generate/brute_run_config_generator.py +154 -0
- model_analyzer/config/generate/concurrency_sweeper.py +75 -0
- model_analyzer/config/generate/config_generator_interface.py +52 -0
- model_analyzer/config/generate/coordinate.py +143 -0
- model_analyzer/config/generate/coordinate_data.py +86 -0
- model_analyzer/config/generate/generator_utils.py +116 -0
- model_analyzer/config/generate/manual_model_config_generator.py +187 -0
- model_analyzer/config/generate/model_config_generator_factory.py +92 -0
- model_analyzer/config/generate/model_profile_spec.py +74 -0
- model_analyzer/config/generate/model_run_config_generator.py +154 -0
- model_analyzer/config/generate/model_variant_name_manager.py +150 -0
- model_analyzer/config/generate/neighborhood.py +536 -0
- model_analyzer/config/generate/optuna_plus_concurrency_sweep_run_config_generator.py +141 -0
- model_analyzer/config/generate/optuna_run_config_generator.py +838 -0
- model_analyzer/config/generate/perf_analyzer_config_generator.py +312 -0
- model_analyzer/config/generate/quick_plus_concurrency_sweep_run_config_generator.py +130 -0
- model_analyzer/config/generate/quick_run_config_generator.py +753 -0
- model_analyzer/config/generate/run_config_generator_factory.py +329 -0
- model_analyzer/config/generate/search_config.py +112 -0
- model_analyzer/config/generate/search_dimension.py +73 -0
- model_analyzer/config/generate/search_dimensions.py +85 -0
- model_analyzer/config/generate/search_parameter.py +49 -0
- model_analyzer/config/generate/search_parameters.py +388 -0
- model_analyzer/config/input/__init__.py +15 -0
- model_analyzer/config/input/config_command.py +483 -0
- model_analyzer/config/input/config_command_profile.py +1747 -0
- model_analyzer/config/input/config_command_report.py +267 -0
- model_analyzer/config/input/config_defaults.py +236 -0
- model_analyzer/config/input/config_enum.py +83 -0
- model_analyzer/config/input/config_field.py +216 -0
- model_analyzer/config/input/config_list_generic.py +112 -0
- model_analyzer/config/input/config_list_numeric.py +151 -0
- model_analyzer/config/input/config_list_string.py +111 -0
- model_analyzer/config/input/config_none.py +71 -0
- model_analyzer/config/input/config_object.py +129 -0
- model_analyzer/config/input/config_primitive.py +81 -0
- model_analyzer/config/input/config_status.py +75 -0
- model_analyzer/config/input/config_sweep.py +83 -0
- model_analyzer/config/input/config_union.py +113 -0
- model_analyzer/config/input/config_utils.py +128 -0
- model_analyzer/config/input/config_value.py +243 -0
- model_analyzer/config/input/objects/__init__.py +15 -0
- model_analyzer/config/input/objects/config_model_profile_spec.py +325 -0
- model_analyzer/config/input/objects/config_model_report_spec.py +173 -0
- model_analyzer/config/input/objects/config_plot.py +198 -0
- model_analyzer/config/input/objects/config_protobuf_utils.py +101 -0
- model_analyzer/config/input/yaml_config_validator.py +82 -0
- model_analyzer/config/run/__init__.py +15 -0
- model_analyzer/config/run/model_run_config.py +313 -0
- model_analyzer/config/run/run_config.py +168 -0
- model_analyzer/constants.py +76 -0
- model_analyzer/device/__init__.py +15 -0
- model_analyzer/device/device.py +24 -0
- model_analyzer/device/gpu_device.py +87 -0
- model_analyzer/device/gpu_device_factory.py +248 -0
- model_analyzer/entrypoint.py +307 -0
- model_analyzer/log_formatter.py +65 -0
- model_analyzer/model_analyzer_exceptions.py +24 -0
- model_analyzer/model_manager.py +255 -0
- model_analyzer/monitor/__init__.py +15 -0
- model_analyzer/monitor/cpu_monitor.py +69 -0
- model_analyzer/monitor/dcgm/DcgmDiag.py +191 -0
- model_analyzer/monitor/dcgm/DcgmFieldGroup.py +83 -0
- model_analyzer/monitor/dcgm/DcgmGroup.py +815 -0
- model_analyzer/monitor/dcgm/DcgmHandle.py +141 -0
- model_analyzer/monitor/dcgm/DcgmJsonReader.py +69 -0
- model_analyzer/monitor/dcgm/DcgmReader.py +623 -0
- model_analyzer/monitor/dcgm/DcgmStatus.py +57 -0
- model_analyzer/monitor/dcgm/DcgmSystem.py +412 -0
- model_analyzer/monitor/dcgm/__init__.py +15 -0
- model_analyzer/monitor/dcgm/common/__init__.py +13 -0
- model_analyzer/monitor/dcgm/common/dcgm_client_cli_parser.py +194 -0
- model_analyzer/monitor/dcgm/common/dcgm_client_main.py +86 -0
- model_analyzer/monitor/dcgm/dcgm_agent.py +887 -0
- model_analyzer/monitor/dcgm/dcgm_collectd_plugin.py +369 -0
- model_analyzer/monitor/dcgm/dcgm_errors.py +395 -0
- model_analyzer/monitor/dcgm/dcgm_field_helpers.py +546 -0
- model_analyzer/monitor/dcgm/dcgm_fields.py +815 -0
- model_analyzer/monitor/dcgm/dcgm_fields_collectd.py +671 -0
- model_analyzer/monitor/dcgm/dcgm_fields_internal.py +29 -0
- model_analyzer/monitor/dcgm/dcgm_fluentd.py +45 -0
- model_analyzer/monitor/dcgm/dcgm_monitor.py +138 -0
- model_analyzer/monitor/dcgm/dcgm_prometheus.py +326 -0
- model_analyzer/monitor/dcgm/dcgm_structs.py +2357 -0
- model_analyzer/monitor/dcgm/dcgm_telegraf.py +65 -0
- model_analyzer/monitor/dcgm/dcgm_value.py +151 -0
- model_analyzer/monitor/dcgm/dcgmvalue.py +155 -0
- model_analyzer/monitor/dcgm/denylist_recommendations.py +573 -0
- model_analyzer/monitor/dcgm/pydcgm.py +47 -0
- model_analyzer/monitor/monitor.py +143 -0
- model_analyzer/monitor/remote_monitor.py +137 -0
- model_analyzer/output/__init__.py +15 -0
- model_analyzer/output/file_writer.py +63 -0
- model_analyzer/output/output_writer.py +42 -0
- model_analyzer/perf_analyzer/__init__.py +15 -0
- model_analyzer/perf_analyzer/genai_perf_config.py +206 -0
- model_analyzer/perf_analyzer/perf_analyzer.py +882 -0
- model_analyzer/perf_analyzer/perf_config.py +479 -0
- model_analyzer/plots/__init__.py +15 -0
- model_analyzer/plots/detailed_plot.py +266 -0
- model_analyzer/plots/plot_manager.py +224 -0
- model_analyzer/plots/simple_plot.py +213 -0
- model_analyzer/record/__init__.py +15 -0
- model_analyzer/record/gpu_record.py +68 -0
- model_analyzer/record/metrics_manager.py +887 -0
- model_analyzer/record/record.py +280 -0
- model_analyzer/record/record_aggregator.py +256 -0
- model_analyzer/record/types/__init__.py +15 -0
- model_analyzer/record/types/cpu_available_ram.py +93 -0
- model_analyzer/record/types/cpu_used_ram.py +93 -0
- model_analyzer/record/types/gpu_free_memory.py +96 -0
- model_analyzer/record/types/gpu_power_usage.py +107 -0
- model_analyzer/record/types/gpu_total_memory.py +96 -0
- model_analyzer/record/types/gpu_used_memory.py +96 -0
- model_analyzer/record/types/gpu_utilization.py +108 -0
- model_analyzer/record/types/inter_token_latency_avg.py +60 -0
- model_analyzer/record/types/inter_token_latency_base.py +74 -0
- model_analyzer/record/types/inter_token_latency_max.py +60 -0
- model_analyzer/record/types/inter_token_latency_min.py +60 -0
- model_analyzer/record/types/inter_token_latency_p25.py +60 -0
- model_analyzer/record/types/inter_token_latency_p50.py +60 -0
- model_analyzer/record/types/inter_token_latency_p75.py +60 -0
- model_analyzer/record/types/inter_token_latency_p90.py +60 -0
- model_analyzer/record/types/inter_token_latency_p95.py +60 -0
- model_analyzer/record/types/inter_token_latency_p99.py +60 -0
- model_analyzer/record/types/output_token_throughput.py +105 -0
- model_analyzer/record/types/perf_client_response_wait.py +97 -0
- model_analyzer/record/types/perf_client_send_recv.py +97 -0
- model_analyzer/record/types/perf_latency.py +111 -0
- model_analyzer/record/types/perf_latency_avg.py +60 -0
- model_analyzer/record/types/perf_latency_base.py +74 -0
- model_analyzer/record/types/perf_latency_p90.py +60 -0
- model_analyzer/record/types/perf_latency_p95.py +60 -0
- model_analyzer/record/types/perf_latency_p99.py +60 -0
- model_analyzer/record/types/perf_server_compute_infer.py +97 -0
- model_analyzer/record/types/perf_server_compute_input.py +97 -0
- model_analyzer/record/types/perf_server_compute_output.py +97 -0
- model_analyzer/record/types/perf_server_queue.py +97 -0
- model_analyzer/record/types/perf_throughput.py +105 -0
- model_analyzer/record/types/time_to_first_token_avg.py +60 -0
- model_analyzer/record/types/time_to_first_token_base.py +74 -0
- model_analyzer/record/types/time_to_first_token_max.py +60 -0
- model_analyzer/record/types/time_to_first_token_min.py +60 -0
- model_analyzer/record/types/time_to_first_token_p25.py +60 -0
- model_analyzer/record/types/time_to_first_token_p50.py +60 -0
- model_analyzer/record/types/time_to_first_token_p75.py +60 -0
- model_analyzer/record/types/time_to_first_token_p90.py +60 -0
- model_analyzer/record/types/time_to_first_token_p95.py +60 -0
- model_analyzer/record/types/time_to_first_token_p99.py +60 -0
- model_analyzer/reports/__init__.py +15 -0
- model_analyzer/reports/html_report.py +195 -0
- model_analyzer/reports/pdf_report.py +50 -0
- model_analyzer/reports/report.py +86 -0
- model_analyzer/reports/report_factory.py +62 -0
- model_analyzer/reports/report_manager.py +1376 -0
- model_analyzer/reports/report_utils.py +42 -0
- model_analyzer/result/__init__.py +15 -0
- model_analyzer/result/constraint_manager.py +150 -0
- model_analyzer/result/model_config_measurement.py +354 -0
- model_analyzer/result/model_constraints.py +105 -0
- model_analyzer/result/parameter_search.py +246 -0
- model_analyzer/result/result_manager.py +430 -0
- model_analyzer/result/result_statistics.py +159 -0
- model_analyzer/result/result_table.py +217 -0
- model_analyzer/result/result_table_manager.py +646 -0
- model_analyzer/result/result_utils.py +42 -0
- model_analyzer/result/results.py +277 -0
- model_analyzer/result/run_config_measurement.py +658 -0
- model_analyzer/result/run_config_result.py +210 -0
- model_analyzer/result/run_config_result_comparator.py +110 -0
- model_analyzer/result/sorted_results.py +151 -0
- model_analyzer/state/__init__.py +15 -0
- model_analyzer/state/analyzer_state.py +76 -0
- model_analyzer/state/analyzer_state_manager.py +215 -0
- model_analyzer/triton/__init__.py +15 -0
- model_analyzer/triton/client/__init__.py +15 -0
- model_analyzer/triton/client/client.py +234 -0
- model_analyzer/triton/client/client_factory.py +57 -0
- model_analyzer/triton/client/grpc_client.py +104 -0
- model_analyzer/triton/client/http_client.py +107 -0
- model_analyzer/triton/model/__init__.py +15 -0
- model_analyzer/triton/model/model_config.py +556 -0
- model_analyzer/triton/model/model_config_variant.py +29 -0
- model_analyzer/triton/server/__init__.py +15 -0
- model_analyzer/triton/server/server.py +76 -0
- model_analyzer/triton/server/server_config.py +269 -0
- model_analyzer/triton/server/server_docker.py +229 -0
- model_analyzer/triton/server/server_factory.py +306 -0
- model_analyzer/triton/server/server_local.py +158 -0
- triton_model_analyzer-1.48.0.dist-info/METADATA +52 -0
- triton_model_analyzer-1.48.0.dist-info/RECORD +204 -0
- triton_model_analyzer-1.48.0.dist-info/WHEEL +5 -0
- triton_model_analyzer-1.48.0.dist-info/entry_points.txt +2 -0
- triton_model_analyzer-1.48.0.dist-info/licenses/LICENSE +67 -0
- triton_model_analyzer-1.48.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from copy import deepcopy
|
|
20
|
+
from typing import Dict, List, Optional, Union
|
|
21
|
+
|
|
22
|
+
from model_analyzer.cli.cli import CLI
|
|
23
|
+
from model_analyzer.config.generate.base_model_config_generator import (
|
|
24
|
+
BaseModelConfigGenerator,
|
|
25
|
+
)
|
|
26
|
+
from model_analyzer.config.generate.model_profile_spec import ModelProfileSpec
|
|
27
|
+
from model_analyzer.config.generate.search_parameters import SearchParameters
|
|
28
|
+
from model_analyzer.constants import LOGGER_NAME, PA_ERROR_LOG_FILENAME
|
|
29
|
+
from model_analyzer.state.analyzer_state_manager import AnalyzerStateManager
|
|
30
|
+
from model_analyzer.triton.server.server import TritonServer
|
|
31
|
+
|
|
32
|
+
from .config.input.config_command_profile import ConfigCommandProfile
|
|
33
|
+
from .config.input.config_command_report import ConfigCommandReport
|
|
34
|
+
from .config.input.config_defaults import DEFAULT_CHECKPOINT_DIRECTORY
|
|
35
|
+
from .device.gpu_device import GPUDevice
|
|
36
|
+
from .model_analyzer_exceptions import TritonModelAnalyzerException
|
|
37
|
+
from .model_manager import ModelManager
|
|
38
|
+
from .record.metrics_manager import MetricsManager
|
|
39
|
+
from .reports.report_manager import ReportManager
|
|
40
|
+
from .result.constraint_manager import ConstraintManager
|
|
41
|
+
from .result.result_manager import ResultManager
|
|
42
|
+
from .result.result_table_manager import ResultTableManager
|
|
43
|
+
from .triton.client.client import TritonClient
|
|
44
|
+
|
|
45
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Analyzer:
|
|
49
|
+
"""
|
|
50
|
+
A class responsible for coordinating the various components of the
|
|
51
|
+
model_analyzer. Configured with metrics to monitor, exposes profiling and
|
|
52
|
+
result writing methods.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
config: Union[ConfigCommandProfile, ConfigCommandReport],
|
|
58
|
+
server: TritonServer,
|
|
59
|
+
state_manager: AnalyzerStateManager,
|
|
60
|
+
checkpoint_required: bool,
|
|
61
|
+
):
|
|
62
|
+
"""
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
config : ConfigCommandProfile or ConfigCommandReport
|
|
66
|
+
Model Analyzer config
|
|
67
|
+
server : TritonServer
|
|
68
|
+
Server handle
|
|
69
|
+
state_manager: AnalyzerStateManager
|
|
70
|
+
The object that maintains Model Analyzer State
|
|
71
|
+
checkpoint_required : bool
|
|
72
|
+
If true, an existing checkpoint is required to run MA
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
self._config = config
|
|
76
|
+
self._server = server
|
|
77
|
+
self._state_manager = state_manager
|
|
78
|
+
state_manager.load_checkpoint(checkpoint_required)
|
|
79
|
+
|
|
80
|
+
self._constraint_manager = ConstraintManager(self._config)
|
|
81
|
+
self._result_manager = ResultManager(
|
|
82
|
+
config=config,
|
|
83
|
+
state_manager=self._state_manager,
|
|
84
|
+
constraint_manager=self._constraint_manager,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self._search_parameters: Dict[str, SearchParameters] = {}
|
|
88
|
+
self._composing_search_parameters: Dict[str, SearchParameters] = {}
|
|
89
|
+
|
|
90
|
+
def profile(
|
|
91
|
+
self, client: TritonClient, gpus: List[GPUDevice], mode: str, verbose: bool
|
|
92
|
+
) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Subcommand: PROFILE
|
|
95
|
+
|
|
96
|
+
Creates a RunConfigGenerator to generate RunConfigs, and then
|
|
97
|
+
profiles each RunConfig on Perf Analyzer and gathers the resulting
|
|
98
|
+
measurements.
|
|
99
|
+
|
|
100
|
+
Each RunConfig contains one or more (in the case of concurrent multi-model)
|
|
101
|
+
ModelRunConfigs, each of which contain a ModelConfig and a PerfAnalyzerConfig
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
client : TritonClient
|
|
106
|
+
Instance used to load/unload models
|
|
107
|
+
gpus: List of GPUDevices
|
|
108
|
+
The gpus being used to profile
|
|
109
|
+
|
|
110
|
+
Raises
|
|
111
|
+
------
|
|
112
|
+
TritonModelAnalyzerException
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
if not isinstance(self._config, ConfigCommandProfile):
|
|
116
|
+
raise TritonModelAnalyzerException(
|
|
117
|
+
f"Expected config of type {ConfigCommandProfile},"
|
|
118
|
+
" got {type(self._config)}."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self._create_metrics_manager(client, gpus)
|
|
122
|
+
self._create_model_manager(client, gpus)
|
|
123
|
+
self._populate_search_parameters(client, gpus)
|
|
124
|
+
self._populate_composing_search_parameters(client, gpus)
|
|
125
|
+
|
|
126
|
+
if self._config.triton_launch_mode == "remote":
|
|
127
|
+
self._warn_if_other_models_loaded_on_remote_server(client)
|
|
128
|
+
|
|
129
|
+
if self._config.model_repository or self._config.triton_launch_mode == "remote":
|
|
130
|
+
self._get_server_only_metrics(client, gpus)
|
|
131
|
+
self._profile_models()
|
|
132
|
+
|
|
133
|
+
# The message is in interrupt_handler(), so we can just exit
|
|
134
|
+
if self._state_manager.exiting():
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
logger.info(self._get_profile_complete_string())
|
|
138
|
+
logger.info("")
|
|
139
|
+
elif self._state_manager.starting_fresh_run():
|
|
140
|
+
raise TritonModelAnalyzerException(
|
|
141
|
+
"No model repository specified and no checkpoint found. Please either specify a model repository (-m) or load a checkpoint (--checkpoint-directory)."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if not self._config.skip_summary_reports:
|
|
145
|
+
self._create_summary_tables(verbose)
|
|
146
|
+
self._create_summary_reports(mode)
|
|
147
|
+
self._create_detailed_reports(mode)
|
|
148
|
+
|
|
149
|
+
self._check_for_perf_analyzer_errors()
|
|
150
|
+
|
|
151
|
+
def report(self, mode: str) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Subcommand: REPORT
|
|
154
|
+
|
|
155
|
+
Generates detailed information on
|
|
156
|
+
one or more model configs
|
|
157
|
+
|
|
158
|
+
Parameters
|
|
159
|
+
----------
|
|
160
|
+
mode : str
|
|
161
|
+
Global mode that the analyzer is running on
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
if not isinstance(self._config, ConfigCommandReport):
|
|
165
|
+
raise TritonModelAnalyzerException(
|
|
166
|
+
f"Expected config of type {ConfigCommandReport}, got {type(self._config)}."
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
gpu_info = self._state_manager.get_state_variable("MetricsManager.gpus")
|
|
170
|
+
if not gpu_info:
|
|
171
|
+
gpu_info = {}
|
|
172
|
+
self._report_manager = ReportManager(
|
|
173
|
+
mode=mode,
|
|
174
|
+
config=self._config,
|
|
175
|
+
result_manager=self._result_manager,
|
|
176
|
+
gpu_info=gpu_info,
|
|
177
|
+
constraint_manager=self._constraint_manager,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if self._multiple_models_in_report_model_config():
|
|
181
|
+
raise TritonModelAnalyzerException(
|
|
182
|
+
"Model Analyzer does not support detailed reporting for multi-model runs.\n"
|
|
183
|
+
"If you are trying to generate detailed reports for different sequentially profiled models you must run "
|
|
184
|
+
"the report command for each model separately."
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
self._report_manager.create_detailed_reports()
|
|
188
|
+
self._report_manager.export_detailed_reports()
|
|
189
|
+
|
|
190
|
+
def _create_metrics_manager(self, client, gpus):
|
|
191
|
+
self._metrics_manager = MetricsManager(
|
|
192
|
+
config=self._config,
|
|
193
|
+
client=client,
|
|
194
|
+
server=self._server,
|
|
195
|
+
gpus=gpus,
|
|
196
|
+
result_manager=self._result_manager,
|
|
197
|
+
state_manager=self._state_manager,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def _create_model_manager(self, client, gpus):
|
|
201
|
+
self._model_manager = ModelManager(
|
|
202
|
+
config=self._config,
|
|
203
|
+
gpus=gpus,
|
|
204
|
+
client=client,
|
|
205
|
+
server=self._server,
|
|
206
|
+
result_manager=self._result_manager,
|
|
207
|
+
metrics_manager=self._metrics_manager,
|
|
208
|
+
state_manager=self._state_manager,
|
|
209
|
+
constraint_manager=self._constraint_manager,
|
|
210
|
+
search_parameters=self._search_parameters,
|
|
211
|
+
composing_search_parameters=self._composing_search_parameters,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _get_server_only_metrics(self, client, gpus):
|
|
215
|
+
if self._config.triton_launch_mode != "c_api":
|
|
216
|
+
if not self._state_manager._starting_fresh_run:
|
|
217
|
+
if self._config.dcgm_disable:
|
|
218
|
+
logger.info(
|
|
219
|
+
"DCGM is disabled - cannot verify that GPU devices match checkpoint"
|
|
220
|
+
)
|
|
221
|
+
elif self._do_checkpoint_gpus_match(gpus):
|
|
222
|
+
logger.info(
|
|
223
|
+
"GPU devices match checkpoint - skipping server metric acquisition"
|
|
224
|
+
)
|
|
225
|
+
return
|
|
226
|
+
elif gpus is not None:
|
|
227
|
+
raise TritonModelAnalyzerException(
|
|
228
|
+
"GPU devices do not match checkpoint - Remove checkpoint file and rerun profile"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
logger.info("Profiling server only metrics...")
|
|
232
|
+
self._server.start()
|
|
233
|
+
client.wait_for_server_ready(
|
|
234
|
+
num_retries=self._config.client_max_retries,
|
|
235
|
+
log_file=self._server.log_file(),
|
|
236
|
+
)
|
|
237
|
+
self._metrics_manager.profile_server()
|
|
238
|
+
self._server.stop()
|
|
239
|
+
|
|
240
|
+
def _profile_models(self):
|
|
241
|
+
models = self._config.profile_models
|
|
242
|
+
|
|
243
|
+
if self._should_profile_multiple_models_concurrently():
|
|
244
|
+
# Profile all models concurrently
|
|
245
|
+
try:
|
|
246
|
+
self._model_manager.run_models(models=models)
|
|
247
|
+
finally:
|
|
248
|
+
self._state_manager.save_checkpoint()
|
|
249
|
+
else:
|
|
250
|
+
# Profile each model, save state after each
|
|
251
|
+
for model in models:
|
|
252
|
+
if self._state_manager.exiting():
|
|
253
|
+
break
|
|
254
|
+
try:
|
|
255
|
+
self._model_manager.run_models(models=[model])
|
|
256
|
+
finally:
|
|
257
|
+
self._state_manager.save_checkpoint()
|
|
258
|
+
|
|
259
|
+
def _create_summary_tables(self, verbose: bool) -> None:
|
|
260
|
+
self._result_table_manager = ResultTableManager(
|
|
261
|
+
self._config, self._result_manager
|
|
262
|
+
)
|
|
263
|
+
self._result_table_manager.create_tables()
|
|
264
|
+
self._result_table_manager.tabulate_results()
|
|
265
|
+
self._result_table_manager.export_results()
|
|
266
|
+
|
|
267
|
+
if verbose:
|
|
268
|
+
self._result_table_manager.write_results()
|
|
269
|
+
|
|
270
|
+
def _create_summary_reports(self, mode: str) -> None:
|
|
271
|
+
gpu_info = self._state_manager.get_state_variable("MetricsManager.gpus")
|
|
272
|
+
if not gpu_info:
|
|
273
|
+
gpu_info = {}
|
|
274
|
+
|
|
275
|
+
self._report_manager = ReportManager(
|
|
276
|
+
mode=mode,
|
|
277
|
+
config=self._config,
|
|
278
|
+
gpu_info=gpu_info,
|
|
279
|
+
result_manager=self._result_manager,
|
|
280
|
+
constraint_manager=self._constraint_manager,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
self._report_manager.create_summaries()
|
|
284
|
+
self._report_manager.export_summaries()
|
|
285
|
+
|
|
286
|
+
def _should_profile_multiple_models_concurrently(self):
|
|
287
|
+
return (
|
|
288
|
+
self._config.run_config_profile_models_concurrently_enable
|
|
289
|
+
and len(self._config.profile_models) > 1
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
def _get_profile_complete_string(self):
|
|
293
|
+
profiled_model_list = self._state_manager.get_state_variable(
|
|
294
|
+
"ResultManager.results"
|
|
295
|
+
).get_list_of_models()
|
|
296
|
+
num_profiled_configs = self._get_num_profiled_configs()
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
f"Profile complete. Profiled {num_profiled_configs} "
|
|
300
|
+
f"configurations for models: {profiled_model_list}"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
def _get_num_profiled_configs(self):
|
|
304
|
+
return sum(
|
|
305
|
+
[
|
|
306
|
+
len(x)
|
|
307
|
+
for x in self._state_manager.get_state_variable(
|
|
308
|
+
"ResultManager.results"
|
|
309
|
+
).get_list_of_model_config_measurement_tuples()
|
|
310
|
+
]
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def _get_report_command_help_string(self, model_name: str) -> str:
|
|
314
|
+
top_n_model_config_names = self._get_top_n_model_config_names(
|
|
315
|
+
n=self._config.num_configs_per_model, model_name=model_name
|
|
316
|
+
)
|
|
317
|
+
return (
|
|
318
|
+
f"To generate detailed reports for the "
|
|
319
|
+
f"{len(top_n_model_config_names)} best {model_name} configurations, run "
|
|
320
|
+
f"`{self._get_report_command_string(top_n_model_config_names)}`"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def _run_report_command(self, model_name: str, mode: str) -> None:
|
|
324
|
+
top_n_model_config_names = self._get_top_n_model_config_names(
|
|
325
|
+
n=self._config.num_configs_per_model, model_name=model_name
|
|
326
|
+
)
|
|
327
|
+
top_n_string = ",".join(top_n_model_config_names)
|
|
328
|
+
logger.info(
|
|
329
|
+
f"Generating detailed reports for the best configurations {top_n_string}:"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# [1:] removes 'model-analyzer' from the args
|
|
333
|
+
args = self._get_report_command_string(top_n_model_config_names).split(" ")[1:]
|
|
334
|
+
|
|
335
|
+
original_profile_config = deepcopy(self._config)
|
|
336
|
+
self._config = self._create_report_config(args)
|
|
337
|
+
self.report(mode)
|
|
338
|
+
self._config = original_profile_config
|
|
339
|
+
|
|
340
|
+
def _get_report_command_string(self, top_n_model_config_names: List[str]) -> str:
|
|
341
|
+
report_command_string = (
|
|
342
|
+
f"model-analyzer report "
|
|
343
|
+
f"--report-model-configs "
|
|
344
|
+
f'{",".join(top_n_model_config_names)}'
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
if self._config.export_path is not None:
|
|
348
|
+
report_command_string += f" --export-path " f"{self._config.export_path}"
|
|
349
|
+
|
|
350
|
+
if self._config.config_file is not None:
|
|
351
|
+
report_command_string += f" --config-file " f"{self._config.config_file}"
|
|
352
|
+
|
|
353
|
+
if self._config.checkpoint_directory != DEFAULT_CHECKPOINT_DIRECTORY:
|
|
354
|
+
report_command_string += (
|
|
355
|
+
f" --checkpoint-directory " f"{self._config.checkpoint_directory}"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return report_command_string
|
|
359
|
+
|
|
360
|
+
def _get_top_n_model_config_names(
|
|
361
|
+
self, n: int = -1, model_name: Optional[str] = None
|
|
362
|
+
) -> List[str]:
|
|
363
|
+
return [
|
|
364
|
+
x.run_config().model_variants_name()
|
|
365
|
+
for x in self._result_manager.top_n_results(n=n, model_name=model_name)
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
def _do_checkpoint_gpus_match(self, gpus: dict) -> bool:
|
|
369
|
+
ckpt_data = self._result_manager.get_server_only_data()
|
|
370
|
+
ckpt_uuids = [ckpt_uuid for ckpt_uuid in ckpt_data.keys()]
|
|
371
|
+
gpu_uuids = [gpu._device_uuid for gpu in gpus]
|
|
372
|
+
|
|
373
|
+
return sorted(ckpt_uuids) == sorted(gpu_uuids)
|
|
374
|
+
|
|
375
|
+
def _multiple_models_in_report_model_config(self) -> bool:
|
|
376
|
+
model_config_names = [
|
|
377
|
+
report_model_config.model_config_name()
|
|
378
|
+
for report_model_config in self._config.report_model_configs
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
model_names = [
|
|
382
|
+
BaseModelConfigGenerator.extract_model_name_from_variant_name(
|
|
383
|
+
model_config_name
|
|
384
|
+
)
|
|
385
|
+
for model_config_name in model_config_names
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
return len(set(model_names)) > 1
|
|
389
|
+
|
|
390
|
+
def _check_for_perf_analyzer_errors(self) -> None:
|
|
391
|
+
if self._metrics_manager.encountered_perf_analyzer_error():
|
|
392
|
+
logger.warning(
|
|
393
|
+
f"Perf Analyzer encountered an error when profiling one or more configurations. "
|
|
394
|
+
f"See {self._config.export_path}/{PA_ERROR_LOG_FILENAME} for further details.\n"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def _create_detailed_reports(self, mode: str) -> None:
|
|
398
|
+
# TODO-TMA-650: Detailed reporting not supported for multi-model
|
|
399
|
+
if not self._config.run_config_profile_models_concurrently_enable:
|
|
400
|
+
for model in self._config.profile_models:
|
|
401
|
+
if not self._config.skip_detailed_reports:
|
|
402
|
+
self._run_report_command(model.model_name(), mode)
|
|
403
|
+
else:
|
|
404
|
+
logger.info(
|
|
405
|
+
self._get_report_command_help_string(model.model_name())
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def _create_report_config(self, args: list) -> ConfigCommandReport:
|
|
409
|
+
config = ConfigCommandReport()
|
|
410
|
+
cli = CLI()
|
|
411
|
+
cli.add_subcommand(cmd="report", help="", config=config)
|
|
412
|
+
cli.parse(args)
|
|
413
|
+
return config
|
|
414
|
+
|
|
415
|
+
def _warn_if_other_models_loaded_on_remote_server(self, client):
|
|
416
|
+
repository_index = client.get_model_repository_index()
|
|
417
|
+
profile_model_names = [pm.model_name() for pm in self._config.profile_models]
|
|
418
|
+
|
|
419
|
+
model_names_loaded_on_server = []
|
|
420
|
+
for repository_item in repository_index:
|
|
421
|
+
if client.is_model_ready(repository_item["name"]):
|
|
422
|
+
model_names_loaded_on_server.append(repository_item["name"])
|
|
423
|
+
|
|
424
|
+
for model_name in model_names_loaded_on_server:
|
|
425
|
+
if model_name not in profile_model_names:
|
|
426
|
+
logger.warning(
|
|
427
|
+
f"A model not being profiled ({model_name}) is loaded on the remote Tritonserver. "
|
|
428
|
+
"This could impact the profile results."
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
def _populate_search_parameters(self, client, gpus):
|
|
432
|
+
for model in self._config.profile_models:
|
|
433
|
+
model_profile_spec = ModelProfileSpec(model, self._config, client, gpus)
|
|
434
|
+
self._search_parameters[model.model_name()] = SearchParameters(
|
|
435
|
+
config=self._config,
|
|
436
|
+
model=model_profile_spec,
|
|
437
|
+
is_bls_model=bool(self._config.bls_composing_models),
|
|
438
|
+
is_ensemble_model=model_profile_spec.is_ensemble(),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
def _populate_composing_search_parameters(self, client, gpus):
|
|
442
|
+
for model in self._config.bls_composing_models:
|
|
443
|
+
model_profile_spec = ModelProfileSpec(model, self._config, client, gpus)
|
|
444
|
+
self._composing_search_parameters[model.model_name()] = SearchParameters(
|
|
445
|
+
config=self._config,
|
|
446
|
+
model=model_profile_spec,
|
|
447
|
+
is_composing_model=True,
|
|
448
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2020-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from argparse import SUPPRESS, ArgumentParser, Namespace
|
|
20
|
+
from typing import List, Optional, Tuple, Union
|
|
21
|
+
|
|
22
|
+
import importlib_metadata
|
|
23
|
+
|
|
24
|
+
from model_analyzer.config.input.config_command_profile import ConfigCommandProfile
|
|
25
|
+
from model_analyzer.config.input.config_command_report import ConfigCommandReport
|
|
26
|
+
from model_analyzer.constants import LOGGER_NAME, PACKAGE_NAME
|
|
27
|
+
from model_analyzer.model_analyzer_exceptions import TritonModelAnalyzerException
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(LOGGER_NAME)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CLI:
|
|
33
|
+
"""
|
|
34
|
+
CLI class to parse the command line arguments
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self._parser = ArgumentParser()
|
|
39
|
+
self._add_global_options()
|
|
40
|
+
self._subparsers = self._parser.add_subparsers(
|
|
41
|
+
help="Subcommands under Model Analyzer", dest="subcommand"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Store subcommands, and their configs
|
|
45
|
+
self._subcommand_configs = {}
|
|
46
|
+
|
|
47
|
+
def _add_global_options(self):
|
|
48
|
+
"""
|
|
49
|
+
Adds the Model Analyzer's global options
|
|
50
|
+
to the parser
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
self._parser.add_argument(
|
|
54
|
+
"-m",
|
|
55
|
+
"--mode",
|
|
56
|
+
type=str,
|
|
57
|
+
default="online",
|
|
58
|
+
choices=["online", "offline"],
|
|
59
|
+
help="Choose a preset configuration mode.",
|
|
60
|
+
)
|
|
61
|
+
self._parser.add_argument(
|
|
62
|
+
"--version", action="store_true", help="Show the Model Analyzer version."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def _add_global_options_to_subparser(self, subparser):
|
|
66
|
+
"""
|
|
67
|
+
Adds global options to a subparser so they can be
|
|
68
|
+
used after the subcommand (e.g., 'model-analyzer profile -v')
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
subparser.add_argument(
|
|
72
|
+
"-q",
|
|
73
|
+
"--quiet",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="Suppress all output except for error messages.",
|
|
76
|
+
)
|
|
77
|
+
subparser.add_argument(
|
|
78
|
+
"-v",
|
|
79
|
+
"--verbose",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Show detailed logs, messages and status.",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def add_subcommand(self, cmd, help, config=None):
|
|
85
|
+
"""
|
|
86
|
+
Adds a subparser to the main parser representing
|
|
87
|
+
a command. Also adds the passed in config to
|
|
88
|
+
the subcommands dict to set its values upon parse.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
cmd : str
|
|
93
|
+
subcommand name
|
|
94
|
+
help: str
|
|
95
|
+
help string or description for the subcommand
|
|
96
|
+
config: Config
|
|
97
|
+
The config containing the arguments that are required
|
|
98
|
+
to be parsed for this subcommand.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
subparser = self._subparsers.add_parser(cmd, help=help)
|
|
102
|
+
|
|
103
|
+
self._add_global_options_to_subparser(subparser)
|
|
104
|
+
|
|
105
|
+
if config:
|
|
106
|
+
self._add_config_arguments(subparser, config)
|
|
107
|
+
self._subcommand_configs[cmd] = config
|
|
108
|
+
|
|
109
|
+
def _add_config_arguments(self, subparser, config):
|
|
110
|
+
"""
|
|
111
|
+
Add the CLI arguments from the config
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
config : Config
|
|
116
|
+
Model Analyzer config object.
|
|
117
|
+
"""
|
|
118
|
+
# configs is dictionary of config_fields objects from config_command_*
|
|
119
|
+
configs = config.get_config()
|
|
120
|
+
for config_field in configs.values():
|
|
121
|
+
parser_args = config_field.parser_args()
|
|
122
|
+
|
|
123
|
+
# Skip the non-CLI flags
|
|
124
|
+
if config_field.flags() is None:
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
# 'store_true' and 'store_false' does not
|
|
128
|
+
# allow 'type' or 'choices' parameters
|
|
129
|
+
if "action" in parser_args and (
|
|
130
|
+
parser_args["action"] == "store_true"
|
|
131
|
+
or parser_args["action"] == "store_false"
|
|
132
|
+
):
|
|
133
|
+
subparser.add_argument(
|
|
134
|
+
*config_field.flags(),
|
|
135
|
+
default=SUPPRESS,
|
|
136
|
+
help=config_field.description(),
|
|
137
|
+
**config_field.parser_args(),
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
subparser.add_argument(
|
|
141
|
+
*config_field.flags(),
|
|
142
|
+
default=SUPPRESS,
|
|
143
|
+
choices=config_field.choices(),
|
|
144
|
+
help=config_field.description(),
|
|
145
|
+
type=config_field.cli_type(),
|
|
146
|
+
**config_field.parser_args(),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _show_model_analyzer_version(self):
|
|
150
|
+
"""
|
|
151
|
+
Displays the current version of Model Analyzer and exits.
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
version = importlib_metadata.version(PACKAGE_NAME)
|
|
155
|
+
print(version)
|
|
156
|
+
sys.exit(0)
|
|
157
|
+
except importlib_metadata.PackageNotFoundError:
|
|
158
|
+
raise TritonModelAnalyzerException(f"Version information is not available")
|
|
159
|
+
|
|
160
|
+
def parse(
|
|
161
|
+
self, input_args: Optional[List] = None
|
|
162
|
+
) -> Tuple[Namespace, Union[ConfigCommandProfile, ConfigCommandReport]]:
|
|
163
|
+
"""
|
|
164
|
+
Parse CLI options using ArgumentParsers
|
|
165
|
+
and set config values.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
input_args: List
|
|
170
|
+
The list of arguments to be parsed
|
|
171
|
+
(if None then command line arguments will be used)
|
|
172
|
+
|
|
173
|
+
Returns
|
|
174
|
+
-------
|
|
175
|
+
args : Namespace
|
|
176
|
+
Object that contains the parse CLI commands
|
|
177
|
+
Used for the global options
|
|
178
|
+
config: CommandConfig
|
|
179
|
+
The config corresponding to the command being run,
|
|
180
|
+
already filled in with values from CLI or YAML.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
args = self._parser.parse_args(input_args)
|
|
184
|
+
|
|
185
|
+
if args.version:
|
|
186
|
+
self._show_model_analyzer_version()
|
|
187
|
+
|
|
188
|
+
if args.subcommand is None:
|
|
189
|
+
self._parser.print_help()
|
|
190
|
+
self._parser.exit()
|
|
191
|
+
config = self._subcommand_configs[args.subcommand]
|
|
192
|
+
config.set_config_values(args)
|
|
193
|
+
return args, config
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright 2021-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|