Issues with symbols with the same name on parent and nested SDFG
- Dominant language
- Python
- Stars
- 593
- Forks
- 163
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 60
Description
I have this rather innocent looking SDFG:
`t`, the transient on on the inside, has a symbolic shape.
The symbol is not passed through `symbol_mapping` but by the `outer_a_shape_scalar` that is turned into a symbol on the first inter state edge.
The remaining shapes are all known at compile time.
So this SDFG compiles.
Now, if I turn the shape of `T`, the transient on the outer SDFG, from a literal shape to a symbolic size.
For some reasons the name of the symbol of the inner and outer SDFG have the same name, but there is not a `smbol_mapping` that ties them together (they are logical coupled together, but to some extend they can be different).
Code generation works, but it is not possible to compile, the error is surprisingly on the inner SDFG, although nothing has changed there.
The problematic code is here:
```c++
inline void inner_sdfg_60ac60ea_5b0f_11f0_ba71_7bc08aeb79ce_0_0_4(outer_sdfg_60b89694_5b0f_11f0_ba71_7bc08aeb79ce_state_t *__state, double* __restrict__ a, const int& inner_a_shape_scalar, double* __restrict__ b) {
double *t;
t = new double DACE_ALIGN(64)[inner_a_shape_sym];
int inner_a_shape_sym;
inner_a_shape_sym = inner_a_shape_scalar;
{
{
#pragma omp parallel for
for (auto __i = 0; __i < inner_a_shape_sym; __i += 1) {
{
double __in = a[__i];
double __out;
///////////////////
// Tasklet code (computation)
__out = (__in + 1.0);
///////////////////
t[__i] = __out;
}
}
}
}
{
dace::CopyNDDynamic::template ConstDst<1>::Copy(
t, b + 1, (inner_a_shape_sym - 1), 1);
}
delete[] t;
}
```
As you can see `t` is allocated before the symbol for its size is computed which leads to a compilation error.
Then I did some experiments:
- As you can see from the image, the write of `t` and the copy `t -> b` are in different states.
If you do that, then it works, the reason is that now the allocation is performed inside that single state, when the size is available.
- If you change the name of the outer symbol to something different, then it works too, even if you keep the separate state of the inner SDFG.
Here is a reproducer (currently `test_extra_write_back_state_and_symbol_on_the_outside_with_same_name_as_inside()` fails, but the other passes):
```python
import dace
from typing import Tuple
import uuid
import pytest
def unique_name(name: str) -> str:
"""Adds a unique string to `name`."""
maximal_length = 200
unique_sufix = str(uuid.uuid1()).replace("-", "_")
if len(name) > (maximal_length - len(unique_sufix)):
name = name[:(maximal_length - len(unique_sufix) - 1)]
return f"{name}_{unique_sufix}"
def _make_sdfg(
outside_uses_symbol: bool,
outside_uses_different_symbol: bool,
separate_write_back_state: bool,
) -> Tuple[dace.SDFG, dace.SDFG, dace.SDFGState, dace.nodes.NestedSDFG]:
"""
Args:
outside_uses_symbol: The outside SDFG also uses a symbol, if `outside_uses_different_symbol` is
not `True` then the same symbol name as on the inside is used.
outside_uses_different_symbol: Use a different symbol name on the outside, if requested.
separate_write_back_state: There is an extra state to perform the `t -> b` copy in the inner SDFG.
"""
inner_symbol_name = "inner_symbol"
outer_symbol_name = "outer_symbol" if outside_uses_different_symbol else inner_symbol_name
# Create the inner SDFG.
inner_sdfg = dace.SDFG(unique_name("inner_sdfg"))
inner_istate = inner_sdfg.add_state(is_start_block=True)
inner_state = inner_sdfg.add_state_after(inner_istate, assignments={inner_symbol_name: "inner_scalar"})
inner_sdfg.add_symbol(inner_symbol_name, dace.int32)
inner_sdfg.add_scalar(
"inner_scalar",
dtype=dace.int32,
transient=False,
)
inner_shapes = {"t": (inner_symbol_name,)}
for name in "abt":
inner_sdfg.add_array(
name,
shape=inner_shapes.get(name, (20, )),
dtype=dace.float64,
transient=(name == "t"),
)
a, t = (inner_state.add_access(name) for name in "at")
inner_state.add_mapped_tasklet(
"computation",
map_ranges={"__i": f"0:{inner_symbol_name}"},
inputs={"__in": dace.Memlet("a[__i]")},
code="__out = __in + 1.0",
outputs={"__out": dace.Memlet("t[__i]")},
input_nodes={a},
output_nodes={t},
external_edges=True,
)
if separate_write_back_state:
inner_astate = inner_sdfg.add_state_after(inner_state)
inner_astate.add_nedge(inner_astate.add_access("t"), inner_astate.add_access("b"),
dace.Memlet(f"t[0:({inner_symbol_name} - 1)] -> [1:{inner_symbol_name}]"))
else:
inner_state.add_nedge(t, inner_state.add_access("b"),
dace.Memlet(f"t[0:({inner_symbol_name} - 1)] -> [1:{inner_symbol_name}]"))
# Creating the outer SDFG.
outer_sdfg = dace.SDFG(unique_name("outer_sdfg"))
outer_state = outer_sdfg.add_state(is_start_block=True)
outer_sdfg.add_scalar(
"outer_scalar",
dace.int32,
transient=True,
)
shape_of_T = (20, )
if outside_uses_symbol:
shape_of_T = (outer_symbol_name,)
outer_sdfg.add_symbol(shape_of_T[0], dace.int32)
outer_sdfg.add_array(
"A",
shape=(20, ),
dtype=dace.float64,
transient=False,
)
outer_sdfg.add_array(
"T",
shape=shape_of_T,
dtype=dace.float64,
transient=True,
)
outer_sdfg.add_array(
"B",
shape=(20, ),
dtype=dace.float64,
transient=False,
)
A, B, T = (outer_state.add_access(name) for name in "ABT")
outer_a_shape_scalar = outer_state.add_access("outer_scalar")
nsdfg_node = outer_state.add_nested_sdfg(
sdfg=inner_sdfg,
parent=outer_sdfg,
inputs={"inner_scalar", "a"},
outputs={"b"},
symbol_mapping={},
)
outer_tasklet_for_setting_size = outer_state.add_tasklet(
"outer_tasklet_for_setting_size",
inputs={},
outputs={"__out"},
code="__out = 20",
)
outer_state.add_edge(
outer_tasklet_for_setting_size,
"__out",
outer_a_shape_scalar,
None,
dace.Memlet("outer_scalar[0]"),
)
outer_state.add_edge(
outer_a_shape_scalar,
None,
nsdfg_node,
"inner_scalar",
dace.Memlet("outer_scalar[0]"),
)
outer_state.add_edge(
A,
None,
nsdfg_node,
"a",
dace.Memlet("A[0:20]"),
)
outer_state.add_edge(
nsdfg_node,
"b",
T,
None,
dace.Memlet(f"T[0:{shape_of_T[0]}]"),
)
outer_state.add_nedge(
T,
B,
dace.Memlet(f"T[0:{shape_of_T[0]}] -> [0:20]", allow_oob=True),
)
outer_sdfg.validate()
return outer_sdfg, inner_sdfg, inner_state, nsdfg_node
@pytest.mark.parametrize("separate_write_back_state", [True, False])
def test_no_symbols_at_the_outside(separate_write_back_state: bool):
"""
The outside does not use symbols. Depending on the argument there is a separate write back
state on the inner SDFG.
"""
outer_sdfg, inner_sdfg, map_state, nsdfg_node = _make_sdfg(
outside_uses_symbol=False,
outside_uses_different_symbol=False,
separate_write_back_state=separate_write_back_state,
)
# Test if it is possible to compile the thing.
initial_outer_csdfg = outer_sdfg.compile()
def _perform_test_symbol_used_by_the_outer_sdfg(
separate_write_back_state: bool,
outside_uses_different_symbol: bool,
):
outer_sdfg, inner_sdfg, map_state, nsdfg_node = _make_sdfg(
outside_uses_symbol=True,
outside_uses_different_symbol=outside_uses_different_symbol,
separate_write_back_state=separate_write_back_state)
# Test if it is possible to compile the thing.
initial_outer_csdfg = outer_sdfg.compile()
def test_extra_write_back_state_and_symbol_on_the_outside_with_same_name_as_inside():
"""
The SDFG has the following properties:
- The inner SDFG has a separate state to perform the `t -> b` write back.
- The transient `T` on the outer SDFG has a symbolic size, the name of the symbol
is also used on the inner SDFG to denote the size of `t`.
Currently for unknown reasons it fails to compile, see [issue#2072](https://github.com/spcl/dace/issues/2072).
"""
_perform_test_symbol_used_by_the_outer_sdfg(
separate_write_back_state=True,
outside_uses_different_symbol=False)
def test_extra_write_back_state_and_symbol_on_the_outside_with_different_name_than_inside():
"""
Similar situation than in `test_extra_write_back_state_and_symbol_on_the_outside_with_same_name_as_inside()`.
The only difference is that this time the symbol on the outside has a different name than on the inside.
Which has the effect that the code now works.
"""
_perform_test_symbol_used_by_the_outer_sdfg(
separate_write_back_state=True,
outside_uses_different_symbol=True)
def test_no_extra_write_back_state_and_symbol_on_the_outside_with_same_name_than_inside():
"""
Similar situation than in `test_extra_write_back_state_and_symbol_on_the_outside_with_same_name_as_inside()`.
Again the outer and inner SDFG use a symbol to denote the size of their respective transients and the name
is the same, but this time the write back, i.e. `t -> b`, is not done in a separate state.
For unknown reasons this allows to compile the code.
"""
_perform_test_symbol_used_by_the_outer_sdfg(
separate_write_back_state=False,
outside_uses_different_symbol=False)
```
Contributor guide
Assessment
This issue has not been assessed yet.