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.
This commit is contained in:
committed by
Bobby R. Bruce
parent
30bfdc8e52
commit
21bd1c28ab
@@ -30,4 +30,6 @@ from .spatter_kernel import (
|
||||
SpatterKernel,
|
||||
parse_kernel,
|
||||
partition_trace,
|
||||
prepare_kernels,
|
||||
unroll_trace,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user