Skip to content

Commit

Permalink
Remove "core" concept from ZigZag (#89)
Browse files Browse the repository at this point in the history
* update pre-commit version

* move DiGraphWrapper from Stream to ZigZag

* fix typeguard issues

* disable pylint issue with super() method

* add mem sharing group to mem instance

* fix bug: shared_memory_group_id not stored in instance

* define class instance types in class body, to ensure they are included in stubs

* memory_sharing_list -> mem_sharing_list

* change open_yaml return type

* Remove Accelerator layer and rename core into accelerator

* fix typeguard issue'

* make has_same_performance method for all classes that make up an accelerator

* **kwargs should be of type Any
  • Loading branch information
RobinGeens authored Oct 7, 2024
1 parent cd78c23 commit cdfa78e
Show file tree
Hide file tree
Showing 23 changed files with 250 additions and 263 deletions.
2 changes: 1 addition & 1 deletion example.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from zigzag.visualization.results.print_mapping import print_mapping

model = "resnet"
workload_path = "zigzag/inputs/workload/resnet18.yaml" # or "zigzag/inputs/workload/resnet18.onnx"
workload_path = "zigzag/inputs/workload/resnet18.onnx" # or "zigzag/inputs/workload/resnet18.yaml"
accelerator_path = "zigzag/inputs/hardware/tpu_like.yaml"
mapping_path = "zigzag/inputs/mapping/tpu_like.yaml"
pickle_filename = f"outputs/{model}-saved_list_of_cmes.pickle"
Expand Down
39 changes: 17 additions & 22 deletions zigzag/cost_model/cost_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class MemoryUtilization(TypedDict):
class CostModelEvaluationABC(metaclass=ABCMeta):
"""! Superclass for CostModelEvaluation and CumulativeCME"""

accelerator: Accelerator

@abstractmethod
def __init__(self) -> None:
# Attributes that all subclasses should define
Expand All @@ -54,6 +56,10 @@ def __init__(self) -> None:

self.accelerator: Accelerator | None

@property
def core(self):
return self.accelerator

def __add__(self, other: "CostModelEvaluationABC") -> "CumulativeCME":
result = CumulativeCME()

Expand Down Expand Up @@ -130,10 +136,8 @@ def __add__(self, other: "CostModelEvaluationABC") -> "CumulativeCME":
for cme in (self, other):
if isinstance(cme, CostModelEvaluation):
result.layer_ids.append(cme.layer.id)
result.core_ids.append(cme.core_id)
elif isinstance(cme, CumulativeCME):
result.layer_ids += cme.layer_ids
result.core_ids += cme.core_ids

# Select accelerator for result
if isinstance(self, CumulativeCME) and self.accelerator is None:
Expand Down Expand Up @@ -252,7 +256,6 @@ def __init__(self):
self.memory_word_access: dict[LayerOperand, list[MemoryAccesses]] = dict()

self.layer_ids: list[int] = []
self.core_ids: list[int] = []

self.mac_energy: float = 0.0
self.mem_energy: float = 0.0
Expand Down Expand Up @@ -307,25 +310,22 @@ def __init__(
@param layer the layer to run
@param access_same_data_considered_as_no_access (optional)
"""
self.accelerator: Accelerator = accelerator # type: ignore
self.accelerator = accelerator
self.layer: LayerNode = layer
self.spatial_mapping = spatial_mapping
self.spatial_mapping_int = spatial_mapping_int # the original spatial mapping without decimal
self.temporal_mapping = temporal_mapping
self.access_same_data_considered_as_no_access = access_same_data_considered_as_no_access

self.core_id = layer.core_allocation[0]
core = accelerator.get_core(self.core_id)
self.mem_level_list = core.memory_hierarchy.mem_level_list
self.mem_hierarchy_dict = core.mem_hierarchy_dict
self.mem_size_dict = core.mem_size_dict
self.mem_r_bw_dict, self.mem_w_bw_dict = core.get_memory_bw_dict()
self.mem_r_bw_min_dict, self.mem_w_bw_min_dict = core.get_memory_bw_min_dict()
self.mem_sharing_tuple = tuple(tuple(i.items()) for i in core.mem_sharing_list)
self.mem_level_list = accelerator.memory_hierarchy.mem_level_list
self.mem_hierarchy_dict = accelerator.mem_hierarchy_dict
self.mem_size_dict = accelerator.mem_size_dict
self.mem_r_bw_dict, self.mem_w_bw_dict = accelerator.get_memory_bw_dict()
self.mem_r_bw_min_dict, self.mem_w_bw_min_dict = accelerator.get_memory_bw_min_dict()
self.mem_sharing_tuple = tuple(tuple(i.items()) for i in accelerator.mem_sharing_list)
self.memory_operand_links = layer.memory_operand_links

self.cumulative_layer_ids: list[int] = [] # In case the CME results from adding other CMEs together
self.cumulative_core_ids: list[int] = []

# generate the integer spatial mapping from fractional spatial mapping (due to greedy mapping support).
# Later the fractional one is used for calculating energy, and the integer one is used for calculating latency
Expand Down Expand Up @@ -547,8 +547,7 @@ def calc_energy(self) -> None:

def calc_mac_energy_cost(self) -> None:
"""! Calculate the dynamic MAC energy"""
core = self.accelerator.get_core(self.core_id)
operational_array = core.operational_array
operational_array = self.accelerator.operational_array
assert isinstance(
operational_array, OperationalArray
), "This method expects an OperationalArray instance. Otherwise, the method should be overridden in a subclass."
Expand All @@ -563,8 +562,7 @@ def calc_memory_energy_cost(self):
The energy total consumption is saved in self.energy_total.
"""
core = self.accelerator.get_core(self.core_id)
mem_hierarchy = core.memory_hierarchy
mem_hierarchy = self.accelerator.memory_hierarchy

mem_energy_breakdown: dict[LayerOperand, list[float]] = {}
mem_energy_breakdown_further: dict[LayerOperand, list[AccessEnergy]] = {}
Expand Down Expand Up @@ -1112,10 +1110,7 @@ def calc_overall_latency(self, cycles_per_mac: float = 1) -> None:
"""
# the ideal cycle count assuming the MAC array is 100% utilized
ideal_cycle = int(
ceil(
self.layer.total_mac_count / self.accelerator.get_core(self.core_id).operational_array.total_unit_count
)
* cycles_per_mac
ceil(self.layer.total_mac_count / self.accelerator.operational_array.total_unit_count) * cycles_per_mac
)

# the ideal temporal cycle count given the spatial mapping (the spatial mapping can be non-ideal)
Expand Down Expand Up @@ -1167,7 +1162,7 @@ def get_total_inst_bandwidth(self, memory_instance: MemoryInstance) -> MemoryAcc
return total_inst_bw

def __str__(self):
return f"CostModelEvaluation({self.layer}, core {self.core_id})"
return f"CostModelEvaluation({self.layer}, {self.accelerator})"

def __repr__(self):
return str(self)
Expand Down
5 changes: 2 additions & 3 deletions zigzag/cost_model/cost_model_imc.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ def __init__(
access_same_data_considered_as_no_access: bool = True,
):
self.is_imc = True
self.core = next(iter(accelerator.cores))
assert isinstance(self.core.operational_array, ImcArray)
self.operational_array: ImcArray = self.core.operational_array
assert isinstance(accelerator.operational_array, ImcArray)
self.operational_array: ImcArray = accelerator.operational_array
super().__init__(
accelerator=accelerator,
layer=layer,
Expand Down
146 changes: 128 additions & 18 deletions zigzag/hardware/architecture/accelerator.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,141 @@
from zigzag.hardware.architecture.core import Core
from zigzag.datatypes import MemoryOperand
from zigzag.hardware.architecture.memory_hierarchy import MemoryHierarchy
from zigzag.hardware.architecture.memory_instance import MemoryInstance
from zigzag.hardware.architecture.memory_level import MemoryLevel
from zigzag.hardware.architecture.operational_array import OperationalArrayABC
from zigzag.mapping.spatial_mapping import SpatialMapping
from zigzag.utils import json_repr_handler


class Accelerator:
"""! The Accelerator class houses a set of Cores with an additional Global Buffer.
This Global Buffer sits above the cores, and can optionally be disabled.
"""! This class houses the array of multipliers and the attached memory hierarchy.
This class supports a singular multiplier array and memory hierarchy, runtime flexibility should be implemented
on top.
"""

def __init__(self, name: str, core_set: set[Core]):
self.name: str = name
self.cores = sorted(list(core_set), key=lambda core: core.id)
id: int
name: str
operational_array: OperationalArrayABC
memory_hierarchy: MemoryHierarchy
dataflows: SpatialMapping | None
mem_hierarchy_dict: dict[MemoryOperand, list[MemoryLevel]]
mem_hierarchy_dict: dict[MemoryOperand, list[MemoryLevel]]
mem_size_dict: dict[MemoryOperand, list[int]]
mem_r_bw_dict: dict[MemoryOperand, list[int]]
mem_w_bw_dict: dict[MemoryOperand, list[int]]
mem_r_bw_min_dict: dict[MemoryOperand, list[int]]
mem_w_bw_min_dict: dict[MemoryOperand, list[int]]
mem_sharing_list: list[dict[MemoryOperand, int]]

def __init__(
self,
core_id: int,
name: str,
operational_array: OperationalArrayABC,
memory_hierarchy: MemoryHierarchy,
dataflows: SpatialMapping | None = None,
):
self.id = core_id
self.name = name
self.operational_array = operational_array
self.memory_hierarchy = memory_hierarchy
self.mem_hierarchy_dict = {}

self.dataflows = dataflows
self.recalculate_memory_hierarchy_information()

def get_memory_level(self, mem_op: MemoryOperand, mem_lv: int) -> MemoryLevel:
"""! Returns a specific memory level in the memory hierarchy for the memory operand"""
# Sort the nodes topologically and filter out all memories that don't store mem_op
memory = [node for node in self.memory_hierarchy.topological_sort() if mem_op in node.operands]
return memory[mem_lv]

def recalculate_memory_hierarchy_information(self) -> None:
self.__generate_memory_hierarchy_dict()
self.__generate_mem_sharing_list()

def __generate_memory_hierarchy_dict(self):
mem_operands = self.memory_hierarchy.nb_levels.keys()
self.mem_hierarchy_dict = {}
self.mem_size_dict = {}
self.mem_r_bw_dict = {}
self.mem_w_bw_dict = {}
self.mem_r_bw_min_dict = {}
self.mem_w_bw_min_dict = {}
for mem_op in mem_operands:
self.mem_hierarchy_dict[mem_op] = [
node for node in self.memory_hierarchy.topological_sort() if mem_op in node.operands
]
self.mem_size_dict[mem_op] = [
node.memory_instance.size
for node in self.memory_hierarchy.topological_sort()
if mem_op in node.operands
]
self.mem_r_bw_dict[mem_op] = [
node.memory_instance.r_bw
for node in self.memory_hierarchy.topological_sort()
if mem_op in node.operands
]
self.mem_w_bw_dict[mem_op] = [
node.memory_instance.w_bw
for node in self.memory_hierarchy.topological_sort()
if mem_op in node.operands
]
self.mem_r_bw_min_dict[mem_op] = [
node.memory_instance.r_bw_min
for node in self.memory_hierarchy.topological_sort()
if mem_op in node.operands
]
self.mem_w_bw_min_dict[mem_op] = [
node.memory_instance.w_bw_min
for node in self.memory_hierarchy.topological_sort()
if mem_op in node.operands
]

def __generate_mem_sharing_list(self):
"""! Generates a list of dictionary that indicates which operand's which memory levels are sharing the same
physical memory"""
self.mem_sharing_list = []
for mem_lv in self.mem_hierarchy_dict.values():
for mem in mem_lv:
operand_mem_share = mem.mem_level_of_operands
if len(operand_mem_share) > 1 and operand_mem_share not in self.mem_sharing_list:
self.mem_sharing_list.append(operand_mem_share)

def get_top_memory_instance(self, mem_op: MemoryOperand) -> MemoryInstance:
if mem_op not in self.memory_hierarchy.get_operands():
raise ValueError(f"Memory operand {mem_op} not in {self}.")
mem_level = self.memory_hierarchy.get_operand_top_level(mem_op)
mem_instance = mem_level.memory_instance
return mem_instance

def get_memory_bw_dict(self):
return self.mem_r_bw_dict, self.mem_w_bw_dict

def get_memory_bw_min_dict(self):
return self.mem_r_bw_min_dict, self.mem_w_bw_min_dict

def __str__(self) -> str:
return f"Accelerator({self.name})"
return f"Core({self.id})"

def __repr__(self) -> str:
return str(self)

def __jsonrepr__(self):
"""! JSON representation used for saving this object to a json file."""
return json_repr_handler({"name": self.name, "cores": self.cores})

def get_core(self, core_id: int) -> Core:
"""! Return the core with id 'core_id'.
Raises ValueError() when a core_id is not found in the available cores.
"""
core = next((core for core in self.cores if core.id == core_id), None)
if not core:
raise ValueError(f"Requested core with id {core_id} is not present in accelerator.")
return core
return json_repr_handler(self.__dict__)

def __hash__(self) -> int:
return self.id

def __eq__(self, other: object) -> bool:
return (
isinstance(other, Accelerator)
and self.id == other.id
and self.operational_array == other.operational_array
and self.memory_hierarchy == other.memory_hierarchy
)

def has_same_performance(self, other: "Accelerator") -> bool:
return self.operational_array == other.operational_array and self.memory_hierarchy.has_same_performance(
other.memory_hierarchy
)
Loading

0 comments on commit cdfa78e

Please sign in to comment.