#!/usr/bin/python3 ################################################################################ # # Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # ################################################################################ import fileinput,csv import argparse from collections import OrderedDict from functools import reduce import operator class DataRow: """ Single result row """ def __init__(self, row): self.dataRow = row self.derivedFields = {} def __getitem__(self, key): return self.dataRow[key] def finalizeDerivedFields(self, solution): self.derivedFields['best-any'] = str(solution.maxPerfRow["gflops"]) if args.ops_per_cu_per_cycle: peakMFlops = float(self["clock-sys"]) * float(self["num-cus"]) * args.ops_per_cu_per_cycle sizes = [int(p) for p in self['problem-sizes'].strip("()").split(",")] work = reduce(operator.mul, sizes, 2.0) self.derivedFields['model-alu-us'] = '%6.0f' % (work / peakMFlops * float(self["total-gran"])) if args.mem_width: peakMBps = float(self["clock-mem"]) * args.mem_width * args.mem_eff/100.0 self.derivedFields['model-mem-read-us'] = "%6.0f" % (float(self["mem-read-bytes"]) / peakMBps) self.derivedFields['model-mem-write-us'] = "%6.0f" % (float(self["mem-write-bytes"]) / peakMBps) class Solution: """ Rows associated with this solution """ def __init__(self): self.problemResults = [] self.maxPerfRow = None def addRow(self,row): self.problemResults.append(row) if self.maxPerfRow==None or float(row["gflops"]) > float(self.maxPerfRow["gflops"]): self.maxPerfRow = row class ProblemSize: """ Rows associated with this problem """ def __init__(self): self.dataRows = [] def addRow(self,row): self.dataRows.append(row) class Reader: def formatCol(self, fieldName, val): return ('{0: <%d}'%self.fieldLength[fieldName]).format(val) def printRow(self, row, fieldsToPrint, derivedFieldsToPrint, end='\n'): for field in fieldsToPrint: print (self.formatCol(field, row[field]), end=" ") for field in derivedFieldsToPrint: print (self.formatCol(field, row.derivedFields[field]), end=" ") print (end=end) def readFile(self, fileName): db = 0 csv_field_names = None csv_table=[] for line in fileinput.input(fileName): if line.startswith("run,"): csv_field_names = line.replace(' ', '').split(',') elif csv_field_names and "Contraction" in line: csv_table.append(line) if db: print ("L=",line), dictReader = csv.DictReader(csv_table, csv_field_names, skipinitialspace=True) fieldsToPrint= ('problem-progress', 'problem-sizes', 'solution', 'time-us', 'gflops', \ 'tiles-per-cu', 'total-gran', 'cu-gran', 'tile0-gran', 'tile1-gran') derivedFieldsToPrint = [] if args.ops_per_cu_per_cycle: derivedFieldsToPrint += ["model-alu-us"] if args.mem_width: derivedFieldsToPrint += ["model-mem-read-us", "model-mem-write-us"] derivedFieldsToPrint += ['best-any'] for f in fieldsToPrint: assert f not in derivedFieldsToPrint self.fieldLength={} for k in fieldsToPrint: self.fieldLength[k] = len(k) # length of name for k in derivedFieldsToPrint: self.fieldLength[k] = len(k) self.dataOut=[] self.solutions = {} # map from solution to Solution class problemSizes = OrderedDict() for row in dictReader: self.solutions.setdefault(row["solution"],Solution()).addRow(DataRow(row)) problemSizes.setdefault(row["problem-sizes"],ProblemSize()).addRow(DataRow(row)) keep = {} for k in fieldsToPrint: keep[k] = row[k] self.fieldLength[k] = max(len(row[k]), self.fieldLength[k]) self.dataOut.append((keep)) # after everything added, compute derived stats: for key,ps in problemSizes.items(): for row in ps.dataRows: row.finalizeDerivedFields(self.solutions[row["solution"]]) for k in derivedFieldsToPrint: self.fieldLength[k] = max(len(row.derivedFields[k]), self.fieldLength[k]) # sort by gflops self.dataOut.sort(key = lambda row: int(row["gflops"]), reverse=True) # print header: for field in fieldsToPrint: print (self.formatCol(field,val=field), end=" ") for field in derivedFieldsToPrint: print (field,end=" ") print() if args.print_winners: for key,ps in problemSizes.items(): if args.problem_progress==None or int(ps.dataRows[0]["problem-progress"].split('/')[0]) in args.problem_progress: dataRows = [row for row in ps.dataRows if args.filter in row["solution"]] sortedRows = sorted(dataRows, key = lambda row: int(row["gflops"]), reverse=True) for i in range(min(len(sortedRows),args.print_winners) if args.print_winners>=0 else len(sortedRows)): self.printRow(sortedRows[i], fieldsToPrint, derivedFieldsToPrint) if args.print_summary: for row in self.dataOut: self.printRow(row, fieldsToPrint, derivedFieldsToPrint) my_parser = argparse.ArgumentParser(description='summarize and analyze tensile benchmark results') my_parser.add_argument('--print_winners', '-w', type=int, default=1, help='print top N winners for each problem. -1 prints all') my_parser.add_argument('--problem_progress', '-p', type=int, action='append', help='show only specified problem. Can specify multiple times.') my_parser.add_argument('--print_summary', '-s', action='store_true', help='print all results') my_parser.add_argument('--filter', '-k', type=str, default="", help='filter solutions, ie "GSU1".') my_parser.add_argument('--ops-per-cu-per-cycle', '-o', type=int, default=0, help='ops per cu per cycle. Typical values 64(vega20 f64), 128(vega20 f32), etc. used to compute AluUs') my_parser.add_argument('--mem-width', '-m', type=int, default=1024, help='mem bus width in bits. Typical values 1024 (vega20). Used to compute Mem*Us') my_parser.add_argument('--mem-eff', '-e', type=int, default=85, choices=range(1,100), help='efficiency of memory bus. Must be integer 1.100.') my_parser.add_argument('input_file', help='file with tensile output to parse') args = my_parser.parse_args() r = Reader() r.readFile(args.input_file)