Index: sasmodels/compare.py
===================================================================
--- sasmodels/compare.py	(revision dd7fc126b4eb39f92db8393c7093392becb55218)
+++ sasmodels/compare.py	(revision 8d620089dc0fbbdb285c976744abfa4439490599)
@@ -49,5 +49,5 @@
     from .modelinfo import ModelInfo, Parameter, ParameterSet
     from .data import Data
-    Calculator = Callable[[float, ...], np.ndarray]
+    Calculator = Callable[[float], np.ndarray]
 
 USAGE = """
@@ -383,6 +383,7 @@
     # import rather than the more obscure smear_selection not imported error
     import sas
+    import sas.models
     from sas.models.qsmearing import smear_selection
-    import sas.models
+    from sas.models.MultiplicationModel import MultiplicationModel
 
     def get_model(name):
@@ -399,5 +400,4 @@
         composition_type, parts = model_info.composition
         if composition_type == 'product':
-            from sas.models.MultiplicationModel import MultiplicationModel
             P, S = [get_model(revert_name(p)) for p in parts]
             model = MultiplicationModel(P, S)
Index: sasmodels/details.py
===================================================================
--- sasmodels/details.py	(revision 7ae2b7f85895f1eddd6ef18f37ff501467a34961)
+++ sasmodels/details.py	(revision 8d620089dc0fbbdb285c976744abfa4439490599)
@@ -5,4 +5,6 @@
 except ImportError:
     pass
+else:
+    from .modelinfo import ModelInfo
 
 
@@ -10,24 +12,22 @@
     parts = None  # type: List["CallDetails"]
     def __init__(self, model_info):
+        # type: (ModelInfo) -> None
         parameters = model_info.parameters
         max_pd = parameters.max_pd
         npars = parameters.npars
         par_offset = 4*max_pd
-        self._details = np.zeros(par_offset + 3*npars + 4, 'i4')
+        self.buffer = np.zeros(par_offset + 3 * npars + 4, 'i4')
 
         # generate views on different parts of the array
-        self._pd_par     = self._details[0*max_pd:1*max_pd]
-        self._pd_length  = self._details[1*max_pd:2*max_pd]
-        self._pd_offset  = self._details[2*max_pd:3*max_pd]
-        self._pd_stride  = self._details[3*max_pd:4*max_pd]
-        self._par_offset = self._details[par_offset+0*npars:par_offset+1*npars]
-        self._par_coord  = self._details[par_offset+1*npars:par_offset+2*npars]
-        self._pd_coord   = self._details[par_offset+2*npars:par_offset+3*npars]
+        self._pd_par     = self.buffer[0 * max_pd:1 * max_pd]
+        self._pd_length  = self.buffer[1 * max_pd:2 * max_pd]
+        self._pd_offset  = self.buffer[2 * max_pd:3 * max_pd]
+        self._pd_stride  = self.buffer[3 * max_pd:4 * max_pd]
+        self._par_offset = self.buffer[par_offset + 0 * npars:par_offset + 1 * npars]
+        self._par_coord  = self.buffer[par_offset + 1 * npars:par_offset + 2 * npars]
+        self._pd_coord   = self.buffer[par_offset + 2 * npars:par_offset + 3 * npars]
 
         # theta_par is fixed
-        self._details[-1] = parameters.theta_offset
-
-    @property
-    def ctypes(self): return self._details.ctypes
+        self.buffer[-1] = parameters.theta_offset
 
     @property
@@ -53,20 +53,20 @@
 
     @property
-    def num_active(self): return self._details[-4]
+    def num_active(self): return self.buffer[-4]
     @num_active.setter
-    def num_active(self, v): self._details[-4] = v
+    def num_active(self, v): self.buffer[-4] = v
 
     @property
-    def total_pd(self): return self._details[-3]
+    def total_pd(self): return self.buffer[-3]
     @total_pd.setter
-    def total_pd(self, v): self._details[-3] = v
+    def total_pd(self, v): self.buffer[-3] = v
 
     @property
-    def num_coord(self): return self._details[-2]
+    def num_coord(self): return self.buffer[-2]
     @num_coord.setter
-    def num_coord(self, v): self._details[-2] = v
+    def num_coord(self, v): self.buffer[-2] = v
 
     @property
-    def theta_par(self): return self._details[-1]
+    def theta_par(self): return self.buffer[-1]
 
     def show(self):
@@ -81,5 +81,5 @@
         print("par_coord", self.par_coord)
         print("pd_coord", self.pd_coord)
-        print("theta par", self._details[-1])
+        print("theta par", self.buffer[-1])
 
 def build_details(kernel, pairs):
@@ -88,5 +88,5 @@
         call_details = poly_details(kernel.info, weights)
     else:
-        call_details = kernel.info.mono_details
+        call_details = mono_details(kernel.info)
     weights, values = [np.hstack(v) for v in (weights, values)]
     weights = weights.astype(dtype=kernel.dtype)
Index: sasmodels/kernelcl.py
===================================================================
--- sasmodels/kernelcl.py	(revision dd7fc126b4eb39f92db8393c7093392becb55218)
+++ sasmodels/kernelcl.py	(revision 8d620089dc0fbbdb285c976744abfa4439490599)
@@ -60,6 +60,7 @@
     # Ask OpenCL for the default context so that we know that one exists
     cl.create_some_context(interactive=False)
-except Exception as exc:
-    warnings.warn(str(exc))
+except Exception as ocl_exc:
+    warnings.warn(str(ocl_exc))
+    del ocl_exc
     raise RuntimeError("OpenCL not available")
 
@@ -479,5 +480,4 @@
         self.dtype = kernel.dtype
         self.dim = '2d' if q_input.is_2d else '1d'
-        self.pd_stop_index = 4*max_pd-1
         # plus three for the normalization values
         self.result = np.empty(q_input.nq+3, q_input.dtype)
@@ -495,17 +495,16 @@
 
         self._need_release = [ self.result_b, self.q_input ]
+        self.real = (np.float32 if self.q_input.dtype == generate.F32
+                     else np.float64 if self.q_input.dtype == generate.F64
+                     else np.float16 if self.q_input.dtype == generate.F16
+                     else np.float32)  # will never get here, so use np.float32
 
     def __call__(self, call_details, weights, values, cutoff):
         # type: (CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray
-        real = (np.float32 if self.q_input.dtype == generate.F32
-                else np.float64 if self.q_input.dtype == generate.F64
-                else np.float16 if self.q_input.dtype == generate.F16
-                else np.float32)  # will never get here, so use np.float32
-        assert call_details.dtype == np.int32
-        assert weights.dtype == real and values.dtype == real
 
         context = self.queue.context
+        # Arrange data transfer to card
         details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR,
-                              hostbuf=call_details)
+                              hostbuf=call_details.buffer)
         weights_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR,
                               hostbuf=weights)
@@ -513,15 +512,16 @@
                              hostbuf=values)
 
-        start, stop = 0, self.details[self.pd_stop_index]
+        start, stop = 0, call_details.total_pd
         args = [
-            np.uint32(self.q_input.nq), np.uint32(start), np.uint32(stop),
-            self.details_b, self.weights_b, self.values_b,
-            self.q_input.q_b, self.result_b, real(cutoff),
+            np.uint32(self.q_input.nq), np.int32(start), np.int32(stop),
+            details_b, weights_b, values_b, self.q_input.q_b, self.result_b,
+            self.real(cutoff),
         ]
         self.kernel(self.queue, self.q_input.global_size, None, *args)
         cl.enqueue_copy(self.queue, self.result, self.result_b)
-        [v.release() for v in (details_b, weights_b, values_b)]
-
-        return self.result[:self.nq]
+        for v in (details_b, weights_b, values_b):
+            v.release()
+
+        return self.result[:self.q_input.nq]
 
     def release(self):
Index: sasmodels/kerneldll.py
===================================================================
--- sasmodels/kerneldll.py	(revision a5b8477c53695e7df03ecec7346f0c1bd770d3f7)
+++ sasmodels/kerneldll.py	(revision 8d620089dc0fbbdb285c976744abfa4439490599)
@@ -294,5 +294,5 @@
             start, # pd_start
             stop, # pd_stop pd_stride[MAX_PD]
-            call_details.ctypes.data, # problem
+            call_details.buffer.ctypes.data, # problem
             weights.ctypes.data,  # weights
             values.ctypes.data,  #pars
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision a5b8477c53695e7df03ecec7346f0c1bd770d3f7)
+++ sasmodels/modelinfo.py	(revision 8d620089dc0fbbdb285c976744abfa4439490599)
@@ -13,6 +13,4 @@
 
 import numpy as np  # type: ignore
-
-from .details import mono_details
 
 # Optional typing
@@ -22,5 +20,4 @@
     pass
 else:
-    from .details import CallDetails
     Limits = Tuple[float, float]
     #LimitsOrChoice = Union[Limits, Tuple[Sequence[str]]]
@@ -658,6 +655,4 @@
     info.hidden = getattr(kernel_module, 'hidden', None) # type: ignore
 
-    # Precalculate the monodisperse parameter details
-    info.mono_details = mono_details(info)
     return info
 
@@ -811,8 +806,4 @@
     #: the SESANS correlation function.  Note: not currently implemented.
     sesans = None           # type: Optional[Callable[[np.ndarray], np.ndarray]]
-    #: :class:details.CallDetails data for mono-disperse function evaluation.
-    #: This field is created automatically by the model loader, and should
-    #: not be defined as part of the model definition file.
-    mono_details = None     # type: CallDetails
 
     def __init__(self):
