From 590bb1fbbb4b311211cf1c1125fbf8d66082cb11 Mon Sep 17 00:00:00 2001 From: Mahyar Samani Date: Fri, 21 Jun 2024 02:23:41 -0700 Subject: [PATCH] Adding an example for Spatter (#1272) This change adds a new utility function for processing Spatter traces into SpatterKernels under parse_kernels. Additionally, it adds documentation for all the utility functions in spatter_kernel.py. Lastly, it adds an example script for running one spatter trace using SpatterGenerator to the examples. --- .../spatter_gen/spatter-gen-test.py | 97 +++++++ .../gem5_library/spatter_gen/traces/amg.json | 1 + .../processors/spatter_gen/__init__.py | 2 + .../processors/spatter_gen/spatter_kernel.py | 263 +++++++++++++++--- 4 files changed, 319 insertions(+), 44 deletions(-) create mode 100644 configs/example/gem5_library/spatter_gen/spatter-gen-test.py create mode 100644 configs/example/gem5_library/spatter_gen/traces/amg.json diff --git a/configs/example/gem5_library/spatter_gen/spatter-gen-test.py b/configs/example/gem5_library/spatter_gen/spatter-gen-test.py new file mode 100644 index 0000000000..ef0cc04aa5 --- /dev/null +++ b/configs/example/gem5_library/spatter_gen/spatter-gen-test.py @@ -0,0 +1,97 @@ +# Copyright (c) 2024 The Regents of the University of California +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +Script that runs a SpatterGen test with a specific trace file. +This script can be used as an example on how to use SpatterGenerator, +SpatterKernel, and its utilities to run a Spatter trace in gem5. + +The script uses a spatter trace taken from the hpcgarage github repository. +Link to the original trace file: + +https://github.com/hpcgarage/spatter/blob/main/standard-suite/app-traces/amg.json + +It will create a system with `num_cores` SpatterGenerators and interleave the +trace by `intlv_size` elements in the `pattern` field from the trace. +Interleaving is done for assigning part of the access to each core. + +Usage: +------ + +``` +scons build/NULL/gem5.opt +./build/NULL/gem5.opt configs/example/gem5_library/spatter_gen/spatter-gen-test.py +``` +""" +import argparse +import json +from pathlib import Path + +import m5 +from m5.objects import Root + +from gem5.components.boards.test_board import TestBoard +from gem5.components.cachehierarchies.classic.private_l1_cache_hierarchy import ( + PrivateL1CacheHierarchy, +) +from gem5.components.memory import DualChannelDDR4_2400 +from gem5.components.processors.spatter_gen import ( + SpatterGenerator, + prepare_kernels, +) +from gem5.simulate.simulator import Simulator + +num_cores = 8 +intlv_size = 128 + +memory = DualChannelDDR4_2400(size="8GiB") + +generator = SpatterGenerator( + processing_mode="synchronous", num_cores=num_cores +) + +kernels = prepare_kernels( + Path(__file__).parent / "traces/amg.json", + num_cores, + intlv_size, + 0, + memory.get_size() // 2, +) +for kernel in kernels: + generator.add_kernel(kernel) + +board = TestBoard( + clk_freq="4GHz", + generator=generator, + cache_hierarchy=PrivateL1CacheHierarchy( + l1d_size="32KiB", l1i_size="32KiB" + ), + memory=memory, +) + +simulator = Simulator(board=board, full_system=False) + +simulator.run() diff --git a/configs/example/gem5_library/spatter_gen/traces/amg.json b/configs/example/gem5_library/spatter_gen/traces/amg.json new file mode 100644 index 0000000000..64da33a2e1 --- /dev/null +++ b/configs/example/gem5_library/spatter_gen/traces/amg.json @@ -0,0 +1 @@ +[{"delta": 1, "kernel": "Gather", "pattern": [1333, 0, 1, 2, 36, 37, 38, 72, 73, 74, 1296, 1297, 1298, 1332, 1334, 1368], "count": 1454647}, {"delta": 1, "kernel": "Gather", "pattern": [1333, 0, 1, 36, 37, 72, 73, 1296, 1297, 1332, 1368, 1369, 2592, 2593, 2628, 2629], "count": 1454647}] diff --git a/src/python/gem5/components/processors/spatter_gen/__init__.py b/src/python/gem5/components/processors/spatter_gen/__init__.py index 3c1847b914..25c603781f 100644 --- a/src/python/gem5/components/processors/spatter_gen/__init__.py +++ b/src/python/gem5/components/processors/spatter_gen/__init__.py @@ -30,4 +30,6 @@ from .spatter_kernel import ( SpatterKernel, parse_kernel, partition_trace, + prepare_kernels, + unroll_trace, ) diff --git a/src/python/gem5/components/processors/spatter_gen/spatter_kernel.py b/src/python/gem5/components/processors/spatter_gen/spatter_kernel.py index 8b79b685c5..d48b620cc1 100644 --- a/src/python/gem5/components/processors/spatter_gen/spatter_kernel.py +++ b/src/python/gem5/components/processors/spatter_gen/spatter_kernel.py @@ -24,7 +24,9 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import json from math import ceil +from pathlib import Path from typing import ( List, Tuple, @@ -35,36 +37,6 @@ from m5.params import Addr from m5.util import inform -def parse_kernel(kernel: dict, default_delta=8) -> Tuple[int, int, str, List]: - delta = kernel.get("delta", default_delta) - if delta < 0: - inform( - f"Negative delta found: {delta}. Setting it to {default_delta}." - ) - delta = default_delta - count = kernel.get("count", 1) - type = kernel.get("kernel", None) - if type is None: - raise ValueError(f"Keyword 'kernel' not found.") - type = SpatterKernelType(type.lower()) - trace = kernel.get("pattern", []) - if len(trace) == 0: - raise ValueError(f"Empty 'pattern' found.") - return (delta, count, type, trace) - - -def partition_trace(original_trace, num_partitions, interleave_size): - partitions = [[] for _ in range(num_partitions)] - num_leaves = ceil(len(original_trace) / interleave_size) - for i in range(num_leaves): - lower_bound = i * interleave_size - upper_bound = min(lower_bound + interleave_size, len(original_trace)) - partitions[i % num_partitions] += original_trace[ - lower_bound:upper_bound - ] - return partitions - - class SpatterKernel: """This class encapsulates one kernel in a spatter trace. A spatter trace is represented with a json file. @@ -85,15 +57,30 @@ class SpatterKernel: are loads or stores. The field `pattern` stores the index array. - This file provides two utility function to parse spatter traces: + This file provides four utility function to parse spatter traces: parse_kernel: takes a dictionary and returns a tuple of delta, count, type, and trace. - partition_trace: takes the original trace, number of partitions, + partition_trace: takes an original trace, number of partitions, and interleave_size. - It returns a list of `num_partitions` partitions where each partition - is an list including interleaved elements from `original_trace`. + It returns a list of `num_partitions` partitions where each + partition includes interleaved elements from `original_trace`. The elements in the `original_trace` are interleaved with a granularity of `interleave_size`. + unroll_trace: takes an original trace, delta, count, and minimum + number of elements. + It will unroll `original_trace` by adding `delta` to the last + `og_len` (len(`original_trace`)) elements of the trace in steps. + In each step it will decrement `count` by 1. + If the logical length of `original_trace` (`og_len` * `count`) is + smaller than `min_elements`, it allows for filling the trace with + zeros or elements from the pattern. + By filling elements from the pattern, the unrolling process goes + beyond the `count` limit. + However, it will not decrements `count`. + prepare_kernels: takes a trace_path, number of cores, + interleave_size, base_index_addr, and base_value_addr. + It will return a list of lists of kernels where each list of + kernels represents a kernel with a length of `num_cores`. The code snippet below shows how to use these functions to create kernels. generator = SpatterGenerator(num_cores) @@ -128,6 +115,15 @@ class SpatterKernel: `count` from spatter trace. kernel_type (SpatterKernelType): The type of the kernel. `kernel` from spatter trace. + base_index (int): The index from the index array to start from. + It's most meaningful when used in multi-generator simulations. + User defined, i.e. spatter traces don't have this field. + indices_per_stride (int): The number of indices from the index + array to read from before making a jump of size `stride_size`. + User defined, i.e. spatter traces don't have this field. + stride_size (int): The size of the jump to make after reading + `indices_per_stride` indices from the index array. + User defined, i.e. spatter traces don't have this field. kernel_trace (List[int]): The elements of the `index` array. `pattern` from spatter trace. index_size (int): The size of elements in `index`. @@ -158,7 +154,6 @@ class SpatterKernel: value_size: int, base_value_addr: Addr, kernel_trace: List[int], - fix_empty_trace: bool = False, ): self._id = kernel_id self._delta = kernel_delta @@ -173,15 +168,6 @@ class SpatterKernel: self._base_value_addr = base_value_addr self._trace = kernel_trace - if fix_empty_trace and len(kernel_trace) == 0: - inform( - "Empty trace found. Fixing it by adding a dummy element. " - "Also setting delta to 0 and count to 1.", - ) - self._trace = [0] - self._delta = 0 - self._count = 1 - def empty(self): return len(self._trace) == 0 @@ -207,3 +193,192 @@ class SpatterKernel: f"count={self._count}, type={self._type}, " f"trace[:8]={self._trace[:8]}" ) + + +def parse_kernel(kernel: dict, default_delta=8) -> Tuple[int, int, str, List]: + """ + Function to parse a kernel from a dictionary. Each Spatter trace is + represented as a list of dictionaries in JSON. Each dictionary in the list + represents a kernel. This function will one kernel and return a tuple of + delta, count, type, and trace. + Args: + kernel (dict): A dictionary representing a kernel. + default_delta (int): The default delta value to use when the delta + value is not found in the kernel dictionary. + Returns: + Tuple[int, int, str, List]: A tuple of delta, count, type, + and trace extracted from the kernel. + """ + delta = kernel.get("delta", default_delta) + if delta < 0: + inform( + f"Negative delta found: {delta}. Setting it to {default_delta}. " + "You can change the default delta value by passing " + "`default_detla` argument to this function." + ) + delta = default_delta + count = kernel.get("count", 1) + type = kernel.get("kernel", None) + if type is None: + raise ValueError(f"Keyword 'kernel' not found.") + type = SpatterKernelType(type.lower()) + trace = kernel.get("pattern", []) + if len(trace) == 0: + raise ValueError(f"Empty 'pattern' found.") + return (delta, count, type, trace) + + +def unroll_trace( + original_trace: List, + delta: int, + count: int, + min_elements: int, + fill_zero=False, + fill_pattern=False, +): + """ + Function to unroll a trace by creating replicated elements in the trace. + This function will add `delta` to the last `og_len` (len(`original_trace`)) + elements of the trace in steps. In each step it will decrement `count`. + If the logical length of `original_trace` (`og_len` * `count`) is smaller + than `min_elements`, it allows for filling the trace with zeros or elements + from the pattern. By filling elements from the pattern, the unrolling + process goes beyond the `count` limit. However, it will not decrement + `count`. + + Args: + original_trace (List): The original trace to unroll. + delta (int): The delta value as provided from the kernel in JSON. + count (int): The count value as provided from the kernel in JSON. + min_elements (int): The minimum number of elements the trace should + have after unrolling. + fill_zero (bool): If True, the trace will be filled with zeros when + the unrolling process runs out of elements from the original trace. + fill_pattern (bool): If True, the trace will be filled with the pattern + allowing to go over the `count` limit (from the kernel in JSON) when + unrolling. + """ + if fill_zero and fill_pattern: + raise ValueError( + f"Only one of fill_zero or fill_pattern can be True. " + "However, both can be False at the same time." + ) + + if (len(original_trace) * count) < min_elements and ( + not fill_zero and not fill_pattern + ): + raise ValueError( + f"Trace is too small (len(`pattern`) * `count`) < {min_elements}. " + f"It will not have {min_elements} elements after unrolling. " + "You can set fill_zero or fill_pattern to True to fill pattern. " + "fill_zero will fill with zeros when the unrolling process runs " + "out of elements from the original trace. " + "fill_pattern will fill with the pattern allowing to go over the " + "`count` limit (from the kernel in JSON) when unrolling." + ) + + og_len = len(original_trace) + ret_count = count + ret_trace = original_trace + while (len(ret_trace) < min_elements) and ret_count > 1: + ret_trace += [element + delta for element in ret_trace[-og_len:]] + ret_count -= 1 + if (len(ret_trace) < min_elements) and fill_zero: + inform( + "You have chosen to fill the trace with zero " + f"until it reaches at least {min_elements} elements." + ) + ret_trace += [0] * (min_elements - len(ret_trace)) + if (len(ret_trace) < min_elements) and fill_pattern: + inform( + "You have chosen to fill the trace with the pattern " + "(without dectementing count) until it " + f"reaches at least {min_elements} elements." + ) + while len(ret_trace) < min_elements: + ret_trace += [element + delta for element in ret_trace[-og_len:]] + return ret_count, ret_trace + + +def partition_trace(original_trace, num_partitions, interleave_size): + if len(original_trace) < (num_partitions * interleave_size): + raise ValueError( + "Trace (`original_trace`) is too small for the " + "given number of partitions and interleave size. " + "The trace (`original_trace`) should have at least " + "`num_partitions` * `interleave_size` elements." + "It might be due to either the trace being too small " + "or it being folded too many times. You can solve " + "this issue by using the `unroll_trace` function. " + ) + partitions = [[] for _ in range(num_partitions)] + num_leaves = ceil(len(original_trace) / interleave_size) + for i in range(num_leaves): + lower_bound = i * interleave_size + upper_bound = min(lower_bound + interleave_size, len(original_trace)) + partitions[i % num_partitions] += original_trace[ + lower_bound:upper_bound + ] + return partitions + + +def prepare_kernels( + trace_path: Path, + num_cores: int, + interleave_size: int, + base_index_addr: Addr, + base_value_addr: Addr, +) -> List[List[SpatterKernel]]: + """ + Function to prepare kernels from a spatter trace. It will read the trace + from the given path and prepare kernels for the given number of cores and + interleave size. It will partition the trace into `num_cores` partitions + with `interleave_size` elements in each partition. It will also unroll the + trace to have at least `num_cores` * `interleave_size` elements in the + trace using the `unroll_trace` function. In case the trace is too small, + it will ask `unroll_trace` to fill the trace with elements from the + pattern. It will return a list of list of kernels where each list of + kernels represents a kernel with a length of `num_cores`. + Args: + trace_path (Path): Path to the spatter trace. + num_cores (int): Number of cores to partition the trace. + interleave_size (int): Number of elements to interleave the trace by. + base_index_addr (Addr): The base address of the index array. + base_value_addr (Addr): The base address of the value array. + Returns: + List[List[SpatterKernel]]: A list of list of kernels where each list + of kernels represents a kernel with a length of `num_cores`. + """ + trace_file = trace_path.open("r") + kernels = json.load(trace_file) + ret = [] + for i, kernel in enumerate(kernels): + delta, count, type, og_trace = parse_kernel(kernel) + new_count, unrolled_trace = unroll_trace( + og_trace, + delta, + count, + num_cores * interleave_size, + fill_pattern=True, + ) + traces = partition_trace(unrolled_trace, num_cores, interleave_size) + temp = [] + for j, trace in enumerate(traces): + temp.append( + SpatterKernel( + kernel_id=i, + kernel_delta=delta, + kernel_count=new_count, + kernel_type=type, + base_index=j * interleave_size, + indices_per_stride=interleave_size, + stride_size=interleave_size * num_cores, + index_size=4, + base_index_addr=base_index_addr, + value_size=8, + base_value_addr=base_value_addr, + kernel_trace=trace, + ) + ) + ret.append(temp) + return ret