Index: sasmodels/core.py
===================================================================
--- sasmodels/core.py	(revision 190fc2b167d3d89ca9c3b7d3128bdf8bde9f67d0)
+++ sasmodels/core.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -118,6 +118,5 @@
     Return a computation kernel from the model definition and the q input.
     """
-    model_input = model.make_input(q_vectors)
-    return model(model_input)
+    return model(q_vectors)
 
 def get_weights(info, pars, name):
Index: sasmodels/data.py
===================================================================
--- sasmodels/data.py	(revision 299edd28798919725aa02bc45a5123571e192e20)
+++ sasmodels/data.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -121,5 +121,5 @@
         # TODO: why is 1D mask False and 2D mask True?
         self.mask = (np.isnan(y) if y is not None
-                     else np.zeros_like(x,'b') if x is not None
+                     else np.zeros_like(x, 'b') if x is not None
                      else None)
         self._xaxis, self._xunit = "x", ""
@@ -350,4 +350,7 @@
     """
     def wrapper(*args, **kw):
+        """
+        Trap and print errors from function %s
+        """%fn.__name__
         try:
             return fn(*args, **kw)
Index: sasmodels/direct_model.py
===================================================================
--- sasmodels/direct_model.py	(revision 803f8354f3fdd4c40b749b45e39404acb013d66b)
+++ sasmodels/direct_model.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -149,6 +149,5 @@
     def _calc_theory(self, pars, cutoff=0.0):
         if self._kernel is None:
-            q_input = self._model.make_input(self._kernel_inputs)
-            self._kernel = self._model(q_input)  # pylint: disable=attribute-defined-outside-init
+            self._kernel = make_kernel(self._model, self._kernel_inputs)  # pylint: disable=attribute-defined-outside-init
 
         Iq_calc = call_kernel(self._kernel, pars, cutoff=cutoff)
@@ -177,5 +176,4 @@
         # Note: _interpret_data defines the model attributes
         self._interpret_data(data, model)
-        self.kernel = make_kernel(self.model, self._kernel_inputs)
 
     def __call__(self, **pars):
Index: sasmodels/generate.py
===================================================================
--- sasmodels/generate.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
+++ sasmodels/generate.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -313,5 +313,5 @@
     raise ValueError("%r not found in %s" % (filename, search_path))
 
-def sources(info):
+def model_sources(info):
     """
     Return a list of the sources file paths for the module.
@@ -465,5 +465,5 @@
 
     # Load additional sources
-    source = [open(f).read() for f in sources(info)]
+    source = [open(f).read() for f in model_sources(info)]
 
     # Prepare defines
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
+++ sasmodels/kernelcl.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -1,4 +1,4 @@
 """
-GPU support through OpenCL
+GPU driver for C kernels
 
 There should be a single GPU environment running on the system.  This
@@ -152,10 +152,4 @@
 
 
-def make_result(self, size):
-    self.res = np.empty(size, dtype=self.dtype)
-    self.res_b = cl.Buffer(self.program.context, mf.READ_WRITE, self.res.nbytes)
-    return self.res, self.res_b
-
-
 # for now, this returns one device in the context
 # TODO: create a context that contains all devices on all platforms
@@ -183,8 +177,16 @@
 
     def has_type(self, dtype):
+        """
+        Return True if all devices support a given type.
+        """
         dtype = generate.F32 if dtype == 'fast' else np.dtype(dtype)
         return all(has_type(d, dtype) for d in self.context.devices)
 
     def _create_some_context(self):
+        """
+        Protected call to cl.create_some_context without interactivity.  Use
+        this if PYOPENCL_CTX is set in the environment.  Sets the *context*
+        attribute.
+        """
         try:
             self.context = cl.create_some_context(interactive=False)
@@ -195,4 +197,7 @@
 
     def compile_program(self, name, source, dtype, fast=False):
+        """
+        Compile the program for the device in the given context.
+        """
         key = "%s-%s-%s"%(name, dtype, fast)
         if key not in self.compiled:
@@ -204,4 +209,7 @@
 
     def release_program(self, name):
+        """
+        Free memory associated with the program on the device.
+        """
         if name in self.compiled:
             self.compiled[name].release()
@@ -209,4 +217,7 @@
 
 def _get_default_context():
+    """
+    Get an OpenCL context, preferring GPU over CPU.
+    """
     default = None
     for platform in cl.get_platforms():
@@ -246,37 +257,30 @@
 
     def __getstate__(self):
-        state = self.__dict__.copy()
-        state['program'] = None
-        return state
+        return self.info, self.source, self.dtype, self.fast
 
     def __setstate__(self, state):
