source: sasmodels/sasmodels/kernelcl.py @ d86f0fc

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since d86f0fc was d86f0fc, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

lint reduction

  • Property mode set to 100644
File size: 23.8 KB
RevLine 
[14de349]1"""
[eafc9fa]2GPU driver for C kernels
[14de349]3
4There should be a single GPU environment running on the system.  This
5environment is constructed on the first call to :func:`env`, and the
6same environment is returned on each call.
7
8After retrieving the environment, the next step is to create the kernel.
9This is done with a call to :meth:`GpuEnvironment.make_kernel`, which
10returns the type of data used by the kernel.
11
12Next a :class:`GpuData` object should be created with the correct kind
13of data.  This data object can be used by multiple kernels, for example,
14if the target model is a weighted sum of multiple kernels.  The data
15should include any extra evaluation points required to compute the proper
16data smearing.  This need not match the square grid for 2D data if there
17is an index saying which q points are active.
18
19Together the GpuData, the program, and a device form a :class:`GpuKernel`.
20This kernel is used during fitting, receiving new sets of parameters and
21evaluating them.  The output value is stored in an output buffer on the
22devices, where it can be combined with other structure factors and form
23factors and have instrumental resolution effects applied.
[92da231]24
25In order to use OpenCL for your models, you will need OpenCL drivers for
26your machine.  These should be available from your graphics card vendor.
27Intel provides OpenCL drivers for CPUs as well as their integrated HD
28graphics chipsets.  AMD also provides drivers for Intel CPUs, but as of
29this writing the performance is lacking compared to the Intel drivers.
30NVidia combines drivers for CUDA and OpenCL in one package.  The result
31is a bit messy if you have multiple drivers installed.  You can see which
32drivers are available by starting python and running:
33
34    import pyopencl as cl
35    cl.create_some_context(interactive=True)
36
37Once you have done that, it will show the available drivers which you
38can select.  It will then tell you that you can use these drivers
[880a2ed]39automatically by setting the SAS_OPENCL environment variable, which is
40PYOPENCL_CTX equivalent but not conflicting with other pyopnecl programs.
[92da231]41
42Some graphics cards have multiple devices on the same card.  You cannot
43yet use both of them concurrently to evaluate models, but you can run
44the program twice using a different device for each session.
45
46OpenCL kernels are compiled when needed by the device driver.  Some
47drivers produce compiler output even when there is no error.  You
48can see the output by setting PYOPENCL_COMPILER_OUTPUT=1.  It should be
49harmless, albeit annoying.
[14de349]50"""
[ba32cdd]51from __future__ import print_function
[a5b8477]52
[250fa25]53import os
54import warnings
[821a9c6]55import logging
[6e5b2a7]56import time
[250fa25]57
[7ae2b7f]58import numpy as np  # type: ignore
[b3f6bc3]59
[3221de0]60
61# Attempt to setup opencl. This may fail if the opencl package is not
62# installed or if it is installed but there are no devices available.
[250fa25]63try:
[3221de0]64    import pyopencl as cl  # type: ignore
65    from pyopencl import mem_flags as mf
66    from pyopencl.characterize import get_fast_inaccurate_build_options
67    # Ask OpenCL for the default context so that we know that one exists
68    cl.create_some_context(interactive=False)
69    HAVE_OPENCL = True
70    OPENCL_ERROR = ""
[9404dd3]71except Exception as exc:
[6dba2f0]72    HAVE_OPENCL = False
[3221de0]73    OPENCL_ERROR = str(exc)
[14de349]74
[cb6ecf4]75from . import generate
[f619de7]76from .kernel import KernelModel, Kernel
[14de349]77
[2d81cfe]78# pylint: disable=unused-import
[a5b8477]79try:
80    from typing import Tuple, Callable, Any
81    from .modelinfo import ModelInfo
82    from .details import CallDetails
83except ImportError:
84    pass
[2d81cfe]85# pylint: enable=unused-import
[a5b8477]86
[20317b3]87# CRUFT: pyopencl < 2017.1  (as of June 2016 needs quotes around include path)
88def quote_path(v):
89    """
90    Quote the path if it is not already quoted.
91
92    If v starts with '-', then assume that it is a -I option or similar
93    and do not quote it.  This is fragile:  -Ipath with space needs to
94    be quoted.
95    """
96    return '"'+v+'"' if v and ' ' in v and not v[0] in "\"'-" else v
97
98def fix_pyopencl_include():
[40a87fa]99    """
100    Monkey patch pyopencl to allow spaces in include file path.
101    """
[20317b3]102    import pyopencl as cl
103    if hasattr(cl, '_DEFAULT_INCLUDE_OPTIONS'):
104        cl._DEFAULT_INCLUDE_OPTIONS = [quote_path(v) for v in cl._DEFAULT_INCLUDE_OPTIONS]
105
[6dba2f0]106if HAVE_OPENCL:
107    fix_pyopencl_include()
[20317b3]108
[ce27e21]109# The max loops number is limited by the amount of local memory available
110# on the device.  You don't want to make this value too big because it will
111# waste resources, nor too small because it may interfere with users trying
112# to do their polydispersity calculations.  A value of 1024 should be much
113# larger than necessary given that cost grows as npts^k where k is the number
114# of polydisperse parameters.
[5d4777d]115MAX_LOOPS = 2048
116
[ce27e21]117
[5464d68]118# Pragmas for enable OpenCL features.  Be sure to protect them so that they
119# still compile even if OpenCL is not present.
120_F16_PRAGMA = """\
121#if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp16)
122#  pragma OPENCL EXTENSION cl_khr_fp16: enable
123#endif
124"""
125
126_F64_PRAGMA = """\
127#if defined(__OPENCL_VERSION__) // && !defined(cl_khr_fp64)
128#  pragma OPENCL EXTENSION cl_khr_fp64: enable
129#endif
130"""
131
[3221de0]132def use_opencl():
133    return HAVE_OPENCL and os.environ.get("SAS_OPENCL", "").lower() != "none"
[5464d68]134
[14de349]135ENV = None
[3221de0]136def reset_environment():
137    """
138    Call to create a new OpenCL context, such as after a change to SAS_OPENCL.
139    """
140    global ENV
141    ENV = GpuEnvironment() if use_opencl() else None
142
[14de349]143def environment():
[dd7fc12]144    # type: () -> "GpuEnvironment"
[14de349]145    """
146    Returns a singleton :class:`GpuEnvironment`.
147
148    This provides an OpenCL context and one queue per device.
149    """
[b4272a2]150    if ENV is None:
151        if not HAVE_OPENCL:
152            raise RuntimeError("OpenCL startup failed with ***"
[d86f0fc]153                               + OPENCL_ERROR + "***; using C compiler instead")
[3221de0]154        reset_environment()
[b4272a2]155        if ENV is None:
156            raise RuntimeError("SAS_OPENCL=None in environment")
[14de349]157    return ENV
158
[5d316e9]159def has_type(device, dtype):
[dd7fc12]160    # type: (cl.Device, np.dtype) -> bool
[14de349]161    """
[5d316e9]162    Return true if device supports the requested precision.
[14de349]163    """
[5d316e9]164    if dtype == generate.F32:
165        return True
166    elif dtype == generate.F64:
167        return "cl_khr_fp64" in device.extensions
168    elif dtype == generate.F16:
169        return "cl_khr_fp16" in device.extensions
170    else:
171        return False
[14de349]172
[f5b9a6b]173def get_warp(kernel, queue):
[dd7fc12]174    # type: (cl.Kernel, cl.CommandQueue) -> int
[f5b9a6b]175    """
176    Return the size of an execution batch for *kernel* running on *queue*.
177    """
[750ffa5]178    return kernel.get_work_group_info(
[63b32bb]179        cl.kernel_work_group_info.PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
180        queue.device)
[14de349]181
[f5b9a6b]182def _stretch_input(vector, dtype, extra=1e-3, boundary=32):
[dd7fc12]183    # type: (np.ndarray, np.dtype, float, int) -> np.ndarray
[14de349]184    """
185    Stretch an input vector to the correct boundary.
186
187    Performance on the kernels can drop by a factor of two or more if the
188    number of values to compute does not fall on a nice power of two
[f5b9a6b]189    boundary.   The trailing additional vector elements are given a
190    value of *extra*, and so f(*extra*) will be computed for each of
191    them.  The returned array will thus be a subset of the computed array.
192
193    *boundary* should be a power of 2 which is at least 32 for good
194    performance on current platforms (as of Jan 2015).  It should
195    probably be the max of get_warp(kernel,queue) and
196    device.min_data_type_align_size//4.
197    """
[c85db69]198    remainder = vector.size % boundary
[f5b9a6b]199    if remainder != 0:
200        size = vector.size + (boundary - remainder)
[c85db69]201        vector = np.hstack((vector, [extra] * (size - vector.size)))
[14de349]202    return np.ascontiguousarray(vector, dtype=dtype)
203
204
[5d316e9]205def compile_model(context, source, dtype, fast=False):
[dd7fc12]206    # type: (cl.Context, str, np.dtype, bool) -> cl.Program
[14de349]207    """
208    Build a model to run on the gpu.
209
[6cbdcd4]210    Returns the compiled program and its type.
211
212    Raises an error if the desired precision is not available.
[14de349]213    """
214    dtype = np.dtype(dtype)
[5d316e9]215    if not all(has_type(d, dtype) for d in context.devices):
216        raise RuntimeError("%s not supported for devices"%dtype)
[14de349]217
[5464d68]218    source_list = [generate.convert_type(source, dtype)]
219
220    if dtype == generate.F16:
221        source_list.insert(0, _F16_PRAGMA)
222    elif dtype == generate.F64:
223        source_list.insert(0, _F64_PRAGMA)
224
[14de349]225    # Note: USE_SINCOS makes the intel cpu slower under opencl
226    if context.devices[0].type == cl.device_type.GPU:
[5464d68]227        source_list.insert(0, "#define USE_SINCOS\n")
[5d316e9]228    options = (get_fast_inaccurate_build_options(context.devices[0])
229               if fast else [])
[ba32cdd]230    source = "\n".join(source_list)
[5d316e9]231    program = cl.Program(context, source).build(options=options)
[821a9c6]232    #print("done with "+program)
[ce27e21]233    return program
[14de349]234
235
236# for now, this returns one device in the context
237# TODO: create a context that contains all devices on all platforms
238class GpuEnvironment(object):
239    """
240    GPU context, with possibly many devices, and one queue per device.
241    """
242    def __init__(self):
[dd7fc12]243        # type: () -> None
[250fa25]244        # find gpu context
245        #self.context = cl.create_some_context()
246
247        self.context = None
[880a2ed]248        if 'SAS_OPENCL' in os.environ:
249            #Setting PYOPENCL_CTX as a SAS_OPENCL to create cl context
250            os.environ["PYOPENCL_CTX"] = os.environ["SAS_OPENCL"]
[a557a99]251        if 'PYOPENCL_CTX' in os.environ:
[250fa25]252            self._create_some_context()
253
254        if not self.context:
[3c56da87]255            self.context = _get_default_context()
[250fa25]256
[f5b9a6b]257        # Byte boundary for data alignment
258        #self.data_boundary = max(d.min_data_type_align_size
259        #                         for d in self.context.devices)
[d18582e]260        self.queues = [cl.CommandQueue(context, context.devices[0])
261                       for context in self.context]
[ce27e21]262        self.compiled = {}
263
[5d316e9]264    def has_type(self, dtype):
[dd7fc12]265        # type: (np.dtype) -> bool
[eafc9fa]266        """
267        Return True if all devices support a given type.
268        """
[d18582e]269        return any(has_type(d, dtype)
270                   for context in self.context
271                   for d in context.devices)
272
273    def get_queue(self, dtype):
[dd7fc12]274        # type: (np.dtype) -> cl.CommandQueue
[d18582e]275        """
276        Return a command queue for the kernels of type dtype.
277        """
278        for context, queue in zip(self.context, self.queues):
279            if all(has_type(d, dtype) for d in context.devices):
280                return queue
281
282    def get_context(self, dtype):
[dd7fc12]283        # type: (np.dtype) -> cl.Context
[d18582e]284        """
285        Return a OpenCL context for the kernels of type dtype.
286        """
[20317b3]287        for context in self.context:
[d18582e]288            if all(has_type(d, dtype) for d in context.devices):
289                return context
[5d316e9]290
[250fa25]291    def _create_some_context(self):
[dd7fc12]292        # type: () -> cl.Context
[eafc9fa]293        """
294        Protected call to cl.create_some_context without interactivity.  Use
[880a2ed]295        this if SAS_OPENCL is set in the environment.  Sets the *context*
[eafc9fa]296        attribute.
297        """
[250fa25]298        try:
[d18582e]299            self.context = [cl.create_some_context(interactive=False)]
[9404dd3]300        except Exception as exc:
[250fa25]301            warnings.warn(str(exc))
302            warnings.warn("pyopencl.create_some_context() failed")
[880a2ed]303            warnings.warn("the environment variable 'SAS_OPENCL' might not be set correctly")
[250fa25]304
[300a2f7]305    def compile_program(self, name, source, dtype, fast, timestamp):
306        # type: (str, str, np.dtype, bool, float) -> cl.Program
[eafc9fa]307        """
308        Compile the program for the device in the given context.
309        """
[300a2f7]310        # Note: PyOpenCL caches based on md5 hash of source, options and device
311        # so we don't really need to cache things for ourselves.  I'll do so
312        # anyway just to save some data munging time.
[7fcdc9f]313        tag = generate.tag_source(source)
314        key = "%s-%s-%s%s"%(name, dtype, tag, ("-fast" if fast else ""))
[300a2f7]315        # Check timestamp on program
316        program, program_timestamp = self.compiled.get(key, (None, np.inf))
317        if program_timestamp < timestamp:
318            del self.compiled[key]
[cde11f0f]319        if key not in self.compiled:
[821a9c6]320            context = self.get_context(dtype)
[20317b3]321            logging.info("building %s for OpenCL %s", key,
322                         context.devices[0].name.strip())
[fec69dd]323            program = compile_model(self.get_context(dtype),
324                                    str(source), dtype, fast)
[300a2f7]325            self.compiled[key] = (program, timestamp)
326        return program
[14de349]327
[3c56da87]328def _get_default_context():
[20317b3]329    # type: () -> List[cl.Context]
[eafc9fa]330    """
[d18582e]331    Get an OpenCL context, preferring GPU over CPU, and preferring Intel
332    drivers over AMD drivers.
[eafc9fa]333    """
[d18582e]334    # Note: on mobile devices there is automatic clock scaling if either the
335    # CPU or the GPU is underutilized; probably doesn't affect us, but we if
336    # it did, it would mean that putting a busy loop on the CPU while the GPU
337    # is running may increase throughput.
338    #
339    # Macbook pro, base install:
340    #     {'Apple': [Intel CPU, NVIDIA GPU]}
341    # Macbook pro, base install:
342    #     {'Apple': [Intel CPU, Intel GPU]}
343    # 2 x nvidia 295 with Intel and NVIDIA opencl drivers installed
344    #     {'Intel': [CPU], 'NVIDIA': [GPU, GPU, GPU, GPU]}
345    gpu, cpu = None, None
[3c56da87]346    for platform in cl.get_platforms():
[e6a5556]347        # AMD provides a much weaker CPU driver than Intel/Apple, so avoid it.
[20317b3]348        # If someone has bothered to install the AMD/NVIDIA drivers, prefer
349        # them over the integrated graphics driver that may have been supplied
350        # with the CPU chipset.
351        preferred_cpu = (platform.vendor.startswith('Intel')
352                         or platform.vendor.startswith('Apple'))
353        preferred_gpu = (platform.vendor.startswith('Advanced')
354                         or platform.vendor.startswith('NVIDIA'))
[3c56da87]355        for device in platform.get_devices():
356            if device.type == cl.device_type.GPU:
[20317b3]357                # If the existing type is not GPU then it will be CUSTOM
358                # or ACCELERATOR so don't override it.
[e6a5556]359                if gpu is None or (preferred_gpu and gpu.type == cl.device_type.GPU):
360                    gpu = device
361            elif device.type == cl.device_type.CPU:
362                if cpu is None or preferred_cpu:
363                    cpu = device
[d18582e]364            else:
[e6a5556]365                # System has cl.device_type.ACCELERATOR or cl.device_type.CUSTOM
366                # Intel Phi for example registers as an accelerator
[20317b3]367                # Since the user installed a custom device on their system
368                # and went through the pain of sorting out OpenCL drivers for
369                # it, lets assume they really do want to use it as their
370                # primary compute device.
[e6a5556]371                gpu = device
[199d40d]372
[20317b3]373    # order the devices by gpu then by cpu; when searching for an available
374    # device by data type they will be checked in this order, which means
375    # that if the gpu supports double then the cpu will never be used (though
376    # we may make it possible to explicitly request the cpu at some point).
[e6a5556]377    devices = []
378    if gpu is not None:
379        devices.append(gpu)
380    if cpu is not None:
381        devices.append(cpu)
382    return [cl.Context([d]) for d in devices]
[3c56da87]383
[250fa25]384
[f619de7]385class GpuModel(KernelModel):
[14de349]386    """
387    GPU wrapper for a single model.
388
[17bbadd]389    *source* and *model_info* are the model source and interface as returned
390    from :func:`generate.make_source` and :func:`generate.make_model_info`.
[14de349]391
392    *dtype* is the desired model precision.  Any numpy dtype for single
393    or double precision floats will do, such as 'f', 'float32' or 'single'
394    for single and 'd', 'float64' or 'double' for double.  Double precision
395    is an optional extension which may not be available on all devices.
[cde11f0f]396    Half precision ('float16','half') may be available on some devices.
397    Fast precision ('fast') is a loose version of single precision, indicating
398    that the compiler is allowed to take shortcuts.
[14de349]399    """
[dd7fc12]400    def __init__(self, source, model_info, dtype=generate.F32, fast=False):
[a4280bd]401        # type: (Dict[str,str], ModelInfo, np.dtype, bool) -> None
[17bbadd]402        self.info = model_info
[ce27e21]403        self.source = source
[dd7fc12]404        self.dtype = dtype
405        self.fast = fast
[ce27e21]406        self.program = None # delay program creation
[20317b3]407        self._kernels = None
[14de349]408
[ce27e21]409    def __getstate__(self):
[dd7fc12]410        # type: () -> Tuple[ModelInfo, str, np.dtype, bool]
[eafc9fa]411        return self.info, self.source, self.dtype, self.fast
[14de349]412
[ce27e21]413    def __setstate__(self, state):
[dd7fc12]414        # type: (Tuple[ModelInfo, str, np.dtype, bool]) -> None
[eafc9fa]415        self.info, self.source, self.dtype, self.fast = state
416        self.program = None
[ce27e21]417
[9eb3632]418    def make_kernel(self, q_vectors):
[dd7fc12]419        # type: (List[np.ndarray]) -> "GpuKernel"
[ce27e21]420        if self.program is None:
[a4280bd]421            compile_program = environment().compile_program
[0dc34c3]422            timestamp = generate.ocl_timestamp(self.info)
[a4280bd]423            self.program = compile_program(
424                self.info.name,
425                self.source['opencl'],
426                self.dtype,
[300a2f7]427                self.fast,
428                timestamp)
[a4280bd]429            variants = ['Iq', 'Iqxy', 'Imagnetic']
430            names = [generate.kernel_name(self.info, k) for k in variants]
431            kernels = [getattr(self.program, k) for k in names]
[20317b3]432            self._kernels = dict((k, v) for k, v in zip(variants, kernels))
[eafc9fa]433        is_2d = len(q_vectors) == 2
[a4280bd]434        if is_2d:
435            kernel = [self._kernels['Iqxy'], self._kernels['Imagnetic']]
436        else:
437            kernel = [self._kernels['Iq']]*2
[f2f67a6]438        return GpuKernel(kernel, self.dtype, self.info, q_vectors)
[ce27e21]439
440    def release(self):
[dd7fc12]441        # type: () -> None
[eafc9fa]442        """
443        Free the resources associated with the model.
444        """
[ce27e21]445        if self.program is not None:
446            self.program = None
[14de349]447
[eafc9fa]448    def __del__(self):
[dd7fc12]449        # type: () -> None
[eafc9fa]450        self.release()
[14de349]451
452# TODO: check that we don't need a destructor for buffers which go out of scope
453class GpuInput(object):
454    """
455    Make q data available to the gpu.
456
457    *q_vectors* is a list of q vectors, which will be *[q]* for 1-D data,
458    and *[qx, qy]* for 2-D data.  Internally, the vectors will be reallocated
459    to get the best performance on OpenCL, which may involve shifting and
460    stretching the array to better match the memory architecture.  Additional
461    points will be evaluated with *q=1e-3*.
462
463    *dtype* is the data type for the q vectors. The data type should be
464    set to match that of the kernel, which is an attribute of
465    :class:`GpuProgram`.  Note that not all kernels support double
466    precision, so even if the program was created for double precision,
467    the *GpuProgram.dtype* may be single precision.
468
469    Call :meth:`release` when complete.  Even if not called directly, the
470    buffer will be released when the data object is freed.
471    """
[cb6ecf4]472    def __init__(self, q_vectors, dtype=generate.F32):
[dd7fc12]473        # type: (List[np.ndarray], np.dtype) -> None
[17bbadd]474        # TODO: do we ever need double precision q?
[14de349]475        env = environment()
476        self.nq = q_vectors[0].size
477        self.dtype = np.dtype(dtype)
[eafc9fa]478        self.is_2d = (len(q_vectors) == 2)
[f5b9a6b]479        # TODO: stretch input based on get_warp()
480        # not doing it now since warp depends on kernel, which is not known
481        # at this point, so instead using 32, which is good on the set of
482        # architectures tested so far.
[c072f83]483        if self.is_2d:
[b8ddf2e]484            # Note: 16 rather than 15 because result is 1 longer than input.
485            width = ((self.nq+16)//16)*16
[c072f83]486            self.q = np.empty((width, 2), dtype=dtype)
487            self.q[:self.nq, 0] = q_vectors[0]
488            self.q[:self.nq, 1] = q_vectors[1]
489        else:
[b8ddf2e]490            # Note: 32 rather than 31 because result is 1 longer than input.
491            width = ((self.nq+32)//32)*32
[c072f83]492            self.q = np.empty(width, dtype=dtype)
493            self.q[:self.nq] = q_vectors[0]
494        self.global_size = [self.q.shape[0]]
[d18582e]495        context = env.get_context(self.dtype)
[c094758]496        #print("creating inputs of size", self.global_size)
[c072f83]497        self.q_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR,
498                             hostbuf=self.q)
[14de349]499
500    def release(self):
[dd7fc12]501        # type: () -> None
[eafc9fa]502        """
503        Free the memory.
504        """
[f2f67a6]505        if self.q_b is not None:
506            self.q_b.release()
507            self.q_b = None
[14de349]508
[eafc9fa]509    def __del__(self):
[dd7fc12]510        # type: () -> None
[eafc9fa]511        self.release()
512
[f619de7]513class GpuKernel(Kernel):
[ff7119b]514    """
515    Callable SAS kernel.
516
[eafc9fa]517    *kernel* is the GpuKernel object to call
[ff7119b]518
[17bbadd]519    *model_info* is the module information
[ff7119b]520
[eafc9fa]521    *q_vectors* is the q vectors at which the kernel should be evaluated
522
523    *dtype* is the kernel precision
[ff7119b]524
525    The resulting call method takes the *pars*, a list of values for
526    the fixed parameters to the kernel, and *pd_pars*, a list of (value,weight)
527    vectors for the polydisperse parameters.  *cutoff* determines the
528    integration limits: any points with combined weight less than *cutoff*
529    will not be calculated.
530
531    Call :meth:`release` when done with the kernel instance.
532    """
[f2f67a6]533    def __init__(self, kernel, dtype, model_info, q_vectors):
534        # type: (cl.Kernel, np.dtype, ModelInfo, List[np.ndarray]) -> None
535        q_input = GpuInput(q_vectors, dtype)
[14de349]536        self.kernel = kernel
[17bbadd]537        self.info = model_info
[f2f67a6]538        self.dtype = dtype
[a5b8477]539        self.dim = '2d' if q_input.is_2d else '1d'
[c072f83]540        # plus three for the normalization values
[b8ddf2e]541        self.result = np.empty(q_input.nq+1, dtype)
[14de349]542
543        # Inputs and outputs for each kernel call
[ce27e21]544        # Note: res may be shorter than res_b if global_size != nq
545        env = environment()
[f2f67a6]546        self.queue = env.get_queue(dtype)
[c072f83]547
548        self.result_b = cl.Buffer(self.queue.context, mf.READ_WRITE,
[b8ddf2e]549                                  q_input.global_size[0] * dtype.itemsize)
[c072f83]550        self.q_input = q_input # allocated by GpuInput above
[14de349]551
[20317b3]552        self._need_release = [self.result_b, self.q_input]
[f2f67a6]553        self.real = (np.float32 if dtype == generate.F32
554                     else np.float64 if dtype == generate.F64
555                     else np.float16 if dtype == generate.F16
[8d62008]556                     else np.float32)  # will never get here, so use np.float32
[d18582e]557
[32e3c9b]558    def __call__(self, call_details, values, cutoff, magnetic):
559        # type: (CallDetails, np.ndarray, np.ndarray, float, bool) -> np.ndarray
[48fbd50]560        context = self.queue.context
[8d62008]561        # Arrange data transfer to card
[48fbd50]562        details_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR,
[8d62008]563                              hostbuf=call_details.buffer)
[48fbd50]564        values_b = cl.Buffer(context, mf.READ_ONLY | mf.COPY_HOST_PTR,
565                             hostbuf=values)
566
[9eb3632]567        kernel = self.kernel[1 if magnetic else 0]
568        args = [
569            np.uint32(self.q_input.nq), None, None,
570            details_b, values_b, self.q_input.q_b, self.result_b,
571            self.real(cutoff),
572        ]
573        #print("Calling OpenCL")
[bde38b5]574        #call_details.show(values)
[ae2b6b5]575        # Call kernel and retrieve results
[6e5b2a7]576        wait_for = None
577        last_nap = time.clock()
578        step = 1000000//self.q_input.nq + 1
[bde38b5]579        for start in range(0, call_details.num_eval, step):
580            stop = min(start + step, call_details.num_eval)
[9eb3632]581            #print("queuing",start,stop)
582            args[1:3] = [np.int32(start), np.int32(stop)]
[6e5b2a7]583            wait_for = [kernel(self.queue, self.q_input.global_size, None,
584                               *args, wait_for=wait_for)]
585            if stop < call_details.num_eval:
586                # Allow other processes to run
587                wait_for[0].wait()
588                current_time = time.clock()
589                if current_time - last_nap > 0.5:
590                    time.sleep(0.05)
591                    last_nap = current_time
[c072f83]592        cl.enqueue_copy(self.queue, self.result, self.result_b)
[bde38b5]593        #print("result", self.result)
[ae2b6b5]594
595        # Free buffers
[a738209]596        for v in (details_b, values_b):
[c1114bf]597            if v is not None:
598                v.release()
[14de349]599
[14a15a3]600        pd_norm = self.result[self.q_input.nq]
[c1114bf]601        scale = values[0]/(pd_norm if pd_norm != 0.0 else 1.0)
[9eb3632]602        background = values[1]
[14a15a3]603        #print("scale",scale,values[0],self.result[self.q_input.nq],background)
[9eb3632]604        return scale*self.result[:self.q_input.nq] + background
605        # return self.result[:self.q_input.nq]
[14de349]606
607    def release(self):
[dd7fc12]608        # type: () -> None
[eafc9fa]609        """
610        Release resources associated with the kernel.
611        """
[d18582e]612        for v in self._need_release:
613            v.release()
614        self._need_release = []
[14de349]615
616    def __del__(self):
[dd7fc12]617        # type: () -> None
[14de349]618        self.release()
Note: See TracBrowser for help on using the repository browser.