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
1011New 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
1629import torch
1730
1831import nsight
1932
33+ # Matrix sizes to benchmark: 2^11, 2^12, 2^13
2034sizes = [(2 ** i ,) for i in range (11 , 14 )]
2135
2236
37+ # ------------------------------------------------------------------------------
38+ # Pattern 1: Returning a single scalar value
39+ # ------------------------------------------------------------------------------
40+
41+
2342def 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)
6084def 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
71177def 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
77192if __name__ == "__main__" :
0 commit comments