-        self.__dict__ = state.copy()
-
-    def __call__(self, q_input):
-        if self.dtype != q_input.dtype:
-            raise TypeError("data is %s kernel is %s"
-                            % (q_input.dtype, self.dtype))
+        self.info, self.source, self.dtype, self.fast = state
+        self.program = None
+
+    def __call__(self, q_vectors):
         if self.program is None:
             compiler = environment().compile_program
             self.program = compiler(self.info['name'], self.source, self.dtype,
                                     self.fast)
-        kernel_name = generate.kernel_name(self.info, q_input.is_2D)
+        is_2d = len(q_vectors) == 2
+        kernel_name = generate.kernel_name(self.info, is_2d)
         kernel = getattr(self.program, kernel_name)
-        return GpuKernel(kernel, self.info, q_input)
+        return GpuKernel(kernel, self.info, q_vectors, self.dtype)
 
     def release(self):
+        """
+        Free the resources associated with the model.
+        """
         if self.program is not None:
             environment().release_program(self.info['name'])
             self.program = None
 
-    def make_input(self, q_vectors):
-        """
-        Make q input vectors available to the model.
-
-        Note that each model needs its own q vector even if the case of
-        mixture models because some models may be OpenCL, some may be
-        ctypes and some may be pure python.
-        """
-        return GpuInput(q_vectors, dtype=self.dtype)
+    def __del__(self):
+        self.release()
 
 # TODO: check that we don't need a destructor for buffers which go out of scope
@@ -304,5 +308,5 @@
         self.nq = q_vectors[0].size
         self.dtype = np.dtype(dtype)
-        self.is_2D = (len(q_vectors) == 2)
+        self.is_2d = (len(q_vectors) == 2)
         # TODO: stretch input based on get_warp()
         # not doing it now since warp depends on kernel, which is not known
@@ -317,18 +321,25 @@
 
     def release(self):
+        """
+        Free the memory.
+        """
         for b in self.q_buffers:
             b.release()
         self.q_buffers = []
 
+    def __del__(self):
+        self.release()
+
 class GpuKernel(object):
     """
     Callable SAS kernel.
 
-    *kernel* is the GpuKernel object to call.
+    *kernel* is the GpuKernel object to call
 
     *info* is the module information
 
-    *q_input* is the DllInput q vectors at which the kernel should be
-    evaluated.
+    *q_vectors* is the q vectors at which the kernel should be evaluated
+
+    *dtype* is the kernel precision
 
     The resulting call method takes the *pars*, a list of values for
@@ -340,10 +351,10 @@
     Call :meth:`release` when done with the kernel instance.
     """
-    def __init__(self, kernel, info, q_input):
-        self.q_input = q_input
+    def __init__(self, kernel, info, q_vectors, dtype):
+        q_input = GpuInput(q_vectors, dtype)
         self.kernel = kernel
         self.info = info
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         self.fixed_pars = info['partype']['fixed-' + dim]
         self.pd_pars = info['partype']['pd-' + dim]
@@ -358,5 +369,5 @@
                                 q_input.global_size[0] * q_input.dtype.itemsize)
                       for _ in env.queues]
-
+        self.q_input = q_input
 
     def __call__(self, fixed_pars, pd_pars, cutoff=1e-5):
@@ -399,4 +410,7 @@
 
     def release(self):
+        """
+        Release resources associated with the kernel.
+        """
         for b in self.loops_b:
             b.release()
@@ -405,4 +419,5 @@
             b.release()
         self.res_b = []
+        self.q_input.release()
 
     def __del__(self):
Index: sasmodels/kerneldll.py
===================================================================
--- sasmodels/kerneldll.py	(revision 823e6204f35d6c2bf32f27a3a3c137a369a973cf)
+++ sasmodels/kerneldll.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -1,4 +1,4 @@
 r"""
-C types wrapper for sasview models.
+DLL driver for C kernels
 
 The global attribute *ALLOW_SINGLE_PRECISION_DLLS* should be set to *True* if
@@ -44,4 +44,5 @@
 directory for this application, then OpenMP should be supported.
 """
+from __future__ import print_function
 
 import sys
@@ -139,5 +140,5 @@
 
     source = generate.convert_type(source, dtype)
-    source_files = generate.sources(info) + [info['filename']]
+    source_files = generate.model_sources(info) + [info['filename']]
     dll = dll_path(info, dtype)
     newest = max(os.path.getmtime(f) for f in source_files)
@@ -218,28 +219,20 @@
 
     def __getstate__(self):
-        return {'info': self.info, 'dllpath': self.dllpath, 'dll': None}
+        return self.info, self.dllpath
 
     def __setstate__(self, state):
