Skip to content
Snippets Groups Projects
kerncraft_interface.py 8.15 KiB
Newer Older
Martin Bauer's avatar
Martin Bauer committed
from tempfile import TemporaryDirectory
Martin Bauer's avatar
Martin Bauer committed
import sympy as sp
Martin Bauer's avatar
Martin Bauer committed
import os
Martin Bauer's avatar
Martin Bauer committed
from collections import defaultdict
Martin Bauer's avatar
Martin Bauer committed
import subprocess
import kerncraft
Martin Bauer's avatar
Martin Bauer committed
import kerncraft.kernel
from typing import Optional
from kerncraft.iaca import iaca_analyse_instrumented_binary, iaca_instrumentation
from kerncraft.machinemodel import MachineModel

Martin Bauer's avatar
Martin Bauer committed
from pystencils.kerncraft_coupling.generate_benchmark import generate_benchmark
from pystencils.astnodes import LoopOverCoordinate, SympyAssignment, ResolvedFieldAccess, KernelFunction
Martin Bauer's avatar
Martin Bauer committed
from pystencils.field import get_layout_from_strides
Martin Bauer's avatar
Martin Bauer committed
from pystencils.sympyextensions import count_operations_in_ast
Martin Bauer's avatar
Martin Bauer committed
from pystencils.utils import DotDict


class PyStencilsKerncraftKernel(kerncraft.kernel.Kernel):
    """
    Implementation of kerncraft's kernel interface for pystencils CPU kernels.
    Analyses a list of equations assuming they will be executed on a CPU
    """
Martin Bauer's avatar
Martin Bauer committed
    LIKWID_BASE = '/usr/local/likwid'

    def __init__(self, ast: KernelFunction, machine: Optional[MachineModel] = None, assumed_layout='SoA'):
        """Create a kerncraft kernel using a pystencils AST

        Args:
            ast: pystencils ast
            machine: kerncraft machine model - specify this if kernel needs to be compiled
            assumed_layout: either 'SoA' or 'AoS' - if fields have symbolic sizes the layout of the index coordinates is not
                    known. In this case either a structures of array (SoA) or array of structures (AoS) layout
                    is assumed
        """
Jan Hönig's avatar
Jan Hönig committed
        super(PyStencilsKerncraftKernel, self).__init__(machine)
Martin Bauer's avatar
Martin Bauer committed

        self.ast = ast
Martin Bauer's avatar
Martin Bauer committed
        self.temporary_dir = TemporaryDirectory()
Martin Bauer's avatar
Martin Bauer committed

        # Loops
Martin Bauer's avatar
Martin Bauer committed
        inner_loops = [l for l in ast.atoms(LoopOverCoordinate) if l.is_innermost_loop]
        if len(inner_loops) == 0:
Martin Bauer's avatar
Martin Bauer committed
            raise ValueError("No loop found in pystencils AST")
Martin Bauer's avatar
Martin Bauer committed
        elif len(inner_loops) > 1:
Martin Bauer's avatar
Martin Bauer committed
            raise ValueError("pystencils AST contains multiple inner loops - only one can be analyzed")
        else:
Martin Bauer's avatar
Martin Bauer committed
            inner_loop = inner_loops[0]
Martin Bauer's avatar
Martin Bauer committed

        self._loop_stack = []
Martin Bauer's avatar
Martin Bauer committed
        cur_node = inner_loop
        while cur_node is not None:
            if isinstance(cur_node, LoopOverCoordinate):
Martin Bauer's avatar
Martin Bauer committed
                loop_counter_sym = cur_node.loop_counter_symbol
                loop_info = (loop_counter_sym.name, cur_node.start, cur_node.stop, cur_node.step)
                self._loop_stack.append(loop_info)
Martin Bauer's avatar
Martin Bauer committed
            cur_node = cur_node.parent
Martin Bauer's avatar
Martin Bauer committed
        self._loop_stack = list(reversed(self._loop_stack))

        # Data sources & destinations
        self.sources = defaultdict(list)
        self.destinations = defaultdict(list)
