nsight-python 1.1.0__py3-none-any.whl → 1.1.1__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.
- nsight/annotation.py +14 -1
- nsight/collection/core.py +17 -117
- nsight/collection/cupti.py +2 -3
- nsight/collection/ncu.py +2 -3
- nsight/extraction.py +51 -48
- nsight/utils.py +15 -0
- {nsight_python-1.1.0.dist-info → nsight_python-1.1.1.dist-info}/METADATA +1 -1
- {nsight_python-1.1.0.dist-info → nsight_python-1.1.1.dist-info}/RECORD +10 -10
- {nsight_python-1.1.0.dist-info → nsight_python-1.1.1.dist-info}/WHEEL +1 -1
- {nsight_python-1.1.0.dist-info → nsight_python-1.1.1.dist-info}/licenses/LICENSE +0 -0
nsight/annotation.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import functools
|
|
5
5
|
import threading
|
|
6
|
+
import warnings
|
|
6
7
|
from collections.abc import Callable
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -112,7 +113,9 @@ class annotate:
|
|
|
112
113
|
ignore_failures: Flag indicating whether to ignore
|
|
113
114
|
failures in the annotate context. If set to ``True``, any exceptions
|
|
114
115
|
raised within the context will be ignored, and the profiling will
|
|
115
|
-
continue. The measured metric for this run will be set to NaN
|
|
116
|
+
continue. The measured metric for this run will be set to NaN, and
|
|
117
|
+
a ``RuntimeWarning`` naming the annotation and the ignored exception
|
|
118
|
+
is emitted.
|
|
116
119
|
Default: ``False``
|
|
117
120
|
|
|
118
121
|
Raises:
|
|
@@ -179,6 +182,16 @@ class annotate:
|
|
|
179
182
|
if exc_type and not self.ignore_failures:
|
|
180
183
|
return False # propagate the exception
|
|
181
184
|
|
|
185
|
+
if exc_type:
|
|
186
|
+
# The failed run only shows up as NaN in the results, so report why
|
|
187
|
+
# it failed rather than discarding the exception.
|
|
188
|
+
warnings.warn(
|
|
189
|
+
f"Ignored failure in annotation '{self.name}': "
|
|
190
|
+
f"{exc_type.__name__}: {exc_value}",
|
|
191
|
+
category=RuntimeWarning,
|
|
192
|
+
stacklevel=2,
|
|
193
|
+
)
|
|
194
|
+
|
|
182
195
|
return True
|
|
183
196
|
|
|
184
197
|
def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
|
nsight/collection/core.py
CHANGED
|
@@ -7,7 +7,6 @@ import functools
|
|
|
7
7
|
import importlib.util
|
|
8
8
|
import inspect
|
|
9
9
|
import os
|
|
10
|
-
import pickle
|
|
11
10
|
import threading
|
|
12
11
|
import time
|
|
13
12
|
import warnings
|
|
@@ -235,112 +234,6 @@ def _sanitize_configs(
|
|
|
235
234
|
return configs # type: ignore[return-value]
|
|
236
235
|
|
|
237
236
|
|
|
238
|
-
def _picklable_or_none(value: Any) -> Any:
|
|
239
|
-
"""Return ``value`` if it can be pickled, otherwise None."""
|
|
240
|
-
try:
|
|
241
|
-
pickle.dumps(value)
|
|
242
|
-
return value
|
|
243
|
-
except Exception:
|
|
244
|
-
return None
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def _sanitize_for_pickle(runs: Sequence[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
248
|
-
"""Replace any non-picklable collected value with None, preserving structure.
|
|
249
|
-
|
|
250
|
-
Used as a fallback when serializing the full collected-info list fails
|
|
251
|
-
because a collector returned something unserializable (a CUDA stream, file
|
|
252
|
-
handle, lambda, ...). Degrading the offending value to None keeps the rest of
|
|
253
|
-
the run's data instead of losing everything.
|
|
254
|
-
"""
|
|
255
|
-
sanitized: list[dict[str, Any]] = []
|
|
256
|
-
for run in runs:
|
|
257
|
-
new = dict(run)
|
|
258
|
-
new["config"] = _picklable_or_none(run.get("config"))
|
|
259
|
-
new["once_info"] = {
|
|
260
|
-
k: _picklable_or_none(v) for k, v in run.get("once_info", {}).items()
|
|
261
|
-
}
|
|
262
|
-
new["config_info"] = {
|
|
263
|
-
k: _picklable_or_none(v) for k, v in run.get("config_info", {}).items()
|
|
264
|
-
}
|
|
265
|
-
annotation_info = []
|
|
266
|
-
for entry in run.get("annotation_info", []):
|
|
267
|
-
new_entry = dict(entry)
|
|
268
|
-
new_entry["config"] = _picklable_or_none(entry.get("config"))
|
|
269
|
-
new_entry["data"] = {
|
|
270
|
-
k: _picklable_or_none(v) for k, v in entry.get("data", {}).items()
|
|
271
|
-
}
|
|
272
|
-
annotation_info.append(new_entry)
|
|
273
|
-
new["annotation_info"] = annotation_info
|
|
274
|
-
sanitized.append(new)
|
|
275
|
-
return sanitized
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _serialize_collected_info(
|
|
279
|
-
runs: Sequence[Mapping[str, Any]],
|
|
280
|
-
) -> tuple[bytes, bool] | None:
|
|
281
|
-
"""Serialize collected info, replacing only non-picklable values.
|
|
282
|
-
|
|
283
|
-
Serialization is completed in memory before opening the output file. This
|
|
284
|
-
keeps collector-value failures separate from filesystem failures such as a
|
|
285
|
-
full disk or an unwritable output directory.
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
The serialized payload and whether sanitization was required, or
|
|
289
|
-
``None`` when the sanitized data still cannot be serialized.
|
|
290
|
-
"""
|
|
291
|
-
try:
|
|
292
|
-
return pickle.dumps(runs), False
|
|
293
|
-
except Exception as exc:
|
|
294
|
-
sanitized = _sanitize_for_pickle(runs)
|
|
295
|
-
try:
|
|
296
|
-
payload = pickle.dumps(sanitized)
|
|
297
|
-
except Exception as fallback_exc:
|
|
298
|
-
warnings.warn(
|
|
299
|
-
"Failed to serialize custom info after replacing non-picklable "
|
|
300
|
-
f"values ({type(fallback_exc).__name__}: {fallback_exc}).",
|
|
301
|
-
category=RuntimeWarning,
|
|
302
|
-
stacklevel=2,
|
|
303
|
-
)
|
|
304
|
-
return None
|
|
305
|
-
|
|
306
|
-
warnings.warn(
|
|
307
|
-
f"Some collected info was not picklable ({type(exc).__name__}: {exc}); "
|
|
308
|
-
"non-picklable collector values have been replaced with None.",
|
|
309
|
-
category=RuntimeWarning,
|
|
310
|
-
stacklevel=2,
|
|
311
|
-
)
|
|
312
|
-
return payload, True
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def _save_collected_info(
|
|
316
|
-
info_file: str,
|
|
317
|
-
runs: Sequence[Mapping[str, Any]],
|
|
318
|
-
verbosity: VerbosityLevel,
|
|
319
|
-
) -> bool:
|
|
320
|
-
"""Serialize and save collected info without conflating failure causes."""
|
|
321
|
-
serialized = _serialize_collected_info(runs)
|
|
322
|
-
if serialized is None:
|
|
323
|
-
return False
|
|
324
|
-
|
|
325
|
-
payload, sanitized = serialized
|
|
326
|
-
try:
|
|
327
|
-
with open(info_file, "wb") as output_file:
|
|
328
|
-
output_file.write(payload)
|
|
329
|
-
except OSError as exc:
|
|
330
|
-
warnings.warn(
|
|
331
|
-
f"Failed to save custom info to {info_file} "
|
|
332
|
-
f"({type(exc).__name__}: {exc}).",
|
|
333
|
-
category=RuntimeWarning,
|
|
334
|
-
stacklevel=2,
|
|
335
|
-
)
|
|
336
|
-
return False
|
|
337
|
-
|
|
338
|
-
if verbosity >= VerbosityLevel.INFO:
|
|
339
|
-
suffix = " (sanitized)" if sanitized else ""
|
|
340
|
-
print(f"[NSIGHT-PYTHON] Saved collected info{suffix} to {info_file}")
|
|
341
|
-
return True
|
|
342
|
-
|
|
343
|
-
|
|
344
237
|
def run_profile_session(
|
|
345
238
|
func: Callable[..., None],
|
|
346
239
|
configs: list[Sequence[Any]],
|
|
@@ -352,8 +245,13 @@ def run_profile_session(
|
|
|
352
245
|
thermal_timeout: int | None = None,
|
|
353
246
|
thermal_device: int | None = None,
|
|
354
247
|
info_collectors_list: list[NormalizedInfoCollector] | None = None,
|
|
355
|
-
|
|
356
|
-
|
|
248
|
+
) -> list[dict[str, Any]]:
|
|
249
|
+
"""Run ``func`` for every config and run inside a profiling session.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The custom info collected for each run, in run order, for the caller
|
|
253
|
+
to pass to extraction.
|
|
254
|
+
"""
|
|
357
255
|
|
|
358
256
|
if verbosity >= VerbosityLevel.INFO:
|
|
359
257
|
print("")
|
|
@@ -385,8 +283,13 @@ def run_profile_session(
|
|
|
385
283
|
bar_length = 100
|
|
386
284
|
progress_time: float = 0
|
|
387
285
|
|
|
388
|
-
# overwrite flag: we do not overwrite when output mode is debug
|
|
389
|
-
|
|
286
|
+
# overwrite flag: we do not overwrite when output mode is debug, or when
|
|
287
|
+
# stdout is not a terminal (overwriting relies on cursor-movement codes)
|
|
288
|
+
is_terminal = utils.stdout_is_terminal()
|
|
289
|
+
overwrite_output = verbosity < VerbosityLevel.DEBUG and is_terminal
|
|
290
|
+
# Without a terminal to overwrite in place, intermediate progress bars would
|
|
291
|
+
# each become a new line, so only the final one is printed (except in debug)
|
|
292
|
+
show_intermediate_progress = is_terminal or verbosity >= VerbosityLevel.DEBUG
|
|
390
293
|
show_return_type_warning = False
|
|
391
294
|
config_lengths: list[int] = list()
|
|
392
295
|
|
|
@@ -439,7 +342,7 @@ def run_profile_session(
|
|
|
439
342
|
)
|
|
440
343
|
|
|
441
344
|
# Store collected info for each run
|
|
442
|
-
collected_info_per_run = []
|
|
345
|
+
collected_info_per_run: list[dict[str, Any]] = []
|
|
443
346
|
|
|
444
347
|
for c in configs:
|
|
445
348
|
sig = inspect.signature(func)
|
|
@@ -560,7 +463,7 @@ def run_profile_session(
|
|
|
560
463
|
|
|
561
464
|
# Update time estimates every half second
|
|
562
465
|
if time.time() - progress_time > 0.5:
|
|
563
|
-
if verbosity >= VerbosityLevel.INFO:
|
|
466
|
+
if verbosity >= VerbosityLevel.INFO and show_intermediate_progress:
|
|
564
467
|
utils.print_progress_bar(
|
|
565
468
|
total_runs,
|
|
566
469
|
curr_run,
|
|
@@ -594,10 +497,7 @@ def run_profile_session(
|
|
|
594
497
|
stacklevel=external_stacklevel,
|
|
595
498
|
)
|
|
596
499
|
|
|
597
|
-
|
|
598
|
-
if info_collectors_list and output_prefix:
|
|
599
|
-
info_file = f"{output_prefix}collected_info.pkl"
|
|
600
|
-
_save_collected_info(info_file, collected_info_per_run, verbosity)
|
|
500
|
+
return collected_info_per_run
|
|
601
501
|
|
|
602
502
|
|
|
603
503
|
@dataclasses.dataclass
|
nsight/collection/cupti.py
CHANGED
|
@@ -365,7 +365,7 @@ class CUPTICollector(core.NsightCollector):
|
|
|
365
365
|
for cbid in _LAUNCH_CBIDS:
|
|
366
366
|
cupti.activity_enable_driver_api(cbid, 1)
|
|
367
367
|
|
|
368
|
-
core.run_profile_session(
|
|
368
|
+
collected_info_per_run = core.run_profile_session(
|
|
369
369
|
func,
|
|
370
370
|
configs_list,
|
|
371
371
|
settings.runs,
|
|
@@ -376,7 +376,6 @@ class CUPTICollector(core.NsightCollector):
|
|
|
376
376
|
settings.thermal_timeout,
|
|
377
377
|
settings.thermal_device,
|
|
378
378
|
settings.info_collectors,
|
|
379
|
-
settings.output_prefix,
|
|
380
379
|
)
|
|
381
380
|
|
|
382
381
|
# A kernel's activity record only exists once it has finished, so
|
|
@@ -443,7 +442,7 @@ class CUPTICollector(core.NsightCollector):
|
|
|
443
442
|
settings.info_collectors,
|
|
444
443
|
config_scope_columns,
|
|
445
444
|
annotation_scope_columns,
|
|
446
|
-
|
|
445
|
+
collected_info_per_run=collected_info_per_run,
|
|
447
446
|
)
|
|
448
447
|
|
|
449
448
|
return df
|
nsight/collection/ncu.py
CHANGED
|
@@ -492,7 +492,7 @@ class NCUCollector(core.NsightCollector):
|
|
|
492
492
|
f"{len(configs_list)} configurations, {settings.runs} runs each",
|
|
493
493
|
)
|
|
494
494
|
|
|
495
|
-
core.run_profile_session(
|
|
495
|
+
collected_info_per_run = core.run_profile_session(
|
|
496
496
|
func,
|
|
497
497
|
configs_list,
|
|
498
498
|
settings.runs,
|
|
@@ -503,7 +503,6 @@ class NCUCollector(core.NsightCollector):
|
|
|
503
503
|
settings.thermal_timeout,
|
|
504
504
|
settings.thermal_device,
|
|
505
505
|
settings.info_collectors,
|
|
506
|
-
settings.output_prefix,
|
|
507
506
|
)
|
|
508
507
|
finally:
|
|
509
508
|
try:
|
|
@@ -557,7 +556,7 @@ class NCUCollector(core.NsightCollector):
|
|
|
557
556
|
settings.info_collectors,
|
|
558
557
|
config_scope_columns,
|
|
559
558
|
annotation_scope_columns,
|
|
560
|
-
|
|
559
|
+
collected_info_per_run=collected_info_per_run,
|
|
561
560
|
)
|
|
562
561
|
|
|
563
562
|
return df
|
nsight/extraction.py
CHANGED
|
@@ -47,6 +47,14 @@ DERIVED_METRIC_MISSING_UNIT_WARNING = (
|
|
|
47
47
|
"np.nan will be added to the 'Unit' column in the dataframe."
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
+
# Hint appended to kernel-count errors. Autotuning and other first-call work
|
|
51
|
+
# launch extra kernels only on the first run.
|
|
52
|
+
_WARMUP_HINT = (
|
|
53
|
+
"If the code in the annotation autotunes or does other work only on its "
|
|
54
|
+
"first call, run it once for every configuration before calling the "
|
|
55
|
+
"profiled function, so that every run launches the same kernels.\n"
|
|
56
|
+
)
|
|
57
|
+
|
|
50
58
|
|
|
51
59
|
def extract_ncu_action_data(action: Any, metrics: Sequence[str]) -> utils.ActionData:
|
|
52
60
|
"""
|
|
@@ -114,8 +122,13 @@ def extract_data_from_report(
|
|
|
114
122
|
report: ncu_report.IContext = ncu_report.load_report(report_path)
|
|
115
123
|
except FileNotFoundError:
|
|
116
124
|
raise exceptions.ProfilerException(
|
|
117
|
-
"No NVIDIA Nsight Compute report found
|
|
118
|
-
"
|
|
125
|
+
f"No NVIDIA Nsight Compute report found at {report_path}. NVIDIA Nsight "
|
|
126
|
+
"Compute writes a report only if it profiled at least one kernel, so "
|
|
127
|
+
"the most likely cause is that no kernel was launched inside any "
|
|
128
|
+
"nsight.annotate region (for example, the annotated code only creates "
|
|
129
|
+
"a view, is a no-op, or returns a cached result). Otherwise, run with "
|
|
130
|
+
"`@nsight.analyze.kernel(verbosity=nsight.VerbosityLevel.DEBUG)` and "
|
|
131
|
+
"check the NVIDIA Nsight Compute log to identify the issue."
|
|
119
132
|
)
|
|
120
133
|
|
|
121
134
|
# Extract all profiling data
|
|
@@ -158,7 +171,7 @@ def extract_df_from_data(
|
|
|
158
171
|
info_collectors_list: List[Tuple[str, Callable[..., Any], str]] | None = None,
|
|
159
172
|
config_scope_columns: List[str] | None = None,
|
|
160
173
|
annotation_scope_columns: List[str] | None = None,
|
|
161
|
-
|
|
174
|
+
collected_info_per_run: Sequence[Mapping[str, Any]] | None = None,
|
|
162
175
|
) -> pd.DataFrame:
|
|
163
176
|
"""
|
|
164
177
|
Aggregates raw per-annotation measurements (from either tool) into a pandas
|
|
@@ -173,6 +186,11 @@ def extract_df_from_data(
|
|
|
173
186
|
derive_metric: Function to transform the raw metric values with config values.
|
|
174
187
|
verbosity: Controls display of extraction progress.
|
|
175
188
|
combine_kernel_metrics: Function to merge multiple kernel metrics.
|
|
189
|
+
info_collectors_list: Custom info collectors as ``(name, callback, scope)``.
|
|
190
|
+
config_scope_columns: Collector columns that vary per config or run.
|
|
191
|
+
annotation_scope_columns: Collector columns that vary per annotation.
|
|
192
|
+
collected_info_per_run: Custom info collected for each run, in run order,
|
|
193
|
+
as returned by :func:`nsight.collection.core.run_profile_session`.
|
|
176
194
|
|
|
177
195
|
Returns:
|
|
178
196
|
A DataFrame containing the extracted and transformed performance data.
|
|
@@ -206,46 +224,28 @@ def extract_df_from_data(
|
|
|
206
224
|
not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
207
225
|
}
|
|
208
226
|
|
|
209
|
-
# Load collected info from the pickle file if it exists
|
|
210
|
-
collected_info_per_run = []
|
|
211
227
|
custom_info_names = set()
|
|
212
228
|
if info_collectors_list:
|
|
213
|
-
# The caller supplies the prefix so the read path matches the
|
|
214
|
-
# collection-session write path exactly.
|
|
215
|
-
if info_prefix is None:
|
|
216
|
-
info_prefix = ""
|
|
217
|
-
|
|
218
229
|
# Column names come from the collector metadata (authoritative), not from
|
|
219
230
|
# the first run's collected data. Deriving from run 0 dropped any
|
|
220
231
|
# annotation-scope collector whose annotation was not entered on run 0.
|
|
221
232
|
custom_info_names = {name for name, _callback, _scope in info_collectors_list}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if os.path.exists(info_file):
|
|
225
|
-
import pickle
|
|
226
|
-
|
|
227
|
-
with open(info_file, "rb") as f:
|
|
228
|
-
collected_info_per_run = pickle.load(f)
|
|
229
|
-
if not isinstance(collected_info_per_run, list):
|
|
230
|
-
warnings.warn(
|
|
231
|
-
f"Collected info file {info_file} is malformed "
|
|
232
|
-
f"(expected a list, got {type(collected_info_per_run).__name__}); "
|
|
233
|
-
"ignoring custom info.",
|
|
234
|
-
category=RuntimeWarning,
|
|
235
|
-
stacklevel=2,
|
|
236
|
-
)
|
|
237
|
-
collected_info_per_run = []
|
|
238
|
-
elif verbosity >= VerbosityLevel.INFO:
|
|
239
|
-
print(f"[NSIGHT-PYTHON] Loaded collected info from {info_file}")
|
|
240
|
-
else:
|
|
241
|
-
if verbosity >= VerbosityLevel.INFO:
|
|
242
|
-
print(
|
|
243
|
-
f"[NSIGHT-PYTHON] Warning: No collected info file found at {info_file}"
|
|
244
|
-
)
|
|
233
|
+
else:
|
|
234
|
+
collected_info_per_run = None
|
|
245
235
|
|
|
246
236
|
# Create arrays for custom info collectors
|
|
247
237
|
custom_info_arrays: dict[str, list[Any]] = {name: [] for name in custom_info_names}
|
|
248
238
|
|
|
239
|
+
# Without this check, a session in which no kernel could be attributed to
|
|
240
|
+
# any annotation would silently return an empty DataFrame.
|
|
241
|
+
if not profiling_data:
|
|
242
|
+
raise RuntimeError(
|
|
243
|
+
"No kernels were profiled: no kernel launch could be attributed to "
|
|
244
|
+
"any nsight.annotate region. Check that each annotated region "
|
|
245
|
+
"launches at least one kernel and that no kernel launched in it is "
|
|
246
|
+
"listed in ignore_kernel_list."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
249
|
for annotation, annotation_data in profiling_data.items():
|
|
250
250
|
if verbosity >= VerbosityLevel.INFO:
|
|
251
251
|
print(f"[NSIGHT-PYTHON] Extracting {annotation} profiling data")
|
|
@@ -261,7 +261,9 @@ def extract_df_from_data(
|
|
|
261
261
|
if len(annotation_data) % len(configs_repeated) != 0:
|
|
262
262
|
raise RuntimeError(
|
|
263
263
|
"Expect same number of kernels per run. "
|
|
264
|
-
f"Got average of {len(annotation_data) / len(configs_repeated)} per run"
|
|
264
|
+
f"Got average of {len(annotation_data) / len(configs_repeated)} per run "
|
|
265
|
+
f"in the {annotation} annotation.\n"
|
|
266
|
+
f"{_WARMUP_HINT}"
|
|
265
267
|
)
|
|
266
268
|
num_kernels = len(annotation_data) // len(configs_repeated)
|
|
267
269
|
|
|
@@ -269,12 +271,15 @@ def extract_df_from_data(
|
|
|
269
271
|
if combine_kernel_metrics is None:
|
|
270
272
|
raise RuntimeError(
|
|
271
273
|
(
|
|
272
|
-
f"More than one (
|
|
274
|
+
f"More than one ({len(annotation_data)} in {len(configs_repeated)} "
|
|
275
|
+
f"runs, {num_kernels} per run on average) kernel is launched "
|
|
276
|
+
f"within the {annotation} annotation.\n"
|
|
273
277
|
"We expect one kernel per annotation.\n"
|
|
274
278
|
"Try one of the following solutions:\n"
|
|
275
279
|
" - Use `replay_mode='range'` to profile the entire annotated range instead of individual kernels\n"
|
|
276
280
|
" - Use `combine_kernel_metrics = lambda x, y: ...` to combine the metrics of multiple kernels\n"
|
|
277
281
|
" - Add some of the kernels to the ignore_kernel_list\n"
|
|
282
|
+
f"{_WARMUP_HINT}"
|
|
278
283
|
"Kernels are:\n"
|
|
279
284
|
+ "\n".join(sorted(set(x.name for x in annotation_data)))
|
|
280
285
|
)
|
|
@@ -395,13 +400,12 @@ def extract_df_from_data(
|
|
|
395
400
|
if (
|
|
396
401
|
collected_info_per_run
|
|
397
402
|
and idx < len(collected_info_per_run)
|
|
398
|
-
and isinstance(collected_info_per_run[idx],
|
|
403
|
+
and isinstance(collected_info_per_run[idx], Mapping)
|
|
399
404
|
):
|
|
400
405
|
run_info = collected_info_per_run[idx]
|
|
401
|
-
# Add once info. Guard against
|
|
402
|
-
# metadata-derived custom_info_names
|
|
403
|
-
#
|
|
404
|
-
# no column and are ignored rather than raising KeyError.
|
|
406
|
+
# Add once info. Guard against keys that are not in the
|
|
407
|
+
# metadata-derived custom_info_names; such keys have no column
|
|
408
|
+
# and are ignored rather than raising KeyError.
|
|
405
409
|
for name, value in run_info.get("once_info", {}).items():
|
|
406
410
|
if name in custom_info_arrays:
|
|
407
411
|
custom_info_arrays[name].append(value)
|
|
@@ -515,7 +519,7 @@ def extract_df_from_report(
|
|
|
515
519
|
info_collectors_list: List[Tuple[str, Callable[..., Any], str]] | None = None,
|
|
516
520
|
config_scope_columns: List[str] | None = None,
|
|
517
521
|
annotation_scope_columns: List[str] | None = None,
|
|
518
|
-
|
|
522
|
+
collected_info_per_run: Sequence[Mapping[str, Any]] | None = None,
|
|
519
523
|
) -> pd.DataFrame:
|
|
520
524
|
"""
|
|
521
525
|
Extracts and aggregates profiling results from an NVIDIA Nsight Compute report.
|
|
@@ -529,6 +533,11 @@ def extract_df_from_report(
|
|
|
529
533
|
derive_metric: Function to transform the raw metric values with config values.
|
|
530
534
|
ignore_kernel_list: Kernel names to ignore in the analysis.
|
|
531
535
|
combine_kernel_metrics: Function to merge multiple kernel metrics.
|
|
536
|
+
info_collectors_list: Custom info collectors as ``(name, callback, scope)``.
|
|
537
|
+
config_scope_columns: Collector columns that vary per config or run.
|
|
538
|
+
annotation_scope_columns: Collector columns that vary per annotation.
|
|
539
|
+
collected_info_per_run: Custom info collected for each run, in run order,
|
|
540
|
+
as returned by :func:`nsight.collection.core.run_profile_session`.
|
|
532
541
|
verbosity: Controls display of extraction progress.
|
|
533
542
|
|
|
534
543
|
Returns:
|
|
@@ -546,12 +555,6 @@ def extract_df_from_report(
|
|
|
546
555
|
verbosity,
|
|
547
556
|
)
|
|
548
557
|
|
|
549
|
-
# Fall back to reconstructing the info prefix from the report directory for
|
|
550
|
-
# direct callers that did not supply one.
|
|
551
|
-
if info_collectors_list and info_prefix is None:
|
|
552
|
-
report_dir = os.path.dirname(report_path)
|
|
553
|
-
info_prefix = os.path.join(report_dir, "") if report_dir else ""
|
|
554
|
-
|
|
555
558
|
return extract_df_from_data(
|
|
556
559
|
profiling_data,
|
|
557
560
|
metrics,
|
|
@@ -564,5 +567,5 @@ def extract_df_from_report(
|
|
|
564
567
|
info_collectors_list,
|
|
565
568
|
config_scope_columns,
|
|
566
569
|
annotation_scope_columns,
|
|
567
|
-
|
|
570
|
+
collected_info_per_run,
|
|
568
571
|
)
|
nsight/utils.py
CHANGED
|
@@ -78,8 +78,23 @@ class Colors:
|
|
|
78
78
|
UNDERLINE = "\033[4m"
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def stdout_is_terminal() -> bool:
|
|
82
|
+
"""
|
|
83
|
+
Returns whether stdout is an interactive terminal.
|
|
84
|
+
|
|
85
|
+
ANSI color and cursor-movement codes are only emitted when this is true, so
|
|
86
|
+
that output redirected to a file, a pipe, or a captured log stays plain text.
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
return sys.stdout.isatty()
|
|
90
|
+
except (AttributeError, ValueError):
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
81
94
|
def purple(msg: str) -> str: # pragma: no cover
|
|
82
95
|
"""Prints ``msg`` in purple."""
|
|
96
|
+
if not stdout_is_terminal():
|
|
97
|
+
return msg
|
|
83
98
|
return Colors.PURPLE + msg + Colors.ENDC
|
|
84
99
|
|
|
85
100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nsight-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Python GPU kernel profiling interface
|
|
5
5
|
Project-URL: Repository, https://github.com/NVIDIA/nsight-python
|
|
6
6
|
Project-URL: License, https://docs.nvidia.com/nsight-python/license.html
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
nsight/__init__.py,sha256=LrDF8j_emdbwNNsqSrABCRsDanTGzfRWfCjj8H2MbsI,1471
|
|
2
2
|
nsight/analyze.py,sha256=91BFc7SdR9JXQER6hBK2USGP43Ile_d9l3nVFVOME2E,35548
|
|
3
|
-
nsight/annotation.py,sha256
|
|
3
|
+
nsight/annotation.py,sha256=-dLOQS0wn54rKEmk-Mu3frINUDQqLkVNlxDaITV4CJo,7249
|
|
4
4
|
nsight/exceptions.py,sha256=LhtCtaOBP6m9KVWv9RjEqx3pGP2trLObC2aqEvE4fuE,2634
|
|
5
5
|
nsight/experimental.py,sha256=iE24Whb6Jo48f_tTNvZm8aLWyEabnB8xL8nUk2Ps9ik,2611
|
|
6
|
-
nsight/extraction.py,sha256=
|
|
6
|
+
nsight/extraction.py,sha256=kh-BZdNhngZZWmctPA7VcqOXWUvQ1y8jkTLQ4LGPzhs,25226
|
|
7
7
|
nsight/info_collector.py,sha256=hGJgFoC--UKYvGeUm76j_g32CUNd-HpseGt4wE3YJxI,12871
|
|
8
8
|
nsight/thermovision.py,sha256=z5_WvFbYLWb1jyuMPSfS1Ia58vdq3_tAtRPJzwg2sIc,14836
|
|
9
9
|
nsight/tools_manager.py,sha256=ZT0MrzBZK1l6HAIwBmAv54veBWbo0ZOxxvD5xhY1Xx4,3353
|
|
10
10
|
nsight/transformation.py,sha256=-Myr51srFaQzGAoFxFScQ7SsKrRpv4rVzW7Ob0yKH7k,8604
|
|
11
|
-
nsight/utils.py,sha256=
|
|
11
|
+
nsight/utils.py,sha256=MU2cVPsuA7sv-RuHECDvA3V9OS70duIFseNsU35NIcA,12508
|
|
12
12
|
nsight/visualization.py,sha256=OJpFVGXQuTPD2Tl2EsEuOO4rj8L-F_0OZczADPBwknk,20341
|
|
13
13
|
nsight/collection/__init__.py,sha256=a4C7kKg9WDdgmOwS-GpgLyXTwv2BVZNd1mjB3Ppax_g,335
|
|
14
|
-
nsight/collection/core.py,sha256=
|
|
15
|
-
nsight/collection/cupti.py,sha256=
|
|
16
|
-
nsight/collection/ncu.py,sha256=
|
|
17
|
-
nsight_python-1.1.
|
|
18
|
-
nsight_python-1.1.
|
|
19
|
-
nsight_python-1.1.
|
|
20
|
-
nsight_python-1.1.
|
|
14
|
+
nsight/collection/core.py,sha256=ykUCwUERY3EAd0_vNCbXF9IaOANiKka_eIYB7q7kb9Y,31112
|
|
15
|
+
nsight/collection/cupti.py,sha256=Sdq7_kjWa-vMVT2vZY5Bt5nuh9NB7SNQfnAuV4ylj6Q,17436
|
|
16
|
+
nsight/collection/ncu.py,sha256=ZT3HjjBMLUnYz5XlxJAmVs_ZsoZ1NIj-3FOsYxz6pnw,21661
|
|
17
|
+
nsight_python-1.1.1.dist-info/METADATA,sha256=UzuCstAbx72oaSYWoU8l0CXwUwWmiN8BvPm_7wcopkU,15121
|
|
18
|
+
nsight_python-1.1.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
19
|
+
nsight_python-1.1.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
20
|
+
nsight_python-1.1.1.dist-info/RECORD,,
|
|
File without changes
|