Repository: Theano/Theano

Issue:
Attribute Error Loading pickled Functions using GpuGemm
While trying to load a pickled theano function I get an AttributeError (traceback printed below). I'm using HEAD. The following patch fixes this problem (although I doubt it's the correct fix):

```
diff --git a/theano/sandbox/cuda/blas.py b/theano/sandbox/cuda/blas.py
index b109e39..86c8322 100644
--- a/theano/sandbox/cuda/blas.py
+++ b/theano/sandbox/cuda/blas.py
@@ -400,6 +400,7 @@ class GpuGemm(GpuOp):
     implement the gemm on the gpu.

     """
+    _op_use_c_code = theano.config.cxx
     def __init__(self, inplace):
         self.__setstate__({'inpfull lace': inplace})
```

Traceback:

```
  File "/users/nboley/src/TF_binding/rSeqDNN/KerasModel.py", line 115, in compile
    self.model = pickle.load(fp)
  File "/users/nboley/src/Theano/theano/compile/function_module.py", line 1018, in _constructor_Function
    f = maker.create(input_storage, trustme=True)
  File "/users/nboley/src/Theano/theano/compile/function_module.py", line 1642, in create
    input_storage=input_storage_lists, storage_map=storage_map)
  File "/users/nboley/src/Theano/theano/gof/link.py", line 690, in make_thunk
    storage_map=storage_map)[:3]
  File "/users/nboley/src/Theano/theano/gof/vm.py", line 1037, in make_all
    no_recycling))
  File "/users/nboley/src/Theano/theano/sandbox/cuda/__init__.py", line 284, in make_thunk
    compute_map, no_recycling)
  File "/users/nboley/src/Theano/theano/gof/op.py", line 915, in make_thunk
    if self._op_use_c_code:
AttributeError: (AttributeError('The following error happened while compiling the node', GpuGemm{no_inplace}(<CudaNdarrayType(float32, matrix)>, TensorConstant{0.20000000298}, GpuElemwise{mul,no_inplace}.0
, <CudaNdarrayType(float32, matrix)>, TensorConstant{0.20000000298}), '\n', "'GpuGemm' object has no attribute '_op_use_c_code'"), <function _constructor_Function at 0x7f0febfa1cf8>, (<theano.compile.funct
ion_module.FunctionMaker object at 0x7f0f503aae50>, [<None>, <None>, <None>, <None>, <None>, <None>, <None>, <None>], [None, None, None, None, None, None, None, None]))
nboley@nandi:/srv/scratch/nboley/tmp/test_new_relaxed_peaks$ THEANO_FLAGS='mode=FAST_RUN,device=gpu2,floatX=float32,optimizer=fast_compile,warn_float64=warn,reoptimize_unpickled_function=False' python ~/sr
c/TF_binding/rSeqDNN/TrainModel.py --annotation-id 1 --tf-id T011266_1.02 --model-prefix MAX.tmp --only-test-one-fold --half-peak-width 500 --max-num-peaks-per-sample 500
```



Discussion:
Thanks for reporting the issue.
I have a tentative fix that I'll try to submit as a PR soon.


Resolving patch:
diff --git a/theano/sandbox/cuda/blas.py b/theano/sandbox/cuda/blas.py
--- a/theano/sandbox/cuda/blas.py
+++ b/theano/sandbox/cuda/blas.py
@@ -401,7 +401,9 @@ class GpuGemm(GpuOp):
 
     """
     def __init__(self, inplace):
-        self.__setstate__({'inplace': inplace})
+        self.inplace = inplace
+        if self.inplace:
+            self.destroy_map = {0: [0]}
 
     def __str__(self):
         if self.inplace:
@@ -417,13 +419,14 @@ def __hash__(self):
         return hash(type(self)) ^ hash(self.inplace)
 
     def __setstate__(self, dct):
-        inplace = dct.get('inplace', True)
-        if inplace:
-            self.destroy_map = {0: [0]}
-        self.inplace = inplace
+        self.__dict__.update(dct)
 
-    def __getstate__(self):
-        return dict(inplace=self.inplace)
+        # Correctly reload older pickles where _op_use_c_code and
+        # destroy_map were not saved
+        if '_op_use_c_code' not in self.__dict__:
+            self._op_use_c_code = theano.config.cxx
+        if 'destroy_map' not in self.__dict__ and self.inplace:
+            self.destroy_map = {0: [0]}
 
     def make_node(self, z, a, x, y, b):
         # the more complicated error checking performed by tensor.gemm
@@ -518,7 +521,9 @@ class GpuGemv(GpuOp):
 
     """
     def __init__(self, inplace):