Martin Bauer's avatar
Martin Bauer committed

        def get_layout_tuple(f):
            if f.has_fixed_shape:
                return get_layout_from_strides(f.strides)
            else:
                layout_list = list(f.layout)
                for _ in range(f.index_dimensions):
                    layout_list.insert(0 if assumed_layout == 'SoA' else -1, max(layout_list) + 1)
                return layout_list

Martin Bauer's avatar
Martin Bauer committed
        reads, writes = search_resolved_field_accesses_in_ast(inner_loop)
Martin Bauer's avatar
Martin Bauer committed
        for accesses, target_dict in [(reads, self.sources), (writes, self.destinations)]:
Martin Bauer's avatar
Martin Bauer committed
            for fa in accesses:
Martin Bauer's avatar
Martin Bauer committed
                coord = [sp.Symbol(LoopOverCoordinate.get_loop_counter_name(i), positive=True, integer=True) + off
Martin Bauer's avatar
Martin Bauer committed
                         for i, off in enumerate(fa.offsets)]
Martin Bauer's avatar
Martin Bauer committed
                coord += list(fa.idx_coordinate_values)
                layout = get_layout_tuple(fa.field)
                permuted_coord = [sp.sympify(coord[i]) for i in layout]
Martin Bauer's avatar
Martin Bauer committed
                target_dict[fa.field.name].append(permuted_coord)
Martin Bauer's avatar
Martin Bauer committed

        # Variables (arrays)
Martin Bauer's avatar
Martin Bauer committed
        fields_accessed = ast.fields_accessed
        for field in fields_accessed:
            layout = get_layout_tuple(field)
Martin Bauer's avatar
Martin Bauer committed
            permuted_shape = list(field.shape[i] for i in layout)
            self.set_variable(field.name, str(field.dtype), tuple(permuted_shape))
Martin Bauer's avatar
Martin Bauer committed

        for param in ast.get_parameters():
            if not param.is_field_parameter:
                self.set_variable(param.symbol.name, str(param.symbol.dtype), None)
                self.sources[param.symbol.name] = [None]
Martin Bauer's avatar
Martin Bauer committed

        # data type
        self.datatype = list(self.variables.values())[0][0]

        # flops
        # FIXME operation_count
Martin Bauer's avatar
Martin Bauer committed
        operation_count = count_operations_in_ast(inner_loop)
Martin Bauer's avatar
Martin Bauer committed
        self._flops = {
Martin Bauer's avatar
Martin Bauer committed
            '+': operation_count['adds'],
            '*': operation_count['muls'],
            '/': operation_count['divs'],
Martin Bauer's avatar
Martin Bauer committed
        }
Jan Hönig's avatar
Jan Hönig committed
        for k in [k for k, v in self._flops.items() if v == 0]:
            del self._flops[k]
Martin Bauer's avatar
Martin Bauer committed
        self.check()

Jan Hönig's avatar
Jan Hönig committed
    def iaca_analysis(self, micro_architecture, asm_block='auto',
                      pointer_increment='auto_with_manual_fallback', verbose=False):
        compiler, compiler_args = self._machine.get_compiler()
Martin Bauer's avatar
Martin Bauer committed
        if '-std=c99' not in compiler_args:
            compiler_args += ['-std=c99']
Martin Bauer's avatar
Martin Bauer committed
        header_path = kerncraft.get_header_path()
Martin Bauer's avatar
Martin Bauer committed

Martin Bauer's avatar
Martin Bauer committed
        compiler_cmd = [compiler] + compiler_args + ['-I' + header_path]
Martin Bauer's avatar
Martin Bauer committed

Martin Bauer's avatar
Martin Bauer committed
        src_file = os.path.join(self.temporary_dir.name, "source.c")
        asm_file = os.path.join(self.temporary_dir.name, "source.s")
        iaca_asm_file = os.path.join(self.temporary_dir.name, "source.iaca.s")
Martin Bauer's avatar
Martin Bauer committed
        dummy_src_file = os.path.join(header_path, "dummy.c")
Martin Bauer's avatar
Martin Bauer committed
        dummy_asm_file = os.path.join(self.temporary_dir.name, "dummy.s")
        binary_file = os.path.join(self.temporary_dir.name, "binary")
Martin Bauer's avatar
Martin Bauer committed

        # write source code to file
Martin Bauer's avatar
Martin Bauer committed
        with open(src_file, 'w') as f:
            f.write(generate_benchmark(self.ast, likwid=False))
