-
Notifications
You must be signed in to change notification settings - Fork 129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix to Read and Write Sets #1678
Changes from 7 commits
145c0ea
38e748b
3748c03
3ab4bf3
b4feddf
e1c25b2
70fa3db
b187a82
9c6cb6c
b5fc16f
b7fe242
b546b07
ae20590
db211fa
570437b
cb80f0b
b704a43
f74d6e8
e103924
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -745,51 +745,90 @@ def update_if_not_none(dic, update): | |
|
||
return defined_syms | ||
|
||
|
||
def _read_and_write_sets(self) -> Tuple[Dict[AnyStr, List[Subset]], Dict[AnyStr, List[Subset]]]: | ||
""" | ||
Determines what data is read and written in this subgraph, returning | ||
dictionaries from data containers to all subsets that are read/written. | ||
""" | ||
from dace.sdfg import utils # Avoid cyclic import | ||
|
||
# Ensures that the `{src,dst}_subset` are properly set. | ||
# TODO: find where the problems are | ||
for edge in self.edges(): | ||
edge.data.try_initialize(self.sdfg, self, edge) | ||
|
||
read_set = collections.defaultdict(list) | ||
write_set = collections.defaultdict(list) | ||
from dace.sdfg import utils # Avoid cyclic import | ||
subgraphs = utils.concurrent_subgraphs(self) | ||
for sg in subgraphs: | ||
rs = collections.defaultdict(list) | ||
ws = collections.defaultdict(list) | ||
|
||
for subgraph in utils.concurrent_subgraphs(self): | ||
subgraph_read_set = collections.defaultdict(list) # read and write set of this subgraph. | ||
subgraph_write_set = collections.defaultdict(list) | ||
# Traverse in topological order, so data that is written before it | ||
# is read is not counted in the read set | ||
for n in utils.dfs_topological_sort(sg, sources=sg.source_nodes()): | ||
if isinstance(n, nd.AccessNode): | ||
in_edges = sg.in_edges(n) | ||
out_edges = sg.out_edges(n) | ||
# Filter out memlets which go out but the same data is written to the AccessNode by another memlet | ||
for out_edge in list(out_edges): | ||
for in_edge in list(in_edges): | ||
if (in_edge.data.data == out_edge.data.data | ||
and in_edge.data.dst_subset.covers(out_edge.data.src_subset)): | ||
out_edges.remove(out_edge) | ||
break | ||
|
||
for e in in_edges: | ||
# skip empty memlets | ||
if e.data.is_empty(): | ||
continue | ||
# Store all subsets that have been written | ||
ws[n.data].append(e.data.subset) | ||
for e in out_edges: | ||
# skip empty memlets | ||
if e.data.is_empty(): | ||
continue | ||
rs[n.data].append(e.data.subset) | ||
# Union all subgraphs, so an array that was excluded from the read | ||
# set because it was written first is still included if it is read | ||
# in another subgraph | ||
for data, accesses in rs.items(): | ||
# TODO: This only works if every data descriptor is only once in a path. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the failure mode here if it appears multiple times? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It took my some time to understand it again, essentially each access node is processed individually and all are combined. But the more I think about this could actually catch some edge case were this would be incorrect. I turned the TODO into a NOTE and added a better description. |
||
for n in utils.dfs_topological_sort(subgraph, sources=subgraph.source_nodes()): | ||
if not isinstance(n, nd.AccessNode): | ||
# Read and writes can only be done through access nodes, | ||
# so ignore every other node. | ||
continue | ||
|
||
# Get a list of all incoming (writes) and outgoing (reads) edges of the | ||
# access node, ignore all empty memlets as they do not carry any data. | ||
in_edges = [in_edge for in_edge in subgraph.in_edges(n) if not in_edge.data.is_empty()] | ||
out_edges = [out_edge for out_edge in subgraph.out_edges(n) if not out_edge.data.is_empty()] | ||
|
||
# Extract the subsets that describes where we read and write the data | ||
# and store them for the later filtering. | ||
# NOTE: In certain cases the corresponding subset might be None, in this case | ||
# we assume that the whole array is written, which is the default behaviour. | ||
ac_desc = n.desc(self.sdfg) | ||
ac_size = ac_desc.total_size | ||
in_subsets = dict() | ||
for in_edge in in_edges: | ||
# Ensure that if the destination subset is not given, our assumption, that the | ||
# whole array is written to, is valid, by testing if the memlet transfers the | ||
# whole array. | ||
assert (in_edge.data.dst_subset is not None) or (in_edge.data.num_elements() == ac_size) | ||
in_subsets[in_edge] = ( | ||
sbs.Range.from_array(ac_desc) | ||
if in_edge.data.dst_subset is None | ||
else in_edge.data.dst_subset | ||
) | ||
out_subsets = dict() | ||
for out_edge in out_edges: | ||
assert (out_edge.data.src_subset is not None) or (out_edge.data.num_elements() == ac_size) | ||
out_subsets[out_edge] = ( | ||
sbs.Range.from_array(ac_desc) | ||
if out_edge.data.src_subset is None | ||
else out_edge.data.src_subset | ||
) | ||
|
||
# If a memlet reads a particular region of data from the access node and there | ||
# exists a memlet at the same access node that writes to the same region, then | ||
# this read is ignored, and not included in the final read set, but only | ||
# accounted fro in the write set. See also note below. | ||
# TODO: Handle the case when multiple disjoint writes are needed to cover the read. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume from the code that the failure mode here is 'conservative', i.e., we overapproximate something as a read if we have disjoint writes that would cover it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I made the description a bit clearer. |
||
for out_edge in list(out_edges): | ||
for in_edge in in_edges: | ||
if in_subsets[in_edge].covers(out_subsets[out_edge]): | ||
out_edges.remove(out_edge) | ||
break | ||
|
||
# Update the read and write sets of the subgraph. | ||
if in_edges: | ||
subgraph_write_set[n.data].extend(in_subsets.values()) | ||
if out_edges: | ||
subgraph_read_set[n.data].extend(out_subsets[out_edge] for out_edge in out_edges) | ||
|
||
# Add the subgraph's read and write set to the final ones. | ||
for data, accesses in subgraph_read_set.items(): | ||
read_set[data] += accesses | ||
for data, accesses in ws.items(): | ||
for data, accesses in subgraph_write_set.items(): | ||
write_set[data] += accesses | ||
return read_set, write_set | ||
|
||
return copy.deepcopy((read_set, write_set)) | ||
|
||
|
||
def read_and_write_sets(self) -> Tuple[Set[AnyStr], Set[AnyStr]]: | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very ugly hack, but I can replicate / encounter the issue too. I am fine with leaving this in, but please make sure there is an issue that keeps track of this TODO somewhere.