@@ -1202,7 +1202,16 @@ def test_cast_in_mul_canonizer():
12021202
12031203
12041204class TestFusion :
1205- mode = copy .copy (compile .mode .get_default_mode ())
1205+ opts = theano .gof .Query (
1206+ include = [
1207+ "local_elemwise_fusion" ,
1208+ "composite_elemwise_fusion" ,
1209+ "canonicalize" ,
1210+ "inplace" ,
1211+ ],
1212+ exclude = ["cxx_only" , "BlasOpt" ],
1213+ )
1214+ mode = theano .compile .Mode (compile .mode .get_default_mode ().linker , opts )
12061215 _shared = staticmethod (shared )
12071216 topo_exclude = ()
12081217
@@ -1879,10 +1888,6 @@ def my_init(shp, dtype="float64", num=0):
18791888 atol = 1e-6
18801889 if not np .allclose (out , answer * nb_repeat , atol = atol ):
18811890 fail1 .append (id )
1882- print ("cases" , id )
1883- print (val_inputs )
1884- print (out )
1885- print (answer * nb_repeat )
18861891 topo = f .maker .fgraph .toposort ()
18871892 topo_ = [n for n in topo if not isinstance (n .op , self .topo_exclude )]
18881893 if assert_len_topo :
@@ -1905,52 +1910,39 @@ def my_init(shp, dtype="float64", num=0):
19051910 if not out_dtype == out .dtype :
19061911 fail4 .append ((id , out_dtype , out .dtype ))
19071912
1908- failed = len (fail1 + fail2 + fail3 + fail4 )
1909- if failed > 0 :
1910- print ("Executed" , len (cases ), "cases" , "failed" , failed )
1911- raise Exception ("Failed %d cases" % failed , fail1 , fail2 , fail3 , fail4 )
1913+ assert len (fail1 + fail2 + fail3 + fail4 ) == 0
19121914
19131915 return times
19141916
19151917 def test_elemwise_fusion (self ):
19161918 shp = (5 , 5 )
1917- mode = copy .copy (self .mode )
1918- # we need the optimisation enabled and the canonicalize.
1919- # the canonicalize is needed to merge multiplication/addition by constant.
1920- mode ._optimizer = mode ._optimizer .including (
1921- "local_elemwise_fusion" , "composite_elemwise_fusion" , "canonicalize"
1922- )
1923- self .do (mode , self ._shared , shp )
1919+ self .do (self .mode , self ._shared , shp )
19241920
1925- @pytest .mark .slow
1926- def test_elemwise_fusion_4d (self ):
1927- shp = (3 , 3 , 3 , 3 )
1928- mode = copy .copy (self .mode )
1929- # we need the optimisation enabled and the canonicalize.
1930- # the canonicalize is needed to merge multiplication/addition by constant.
1931- mode ._optimizer = mode ._optimizer .including (
1932- "local_elemwise_fusion" , "composite_elemwise_fusion" , "canonicalize"
1933- )
1934- self .do (mode , self ._shared , shp , slice = slice (0 , 1 ))
1935-
1936- def test_fusion_35inputs (self ):
1937- # Make sure a fused graph with more than 35 inputs does not segfault
1938- # or error.
1921+ def test_fusion_35_inputs (self ):
1922+ """Make sure we don't fuse too many `Op`s and go past the 31 function arguments limit."""
19391923 inpts = vectors (["i%i" % i for i in range (35 )])
1924+
19401925 # Make an elemwise graph looking like:
19411926 # sin(i34 + sin(i33 + sin(... i1 + sin(i0) ...)))
19421927 out = tt .sin (inpts [0 ])
19431928 for idx in range (1 , 35 ):
19441929 out = tt .sin (inpts [idx ] + out )
19451930
1946- f = function (inpts , out , mode = self .mode )
1947- # Test it on some dummy values
1948- f (* [list (range (i , 4 + i )) for i in range (35 )])
1931+ with theano .change_flags (cxx = "" ):
1932+ f = function (inpts , out , mode = self .mode )
1933+
1934+ # Make sure they all weren't fused
1935+ composite_nodes = [
1936+ node
1937+ for node in f .maker .fgraph .toposort ()
1938+ if isinstance (getattr (node .op , "scalar_op" , None ), scal .basic .Composite )
1939+ ]
1940+ assert not any (len (node .inputs ) > 31 for node in composite_nodes )
19491941
19501942 @pytest .mark .skipif (not theano .config .cxx , reason = "No cxx compiler" )
1951- def test_pickle_big_fusion (self ):
1943+ def test_big_fusion (self ):
19521944 # In the past, pickle of Composite generated in that case
1953- # crashed with max recusion limit. So we where not able to
1945+ # crashed with max recursion limit. So we were not able to
19541946 # generate C code in that case.
19551947 factors = []
19561948 sd = tt .dscalar ()
@@ -1974,8 +1966,47 @@ def test_pickle_big_fusion(self):
19741966 logp = tt .add (* factors )
19751967
19761968 vars = [sd , means ]
1977- dlogp = function (vars , [theano .grad (logp , v ) for v in vars ])
1978- dlogp (2 , np .random .rand (n ))
1969+
1970+ # Make sure that C compilation is used
1971+ mode = theano .compile .Mode ("cvm" , self .opts )
1972+ dlogp = function (vars , [theano .grad (logp , v ) for v in vars ], mode = mode )
1973+
1974+ # Make sure something was fused
1975+ assert any (
1976+ isinstance (getattr (node .op , "scalar_op" , None ), scal .basic .Composite )
1977+ for node in dlogp .maker .fgraph .toposort ()
1978+ )
1979+
1980+ def test_add_mul_fusion_inplace (self ):
1981+
1982+ opts = theano .gof .Query (
1983+ include = [
1984+ "local_elemwise_fusion" ,
1985+ "composite_elemwise_fusion" ,
1986+ "canonicalize" ,
1987+ "inplace" ,
1988+ ],
1989+ exclude = ["cxx_only" , "BlasOpt" ],
1990+ )
1991+
1992+ mode = theano .compile .mode .Mode (self .mode .linker , opts )
1993+
1994+ x , y , z = dmatrices ("xyz" )
1995+ out = tt .dot (x , y ) + x + y + z
1996+ f = function ([x , y , z ], out , mode = mode )
1997+ topo = [n for n in f .maker .fgraph .toposort ()]
1998+ assert len (topo ) == 2
1999+ assert topo [- 1 ].op .inplace_pattern
2000+
2001+ new_out = f .maker .fgraph .outputs [0 ]
2002+ assert isinstance (new_out .owner .op , Elemwise )
2003+ assert isinstance (new_out .owner .op .scalar_op , scal .basic .Add )
2004+ assert len (new_out .owner .inputs ) == 4
2005+
2006+ # TODO: Do we really need to do this?
2007+ _ = f (
2008+ np .random .random ((5 , 5 )), np .random .random ((5 , 5 )), np .random .random ((5 , 5 ))
2009+ )
19792010
19802011 def speed_fusion (self , s = None ):
19812012 """
@@ -2035,28 +2066,6 @@ def speed_fusion(self, s=None):
20352066 d .std (),
20362067 )
20372068
2038- def test_fusion_inplace (self ):
2039- mode = copy .copy (self .mode )
2040- # we need the optimisation enabled and the canonicalize.
2041- # the canonicalize is needed to merge multiplication/addition by constant.
2042- mode ._optimizer = mode ._optimizer .including (
2043- "local_elemwise_fusion" ,
2044- "composite_elemwise_fusion" ,
2045- "canonicalize" ,
2046- "inplace" ,
2047- )
2048-
2049- x , y , z = dmatrices ("xyz" )
2050- f = function ([x , y , z ], tt .dot (x , y ) + x + y + z , mode = mode )
2051- topo = [
2052- n
2053- for n in f .maker .fgraph .toposort ()
2054- if not isinstance (n .op , self .topo_exclude )
2055- ]
2056- assert len (topo ) == 2
2057- assert topo [- 1 ].op .inplace_pattern
2058- f (np .random .random ((5 , 5 )), np .random .random ((5 , 5 )), np .random .random ((5 , 5 )))
2059-
20602069 def speed_log_exp (self ):
20612070 s = slice (31 , 36 )
20622071 print (
@@ -2071,6 +2080,34 @@ def speed_log_exp(self):
20712080 ),
20722081 )
20732082
2083+ @pytest .mark .skipif (not theano .config .cxx , reason = "No cxx compiler" )
2084+ def test_no_c_code (self ):
2085+ """Make sure we avoid fusions for `Op`s without C code implementations."""
2086+
2087+ # This custom `Op` has no `c_code` method
2088+ class NoCCodeOp (scal .basic .UnaryScalarOp ):
2089+ def impl (self , x ):
2090+ return x * 2
2091+
2092+ no_c_code_op = Elemwise (NoCCodeOp (scal .basic .upgrade_to_float ))
2093+
2094+ mode = theano .Mode (linker = "cvm" )
2095+ mode ._optimizer = mode ._optimizer .including (
2096+ "local_elemwise_fusion" ,
2097+ "composite_elemwise_fusion" ,
2098+ "canonicalize" ,
2099+ "inplace" ,
2100+ )
2101+
2102+ x = tt .vector ()
2103+ out = x * no_c_code_op (x + 1 )
2104+ f = function ([x ], out , mode = mode )
2105+
2106+ assert not any (
2107+ isinstance (getattr (n .op , "scalar_op" ), scal .basic .Composite )
2108+ for n in f .maker .fgraph .toposort ()
2109+ )
2110+
20742111
20752112class TimesN (scal .basic .UnaryScalarOp ):
20762113 """
0 commit comments