Martin Bauer's avatar
Martin Bauer committed

        # compile to asm files
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(compiler_cmd + [src_file, '-S', '-o', asm_file])
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(compiler_cmd + [dummy_src_file, '-S', '-o', dummy_asm_file])
Martin Bauer's avatar
Martin Bauer committed
        with open(asm_file) as read, open(iaca_asm_file, 'w') as write:
            instrumented_asm_block = iaca_instrumentation(read, write)
Martin Bauer's avatar
Martin Bauer committed

        # assemble asm files to executable
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(compiler_cmd + [iaca_asm_file, dummy_asm_file, '-o', binary_file])
Martin Bauer's avatar
Martin Bauer committed
        result = iaca_analyse_instrumented_binary(binary_file, micro_architecture)
Martin Bauer's avatar
Martin Bauer committed
        return result, instrumented_asm_block
Jan Hönig's avatar
Jan Hönig committed
    def build(self, lflags=None, verbose=False, openmp=False):
        # TODO do we use openmp or not???
Jan Hönig's avatar
Jan Hönig committed
        compiler, compiler_args = self._machine.get_compiler()
Martin Bauer's avatar
Martin Bauer committed
        if '-std=c99' not in compiler_args:
            compiler_args.append('-std=c99')
Martin Bauer's avatar
Martin Bauer committed
        header_path = kerncraft.get_header_path()
Martin Bauer's avatar
Martin Bauer committed

        cmd = [compiler] + compiler_args + [
            '-I' + os.path.join(self.LIKWID_BASE, 'include'),
            '-L' + os.path.join(self.LIKWID_BASE, 'lib'),
Martin Bauer's avatar
Martin Bauer committed
            '-I' + header_path,
Martin Bauer's avatar
Martin Bauer committed
            '-Wl,-rpath=' + os.path.join(self.LIKWID_BASE, 'lib'),
        ]

Martin Bauer's avatar
Martin Bauer committed
        dummy_src_file = os.path.join(header_path, 'dummy.c')
Martin Bauer's avatar
Martin Bauer committed
        src_file = os.path.join(self.temporary_dir.name, "source_likwid.c")
        bin_file = os.path.join(self.temporary_dir.name, "benchmark")
Martin Bauer's avatar
Martin Bauer committed
        with open(src_file, 'w') as f:
            f.write(generate_benchmark(self.ast, likwid=True))
Martin Bauer's avatar
Martin Bauer committed
        subprocess.check_output(cmd + [src_file, dummy_src_file, '-pthread', '-llikwid', '-o', bin_file])
        return bin_file
Martin Bauer's avatar
Martin Bauer committed

class KerncraftParameters(DotDict):
Martin Bauer's avatar
Martin Bauer committed
    def __init__(self, **kwargs):
        super(KerncraftParameters, self).__init__(**kwargs)
Martin Bauer's avatar
Martin Bauer committed
        self['asm_block'] = 'auto'
        self['asm_increment'] = 0
        self['cores'] = 1
        self['cache_predictor'] = 'SIM'
        self['verbose'] = 0
Jan Hönig's avatar
Jan Hönig committed
        self['pointer_increment'] = 'auto'
Jan Hönig's avatar
Jan Hönig committed
        self['iterations'] = 10
        self['unit'] = 'cy/CL'
        self['ignore_warnings'] = True
Martin Bauer's avatar
Martin Bauer committed

# ------------------------------------------- Helper functions ---------------------------------------------------------


Martin Bauer's avatar
Martin Bauer committed
def search_resolved_field_accesses_in_ast(ast):
Martin Bauer's avatar
Martin Bauer committed
    def visit(node, reads, writes):
        if not isinstance(node, SympyAssignment):
            for a in node.args:
                visit(a, reads, writes)
            return

        for expr, accesses in [(node.lhs, writes), (node.rhs, reads)]:
            accesses.update(expr.atoms(ResolvedFieldAccess))

Martin Bauer's avatar
Martin Bauer committed
    read_accesses = set()
    write_accesses = set()
    visit(ast, read_accesses, write_accesses)
Martin Bauer's avatar
Martin Bauer committed
    return read_accesses, write_accesses