nsight-python 0.9.6__py3-none-any.whl → 1.0.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.
- nsight/__init__.py +6 -2
- nsight/analyze.py +82 -50
- nsight/annotation.py +4 -0
- nsight/collection/core.py +151 -34
- nsight/collection/ncu.py +285 -121
- nsight/extraction.py +91 -32
- nsight/thermovision.py +121 -25
- nsight/transformation.py +53 -22
- nsight/utils.py +25 -13
- nsight/visualization.py +4 -1
- {nsight_python-0.9.6.dist-info → nsight_python-1.0.0.dist-info}/METADATA +4 -4
- nsight_python-1.0.0.dist-info/RECORD +16 -0
- {nsight_python-0.9.6.dist-info → nsight_python-1.0.0.dist-info}/WHEEL +1 -1
- nsight_python-0.9.6.dist-info/RECORD +0 -16
- {nsight_python-0.9.6.dist-info → nsight_python-1.0.0.dist-info}/licenses/LICENSE +0 -0
nsight/__init__.py
CHANGED
|
@@ -5,8 +5,12 @@ from importlib.metadata import version
|
|
|
5
5
|
|
|
6
6
|
from nsight import analyze
|
|
7
7
|
from nsight.annotation import annotate
|
|
8
|
-
from nsight.
|
|
8
|
+
from nsight.collection.ncu import try_init_injection
|
|
9
|
+
from nsight.utils import VerbosityLevel, col_panel, row_panel
|
|
10
|
+
|
|
11
|
+
# Load injection library and symbols for NCU attach (if available)
|
|
12
|
+
try_init_injection()
|
|
9
13
|
|
|
10
14
|
__version__ = version("nsight-python")
|
|
11
15
|
|
|
12
|
-
__all__ = ["analyze", "annotate"]
|
|
16
|
+
__all__ = ["analyze", "annotate", "VerbosityLevel"]
|
nsight/analyze.py
CHANGED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import tempfile
|
|
8
8
|
import warnings
|
|
9
9
|
from collections.abc import Callable, Iterable, Sequence
|
|
10
|
-
from typing import Any, Literal, overload
|
|
10
|
+
from typing import Any, Literal, Mapping, overload
|
|
11
11
|
|
|
12
12
|
import matplotlib
|
|
13
13
|
import matplotlib.figure
|
|
@@ -16,6 +16,7 @@ from numpy.typing import NDArray
|
|
|
16
16
|
|
|
17
17
|
import nsight.collection as collection
|
|
18
18
|
import nsight.visualization as visualization
|
|
19
|
+
from nsight.utils import VerbosityLevel
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
# Overload 1: When used without parentheses: @kernel
|
|
@@ -32,9 +33,14 @@ def kernel(
|
|
|
32
33
|
*,
|
|
33
34
|
configs: Iterable[Any] | None = None,
|
|
34
35
|
runs: int = 1,
|
|
35
|
-
derive_metric:
|
|
36
|
+
derive_metric: (
|
|
37
|
+
Callable[
|
|
38
|
+
..., float | tuple[float, str] | Mapping[str, float | tuple[float, str]]
|
|
39
|
+
]
|
|
40
|
+
| None
|
|
41
|
+
) = None,
|
|
36
42
|
normalize_against: str | None = None,
|
|
37
|
-
|
|
43
|
+
verbosity: VerbosityLevel = VerbosityLevel.INFO,
|
|
38
44
|
metrics: Sequence[str] = ["gpu__time_duration.sum"],
|
|
39
45
|
ignore_kernel_list: Sequence[str] | None = None,
|
|
40
46
|
clock_control: Literal["base", "none"] = "none",
|
|
@@ -44,6 +50,7 @@ def kernel(
|
|
|
44
50
|
thermal_wait: int | None = None,
|
|
45
51
|
thermal_cont: int | None = None,
|
|
46
52
|
thermal_timeout: int | None = None,
|
|
53
|
+
thermal_device: int | None = None,
|
|
47
54
|
combine_kernel_metrics: Callable[[float, float], float] | None = None,
|
|
48
55
|
output_prefix: str | None = None,
|
|
49
56
|
output_csv: bool = False,
|
|
@@ -56,9 +63,14 @@ def kernel(
|
|
|
56
63
|
*,
|
|
57
64
|
configs: Iterable[Any] | None = None,
|
|
58
65
|
runs: int = 1,
|
|
59
|
-
derive_metric:
|
|
66
|
+
derive_metric: (
|
|
67
|
+
Callable[
|
|
68
|
+
..., float | tuple[float, str] | Mapping[str, float | tuple[float, str]]
|
|
69
|
+
]
|
|
70
|
+
| None
|
|
71
|
+
) = None,
|
|
60
72
|
normalize_against: str | None = None,
|
|
61
|
-
|
|
73
|
+
verbosity: VerbosityLevel = VerbosityLevel.INFO,
|
|
62
74
|
metrics: Sequence[str] = ["gpu__time_duration.sum"],
|
|
63
75
|
ignore_kernel_list: Sequence[str] | None = None,
|
|
64
76
|
clock_control: Literal["base", "none"] = "none",
|
|
@@ -68,6 +80,7 @@ def kernel(
|
|
|
68
80
|
thermal_wait: int | None = None,
|
|
69
81
|
thermal_cont: int | None = None,
|
|
70
82
|
thermal_timeout: int | None = None,
|
|
83
|
+
thermal_device: int | None = None,
|
|
71
84
|
combine_kernel_metrics: Callable[[float, float], float] | None = None,
|
|
72
85
|
output_prefix: str | None = None,
|
|
73
86
|
output_csv: bool = False,
|
|
@@ -93,6 +106,13 @@ def kernel(
|
|
|
93
106
|
- ``**kwargs``: Original function keyword arguments
|
|
94
107
|
- Returns ``ProfileResults`` object containing profiling data
|
|
95
108
|
|
|
109
|
+
.. note::
|
|
110
|
+
Configs are passed to the decorated function as positional arguments.
|
|
111
|
+
Both positional and keyword-only parameters are supported in the function
|
|
112
|
+
signature. If the function has ``*args`` or ``**kwargs``, they are tolerated
|
|
113
|
+
but will always be empty during profiling. Parameters with default values
|
|
114
|
+
may be omitted from configs — missing values will be filled from the defaults.
|
|
115
|
+
|
|
96
116
|
|
|
97
117
|
Parameters:
|
|
98
118
|
configs: An iterable of configurations to run the function with. Each configuration can be either:
|
|
@@ -105,6 +125,10 @@ def kernel(
|
|
|
105
125
|
|
|
106
126
|
configs = [1, 2, 3, 4]
|
|
107
127
|
|
|
128
|
+
If configs provide fewer values than the function has parameters,
|
|
129
|
+
the remaining parameters must have default values, which will be used
|
|
130
|
+
automatically.
|
|
131
|
+
|
|
108
132
|
If the configs are not provided at decoration time, they must be provided when calling the decorated function.
|
|
109
133
|
runs: Number of times each configuration should be executed.
|
|
110
134
|
derive_metric:
|
|
@@ -115,13 +139,18 @@ def kernel(
|
|
|
115
139
|
metrics. Return value can be either:
|
|
116
140
|
|
|
117
141
|
- A single value (float/int): Will be added as a new metric with the function name as the metric name. For lambda functions, the metric name will be ``"<lambda>"``.
|
|
118
|
-
- A
|
|
142
|
+
- A tuple (value, unit): A single metric value with its unit specified. The value will be added with the function name as the metric name, and the unit will be stored in the ``Unit`` column of the dataframe. Example: ``(tflops_value, "TFLOPS")``.
|
|
143
|
+
- A dictionary: Keys will be used as metric names, values can be either:
|
|
144
|
+
- Scalar values (float/int): Metric values without units. ``np.nan`` will be added to the ``Unit`` column and a warning will be issued.
|
|
145
|
+
- Tuples (value, unit): Metric values with units specified. The unit will be stored in the ``Unit`` column of the dataframe. Example: ``{"TFLOPS": (tflops_value, "TFLOPS"), "ArithIntensity": (intensity_value, "FLOP/byte")}``.
|
|
119
146
|
|
|
120
147
|
The parameter order requirements for the custom function:
|
|
121
148
|
|
|
122
149
|
- First several arguments: Must exactly match the order of metrics declared in the @kernel decorator. These arguments will receive the actual measured values of those metrics.
|
|
123
150
|
- Remaining arguments: Must exactly match the signature of the decorated function. In other words, the original function's parameters are passed in order.
|
|
124
151
|
|
|
152
|
+
**Note on units**: When units are not provided (returning scalar values instead of tuples), ``np.nan`` will be added to the ``Unit`` column in the dataframe and a warning will be issued. To avoid warnings and ensure proper unit tracking, return tuples ``(value, unit)`` for all derived metrics.
|
|
153
|
+
|
|
125
154
|
See the examples for concrete use cases.
|
|
126
155
|
normalize_against:
|
|
127
156
|
Annotation name to normalize metrics against (computes current / baseline).
|
|
@@ -193,11 +222,22 @@ def kernel(
|
|
|
193
222
|
thermal_timeout: Maximum wait time in seconds for GPU to cool down.
|
|
194
223
|
Default: 180 seconds
|
|
195
224
|
|
|
196
|
-
|
|
225
|
+
thermal_device: CUDA device ordinal to monitor for thermal throttling.
|
|
226
|
+
|
|
227
|
+
If set, monitor that CUDA device. If ``None``, monitor the current
|
|
228
|
+
CUDA device context. Device numbering follows CUDA runtime ordinals and
|
|
229
|
+
honors ``CUDA_VISIBLE_DEVICES``.
|
|
230
|
+
|
|
231
|
+
Default: ``None``
|
|
197
232
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
233
|
+
verbosity: Controls the verbosity level of the output. Selecting level N
|
|
234
|
+
enables output at all levels with value <= N. Default: ``VerbosityLevel.INFO``.
|
|
235
|
+
|
|
236
|
+
- ``VerbosityLevel.SILENT``: Suppresses all output.
|
|
237
|
+
- ``VerbosityLevel.INFO``: Shows a progress bar, profiling completion messages,
|
|
238
|
+
and report file paths.
|
|
239
|
+
- ``VerbosityLevel.DEBUG``: Adds configuration-specific logs, the full NCU CLI
|
|
240
|
+
command, and enables NCU verbose output.
|
|
201
241
|
|
|
202
242
|
output_prefix: When specified, all intermediate profiler files are created with this prefix.
|
|
203
243
|
For example, if `output_prefix="/home/user/run1_"`, the profiler will generate:
|
|
@@ -221,6 +261,7 @@ def kernel(
|
|
|
221
261
|
- ``Annotation``: Name of the annotated region being profiled
|
|
222
262
|
- ``Value``: Raw metric values collected by the profiler
|
|
223
263
|
- ``Metric``: The metrics being collected (e.g., ``gpu__time_duration.sum``) and the metrics being derived
|
|
264
|
+
- ``Unit``: Unit of measurement for the metric value (e.g., ``ns`` for nanoseconds)
|
|
224
265
|
- ``Kernel``: Name of the GPU kernel(s) launched
|
|
225
266
|
- ``GPU``: GPU device name
|
|
226
267
|
- ``Host``: Host machine name
|
|
@@ -242,6 +283,7 @@ def kernel(
|
|
|
242
283
|
- ``RelativeStdDevPct``: Standard deviation as a percentage of the mean
|
|
243
284
|
- ``StableMeasurement``: Boolean indicating if the measurement is stable (low variance). The measurement is stable if ``RelativeStdDevPct`` < 2 % .
|
|
244
285
|
- ``Metric``: The metrics being collected and the metrics being derived
|
|
286
|
+
- ``Unit``: Unit of measurement for the metric value (e.g., ``ns`` for nanoseconds)
|
|
245
287
|
- ``Kernel``: Name of the GPU kernel(s) launched
|
|
246
288
|
- ``GPU``: GPU device name
|
|
247
289
|
- ``Host``: Host machine name
|
|
@@ -263,34 +305,25 @@ def kernel(
|
|
|
263
305
|
|
|
264
306
|
def _create_profiler() -> collection.core.NsightProfiler:
|
|
265
307
|
"""Helper to create the profiler with the given settings."""
|
|
266
|
-
if output not in ("quiet", "progress", "verbose"):
|
|
267
|
-
raise ValueError("output must be 'quiet', 'progress' or 'verbose'")
|
|
268
|
-
|
|
269
|
-
output_progress = output == "progress" or output == "verbose"
|
|
270
|
-
output_detailed = output == "verbose"
|
|
271
|
-
|
|
272
308
|
# Create the output paths needed for the ncu report, ncu logs and the CSVs
|
|
273
309
|
prefix = output_prefix
|
|
274
|
-
if
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
) # Adds a trailing forward/backward slash
|
|
280
|
-
else:
|
|
281
|
-
os.makedirs(os.path.dirname(prefix) or ".", exist_ok=True)
|
|
310
|
+
if prefix is None:
|
|
311
|
+
prefix = tempfile.mkdtemp(prefix="nspy_")
|
|
312
|
+
prefix = os.path.join(prefix, "") # Adds a trailing forward/backward slash
|
|
313
|
+
else:
|
|
314
|
+
os.makedirs(os.path.dirname(prefix) or ".", exist_ok=True)
|
|
282
315
|
|
|
283
316
|
settings = collection.core.ProfileSettings(
|
|
284
317
|
configs=configs,
|
|
285
318
|
runs=runs,
|
|
286
|
-
|
|
287
|
-
output_detailed=output_detailed,
|
|
319
|
+
verbosity=verbosity,
|
|
288
320
|
derive_metric=derive_metric,
|
|
289
321
|
normalize_against=normalize_against,
|
|
290
322
|
thermal_mode=thermal_mode,
|
|
291
323
|
thermal_wait=thermal_wait,
|
|
292
324
|
thermal_cont=thermal_cont,
|
|
293
325
|
thermal_timeout=thermal_timeout,
|
|
326
|
+
thermal_device=thermal_device,
|
|
294
327
|
output_prefix=prefix,
|
|
295
328
|
output_csv=output_csv,
|
|
296
329
|
)
|
|
@@ -451,30 +484,29 @@ def plot(
|
|
|
451
484
|
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
452
485
|
result = func(*args, **kwargs)
|
|
453
486
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
)
|
|
487
|
+
# Legalize the user-provided metric for plotting
|
|
488
|
+
legalized_metric = _legalize_metric(result, metric)
|
|
489
|
+
|
|
490
|
+
visualization.visualize(
|
|
491
|
+
result.to_dataframe(),
|
|
492
|
+
metric=legalized_metric,
|
|
493
|
+
row_panels=row_panels,
|
|
494
|
+
col_panels=col_panels,
|
|
495
|
+
x_keys=x_keys,
|
|
496
|
+
print_data=print_data,
|
|
497
|
+
title=title,
|
|
498
|
+
filename=filename,
|
|
499
|
+
ylabel=ylabel or "",
|
|
500
|
+
annotate_points=annotate_points,
|
|
501
|
+
show_avg=show_avg,
|
|
502
|
+
show_geomean=show_geomean,
|
|
503
|
+
plot_type=plot_type,
|
|
504
|
+
plot_width=plot_width,
|
|
505
|
+
plot_height=plot_height,
|
|
506
|
+
variant_fields=variant_fields,
|
|
507
|
+
variant_annotations=variant_annotations,
|
|
508
|
+
plot_callback=plot_callback,
|
|
509
|
+
)
|
|
478
510
|
return result
|
|
479
511
|
|
|
480
512
|
return wrapper
|
nsight/annotation.py
CHANGED
|
@@ -129,6 +129,10 @@ class annotate(nvtx.annotate): # type: ignore[misc]
|
|
|
129
129
|
Nested annotations are currently not supported. However, since each
|
|
130
130
|
annotation is expected to contain a single kernel launch by default,
|
|
131
131
|
nested annotations should not be necessary in typical usage scenarios.
|
|
132
|
+
Entering and exiting the annotation context pushes and pops an NVTX
|
|
133
|
+
range, so calling ``nvtx.pop_range()`` inside the annotation context
|
|
134
|
+
without a corresponding push would pop the annotation context's own
|
|
135
|
+
range instead, resulting in incorrect profiling results.
|
|
132
136
|
|
|
133
137
|
"""
|
|
134
138
|
|
nsight/collection/core.py
CHANGED
|
@@ -11,12 +11,99 @@ import threading
|
|
|
11
11
|
import time
|
|
12
12
|
import warnings
|
|
13
13
|
from collections.abc import Callable, Collection, Iterable, Sequence
|
|
14
|
-
from typing import Any, List, Literal
|
|
14
|
+
from typing import Any, List, Literal, Mapping
|
|
15
15
|
|
|
16
16
|
import numpy as np
|
|
17
17
|
import pandas as pd
|
|
18
18
|
|
|
19
19
|
from nsight import annotation, exceptions, thermovision, transformation, utils
|
|
20
|
+
from nsight.utils import VerbosityLevel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_regular_params(
|
|
24
|
+
sig: inspect.Signature,
|
|
25
|
+
) -> list[inspect.Parameter]:
|
|
26
|
+
"""Return the list of regular (non-variadic) parameters from a signature."""
|
|
27
|
+
return [
|
|
28
|
+
p
|
|
29
|
+
for p in sig.parameters.values()
|
|
30
|
+
if p.kind
|
|
31
|
+
not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _count_params(
|
|
36
|
+
sig: inspect.Signature,
|
|
37
|
+
) -> tuple[int, int]:
|
|
38
|
+
"""Count (required, total) regular parameters in a signature.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
A (required_count, total_count) tuple. required_count excludes
|
|
42
|
+
parameters with defaults, total_count includes them.
|
|
43
|
+
"""
|
|
44
|
+
params = _get_regular_params(sig)
|
|
45
|
+
required = sum(1 for p in params if p.default is inspect.Parameter.empty)
|
|
46
|
+
return required, len(params)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _pad_config_with_defaults(
|
|
50
|
+
sig: inspect.Signature, config: Sequence[Any]
|
|
51
|
+
) -> tuple[Any, ...]:
|
|
52
|
+
"""Pad a config tuple with default values for any missing trailing parameters.
|
|
53
|
+
|
|
54
|
+
If the config provides fewer values than the function has parameters, the
|
|
55
|
+
remaining parameters must have defaults, which are appended to the config.
|
|
56
|
+
This ensures the rest of the pipeline always sees full-length configs.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
sig: The function's inspect.Signature.
|
|
60
|
+
config: The (possibly short) config tuple.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A full-length config tuple with defaults filled in.
|
|
64
|
+
"""
|
|
65
|
+
params = _get_regular_params(sig)
|
|
66
|
+
if len(config) == len(params):
|
|
67
|
+
return tuple(config)
|
|
68
|
+
padded = list(config)
|
|
69
|
+
for param in params[len(config) :]:
|
|
70
|
+
padded.append(param.default)
|
|
71
|
+
return tuple(padded)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _bind_config_to_signature(
|
|
75
|
+
sig: inspect.Signature, config: Sequence[Any]
|
|
76
|
+
) -> tuple[list[Any], dict[str, Any]]:
|
|
77
|
+
"""Split a config tuple into positional args and keyword-only kwargs.
|
|
78
|
+
|
|
79
|
+
Maps config values to function parameters in declaration order, separating
|
|
80
|
+
them into positional arguments (for POSITIONAL_OR_KEYWORD params) and
|
|
81
|
+
keyword arguments (for KEYWORD_ONLY params). VAR_POSITIONAL and VAR_KEYWORD
|
|
82
|
+
parameters are skipped. Parameters with defaults that are not covered by
|
|
83
|
+
the config are left for the function to fill in.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
sig: The function's inspect.Signature.
|
|
87
|
+
config: The config tuple to bind.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A (positional_args, keyword_args) tuple ready for func(*pos, **kw).
|
|
91
|
+
"""
|
|
92
|
+
positional: list[Any] = []
|
|
93
|
+
keyword: dict[str, Any] = {}
|
|
94
|
+
config_iter = iter(config)
|
|
95
|
+
for param in sig.parameters.values():
|
|
96
|
+
if param.kind in (
|
|
97
|
+
inspect.Parameter.VAR_POSITIONAL,
|
|
98
|
+
inspect.Parameter.VAR_KEYWORD,
|
|
99
|
+
):
|
|
100
|
+
continue
|
|
101
|
+
val = next(config_iter)
|
|
102
|
+
if param.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
103
|
+
keyword[param.name] = val
|
|
104
|
+
else:
|
|
105
|
+
positional.append(val)
|
|
106
|
+
return positional, keyword
|
|
20
107
|
|
|
21
108
|
|
|
22
109
|
def _sanitize_configs(
|
|
@@ -60,6 +147,10 @@ def _sanitize_configs(
|
|
|
60
147
|
- For functions with no parameters, an empty config [()] is created automatically.
|
|
61
148
|
- The function combines `args` and `kwargs` into a single list if `args` are provided.
|
|
62
149
|
- The function assumes that `kwargs` keys are in the expected order when combining.
|
|
150
|
+
- Config values are mapped to parameters in declaration order. Both
|
|
151
|
+
regular and keyword-only parameters are supported. Parameters with
|
|
152
|
+
defaults may be omitted from configs. ``*args`` and ``**kwargs``
|
|
153
|
+
are tolerated but ignored.
|
|
63
154
|
"""
|
|
64
155
|
if len(args) > 0:
|
|
65
156
|
# We do not expect any configs in this case
|
|
@@ -76,8 +167,8 @@ def _sanitize_configs(
|
|
|
76
167
|
if decorator_configs is None:
|
|
77
168
|
# Check if function takes no arguments
|
|
78
169
|
sig = inspect.signature(func)
|
|
79
|
-
|
|
80
|
-
if
|
|
170
|
+
required_count, total_count = _count_params(sig)
|
|
171
|
+
if required_count == 0:
|
|
81
172
|
# For functions with no arguments, create a single empty config
|
|
82
173
|
# This allows calling the function without requiring explicit configs
|
|
83
174
|
configs = [()]
|
|
@@ -105,8 +196,8 @@ def _sanitize_configs(
|
|
|
105
196
|
|
|
106
197
|
# If function takes exactly one argument, allow scalar configs
|
|
107
198
|
sig = inspect.signature(func)
|
|
108
|
-
|
|
109
|
-
if
|
|
199
|
+
required_count, total_count = _count_params(sig)
|
|
200
|
+
if total_count == 1:
|
|
110
201
|
normalized_configs: list[Sequence[Any]] = []
|
|
111
202
|
for config in configs:
|
|
112
203
|
if utils.is_scalar(config):
|
|
@@ -122,11 +213,21 @@ def _sanitize_configs(
|
|
|
122
213
|
)
|
|
123
214
|
first_config_arg_count = config_lengths[0]
|
|
124
215
|
|
|
125
|
-
# Validate that the number of args
|
|
126
|
-
if first_config_arg_count
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
216
|
+
# Validate that the number of args is between required and total parameters
|
|
217
|
+
if not (required_count <= first_config_arg_count <= total_count):
|
|
218
|
+
if required_count == total_count:
|
|
219
|
+
raise exceptions.ProfilerException(
|
|
220
|
+
f"Configs have {first_config_arg_count} arguments, but function expects {total_count}"
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
raise exceptions.ProfilerException(
|
|
224
|
+
f"Configs have {first_config_arg_count} arguments, but function expects "
|
|
225
|
+
f"between {required_count} and {total_count}"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Pad configs with default values for any missing trailing parameters
|
|
229
|
+
if first_config_arg_count < total_count:
|
|
230
|
+
configs = [_pad_config_with_defaults(sig, config) for config in configs]
|
|
130
231
|
|
|
131
232
|
return configs # type: ignore[return-value]
|
|
132
233
|
|
|
@@ -135,15 +236,15 @@ def run_profile_session(
|
|
|
135
236
|
func: Callable[..., None],
|
|
136
237
|
configs: List[Sequence[Any]],
|
|
137
238
|
runs: int,
|
|
138
|
-
|
|
139
|
-
output_detailed: bool,
|
|
239
|
+
verbosity: VerbosityLevel,
|
|
140
240
|
thermal_mode: Literal["auto", "manual", "off"],
|
|
141
241
|
thermal_wait: int | None = None,
|
|
142
242
|
thermal_cont: int | None = None,
|
|
143
243
|
thermal_timeout: int | None = None,
|
|
244
|
+
thermal_device: int | None = None,
|
|
144
245
|
) -> None:
|
|
145
246
|
|
|
146
|
-
if
|
|
247
|
+
if verbosity >= VerbosityLevel.INFO:
|
|
147
248
|
print("")
|
|
148
249
|
print("")
|
|
149
250
|
|
|
@@ -155,7 +256,8 @@ def run_profile_session(
|
|
|
155
256
|
thermal_wait=thermal_wait,
|
|
156
257
|
thermal_cont=thermal_cont,
|
|
157
258
|
thermal_timeout=thermal_timeout,
|
|
158
|
-
|
|
259
|
+
thermal_device=thermal_device,
|
|
260
|
+
verbose=verbosity >= VerbosityLevel.DEBUG,
|
|
159
261
|
)
|
|
160
262
|
thermovision_initialized = thermal_controller.init()
|
|
161
263
|
if not thermovision_initialized:
|
|
@@ -170,21 +272,22 @@ def run_profile_session(
|
|
|
170
272
|
bar_length = 100
|
|
171
273
|
progress_time: float = 0
|
|
172
274
|
|
|
173
|
-
# overwrite flag: we do not overwrite when output mode is
|
|
174
|
-
overwrite_output =
|
|
275
|
+
# overwrite flag: we do not overwrite when output mode is debug
|
|
276
|
+
overwrite_output = verbosity < VerbosityLevel.DEBUG
|
|
175
277
|
show_return_type_warning = False
|
|
176
278
|
config_lengths: list[int] = list()
|
|
177
279
|
|
|
178
280
|
for c in configs:
|
|
179
|
-
|
|
281
|
+
sig = inspect.signature(func)
|
|
282
|
+
required_count, total_count = _count_params(sig)
|
|
180
283
|
|
|
181
284
|
# Handle scalar values
|
|
182
|
-
if
|
|
285
|
+
if total_count == 1:
|
|
183
286
|
if utils.is_scalar(c):
|
|
184
287
|
c = (c,)
|
|
185
288
|
|
|
186
289
|
# Check if func supports the input configs
|
|
187
|
-
if
|
|
290
|
+
if not (required_count <= len(c) <= total_count):
|
|
188
291
|
raise exceptions.ProfilerException(
|
|
189
292
|
f"Function '{func.__name__}' does not support the input configuration"
|
|
190
293
|
)
|
|
@@ -199,7 +302,7 @@ def run_profile_session(
|
|
|
199
302
|
|
|
200
303
|
curr_config += 1
|
|
201
304
|
|
|
202
|
-
if
|
|
305
|
+
if verbosity >= VerbosityLevel.INFO:
|
|
203
306
|
utils.print_config(total_configs, curr_config, c, overwrite_output)
|
|
204
307
|
|
|
205
308
|
for i in range(runs):
|
|
@@ -211,8 +314,10 @@ def run_profile_session(
|
|
|
211
314
|
# Clear active annotations before each run
|
|
212
315
|
annotation.clear_active_annotations()
|
|
213
316
|
|
|
214
|
-
# Run the function with the config
|
|
215
|
-
|
|
317
|
+
# Run the function with the config, splitting into positional
|
|
318
|
+
# and keyword-only args based on the function signature
|
|
319
|
+
pos_args, kw_args = _bind_config_to_signature(sig, c)
|
|
320
|
+
result = func(*pos_args, **kw_args) # type: ignore[func-returns-value]
|
|
216
321
|
if result is not None:
|
|
217
322
|
show_return_type_warning = True
|
|
218
323
|
|
|
@@ -225,7 +330,7 @@ def run_profile_session(
|
|
|
225
330
|
|
|
226
331
|
# Update time estimates every half second
|
|
227
332
|
if time.time() - progress_time > 0.5:
|
|
228
|
-
if
|
|
333
|
+
if verbosity >= VerbosityLevel.INFO:
|
|
229
334
|
utils.print_progress_bar(
|
|
230
335
|
total_runs,
|
|
231
336
|
curr_run,
|
|
@@ -236,7 +341,7 @@ def run_profile_session(
|
|
|
236
341
|
progress_time = time.time()
|
|
237
342
|
|
|
238
343
|
# Update progress bar at end so it shows 100%
|
|
239
|
-
if
|
|
344
|
+
if verbosity >= VerbosityLevel.INFO:
|
|
240
345
|
utils.print_progress_bar(
|
|
241
346
|
total_runs,
|
|
242
347
|
curr_run,
|
|
@@ -274,17 +379,18 @@ class ProfileSettings:
|
|
|
274
379
|
runs: int
|
|
275
380
|
"""Number of times each configuration should be executed."""
|
|
276
381
|
|
|
277
|
-
|
|
278
|
-
"""
|
|
279
|
-
Will display a progress bar on stdout during profiling
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
output_detailed: bool
|
|
382
|
+
verbosity: VerbosityLevel
|
|
283
383
|
"""
|
|
284
|
-
|
|
384
|
+
Controls verbosity of output. SILENT suppresses all output, INFO shows progress bar,
|
|
385
|
+
DEBUG shows progress bar plus per-config logs and profiler output.
|
|
285
386
|
"""
|
|
286
387
|
|
|
287
|
-
derive_metric:
|
|
388
|
+
derive_metric: (
|
|
389
|
+
Callable[
|
|
390
|
+
..., float | tuple[float, str] | Mapping[str, float | tuple[float, str]]
|
|
391
|
+
]
|
|
392
|
+
| None
|
|
393
|
+
)
|
|
288
394
|
"""
|
|
289
395
|
A function to transform the collected metrics.
|
|
290
396
|
This can be used to compute derived metrics like TFLOPs that cannot
|
|
@@ -350,6 +456,16 @@ class ProfileSettings:
|
|
|
350
456
|
If None, uses default value (180 seconds).
|
|
351
457
|
"""
|
|
352
458
|
|
|
459
|
+
thermal_device: int | None
|
|
460
|
+
"""
|
|
461
|
+
CUDA device ordinal to monitor for thermal throttling.
|
|
462
|
+
|
|
463
|
+
If set, monitor that CUDA device. If None, monitor the current CUDA device
|
|
464
|
+
context.
|
|
465
|
+
Device numbering follows CUDA runtime ordinals and honors
|
|
466
|
+
``CUDA_VISIBLE_DEVICES``.
|
|
467
|
+
"""
|
|
468
|
+
|
|
353
469
|
output_prefix: str | None
|
|
354
470
|
"""
|
|
355
471
|
All intermediate profiler files are created with this prefix
|
|
@@ -399,6 +515,7 @@ class ProfileResults:
|
|
|
399
515
|
- ``RelativeStdDevPct``: Standard deviation as a percentage of the mean
|
|
400
516
|
- ``StableMeasurement``: Boolean indicating if the measurement is stable (low variance). The measurement is stable if ``RelativeStdDevPct`` < 2 % .
|
|
401
517
|
- ``Metric``: The metrics being collected and the metrics being derived
|
|
518
|
+
- ``Unit``: Unit of measurement for the metric value (e.g., ``ns`` for nanoseconds)
|
|
402
519
|
- ``Kernel``: Name of the GPU kernel(s) launched
|
|
403
520
|
- ``GPU``: GPU device name
|
|
404
521
|
- ``Host``: Host machine name
|
|
@@ -487,7 +604,7 @@ class NsightProfiler:
|
|
|
487
604
|
raw_df,
|
|
488
605
|
func,
|
|
489
606
|
self.settings.normalize_against,
|
|
490
|
-
self.settings.
|
|
607
|
+
self.settings.verbosity,
|
|
491
608
|
)
|
|
492
609
|
|
|
493
610
|
# Save to CSV if enabled
|
|
@@ -508,7 +625,7 @@ class NsightProfiler:
|
|
|
508
625
|
index=False,
|
|
509
626
|
)
|
|
510
627
|
|
|
511
|
-
if self.settings.
|
|
628
|
+
if self.settings.verbosity >= VerbosityLevel.INFO:
|
|
512
629
|
print(
|
|
513
630
|
f"[NSIGHT-PYTHON] Refer to {raw_csv_path} for the raw profiling data"
|
|
514
631
|
)
|