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.
Files changed (204) hide show
  1. model_analyzer/__init__.py +15 -0
  2. model_analyzer/analyzer.py +448 -0
  3. model_analyzer/cli/__init__.py +15 -0
  4. model_analyzer/cli/cli.py +193 -0
  5. model_analyzer/config/__init__.py +15 -0
  6. model_analyzer/config/generate/__init__.py +15 -0
  7. model_analyzer/config/generate/automatic_model_config_generator.py +164 -0
  8. model_analyzer/config/generate/base_model_config_generator.py +352 -0
  9. model_analyzer/config/generate/brute_plus_binary_parameter_search_run_config_generator.py +164 -0
  10. model_analyzer/config/generate/brute_run_config_generator.py +154 -0
  11. model_analyzer/config/generate/concurrency_sweeper.py +75 -0
  12. model_analyzer/config/generate/config_generator_interface.py +52 -0
  13. model_analyzer/config/generate/coordinate.py +143 -0
  14. model_analyzer/config/generate/coordinate_data.py +86 -0
  15. model_analyzer/config/generate/generator_utils.py +116 -0
  16. model_analyzer/config/generate/manual_model_config_generator.py +187 -0
  17. model_analyzer/config/generate/model_config_generator_factory.py +92 -0
  18. model_analyzer/config/generate/model_profile_spec.py +74 -0
  19. model_analyzer/config/generate/model_run_config_generator.py +154 -0
  20. model_analyzer/config/generate/model_variant_name_manager.py +150 -0
  21. model_analyzer/config/generate/neighborhood.py +536 -0
  22. model_analyzer/config/generate/optuna_plus_concurrency_sweep_run_config_generator.py +141 -0
  23. model_analyzer/config/generate/optuna_run_config_generator.py +838 -0
  24. model_analyzer/config/generate/perf_analyzer_config_generator.py +312 -0
  25. model_analyzer/config/generate/quick_plus_concurrency_sweep_run_config_generator.py +130 -0
  26. model_analyzer/config/generate/quick_run_config_generator.py +753 -0
  27. model_analyzer/config/generate/run_config_generator_factory.py +329 -0
  28. model_analyzer/config/generate/search_config.py +112 -0
  29. model_analyzer/config/generate/search_dimension.py +73 -0
  30. model_analyzer/config/generate/search_dimensions.py +85 -0
  31. model_analyzer/config/generate/search_parameter.py +49 -0
  32. model_analyzer/config/generate/search_parameters.py +388 -0
  33. model_analyzer/config/input/__init__.py +15 -0
  34. model_analyzer/config/input/config_command.py +483 -0
  35. model_analyzer/config/input/config_command_profile.py +1747 -0
  36. model_analyzer/config/input/config_command_report.py +267 -0
  37. model_analyzer/config/input/config_defaults.py +236 -0
  38. model_analyzer/config/input/config_enum.py +83 -0
  39. model_analyzer/config/input/config_field.py +216 -0
  40. model_analyzer/config/input/config_list_generic.py +112 -0
  41. model_analyzer/config/input/config_list_numeric.py +151 -0
  42. model_analyzer/config/input/config_list_string.py +111 -0
  43. model_analyzer/config/input/config_none.py +71 -0
  44. model_analyzer/config/input/config_object.py +129 -0
  45. model_analyzer/config/input/config_primitive.py +81 -0
  46. model_analyzer/config/input/config_status.py +75 -0
  47. model_analyzer/config/input/config_sweep.py +83 -0
  48. model_analyzer/config/input/config_union.py +113 -0
  49. model_analyzer/config/input/config_utils.py +128 -0
  50. model_analyzer/config/input/config_value.py +243 -0
  51. model_analyzer/config/input/objects/__init__.py +15 -0
  52. model_analyzer/config/input/objects/config_model_profile_spec.py +325 -0
  53. model_analyzer/config/input/objects/config_model_report_spec.py +173 -0
  54. model_analyzer/config/input/objects/config_plot.py +198 -0
  55. model_analyzer/config/input/objects/config_protobuf_utils.py +101 -0
  56. model_analyzer/config/input/yaml_config_validator.py +82 -0
  57. model_analyzer/config/run/__init__.py +15 -0
  58. model_analyzer/config/run/model_run_config.py +313 -0
  59. model_analyzer/config/run/run_config.py +168 -0
  60. model_analyzer/constants.py +76 -0
  61. model_analyzer/device/__init__.py +15 -0
  62. model_analyzer/device/device.py +24 -0
  63. model_analyzer/device/gpu_device.py +87 -0
  64. model_analyzer/device/gpu_device_factory.py +248 -0
  65. model_analyzer/entrypoint.py +307 -0
  66. model_analyzer/log_formatter.py +65 -0
  67. model_analyzer/model_analyzer_exceptions.py +24 -0
  68. model_analyzer/model_manager.py +255 -0
  69. model_analyzer/monitor/__init__.py +15 -0
  70. model_analyzer/monitor/cpu_monitor.py +69 -0
  71. model_analyzer/monitor/dcgm/DcgmDiag.py +191 -0
  72. model_analyzer/monitor/dcgm/DcgmFieldGroup.py +83 -0
  73. model_analyzer/monitor/dcgm/DcgmGroup.py +815 -0
  74. model_analyzer/monitor/dcgm/DcgmHandle.py +141 -0
  75. model_analyzer/monitor/dcgm/DcgmJsonReader.py +69 -0
  76. model_analyzer/monitor/dcgm/DcgmReader.py +623 -0
  77. model_analyzer/monitor/dcgm/DcgmStatus.py +57 -0
  78. model_analyzer/monitor/dcgm/DcgmSystem.py +412 -0
  79. model_analyzer/monitor/dcgm/__init__.py +15 -0
  80. model_analyzer/monitor/dcgm/common/__init__.py +13 -0
  81. model_analyzer/monitor/dcgm/common/dcgm_client_cli_parser.py +194 -0
  82. model_analyzer/monitor/dcgm/common/dcgm_client_main.py +86 -0
  83. model_analyzer/monitor/dcgm/dcgm_agent.py +887 -0
  84. model_analyzer/monitor/dcgm/dcgm_collectd_plugin.py +369 -0
  85. model_analyzer/monitor/dcgm/dcgm_errors.py +395 -0
  86. model_analyzer/monitor/dcgm/dcgm_field_helpers.py +546 -0
  87. model_analyzer/monitor/dcgm/dcgm_fields.py +815 -0
  88. model_analyzer/monitor/dcgm/dcgm_fields_collectd.py +671 -0
  89. model_analyzer/monitor/dcgm/dcgm_fields_internal.py +29 -0
  90. model_analyzer/monitor/dcgm/dcgm_fluentd.py +45 -0
  91. model_analyzer/monitor/dcgm/dcgm_monitor.py +138 -0
  92. model_analyzer/monitor/dcgm/dcgm_prometheus.py +326 -0
  93. model_analyzer/monitor/dcgm/dcgm_structs.py +2357 -0
  94. model_analyzer/monitor/dcgm/dcgm_telegraf.py +65 -0
  95. model_analyzer/monitor/dcgm/dcgm_value.py +151 -0
  96. model_analyzer/monitor/dcgm/dcgmvalue.py +155 -0
  97. model_analyzer/monitor/dcgm/denylist_recommendations.py +573 -0
  98. model_analyzer/monitor/dcgm/pydcgm.py +47 -0
  99. model_analyzer/monitor/monitor.py +143 -0
  100. model_analyzer/monitor/remote_monitor.py +137 -0
  101. model_analyzer/output/__init__.py +15 -0
  102. model_analyzer/output/file_writer.py +63 -0
  103. model_analyzer/output/output_writer.py +42 -0
  104. model_analyzer/perf_analyzer/__init__.py +15 -0
  105. model_analyzer/perf_analyzer/genai_perf_config.py +206 -0
  106. model_analyzer/perf_analyzer/perf_analyzer.py +882 -0
  107. model_analyzer/perf_analyzer/perf_config.py +479 -0
  108. model_analyzer/plots/__init__.py +15 -0
  109. model_analyzer/plots/detailed_plot.py +266 -0
  110. model_analyzer/plots/plot_manager.py +224 -0
  111. model_analyzer/plots/simple_plot.py +213 -0
  112. model_analyzer/record/__init__.py +15 -0
  113. model_analyzer/record/gpu_record.py +68 -0
  114. model_analyzer/record/metrics_manager.py +887 -0
  115. model_analyzer/record/record.py +280 -0
  116. model_analyzer/record/record_aggregator.py +256 -0
  117. model_analyzer/record/types/__init__.py +15 -0
  118. model_analyzer/record/types/cpu_available_ram.py +93 -0
  119. model_analyzer/record/types/cpu_used_ram.py +93 -0
  120. model_analyzer/record/types/gpu_free_memory.py +96 -0
  121. model_analyzer/record/types/gpu_power_usage.py +107 -0
  122. model_analyzer/record/types/gpu_total_memory.py +96 -0
  123. model_analyzer/record/types/gpu_used_memory.py +96 -0
  124. model_analyzer/record/types/gpu_utilization.py +108 -0
  125. model_analyzer/record/types/inter_token_latency_avg.py +60 -0
  126. model_analyzer/record/types/inter_token_latency_base.py +74 -0
  127. model_analyzer/record/types/inter_token_latency_max.py +60 -0
  128. model_analyzer/record/types/inter_token_latency_min.py +60 -0
  129. model_analyzer/record/types/inter_token_latency_p25.py +60 -0
  130. model_analyzer/record/types/inter_token_latency_p50.py +60 -0
  131. model_analyzer/record/types/inter_token_latency_p75.py +60 -0
  132. model_analyzer/record/types/inter_token_latency_p90.py +60 -0
  133. model_analyzer/record/types/inter_token_latency_p95.py +60 -0
  134. model_analyzer/record/types/inter_token_latency_p99.py +60 -0
  135. model_analyzer/record/types/output_token_throughput.py +105 -0
  136. model_analyzer/record/types/perf_client_response_wait.py +97 -0
  137. model_analyzer/record/types/perf_client_send_recv.py +97 -0
  138. model_analyzer/record/types/perf_latency.py +111 -0
  139. model_analyzer/record/types/perf_latency_avg.py +60 -0
  140. model_analyzer/record/types/perf_latency_base.py +74 -0
  141. model_analyzer/record/types/perf_latency_p90.py +60 -0
  142. model_analyzer/record/types/perf_latency_p95.py +60 -0
  143. model_analyzer/record/types/perf_latency_p99.py +60 -0
  144. model_analyzer/record/types/perf_server_compute_infer.py +97 -0
  145. model_analyzer/record/types/perf_server_compute_input.py +97 -0
  146. model_analyzer/record/types/perf_server_compute_output.py +97 -0
  147. model_analyzer/record/types/perf_server_queue.py +97 -0
  148. model_analyzer/record/types/perf_throughput.py +105 -0
  149. model_analyzer/record/types/time_to_first_token_avg.py +60 -0
  150. model_analyzer/record/types/time_to_first_token_base.py +74 -0
  151. model_analyzer/record/types/time_to_first_token_max.py +60 -0
  152. model_analyzer/record/types/time_to_first_token_min.py +60 -0
  153. model_analyzer/record/types/time_to_first_token_p25.py +60 -0
  154. model_analyzer/record/types/time_to_first_token_p50.py +60 -0
  155. model_analyzer/record/types/time_to_first_token_p75.py +60 -0
  156. model_analyzer/record/types/time_to_first_token_p90.py +60 -0
  157. model_analyzer/record/types/time_to_first_token_p95.py +60 -0
  158. model_analyzer/record/types/time_to_first_token_p99.py +60 -0
  159. model_analyzer/reports/__init__.py +15 -0
  160. model_analyzer/reports/html_report.py +195 -0
  161. model_analyzer/reports/pdf_report.py +50 -0
  162. model_analyzer/reports/report.py +86 -0
  163. model_analyzer/reports/report_factory.py +62 -0
  164. model_analyzer/reports/report_manager.py +1376 -0
  165. model_analyzer/reports/report_utils.py +42 -0
  166. model_analyzer/result/__init__.py +15 -0
  167. model_analyzer/result/constraint_manager.py +150 -0
  168. model_analyzer/result/model_config_measurement.py +354 -0
  169. model_analyzer/result/model_constraints.py +105 -0
  170. model_analyzer/result/parameter_search.py +246 -0
  171. model_analyzer/result/result_manager.py +430 -0
  172. model_analyzer/result/result_statistics.py +159 -0
  173. model_analyzer/result/result_table.py +217 -0
  174. model_analyzer/result/result_table_manager.py +646 -0
  175. model_analyzer/result/result_utils.py +42 -0
  176. model_analyzer/result/results.py +277 -0
  177. model_analyzer/result/run_config_measurement.py +658 -0
  178. model_analyzer/result/run_config_result.py +210 -0
  179. model_analyzer/result/run_config_result_comparator.py +110 -0
  180. model_analyzer/result/sorted_results.py +151 -0
  181. model_analyzer/state/__init__.py +15 -0
  182. model_analyzer/state/analyzer_state.py +76 -0
  183. model_analyzer/state/analyzer_state_manager.py +215 -0
  184. model_analyzer/triton/__init__.py +15 -0
  185. model_analyzer/triton/client/__init__.py +15 -0
  186. model_analyzer/triton/client/client.py +234 -0
  187. model_analyzer/triton/client/client_factory.py +57 -0
  188. model_analyzer/triton/client/grpc_client.py +104 -0
  189. model_analyzer/triton/client/http_client.py +107 -0
  190. model_analyzer/triton/model/__init__.py +15 -0
  191. model_analyzer/triton/model/model_config.py +556 -0
  192. model_analyzer/triton/model/model_config_variant.py +29 -0
  193. model_analyzer/triton/server/__init__.py +15 -0
  194. model_analyzer/triton/server/server.py +76 -0
  195. model_analyzer/triton/server/server_config.py +269 -0
  196. model_analyzer/triton/server/server_docker.py +229 -0
  197. model_analyzer/triton/server/server_factory.py +306 -0
  198. model_analyzer/triton/server/server_local.py +158 -0
  199. triton_model_analyzer-1.48.0.dist-info/METADATA +52 -0
  200. triton_model_analyzer-1.48.0.dist-info/RECORD +204 -0
  201. triton_model_analyzer-1.48.0.dist-info/WHEEL +5 -0
  202. triton_model_analyzer-1.48.0.dist-info/entry_points.txt +2 -0
  203. triton_model_analyzer-1.48.0.dist-info/licenses/LICENSE +67 -0
  204. triton_model_analyzer-1.48.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,266 @@
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 os
19
+ from collections import defaultdict
20
+
21
+ import matplotlib.pyplot as plt
22
+ from matplotlib import patches as mpatches
23
+
24
+ from model_analyzer.constants import LOGGER_NAME
25
+ from model_analyzer.record.metrics_manager import MetricsManager
26
+
27
+ logging.getLogger("matplotlib").setLevel(logging.ERROR)
28
+
29
+ logger = logging.getLogger(LOGGER_NAME)
30
+
31
+
32
+ class DetailedPlot:
33
+ """
34
+ A wrapper class around a matplotlib
35
+ plot that adapts with the kinds of
36
+ plots the model analyzer wants to generates
37
+
38
+ Detailed plots detail th
39
+ """
40
+
41
+ detailed_metrics = [
42
+ "perf_server_queue",
43
+ "perf_server_compute_input",
44
+ "perf_server_compute_infer",
45
+ "perf_server_compute_output",
46
+ ]
47
+
48
+ def __init__(self, name, title, bar_width=0.5):
49
+ """
50
+ Parameters
51
+ ----------
52
+ name: str
53
+ The name of the file that the plot
54
+ will be saved as
55
+ title : str
56
+ The title of this plot/figure
57
+ bar_width: float
58
+ width of the latency breakdown bars
59
+ """
60
+
61
+ self._name = name
62
+ self._title = title
63
+
64
+ self._fig, self._ax_latency = plt.subplots()
65
+ self._ax_latency.set_title(title)
66
+ self._ax_throughput = self._ax_latency.twinx()
67
+
68
+ latency_axis_label, throughput_axis_label = [
69
+ metric.header(aggregation_tag="")
70
+ for metric in MetricsManager.get_metric_types(
71
+ ["perf_latency_avg", "perf_throughput"]
72
+ )
73
+ ]
74
+
75
+ # Okabe-Ito guideline colors for colorblind
76
+ # https://jfly.uni-koeln.de/color/#select
77
+ self._bar_colors = {
78
+ "perf_server_queue": "#e69f00",
79
+ "perf_server_compute_input": "#56b4e9",
80
+ "perf_server_compute_infer": "#009e73",
81
+ "perf_server_compute_output": "#f0e442",
82
+ "perf_throughput": "#000000",
83
+ }
84
+
85
+ self._bar_width = bar_width
86
+ self._legend_x = 0.92
87
+ self._legend_y = 1.15
88
+ self._legend_font_size = 10
89
+ self._fig.set_figheight(8)
90
+ self._fig.set_figwidth(12)
91
+
92
+ self._ax_latency.set_xlabel("Concurrent Client Requests")
93
+ self._ax_latency.set_ylabel(latency_axis_label)
94
+ self._ax_throughput.set_ylabel(throughput_axis_label)
95
+
96
+ self._data = defaultdict(list)
97
+
98
+ def data(self):
99
+ """
100
+ Get the data in this plot
101
+
102
+ Returns
103
+ -------
104
+ dict
105
+ keys are line labels
106
+ and values are lists of floats
107
+ """
108
+
109
+ return self._data
110
+
111
+ def add_run_config_measurement(self, run_config_measurement):
112
+ """
113
+ Adds a measurement to this plot
114
+
115
+ Parameters
116
+ ----------
117
+ measurement : Measurement
118
+ The measurement containing the data to
119
+ be plotted.
120
+ """
121
+
122
+ # TODO-TMA-568: This needs to be updated because there will be multiple model configs
123
+ if (
124
+ "concurrency-range" in run_config_measurement.model_specific_pa_params()[0]
125
+ and run_config_measurement.model_specific_pa_params()[0][
126
+ "concurrency-range"
127
+ ]
128
+ ):
129
+ self._data["concurrency"].append(
130
+ run_config_measurement.model_specific_pa_params()[0][
131
+ "concurrency-range"
132
+ ]
133
+ )
134
+
135
+ if (
136
+ "request-rate-range" in run_config_measurement.model_specific_pa_params()[0]
137
+ and run_config_measurement.model_specific_pa_params()[0][
138
+ "request-rate-range"
139
+ ]
140
+ ):
141
+ self._data["request_rate"].append(
142
+ run_config_measurement.model_specific_pa_params()[0][
143
+ "request-rate-range"
144
+ ]
145
+ )
146
+
147
+ self._data["perf_throughput"].append(
148
+ run_config_measurement.get_non_gpu_metric_value(tag="perf_throughput")
149
+ )
150
+
151
+ for metric in self.detailed_metrics:
152
+ if MetricsManager.is_gpu_metric(tag=metric):
153
+ self._data[metric].append(
154
+ run_config_measurement.get_gpu_metric_value(tag=metric)
155
+ )
156
+ else:
157
+ self._data[metric].append(
158
+ run_config_measurement.get_non_gpu_metric_value(tag=metric)
159
+ )
160
+
161
+ def plot_data(self):
162
+ """
163
+ Calls plotting function
164
+ on this plot's Axes object
165
+ """
166
+
167
+ # Need to change the default x-axis plot title for request rates
168
+ if "request_rate" in self._data and self._data["request_rate"][0]:
169
+ self._ax_latency.set_xlabel("Client Request Rate")
170
+
171
+ # Sort the data by request rate or concurrency
172
+ if "request_rate" in self._data and self._data["request_rate"][0]:
173
+ sort_indices = list(
174
+ zip(*sorted(enumerate(self._data["request_rate"]), key=lambda x: x[1]))
175
+ )[0]
176
+ else:
177
+ sort_indices = list(
178
+ zip(*sorted(enumerate(self._data["concurrency"]), key=lambda x: x[1]))
179
+ )[0]
180
+
181
+ sorted_data = {
182
+ key: [data_list[i] for i in sort_indices]
183
+ for key, data_list in self._data.items()
184
+ }
185
+
186
+ # Plot latency breakdown bars
187
+ labels = dict(
188
+ zip(
189
+ self.detailed_metrics,
190
+ [
191
+ metric.header()
192
+ for metric in MetricsManager.get_metric_types(
193
+ tags=self.detailed_metrics
194
+ )
195
+ ],
196
+ )
197
+ )
198
+ bottoms = None
199
+
200
+ if "request_rate" in self._data:
201
+ sorted_data["indices"] = list(map(str, sorted_data["request_rate"]))
202
+ else:
203
+ sorted_data["indices"] = list(map(str, sorted_data["concurrency"]))
204
+
205
+ # Plot latency breakdown with concurrency casted as string to make uniform x
206
+ for metric, label in labels.items():
207
+ self._ax_latency.bar(
208
+ sorted_data["indices"],
209
+ sorted_data[metric],
210
+ width=self._bar_width,
211
+ label=label,
212
+ bottom=bottoms,
213
+ color=self._bar_colors[metric],
214
+ )
215
+ if not bottoms:
216
+ bottoms = sorted_data[metric]
217
+ else:
218
+ bottoms = list(map(lambda x, y: x + y, bottoms, sorted_data[metric]))
219
+
220
+ # Plot the inference line
221
+ inference_line = self._ax_throughput.plot(
222
+ sorted_data["indices"],
223
+ sorted_data["perf_throughput"],
224
+ label="Inferences/second",
225
+ marker="o",
226
+ color=self._bar_colors["perf_throughput"],
227
+ )
228
+
229
+ # Create legend handles
230
+ handles = [
231
+ mpatches.Patch(color=self._bar_colors[m], label=labels[m])
232
+ for m in self._bar_colors
233
+ if m != "perf_throughput"
234
+ ]
235
+ handles.append(inference_line[0])
236
+
237
+ self._ax_latency.legend(
238
+ handles=handles,
239
+ ncol=(len(self._bar_colors) // 2) + 1,
240
+ bbox_to_anchor=(self._legend_x, self._legend_y),
241
+ prop=dict(size=self._legend_font_size),
242
+ )
243
+ # Annotate inferences
244
+ for x, y in zip(sorted_data["indices"], sorted_data["perf_throughput"]):
245
+ self._ax_throughput.annotate(
246
+ str(round(y, 2)),
247
+ xy=(x, y),
248
+ textcoords="offset points", # how to position the text
249
+ xytext=(0, 10), # distance from text to points (x,y)
250
+ ha="center",
251
+ )
252
+
253
+ self._ax_latency.grid()
254
+ self._ax_latency.set_axisbelow(True)
255
+
256
+ def save(self, filepath):
257
+ """
258
+ Saves a .png of the plot to disk
259
+
260
+ Parameters
261
+ ----------
262
+ filepath : the path to the directory
263
+ this plot should be saved to
264
+ """
265
+
266
+ self._fig.savefig(os.path.join(filepath, self._name))
@@ -0,0 +1,224 @@
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 os
18
+ from collections import defaultdict
19
+ from typing import DefaultDict, Dict, Union
20
+
21
+ from model_analyzer.config.input.config_command_profile import ConfigCommandProfile
22
+ from model_analyzer.config.input.config_command_report import ConfigCommandReport
23
+ from model_analyzer.config.input.config_defaults import DEFAULT_CPU_MEM_PLOT
24
+ from model_analyzer.config.input.objects.config_plot import ConfigPlot
25
+ from model_analyzer.constants import GLOBAL_CONSTRAINTS_KEY, TOP_MODELS_REPORT_KEY
26
+ from model_analyzer.result.constraint_manager import ConstraintManager
27
+ from model_analyzer.result.result_manager import ResultManager
28
+
29
+ from .detailed_plot import DetailedPlot
30
+ from .simple_plot import SimplePlot
31
+
32
+
33
+ class PlotManager:
34
+ """
35
+ This class manages the construction and arrangement
36
+ of plots generated by model analyzer
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ config: Union[ConfigCommandProfile, ConfigCommandReport],
42
+ result_manager: ResultManager,
43
+ constraint_manager: ConstraintManager,
44
+ ):
45
+ """
46
+ Parameters
47
+ ----------
48
+ config : ConfigCommandProfile or ConfigCommandReport
49
+ The model analyzer's config containing information
50
+ about the kind of plots to generate
51
+ result_manager : ResultManager
52
+ instance that manages the result tables and
53
+ adding results
54
+ constraint_manager: ConstraintManager
55
+ instance that manages constraints
56
+ """
57
+
58
+ self._config = config
59
+ self._result_manager = result_manager
60
+
61
+ # Constraints should be plotted as well
62
+ self._constraints = constraint_manager.get_constraints_for_all_models()
63
+
64
+ # Construct plot output directory
65
+ self._plot_export_directory = os.path.join(config.export_path, "plots")
66
+ os.makedirs(self._plot_export_directory, exist_ok=True)
67
+
68
+ # Dict of list of plots
69
+ self._simple_plots: DefaultDict[str, Dict[str, SimplePlot]] = defaultdict()
70
+ self._detailed_plots: Dict[str, DetailedPlot] = {}
71
+
72
+ def create_summary_plots(self):
73
+ """
74
+ Constructs simple plots based on config specs
75
+ """
76
+
77
+ model_names = self._result_manager._profile_model_names
78
+
79
+ for plots_key in model_names:
80
+ self._create_summary_plot_for_model(
81
+ plots_key=plots_key,
82
+ model_name=plots_key,
83
+ num_results=self._config.num_configs_per_model,
84
+ )
85
+
86
+ if self._config.num_top_model_configs:
87
+ self._create_summary_plot_for_model(
88
+ plots_key=TOP_MODELS_REPORT_KEY,
89
+ model_name=None,
90
+ num_results=self._config.num_top_model_configs,
91
+ )
92
+
93
+ def _create_summary_plot_for_model(self, model_name, plots_key, num_results):
94
+ """
95
+ helper function that creates the summary plots
96
+ for a given model
97
+ """
98
+
99
+ for plot_config in self._config.plots:
100
+ constraints = self._constraints[GLOBAL_CONSTRAINTS_KEY]
101
+ if plots_key in self._constraints:
102
+ constraints = self._constraints[plots_key]
103
+ for run_config_result in self._result_manager.top_n_results(
104
+ model_name=model_name, n=num_results, include_default=True
105
+ ):
106
+ if run_config_result.run_config().cpu_only():
107
+ if plot_config.y_axis() == "gpu_used_memory":
108
+ plot_name, plot_config_dict = list(
109
+ DEFAULT_CPU_MEM_PLOT.items()
110
+ )[0]
111
+ plot_config = ConfigPlot(plot_name, **plot_config_dict)
112
+ self._create_update_simple_plot(
113
+ plots_key=plots_key,
114
+ plot_config=plot_config,
115
+ run_config_measurements=run_config_result.run_config_measurements(),
116
+ constraints=constraints,
117
+ )
118
+
119
+ def _create_update_simple_plot(
120
+ self, plots_key, plot_config, run_config_measurements, constraints
121
+ ):
122
+ """
123
+ Creates or updates a single simple plot, given a config name,
124
+ some measurements, and a key to put the plot into the simple plots
125
+ """
126
+
127
+ if plots_key not in self._simple_plots:
128
+ self._simple_plots[plots_key] = {}
129
+ if plot_config.name() not in self._simple_plots[plots_key]:
130
+ self._simple_plots[plots_key][plot_config.name()] = SimplePlot(
131
+ name=plot_config.name(),
132
+ title=plot_config.title(),
133
+ x_axis=plot_config.x_axis(),
134
+ y_axis=plot_config.y_axis(),
135
+ monotonic=plot_config.monotonic(),
136
+ )
137
+
138
+ for run_config_measurement in run_config_measurements:
139
+ self._simple_plots[plots_key][
140
+ plot_config.name()
141
+ ].add_run_config_measurement(
142
+ label=run_config_measurement.model_variants_name(),
143
+ run_config_measurement=run_config_measurement,
144
+ )
145
+
146
+ # In case this plot already had lines, we want to clear and replot
147
+ self._simple_plots[plots_key][plot_config.name()].clear()
148
+ self._simple_plots[plots_key][plot_config.name()].plot_data_and_constraints(
149
+ constraints=constraints
150
+ )
151
+
152
+ def create_detailed_plots(self):
153
+ """
154
+ Constructs detailed plots based on
155
+ requested config specs
156
+ """
157
+
158
+ # Create detailed plots
159
+ for model in self._config.report_model_configs:
160
+ model_config_name = model.model_config_name()
161
+ self._detailed_plots[model_config_name] = DetailedPlot(
162
+ f"latency_breakdown", "Online Performance"
163
+ )
164
+ (
165
+ model_config,
166
+ run_config_measurements,
167
+ ) = self._result_manager.get_model_configs_run_config_measurements(
168
+ model_config_name
169
+ )
170
+
171
+ # If model_config_name was present in results
172
+ if run_config_measurements:
173
+ for run_config_measurement in run_config_measurements:
174
+ self._detailed_plots[model_config_name].add_run_config_measurement(
175
+ run_config_measurement
176
+ )
177
+ self._detailed_plots[model_config_name].plot_data()
178
+
179
+ # Create the simple plots for the detailed reports
180
+ for plot_config in model.plots():
181
+ if model_config.cpu_only() and (
182
+ plot_config.y_axis().startswith("gpu_")
183
+ or plot_config.x_axis().startswith("gpu_")
184
+ ):
185
+ continue
186
+ self._create_update_simple_plot(
187
+ plots_key=model_config_name,
188
+ plot_config=plot_config,
189
+ run_config_measurements=run_config_measurements,
190
+ constraints=None,
191
+ )
192
+
193
+ def export_summary_plots(self):
194
+ """
195
+ write the plots to disk
196
+ """
197
+
198
+ simple_plot_dir = os.path.join(self._plot_export_directory, "simple")
199
+ for plots_key, plot_dicts in self._simple_plots.items():
200
+ model_plot_dir = os.path.join(simple_plot_dir, plots_key)
201
+ os.makedirs(model_plot_dir, exist_ok=True)
202
+ for plot in plot_dicts.values():
203
+ plot.save(model_plot_dir)
204
+
205
+ def export_detailed_plots(self):
206
+ """
207
+ Write detailed plots to disk
208
+ """
209
+
210
+ detailed_plot_dir = os.path.join(self._plot_export_directory, "detailed")
211
+ simple_plot_dir = os.path.join(self._plot_export_directory, "simple")
212
+ for model_config_name, detailed_plot in self._detailed_plots.items():
213
+ detailed_model_config_plot_dir = os.path.join(
214
+ detailed_plot_dir, model_config_name
215
+ )
216
+ os.makedirs(detailed_model_config_plot_dir, exist_ok=True)
217
+ detailed_plot.save(detailed_model_config_plot_dir)
218
+
219
+ simple_model_config_plot_dir = os.path.join(
220
+ simple_plot_dir, model_config_name
221
+ )
222
+ os.makedirs(simple_model_config_plot_dir, exist_ok=True)
223
+ for simple_plot in self._simple_plots[model_config_name].values():
224
+ simple_plot.save(simple_model_config_plot_dir)
@@ -0,0 +1,213 @@
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 os
18
+ from collections import defaultdict
19
+
20
+ import matplotlib.pyplot as plt
21
+
22
+ from model_analyzer.perf_analyzer.perf_config import PerfAnalyzerConfig
23
+ from model_analyzer.record.metrics_manager import MetricsManager
24
+ from model_analyzer.reports.report_utils import truncate_model_config_name
25
+
26
+
27
+ class SimplePlot:
28
+ """
29
+ A wrapper class around a matplotlib
30
+ plot that adapts with the kinds of
31
+ plots the model analyzer wants to generates
32
+
33
+ A singe plot holds data for multiple
34
+ model configs, but only holds one
35
+ type of plot
36
+ """
37
+
38
+ def __init__(self, name, title, x_axis, y_axis, monotonic=False):
39
+ """
40
+ Parameters
41
+ ----------
42
+ name: str
43
+ The name of the file that the plot
44
+ will be saved as
45
+ title : str
46
+ The title of this plot/figure
47
+ x_axis : str
48
+ The metric tag for the x-axis of this plot
49
+ y_axis : str
50
+ The metric tag for the y-axis of this plot
51
+ monotonic: bool
52
+ Whether or not to prune decreasing points in this
53
+ plot
54
+ """
55
+
56
+ self._name = name
57
+ self._title = title
58
+ self._x_axis = x_axis
59
+ self._y_axis = y_axis
60
+ self._monotonic = monotonic
61
+
62
+ self._fig, self._ax = plt.subplots()
63
+
64
+ self._data = {}
65
+
66
+ def add_run_config_measurement(self, label, run_config_measurement):
67
+ """
68
+ Adds a measurement to this plot
69
+
70
+ Parameters
71
+ ----------
72
+ label : str
73
+ The name of the config(s) this measurement
74
+ is taken from.
75
+ run_config_measurement : RunConfigMeasurement
76
+ The measurement containing the data to
77
+ be plotted.
78
+ """
79
+
80
+ if label not in self._data:
81
+ self._data[label] = defaultdict(list)
82
+
83
+ if self._x_axis.replace("_", "-") in PerfAnalyzerConfig.allowed_keys():
84
+ self._data[label]["x_data"].append(
85
+ run_config_measurement.model_specific_pa_params()[0][
86
+ self._x_axis.replace("_", "-")
87
+ ]
88
+ )
89
+ else:
90
+ if MetricsManager.is_gpu_metric(tag=self._x_axis):
91
+ self._data[label]["x_data"].append(
92
+ run_config_measurement.get_gpu_metric_value(tag=self._x_axis)
93
+ )
94
+ else:
95
+ self._data[label]["x_data"].append(
96
+ run_config_measurement.get_non_gpu_metric_value(tag=self._x_axis)
97
+ )
98
+
99
+ if self._y_axis.replace("_", "-") in PerfAnalyzerConfig.allowed_keys():
100
+ self._data[label]["y_data"].append(
101
+ run_config_measurement.model_specific_pa_params()[0][
102
+ self._y_axis.replace("_", "-")
103
+ ]
104
+ )
105
+ else:
106
+ if MetricsManager.is_gpu_metric(tag=self._y_axis):
107
+ self._data[label]["y_data"].append(
108
+ run_config_measurement.get_gpu_metric_value(tag=self._y_axis)
109
+ )
110
+ else:
111
+ self._data[label]["y_data"].append(
112
+ run_config_measurement.get_non_gpu_metric_value(tag=self._y_axis)
113
+ )
114
+
115
+ def clear(self):
116
+ """
117
+ Clear the contents of the current Axes object
118
+ """
119
+
120
+ self._ax.clear()
121
+
122
+ def plot_data_and_constraints(self, constraints):
123
+ """
124
+ Calls plotting function
125
+ on this plot's Axes object
126
+
127
+ Parameters
128
+ ----------
129
+ constraints: ModelConstraints object
130
+ The keys are metric tags and values are dicts whose
131
+ keys are constraint types (min, max) and values are their
132
+ values
133
+ """
134
+
135
+ self._ax.set_title(self._title)
136
+
137
+ if self._x_axis.replace("_", "-") in PerfAnalyzerConfig.allowed_keys():
138
+ self._x_header = self._x_axis.replace("_", " ").title()
139
+ else:
140
+ self._x_header = MetricsManager.get_metric_types([self._x_axis])[0].header(
141
+ aggregation_tag=""
142
+ )
143
+
144
+ if self._y_axis.replace("_", "-") in PerfAnalyzerConfig.allowed_keys():
145
+ self._y_header = self._y_axis.replace("_", " ").title()
146
+ else:
147
+ self._y_header = MetricsManager.get_metric_types([self._y_axis])[0].header(
148
+ aggregation_tag=""
149
+ )
150
+
151
+ self._ax.set_xlabel(self._x_header)
152
+ self._ax.set_ylabel(self._y_header)
153
+
154
+ for model_config_name, data in self._data.items():
155
+ # Sort the data by x-axis
156
+ x_data, y_data = (
157
+ list(t) for t in zip(*sorted(zip(data["x_data"], data["y_data"])))
158
+ )
159
+
160
+ model_config_name = truncate_model_config_name(model_config_name)
161
+
162
+ if self._monotonic:
163
+ filtered_x, filtered_y = [x_data[0]], [y_data[0]]
164
+ for i in range(1, len(x_data)):
165
+ if y_data[i] > filtered_y[-1]:
166
+ filtered_x.append(x_data[i])
167
+ filtered_y.append(y_data[i])
168
+ x_data, y_data = filtered_x, filtered_y
169
+
170
+ self._ax.plot(x_data, y_data, marker="o", label=model_config_name)
171
+
172
+ # Plot constraints
173
+ if constraints:
174
+ if constraints.has_metric(self._x_axis):
175
+ for _, constraint_val in constraints[self._x_axis].items():
176
+ constraint_label = f"Target {self._x_header.rsplit(' ',1)[0]}"
177
+ self._ax.axvline(
178
+ x=constraint_val, linestyle="--", label=constraint_label
179
+ )
180
+ if constraints.has_metric(self._y_axis):
181
+ for _, constraint_val in constraints[self._y_axis].items():
182
+ constraint_label = f"Target {self._y_header.rsplit(' ', 1)[0]}"
183
+ self._ax.axhline(
184
+ y=constraint_val, linestyle="--", label=constraint_label
185
+ )
186
+ # plot h lines
187
+ self._ax.legend()
188
+ self._ax.grid()
189
+
190
+ def data(self):
191
+ """
192
+ Get the data in this plot
193
+
194
+ Returns
195
+ -------
196
+ dict
197
+ keys are line labels
198
+ and values are lists of floats
199
+ """
200
+
201
+ return self._data
202
+
203
+ def save(self, filepath):
204
+ """
205
+ Saves a .png of the plot to disk
206
+
207
+ Parameters
208
+ ----------
209
+ filepath : the path to the directory
210
+ this plot should be saved to
211
+ """
212
+
213
+ self._fig.savefig(os.path.join(filepath, self._name))