-        self.__setstate__({'inplace': inplace})
+        self.inplace = inplace
+        if self.inplace:
+            self.destroy_map = {0: [0]}
 
     def __str__(self):
         if self.inplace:
@@ -534,13 +539,14 @@ def __hash__(self):
         return hash(type(self)) ^ hash(self.inplace)
 
     def __setstate__(self, dct):
-        inplace = dct.get('inplace', True)
-        if inplace:
-            self.destroy_map = {0: [0]}
-        self.inplace = inplace
+        self.__dict__.update(dct)
 
-    def __getstate__(self):
-        return dict(inplace=self.inplace)
+        # Correctly reload older pickles where _op_use_c_code and
+        # destroy_map were not saved
+        if '_op_use_c_code' not in self.__dict__:
+            self._op_use_c_code = theano.config.cxx
+        if 'destroy_map' not in self.__dict__ and self.inplace:
+            self.destroy_map = {0: [0]}
 
     def make_node(self, z, a, x, y, b):
         # the more complicated error checking performed by tensor.gemv
@@ -615,7 +621,9 @@ class GpuGer(GpuOp):
 
     """
     def __init__(self, inplace):
-        self.__setstate__({'inplace': inplace})
+        self.inplace = inplace
+        if self.inplace:
+            self.destroy_map = {0: [0]}
 
     def __str__(self):
         if self.inplace:
@@ -631,13 +639,14 @@ def __hash__(self):
         return hash(type(self)) ^ hash(self.inplace)
 
     def __setstate__(self, dct):
-        inplace = dct.get('inplace', True)
-        if inplace:
-            self.destroy_map = {0: [0]}
-        self.inplace = inplace
+        self.__dict__.update(dct)
 
-    def __getstate__(self):
-        return dict(inplace=self.inplace)
+        # Correctly reload older pickles where _op_use_c_code and
+        # destroy_map were not saved
+        if '_op_use_c_code' not in self.__dict__:
+            self._op_use_c_code = theano.config.cxx
+        if 'destroy_map' not in self.__dict__ and self.inplace:
+            self.destroy_map = {0: [0]}
 
     def make_node(self, z, a, x, y):
         # the more complicated error checking performed by tensor.ger is
diff --git a/theano/tensor/blas.py b/theano/tensor/blas.py
--- a/theano/tensor/blas.py
+++ b/theano/tensor/blas.py
@@ -962,7 +962,12 @@ class Gemm(GemmRelated):
     E_float = 'gemm requires floating-point dtypes'
 
     def __init__(self, inplace):
-        self.__setstate__({'inplace': inplace})
+        self.inplace = inplace
+        if self.inplace:
+            self.destroy_map = {0: [0]}
+            self.setup_z_Nz_Sz = self.setup_z_Nz_Sz_inplace
+        else:
+            self.setup_z_Nz_Sz = self.setup_z_Nz_Sz_outplace
 
     def __eq__(self, other):
         return (type(self) == type(other) and
@@ -979,16 +984,25 @@ def __str__(self):
         return '%s{%s}' % (self.__class__.__name__, inplace_str)
 
     def __setstate__(self, dct):
-        inplace = dct.get('inplace', True)
-        if inplace:
-            self.destroy_map = {0: [0]}
+        self.__dict__.update(dct)
+        if self.inplace:
             self.setup_z_Nz_Sz = self.setup_z_Nz_Sz_inplace
         else:
             self.setup_z_Nz_Sz = self.setup_z_Nz_Sz_outplace
-        self.inplace = inplace
+
+        # Correctly reload older pickles where _op_use_c_code and
+        # destroy_map were not saved
+        if '_op_use_c_code' not in self.__dict__:
+            self._op_use_c_code = theano.config.cxx
+        if 'destroy_map' not in self.__dict__ and self.inplace:
+            self.destroy_map = {0: [0]}
 
     def __getstate__(self):
-        return dict(inplace=self.inplace)
+        rval = self.__dict__.copy()
+        # Do not serialize the setup code, it will be restored in __setstate__
+        # depending on the value of 'inplace'
+        rval.pop('setup_z_Nz_Sz')
+        return rval
 
     def make_node(self, *inputs):
         inputs = list(map(T.as_tensor_variable, inputs))


Test patch: