Skip to content

Commit 9367864

Browse files
authored
Fix: Memlets in StripMining transformation (spcl#2118)
**Bug Description:** To put it simple, the `StripMining` transformation essentially performs tiling- that is, it adds a **new outer** map which encloses an existing one, where the new map runs over chunks (=tiles) of the original iteration space, while the inner map handles individual elements inside each chunk. The inner map is the original map, where the range just go adapted. Now, the new (outer) map needs to be connected to the original one, and the dataflow between them must be captured correctly. This essentially involves analyzing which parts of a data descriptor should flow for each chunk. For the sake of simplicity, one can think of this process as: copying an existing memlet (flowing to and from the original inner map) and update it to create the correct memlets between the two maps. The screenshots below help visualize this - you can clearly see the similarity between the two highlighted memlets. However, this copying step is problematic when an `other_subset` is present - in such cases, it should be explicitly set to `None` for the newly created memlet. Otherwise there can be a dimensionality mismatch between the two subsets leading to an invalid SDFG. **Fix Description** Set the `other_subset` to `None` for memlets created between the two maps. Additionally, a test case has been added which now passes and would have failed before this fix. **Failing Example** After the fix, ```[0] -> A[block_i : Min(63, block_i + 31) + 1]``` in the right image will become ```A[block_i : Min(63, block_i + 31) + 1]```. <p float="left"> <img width="45%" src="https://github.com/user-attachments/assets/33c14e92-43c6-4d93-a77e-87691fa8f9b8" /> <img width="45%" src="https://github.com/user-attachments/assets/76b7f030-91c6-424d-a91b-3ba9df710840" /> </p>
1 parent 16a159b commit 9367864

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

dace/transformation/dataflow/strip_mining.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def _stripmine(self, sdfg: SDFG, graph: SDFGState, map_entry: nodes.MapEntry):
397397
entry_out_conn['OUT_' + conn] = None
398398
new_memlet = dcpy(memlet)
399399
new_memlet.subset = new_subset
400+
new_memlet.other_subset = None
400401
if memlet.dynamic:
401402
new_memlet.num_accesses = memlet.num_accesses
402403
else:
@@ -442,6 +443,7 @@ def _stripmine(self, sdfg: SDFG, graph: SDFGState, map_entry: nodes.MapEntry):
442443
exit_out_conn['OUT_' + conn] = None
443444
new_memlet = dcpy(memlet)
444445
new_memlet.subset = new_subset
446+
new_memlet.other_subset = None
445447
if memlet.dynamic:
446448
new_memlet.num_accesses = memlet.num_accesses
447449
else:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2019-2025 ETH Zurich and the DaCe authors. All rights reserved.
2+
import dace
3+
from dace.transformation.dataflow.strip_mining import StripMining
4+
5+
import numpy as np
6+
7+
8+
def test_strip_mining():
9+
"""
10+
Test a simple example example where Stripmining works
11+
"""
12+
# 1. The program
13+
sdfg = dace.SDFG("assign")
14+
state = sdfg.add_state("main")
15+
16+
# inputs
17+
A = state.add_array("A", (64, ), dtype=dace.uint32)
18+
B = state.add_array("B", (1, ), dtype=dace.uint32)
19+
20+
# kernel map
21+
map_entry, map_exit = state.add_map("map", dict(i="0:64"))
22+
23+
# Assign tasklet
24+
tasklet = state.add_tasklet("assign",
25+
inputs=dict(),
26+
outputs={"_out"},
27+
code="_out = 1;",
28+
language=dace.dtypes.Language.CPP)
29+
30+
# Write first 1 to B[0] then B[0] to A[i]
31+
state.add_edge(map_entry, None, tasklet, None, dace.Memlet())
32+
state.add_edge(tasklet, "_out", B, None, dace.Memlet("B[0]"))
33+
state.add_edge(B, None, map_exit, "IN_A", dace.Memlet("[0] -> A[i]"))
34+
state.add_edge(map_exit, "OUT_A", A, None, dace.Memlet("A[0:64]", volume=64))
35+
map_exit.add_in_connector("IN_A")
36+
map_exit.add_out_connector("OUT_A")
37+
38+
# 2. Apply StripMining
39+
stripmine = StripMining()
40+
stripmine.map_entry = map_entry
41+
stripmine.dim_idx = 0
42+
stripmine.new_dim_prefix = "block"
43+
stripmine.tile_size = 32
44+
stripmine.tile_stride = 32
45+
stripmine.apply(state, sdfg)
46+
47+
# 3. Run with example input and check correctness
48+
A_np = np.zeros([64], dtype=np.uint32)
49+
B_np = np.zeros([1], dtype=np.uint32)
50+
sdfg(A=A_np, B=B_np)
51+
assert np.all(A_np == 1), f"A should be all ones. but got {A_np}"
52+
53+
54+
if __name__ == '__main__':
55+
test_strip_mining()

0 commit comments

Comments
 (0)