Skip to content

Commit 52d9119

Browse files
#11 - Support multiple derived metrics and add metric parameter to plot decorator (#16)
Signed-off-by: ConvolutedDog <yangjianchao16@nudt.edu.cn>
1 parent ef715e0 commit 52d9119

15 files changed

Lines changed: 516 additions & 150 deletions

‎docs/source/overview/architecture.rst‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,36 @@ Nsight Python collects `gpu__time_duration.sum` by default. To collect other NVI
4141
...
4242
4343
**Derived Metrics**
44-
Define a Python function that computes metrics like TFLOPs based on runtime and input configuration:
44+
Define a Python function that computes custom metrics like TFLOPS based on runtime and input configuration:
4545

4646
.. code-block:: python
4747
48-
def tflops(t, m, n, k):
48+
def tflops_scalar(t, m, n, k):
4949
return 2 * m * n * k / (t / 1e9) / 1e12
5050
51-
@nsight.analyze.kernel(configs=[(1024, 1024, 64)], derive_metric=tflops)
52-
def benchmark(m, n, k):
51+
@nsight.analyze.kernel(configs=[(1024, 1024, 64)], derive_metric=tflops_scalar)
52+
def benchmark1(m, n, k):
53+
...
54+
55+
# or
56+
def tflops_dict(t, m, n, k):
57+
return {"TFLOPS": 2 * m * n * k / (t / 1e9) / 1e12}
58+
59+
@nsight.analyze.kernel(configs=[(1024, 1024, 64)], derive_metric=tflops_dict)
60+
def benchmark2(m, n, k):
61+
...
62+
63+
# or
64+
def tflops_and_arith_intensity(t, m, n, k):
65+
tflops = 2 * m * n * k / (t / 1e9) / 1e12
66+
memory_bytes = (m * k + k * n + m * n) * 4
67+
return {
68+
"TFLOPS": tflops,
69+
"ArithIntensity": tflops / memory_bytes,
70+
}
71+
72+
@nsight.analyze.kernel(configs=[(1024, 1024, 64)], derive_metric=tflops_and_arith_intensity)
73+
def benchmark3(m, n, k):
5374
...
5475
5576
**Relative Metrics**

‎examples/03_custom_metrics.py‎

Lines changed: 137 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,170 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
"""
5-
Example 3: Custom Metrics (TFLOPs)
6-
===================================
5+
Example 3: Custom Metrics (TFLOPs and Arithmetic Intensity)
6+
============================================================
77
8-
This example shows how to compute custom metrics from timing data.
8+
This example demonstrates two patterns for using `derive_metric` to compute
9+
custom performance metrics from profiling data.
910
1011
New concepts:
1112
- Using `derive_metric` to compute custom values (e.g., TFLOPs)
1213
- Customizing plot labels with `ylabel`
1314
- The `annotate_points` parameter to show values on the plot
15+
16+
Additional insights on `derive_metric` usage patterns:
17+
1. `derive_metric` can return either:
18+
- A single scalar value (e.g., TFLOPS)
19+
- A dictionary containing one or more derived metrics (e.g., {"TFLOPS": value} or
20+
{"TFLOPS": value, "ArithIntensity": value})
21+
2. When using `derive_metric`, plotting requires explicit metric specification:
22+
- Without `derive_metric`: Only one collected metric exists -> plot(metric=None) works
23+
- With `derive_metric`: Multiple metrics exist -> MUST specify which metric to plot
24+
3. How to specify which metric to plot in different scenarios:
25+
- For scalar returns: plot(metric="function_name")
26+
- For dictionary returns: plot(metric="dictionary_key")
1427
"""
1528

1629
import torch
1730

1831
import nsight
1932

33+
# Matrix sizes to benchmark: 2^11, 2^12, 2^13
2034
sizes = [(2**i,) for i in range(11, 14)]
2135

2236