-        self.__dict__ = state
-
-    def __call__(self, q_input):
-        if self.dtype != q_input.dtype:
-            raise TypeError("data is %s kernel is %s" % (q_input.dtype, self.dtype))
+        self.info, self.dllpath = state
+        self.dll = None
+
+    def __call__(self, q_vectors):
+        q_input = PyInput(q_vectors, self.dtype)
         if self.dll is None: self._load_dll()
-        kernel = self.Iqxy if q_input.is_2D else self.Iq
+        kernel = self.Iqxy if q_input.is_2d else self.Iq
         return DllKernel(kernel, self.info, q_input)
 
-    # pylint: disable=no-self-use
-    def make_input(self, q_vectors):
-        """
-        Make q input vectors available to the model.
-
-        Note that each model needs its own q vector even if the case of
-        mixture models because some models may be OpenCL, some may be
-        ctypes and some may be pure python.
-        """
-        return PyInput(q_vectors, dtype=self.dtype)
-
     def release(self):
+        """
+        Release any resources associated with the model.
+        """
         pass # TODO: should release the dll
 
@@ -269,5 +262,5 @@
         self.kernel = kernel
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         self.fixed_pars = info['partype']['fixed-'+dim]
         self.pd_pars = info['partype']['pd-'+dim]
@@ -299,3 +292,6 @@
 
     def release(self):
+        """
+        Release any resources associated with the kernel.
+        """
         pass
Index: sasmodels/kernelpy.py
===================================================================
--- sasmodels/kernelpy.py	(revision 4c2c535bfe4a2a460e9aaf4ef0ed8b22d96965fd)
+++ sasmodels/kernelpy.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -1,2 +1,10 @@
+"""
+Python driver for python kernels
+
+Calls the kernel with a vector of $q$ values for a single parameter set.
+Polydispersity is supported by looping over different parameter sets and
+summing the results.  The interface to :class:`PyModel` matches those for
+:class:`kernelcl.GpuModel` and :class:`kerneldll.DllModel`.
+"""
 import numpy as np
 from numpy import pi, cos
@@ -5,16 +13,19 @@
 
 class PyModel(object):
+    """
+    Wrapper for pure python models.
+    """
     def __init__(self, info):
         self.info = info
 
-    def __call__(self, q_input):
-        kernel = self.info['Iqxy'] if q_input.is_2D else self.info['Iq']
+    def __call__(self, q_vectors):
+        q_input = PyInput(q_vectors, dtype=F64)
+        kernel = self.info['Iqxy'] if q_input.is_2d else self.info['Iq']
         return PyKernel(kernel, self.info, q_input)
 
-    # pylint: disable=no-self-use
-    def make_input(self, q_vectors):
-        return PyInput(q_vectors, dtype=F64)
-
     def release(self):
+        """
+        Free resources associated with the model.
+        """
         pass
 
@@ -41,9 +52,12 @@
         self.nq = q_vectors[0].size
         self.dtype = dtype
-        self.is_2D = (len(q_vectors) == 2)
+        self.is_2d = (len(q_vectors) == 2)
         self.q_vectors = [np.ascontiguousarray(q, self.dtype) for q in q_vectors]
         self.q_pointers = [q.ctypes.data for q in self.q_vectors]
 
     def release(self):
+        """
+        Free resources associated with the model inputs.
+        """
         self.q_vectors = []
 
@@ -71,5 +85,5 @@
         self.q_input = q_input
         self.res = np.empty(q_input.nq, q_input.dtype)
-        dim = '2d' if q_input.is_2D else '1d'
+        dim = '2d' if q_input.is_2d else '1d'
         # Loop over q unless user promises that the kernel is vectorized by
         # taggining it with vectorized=True
@@ -77,8 +91,14 @@
             if dim == '2d':
                 def vector_kernel(qx, qy, *args):
+                    """
+                    Vectorized 2D kernel.
+                    """
                     return np.array([kernel(qxi, qyi, *args)
                                      for qxi, qyi in zip(qx, qy)])
             else:
                 def vector_kernel(q, *args):
+                    """
+                    Vectorized 1D kernel.
+                    """
                     return np.array([kernel(qi, *args)
                                      for qi in q])
@@ -122,4 +142,7 @@
 
     def release(self):
+        """
+        Free resources associated with the kernel.
+        """
         self.q_input = None
 
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision 9404dd3f599fcbe72790aae8a6d176e64f292ea6)
+++ sasmodels/sasview_model.py	(revision eafc9fa8b200a3b754e7dafb960c8891361eacef)
@@ -283,5 +283,5 @@
         """
         q_vectors = [np.asarray(q) for q in args]
-        fn = self._model(self._model.make_input(q_vectors))
+        fn = self._model(q_vectors)
         pars = [self.params[v] for v in fn.fixed_pars]
         pd_pars = [self._get_weights(p) for p in fn.pd_pars]
