Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 00685293 authored by GRUBER Fabian's avatar GRUBER Fabian
Browse files

ILP model: save latest version.

parent 9386c13a
No related branches found
No related tags found
No related merge requests found
import collections
import datetime
import dataclasses
import datetime
import enum
import functools
import heapq
......@@ -9,8 +9,12 @@ import io
import json
import math
import numpy
import pathlib
import sys
import typing as ty
import msgpack
from pipedream.utils import yaml
from pipedream.utils.statistics import Statistics
from pipedream.asm import ir
......@@ -467,6 +471,38 @@ class Benchmark_Run(yaml.YAML_Struct):
return frozenset(self.ports_usage(min_usage).keys())
def to_json(self) -> str:
"""
Convert to JSON string (one line of text)
"""
return json.dumps(self.to_jsonish())
@staticmethod
def from_json(txt) -> 'Benchmark_Run':
"""
Parse from JSON string
"""
data = json.loads(txt)
assert type(data) is dict
return Benchmark_Run.from_jsonish(data)
def write_msgpack(self, fd: ty.IO[str]):
"""
Write to file in msgpack format
"""
msgpack.pack(self.to_jsonish(), fd)
@staticmethod
def read_msgpack(unpacker: msgpack.Unpacker) -> 'Benchmark_Run':
"""
Read from file in msgpack format
"""
data = unpacker.unpack()
assert type(data) is dict
return Benchmark_Run.from_jsonish(data)
def to_jsonish(self) -> dict:
out = {}
for slot in self._yaml_slots_:
......@@ -489,13 +525,10 @@ class Benchmark_Run(yaml.YAML_Struct):
out[slot.yaml_name] = val
return json.dumps(out)
return out
@staticmethod
def from_json(txt) -> 'Benchmark_Run':
data = json.loads(txt)
assert type(data) is dict
def from_jsonish(data: dict) -> 'Benchmark_Run':
out = {}
for slot in Benchmark_Run._yaml_slots_:
......@@ -696,6 +729,142 @@ class Benchmark_Run_Aggregator:
def __len__(self) -> int:
return sum(len(heap) for heap in self._measurements.values())
######################################################################################################################
##### I/O
class File_Format(enum.Enum):
JSONL = '.jsonl'
MSGPACK = '.msgpack'
def read_from_file(self, path: pathlib.Path, format: File_Format = None):
"""
Read measurements from file.
If not given, file format is detected from the suffix of :path:. If it has no known prefix we default to JSONL
"""
if format is None:
if path.suffix == '.jsonl':
format = self.File_Format.JSONL
elif path.suffix == '.msgpack':
format = self.File_Format.MSGPACK
else:
format = self.File_Format.JSONL
if format == self.File_Format.JSONL:
with open(path, 'r') as fd:
return self.read_jsonl_file(fd)
elif format == self.File_Format.MSGPACK:
with open(path, 'rb') as fd:
return self.read_msgpack_file(fd)
else:
print('error: unknown file format:', repr(str(path)), file=sys.stderr)
exit(1)
def write_to_file(self, path: pathlib.Path, format: File_Format = None, only_best: bool = False):
"""
Write measurements to file.
If not given, file format is detected from the suffix of :path:. If it has no known prefix we default to JSONL
"""
if format is None:
if path.suffix == '.jsonl':
format = self.File_Format.JSONL
elif path.suffix == '.msgpack':
format = self.File_Format.MSGPACK
else:
format = self.File_Format.JSONL
if format == self.File_Format.JSONL:
with open(path, 'w') as fd:
return self.write_jsonl_file(fd, only_best=only_best)
elif format == self.File_Format.MSGPACK:
with open(path, 'wb') as fd:
return self.write_msgpack_file(fd, only_best=only_best)
else:
print('error: unknown file format:', repr(str(path)), file=sys.stderr)
exit(1)
def read_jsonl_file(self, fd: ty.IO[str]) -> int:
"""
Read measurements from file in JSONL format (one json dict per line of text)
Returns the number of records read
"""
from_json = Benchmark_Run.from_json
add_measurement = self.add_measurement
i = 0
for line in fd:
add_measurement(from_json(line))
i += 1
return i
def write_jsonl_file(self, fd: ty.IO[str], only_best: bool = False) -> int:
"""
Write measurements to file in JSONL format (one json dict per line of text)
If :only_best: is true only the run with the best unfused MPC for each benchmark will be stored
Returns the number of records written
"""
to_jsonish = Benchmark_Run.to_jsonish
dump = json.dump
write = fd.write
runs = iter(self) if only_best else self.all_measurements()
i = 0
for run in runs:
dump(to_jsonish(run), fd, check_circular=False)
write('\n')
i += 1
return i
def read_msgpack_file(self, fd: ty.IO[bytes]) -> int:
"""
Read measurements from file in msgpack binary format
Returns the number of records read
"""
from_jsonish = Benchmark_Run.from_jsonish
add_measurement = self.add_measurement
i = 0
u = msgpack.Unpacker(fd, raw=False)
for obj in u:
add_measurement(from_jsonish(obj))
i += 1
return i
def write_msgpack_file(self, fd: ty.IO[bytes], only_best: bool = False) -> int:
"""
Write measurements to file in msgpack binary format
If :only_best: is true only the run with the best unfused MPC for each benchmark will be stored
Returns the number of records written
"""
to_jsonish = Benchmark_Run.to_jsonish
pack = msgpack.Packer(use_bin_type=True).pack
write = fd.write
runs = iter(self) if only_best else self.all_measurements()
i = 0
for run in runs:
write(pack(to_jsonish(run)))
i += 1
return i
######################################################################################################################
##### private parts
@staticmethod
def _normalize_key(key):
if type(key) is str:
......
......@@ -28,7 +28,7 @@ def main():
)
parser.add_argument('FILE', nargs='*')
parser.add_argument('-o', '--output', default=sys.stdout, type=argparse.FileType('w'))
parser.add_argument('-o', '--output', default=pathlib.Path('/dev/stdout'), type=pathlib.Path)
parser.add_argument(
'--min-stddev',
......@@ -78,8 +78,6 @@ def main():
if not args.FILE:
args.FILE = ['/dev/stdin']
out = args.output
if args.tag:
arch = ir.Architecture.for_name('x86')
inst_set = arch.instruction_set()
......@@ -97,28 +95,14 @@ def main():
predicate=predicate,
)
if args.reduce_stats:
reduce_stats = Benchmark_Run.drop_details
else:
def reduce_stats(run):
pass
for file in args.FILE:
with open(file) as fd:
for line in fd:
run = Benchmark_Run.from_json(line)
measurements.read_from_file(pathlib.Path(file))
reduce_stats(run)
measurements.add_measurement(run)
if args.only_best:
runs = iter(measurements)
else:
runs = measurements.all_measurements()
if args.reduce_stats:
for run in measurements.all_measurements():
run.drop_details()
for run in runs:
print(run.to_json(), file=out)
measurements.write_to_file(args.output, only_best=args.only_best)
if __name__ == '__main__':
......
......@@ -254,19 +254,26 @@ def generate_simple_ilp_input(*,
kernels = [k for k in kernels if tuple(i.name for i in k) not in measurements]
for chunk in chunks(kernels, 1000):
CHUNK_SIZE = 2000
for chunk in chunks(kernels, CHUNK_SIZE):
try:
EQ.make_measurements(chunk, measurements, measure_ports=measure_ports, output=measurements_output)
finally:
os.sched_yield()
log('MEASURE: muI ', f'({len(representatives)})')
make_measurements([(i,) for i in representatives], measure_ports=True)
log('MEASURE: complexI', f'({len(complex_insts)})')
make_measurements([(i,) for i in complex_insts], measure_ports=True)
complex_insts = [i for i in complex_insts if measurements[i.name].num_unfused_muops > 1]
if not complex_insts:
log('no real complexI found')
return
log('MEASURE: muI ', f'({len(representatives)})')
make_measurements([(i,) for i in representatives], measure_ports=True)
def combinations_of_instructions_using_only_ports(ports: ty.FrozenSet['Port']) -> ty.Iterable[ty.Tuple[ir.Instruction,
...]]:
port_sets = set(frozenset(ps) for ps in powerset(ports))
......@@ -560,6 +567,9 @@ def _find_equivalence_classes(*,
print(',', file=fd)
first = False
print(' ', '{', file=fd)
print(' ', ' ', '"avg-ipc": ' + str(numpy.mean([measurements[i].ipc.mean for i in insts])) + ',', file=fd),
print(' ', ' ', '"avg-fused-mpc": ' + str(numpy.mean([measurements[i].fmpc.mean for i in insts])) + ',', file=fd),
print(' ', ' ', '"avg-unfused-mpc": ' + str(numpy.mean([measurements[i].umpc.mean for i in insts])) + ',', file=fd),
print(' ', ' ', '"insts": [' + ', '.join('"' + i + '"' for i in insts) + ']', file=fd)
print(' ', '}', end = '', file=fd)
print(file=fd)
......@@ -959,13 +969,7 @@ def read_measurements_from_files(measurements: Benchmark_Run_Aggregator,
for file in files:
log('READING MEASUREMENTS FROM', shlex.quote(str(file)))
with open(file) as fd:
for line in fd:
run = Benchmark_Run.from_json(line)
run.drop_details()
measurements.add_measurement(run)
measurements.read_from_file(file)
log('FOUND', len(measurements), 'measurement(s)')
......@@ -1007,18 +1011,13 @@ def write_measurements(measurements: Benchmark_Run_Aggregator, file: pathlib.Pat
written = 0
if str(file) in ['/dev/stdout', '/dev/stderr']:
with open(file, 'w') as fd:
for m in measurements.all_measurements():
print(m.to_json(), file=fd)
written += 1
written += measurements.write_to_file(file, only_best=False)
else:
with tempfile.NamedTemporaryFile(mode='w',
prefix='eq-class-measurements.',
suffix='.jsonl',
suffix=file.suffix,
delete=False,) as fd:
for m in measurements.all_measurements():
print(m.to_json(), file=fd)
written += 1
written += measurements.write_to_file(pathlib.Path(fd.name), only_best=False)
os.makedirs(file.parent, exist_ok=True)
if file.exists():
shutil.move(file, file.with_suffix(file.suffix + '.bkp'))
......@@ -1128,7 +1127,9 @@ def test_equivalence(A: ir.Instruction, B: ir.Instruction,
assert MPCs_are_equivalent(runA, runAB, margin), f'MPC({A})={pstat(runA.umpc)} MPC({A} {B})={pstat(runAB.umpc)}'
assert MPCs_are_equivalent(runB, runAB, margin), f'MPC({B})={pstat(runB.umpc)} MPC({A} {B})={pstat(runAB.umpc)}'
if runA.port_muops and runB.port_muops:
USE_PORTS = False
if USE_PORTS and runA.port_muops and runB.port_muops:
def ports_used(run) -> ty.Set[int]:
"return set of ports used by a benchmark run (not by how much each port is used)"
......@@ -1249,8 +1250,8 @@ P_VALUE = 0.05
def IPCs_are_equivalent(a: Benchmark_Run, b: Benchmark_Run, margin: Percent = Percent.FIVE):
assert 0 <= margin <= 1
if round(a.ipc.p75, 2) == round(b.ipc.p75, 2):
return True
# if round(a.ipc.p75, 2) == round(b.ipc.p75, 2):
# return True
margin = min(a.ipc.mean, b.ipc.mean) * float(margin)
......@@ -1260,8 +1261,8 @@ def IPCs_are_equivalent(a: Benchmark_Run, b: Benchmark_Run, margin: Percent = Pe
def MPCs_are_equivalent(a: Benchmark_Run, b: Benchmark_Run, margin: Percent = Percent.FIVE):
assert 0 <= margin <= 1
if round(a.umpc.p75, 2) == round(b.umpc.p75, 2) and round(a.fmpc.p75, 2) == round(b.fmpc.p75, 2):
return True
# if round(a.umpc.p75, 2) == round(b.umpc.p75, 2) and round(a.fmpc.p75, 2) == round(b.fmpc.p75, 2):
# return True
u_margin = min(a.umpc.mean, b.umpc.mean) * float(margin)
f_margin = min(a.fmpc.mean, b.fmpc.mean) * float(margin)
......
......@@ -25,32 +25,9 @@ import pipedream.utils.terminal as terminal
MEASURE_PORTS = True
def main_solve_for_muI_mapping(num_ports: int = 8):
parser = argparse.ArgumentParser()
parser.add_argument('FILE')
parser.add_argument('EQUIVALENCE_CLASSES')
args = parser.parse_args()
measurements = Benchmark_Run_Aggregator(max_stddev=math.inf, min_samples=0)
for F in [args.FILE]:
with open(F) as fd:
try:
for line in fd:
run = Benchmark_Run.from_json(line)
measurements.add_measurement(run)
except json.JSONDecodeError as e:
print('error: malformed file', repr(F) + ':', e, file=sys.stderr)
with open(args.EQUIVALENCE_CLASSES) as fd:
eq_json = json.load(fd)
eq_classes = [eq['insts'] for eq in eq_json]
# muIs = [random.choice(eq) for eq in eq_classes]
muIs = [eq[0] for eq in eq_classes]
def main_solve_for_muI_mapping(measurements: Benchmark_Run_Aggregator,
representatives: ty.List[str], num_ports: int = 8):
muIs = representatives
muI_runs = [measurements[muI] for muI in muIs]
pair_runs = [measurements[a, b] for a, b in itertools.combinations(muIs, 2)]
......@@ -67,9 +44,13 @@ def main_solve_for_muI_mapping(num_ports: int = 8):
delta_IM = {}
delta_MP = {}
delta_IT = {}
print('SELECTED MUOP INSTRUCTIONS', len(muI_runs))
for M in muI_runs:
print(M.name)
for run in list(muI_runs):
assert len(run.kernel) == 1
......@@ -82,7 +63,7 @@ def main_solve_for_muI_mapping(num_ports: int = 8):
slowdown = (mpc < 0.95)
n = ' '.join(run.kernel)
n = run.name
i = Inst(name=n, num_fused_muops=run.num_fused_muops, num_unfused_muops=run.num_unfused_muops)
m = Muop(name=n)
......@@ -111,69 +92,72 @@ def main_solve_for_muI_mapping(num_ports: int = 8):
PORTS.sort(key=lambda p: p.name)
THROTTLES.sort(key=lambda p: p.name)
for run in sorted(muI_runs) + sorted(pair_runs):
assert all(i in NAME_2_INST for i in run.kernel), run.kernel
for run in sorted(muI_runs, key=lambda r: r.name) + sorted(pair_runs, key=lambda r: r.name):
# assert all(i in NAME_2_INST for i in run.kernel), run.kernel
if run in pair_runs:
print(' ', Benchmark_Spec.name_from_instruction_names(run.kernel).ljust(75), ':',
f'IPC({run.ipc.mean:5.3}) MPC({run.umpc.mean:5.3})',
f': {" ".join(str(p) for p in sorted(ports_used(run)))}')
if run in pair_runs:
print(' ', Benchmark_Spec.name_from_instruction_names(run.kernel).ljust(75), ':',
f'IPC({run.ipc.mean:5.3}) MPC({run.umpc.mean:5.3})',
f': {" ".join(str(p) for p in sorted(ports_used(run)))}')
ipc = run.ipc.p90
mpc = run.umpc.p90
ipc = run.ipc.p90
umpc = run.umpc.p90
fmpc = run.fmpc.p90
try:
insts = [NAME_2_INST[i] for i in run.kernel]
except KeyError:
print(NAME_2_INST)
raise
K = Kernel(mpc=mpc, ipc=ipc, insts=insts)
KERNELS += [K]
K = Kernel(insts=insts, ipc=ipc, fmpc=fmpc, umpc=umpc)
KERNELS += [K]
if len(run.kernel) == 1 and run.kernel[0] in complexIs:
KERNELS += [Kernel(mpc=mpc, ipc=ipc, insts=insts)]
# FIXME: ??? how to do this without ports ???
# has_bottleneck = run.umpc.mean >= 0.95
# assert has_bottleneck
has_bottleneck = True
# FIXME: ??? how to do this without ports ???
has_bottleneck = run.umpc >= 0.95
assert has_bottleneck
if has_bottleneck:
kernels_with_bottleneck.add(K)
if has_bottleneck:
kernels_with_bottleneck.add(K)
KERNELS = Domain(Kernel, sorted(KERNELS, key=lambda k: (len(list(k)), str(K))))
INSTS = Domain(Inst, INSTS)
MUOPS = Domain(Muop, MUOPS)
PORTS = Domain(Port, PORTS)
KERNELS = Domain(Kernel, sorted(KERNELS, key=lambda k: (len(list(k)), str(K))))
INSTS = Domain(Inst, INSTS)
MUOPS = Domain(Muop, MUOPS)
PORTS = Domain(Port, PORTS)
if verbose:
print('SOLVE WITH', len(KERNELS), 'KERNELS')
if verbose:
print('SOLVE WITH', len(KERNELS), 'KERNELS')
# print(*[I.name for I in INSTS])
# print(*[repr(str(K)) for K in KERNELS])
# print(*[I.name for I in INSTS])
# print(*[repr(str(K)) for K in KERNELS])
try:
outputs = solve_for_delta(
KERNELS, INSTS, MUOPS, PORTS,
const_delta_im = delta_IM,
const_delta_mp = delta_MP,
kernels_with_bottleneck = kernels_with_bottleneck,
saturation_margin = 0.975,
max_error = 0.01,
min_throughput = 0.05,
# print_iis = True,
print_iis = False,
)
try:
outputs = solve_for_delta(
KERNELS, INSTS, MUOPS, PORTS,
const_delta_im = delta_IM,
const_delta_mp = delta_MP,
kernels_with_bottleneck = kernels_with_bottleneck,
saturation_margin = 0.975,
max_error = 0.01,
min_throughput = 0.05,
# print_iis = True,
print_iis = False,
)
muI_outputs = [o for o in outputs if len(o.kernel) == 1]
# print()
# print('FOUND DECOMPOSITIONS:')
# for o in muI_outputs:
# print(' ', Benchmark_Spec.name_from_instruction_names(i.name for i in o.kernel) + ':',
# o.cpu.merged_muop_str(o.used_muops()))
# print()
ilp_outputs += muI_outputs
all_outputs += outputs
except ILP_Error as e:
# print(e, file=sys.stderr)
raise
muI_outputs = [o for o in outputs if len(o.kernel) == 1]
# print()
# print('FOUND DECOMPOSITIONS:')
# for o in muI_outputs:
# print(' ', Benchmark_Spec.name_from_instruction_names(i.name for i in o.kernel) + ':',
# o.cpu.merged_muop_str(o.used_muops()))
# print()
ilp_outputs += muI_outputs
all_outputs += outputs
except ILP_Error as e:
# print(e, file=sys.stderr)
raise
if not ilp_outputs:
exit()
......@@ -466,69 +450,9 @@ def solve_for_complex_instruction(complexI, muIs, measurements,
raise e
def main_solve_for_complex_instructions():
parser = argparse.ArgumentParser()
parser.add_argument('MEASUREMENTS_FILE')
parser.add_argument('EQUIVALENCE_CLASSES')
args = parser.parse_args()
measurements = Benchmark_Run_Aggregator(max_stddev=math.inf, min_samples=0)
log('READ MEASUREMENTS FROM', args.MEASUREMENTS_FILE)
with open(args.MEASUREMENTS_FILE) as fd:
try:
for line in fd:
run = Benchmark_Run.from_json(line)
measurements.add_measurement(run)
except json.JSONDecodeError as e:
print('error: malformed file', repr(F) + ':', e, file=sys.stderr)
log('READ', len(measurements), 'MEASUREMENTS')
log('READ EQUIVALENCE CLASSES FROM', args.EQUIVALENCE_CLASSES)
with open(args.EQUIVALENCE_CLASSES) as fd:
eq_json = json.load(fd)
eq_classes = [eq['insts'] for eq in eq_json]
# muIs = [random.choice(eq) for eq in eq_classes]
muIs = [eq[0] for eq in eq_classes]
for muI in list(muIs):
run = measurements.get(muI)
if not run:
muIs.remove(muI)
continue
## exclude movups/movaps
if run.umpc.mean >= len(run.ports_used()):
muIs.remove(muI)
continue
## exclude div/sqrt/...
if run.umpc.mean <= 0.5:
muIs.remove(muI)
continue
# muIs = random.sample(muIs, 15)
log('READ', len(muIs), 'SIMPLE INSTRUCTIONS/EQUIVALENCE CLASSES')
## choose muI representatives
if False:
just = max(map(len, muIs))
x = []
for muI in muIs:
m = measurements[muI]
x.append((muI, m.umpc.p90, sorted(m.ports_used())))
x.sort(key=lambda y: [len(y[2]), y[2], y[1]])
for muI, mpc, ports in x:
print(("'" + muI + "',").ljust(just + 3), ' #', 'mpc:', f'{mpc:.3f}', ' ', 'ports:', *sorted(ports))
del x
return
def main_solve_for_complex_instructions(measurements: Benchmark_Run_Aggregator,
representatives: ty.List[str]):
muIs = representatives
complexIs = [
# 'ADC_EAXi32_IMMi32',
......@@ -855,5 +779,72 @@ def log(*msg):
if __name__ == '__main__':
main_solve_for_complex_instructions()
# main_solve_for_muI_mapping()
parser = argparse.ArgumentParser()
parser.set_defaults(command=None)
subps = parser.add_subparsers()
##############################################################################
subp = subps.add_parser('complexI-mapping')
subp.set_defaults(command=main_solve_for_complex_instructions)
subp.add_argument('MEASUREMENTS_FILE', type=pathlib.Path)
subp.add_argument('EQUIVALENCE_CLASSES', type=pathlib.Path)
##############################################################################
subp = subps.add_parser('muI-mapping')
subp.set_defaults(command=main_solve_for_muI_mapping)
subp.add_argument('MEASUREMENTS_FILE', type=pathlib.Path)
subp.add_argument('EQUIVALENCE_CLASSES', type=pathlib.Path)
##############################################################################
args = parser.parse_args()
if not args.command:
parser.error('No command specified')
measurements = Benchmark_Run_Aggregator(max_stddev=math.inf, min_samples=0)
log('READ MEASUREMENTS FROM', args.MEASUREMENTS_FILE)
try:
measurements.read_from_file(args.MEASUREMENTS_FILE)
except json.JSONDecodeError as e:
print('error: malformed file', repr(F) + ':', e, file=sys.stderr)
log('READ', len(measurements), 'MEASUREMENTS')
log('READ EQUIVALENCE CLASSES FROM', args.EQUIVALENCE_CLASSES)
with open(args.EQUIVALENCE_CLASSES) as fd:
eq_json = json.load(fd)
eq_classes = [eq['insts'] for eq in eq_json]
# muIs = [random.choice(eq) for eq in eq_classes]
muIs = [eq[0] for eq in eq_classes]
for muI in list(muIs):
run = measurements.get(muI)
if not run:
muIs.remove(muI)
continue
## exclude movups/movaps
if run.umpc.mean >= len(run.ports_used()):
muIs.remove(muI)
continue
## exclude div/sqrt/...
if run.umpc.mean <= 0.5:
muIs.remove(muI)
continue
# muIs = random.sample(muIs, 15)
log('READ', len(muIs), 'SIMPLE INSTRUCTIONS/EQUIVALENCE CLASSES')
##############################################################################
args.command(measurements, muIs)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment