benchmarking/utils/monsoon_power.py (187 lines of code) (raw):

#!/usr/bin/env python ############################################################################## # Copyright 2017-present, Facebook, Inc. # All rights reserved. # # This source code is licensed under the license found in the # LICENSE file in the root directory of this source tree. ############################################################################## from __future__ import absolute_import, division, print_function, unicode_literals import json import re import tempfile from time import sleep import Monsoon.HVPM as HVPM import Monsoon.sampleEngine as sampleEngine from utils.custom_logger import getLogger def collectPowerData(hash, sample_time, voltage, num_iters, monsoon_map=None): serialno = _getSerialno(hash, monsoon_map) if serialno is not None: getLogger().info( "Collecting current from monsoon {} for {}".format(str(serialno), hash) ) # wait till all actions are performed sleep(1) Mon = HVPM.Monsoon() Mon.setup_usb(serialno) # Need to sleep to be functional correctly sleep(0.2) getLogger().info("Setup Vout") Mon.setVout(voltage) getLogger().info("Setup setPowerupTime") Mon.setPowerupTime(60) getLogger().info("Setup setPowerUpCurrentLimit") Mon.setPowerUpCurrentLimit(14) getLogger().info("Setup setRunTimeCurrentLimit") Mon.setRunTimeCurrentLimit(14) # main channel getLogger().info("Setup setVoltageChannel") Mon.setVoltageChannel(0) engine = sampleEngine.SampleEngine(Mon) getLogger().info("Setup enableCSVOutput") # we may leak the file content f = tempfile.NamedTemporaryFile(delete=False) f.close() filename = f.name engine.enableCSVOutput(filename) getLogger().info("Setup ConsoleOutput") engine.ConsoleOutput(False) sleep(1) # 200 us per sample num_samples = sample_time / 0.0002 getLogger().info("startSampling on {}".format(filename)) engine.startSampling(num_samples) engine.disableCSVOutput() getLogger().info("Written power data to file: {}".format(filename)) # retrieve statistics from the power data getLogger().info("Reading data from CSV file") power_data = _getPowerData(filename) getLogger().info( "Calculating the benchmark data range from " "{} data points".format(len(power_data)) ) start_idx, end_idx = _calculatePowerDataRange(power_data) getLogger().info("Collecting data from " "{} to {}".format(start_idx, end_idx)) getLogger().info( "Benchmark time: " "{} - {} s".format(power_data[start_idx]["time"], power_data[end_idx]["time"]) ) data = _retrievePowerData(power_data, start_idx, end_idx, num_iters) data["power_data"] = filename return data def _getPowerData(filename): lines = [] with open(filename, "r") as f: # skip the first line since it is the title line = f.readline() while line != "": line = f.readline() # only the main output channel is enabled pattern = re.compile(r"^([\d|\.]+),([\d|\.]+),([\d|\.]+),") match = pattern.match(line) if match: new_line = { "time": float(match.group(1)), "current": float(match.group(2)), "voltage": float(match.group(3)), } lines.append(new_line) return lines # This only works in one specific scenario: # In the beginning, the current is low and below threshold # Then there is a sudden jump in current and the current keeps high # After the test, the current restores back to below threshold for some time # All other scenarios are not caught def _calculatePowerDataRange(power_data): num = len(power_data) WINDOW_SIZE = 500 THRESHOLD = 150 if num <= WINDOW_SIZE: return -1, -1 # first get the sum of the window size values sum = 0 for i in range(WINDOW_SIZE): sum += power_data[i]["current"] ranges = [] i = WINDOW_SIZE - 1 while i < num - 1: # first find the average current is less than the threshold while i < num - 1 and (sum / WINDOW_SIZE) > THRESHOLD: i = i + 1 sum = ( sum - power_data[i - WINDOW_SIZE]["current"] + power_data[i]["current"] ) # find the first item with sudden jump in current while ( i < num - 1 and ((sum / WINDOW_SIZE) <= THRESHOLD) and ( (power_data[i]["current"] < THRESHOLD) or (power_data[i]["current"] < 2 * (sum / WINDOW_SIZE)) ) ): i = i + 1 sum = ( sum - power_data[i - WINDOW_SIZE]["current"] + power_data[i]["current"] ) # find the last entry below threshold while i > 0 and power_data[i]["current"] > THRESHOLD: i = i - 1 start = i # find the last item whose current is above THRESHOLD but # all later items are below THRESHOLD sum = 0 while i < num - 1 and i < start + WINDOW_SIZE: i = i + 1 sum += power_data[i]["current"] # wait till the average of the current is below threshold while i < num - 1 and ((sum / WINDOW_SIZE) > THRESHOLD): i = i + 1 sum = ( sum - power_data[i - WINDOW_SIZE]["current"] + power_data[i]["current"] ) # get the last entry below threshold end = i while end > i - WINDOW_SIZE and (power_data[end - 1]["current"] < THRESHOLD): end = end - 1 if start < num and end < num: ranges.append({"start": start, "end": end}) if len(ranges) == 0: return -1, -1 # get the max range of all collected ranges max_range = ranges[0] for r in ranges: if r["end"] - r["start"] > max_range["end"] - max_range["start"]: max_range = r return max_range["start"], max_range["end"] def _retrievePowerData(power_data, start_idx, end_idx, num_iters): data = {} if start_idx < 0 or end_idx < 0: return data # get base current. It is just an approximation THRESHOLD = 150 num = len(power_data) i = end_idx sum = 0 count = 0 for i in range(end_idx, num): if power_data[i]["current"] < THRESHOLD: sum += power_data[i]["current"] count += 1 base_current = sum / count if count > 0 else 0 energy = 0 prev_time = power_data[start_idx - 1]["time"] for i in range(start_idx, end_idx): entry = power_data[i] curr_time = entry["time"] energy += ( entry["voltage"] * (entry["current"] - base_current) * (curr_time - prev_time) ) prev_time = curr_time total_time = power_data[end_idx]["time"] - power_data[start_idx]["time"] power = energy / total_time energy_per_inference = energy / num_iters latency = total_time * 1000 * 1000 / num_iters data["energy"] = _composeStructuredData(energy_per_inference, "energy", "mJ") data["power"] = _composeStructuredData(power, "power", "mW") data["latency"] = _composeStructuredData(latency, "latency", "uS") getLogger().info("Base current: {} mA".format(base_current)) getLogger().info("Energy per inference: {} mJ".format(energy_per_inference)) getLogger().info("Power: {} mW".format(power)) getLogger().info("Latency per inference: {} uS".format(latency)) return data def _composeStructuredData(data, metric, unit): return { "values": [data], "type": "NET", "metric": metric, "unit": unit, "summary": { "p0": data, "p10": data, "p50": data, "p90": data, "p100": data, "mean": data, "stdev": 0, "MAD": 0, }, } def _getSerialno(hash, monsoon_map=None): serialno = None if monsoon_map: map = json.loads(monsoon_map) if hash in map: serialno = map[hash] return serialno