Files
TheRock/rocm-libraries/projects/rocfft/scripts/perf/rocfft-perf
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1274 lines
43 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (C) 2021 - 2025 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.
"""Performance utilities for rocFFT.
Overview
========
General workflow:
- run: runs a suite of FFTs to collect timing information
- post: post processes timing information to compute various statistics
- plot: generate pdf or html plots of the results
- autoperf: clones, builds, runs, posts, and plots two rocFFT commits
Multiple runs can be compared at the post processing and plotting
stages. Multiple runs may:
- be from different benchmark programs (eg, rocFFT, cuFFT, vkFFT, etc)
- be from dyna-rocfft-bench.
Usually:
- a single bench (rocFFT) would be used to track performance over
time;
- multiple benchess (rocFFT, cuFFT) would be used to compare different
FFT libraries;
- a dyna-bench with multiple libraries (rocFFT) would be used to
compare two different rocFFT commits.
Runs/subprocesses are logged to `rocfft-perf.log`.
Run
===
The 'run' command drives FFT benchmarkers (if they accept the same
command line arguments as `rocfft-bench`). The benchmark program to
use is specified by the `--bench/-w` switch.
Test problems are generated using a `ProblemGenerator` and a filter.
The default generator is a simple radix based generator.
See
$ rocfft-perf run -h
for more details. To see which problems will be run without running
them, use `--list/-l`.
Using the `--suite/-S` option, problems are loaded from a "suites"
file. The default suites file is `suites.py`. Alternatively, you can
load the suite named "qa1" from a file called "mysuites.py" like this:
$ rocfft-perf run -S mysuites:qa1 ...
That is, FILENAME:SUITENAME.
By default, output files are stored in the `out0` directory. This can
be changed with the `--output/-o` agrument.
Dynamic testing
===============
Dynamic testing is enabled by specifying more than one `--lib/-i`
option. These are passed down to the benchmarker, and hence it is assumed
that the specific benchmarker is a "dyna" bench.
Multiple output directories are used to store the results.
Post processing
===============
During the post processing stage, various statistics are computed and
saved:
$ rocfft-perf post DOCDIR OUTPUT [OUTPUT ...]
The first directory is the 'document directory'. When comparing
multiple runs, comparative statistics are saved here in `.sdat` files.
For each `.dat` file in the output directories, summary statistics are
saved in `.mdat` files.
Plotting
========
Based on the results from post processing, generate either an html or
pdf report:
$ rocfft-perf html DOCDIR OUTPUT [OUTPUT ...]
$ rocfft-perf pdf DOCDIR OUTPUT [OUTPUT ...]
"""
import argparse
import logging
import statistics
import sys
import os
import tempfile
import re
import collections
from pathlib import Path
import multiprocessing
top = Path(__file__).resolve().parent
sys.path.append(str(top))
import perflib
console = logging.StreamHandler()
import types
#
# Helpers
#
def update(attr, dst, src):
"""Set attribute `attr` on dst if it is not None on `src`."""
value = getattr(src, attr, None)
if value is not None:
setattr(dst, attr, value)
#
# Commands
#
def command_test(arguments):
"""Test for regressions."""
# FIXME: rename and replace above comment
verbose = arguments.verbose
significance = arguments.significance
multitest = arguments.multitest
print("statistical test:", arguments.method)
print("significance:", significance)
print("multi-hypothesis framework:", multitest)
outdirs = [Path(x) for x in arguments.runs]
all_runs = perflib.utils.read_runs(outdirs, verbose)
if len(all_runs) != 2:
print(
"Error: one must provide exactly two runs for statistical comparison"
)
sys.exit(1)
runs = perflib.utils.by_dat(all_runs)
refdir, testdir = outdirs
label = ["reference", "test"]
if arguments.label != None:
for idx in range(len(arguments.label)):
label[idx] = arguments.label[idx]
print("reference data:", outdirs[0], label[0])
print("comparison data:", outdirs[1], label[1])
print()
ncompare = perflib.utils.find_ncompare(arguments.runs)
slower, faster, new_sig = perflib.utils.find_slower_faster(
outdirs, arguments.method, multitest, significance, ncompare, verbose)
if verbose:
print("faster:")
for case in faster:
print(case)
print("slower:")
for case in slower:
print(case)
nh1 = len(faster) + len(slower)
nh0 = ncompare - nh1
print("ncompare:", ncompare)
print("nh0:", nh0)
print("nh1:", nh1)
print("faster (" + label[1] + " wins): " + str(len(faster)))
print("slower (" + label[0] + " wins): " + str(len(slower)))
import numpy as np
geomean = 1.0
if nh1 > 0:
sig_speedups = []
for case in slower:
sig_speedups.append(case[1] / case[2])
for case in faster:
sig_speedups.append(case[1] / case[2])
import scipy
geomean = scipy.stats.mstats.gmean(sig_speedups)
print("geomean (significant cases):", geomean)
globalgeomean = perflib.utils.find_geomean(outdirs, verbose)
print("geomean:", globalgeomean)
return len(slower) > 0
def generate_mdat(dat, measure, confidence):
import numpy
vals = [['token', 'median_sample', 'median_low', 'median_high']]
for token, sample in dat.get_samples():
if measure == "median":
median = statistics.median(sample.times)
elif measure == "mean":
median = numpy.mean(sample.times)
low, high = perflib.analysis.confidence_interval(sample.times,
measure=measure,
confidence=confidence)
vals.append([sample.label, median, low, high])
path = dat.path.with_suffix('.mdat')
perflib.utils.write_tsv(path, vals, meta=dat.meta, overwrite=True)
def generate_pts_dat(dat):
"""
For PTS system, extract data from raw dat and mdat.
"""
import pandas
mdat = dat.path.with_suffix('.mdat')
mdat_df = pandas.read_csv(mdat, delimiter='\t', comment='#')
# The parsing rule subjects to changes in the future
ss = dat.tag
input_params = []
# placeness
input_params.append(ss[ss.rfind('_') + 1:])
ss = ss[:ss.rfind('_')]
# transform type
input_params.append(ss[ss.rfind('_', 0, ss.rfind('_') - 1) + 1:])
ss = ss[:ss.rfind('_', 0, ss.rfind('_') - 1)]
# precision
input_params.append(ss[ss.rfind('_') + 1:])
# suite
input_params.append(ss[:ss.rfind('_')])
input_params.reverse()
dimensions = set()
rows = []
for row_idx, sample in enumerate(dat.get_samples()):
new_row = []
token = sample[0]
# sanity check
if not token or token.count('_') < 3:
print(f"Skipping malformed token on row {row_idx}: '{token}'")
continue
try:
transform_type, placeness, length, batch, precision, bricks, gpus, ranks = perflib.utils.parse_token(
token)
except Exception as e:
print(
f"Error parsing token on row {row_idx}: '{token}'\nException: {e}"
)
continue
new_row.extend(input_params)
new_row.insert(1, token)
dimensions.add(len(length))
new_row.append(len(length))
new_row.extend(length)
if len(batch) == 1:
new_row.append(batch[0])
else:
print("multi-batch data format; exiting abnormally")
sys.exit(1)
# Multi-process and multi-gpu performance
ngpus_per_node = max(1, len(gpus))
nranks = max(1, len(ranks))
nnodes = max(1,
(nranks + 1) / ngpus_per_node) # assumes 1 gpu per rank
new_row.append(nranks)
new_row.append(ngpus_per_node)
new_row.append(nnodes)
# Input layout
input_split_type = perflib.utils.get_decomposition_type(
bricks['ifield'], label="ifield")
input_grid = perflib.utils.get_proc_grid(bricks['ifield'])
new_row.append(input_split_type)
new_row.extend(input_grid)
# Output layout
output_split_type = perflib.utils.get_decomposition_type(
bricks['ofield'], label="ofield")
output_grid = perflib.utils.get_proc_grid(bricks['ofield'])
new_row.append(output_split_type)
new_row.extend(output_grid)
# Statistics
new_row.extend(
mdat_df.loc[row_idx,
['median_sample', 'median_low', 'median_high']].
to_numpy().tolist())
times = sample[1].times
new_row.append(len(times))
new_row.extend(times)
rows.append(new_row)
if len(set(dimensions)) > 1:
print("mixed dimensions in the set; exiting abnormally")
sys.exit(1)
if len(set(dimensions)) == 0:
print("PTS data set empty")
return
dimension = list(dimensions)[0]
header = [
'suite', 'token', 'precision', 'transform type', 'placeness',
'dimension', 'xlength'
]
if dimension == 2:
header.append('ylength')
elif dimension == 3:
header.extend(['ylength', 'zlength'])
header.append('nbatch')
# Multi-process headers
header.extend([
'nprocs', 'ngpus_per_node', 'nnodes', 'input_split_type', 'ingrid_x',
'ingrid_y', 'ingrid_z', 'output_split_type', 'outgrid_x', 'outgrid_y',
'outgrid_z'
])
header.extend(
['median_sample', 'median_low', 'median_high', 'nsample', 'samples'])
content = [header]
content.extend(rows)
perflib.utils.write_pts_dat(dat.path.with_suffix('.ptsdat'),
content,
meta=dat.meta)
def command_post(arguments):
"""Post process results in directories listed in `outdirs`.
Median confidence intervals for each run are written in 'mdat'
files.
Speedups and pvals are written in 'sdat' files.
"""
outdirs = arguments.runs
docdir = arguments.output
verbose = arguments.verbose
import itertools
if verbose:
print("docdir:", docdir)
print("outdirs:", outdirs)
outdirs = [Path(x) for x in outdirs]
all_runs = perflib.utils.read_runs(outdirs, verbose)
# median confidence intervals
for run in all_runs:
with multiprocessing.Pool(None) as p:
p.starmap(
generate_mdat,
itertools.product(run.dats.values(), [arguments.measure],
[arguments.confidence]))
p.map(generate_pts_dat, run.dats.values())
# speedup and pvals
if len(outdirs) > 1:
docdir = Path(docdir)
docdir.mkdir(parents=True, exist_ok=True)
import scipy.stats, numpy
runs = perflib.utils.by_dat(all_runs)
group_outdirs = []
if arguments.ngroup == None:
group_outdirs.append(outdirs)
else:
totalgroups = (len(outdirs) + arguments.ngroup -
1) // arguments.ngroup
for gidx in range(totalgroups):
group_outdirs.append(
outdirs[gidx * arguments.ngroup:(gidx + 1) *
arguments.ngroup])
#print(group_runs)
#sys.exit(1)
for gidx, group in enumerate(group_outdirs):
refdir, *otherdirs = group
for dat_name, dat_runs in runs.items():
refdat = dat_runs[refdir]
for otherdat in [
dat_runs[otherdir] for otherdir in otherdirs
if otherdir in dat_runs
]:
speedups = [[
'token', 'speedup', 'speedup_low', 'speedup_high',
'speedup_pval'
]]
for token, sample in refdat.get_samples():
if token not in otherdat.samples:
continue
sample = refdat.samples[token]
Avals = refdat.samples[token].times
Bvals = otherdat.samples[token].times
if arguments.measure == "median":
speedup = statistics.median(
Avals) / statistics.median(Bvals)
elif arguments.measure == "mean":
speedup = numpy.mean(Avals) / numpy.mean(Bvals)
low, high = perflib.analysis.ratio_confidence_interval(
Avals, Bvals)
pval = -1
if arguments.method == 'moods':
_, pval, _, _ = scipy.stats.median_test(
Avals, Bvals)
elif arguments.method == 'ttest':
_, pval = scipy.stats.ttest_ind(Avals, Bvals)
elif arguments.method == 'mwu':
_, pval = scipy.stats.mannwhitneyu(Avals, Bvals)
else:
print("unsupported statistical method")
sys.exit(1)
speedups.append(
[sample.token, speedup, low, high, pval])
path = docdir / ('group_' + str(gidx) + '-' + dat_name +
'.sdat')
perflib.utils.write_tsv(path,
speedups,
meta=refdat.meta,
overwrite=True)
def command_generate(arguments):
"""Generate PDF/HTML/DOCX from run results."""
import perflib.pdf
import perflib.html
Figure = {
'pdf': perflib.pdf.PDFFigure,
'html': perflib.html.HTMLFigure,
'docx': perflib.pdf.PDFFigure,
}[arguments.command]
docdir = Path(arguments.output)
docdir.mkdir(parents=True, exist_ok=True)
outdirs = [Path(outdir) for outdir in arguments.runs]
label = arguments.label
if label is None:
label = [outdir.stem for outdir in outdirs]
reference = perflib.utils.read_run(outdirs[0])
import pandas
import perflib.utils
ncompare = perflib.utils.find_ncompare(arguments.runs)
print("ncompare:", ncompare)
slower, faster, new_sig = perflib.utils.find_slower_faster(
outdirs, arguments.method, arguments.multitest, arguments.significance,
ncompare, arguments.verbose)
figures = []
for datname in perflib.utils.list_runs(outdirs[0]):
tag = datname.stem
title = reference.dats[datname.stem].meta.get('title', tag)
caption = reference.dats[datname.stem].meta.get('caption',
title).replace(
'_', ' ')
figtype = reference.dats[datname.stem].meta.get('figtype', 'linegraph')
primary, secondary = perflib.utils.get_post_processed(
tag, docdir, outdirs, arguments.ngroup)
figure = Figure(tag, title, caption, docdir, label, primary, secondary,
figtype, reference.dats[datname.stem].meta)
figures.append(figure)
for figure in figures:
figure.make(new_sig)
if arguments.command == 'pdf':
if False:
for figure in figures:
figure.runasy()
else:
pool = multiprocessing.Pool(None)
for figure in figures:
pool.map_async(Figure.runasy, [figure])
pool.close()
pool.join()
if arguments.command == 'pdf':
perflib.pdf.make_tex(figures, docdir, outdirs, label, new_sig,
ncompare)
if arguments.command == 'html':
title = f"Performance report: {perflib.utils.cjoin(outdirs)}"
perflib.html.make_html(figures, title, docdir, outdirs,
arguments.significance)
if arguments.command == 'docx':
import perflib.docx
perflib.docx.make_docx(figures, docdir, outdirs,
arguments.significance)
def command_run(arguments):
"""Run dyna-bench or bench."""
# build generator
generator = None
if arguments.suite is not None:
generator = perflib.generators.SuiteProblemGenerator(arguments.suite)
else:
generator = perflib.generators.RadixProblemGenerator()
for attr in [
'radix', 'xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax',
'verbose', 'timeout', 'hipskip'
]:
update(attr, generator, arguments)
for attr in ['nbatch', 'mp_size', 'mp_exec', 'ingrid', 'outgrid', 'ngpus']:
update(attr, generator, arguments)
# build filter
filtered = perflib.generators.FilteredProblemGenerator()
if arguments.direction is not None:
filtered.direction = [arguments.direction]
if arguments.inplace:
filtered.inplace = [True]
if arguments.outplace:
filtered.inplace = [False]
if arguments.real:
filtered.real = [True]
if arguments.complex:
filtered.real = [False]
if arguments.precision:
filtered.precision = arguments.precision
if arguments.dimension:
filtered.dimension = arguments.dimension
if arguments.mp_size:
filtered.mp_size = arguments.mp_size
if arguments.mp_exec:
filtered.mp_exec = arguments.mp_exec
if arguments.ingrid:
filtered.ingrid = arguments.ingrid
if arguments.outgrid:
filtered.outgrid = arguments.outgrid
if arguments.ngpus:
filtered.ngpus = arguments.ngpus
if arguments.list:
for test in filtered(generator).generate_problems():
print(test)
return
# build timer
if arguments.bench is None:
print("No benchmarker set... use -w /path/to/benchmarker.")
return
dyna = 'dyna' in arguments.bench
if dyna:
if not arguments.lib:
print(
"Need to set dynamically loaded library when using dyna-bench."
)
return
if not arguments.out:
nout = len(arguments.lib) if dyna else 1
arguments.out = ['out' + str(i) for i in range(nout)]
timer = perflib.timer.GroupedTimer()
for attr in [
'device', 'bench', 'accutest', 'lib', 'out', 'device', 'ntrial',
'verbose', 'timeout', 'sequence', 'mp_size', 'mp_exec', 'ingrid',
'outgrid', 'ngpus'
]:
update(attr, timer, arguments)
specs = perflib.specs.get_machine_specs(timer.device, arguments.specs_type)
for out in timer.out:
specs_file = Path(out) / 'specs.txt'
specs_file.parent.mkdir(parents=True, exist_ok=True)
specs_file.write_text(str(specs))
failed_tokens = timer.run_cases(filtered(generator))
print()
if failed_tokens:
logging.info("failed tokens: " + "\n".join(failed_tokens))
print("failed tokens:\n" + "\n".join(failed_tokens))
def command_autoperf(arguments):
"""Compare performance of two builds automagically."""
workdir = arguments.workdir
reference_commit = arguments.reference_commit
reference_repository = arguments.reference_repository
reference_label = arguments.reference_label
commit = arguments.commit
repository = arguments.repository
label = arguments.label
suite = arguments.suite
format = arguments.format
static = arguments.static
timeout = arguments.timeout
# Use the short version of the hashes (default length: 7)
if commit != None:
commit = commit[0:6]
if reference_commit != None:
reference_commit = reference_commit[0:6]
from perflib.build import build_rocfft
if reference_repository is None:
reference_repository = repository
if reference_label is None:
reference_label = reference_commit
if label is None:
label = commit
top = Path(workdir).resolve()
build1 = top / f'build-{reference_commit}'
build2 = top / f'build-{commit}'
output = top / f'doc-{commit}'
# build rocFFTs
top.mkdir(parents=True, exist_ok=True)
os.chdir(str(top))
lib1 = build1 / 'lib' / 'librocfft.so'
lib1.parent.mkdir(parents=True, exist_ok=True)
if not lib1.exists():
build_rocfft(reference_commit, dest=build1, repo=reference_repository)
lib2 = build2 / 'lib' / 'librocfft.so'
lib2.parent.mkdir(parents=True, exist_ok=True)
if not lib2.exists():
build_rocfft(commit, dest=build2, repo=repository)
# run cases
if static:
# use more trials for static bench
timer1 = perflib.timer.GroupedTimer()
timer1.bench = build1 / 'rocfft-bench'
timer1.lib = None
timer1.out = [build1]
timer1.ntrial = 20
timer1.timeout = timeout
timer2 = perflib.timer.GroupedTimer()
timer2.bench = build2 / 'rocfft-bench'
timer2.lib = None
timer2.out = [build2]
timer2.ntrial = 20
timer2.timeout = timeout
timers = [timer1, timer2]
else:
timer = perflib.timer.GroupedTimer()
timer.bench = build1 / 'dyna-rocfft-bench'
timer.lib = [lib1, lib2]
timer.out = [build1, build2]
timer.timeout = timeout
timers = [timer]
specs = perflib.specs.get_machine_specs(timers[0].device,
arguments.specs_type)
for t in timers:
for out in t.out:
specs_file = Path(out) / 'specs.txt'
specs_file.write_text(str(specs))
generator = perflib.generators.SuiteProblemGenerator(suite)
for t in timers:
t.run_cases(generator)
# post-process results
arguments.runs = [build1, build2]
arguments.output = output
arguments.label = [reference_label, label]
command_post(arguments)
# generate report
for report_type in format:
command_generate(arguments)
def command_bweff(arguments):
"""Collect bandwidth efficiency information."""
# build generator from suite
generator = perflib.generators.SuiteProblemGenerator(arguments.suite)
Path(arguments.out).mkdir(parents=True, exist_ok=True)
all_problems = collections.defaultdict(list)
for problem in generator.generate_problems():
all_problems[problem.tag].append(problem)
# create temporary file
fp = tempfile.NamedTemporaryFile()
# set environment variables
os.environ['ROCFFT_LAYER'] = '4'
os.environ['ROCFFT_LOG_PROFILE_PATH'] = fp.name
data = []
for i, (tag, problems) in enumerate(all_problems.items()):
print(
f'\n{tag} (group {i} of {len(all_problems)}): {len(problems)} problems'
)
bench = Path(arguments.bench)
if not bench.is_file():
raise RuntimeError(
f"Unable to find benchmarker: {arguments.bench}")
effdat_paths = [Path(arguments.out) / (tag + '.effdat')]
generator = perflib.generators.VerbatimGenerator(problems)
for prob in generator.generate_problems():
# determine appropriate batch size
if prob.precision == "half":
elem_size_bytes = 4
elif prob.precision == "single":
elem_size_bytes = 8
elif prob.precision == "double":
elem_size_bytes = 16
for length in prob.length:
elem_size_bytes *= length
nbatch = (arguments.target_size << 30) // elem_size_bytes
# run bench
token = perflib.bench.run(arguments.bench,
prob.length,
direction=prob.direction,
real=prob.real,
inplace=prob.inplace,
precision=prob.precision,
nbatch=nbatch,
ntrial=arguments.ntrial,
mp_size=prob.mp_size,
mp_exec=prob.mp_exec,
ingrid=prob.ingrid,
outgrid=prob.outgrid,
ngpus=prob.ngpus)[0]
fp.seek(0)
# parse profile log
profile_log = []
for line in fp:
line = line.decode('UTF-8').strip('\n')
perf_info = {}
items = re.split(r',(?![^\[]*[\]])', line)
for i in range(1, len(items), 2):
perf_info.update({items[i]: items[i + 1]})
profile_log.append(perf_info)
fp.truncate(0)
# collect data in tab-separated .effdat files
for path in effdat_paths:
out = Path(path)
logging.info("output: " + str(out))
meta = {'title': prob.tag}
meta.update(prob.meta)
for row in profile_log:
records = [
token, # testcase token
row['scheme'], # scheme
row['duration_ms'], # kernel duration in milliseconds
row['bw_efficiency_pct'], # estimated efficiency
row['kernel_index'] # index number of this kernel in the execution plan
]
data.append(records)
perflib.utils.write_tsv(out, [records], meta=meta)
# close temporary file
fp.close()
# unset environment variables
if 'ROCFFT_LAYER' in os.environ:
del os.environ['ROCFFT_LAYER']
del os.environ['ROCFFT_LOG_PROFILE_PATH']
# determine median duration and efficiency by token and index
medians = collections.defaultdict(list)
for entry in data:
token = entry[0]
scheme = entry[1]
duration = float(entry[2])
efficiency = float(entry[3])
index = int(entry[4])
medians[(token, index, scheme)].append((duration, efficiency))
# collect median data in tab-separated .effdat files
out = Path(arguments.out) / ("median_values.effdat")
logging.info("output: " + str(out))
meta = {'title': "median values"}
for key in medians:
if arguments.mesaure == "median":
records = [
key[0], # token
key[1], # index
key[2], # scheme
statistics.median(medians[key][0]), # duration_ms
statistics.median(medians[key][1]) # bw_efficiency_pct
]
elif arguments.mesaure == "mean":
records = [
key[0], # token
key[1], # index
key[2], # scheme
numpy.mean(medians[key][0]), # duration_ms
numpy.mean(medians[key][1]) # bw_efficiency_pct
]
perflib.utils.write_tsv(out, [records], meta=meta)
#
# Main
#
def main():
parser = argparse.ArgumentParser(
prog='rocfft-perf',
epilog="For a detailed usage overview, run: %(prog)s overview")
parser.add_argument('-v', '--verbose', action='store_true', default=False)
parser.add_argument('--logfile',
type=str,
help="log file name",
default="rocfft-perf.log")
subparsers = parser.add_subparsers(dest='command')
subparsers.add_parser('overview', help='print a general usage overview')
specs_parser = subparsers.add_parser('specs', help='print machine specs')
run_parser = subparsers.add_parser('run', help='run!')
post_parser = subparsers.add_parser('post', help='post processing')
pdf_parser = subparsers.add_parser('pdf', help='generate pdf plots')
html_parser = subparsers.add_parser('html', help='generate html plots')
docx_parser = subparsers.add_parser('docx', help='generate docx plots')
test_parser = subparsers.add_parser('test', help='test for regressions')
autoperf_parser = subparsers.add_parser(
'autoperf',
help='clone, build, run, post, and plot two rocFFT commits')
specs_parser.add_argument(dest='specs_type',
type=str,
default='default',
nargs='?',
choices=['default', 'host', 'device'],
help="type of specs")
for p in [post_parser, pdf_parser, html_parser, docx_parser]:
p.add_argument('output', type=str)
for p in [post_parser, pdf_parser, html_parser, docx_parser, test_parser]:
p.add_argument('runs', type=str, nargs='+')
for p in [post_parser, autoperf_parser]:
p.add_argument('--confidence',
type=str,
choices=["bootstrap", "stdev"],
help="method for generating confidence interval",
default="bootstrap")
for p in [post_parser, pdf_parser, html_parser]:
p.add_argument('--ngroup',
type=int,
help="size of comparison subgroup")
for p in [
post_parser, pdf_parser, test_parser, autoperf_parser, html_parser
]:
p.add_argument('--method',
type=str,
choices=["moods", "ttest", "mwu"],
help="statistical method",
default="moods")
for p in [
post_parser, pdf_parser, html_parser, docx_parser, test_parser,
autoperf_parser
]:
p.add_argument('--measure',
type=str,
choices=["mean", "median"],
help="measure of central tendency: median or mean",
default="median")
for p in [
pdf_parser, html_parser, docx_parser, test_parser, autoperf_parser
]:
p.add_argument('--significance',
type=float,
help='moods significance threshold',
default=0.05)
p.add_argument('--multitest',
type=str,
choices=["none", "bonferroni", "bh"],
help='Multi-hypothesis correction method',
default="bonferroni")
for p in [pdf_parser, html_parser, docx_parser, test_parser]:
p.add_argument('-l',
'--label',
type=str,
help='label (appendable)',
action='append')
run_parser.add_argument('-g', '--device', type=int, help='device number')
run_parser.add_argument(
'--hipskip',
action=argparse.BooleanOptionalAction,
help='Do not count hip runtime error codes as failures')
run_parser.add_argument('-l',
'--list',
help='list runs (but do not run them)',
action='store_true',
default=False)
run_parser.add_argument('-o',
'--out',
type=str,
help='output (appendable)',
action='append')
run_parser.add_argument('-S',
'--suite',
type=str,
help='test suite name (appendable)',
action='append')
run_parser.add_argument('-w',
'--bench',
type=str,
help='test executable path')
run_parser.add_argument('-i',
'--lib',
type=str,
help='test library path (appendable)',
action='append')
run_parser.add_argument('-r', '--radix', type=int, help='radix')
run_parser.add_argument('-x',
'--xmin',
type=int,
help='minimum problem size in x direction')
run_parser.add_argument('-X',
'--xmax',
type=int,
help='maximum problem size in x direction')
run_parser.add_argument('-y',
'--ymin',
type=int,
help='minimum problem size in y direction')
run_parser.add_argument('-Y',
'--ymax',
type=int,
help='maximum problem size in y direction')
run_parser.add_argument('-z',
'--zmin',
type=int,
help='minimum problem size in z direction')
run_parser.add_argument('-Z',
'--zmax',
type=int,
help='maximum problem size in z direction')
run_parser.add_argument('-D',
'--direction',
type=int,
help='direction of transform')
run_parser.add_argument('-I',
'--inplace',
help='make transform in-place',
action='store_true',
default=False)
run_parser.add_argument('-O',
'--outplace',
help='make transform out-of-place',
action='store_true',
default=False)
run_parser.add_argument('-R',
'--real',
help='make transform real/complex',
action='store_true',
default=False)
run_parser.add_argument('-C',
'--complex',
help='make transform complex/complex',
action='store_true',
default=False)
run_parser.add_argument('-d',
'--dimension',
type=int,
help='dimension of transform',
action='append')
run_parser.add_argument('-b',
'--nbatch',
type=int,
help='number of batches')
run_parser.add_argument('-N',
'--ntrial',
type=int,
help='number of trials',
default=20)
run_parser.add_argument('-mp_size',
'--mp_size',
type=int,
help='number of MPI processors',
default=1)
run_parser.add_argument('-mp_exec',
'--mp_exec',
type=str,
help="path to mpi executable",
default="/usr/bin/mpirun")
run_parser.add_argument('-ngpus',
'--ngpus',
type=int,
help='number of GPUs',
default=1)
run_parser.add_argument(
'-T',
'--timeout',
type=int,
help='test timeout in seconds (0 disables timeout)',
default=600)
run_parser.add_argument('--sequence',
type=int,
help='dyna-bench test sequence',
default=0)
run_parser.add_argument('--ingrid',
type=int,
required=False,
nargs='+',
help='input processors grid')
run_parser.add_argument('--outgrid',
type=int,
required=False,
nargs='+',
help='output processors grid')
run_parser.add_argument('-f',
'--precision',
type=str,
help='precision',
action='append')
run_parser.add_argument('-t',
'--accutest',
type=str,
help='accuracy test executable path')
run_parser.add_argument('--specs_type',
type=str,
default='default',
nargs='?',
choices=['default', 'host', 'device'],
help="type of specs")
autoperf_parser.add_argument('--workdir',
type=str,
help='Working directory',
default='.')
autoperf_parser.add_argument('--reference_commit',
type=str,
help='Reference commit',
required=True)
autoperf_parser.add_argument(
'--reference_repository',
type=str,
help='Reference repository (if different from repository)')
autoperf_parser.add_argument(
'--reference_label',
type=str,
help='Reference label (if different from reference commit)')
autoperf_parser.add_argument('--commit',
type=str,
help='Commit to test',
required=True)
autoperf_parser.add_argument('--repository',
type=str,
help='Repository to test',
required=True)
autoperf_parser.add_argument(
'--label', type=str, help='Test label (if different from test commit)')
autoperf_parser.add_argument('--suite',
type=str,
help='Test suite name (appendable)',
action='append',
required=True)
autoperf_parser.add_argument('--format',
type=str,
help='Output format (appendable)',
action='append',
default=['html'])
autoperf_parser.add_argument('--static',
help='Use static bench instead of dyna',
action='store_true',
default=False)
autoperf_parser.add_argument(
'-T',
'--timeout',
type=int,
help='test timeout in seconds (0 disables timeout)',
default=600)
autoperf_parser.add_argument('--specs_type',
type=str,
default='default',
nargs='?',
choices=['default', 'host', 'device'],
help="type of specs")
bweff_parser = subparsers.add_parser(
'bweff', help='bandwidth efficiency collection')
# suite of tests to run
bweff_parser.add_argument('-S',
'--suite',
type=str,
help='test suite name (appendable)',
action='append',
required=True)
# path to bench executable
bweff_parser.add_argument('-w',
'--bench',
type=str,
help='test executable path',
required=True)
# output directory for results
bweff_parser.add_argument('-o',
'--out',
type=str,
help='output',
default='out')
# number of trials to run per test case
bweff_parser.add_argument('-N',
'--ntrial',
type=int,
help='number of trials',
default=10)
bweff_parser.add_argument('-mp_size',
'--mp_size',
type=int,
help='number of MPI processors',
default=1)
bweff_parser.add_argument('-mp_exec',
'--mp_exec',
type=str,
help="path to mpi executable",
default="/usr/bin/mpirun")
bweff_parser.add_argument('-ngpus',
'--ngpus',
type=int,
help='number of GPUs',
default=1)
# target transform size
bweff_parser.add_argument('--target_size',
type=int,
help='target transform size in GiB',
default=5)
arguments = parser.parse_args()
logging.basicConfig(filename=arguments.logfile,
format='%(asctime)s %(levelname)s: %(message)s',
level=logging.DEBUG)
console.setLevel(logging.WARNING)
console.setFormatter(logging.Formatter('%(levelname)-8s: %(message)s'))
logging.getLogger('').addHandler(console)
if arguments.verbose:
console.setLevel(logging.INFO)
if arguments.command == 'specs':
# Todo: find unified way to specify device id
print(perflib.specs.get_machine_specs(0, arguments.specs_type))
if arguments.command == 'overview':
print(globals()['__doc__'])
if arguments.command == 'run':
command_run(arguments)
if arguments.command == 'post':
command_post(arguments)
if arguments.command == 'test':
sys.exit(command_test(arguments))
if arguments.command == 'pdf':
command_generate(arguments)
if arguments.command == 'html':
command_generate(arguments)
if arguments.command == 'docx':
command_generate(arguments)
if arguments.command == 'autoperf':
command_autoperf(arguments)
if arguments.command == 'bweff':
command_bweff(arguments)
sys.exit(0)
if __name__ == '__main__':
main()