From 46b4813849d72e237523df3c1d71536137abeca8 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sun, 3 Mar 2024 17:52:37 +0100 Subject: [PATCH 01/20] Write tests with mapped tasklets --- dace/viewer/webclient | 2 +- tests/warn_on_potential_data_race_test.py | 84 +++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/warn_on_potential_data_race_test.py diff --git a/dace/viewer/webclient b/dace/viewer/webclient index 2128d61489..dd34948875 160000 --- a/dace/viewer/webclient +++ b/dace/viewer/webclient @@ -1 +1 @@ -Subproject commit 2128d61489ff249db5a0f92587ef4d55eefc8add +Subproject commit dd34948875d01f63749faee5dd0fd34a198aaaa6 diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py new file mode 100644 index 0000000000..7efbf9371b --- /dev/null +++ b/tests/warn_on_potential_data_race_test.py @@ -0,0 +1,84 @@ +# Copyright 2019-2024 ETH Zurich and the DaCe authors. All rights reserved. + +import warnings +import dace +import numpy as np +import pytest + + +def test_memlet_range_not_overlap_ranges(): + sdfg = dace.SDFG('memlet_range_not_overlap_ranges') + state = sdfg.add_state() + N = dace.symbol("N", dtype=dace.int32) + sdfg.add_array("A", (N//2,), dace.int32) + A = state.add_access("A") + sdfg.add_array("B", (N,), dace.int32) + B = state.add_access("B") + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N//2"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k+N//2")}, + map_ranges={"k": "0:N//2"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + N = 6 + A = np.arange(N//2, dtype=np.int32) + B = np.zeros((N,), dtype=np.int32) + with warnings.catch_warnings(): + warnings.simplefilter("error") + sdfg(N=N, A=A, B=B) + + +def test_memlet_range_overlap_ranges(): + sdfg = dace.SDFG('memlet_range_overlap_ranges') + state = sdfg.add_state() + N = dace.symbol("N", dtype=dace.int32) + sdfg.add_array("A", (N,), dace.int32) + A = state.add_access("A") + sdfg.add_array("B", (N,), dace.int32) + B = state.add_access("B") + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + N = 6 + A = np.arange(N, dtype=np.int32) + B = np.zeros((N,), dtype=np.int32) + with pytest.warns(UserWarning): + sdfg(N=N, A=A, B=B) + + +if __name__ == '__main__': + test_memlet_range_not_overlap_ranges() + test_memlet_range_overlap_ranges() From 922d5ef92e7c608365a381a08f2d494100be80a7 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sun, 3 Mar 2024 18:28:52 +0100 Subject: [PATCH 02/20] Add config flag --- dace/config_schema.yml | 6 ++++++ dace/sdfg/validation.py | 3 +++ tests/warn_on_potential_data_race_test.py | 6 ++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/dace/config_schema.yml b/dace/config_schema.yml index 737862cacc..6e4e1cea5f 100644 --- a/dace/config_schema.yml +++ b/dace/config_schema.yml @@ -904,6 +904,12 @@ required: be called before every compiled SDFG's generated code is invoked. Used for functionality such as low-level profiling. + check_race_conditions: + type: bool + default: false + title: Check race conditions + description: Check for probable race conditions during the program's execution. + ############################################# # Experimental features diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index 660e45e574..e797902bc3 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -790,6 +790,9 @@ def validate_state(state: 'dace.sdfg.SDFGState', continue raise error + if Config.get_bool("check_race_conditions"): + pass + ######################################## diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 7efbf9371b..a83dc33b43 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -40,7 +40,8 @@ def test_memlet_range_not_overlap_ranges(): B = np.zeros((N,), dtype=np.int32) with warnings.catch_warnings(): warnings.simplefilter("error") - sdfg(N=N, A=A, B=B) + with dace.config.set_temporary("check_race_conditions", value=True): + sdfg(N=N, A=A, B=B) def test_memlet_range_overlap_ranges(): @@ -76,7 +77,8 @@ def test_memlet_range_overlap_ranges(): A = np.arange(N, dtype=np.int32) B = np.zeros((N,), dtype=np.int32) with pytest.warns(UserWarning): - sdfg(N=N, A=A, B=B) + with dace.config.set_temporary("check_race_conditions", value=True): + sdfg(N=N, A=A, B=B) if __name__ == '__main__': From bf1b814e1e664ede0c2b4d90349bcb1036f109cf Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Mon, 4 Mar 2024 12:33:03 +0100 Subject: [PATCH 03/20] Check if there are intersecting memlet subsets for a given access node --- dace/sdfg/validation.py | 7 ++++++- dace/subsets.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index e797902bc3..e5ecb3813a 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -7,6 +7,7 @@ import warnings from dace import dtypes, subsets from dace import symbolic +from dace.sdfg.nodes import AccessNode if TYPE_CHECKING: import dace @@ -791,7 +792,11 @@ def validate_state(state: 'dace.sdfg.SDFGState', raise error if Config.get_bool("check_race_conditions"): - pass + for node in state.nodes(): + if isinstance(node, AccessNode): + in_memlet_ranges = [e.data.dst_subset for e in state.in_edges(node)] + if subsets.Range.are_intersecting(in_memlet_ranges): + warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') ######################################## diff --git a/dace/subsets.py b/dace/subsets.py index 0d6037e682..9c6456243a 100644 --- a/dace/subsets.py +++ b/dace/subsets.py @@ -813,6 +813,17 @@ def intersects(self, other: 'Range'): return True + @staticmethod + def are_intersecting(ranges: List['Range']) -> bool: + """ Check if any of the given ranges are intersecting + :param ranges: the ranges to be checked. + """ + for i in range(len(ranges)): + for j in range(i+1, len(ranges)): + if ranges[i].intersects(ranges[j]): + return True + return False + @dace.serialize.serializable class Indices(Subset): From b928aa40dadd491c1e0c650c25bd0ce59d696844 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Mon, 4 Mar 2024 14:56:10 +0100 Subject: [PATCH 04/20] Filter only UserWarning in test_memlet_range_not_overlap_ranges --- tests/warn_on_potential_data_race_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index a83dc33b43..69e47a21e7 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -39,7 +39,7 @@ def test_memlet_range_not_overlap_ranges(): A = np.arange(N//2, dtype=np.int32) B = np.zeros((N,), dtype=np.int32) with warnings.catch_warnings(): - warnings.simplefilter("error") + warnings.simplefilter("error", UserWarning) with dace.config.set_temporary("check_race_conditions", value=True): sdfg(N=N, A=A, B=B) From 19b4a0379e75737379fe91599f15bdbfd7389ce0 Mon Sep 17 00:00:00 2001 From: luca-patrignani <92518571+luca-patrignani@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:59:52 +0100 Subject: [PATCH 05/20] Update dace/config_schema.yml Co-authored-by: Tal Ben-Nun --- dace/config_schema.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dace/config_schema.yml b/dace/config_schema.yml index 6e4e1cea5f..d710bcb37d 100644 --- a/dace/config_schema.yml +++ b/dace/config_schema.yml @@ -908,7 +908,7 @@ required: type: bool default: false title: Check race conditions - description: Check for probable race conditions during the program's execution. + description: Check for probable race conditions during validation. ############################################# # Experimental features From 653b0dec61e723944e91d60139300d85bd9d6a71 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Mon, 11 Mar 2024 14:11:05 +0100 Subject: [PATCH 06/20] Move check_race_conditions flag into experimental category and add test for read/write race condition --- dace/config_schema.yml | 11 ++--- dace/sdfg/validation.py | 2 +- tests/warn_on_potential_data_race_test.py | 49 +++++++++++++++++++++-- 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/dace/config_schema.yml b/dace/config_schema.yml index d710bcb37d..55da656b36 100644 --- a/dace/config_schema.yml +++ b/dace/config_schema.yml @@ -904,11 +904,6 @@ required: be called before every compiled SDFG's generated code is invoked. Used for functionality such as low-level profiling. - check_race_conditions: - type: bool - default: false - title: Check race conditions - description: Check for probable race conditions during validation. ############################################# # Experimental features @@ -925,6 +920,12 @@ required: description: > Check for undefined symbols in memlets during SDFG validation. + check_race_conditions: + type: bool + default: false + title: Check race conditions + description: Check for probable race conditions during validation. + ############################################# # Features for unit testing diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index e5ecb3813a..83cee557dd 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -791,7 +791,7 @@ def validate_state(state: 'dace.sdfg.SDFGState', continue raise error - if Config.get_bool("check_race_conditions"): + if Config.get_bool("experimental.check_race_conditions"): for node in state.nodes(): if isinstance(node, AccessNode): in_memlet_ranges = [e.data.dst_subset for e in state.in_edges(node)] diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 69e47a21e7..b85c0feef6 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -40,11 +40,11 @@ def test_memlet_range_not_overlap_ranges(): B = np.zeros((N,), dtype=np.int32) with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) - with dace.config.set_temporary("check_race_conditions", value=True): + with dace.config.set_temporary("experimental.check_race_conditions", value=True): sdfg(N=N, A=A, B=B) -def test_memlet_range_overlap_ranges(): +def test_memlet_range_write_write_overlap_ranges(): sdfg = dace.SDFG('memlet_range_overlap_ranges') state = sdfg.add_state() N = dace.symbol("N", dtype=dace.int32) @@ -77,10 +77,51 @@ def test_memlet_range_overlap_ranges(): A = np.arange(N, dtype=np.int32) B = np.zeros((N,), dtype=np.int32) with pytest.warns(UserWarning): - with dace.config.set_temporary("check_race_conditions", value=True): + with dace.config.set_temporary("experimental.check_race_conditions", value=True): sdfg(N=N, A=A, B=B) +def test_memlet_range_write_read_overlap_ranges(): + sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') + state = sdfg.add_state() + N = dace.symbol("N", dtype=dace.int32) + sdfg.add_array("A", (N,), dace.int32) + A_read = state.add_read("A") + A_write = state.add_write("A") + sdfg.add_array("B", (N,), dace.int32) + B = state.add_access("B") + sdfg.add_array("C", (N,), dace.int32) + C = state.add_access("C") + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A_read}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="a = c - 20", + inputs={"c": dace.Memlet(data="C", subset="k")}, + outputs={"a": dace.Memlet(data="A", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"C": C}, + output_nodes={"A": A_write} + ) + + N = 6 + A = np.arange(N, dtype=np.int32) + B = np.zeros((N,), dtype=np.int32) + C = 20 * A + + with pytest.warns(UserWarning): + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg(N=N, A=A, B=B, C=C) if __name__ == '__main__': test_memlet_range_not_overlap_ranges() - test_memlet_range_overlap_ranges() + test_memlet_range_write_write_overlap_ranges() + test_memlet_range_write_read_overlap_ranges() From 8534fb3b1766f8873d78bfd55cf4963a8c78fc08 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Tue, 12 Mar 2024 15:06:59 +0100 Subject: [PATCH 07/20] Create test with overlapping ranges with two different access nodes --- tests/warn_on_potential_data_race_test.py | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index b85c0feef6..a066f79ade 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -121,7 +121,50 @@ def test_memlet_range_write_read_overlap_ranges(): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg(N=N, A=A, B=B, C=C) +def test_memlet_overlap_ranges_two_access_nodes(): + sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') + state = sdfg.add_state() + N = dace.symbol("N", dtype=dace.int32) + sdfg.add_array("A", (N,), dace.int32) + A1 = state.add_access("A") + A2 = state.add_access("A") + sdfg.add_array("B", (N,), dace.int32) + B1 = state.add_access("B") + B2 = state.add_access("B") + + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A1}, + output_nodes={"B": B1} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A2}, + output_nodes={"B": B2} + ) + + N = 6 + A = np.arange(N, dtype=np.int32) + B = np.zeros((N,), dtype=np.int32) + C = 20 * A + + with pytest.warns(UserWarning): + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg(N=N, A=A, B=B, C=C) + + if __name__ == '__main__': test_memlet_range_not_overlap_ranges() test_memlet_range_write_write_overlap_ranges() test_memlet_range_write_read_overlap_ranges() + test_memlet_overlap_ranges_two_access_nodes() From 3469315bd27a13bc76b906ccce3c67b01b823505 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Tue, 12 Mar 2024 19:27:20 +0100 Subject: [PATCH 08/20] Create test for symbolic overlap --- tests/warn_on_potential_data_race_test.py | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index a066f79ade..274388d44a 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -162,9 +162,48 @@ def test_memlet_overlap_ranges_two_access_nodes(): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg(N=N, A=A, B=B, C=C) +def test_memlet_overlap_symbolic_ranges(): + sdfg = dace.SDFG('memlet_overlap_symbolic_ranges') + state = sdfg.add_state() + N = dace.symbol("N", dtype=dace.int32) + sdfg.add_array("A", (2*N,), dace.int32) + A = state.add_access("A") + sdfg.add_array("B", (2*N,), dace.int32) + B = state.add_access("B") + + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:N"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "0:2*N"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + N = 6 + A = np.arange(2*N, dtype=np.int32) + B = np.zeros((2*N,), dtype=np.int32) + C = 20 * A + + with pytest.warns(UserWarning): + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg(N=N, A=A, B=B, C=C) if __name__ == '__main__': test_memlet_range_not_overlap_ranges() test_memlet_range_write_write_overlap_ranges() test_memlet_range_write_read_overlap_ranges() test_memlet_overlap_ranges_two_access_nodes() + test_memlet_overlap_symbolic_ranges() From 87cf81ed685ecbba4de666df394c6e40141d4f1e Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Tue, 12 Mar 2024 19:33:11 +0100 Subject: [PATCH 09/20] Create tests for constant memlet overlap and almost overlap --- tests/warn_on_potential_data_race_test.py | 75 +++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 274388d44a..9e7ba6bff3 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -201,9 +201,84 @@ def test_memlet_overlap_symbolic_ranges(): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg(N=N, A=A, B=B, C=C) +def test_constant_memlet_overlap(): + sdfg = dace.SDFG('constant_memlet_overlap') + state = sdfg.add_state() + sdfg.add_array("A", (12,), dace.int32) + A = state.add_access("A") + sdfg.add_array("B", (12,), dace.int32) + B = state.add_access("B") + + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "3:10"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "6:12"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + A = np.arange(12, dtype=np.int32) + B = np.zeros((12,), dtype=np.int32) + + with pytest.warns(UserWarning): + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg(A=A, B=B) + +def test_constant_memlet_almost_overlap(): + sdfg = dace.SDFG('constant_memlet_almost_overlap') + state = sdfg.add_state() + sdfg.add_array("A", (20,), dace.int32) + A = state.add_access("A") + sdfg.add_array("B", (20,), dace.int32) + B = state.add_access("B") + + state.add_mapped_tasklet( + name="first_tasklet", + code="b = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "3:10"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + state.add_mapped_tasklet( + name="second_tasklet", + code="b = a - 20", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="k")}, + map_ranges={"k": "10:20"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + A = np.arange(20, dtype=np.int32) + B = np.zeros((20,), dtype=np.int32) + + with pytest.warns(UserWarning): + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg(A=A, B=B) + + if __name__ == '__main__': test_memlet_range_not_overlap_ranges() test_memlet_range_write_write_overlap_ranges() test_memlet_range_write_read_overlap_ranges() test_memlet_overlap_ranges_two_access_nodes() test_memlet_overlap_symbolic_ranges() + test_constant_memlet_overlap() + test_constant_memlet_almost_overlap() From 8cbf45ff92265b17874624e5eec23d0fe2f5188d Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Tue, 12 Mar 2024 19:41:00 +0100 Subject: [PATCH 10/20] Revert changes to submodule --- dace/viewer/webclient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dace/viewer/webclient b/dace/viewer/webclient index dd34948875..2128d61489 160000 --- a/dace/viewer/webclient +++ b/dace/viewer/webclient @@ -1 +1 @@ -Subproject commit dd34948875d01f63749faee5dd0fd34a198aaaa6 +Subproject commit 2128d61489ff249db5a0f92587ef4d55eefc8add From 8120d39155c25e0e9c21a38c5a8560827b2a45e5 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Thu, 14 Mar 2024 10:23:55 +0100 Subject: [PATCH 11/20] Fix constant_memlet_almost_overlap_test --- tests/warn_on_potential_data_race_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 9e7ba6bff3..232eb7b2f5 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -270,6 +270,7 @@ def test_constant_memlet_almost_overlap(): B = np.zeros((20,), dtype=np.int32) with pytest.warns(UserWarning): + warnings.simplefilter("error", UserWarning) with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg(A=A, B=B) From 7f7f8639bff3a27e99f1dd0a6f0d6e04bb9269d6 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Thu, 14 Mar 2024 10:34:16 +0100 Subject: [PATCH 12/20] Fix constant_memlet_almost_overlap_test for real --- tests/warn_on_potential_data_race_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 232eb7b2f5..1f6b3049c5 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -269,7 +269,7 @@ def test_constant_memlet_almost_overlap(): A = np.arange(20, dtype=np.int32) B = np.zeros((20,), dtype=np.int32) - with pytest.warns(UserWarning): + with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg(A=A, B=B) From ff497ea99b188079226a7dce1cb295bbf8c45519 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Thu, 14 Mar 2024 11:22:52 +0100 Subject: [PATCH 13/20] Check write-write data races and read-write data races --- dace/sdfg/validation.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index 83cee557dd..f211578a90 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -1,5 +1,6 @@ # Copyright 2019-2021 ETH Zurich and the DaCe authors. All rights reserved. """ Exception classes and methods for validation of SDFGs. """ +from collections import defaultdict import copy from dace.dtypes import DebugInfo import os @@ -791,12 +792,32 @@ def validate_state(state: 'dace.sdfg.SDFGState', continue raise error - if Config.get_bool("experimental.check_race_conditions"): - for node in state.nodes(): - if isinstance(node, AccessNode): - in_memlet_ranges = [e.data.dst_subset for e in state.in_edges(node)] - if subsets.Range.are_intersecting(in_memlet_ranges): - warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') + if Config.get_bool("experimental.check_race_conditions"): + node_labels = [] + write_accesses = defaultdict(list) + read_accesses = defaultdict(list) + for node in state.data_nodes(): + node_labels.append(node.label) + write_accesses[node.label].extend([e.data.dst_subset for e in state.in_edges(node)]) + read_accesses[node.label].extend([e.data.src_subset for e in state.out_edges(node)]) + + for node_label in node_labels: + write_memlet_subsets = write_accesses[node_label] + read_memlet_subsets = read_accesses[node_label] + # check write-write data races + for i in range(len(write_memlet_subsets)): + for j in range(i+1, len(write_memlet_subsets)): + intersects = subsets.intersects(write_memlet_subsets[i], write_memlet_subsets[j]) + if intersects is not None: + if intersects: + warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') + # check read-write data races + for write in write_memlet_subsets: + for read in read_memlet_subsets: + intersects = subsets.intersects(write, read) + if intersects is not None: + if intersects: + warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') ######################################## From 4b5ac97c55d7675ff699f9729e0b94136b8f0799 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Fri, 15 Mar 2024 09:54:10 +0100 Subject: [PATCH 14/20] Remove are_intesecting function --- dace/subsets.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dace/subsets.py b/dace/subsets.py index 9c6456243a..0d6037e682 100644 --- a/dace/subsets.py +++ b/dace/subsets.py @@ -813,17 +813,6 @@ def intersects(self, other: 'Range'): return True - @staticmethod - def are_intersecting(ranges: List['Range']) -> bool: - """ Check if any of the given ranges are intersecting - :param ranges: the ranges to be checked. - """ - for i in range(len(ranges)): - for j in range(i+1, len(ranges)): - if ranges[i].intersects(ranges[j]): - return True - return False - @dace.serialize.serializable class Indices(Subset): From 1c8d803ee479699d9ee98073a0a122ab9bf996df Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sat, 16 Mar 2024 14:06:47 +0100 Subject: [PATCH 15/20] Validate sdfgs instead of running the programs --- tests/warn_on_potential_data_race_test.py | 41 ++++------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 1f6b3049c5..adb554a5a8 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -35,13 +35,10 @@ def test_memlet_range_not_overlap_ranges(): output_nodes={"B": B} ) - N = 6 - A = np.arange(N//2, dtype=np.int32) - B = np.zeros((N,), dtype=np.int32) with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) with dace.config.set_temporary("experimental.check_race_conditions", value=True): - sdfg(N=N, A=A, B=B) + sdfg.validate() def test_memlet_range_write_write_overlap_ranges(): @@ -73,12 +70,9 @@ def test_memlet_range_write_write_overlap_ranges(): output_nodes={"B": B} ) - N = 6 - A = np.arange(N, dtype=np.int32) - B = np.zeros((N,), dtype=np.int32) with pytest.warns(UserWarning): with dace.config.set_temporary("experimental.check_race_conditions", value=True): - sdfg(N=N, A=A, B=B) + sdfg.validate() def test_memlet_range_write_read_overlap_ranges(): sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') @@ -112,14 +106,9 @@ def test_memlet_range_write_read_overlap_ranges(): output_nodes={"A": A_write} ) - N = 6 - A = np.arange(N, dtype=np.int32) - B = np.zeros((N,), dtype=np.int32) - C = 20 * A - with pytest.warns(UserWarning): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): - sdfg(N=N, A=A, B=B, C=C) + sdfg.validate() def test_memlet_overlap_ranges_two_access_nodes(): sdfg = dace.SDFG('memlet_range_write_read_overlap_ranges') @@ -153,14 +142,9 @@ def test_memlet_overlap_ranges_two_access_nodes(): output_nodes={"B": B2} ) - N = 6 - A = np.arange(N, dtype=np.int32) - B = np.zeros((N,), dtype=np.int32) - C = 20 * A - with pytest.warns(UserWarning): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): - sdfg(N=N, A=A, B=B, C=C) + sdfg.validate() def test_memlet_overlap_symbolic_ranges(): sdfg = dace.SDFG('memlet_overlap_symbolic_ranges') @@ -192,14 +176,9 @@ def test_memlet_overlap_symbolic_ranges(): output_nodes={"B": B} ) - N = 6 - A = np.arange(2*N, dtype=np.int32) - B = np.zeros((2*N,), dtype=np.int32) - C = 20 * A - with pytest.warns(UserWarning): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): - sdfg(N=N, A=A, B=B, C=C) + sdfg.validate() def test_constant_memlet_overlap(): sdfg = dace.SDFG('constant_memlet_overlap') @@ -230,12 +209,9 @@ def test_constant_memlet_overlap(): output_nodes={"B": B} ) - A = np.arange(12, dtype=np.int32) - B = np.zeros((12,), dtype=np.int32) - with pytest.warns(UserWarning): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): - sdfg(A=A, B=B) + sdfg.validate() def test_constant_memlet_almost_overlap(): sdfg = dace.SDFG('constant_memlet_almost_overlap') @@ -266,13 +242,10 @@ def test_constant_memlet_almost_overlap(): output_nodes={"B": B} ) - A = np.arange(20, dtype=np.int32) - B = np.zeros((20,), dtype=np.int32) - with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): - sdfg(A=A, B=B) + sdfg.validate() if __name__ == '__main__': From 655fb90c1bdf3b58e959a2453f5b6c4f1c1afcd7 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sat, 16 Mar 2024 14:12:36 +0100 Subject: [PATCH 16/20] Use is True instead of first checking if it's null and then check if True --- dace/sdfg/validation.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index f211578a90..a2e68dad8e 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -807,17 +807,13 @@ def validate_state(state: 'dace.sdfg.SDFGState', # check write-write data races for i in range(len(write_memlet_subsets)): for j in range(i+1, len(write_memlet_subsets)): - intersects = subsets.intersects(write_memlet_subsets[i], write_memlet_subsets[j]) - if intersects is not None: - if intersects: - warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') + if subsets.intersects(write_memlet_subsets[i], write_memlet_subsets[j]) is True: + warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') # check read-write data races for write in write_memlet_subsets: for read in read_memlet_subsets: - intersects = subsets.intersects(write, read) - if intersects is not None: - if intersects: - warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') + if subsets.intersects(write, read) is True: + warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') ######################################## From a849a84b42661f9d2f734005f7f476b2420b3983 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sat, 16 Mar 2024 14:32:04 +0100 Subject: [PATCH 17/20] Add test_elementwise_map --- tests/warn_on_potential_data_race_test.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index adb554a5a8..9c7ec5e3a2 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -247,6 +247,28 @@ def test_constant_memlet_almost_overlap(): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg.validate() +def test_elementwise_map(): + sdfg = dace.SDFG('elementwise_map') + state = sdfg.add_state() + sdfg.add_array("A", (20,), dace.int32) + A_read = state.add_read("A") + A_write = state.add_write("A") + + state.add_mapped_tasklet( + name="first_tasklet", + code="aa = a + 10", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"aa": dace.Memlet(data="A", subset="k")}, + map_ranges={"k": "0:20"}, + external_edges=True, + input_nodes={"A": A_read}, + output_nodes={"A": A_write} + ) + + with warnings.catch_warnings(): + warnings.simplefilter("error", UserWarning) + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg.validate() if __name__ == '__main__': test_memlet_range_not_overlap_ranges() @@ -256,3 +278,4 @@ def test_constant_memlet_almost_overlap(): test_memlet_overlap_symbolic_ranges() test_constant_memlet_overlap() test_constant_memlet_almost_overlap() + test_elementwise_map() From 2f8f60c564d900eca33c6bc7de53199076c169ae Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Sun, 17 Mar 2024 11:08:23 +0100 Subject: [PATCH 18/20] Add test for wcr --- tests/warn_on_potential_data_race_test.py | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 9c7ec5e3a2..1a72f34c5e 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -270,6 +270,42 @@ def test_elementwise_map(): with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): sdfg.validate() +def test_memlet_overlap_with_wcr(): + sdfg = dace.SDFG('memlet_overlap_with_wcr') + state = sdfg.add_state() + sdfg.add_array("A", (20,), dace.int32) + sdfg.add_array("B", (1,), dace.int32) + A = state.add_read("A") + B = state.add_write("B") + + state.add_mapped_tasklet( + name="first_reduction", + code="b = a", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="0", wcr="lambda old, new: old + new")}, + map_ranges={"k": "0:20"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + state.add_mapped_tasklet( + name="second_reduction", + code="b = a", + inputs={"a": dace.Memlet(data="A", subset="k")}, + outputs={"b": dace.Memlet(data="B", subset="0", wcr="lambda old, new: old + new")}, + map_ranges={"k": "0:20"}, + external_edges=True, + input_nodes={"A": A}, + output_nodes={"B": B} + ) + + with warnings.catch_warnings(): + warnings.simplefilter("error", UserWarning) + with dace.config.set_temporary('experimental', 'check_race_conditions', value=True): + sdfg.validate() + + if __name__ == '__main__': test_memlet_range_not_overlap_ranges() test_memlet_range_write_write_overlap_ranges() @@ -279,3 +315,4 @@ def test_elementwise_map(): test_constant_memlet_overlap() test_constant_memlet_almost_overlap() test_elementwise_map() + test_memlet_overlap_with_wcr() From 7293fa932e7a1f96858dfcf082cdfc8a8592f386 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Mon, 18 Mar 2024 16:53:45 +0100 Subject: [PATCH 19/20] Check if there are paths between the two nodes when looking for read-write and write-write data races --- dace/sdfg/validation.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/dace/sdfg/validation.py b/dace/sdfg/validation.py index a2e68dad8e..39b2e74f3b 100644 --- a/dace/sdfg/validation.py +++ b/dace/sdfg/validation.py @@ -8,7 +8,7 @@ import warnings from dace import dtypes, subsets from dace import symbolic -from dace.sdfg.nodes import AccessNode +import networkx as nx if TYPE_CHECKING: import dace @@ -798,21 +798,24 @@ def validate_state(state: 'dace.sdfg.SDFGState', read_accesses = defaultdict(list) for node in state.data_nodes(): node_labels.append(node.label) - write_accesses[node.label].extend([e.data.dst_subset for e in state.in_edges(node)]) - read_accesses[node.label].extend([e.data.src_subset for e in state.out_edges(node)]) + write_accesses[node.label].extend([{"subset": e.data.dst_subset, "node": node, "wcr": e.data.wcr} for e in state.in_edges(node)]) + read_accesses[node.label].extend([{"subset": e.data.src_subset, "node": node} for e in state.out_edges(node)]) for node_label in node_labels: - write_memlet_subsets = write_accesses[node_label] - read_memlet_subsets = read_accesses[node_label] + writes = write_accesses[node_label] + reads = read_accesses[node_label] # check write-write data races - for i in range(len(write_memlet_subsets)): - for j in range(i+1, len(write_memlet_subsets)): - if subsets.intersects(write_memlet_subsets[i], write_memlet_subsets[j]) is True: + for i in range(len(writes)): + for j in range(i+1, len(writes)): + if (writes[i]["node"] == writes[j]["node"] or not nx.has_path(state.nx, writes[i]["node"], writes[j]["node"])) and \ + writes[i]["wcr"] is None and writes[j]["wcr"] is None and \ + subsets.intersects(writes[i]["subset"], writes[j]["subset"]) is True: warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') # check read-write data races - for write in write_memlet_subsets: - for read in read_memlet_subsets: - if subsets.intersects(write, read) is True: + for write in writes: + for read in reads: + if not nx.has_path(state.nx, read["node"], write["node"]) and \ + subsets.intersects(write["subset"], read["subset"]) is True: warnings.warn(f'Memlet range overlap while writing to "{node}" in state "{state.label}"') ######################################## From 322b25671ac6898f0677644893201b087d8039b5 Mon Sep 17 00:00:00 2001 From: Luca Patrignani Date: Mon, 18 Mar 2024 16:55:42 +0100 Subject: [PATCH 20/20] Remove unused numpy import --- tests/warn_on_potential_data_race_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/warn_on_potential_data_race_test.py b/tests/warn_on_potential_data_race_test.py index 1a72f34c5e..3300a1b9f9 100644 --- a/tests/warn_on_potential_data_race_test.py +++ b/tests/warn_on_potential_data_race_test.py @@ -2,7 +2,6 @@ import warnings import dace -import numpy as np import pytest