37+
# ------------------------------------------------------------------------------
38+
# Pattern 1: Returning a single scalar value
39+
# ------------------------------------------------------------------------------
40+
41+
2342
def compute_tflops(time_ns: float, n: int) -> float:
2443
"""
25-
Compute TFLOPs/s for matrix multiplication.
44+
Compute TFLOPS for matrix multiplication.
2645
27-
Custom metric function signature:
28-
- First argument: the measured metric (time in nanoseconds by default)
29-
- Remaining arguments: must match the decorated function's signature
46+
This function demonstrates the first pattern: returning a single scalar value.
3047
31-
In this example:
32-
- time_ns: The measured metric (gpu__time_duration.sum in nanoseconds)
33-
- n: Matches the 'n' parameter from benchmark_tflops(n)
48+
Function signature convention for `derive_metric`:
49+
- First argument: the measured base metric (default: gpu__time_duration.sum in nanoseconds)
50+
- Remaining arguments: must match the decorated function's parameters
3451
35-
If your function was benchmark(size, dtype, batch), your metric function
36-
would be: my_metric(time_ns, size, dtype, batch)
52+
Note: When `derive_metric` returns a single value, the plot decorator's
53+
`metric` parameter must be set to the FUNCTION NAME (as a string,
54+
"compute_tflops" in this case).
3755
3856
Args:
3957
time_ns: Kernel execution time in nanoseconds (automatically passed)
4058
n: Matrix size (n x n) - matches benchmark_tflops parameter
4159
4260
Returns:
43-
TFLOPs/s (higher is better)
61+
TFLOPS (higher is better)
4462
"""
45-
# Matrix multiplication: 2*n^3 FLOPs (n^3 multiplies + n^3 adds)
63+
# Matrix multiplication FLOPs: 2 * n^3 (n^3 multiplications + n^3 additions)
4664
flops = 2 * n * n * n
47-
# Convert ns to seconds and FLOPs to TFLOPs
65+
66+
# Compute TFLOPS
4867
tflops = flops / (time_ns / 1e9) / 1e12
68+
69+
# This function can also return a directory of one metric, such as
70+
# {"TFLOPS": tflops}, but the "metric" of the plot decorator must be
71+
# set to "TFLOPS" instead of "compute_tflops".
4972
return tflops
5073

5174

5275
@nsight.analyze.plot(
53-
filename="03_custom_metrics.png",
54-
ylabel="Performance (TFLOPs/s)", # Custom y-axis label
55-
annotate_points=True, # Show values on the plot
76+
filename="03_custom_metrics_tflops.png",
77+
metric="compute_tflops", # Must match the function name of `derive_metric` when returning scalar
78+
ylabel="Performance (TFLOPS)",
79+
annotate_points=True,
5680
)
5781
@nsight.analyze.kernel(
58-
configs=sizes, runs=10, derive_metric=compute_tflops # Use custom metric
82+
configs=sizes, runs=10, derive_metric=compute_tflops # Single scalar return
5983
)
6084
def benchmark_tflops(n: int) -> None:
6185
"""
62-
Benchmark matmul and display results in TFLOPs/s.
86+
Benchmark matrix multiplication and display results in TFLOPS.
87+
88+
This example shows:
89+
- When `derive_metric` returns a single value, the plot metric parameter
90+
must be the function name ("compute_tflops")
91+
- Without `derive_metric`, there's only one collected metric (time duration),
92+
we don't need to specify a metric because plot(metric=None) works by default
93+
- With `derive_metric`, we have >1 metrics (time duration + derived), so we must
94+
explicitly specify which metric to plot
95+
"""
96+
a = torch.randn(n, n, device="cuda")
97+
b = torch.randn(n, n, device="cuda")
98+
99+
with nsight.annotate("matmul"):
100+
_ = a @ b
101+
102+
103+
# ------------------------------------------------------------------------------
104+
# Pattern 2: Returning a dictionary of metrics
105+
# ------------------------------------------------------------------------------
106+
107+
108+
def compute_tflops_and_arithmetic_intensity(time_ns: float, n: int) -> dict[str, float]:
109+
"""
110+
Compute both TFLOPS and Arithmetic Intensity for matrix multiplication.
111+
112+
This function demonstrates the second pattern: returning a dictionary
113+
containing multiple derived metrics.
114+
115+
Important: When derive_metric returns a dictionary, the plot decorator's
116+
`metric` parameter must be set to a KEY from the dictionary (as a string).
117+
118+
Note: A single scalar value could also be returned as a dictionary with
119+
one key-value pair for consistency, but returning the scalar directly is
120+
more concise.
121+
122+
Args:
123+
time_ns: Kernel execution time in nanoseconds (automatically passed)
124+
n: Matrix size (n x n) - matches benchmark_tflops parameter
125+
126+
Returns:
127+
Dictionary with two metrics (TFLOPS and ArithIntensity)
128+
"""
129+
# Matrix multiplication FLOPs: 2 * n^3
130+
flops = 2 * n * n * n
131+
132+
# Compute TFLOPS
133+
tflops = flops / (time_ns / 1e9) / 1e12
134+
135+
# Memory access calculation:
136+
# - Input matrices: n * n each, Output matrix: n * n
137+
# - Float32 datatype (4 bytes per element)
138+
memory_bytes = (n * n + n * n + n * n) * 4
139+
140+
# Arithmetic Intensity = FLOPs / Bytes accessed
141+
arithmetic_intensity = flops / memory_bytes
142+
143+
return {
144+
"TFLOPS": tflops,
145+
"ArithIntensity": arithmetic_intensity,
146+
}
147+
148+
149+
@nsight.analyze.plot(
150+
filename="03_custom_metrics_arith_intensity.png",
151+
metric="ArithIntensity", # Must be a key from the returned dictionary
152+
ylabel="Arithmetic Intensity (FLOPs/Byte)",
153+
annotate_points=True,
154+
)
155+
@nsight.analyze.kernel(
156+
configs=sizes,
157+
runs=10,
158+
derive_metric=compute_tflops_and_arithmetic_intensity, # Dictionary return
159+
)
160+
def benchmark_tflops_and_arithmetic_intensity(n: int) -> None:
161+
"""
162+
Benchmark matrix multiplication with multiple derived metrics.
163+
164+
This example shows:
165+
- When `derive_metric` returns a dictionary, the plot metric parameter
166+
must be a key from that dictionary (e.g., "ArithIntensity")
167+
- You can have multiple derived metrics but plot only one at a time
168+
- All derived metrics are available in the ProfileResults object
63169
"""
64170
a = torch.randn(n, n, device="cuda")
65171
b = torch.randn(n, n, device="cuda")
@@ -69,9 +175,18 @@ def benchmark_tflops(n: int) -> None:
69175

