Skip to content
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

Memory Bug, Possible Undefined Baheviour #1921

Open
philip-paul-mueller opened this issue Feb 4, 2025 · 2 comments
Open

Memory Bug, Possible Undefined Baheviour #1921

philip-paul-mueller opened this issue Feb 4, 2025 · 2 comments

Comments

@philip-paul-mueller
Copy link
Collaborator

philip-paul-mueller commented Feb 4, 2025

Create the following unit test file, which is based on program_strides_2() from tests/numpy/array_creation_test.py.

import dace
import numpy as np
import copy

@dace.program
def program():
    # Was originally `program_strides_2()` in `tests/numpy/array_creation_test.py`.
    A = dace.ndarray((2, 2), dtype=dace.int32, strides=(1, 2))
    for i, j in dace.map[0:2, 0:2]:
        A[i, j] = j + i
    return A

def _impl_of_memory_test():
    sdfg = program.to_sdfg()
    csdfg = sdfg.compile()

    A1 = csdfg()
    Res1 = copy.deepcopy(A1)
    A2 = csdfg()
    assert A1 is A2
    Res2 = copy.deepcopy(A2)

    assert A2.strides == (4, 8)
    assert np.allclose(Res2, [[0, 1], [1, 2]]), "Never expected that this fails."
    assert np.allclose(Res1, [[0, 1], [1, 2]]), "Expected that this fails."

def test():
    _impl_of_memory_test()

if __name__ == "__main__":
    test()

Note that the bug does not surfaces every time, so you have to call the test in the following way:

for i in $(seq 100) ; do echo "ITERATION ${i}" ; pytest memory_issue_test.py --pdb ; done

It usually happens within the first 10 to 20 iterations.

I tried the following:

  • If the Maps are serial, then it works as expected.
  • If we add an additional operation, i.e. at the end we add A[0, 0] += 1 the error still surfaces.
  • If we set may_alias of __return to True it still fails.
  • if we change the assignment to any of A[i, j] = 0, A[i, j] = i or A[i, j] = j then the test passes.
  • If we move the for loop that calls the test, i.e. the bash loop suggested above, into python, then it seems that the bug is not triggered.
  • Passing A as an argument, i.e. allocating it explicitly, the result is still the same, it fails; See below for the code.
  • I do not observe the issue if A is allocated in C order (for that I used the version that passes A as argument).

I tried it on commit 8c24a34, which was main at the point.

I am using Python 3.9.20 and 3.12.3 (but I have the impression that it happens less often for it).

philip-paul-mueller added a commit to philip-paul-mueller/dace that referenced this issue Feb 4, 2025
I created an [issue](spcl#1921), I think it is now time to run the CI until I pass, whcih is not a very good stragety.
However, I wonder why it happens soley to me.
@philip-paul-mueller
Copy link
Collaborator Author

philip-paul-mueller commented Feb 6, 2025

Here is a second reproducer, that allocates the array outside.

  • If the memory order of A is switched from F to C then the code works as expected (did not crash within 100 invocations).
  • If A is not initialized with np.empty but with np.zeros it works as expected (this is the same behaviour I see; Only the second write takes effect).
import dace
import numpy as np
import copy

# Setting `order` to `C` will stop the issues.
order = "F"

if order == "F":
    AType = dace.data.Array(dace.int32, shape=(2, 2), strides=(1, 2))
else:
    assert order == "C"
    AType = dace.data.Array(dace.int32, shape=(2, 2))

# Was originally `program_strides_2()` in `tests/numpy/array_creation_test.py`.
@dace.program
def program(A: AType):
    for i, j in dace.map[0:2, 0:2]:
        A[i, j] = j + i

def _impl_of_memory_test():
    sdfg = program.to_sdfg()
    csdfg = sdfg.compile()

    A = np.empty((2, 2), dtype=np.int32, order=order)
    if order == "F":
        assert A.strides == (4, 8)
    else:
        assert A.strides == (8, 4)

    csdfg(A)
    Res1 = copy.deepcopy(A)
    csdfg(A)
    Res2 = copy.deepcopy(A)

    assert np.all((Res1 - Res2) == 0)
    assert np.allclose(Res2, [[0, 1], [1, 2]])
    assert np.allclose(Res1, [[0, 1], [1, 2]])

def test():
    _impl_of_memory_test()

if __name__ == "__main__":
    test()

@philip-paul-mueller
Copy link
Collaborator Author

philip-paul-mueller commented Feb 6, 2025

This is a reproducer, that also fails, here the copy operation is omitted, so we get the genuine true output:

import dace
import numpy as np

@dace.program
def program():
    # Was originally `program_strides_2()` in `tests/numpy/array_creation_test.py`.
    A = dace.ndarray((2, 2), dtype=dace.int32, strides=(1, 2))
    for i, j in dace.map[0:2, 0:2]:
        A[i, j] = j + i
    return A

def _impl_of_memory_test():
    sdfg = program.to_sdfg()
    csdfg = sdfg.compile()

    A1 = csdfg()
    assert np.allclose(A1, [[0, 1], [1, 2]])
    assert A1.strides == (4, 8)
    A2 = csdfg()
    assert np.allclose(A2, [[0, 1], [1, 2]])
    assert A2.strides == (4, 8)

def test():
    _impl_of_memory_test()

if __name__ == "__main__":
    test()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant