Index: sasmodels/details.py
===================================================================
--- sasmodels/details.py	(revision d2fc9a4667a15d147311d3e673314fd312813afc)
+++ sasmodels/details.py	(revision 4bfd2776ad7d85e00cad95f2a94c6553b27a3e7c)
@@ -144,5 +144,5 @@
             return [np.asarray(v) for v in args]
 
-def dispersion_mesh(pars):
+def dispersion_mesh(model_info, pars):
     """
     Create a mesh grid of dispersion parameters and weights.
@@ -154,7 +154,16 @@
     value, weight = zip(*pars)
     weight = [w if w else [1.] for w in weight]
-    value = [v.flatten() for v in meshgrid(*value)]
     weight = np.vstack([v.flatten() for v in meshgrid(*weight)])
     weight = np.prod(weight, axis=0)
+    value = [v.flatten() for v in meshgrid(*value)]
+    lengths = [par.length for par in model_info.parameters.kernel_parameters
+               if par.type == 'volume']
+    if any(n > 1 for n in lengths):
+        pars = []
+        offset = 0
+        for n in lengths:
+            pars.append(np.vstack(value[offset:offset+n]) if n > 1 else value[offset])
+            offset += n
+        value = pars
     return value, weight
 
Index: sasmodels/kernelpy.py
===================================================================
--- sasmodels/kernelpy.py	(revision 6d6508e66dc590f2ad678c70ef6fc78c3d4e684c)
+++ sasmodels/kernelpy.py	(revision 4bfd2776ad7d85e00cad95f2a94c6553b27a3e7c)
@@ -10,4 +10,5 @@
 from numpy import pi, cos
 
+from . import details
 from .generate import F64
 
@@ -145,5 +146,5 @@
 
     def __call__(self, call_details, weights, values, cutoff):
-        # type: (.generate.CallDetails, np.ndarray, np.ndarray, float) -> np.ndarray
+        assert isinstance(call_details, details.CallDetails)
         res = _loops(self._parameter_vector, self._form, self._volume,
                      self.q_input.nq, call_details, weights, values, cutoff)
@@ -160,5 +161,5 @@
            form_volume, # type: Callable[[], float]
            nq,          # type: int
-           call_details,# type: .generate.CallDetails
+           call_details,# type: details.CallDetails
            weights,     # type: np.ndarray
            values,      # type: np.ndarray
@@ -205,5 +206,5 @@
                 this_offset = call_details.par_offset[par]
                 block_size = 1
-                for bit in xrange(32):
+                for bit in range(len(pd_offset)):
                     if coord&1:
                         this_offset += block_size * pd_index[bit]
@@ -245,4 +246,15 @@
 
 
+def _create_default_functions(model_info):
+    """
+    Autogenerate missing functions, such as Iqxy from Iq.
+
+    This only works for Iqxy when Iq is written in python. :func:`make_source`
+    performs a similar role for Iq written in C.  This also vectorizes
+    any functions that are not already marked as vectorized.
+    """
+    _create_vector_Iq(model_info)
+    _create_vector_Iqxy(model_info)  # call create_vector_Iq() first
+
 
 def _create_vector_Iq(model_info):
@@ -252,4 +264,5 @@
     Iq = model_info.Iq
     if callable(Iq) and not getattr(Iq, 'vectorized', False):
+        #print("vectorizing Iq")
         def vector_Iq(q, *args):
             """
@@ -265,13 +278,16 @@
     """
     Iq, Iqxy = model_info.Iq, model_info.Iqxy
-    if callable(Iqxy) and not getattr(Iqxy, 'vectorized', False):
-        def vector_Iqxy(qx, qy, *args):
-            """
-            Vectorized 2D kernel.
-            """
-            return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)])
-        vector_Iqxy.vectorized = True
-        model_info.Iqxy = vector_Iqxy
+    if callable(Iqxy):
+        if not getattr(Iqxy, 'vectorized', False):
+            #print("vectorizing Iqxy")
+            def vector_Iqxy(qx, qy, *args):
+                """
+                Vectorized 2D kernel.
+                """
+                return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)])
+            vector_Iqxy.vectorized = True
+            model_info.Iqxy = vector_Iqxy
     elif callable(Iq):
+        #print("defaulting Iqxy")
         # Iq is vectorized because create_vector_Iq was already called.
         def default_Iqxy(qx, qy, *args):
@@ -283,13 +299,2 @@
         model_info.Iqxy = default_Iqxy
 
-def _create_default_functions(model_info):
-    """
-    Autogenerate missing functions, such as Iqxy from Iq.
-
-    This only works for Iqxy when Iq is written in python. :func:`make_source`
-    performs a similar role for Iq written in C.  This also vectorizes
-    any functions that are not already marked as vectorized.
-    """
-    _create_vector_Iq(model_info)
-    _create_vector_Iqxy(model_info)  # call create_vector_Iq() first
-
Index: sasmodels/model_test.py
===================================================================
--- sasmodels/model_test.py	(revision 6d6508e66dc590f2ad678c70ef6fc78c3d4e684c)
+++ sasmodels/model_test.py	(revision 4bfd2776ad7d85e00cad95f2a94c6553b27a3e7c)
@@ -56,7 +56,7 @@
 from .direct_model import call_kernel, get_weights
 from .exception import annotate_exception
-
-
-def call_ER(model_info, values):
+from .modelinfo import expand_pars
+
+def call_ER(model_info, pars):
     """
     Call the model ER function using *values*. *model_info* is either
@@ -67,12 +67,9 @@
         return 1.0
     else:
-        vol_pars = [get_weights(parameter, values)
-                    for parameter in model_info.parameters.call_parameters
-                    if parameter.type == 'volume']
-        value, weight = dispersion_mesh(vol_pars)
+        value, weight = _vol_pars(model_info, pars)
         individual_radii = model_info.ER(*value)
         return np.sum(weight*individual_radii) / np.sum(weight)
 
-def call_VR(model_info, values):
+def call_VR(model_info, pars):
     """
     Call the model VR function using *pars*.
@@ -83,10 +80,14 @@
         return 1.0
     else:
-        vol_pars = [get_weights(parameter, values)
-                    for parameter in model_info.parameters.call_parameters
-                    if parameter.type == 'volume']
-        value, weight = dispersion_mesh(vol_pars)
+        value, weight = _vol_pars(model_info, pars)
         whole, part = model_info.VR(*value)
         return np.sum(weight*part)/np.sum(weight*whole)
+
+def _vol_pars(model_info, pars):
+    vol_pars = [get_weights(p, pars)
+                for p in model_info.parameters.call_parameters
+                if p.type == 'volume']
+    value, weight = dispersion_mesh(model_info, vol_pars)
+    return value, weight
 
 
@@ -175,8 +176,8 @@
             self.dtype = dtype
 
-            setattr(self, test_method_name, self._runTest)
+            setattr(self, test_method_name, self.run_all)
             unittest.TestCase.__init__(self, test_method_name)
 
-        def _runTest(self):
+        def run_all(self):
             smoke_tests = [
                 [{}, 0.1, None],
@@ -191,5 +192,5 @@
                                     platform=self.platform)
                 for test in smoke_tests + tests:
-                    self._run_one_test(model, test)
+                    self.run_one(model, test)
 
                 if not tests and self.platform == "dll":
@@ -205,6 +206,7 @@
                 raise
 
-        def _run_one_test(self, model, test):
+        def run_one(self, model, test):
             pars, x, y = test
+            pars = expand_pars(self.info.parameters, pars)
 
             if not isinstance(y, list):
@@ -229,5 +231,5 @@
                 actual = call_kernel(kernel, pars)
 
-            self.assertGreater(len(actual), 0)
+            self.assertTrue(len(actual) > 0)
             self.assertEqual(len(y), len(actual))
 
@@ -311,5 +313,5 @@
     tests = make_suite(['opencl', 'dll'], ['all'])
     for test_i in tests:
-        yield test_i._runTest
+        yield test_i.run_all
 
 
Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision 6d6508e66dc590f2ad678c70ef6fc78c3d4e684c)
+++ sasmodels/modelinfo.py	(revision 4bfd2776ad7d85e00cad95f2a94c6553b27a3e7c)
@@ -117,31 +117,31 @@
 
 
-def make_demo_pars(partable, demo):
+def expand_pars(partable, pars):
     """
     Create demo parameter set from key-value pairs.
 
-    *demo* are the key-value pairs to use for the demo parameters.  Any
-    parameters not specified in *demo* are set from the *partable* defaults.
-
-    If *demo* references vector fields, such as thickness[n], then support
+    *pars* are the key-value pairs to use for the parameters.  Any
+    parameters not specified in *pars* are set from the *partable* defaults.
+
+    If *pars* references vector fields, such as thickness[n], then support
     different ways of assigning the demo values, including assigning a
     specific value (e.g., thickness3=50.0), assigning a new value to all
     (e.g., thickness=50.0) or assigning values using list notation.
     """
-    if demo is None:
+    if pars is None:
         result = partable.defaults
     else:
-        pars = dict((p.id, p) for p in partable.kernel_parameters)
+        lookup = dict((p.id, p) for p in partable.kernel_parameters)
         result = partable.defaults.copy()
-        vectors = dict((name,value) for name,value in demo.items()
-                       if name in pars and pars[name].length > 1)
+        vectors = dict((name,value) for name,value in pars.items()
+                       if name in lookup and lookup[name].length > 1)
         if vectors:
-            scalars = dict((name, value) for name, value in demo.items()
-                           if name not in pars or pars[name].length == 1)
+            scalars = dict((name, value) for name, value in pars.items()
+                           if name not in lookup or lookup[name].length == 1)
             for name, value in vectors.items():
                 if np.isscalar(value):
                     # support for the form
-                    #    demo(thickness=0, thickness2=50)
-                    for k in range(1, pars[name].length+1):
+                    #    dict(thickness=0, thickness2=50)
+                    for k in range(1, lookup[name].length+1):
                         key = name+str(k)
                         if key not in scalars:
@@ -149,10 +149,10 @@
                 else:
                     # supoprt for the form
-                    #    demo(thickness=[20,10,3])
+                    #    dict(thickness=[20,10,3])
                     for (k,v) in enumerate(value):
                         scalars[name+str(k)] = v
             result.update(scalars)
         else:
-            result.update(demo)
+            result.update(pars)
 
     return result
@@ -537,5 +537,5 @@
     #print("make parameter table", kernel_module.parameters)
     parameters = make_parameter_table(kernel_module.parameters)
-    demo = make_demo_pars(parameters, getattr(kernel_module, 'demo', None))
+    demo = expand_pars(parameters, getattr(kernel_module, 'demo', None))
     filename = abspath(kernel_module.__file__)
     kernel_id = splitext(basename(filename))[0]
Index: sasmodels/sasview_model.py
===================================================================
--- sasmodels/sasview_model.py	(revision 6d6508e66dc590f2ad678c70ef6fc78c3d4e684c)
+++ sasmodels/sasview_model.py	(revision 4bfd2776ad7d85e00cad95f2a94c6553b27a3e7c)
@@ -83,5 +83,5 @@
     Sasview wrapper for opencl/ctypes model.
     """
-    _model_info = {}
+    _model_info = None # type: modelinfo.ModelInfo
     def __init__(self):
         self._model = None
@@ -353,6 +353,6 @@
         pairs = [self._get_weights(p)
                  for p in self._model_info.parameters.call_parameters]
-        call_details, weights, values = details.build_details(kernel, pairs)
-        result = kernel(call_details, weights, values, cutoff=self.cutoff)
+        call_details, weight, value = details.build_details(kernel, pairs)
+        result = kernel(call_details, weight, value, cutoff=self.cutoff)
         kernel.q_input.release()
         kernel.release()
@@ -365,11 +365,11 @@
         :return: the value of the effective radius
         """
-        if model_info.ER is None:
+        if self._model_info.ER is None:
             return 1.0
         else:
-            values, weights = self._dispersion_mesh()
-            fv = model_info.ER(*values)
+            value, weight = self._dispersion_mesh()
+            fv = self._model_info.ER(*value)
             #print(values[0].shape, weights.shape, fv.shape)
-            return np.sum(weights * fv) / np.sum(weights)
+            return np.sum(weight * fv) / np.sum(weight)
 
     def calculate_VR(self):
@@ -379,10 +379,10 @@
         :return: the value of the volf ratio
         """
-        if model_info.VR is None:
+        if self._model_info.VR is None:
             return 1.0
         else:
-            values, weights = self._dispersion_mesh()
-            whole, part = model_info.VR(*values)
-            return np.sum(weights * part) / np.sum(weights * whole)
+            value, weight = self._dispersion_mesh()
+            whole, part = self._model_info.VR(*value)
+            return np.sum(weight * part) / np.sum(weight * whole)
 
     def set_dispersion(self, parameter, dispersion):
@@ -422,6 +422,8 @@
         parameter set in the vector.
         """
-        pars = self._model_info.partype['volume']
-        return details.dispersion_mesh([self._get_weights(p) for p in pars])
+        pars = [self._get_weights(p)
+                for p in self._model_info.parameters.call_parameters
+                if p.type == 'volume']
+        return details.dispersion_mesh(self._model_info, pars)
 
     def _get_weights(self, par):
