diff --git a/tools/SeeDot/.vscode/launch.json b/tools/SeeDot/.vscode/launch.json index 3cde2f9f7..c64bbf6b3 100644 --- a/tools/SeeDot/.vscode/launch.json +++ b/tools/SeeDot/.vscode/launch.json @@ -24,7 +24,7 @@ "-t", "x86", "-n", - "1" + "1", ] } ] diff --git a/tools/SeeDot/README.md b/tools/SeeDot/README.md index 97a14e2bd..23b6d9a5b 100644 --- a/tools/SeeDot/README.md +++ b/tools/SeeDot/README.md @@ -26,7 +26,7 @@ This document describes the tool usage with an example. SeeDot can be invoked using **`SeeDot-dev.py`** file. The arguments for the script are supplied as follows: ``` -usage: SeeDot-dev.py [-h] [-a] [-v] [-d] [-m] [-n] [-dt] [-t] [-s] [-sf] +usage: SeeDot-dev.py [-h] [-a] [-v] [-d] [-m] [-n] [-dt] [-t] [-s] [-sf] [-l] [--load-sf] [--convert] [--tempdir] [-o] optional arguments: @@ -40,11 +40,12 @@ optional arguments: procedure (1 for a single-class classification problem) -t , --target Target device ['x86', 'arduino', 'm3'] -s , --source model source type ['seedot', 'onnx', 'tf'] + -l , --log Logging level (in increasing order) ['error', 'critical', 'warning', 'info', 'debug'] ``` An example invocation is as follows: ``` -python SeeDot-dev.py -a rnn -v fixed -d usps10 -n 1 -t arduino -m red_disagree +python SeeDot-dev.py -a rnn -v fixed -d usps10 -n 1 -t arduino -m red_disagree -l info ``` SeeDot expects the `train` and the `test` data files in a specific format. Each data file should be of the shape `[numberOfDataPoints, numberOfFeatures + n]`, where the ground truth/output is in the first `n` columns. The tool currently supports numpy arrays (.npy) for inputting model parameters. diff --git a/tools/SeeDot/SeeDot-dev.py b/tools/SeeDot/SeeDot-dev.py index 99eed9a48..cf30ab0b3 100644 --- a/tools/SeeDot/SeeDot-dev.py +++ b/tools/SeeDot/SeeDot-dev.py @@ -15,7 +15,7 @@ import seedot.main as main import seedot.predictor as predictor import seedot.util as util - +import logging import seedot.compiler.converter.converter as converter # This is the file which is invoked to run the compiler (Refer to README.md). @@ -82,6 +82,8 @@ def parseArgs(self): help="Scratch directory for intermediate files") parser.add_argument("-o", "--outdir", metavar='', help="Directory to output the generated Arduino sketch") + parser.add_argument("-l", "--log", choices=config.Log.all, + default=config.Log.default, metavar='', help="Log Level to use") self.args = parser.parse_args() @@ -135,7 +137,12 @@ def checkMSBuildPath(self): def setGlobalFlags(self): np.seterr(all='warn') + def setLogLevel(self): + logging.basicConfig(level=os.environ.get("LOGLEVEL", self.args.log.upper())) + def run(self): + self.setLogLevel() + if util.windows(): self.checkMSBuildPath() diff --git a/tools/SeeDot/requirements.txt b/tools/SeeDot/requirements.txt new file mode 100644 index 000000000..615ba7ca9 --- /dev/null +++ b/tools/SeeDot/requirements.txt @@ -0,0 +1,21 @@ +antlr4-python3-runtime==4.7 +bokeh==2.2.3 +certifi==2020.12.5 +Jinja2==2.11.2 +joblib==1.0.0 +MarkupSafe==1.1.1 +numpy==1.19.5 +onnx==1.8.0 +packaging==20.8 +Pillow==8.1.0 +protobuf==3.14.0 +pyparsing==2.4.7 +python-dateutil==2.8.1 +PyYAML==5.3.1 +scikit-learn==0.24.0 +scipy==1.6.0 +six==1.15.0 +threadpoolctl==2.1.0 +tornado==6.1 +tqdm==4.56.0 +typing-extensions==3.7.4.3 diff --git a/tools/SeeDot/seedot/compiler/codegen/codegenBase.py b/tools/SeeDot/seedot/compiler/codegen/codegenBase.py index 89ee3ca48..4b408df72 100644 --- a/tools/SeeDot/seedot/compiler/codegen/codegenBase.py +++ b/tools/SeeDot/seedot/compiler/codegen/codegenBase.py @@ -864,7 +864,7 @@ def getBestAlignment(mA, align, maxMem, memUsage, f): p.terminate() p.join() optimalInputGenSuccess = False - print("Timeout while generating DLX input files for optimal memory usage, attempting to fit variables within %d bytes" % maxAllowedMemUsage) + Util.getLogger().error("Timeout while generating DLX input files for optimal memory usage, attempting to fit variables within %d bytes. Returning to exploration..." % maxAllowedMemUsage) alignment = getBestAlignment(memAlloc, alignment, 0, maxAllowedMemUsage, operator.le) p = mp.Process(target=DLXInputGen.generateDLXInput, args=(memAlloc, alignment, maxAllowedMemUsage, False, dlxInputDumpDirectory)) p.start() @@ -881,10 +881,10 @@ def getBestAlignment(mA, align, maxMem, memUsage, f): try: process = subprocess.call([exeFile], stdin=fin, stdout=fout, stderr=ferr, timeout=timeout) except subprocess.TimeoutExpired: - print("Memory Allocator Program Timed out.") + Util.getLogger().error("DLX program for memory management timed out. Retrying with maximum allowed memory...") if not self.checkDlxSuccess(dlxErrorDumpDirectory): if not optimalInputGenSuccess: - assert False, "Unable to allocate variables within %d bytes. ABORT" % maxAllowedMemUsage + assert False, "DLX unable to allocate variables within %d bytes. ABORT" % maxAllowedMemUsage else: alignment = getBestAlignment(memAlloc, alignment, 0, maxAllowedMemUsage, operator.le) p = mp.Process(target=DLXInputGen.generateDLXInput, args=(memAlloc, alignment, maxAllowedMemUsage, False, dlxInputDumpDirectory)) @@ -898,9 +898,9 @@ def getBestAlignment(mA, align, maxMem, memUsage, f): try: process = subprocess.call([exeFile], stdin=fin, stdout=fout, stderr=ferr, timeout=timeout) except subprocess.TimeoutExpired: - print("Memory Allocator Program Timed out.") + Util.getLogger().error("DLX program for memory management timed out.") if not self.checkDlxSuccess(dlxErrorDumpDirectory): - assert False, "Unable to allovate variables within %d bytes. ABORT" % maxAllowedMemUsage + assert False, "DLX unable to allovate variables within %d bytes. ABORT" % maxAllowedMemUsage totalScratchSize = self.readDlxAllocation(dlxOutputDumpDirectory, alignment, varOrderAndSize) if not forM3(): self.out.printf("char scratch[%d];\n"%(totalScratchSize), indent=True) diff --git a/tools/SeeDot/seedot/compiler/codegen/x86.py b/tools/SeeDot/seedot/compiler/codegen/x86.py index 37773950c..f051e3aa5 100644 --- a/tools/SeeDot/seedot/compiler/codegen/x86.py +++ b/tools/SeeDot/seedot/compiler/codegen/x86.py @@ -26,16 +26,16 @@ def __init__(self, outputDir, generateAllFiles, printSwitch, idStr, paramInNativ if generateAllFiles: self.out = Writer(cppFile) else: - print("Opening file to output cpp code: ID" + idStr) + getLogger().info("Opening file to output cpp code: ID" + idStr) for i in range(3): - print("Try %d" % (i+1)) + getLogger().debug("Try %d" % (i+1)) try: self.out = Writer(cppFile, "a") except: - print("OS prevented file from opening. Sleeping for %d seconds" % (i+1)) + getLogger().exception("OS prevented file from opening. Sleeping for %d seconds" % (i+1)) time.sleep(i+1) else: - print("Opened") + getLogger().debug("Opened") break self.generateAllFiles = generateAllFiles @@ -323,7 +323,7 @@ def isInt(a): self.out.decreaseIndent() self.out.printf('}\n', indent=True) - print("Closing File after outputting cpp code: ID " + self.idStr) + getLogger().debug("Closing file after outputting cpp code: ID " + self.idStr) self.out.close() def printFor(self, ir): diff --git a/tools/SeeDot/seedot/compiler/compiler.py b/tools/SeeDot/seedot/compiler/compiler.py index 84ffd80af..ed7a63f64 100644 --- a/tools/SeeDot/seedot/compiler/compiler.py +++ b/tools/SeeDot/seedot/compiler/compiler.py @@ -33,6 +33,7 @@ import numpy as np + # The Compiler class reads in the input code, converts it first into an AST, and subsequently into an IR which # contains a sequence of function calls (which are implemented by hand in a library). The IR is fed into the # desired target codegen, which outputs the C/C++ code which can be run on the target device. @@ -139,7 +140,7 @@ def genCodeWithFuncCalls(self, ast): compiler = irBuilder.IRBuilder(outputLog, self.intermediateScales, self.substitutions, self.scaleForX, self.variableToBitwidthMap, self.sparseMatrixSizes, self.demotedVarsList, self.demotedVarsOffsets) res = compiler.visit(ast) - print(compiler.varScales) + util.getLogger().debug(compiler.varScales) self.biasShifts = compiler.biasShifts self.varScales = dict(compiler.varScales) diff --git a/tools/SeeDot/seedot/compiler/ir/ir.py b/tools/SeeDot/seedot/compiler/ir/ir.py index d6a7f1417..3d0b76d27 100644 --- a/tools/SeeDot/seedot/compiler/ir/ir.py +++ b/tools/SeeDot/seedot/compiler/ir/ir.py @@ -362,9 +362,9 @@ def getInt(x: int): if x_np != x: x_np = DataType.intType[target][wordLen * 2](x) if x_np != x: - print('Warning: Integer overflow for %d' % (x)) + getLogger().debug('Warning: Integer overflow for %d' % (x)) else: - print('Integer overflow for %d handled' % (x)) + getLogger().debug('Integer overflow for %d handled' % (x)) return x_np @staticmethod diff --git a/tools/SeeDot/seedot/compiler/ir/irBuilder.py b/tools/SeeDot/seedot/compiler/ir/irBuilder.py index 95b9f6b38..3a36e36d2 100644 --- a/tools/SeeDot/seedot/compiler/ir/irBuilder.py +++ b/tools/SeeDot/seedot/compiler/ir/irBuilder.py @@ -68,7 +68,7 @@ def __init__(self, outputLog, ddsScaleInfo = {}, substitutions = {}, scaleForX = # Large value which makes MAX_SCALE ineffective. elif getMaxScale() == None: if forFloat(): - print( + getLogger().info( "Setting MAX_SCALE = 0. This value will not affect the generated code.") self.MAX_SCALE = 0 else: @@ -2818,7 +2818,7 @@ def visitTanh(self, node: AST.Func): intv_out = self.updateTanhIntv(intv_in, tanh_intv) scale_new = self.getScale(config.tanhLimit) - print("Scale changes in TanH operation: old = %d, new = %d, diff = %d" % ( + getLogger().debug("Scale changes in TanH operation: old = %d, new = %d, diff = %d" % ( scale_in, scale_new, abs(scale_in - scale_new))) expr_in.inputVar = False @@ -3605,7 +3605,7 @@ def visitLet(self, node: AST.Let): # TODO: When is this triggered and why is this required? if forFixed() and idf in self.mutableVars: - print("TODO: Fix this if condition") + getLogger().warning("TODO: Fix this if condition") idfs = idf while idfs in self.substitutions.keys(): idfs = self.substitutions[idfs] @@ -3965,7 +3965,7 @@ def getShrTreeSumAndDemoteParamsForMul(self, # Last stage, adjusting scale to avoid invalid values. demote = totalShr - shr_A - shr_B - H1 if height < H1: - print("Rolling back H1 in matrix multiplication. Current H1: %d height: %d" % (H1, height)) + getLogger().info("Rolling back H1 in matrix multiplication. Current H1: %d height: %d" % (H1, height)) if height < H1: diff = H1 - height H1 = height @@ -3976,7 +3976,7 @@ def getShrTreeSumAndDemoteParamsForMul(self, shr_B += diff // 2 shr_A += diff - diff // 2 if demote < 0: - print("Rolling back shr in matrix multiplication. Current demote: %d" % (demote)) + getLogger().info("Rolling back shr in matrix multiplication. Current demote: %d" % (demote)) if demote < 0: if demote + H1 >= 0: H1 += demote diff --git a/tools/SeeDot/seedot/config.py b/tools/SeeDot/seedot/config.py index 7f00a6f62..bd8a57a2d 100644 --- a/tools/SeeDot/seedot/config.py +++ b/tools/SeeDot/seedot/config.py @@ -110,3 +110,13 @@ class Source: onnx = "onnx" default = seedot all = [seedot, tf, onnx] + + +class Log: + error = "error" + critical = "critical" + warning = "warning" + info = "info" + debug = "debug" + default = error + all = [error, critical, warning, info, debug] diff --git a/tools/SeeDot/seedot/main.py b/tools/SeeDot/seedot/main.py index ee523582f..d73a7ec83 100644 --- a/tools/SeeDot/seedot/main.py +++ b/tools/SeeDot/seedot/main.py @@ -10,6 +10,7 @@ import operator import tempfile import traceback +from tqdm import tqdm import numpy as np from seedot.compiler.converter.converter import Converter @@ -140,7 +141,7 @@ def get_input_file(self): # paramInNativeBitwidth: if False, it means model parameters are stored as 8-bit/16-bit integers mixed. # If True, it means model parameters are stored as 16-bit integers only (16 is native bit-width). def compile(self, version, target, sf, generateAllFiles=True, id=None, printSwitch=-1, scaleForX=None, variableToBitwidthMap=None, demotedVarsList=[], demotedVarsOffsets={}, paramInNativeBitwidth=True): - print("Generating code...", end='') + Util.getLogger().debug("Generating code...") if variableToBitwidthMap is None: variableToBitwidthMap = dict(self.variableToBitwidthMap) @@ -193,7 +194,7 @@ def compile(self, version, target, sf, generateAllFiles=True, id=None, printSwit self.scalesForX[id] = obj.scaleForX self.scalesForY[id] = obj.scaleForY - print("completed") + Util.getLogger().debug("completed") return True # Runs the converter project to generate the input files using reading the training model. @@ -205,8 +206,8 @@ def compile(self, version, target, sf, generateAllFiles=True, id=None, printSwit # default bitwidth 16 used for all variables. # demotedVarsOffsets: Keys are list of variables which use 8 bits. def convert(self, version, datasetType, target, varsForBitwidth={}, demotedVarsOffsets={}): - print("Generating input files for %s %s dataset..." % - (version, datasetType), end='') + Util.getLogger().debug("Generating input files for %s %s dataset..." % + (version, datasetType)) # Create output dirs. if target == config.Target.arduino: @@ -241,7 +242,7 @@ def convert(self, version, datasetType, target, varsForBitwidth={}, demotedVarsO traceback.print_exc() return False - print("done\n") + Util.getLogger().debug("done") return True # Build and run the Predictor project. @@ -274,7 +275,7 @@ def partialCompile(self, version, target, scale, generateAllFiles, id, printSwit # Runs the C++ file which contains multiple inference codes. Reads the output of all inference codes, # arranges them and returns a map of inference code descriptor to performance. - def runAll(self, version, datasetType, codeIdToScaleFactorMap, demotedVarsToOffsetToCodeId=None, doNotSort=False): + def runAll(self, version, datasetType, codeIdToScaleFactorMap, demotedVarsToOffsetToCodeId=None, doNotSort=False, printAlso=False): execMap = self.predict(version, datasetType) if execMap == None: return False, True @@ -283,9 +284,8 @@ def runAll(self, version, datasetType, codeIdToScaleFactorMap, demotedVarsToOffs if self.algo == config.Algo.test: for codeId, sf in codeIdToScaleFactorMap.items(): self.accuracy[sf] = execMap[str(codeId)] - print("The 95th percentile error for sf" + str(sf) + "with respect to dataset is " + str(execMap[str(codeId)][0]) + "%.") - print("The 95th percentile error for sf" + str(sf) + "with respect to float execution is " + str(execMap[str(codeId)][1]) + "%.") - print("\n") + Util.getLogger().debug("The 95th percentile error for sf" + str(sf) + "with respect to dataset is " + str(execMap[str(codeId)][0]) + "%.") + Util.getLogger().debug("The 95th percentile error for sf" + str(sf) + "with respect to float execution is " + str(execMap[str(codeId)][1]) + "%.\n") return True, False # During the third exploration phase, when multiple codes are generated at once, codeIdToScaleFactorMap @@ -294,7 +294,10 @@ def runAll(self, version, datasetType, codeIdToScaleFactorMap, demotedVarsToOffs if codeIdToScaleFactorMap is not None: for codeId, sf in codeIdToScaleFactorMap.items(): self.accuracy[sf] = execMap[str(codeId)] - print("Accuracy at scale factor %d is %.3f%%, Disagreement Count is %d, Reduced Disagreement Count is %d\n" % (sf, execMap[str(codeId)][0], execMap[str(codeId)][1], execMap[str(codeId)][2])) + if printAlso: + print("Accuracy at scale factor %d is %.3f%%, Disagreement Count is %d, Reduced Disagreement Count is %d" % (sf, execMap[str(codeId)][0], execMap[str(codeId)][1], execMap[str(codeId)][2])) + else: + Util.getLogger().info("Accuracy at scale factor %d is %.3f%%, Disagreement Count is %d, Reduced Disagreement Count is %d\n" % (sf, execMap[str(codeId)][0], execMap[str(codeId)][1], execMap[str(codeId)][2])) if datasetType == config.DatasetType.testing and self.target == config.Target.arduino: outdir = os.path.join(config.outdir, str(config.wordLength), self.algo, self.dataset) os.makedirs(outdir, exist_ok=True) @@ -318,7 +321,7 @@ def getMaximisingMetricValue(a): allVars = [] for demotedVars in demotedVarsToOffsetToCodeId: offsetToCodeId = demotedVarsToOffsetToCodeId[demotedVars] - print("Demoted vars: %s\n" % str(demotedVars)) + Util.getLogger().debug("Demoted vars: %s\n" % str(demotedVars)) x = [(i, execMap[str(offsetToCodeId[i])]) for i in offsetToCodeId] x.sort(key=getMaximisingMetricValue, reverse=True) @@ -326,7 +329,7 @@ def getMaximisingMetricValue(a): for offset in offsetToCodeId: codeId = offsetToCodeId[offset] - print("Offset %d (Code ID %d): Accuracy %.3f%%, Disagreement Count %d, Reduced Disagreement Count %d\n" %(offset, codeId, execMap[str(codeId)][0], execMap[str(codeId)][1], execMap[str(codeId)][2])) + Util.getLogger().debug("Offset %d (Code ID %d): Accuracy %.3f%%, Disagreement Count %d, Reduced Disagreement Count %d\n" %(offset, codeId, execMap[str(codeId)][0], execMap[str(codeId)][1], execMap[str(codeId)][2])) self.varDemoteDetails += allVars # For the sec if not doNotSort: @@ -351,14 +354,17 @@ def performSearch(self): fixedPointCounter = 0 while True: # STAGE I exploration. + print("Stage I Exploration: Determining scale for input \'X\'...") fixedPointCounter += 1 if config.fixedPointVbwIteration: - print("Will compile until conversion to fixed point. Iteration %d"%fixedPointCounter) + Util.getLogger().debug("Will compile until conversion to fixed point. Iteration %d"%fixedPointCounter) highestValidScale = start firstCompileSuccess = False + # Bar longer than actually required + stage_1_bar = tqdm(total=(2*abs(start-end)+2),mininterval=0, miniters=1, leave=True) while firstCompileSuccess == False: if highestValidScale == end: - print("Compilation not possible for any Scale Factor. Abort") + Util.getLogger().error("Compilation not possible for any scale factor of variable \'X\'. Aborting code!") return False # Refactor and remove this try/catch block in the future. @@ -370,7 +376,8 @@ def performSearch(self): if firstCompileSuccess: break highestValidScale -= 1 - + stage_1_bar.update(1) + lowestValidScale = end + 1 firstCompileSuccess = False while firstCompileSuccess == False: @@ -381,10 +388,13 @@ def performSearch(self): if firstCompileSuccess: break lowestValidScale += 1 + stage_1_bar.update(1) + stage_1_bar.close() # Ignored. self.partialCompile(config.Version.fixed, config.Target.x86, lowestValidScale, True, None, -1, dict(self.variableToBitwidthMap), list(self.demotedVarsList), dict(self.demotedVarsOffsets)) + print("Stage II Exploration: Determining scale for all non-\'X\' variables...") # The iterator logic is as follows: # Search begins when the first valid scaling factor is found (runOnce returns True). # Search ends when the execution fails on a particular scaling factor (runOnce returns False). @@ -393,11 +403,11 @@ def performSearch(self): numCodes = highestValidScale - lowestValidScale + 1 codeId = 0 codeIdToScaleFactorMap = {} - for i in range(highestValidScale, lowestValidScale - 1, -1): + for i in tqdm(range(highestValidScale, lowestValidScale - 1, -1)): if config.ddsEnabled: - print("Testing with DDS and scale of X as " + str(i)) + Util.getLogger().debug("Testing with DDS and scale of X as " + str(i) + "\n") else: - print("Testing with max scale factor of " + str(i)) + Util.getLogger().debug("Testing with max scale factor of " + str(i) + "\n") codeId += 1 try: @@ -410,28 +420,31 @@ def performSearch(self): return False codeIdToScaleFactorMap[codeId] = i + print("Stage II Code Run Started...") res, exit = self.runAll(config.Version.fixed, config.DatasetType.training, codeIdToScaleFactorMap) - + print("Stage II Code Run Completed!\n") if exit == True or res == False: return False - print("\nSearch completed\n") - print("----------------------------------------------") - print("Best performing scaling factors with accuracy, disagreement, reduced disagreement:") + Util.getLogger().info("\nSearch completed\n") + Util.getLogger().info("----------------------------------------------\n\n") + Util.getLogger().info("Best performing scaling factors with accuracy, disagreement, reduced disagreement:") self.sf = self.getBestScale() if self.accuracy[self.sf][0] != lastStageAcc: lastStageAcc = self.accuracy[self.sf][0] elif config.fixedPointVbwIteration: - print("No difference in iteration %d Stage 2 and iteration %d Stage 1. Stopping search"%(fixedPointCounter-1, fixedPointCounter)) + Util.getLogger().info("No difference in iteration %d Stage 2 and iteration %d Stage 1. Stopping search\n"%(fixedPointCounter-1, fixedPointCounter)) break if config.vbwEnabled: # Stage III exploration. + print("Stage III Exploration: Demoting Varibles one at a time...") + assert config.ddsEnabled, "Currently VBW on maxscale not supported" if config.wordLength != 16: assert False, "VBW mode only supported if native bitwidth is 16" - print("Scales computed in native bitwidth. Starting exploration over other bitwidths.") + Util.getLogger().debug("Scales computed in native bitwidth. Starting exploration over other bitwidths.") # We attempt to demote all possible variables in the code. We try out multiple different scales # (controlled by config.offsetsPerDemotedVariable) for each demoted variable. When a variable is @@ -451,8 +464,8 @@ def performSearch(self): numBatches = int(np.ceil(totalSize / redBatchSize)) self.varDemoteDetails = [] - for i in range(numBatches): - print("=====\nBatch %i out of %d\n=====" %(i + 1, numBatches)) + for i in tqdm(range(numBatches)): + Util.getLogger().info("=====\nBatch %i out of %d\n=====\n" %(i + 1, numBatches)) firstVarIndex = (totalSize * i) // numBatches lastVarIndex = (totalSize * (i + 1)) // numBatches @@ -487,11 +500,12 @@ def performSearch(self): contentToCodeIdMap[tuple(demotedVarsList)][demOffset] = codeId compiled = self.partialCompile(config.Version.fixed, config.Target.x86, self.sf, False, codeId, -1 if codeId != numCodes else codeId, dict(newbitwidths), list(demotedVarsList), dict(demotedVarsOffsets)) if compiled == False: - print("Variable Bitwidth exploration resulted in a compilation error") + Util.getLogger().error("Variable bitwidth exploration resulted in a compilation error\n") return False res, exit = self.runAll(config.Version.fixed, config.DatasetType.training, None, contentToCodeIdMap) - + + print("Stage IV Exploration: Cumulatively demoting variables...") # Stage IV exploration. # Again, we compute only a limited number of inference codes per generated C++ so as to not bloat up the memory usage of the compiler. redBatchSize *= config.offsetsPerDemotedVariable @@ -524,8 +538,8 @@ def performSearch(self): # Knowing the accuracy when each single variable is demoted to 8-bits one at a time, we proceed to cumulatively # demoting all of them one after the other ensuring accuracy of target code does not fall below a threshold. The # following for loop controls generation of inference codes. - for i in range(numBatches): - print("=====\nBatch %i out of %d\n=====" %(i + 1, numBatches)) + for i in tqdm(range(numBatches)): + Util.getLogger().info("=====\nBatch %i out of %d\n=====\n" %(i + 1, numBatches)) firstVarIndex = (totalSize * i) // numBatches lastVarIndex = (totalSize * (i+1)) // numBatches @@ -549,7 +563,7 @@ def performSearch(self): demotedVarsListToOffsets[tuple(demotedVarsList)] = dict(demotedVarsOffsets) compiled = self.partialCompile(config.Version.fixed, config.Target.x86, self.sf, False, codeId, -1 if codeId != numCodes else codeId, dict(newbitwidths), list(demotedVarsList), dict(demotedVarsOffsets)) if compiled == False: - print("Variable Bitwidth exploration resulted in another compilation error") + Util.getLogger().error("Variable bitwidth exploration resulted in another compilation error\n") return False res, exit = self.runAll(config.Version.fixed, config.DatasetType.training, None, contentToCodeIdMap, True) @@ -577,7 +591,7 @@ def performSearch(self): if acceptedAcc != lastStageAcc: lastStageAcc = acceptedAcc else: - print("No difference in iteration %d's stages 1 & 2. Stopping search."%fixedPointCounter) + Util.getLogger().warning("No difference in iteration %d's stages 1 & 2. Stopping search."%fixedPointCounter) break if not config.vbwEnabled or not config.fixedPointVbwIteration: @@ -602,15 +616,15 @@ def getMaximisingMetricValue(a): x = [(i, self.accuracy[i]) for i in self.accuracy] x.sort(key=getMaximisingMetricValue, reverse=True) sorted_accuracy = x[:5] - print(sorted_accuracy) + Util.getLogger().info(sorted_accuracy) return sorted_accuracy[0][0] # Find the scaling factor which works best on the training dataset and # predict on the testing dataset. def findBestScalingFactor(self): - print("-------------------------------------------------") - print("Performing search to find the best scaling factor") - print("-------------------------------------------------\n") + print("-------------------------") + print("Performing Exploration...") + print("-------------------------\n") # Generate input files for training dataset. res = self.convert(config.Version.fixed, @@ -623,7 +637,7 @@ def findBestScalingFactor(self): if res == False: return False - print("Best scaling factor = %d" % (self.sf)) + Util.getLogger().info("Best scaling factor = %d" % (self.sf)) return True # After exploration is completed, this function is invoked to show the performance of the final quantised code on a testing dataset, @@ -633,10 +647,10 @@ def runOnTestingDataset(self): print("Prediction on testing dataset") print("-------------------------------\n") - print("Setting max scaling factor to %d\n" % (self.sf)) + Util.getLogger().debug("Setting max scaling factor to %d\n" % (self.sf)) if config.vbwEnabled: - print("Demoted Vars with Offsets: %s\n" % (str(self.demotedVarsOffsets))) + Util.getLogger().debug("Demoted Vars with Offsets: %s\n" % (str(self.demotedVarsOffsets))) # Generate files for the testing dataset. res = self.convert(config.Version.fixed, @@ -652,7 +666,7 @@ def runOnTestingDataset(self): if compiled == False: return False - res, exit = self.runAll(config.Version.fixed, config.DatasetType.testing, {"default" : self.sf}) + res, exit = self.runAll(config.Version.fixed, config.DatasetType.testing, {"default" : self.sf}, printAlso=True) if res == False: return False @@ -661,9 +675,9 @@ def runOnTestingDataset(self): # This function is invoked before the exploration to obtain floating point accuracy, as well as profiling each variable # in the floating point code to compute their ranges and consequently their fixed-point ranges. def collectProfileData(self): - print("-----------------------") - print("Collecting profile data") - print("-----------------------") + Util.getLogger().info("-----------------------\n") + Util.getLogger().info("Collecting profile data\n") + Util.getLogger().info("-----------------------\n") res = self.convert(config.Version.floatt, config.DatasetType.training, config.Target.x86) @@ -679,9 +693,9 @@ def collectProfileData(self): return False self.flAccuracy = execMap["default"][0] - print("Accuracy is %.3f%%\n" % (execMap["default"][0])) - print("Disagreement is %.3f%%\n" % (execMap["default"][1])) - print("Reduced Disagreement is %.3f%%\n" % (execMap["default"][2])) + Util.getLogger().info("Accuracy is %.3f%%\n" % (execMap["default"][0])) + Util.getLogger().info("Disagreement is %.3f%%\n" % (execMap["default"][1])) + Util.getLogger().info("Reduced Disagreement is %.3f%%\n" % (execMap["default"][2])) # Generate code for Arduino. def compileFixedForTarget(self): @@ -785,9 +799,9 @@ def compileFloatForTarget(self): # Floating point x86 code. def runForFloat(self): - print("---------------------------") - print("Executing for X86 target...") - print("---------------------------\n") + Util.getLogger().info("---------------------------") + Util.getLogger().info("Executing for X86 target...") + Util.getLogger().info("---------------------------\n") res = self.convert(config.Version.floatt, config.DatasetType.testing, config.Target.x86) @@ -804,7 +818,7 @@ def runForFloat(self): else: self.testingAccuracy = execMap["default"][0] - print("Accuracy is %.3f%%\n" % (self.testingAccuracy)) + Util.getLogger().info("Accuracy is %.3f%%\n" % (self.testingAccuracy)) if self.target == config.Target.arduino: self.compileFloatForTarget() diff --git a/tools/SeeDot/seedot/predictor.py b/tools/SeeDot/seedot/predictor.py index 3b4926ac4..99cfa105c 100644 --- a/tools/SeeDot/seedot/predictor.py +++ b/tools/SeeDot/seedot/predictor.py @@ -89,7 +89,7 @@ def buildForWindows(self): Builds using the Predictor.vcxproj project file and creates the executable. The target platform is currently set to x64. ''' - print("Build...", end='') + Util.getLogger().debug("Build...") projFile = "Predictor.vcxproj" args = [config.msbuildPath, projFile, r"/t:Build", @@ -100,14 +100,14 @@ def buildForWindows(self): process = subprocess.call(args, stdout=file, stderr=subprocess.STDOUT) if process == 1: - print("FAILED!!\n") + Util.getLogger().debug("FAILED!!\n") return False else: - print("success") + Util.getLogger().debug("SUCCESS") return True def buildForLinux(self): - print("Build...", end='') + Util.getLogger().debug("Build...") args = ["make"] @@ -116,10 +116,10 @@ def buildForLinux(self): process = subprocess.call(args, stdout=file, stderr=subprocess.STDOUT) if process == 1: - print("FAILED!!\n") + Util.getLogger().debug("FAILED!!\n\n") return False else: - print("success") + Util.getLogger().debug("SUCCESS\n") return True def build(self): @@ -132,7 +132,7 @@ def executeForWindows(self): ''' Invokes the executable with arguments. ''' - print("Execution...", end='') + Util.getLogger().debug("Execution...") exeFile = os.path.join("x64", "Release", "Predictor.exe") args = [exeFile, self.version, self.datasetType, self.problemType, str(self.numOutputs)] @@ -142,15 +142,15 @@ def executeForWindows(self): process = subprocess.call(args, stdout=file, stderr=subprocess.STDOUT) if process == 1: - print("FAILED!!\n") + Util.getLogger().debug("FAILED!!\n\n") return None else: - print("success") + Util.getLogger().debug("SUCCESS\n") execMap = self.readStatsFile() return execMap def executeForLinux(self): - print("Execution...", end='') + Util.getLogger().debug("Execution...") exeFile = os.path.join("./Predictor") args = [exeFile, self.version, self.datasetType, self.problemType, str(self.numOutputs)] @@ -160,10 +160,10 @@ def executeForLinux(self): process = subprocess.call(args, stdout=file, stderr=subprocess.STDOUT) if process == 1: - print("FAILED!!\n") + Util.getLogger().debug("FAILED!!\n\n") return None else: - print("success") + Util.getLogger().debug("SUCCESS\n") execMap = self.readStatsFile() return execMap diff --git a/tools/SeeDot/seedot/util.py b/tools/SeeDot/seedot/util.py index 1b92d5cca..ab5ee6f92 100644 --- a/tools/SeeDot/seedot/util.py +++ b/tools/SeeDot/seedot/util.py @@ -5,6 +5,7 @@ import platform import seedot.config as config +import logging class Config: @@ -163,3 +164,9 @@ def computeScalingFactorForFuncCalls(val): def computeScalingFactorForInlineCodegen(val): return int(np.ceil(np.log2(val) - np.log2((1 << (config.wordLength - 2)) - 1))) + + +# Logging Section +def getLogger(): + log = logging.getLogger() + return log