11import numpy as np
22
3- from kernel_tuner .observers .observer import BenchmarkObserver
3+ from kernel_tuner .observers .observer import BenchmarkObserver , ContinuousObserver
44
55# check if pmt is installed
66try :
@@ -28,9 +28,25 @@ class PMTObserver(BenchmarkObserver):
2828
2929 :type observables: string,list/dictionary
3030
31+
32+ :param use_continuous_observer:
33+ Boolean to control whether or not to measure power/energy using
34+ Kernel Tuner's continuous benchmarking mode. This improves measurement
35+ accuracy when using internal power sensors, such as NVML or ROCM,
36+ which have limited sampling frequency and might return averages
37+ instead of instantaneous power readings. Default value: False.
38+
39+ :type use_continuous_observer: boolean
40+
41+
42+ :param continuous_duration:
43+ Number of seconds to measure continuously for.
44+
45+ :type continuous_duration: scalar
46+
3147 """
3248
33- def __init__ (self , observable = None ):
49+ def __init__ (self , observable = None , use_continuous_observer = False , continuous_duration = 1 ):
3450 if not pmt :
3551 raise ImportError ("could not import pmt" )
3652
@@ -54,6 +70,9 @@ def __init__(self, observable=None):
5470 self .begin_states = [None ] * len (self .pms )
5571 self .initialize_results (self .pm_names )
5672
73+ if use_continuous_observer :
74+ self .continuous_observer = PMTContinuousObserver ("pmt" , [], self , continuous_duration = continuous_duration )
75+
5776 def initialize_results (self , pm_names ):
5877 self .results = dict ()
5978 for pm_name in pm_names :
@@ -82,3 +101,39 @@ def get_results(self):
82101 averages = {key : np .average (values ) for key , values in self .results .items ()}
83102 self .initialize_results (self .pm_names )
84103 return averages
104+
105+
106+ class PMTContinuousObserver (ContinuousObserver ):
107+ """Generic observer that measures power while and continuous benchmarking.
108+
109+ To support continuous benchmarking an Observer should support:
110+ a .read_power() method, which the ContinuousObserver can call to read power in Watt
111+ """
112+ def before_start (self ):
113+ """ Override default method in ContinuousObserver """
114+ pass
115+
116+ def after_start (self ):
117+ self .parent .after_start ()
118+
119+ def during (self ):
120+ """ Override default method in ContinuousObserver """
121+ pass
122+
123+ def after_finish (self ):
124+ self .parent .after_finish ()
125+
126+ def get_results (self ):
127+ average_kernel_execution_time_ms = self .results ["time" ]
128+
129+ averages = {key : np .average (values ) for key , values in self .results .items ()}
130+ self .parent .initialize_results (self .parent .pm_names )
131+
132+ # correct energy measurement, because current _energy number is collected over the entire duration
133+ # we estimate energy as the average power over the continuous duration times the kernel execution time
134+ for pm_name in self .parent .pm_names :
135+ energy_result_name = f"{ pm_name } _energy"
136+ power_result_name = f"{ pm_name } _power"
137+ averages [energy_result_name ] = averages [power_result_name ] * (average_kernel_execution_time_ms / 1e3 )
138+
139+ return averages
0 commit comments