Skip to content

Commit f090f8d

Browse files
reduced code duplication between tegra and nvml continuous observers
1 parent 7faff0c commit f090f8d

File tree

3 files changed

+88
-150
lines changed

3 files changed

+88
-150
lines changed

kernel_tuner/observers/nvml.py

Lines changed: 4 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,7 @@ def __init__(
384384
if any([obs in self.needs_power for obs in observables]):
385385
self.measure_power = True
386386
power_observables = [obs for obs in observables if obs in self.needs_power]
387-
self.continuous_observer = NVMLPowerObserver(
388-
power_observables, self, self.nvml, continous_duration
389-
)
387+
self.continuous_observer = ContinuousObserver("nvml", self, power_observables, continous_duration=continuous_duration)
390388

391389
# remove power observables
392390
self.observables = [obs for obs in observables if obs not in self.needs_power]
@@ -408,6 +406,9 @@ def __init__(
408406
]
409407
self.iteration = {obs: [] for obs in self.during_obs}
410408

409+
def read_power(self):
410+
return self.nvml.pwr_usage()
411+
411412
def before_start(self):
412413
# clear results of the observables for next measurement
413414
self.iteration = {obs: [] for obs in self.during_obs}
@@ -471,76 +472,6 @@ def get_results(self):
471472
return averaged_results
472473

473474

474-
class NVMLPowerObserver(ContinuousObserver):
475-
"""Observer that measures power using NVML and continuous benchmarking."""
476-
477-
def __init__(self, observables, parent, nvml_instance, continous_duration=1):
478-
self.parent = parent
479-
self.nvml = nvml_instance
480-
481-
supported = ["power_readings", "nvml_power", "nvml_energy"]
482-
for obs in observables:
483-
if obs not in supported:
484-
raise ValueError(f"Observable {obs} not in supported: {supported}")
485-
self.observables = observables
486-
487-
# duration in seconds
488-
self.continuous_duration = continous_duration
489-
490-
self.power = 0
491-
self.energy = 0
492-
self.power_readings = []
493-
self.t0 = 0
494-
495-
# results from the last iteration-based benchmark
496-
self.results = None
497-
498-
def before_start(self):
499-
self.parent.before_start()
500-
self.power = 0
501-
self.energy = 0
502-
self.power_readings = []
503-
504-
def after_start(self):
505-
self.parent.after_start()
506-
self.t0 = time.perf_counter()
507-
508-
def during(self):
509-
self.parent.during()
510-
power_usage = self.nvml.pwr_usage()
511-
timestamp = time.perf_counter() - self.t0
512-
# only store the result if we get a new measurement from NVML
513-
if len(self.power_readings) == 0 or (
514-
self.power_readings[-1][1] != power_usage
515-
or timestamp - self.power_readings[-1][0] > 0.01
516-
):
517-
self.power_readings.append([timestamp, power_usage])
518-
519-
def after_finish(self):
520-
self.parent.after_finish()
521-
# safeguard in case we have no measurements, perhaps the kernel was too short to measure anything
522-
if not self.power_readings:
523-
return
524-
525-
# convert to seconds from milliseconds
526-
execution_time = self.results["time"] / 1e3
527-
self.power = np.median([d[1] / 1e3 for d in self.power_readings])
528-
self.energy = self.power * execution_time
529-
530-
def get_results(self):
531-
results = self.parent.get_results()
532-
keys = list(results.keys())
533-
for key in keys:
534-
results["pwr_" + key] = results.pop(key)
535-
if "nvml_energy" in self.observables:
536-
results["nvml_energy"] = self.energy
537-
if "nvml_power" in self.observables:
538-
results["nvml_power"] = self.power
539-
if "power_readings" in self.observables:
540-
results["power_readings"] = self.power_readings
541-
return results
542-
543-
544475
# High-level Helper functions
545476

546477

kernel_tuner/observers/observer.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,78 @@ class IterationObserver(BenchmarkObserver):
4444

4545

4646
class ContinuousObserver(BenchmarkObserver):
47-
pass
47+
"""Generic observer that measures power while and continuous benchmarking.
48+
49+
To support continuous benchmarking an Observer should support:
50+
a .read_power() method, which the ContinuousObserver can call to read power
51+
"""
52+
def __init__(self, name, observables, parent, continous_duration=1):
53+
self.parent = parent
54+
self.name = name
55+
56+
supported = [self.name + "_power", self.name + "_energy", "power_readings"]
57+
for obs in observables:
58+
if obs not in supported:
59+
raise ValueError(f"Observable {obs} not in supported: {supported}")
60+
self.observables = observables
61+
62+
# duration in seconds
63+
self.continuous_duration = continous_duration
64+
65+
self.power = 0
66+
self.energy = 0
67+
self.power_readings = []
68+
self.t0 = 0
69+
70+
# results from the last iteration-based benchmark
71+
# these are set by the benchmarking function of Kernel Tuner before
72+
# the continuous observer is called.
73+
self.results = None
74+
75+
def before_start(self):
76+
self.parent.before_start()
77+
self.power = 0
78+
self.energy = 0
79+
self.power_readings = []
4880

81+
def after_start(self):
82+
self.parent.after_start()
83+
self.t0 = time.perf_counter()
84+
85+
def during(self):
86+
self.parent.during()
87+
power_usage = self.parent.read_power()
88+
timestamp = time.perf_counter() - self.t0
89+
# only store the result if we get a new measurement from the GPU
90+
if len(self.power_readings) == 0 or (
91+
self.power_readings[-1][1] != power_usage
92+
or timestamp - self.power_readings[-1][0] > 0.01
93+
):
94+
self.power_readings.append([timestamp, power_usage])
95+
96+
def after_finish(self):
97+
self.parent.after_finish()
98+
# safeguard in case we have no measurements, perhaps the kernel was too short to measure anything
99+
if not self.power_readings:
100+
return
101+
102+
# convert to seconds from milliseconds
103+
execution_time = self.results["time"] / 1e3
104+
self.power = np.median([d[1] for d in self.power_readings])
105+
self.energy = self.power * execution_time
106+
107+
def get_results(self):
108+
results = self.parent.get_results()
109+
keys = list(results.keys())
110+
for key in keys:
111+
results["pwr_" + key] = results.pop(key)
112+
if self.name + "_power" in self.observables:
113+
results[self.name + "_power"] = self.power
114+
if self.name + "_energy" in self.observables:
115+
results[self.name + "_energy"] = self.energy
116+
if "power_readings" in self.observables:
117+
results["power_readings"] = self.power_readings
118+
return results
49119

50120
class OutputObserver(BenchmarkObserver):
51121
"""Observer that can verify or measure something about the output produced by a kernel."""

kernel_tuner/observers/tegra.py

Lines changed: 13 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,8 @@ def read_gpu_power(self):
186186
voltage = int(result_vol.stdout.strip()) / 1000
187187

188188
return current * voltage
189-
189+
190+
190191
class TegraObserver(BenchmarkObserver):
191192
"""Observer that uses /sys/ to monitor and control graphics clock frequencies on a Tegra device.
192193
@@ -211,21 +212,20 @@ def __init__(
211212
self.save_all = save_all
212213
self._set_units = False
213214

214-
supported = ["core_freq", "gpu_temp", "gpu_power", "gpu_energy"]
215+
supported = ["core_freq", "tegra_temp", "tegra_power", "tegra_energy"]
215216
for obs in observables:
216217
if obs not in supported:
217218
raise ValueError(f"Observable {obs} not in supported: {supported}")
218219
self.observables = observables
219220

220221
# Observe power measurements with the continuous observer
221222
self.measure_power = False
222-
self.needs_power = ["gpu_power", "gpu_energy"]
223+
self.needs_power = ["tegra_power", "tegra_energy"]
223224
if any([obs in self.needs_power for obs in observables]):
224225
self.measure_power = True
225226
power_observables = [obs for obs in observables if obs in self.needs_power]
226-
self.continuous_observer = tegraPowerObserver(
227-
power_observables, self, continous_duration=3
228-
)
227+
self.continuous_observer = ContinuousObserver("tegra", power_observables, self, continous_duration=3)
228+
229229
# remove power observables
230230
self.observables = [obs for obs in observables if obs not in self.needs_power]
231231

@@ -236,11 +236,15 @@ def __init__(
236236
self.during_obs = [
237237
obs
238238
for obs in observables
239-
if obs in ["core_freq", "gpu_temp"]
239+
if obs in ["core_freq", "tegra_temp"]
240240
]
241241

242242
self.iteration = {obs: [] for obs in self.during_obs}
243-
243+
244+
245+
def read_power(self):
246+
return self.tegra.read_gpu_power()
247+
244248

245249
def before_start(self):
246250
# clear results of the observables for next measurement
@@ -266,7 +270,7 @@ def after_finish(self):
266270
self.results["core_freqs"].append(np.average(self.iteration["core_freq"]))
267271
if "gpu_temp" in self.observables:
268272
self.results["gpu_temps"].append(np.average(self.iteration["gpu_temp"]))
269-
273+
270274
def get_results(self):
271275
averaged_results = {}
272276

@@ -303,70 +307,3 @@ def get_tegra_gr_clocks(n=None, quiet=False):
303307
if not quiet:
304308
print("Using gr frequencies:", tune_params["tegra_gr_clock"])
305309
return tune_params
306-
307-
308-
class tegraPowerObserver(ContinuousObserver):
309-
"""Observer that measures power using tegra and continuous benchmarking."""
310-
def __init__(self, observables, parent, continous_duration=1):
311-
self.parent = parent
312-
313-
supported = ["gpu_power", "gpu_energy"]
314-
for obs in observables:
315-
if obs not in supported:
316-
raise ValueError(f"Observable {obs} not in supported: {supported}")
317-
self.observables = observables
318-
319-
# duration in seconds
320-
self.continuous_duration = continous_duration
321-
322-
self.power = 0
323-
self.energy = 0
324-
self.power_readings = []
325-
self.t0 = 0
326-
327-
# results from the last iteration-based benchmark
328-
self.results = None
329-
330-
def before_start(self):
331-
self.parent.before_start()
332-
self.power = 0
333-
self.energy = 0
334-
self.power_readings = []
335-
336-
def after_start(self):
337-
self.parent.after_start()
338-
self.t0 = time.perf_counter()
339-
340-
def during(self):
341-
self.parent.during()
342-
power_usage = self.parent.tegra.read_gpu_power()
343-
timestamp = time.perf_counter() - self.t0
344-
# only store the result if we get a new measurement from tegra
345-
if len(self.power_readings) == 0 or (
346-
self.power_readings[-1][1] != power_usage
347-
or timestamp - self.power_readings[-1][0] > 0.01
348-
):
349-
self.power_readings.append([timestamp, power_usage])
350-
351-
def after_finish(self):
352-
self.parent.after_finish()
353-
# safeguard in case we have no measurements, perhaps the kernel was too short to measure anything
354-
if not self.power_readings:
355-
return
356-
357-
# convert to seconds from milliseconds
358-
execution_time = self.results["time"] / 1e3
359-
self.power = np.median([d[1] for d in self.power_readings])
360-
self.energy = self.power * execution_time
361-
362-
def get_results(self):
363-
results = self.parent.get_results()
364-
keys = list(results.keys())
365-
for key in keys:
366-
results["pwr_" + key] = results.pop(key)
367-
if "gpu_power" in self.observables:
368-
results["gpu_power"] = self.power
369-
if "gpu_energy" in self.observables:
370-
results["gpu_energy"] = self.energy
371-
372-
return results

0 commit comments

Comments
 (0)