提交 ce1fdfdf authored 作者: Brandon T. Willard's avatar Brandon T. Willard 提交者: Brandon T. Willard

Fix versioning of empty __props__ in CLinker

上级 91303337
...@@ -1461,8 +1461,12 @@ class CLinker(Linker): ...@@ -1461,8 +1461,12 @@ class CLinker(Linker):
for node_pos, node in enumerate(order): for node_pos, node in enumerate(order):
if hasattr(node.op, "c_code_cache_version_apply"): if hasattr(node.op, "c_code_cache_version_apply"):
version.append(node.op.c_code_cache_version_apply(node)) version.append(node.op.c_code_cache_version_apply(node))
if hasattr(node.op, "__props__"):
version.append(node.op.__props__) props = getattr(node.op, "__props__", None)
if props:
version.append(props)
for i in node.inputs: for i in node.inputs:
if isinstance(i.type, CLinkerObject): if isinstance(i.type, CLinkerObject):
version.append(i.type.c_code_cache_version()) version.append(i.type.c_code_cache_version())
......
...@@ -7,7 +7,7 @@ def pytest_sessionstart(session): ...@@ -7,7 +7,7 @@ def pytest_sessionstart(session):
os.environ["AESARA_FLAGS"] = ",".join( os.environ["AESARA_FLAGS"] = ",".join(
[ [
os.environ.setdefault("AESARA_FLAGS", ""), os.environ.setdefault("AESARA_FLAGS", ""),
"warn__ignore_bug_before=all,on_opt_error=raise,on_shape_error=raise", "warn__ignore_bug_before=all,on_opt_error=raise,on_shape_error=raise,cmodule__warn_no_version=True",
] ]
) )
......
...@@ -455,3 +455,43 @@ def test_shared_input_output(): ...@@ -455,3 +455,43 @@ def test_shared_input_output():
gv0 = gv(0) gv0 = gv(0)
assert np.all(fv0 == 5), fv0 assert np.all(fv0 == 5), fv0
assert np.all(gv0 == 5), gv0 assert np.all(gv0 == 5), gv0
def test_cmodule_key_empty_props():
"""Make sure `CLinker.cmodule_key_` is correct when `COp.__props__` is empty."""
class MyAdd(COp):
__props__ = ()
def make_node(self, *inputs):
inputs = list(map(as_variable, inputs))
outputs = [tdouble()]
return Apply(self, inputs, outputs)
def __str__(self):
return self.name
def perform(self, node, inputs, out_):
(out,) = out_
out[0] = sum(*inputs)
def c_code_cache_version(self):
return (1,)
def c_code(self, node, name, inp, out, sub):
x, y = inp
(z,) = out
return f"{z} = {x} + {y};"
x = tdouble("x")
y = tdouble("y")
z = MyAdd()(x, y)
fg = FunctionGraph(outputs=[z])
linker = CLinker()
linker.accept(fg)
key = linker.cmodule_key()
# None of the C version values should be empty
assert all(kv for kv in key[0])
...@@ -18,9 +18,13 @@ import aesara.tensor as at ...@@ -18,9 +18,13 @@ import aesara.tensor as at
from aesara.compile.function import function from aesara.compile.function import function
from aesara.compile.ops import DeepCopyOp from aesara.compile.ops import DeepCopyOp
from aesara.configdefaults import config from aesara.configdefaults import config
from aesara.link.c.cmodule import GCC_compiler, default_blas_ldflags from aesara.graph.basic import Apply
from aesara.graph.fg import FunctionGraph
from aesara.link.c.basic import CLinker
from aesara.link.c.cmodule import GCC_compiler, ModuleCache, default_blas_ldflags
from aesara.link.c.exceptions import CompileError from aesara.link.c.exceptions import CompileError
from aesara.tensor.type import dvectors from aesara.link.c.op import COp
from aesara.tensor.type import dvectors, vector
class MyOp(DeepCopyOp): class MyOp(DeepCopyOp):
...@@ -43,20 +47,47 @@ class MyOp(DeepCopyOp): ...@@ -43,20 +47,47 @@ class MyOp(DeepCopyOp):
return super(DeepCopyOp, self).c_code(node, name, inames, onames, sub) return super(DeepCopyOp, self).c_code(node, name, inames, onames, sub)
class MyAdd(COp):
__props__ = ()
def make_node(self, *inputs):
outputs = [vector()]
return Apply(self, inputs, outputs)
def perform(self, node, inputs, out_):
(out,) = out_
out[0] = inputs[0][0] + 1
def c_code(self, node, name, inp, out, sub):
(x,) = inp
(z,) = out
return f"{z} = {x} + 1;"
class MyAddVersioned(MyAdd):
def c_code_cache_version(self):
return (1,)
def test_compiler_error(): def test_compiler_error():
with pytest.raises(CompileError), tempfile.TemporaryDirectory() as dir_name: with pytest.raises(CompileError), tempfile.TemporaryDirectory() as dir_name:
GCC_compiler.compile_str("module_name", "blah", location=dir_name) GCC_compiler.compile_str("module_name", "blah", location=dir_name)
def test_inter_process_cache(): def test_inter_process_cache():
# When an op with c_code, but no version. If we have 2 apply node """
# in the graph with different inputs variable(so they don't get TODO FIXME: This explanation is very poorly written.
# merged) but the inputs variable have the same type, do we reuse
# the same module? Even if they would generate different c_code? When a `COp` with `COp.c_code`, but no version. If we have two `Apply`
# Currently this test show that we generate the c_code only once. nodes in a graph with distinct inputs variable, but the input variables
# have the same `Type`, do we reuse the same module? Even if they would
# This is to know if the c_code can add information specific to the generate different `COp.c_code`? Currently this test show that we generate
# node.inputs[*].owner like the name of the variable. the `COp.c_code` only once.
This is to know if the `COp.c_code` can add information specific to the
``node.inputs[*].owner`` like the name of the variable.
"""
x, y = dvectors("xy") x, y = dvectors("xy")
f = function([x, y], [MyOp()(x), MyOp()(y)]) f = function([x, y], [MyOp()(x), MyOp()(y)])
...@@ -76,12 +107,58 @@ def test_inter_process_cache(): ...@@ -76,12 +107,58 @@ def test_inter_process_cache():
assert MyOp.nb_called == 1 assert MyOp.nb_called == 1
@pytest.mark.filterwarnings("error")
def test_cache_versioning():
"""Make sure `ModuleCache._add_to_cache` is working."""
my_add = MyAdd()
with pytest.warns(match=".*specifies no C code cache version.*"):
assert my_add.c_code_cache_version() == ()
my_add_ver = MyAddVersioned()
assert my_add_ver.c_code_cache_version() == (1,)
assert len(MyOp.__props__) == 0
assert len(MyAddVersioned.__props__) == 0
x = vector("x")
z = my_add(x)
z_v = my_add_ver(x)
with tempfile.TemporaryDirectory() as dir_name:
cache = ModuleCache(dir_name)
lnk = CLinker().accept(FunctionGraph(outputs=[z]))
with pytest.warns(match=".*specifies no C code cache version.*"):
key = lnk.cmodule_key()
assert key[0] == ()
with pytest.warns(match=".*c_code_cache_version.*"):
cache.module_from_key(key, lnk)
lnk_v = CLinker().accept(FunctionGraph(outputs=[z_v]))
key_v = lnk_v.cmodule_key()
assert len(key_v[0]) > 0
assert key_v not in cache.entry_from_key
stats_before = cache.stats[2]
cache.module_from_key(key_v, lnk_v)
assert stats_before < cache.stats[2]
def test_flag_detection(): def test_flag_detection():
# Check that the code detecting blas flags does not raise any exception. """
# It used to happen on python 3 because of improper string handling, TODO FIXME: This is a very poor test.
# but was not detected because that path is not usually taken,
# so we test it here directly. Check that the code detecting blas flags does not raise any exception.
GCC_compiler.try_flags(["-lblas"]) It used to happen on Python 3 because of improper string handling,
but was not detected because that path is not usually taken,
so we test it here directly.
"""
res = GCC_compiler.try_flags(["-lblas"])
assert isinstance(res, bool)
@patch("aesara.link.c.cmodule.try_blas_flag", return_value=None) @patch("aesara.link.c.cmodule.try_blas_flag", return_value=None)
......
...@@ -11,11 +11,7 @@ from aesara.link.c.type import CDataType, CEnumType, EnumList, EnumType ...@@ -11,11 +11,7 @@ from aesara.link.c.type import CDataType, CEnumType, EnumList, EnumType
from aesara.tensor.type import TensorType, continuous_dtypes from aesara.tensor.type import TensorType, continuous_dtypes
@pytest.mark.skipif( class ProdOp(COp):
not aesara.config.cxx, reason="G++ not available, so we need to skip this test."
)
def test_cdata():
class ProdOp(COp):
__props__ = () __props__ = ()
def make_node(self, i): def make_node(self, i):
...@@ -23,17 +19,17 @@ def test_cdata(): ...@@ -23,17 +19,17 @@ def test_cdata():
def c_support_code(self, **kwargs): def c_support_code(self, **kwargs):
return """ return """
void py_decref(void *p) { void py_decref(void *p) {
Py_XDECREF((PyObject *)p); Py_XDECREF((PyObject *)p);
} }
""" """
def c_code(self, node, name, inps, outs, sub): def c_code(self, node, name, inps, outs, sub):
return """ return """
Py_XDECREF(%(out)s); Py_XDECREF(%(out)s);
%(out)s = (void *)%(inp)s; %(out)s = (void *)%(inp)s;
Py_INCREF(%(inp)s); Py_INCREF(%(inp)s);
""" % dict( """ % dict(
out=outs[0], inp=inps[0] out=outs[0], inp=inps[0]
) )
...@@ -43,7 +39,8 @@ def test_cdata(): ...@@ -43,7 +39,8 @@ def test_cdata():
def perform(self, *args, **kwargs): def perform(self, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
class GetOp(COp):
class GetOp(COp):
__props__ = () __props__ = ()
def make_node(self, c): def make_node(self, c):
...@@ -51,17 +48,17 @@ def test_cdata(): ...@@ -51,17 +48,17 @@ def test_cdata():
def c_support_code(self, **kwargs): def c_support_code(self, **kwargs):
return """ return """
void py_decref(void *p) { void py_decref(void *p) {
Py_XDECREF((PyObject *)p); Py_XDECREF((PyObject *)p);
} }
""" """
def c_code(self, node, name, inps, outs, sub): def c_code(self, node, name, inps, outs, sub):
return """ return """
Py_XDECREF(%(out)s); Py_XDECREF(%(out)s);
%(out)s = (PyArrayObject *)%(inp)s; %(out)s = (PyArrayObject *)%(inp)s;
Py_INCREF(%(out)s); Py_INCREF(%(out)s);
""" % dict( """ % dict(
out=outs[0], inp=inps[0] out=outs[0], inp=inps[0]
) )
...@@ -71,6 +68,11 @@ def test_cdata(): ...@@ -71,6 +68,11 @@ def test_cdata():
def perform(self, *args, **kwargs): def perform(self, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
@pytest.mark.skipif(
not aesara.config.cxx, reason="G++ not available, so we need to skip this test."
)
def test_cdata():
i = TensorType("float32", (False,))() i = TensorType("float32", (False,))()
c = ProdOp()(i) c = ProdOp()(i)
i2 = GetOp()(c) i2 = GetOp()(c)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论