70176

71177
def main() -> None:
72-
result = benchmark_tflops()
178+
# Run single-metric benchmark
179+
print("Running TFLOPs benchmark (scalar return pattern)...")
180+
benchmark_tflops()
181+
print("✓ TFLOPs benchmark complete! Check '03_custom_metrics_tflops.png'\n")
182+
183+
# Run multi-metric benchmark
184+
print("Running combined benchmark (dictionary return pattern)...")
185+
result = benchmark_tflops_and_arithmetic_intensity()
73186
print(result.to_dataframe())
74-
print("✓ TFLOPs benchmark complete! Check '03_custom_metrics.png'")
187+
188+
print("\n✓ TFLOPs and Arithmetic Intensity benchmark complete! ", end="")
189+
print("Check '03_custom_metrics_arith_intensity.png'")
75190

76191

77192
if __name__ == "__main__":

‎examples/04_multi_parameter.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
configs = list(itertools.product(sizes, dtypes))
3030

3131

32-
def compute_tflops(time_ns: float, *conf: Any) -> float:
32+
def compute_tflops(time_ns: float, *conf: Any) -> dict[str, float]:
3333
"""
3434
Compute TFLOPs/s.
3535
@@ -45,11 +45,12 @@ def compute_tflops(time_ns: float, *conf: Any) -> float:
4545

4646
flops = 2 * n * n * n
4747
tflops: float = flops / (time_ns / 1e9) / 1e12
48-
return tflops
48+
return {"TFLOPS": tflops}
4949

5050

5151
@nsight.analyze.plot(
5252
filename="04_multi_parameter.png",
53+
metric="TFLOPS",
5354
ylabel="Performance (TFLOPs/s)",
5455
annotate_points=True,
5556
)

‎examples/05_subplots.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,17 @@
2828
configs = list(itertools.product(sizes, dtypes, transpose))
2929

3030

31-
def compute_tflops(time_ns: float, *conf: Any) -> float:
31+
def compute_tflops(time_ns: float, *conf: Any) -> dict[str, float]:
3232
"""Compute TFLOPs/s using *conf to extract only what we need."""
3333
n: int = conf[0] # Extract size (dtype and transpose not needed)
3434
flops = 2 * n * n * n
3535
tflops: float = flops / (time_ns / 1e9) / 1e12
36-
return tflops
36+
return {"TFLOPS": tflops}
3737

3838

3939
@nsight.analyze.plot(
4040
filename="05_subplots.png",
41+
metric="TFLOPS",
4142
title="Matrix Multiplication Performance",
4243
ylabel="TFLOPs/s",
4344
row_panels=["dtype"], # Create row for each dtype
@@ -61,7 +62,8 @@ def benchmark_with_subplots(n: int, dtype: torch.dtype, transpose: bool) -> None
6162

6263

6364
def main() -> None:
64-
benchmark_with_subplots()
65+
result = benchmark_with_subplots()
66+
print(result.to_dataframe())
6567
print("✓ Subplot benchmark complete! Check '05_subplots.png'")
6668

6769

‎examples/06_plot_customization.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
sizes = [(2**i,) for i in range(11, 14)]
2323

2424

25-
def compute_tflops(time_ns: float, n: int) -> float:
25+
def compute_tflops(time_ns: float, n: int) -> dict[str, float]:
2626
flops = 2 * n * n * n
27-
return flops / (time_ns / 1e9) / 1e12
27+
return {"TFLOPS": flops / (time_ns / 1e9) / 1e12}
2828

2929

3030
# Example 1: Bar chart
3131
@nsight.analyze.plot(
3232
filename="06_bar_chart.png",
33+
metric="TFLOPS",
3334
title="Matrix Multiplication Performance",
3435
ylabel="TFLOPs/s",
3536
plot_type="bar", # Use bar chart instead of line plot
@@ -68,6 +69,7 @@ def custom_style(fig: Any) -> None:
6869

6970
@nsight.analyze.plot(
7071
filename="06_custom_plot.png",
72+
metric="TFLOPS",
7173
plot_callback=custom_style, # Apply custom styling
7274
)
7375
@nsight.analyze.kernel(configs=sizes, runs=10, derive_metric=compute_tflops)

0 commit comments

Comments
 (0)