Rename all source files from - to _ for consistency.
[alexxy/gromacs.git] / src / gromacs / ewald / pme_gather.cu
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2016,2017,2018,2019, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35
36 /*! \internal \file
37  *  \brief Implements PME force gathering in CUDA.
38  *
39  *  \author Aleksei Iupinov <a.yupinov@gmail.com>
40  */
41
42 #include "gmxpre.h"
43
44 #include <cassert>
45
46 #include "pme.cuh"
47 #include "pme_gpu_utils.h"
48
49 /*! \brief
50  * An inline CUDA function: unroll the dynamic index accesses to the constant grid sizes to avoid local memory operations.
51  */
52 __device__ __forceinline__ float read_grid_size(const float *realGridSizeFP,
53                                                 const int    dimIndex)
54 {
55     switch (dimIndex)
56     {
57         case XX: return realGridSizeFP[XX];
58         case YY: return realGridSizeFP[YY];
59         case ZZ: return realGridSizeFP[ZZ];
60     }
61     assert(false);
62     return 0.0f;
63 }
64
65 /*! \brief Reduce the partial force contributions.
66  *
67  * \tparam[in] order              The PME order (must be 4).
68  * \tparam[in] atomDataSize       The number of partial force contributions for each atom (currently order^2 == 16)
69  * \tparam[in] blockSize          The CUDA block size
70  * \param[out] sm_forces          Shared memory array with the output forces (number of elements is number of atoms per block)
71  * \param[in]  atomIndexLocal     Local atom index
72  * \param[in]  splineIndex        Spline index
73  * \param[in]  lineIndex          Line index (same as threadLocalId)
74  * \param[in]  realGridSizeFP     Local grid size constant
75  * \param[in]  fx                 Input force partial component X
76  * \param[in]  fy                 Input force partial component Y
77  * \param[in]  fz                 Input force partial component Z
78  */
79 template <
80     const int order,
81     const int atomDataSize,
82     const int blockSize
83     >
84 __device__ __forceinline__ void reduce_atom_forces(float3 * __restrict__ sm_forces,
85                                                    const int             atomIndexLocal,
86                                                    const int             splineIndex,
87                                                    const int             lineIndex,
88                                                    const float          *realGridSizeFP,
89                                                    float                &fx,
90                                                    float                &fy,
91                                                    float                &fz)
92 {
93     if (!(order & (order - 1))) // Only for orders of power of 2
94     {
95         const unsigned int activeMask = c_fullWarpMask;
96
97         // A tricky shuffle reduction inspired by reduce_force_j_warp_shfl
98         // TODO: find out if this is the best in terms of transactions count
99         static_assert(order == 4, "Only order of 4 is implemented");
100         static_assert(atomDataSize <= warp_size, "TODO: rework for atomDataSize > warp_size (order 8 or larger)");
101         const int width = atomDataSize;
102
103         fx += gmx_shfl_down_sync(activeMask, fx, 1, width);
104         fy += gmx_shfl_up_sync  (activeMask, fy, 1, width);
105         fz += gmx_shfl_down_sync(activeMask, fz, 1, width);
106
107         if (splineIndex & 1)
108         {
109             fx = fy;
110         }
111
112         fx += gmx_shfl_down_sync(activeMask, fx, 2, width);
113         fz += gmx_shfl_up_sync  (activeMask, fz, 2, width);
114
115         if (splineIndex & 2)
116         {
117             fx = fz;
118         }
119
120         // By now fx contains intermediate quad sums of all 3 components:
121         // splineIndex    0            1            2 and 3      4            5            6 and 7      8...
122         // sum of...      fx0 to fx3   fy0 to fy3   fz0 to fz3   fx4 to fx7   fy4 to fy7   fz4 to fz7   etc.
123
124         // We have to just further reduce those groups of 4
125         for (int delta = 4; delta < atomDataSize; delta <<= 1)
126         {
127             fx += gmx_shfl_down_sync(activeMask, fx, delta, width);
128         }
129
130         const int dimIndex = splineIndex;
131         if (dimIndex < DIM)
132         {
133             const float n = read_grid_size(realGridSizeFP, dimIndex);
134             *((float *)(&sm_forces[atomIndexLocal]) + dimIndex) = fx * n;
135         }
136     }
137     else
138     {
139         // We use blockSize shared memory elements to read fx, or fy, or fz, and then reduce them to fit into smemPerDim elements
140         // which are stored separately (first 2 dimensions only)
141         const int         smemPerDim   = warp_size;
142         const int         smemReserved = (DIM - 1) * smemPerDim;
143         __shared__ float  sm_forceReduction[smemReserved + blockSize];
144         __shared__ float *sm_forceTemp[DIM];
145
146         const int         numWarps  = blockSize / smemPerDim;
147         const int         minStride = max(1, atomDataSize / numWarps); // order 4: 128 threads => 4, 256 threads => 2, etc
148
149 #pragma unroll
150         for (int dimIndex = 0; dimIndex < DIM; dimIndex++)
151         {
152             int elementIndex = smemReserved + lineIndex;
153             // Store input force contributions
154             sm_forceReduction[elementIndex] = (dimIndex == XX) ? fx : (dimIndex == YY) ? fy : fz;
155             // Reduce to fit into smemPerDim (warp size)
156 #pragma unroll
157             for (int redStride = atomDataSize / 2; redStride > minStride; redStride >>= 1)
158             {
159                 if (splineIndex < redStride)
160                 {
161                     sm_forceReduction[elementIndex] += sm_forceReduction[elementIndex + redStride];
162                 }
163             }
164             __syncthreads();
165             // Last iteration - packing everything to be nearby, storing convenience pointer
166             sm_forceTemp[dimIndex] = sm_forceReduction + dimIndex * smemPerDim;
167             int redStride = minStride;
168             if (splineIndex < redStride)
169             {
170                 const int packedIndex = atomIndexLocal * redStride + splineIndex;
171                 sm_forceTemp[dimIndex][packedIndex] = sm_forceReduction[elementIndex] + sm_forceReduction[elementIndex + redStride];
172             }
173         }
174
175         __syncthreads();
176
177         assert ((blockSize / warp_size) >= DIM);
178         //assert (atomsPerBlock <= warp_size);
179
180         const int warpIndex = lineIndex / warp_size;
181         const int dimIndex  = warpIndex;
182
183         // First 3 warps can now process 1 dimension each
184         if (dimIndex < DIM)
185         {
186             int sourceIndex = lineIndex % warp_size;
187 #pragma unroll
188             for (int redStride = minStride / 2; redStride > 1; redStride >>= 1)
189             {
190                 if (!(splineIndex & redStride))
191                 {
192                     sm_forceTemp[dimIndex][sourceIndex] += sm_forceTemp[dimIndex][sourceIndex + redStride];
193                 }
194             }
195
196             const float n = read_grid_size(realGridSizeFP, dimIndex);
197
198             const int   atomIndex = sourceIndex / minStride;
199             if (sourceIndex == minStride * atomIndex)
200             {
201                 *((float *)(&sm_forces[atomIndex]) + dimIndex) = (sm_forceTemp[dimIndex][sourceIndex] + sm_forceTemp[dimIndex][sourceIndex + 1]) * n;
202             }
203         }
204     }
205 }
206
207 /*! \brief
208  * A CUDA kernel which gathers the atom forces from the grid.
209  * The grid is assumed to be wrapped in dimension Z.
210  *
211  * \tparam[in] order                The PME order (must be 4 currently).
212  * \tparam[in] overwriteForces      True: the forces are written to the output buffer;
213  *                                  False: the forces are added non-atomically to the output buffer (e.g. to the bonded forces).
214  * \tparam[in] wrapX                Tells if the grid is wrapped in the X dimension.
215  * \tparam[in] wrapY                Tells if the grid is wrapped in the Y dimension.
216  * \param[in]  kernelParams         All the PME GPU data.
217  */
218 template <
219     const int order,
220     const bool overwriteForces,
221     const bool wrapX,
222     const bool wrapY
223     >
224 __launch_bounds__(c_gatherMaxThreadsPerBlock, c_gatherMinBlocksPerMP)
225 __global__ void pme_gather_kernel(const PmeGpuCudaKernelParams    kernelParams)
226 {
227     /* Global memory pointers */
228     const float * __restrict__  gm_coefficients     = kernelParams.atoms.d_coefficients;
229     const float * __restrict__  gm_grid             = kernelParams.grid.d_realGrid;
230     const float * __restrict__  gm_theta            = kernelParams.atoms.d_theta;
231     const float * __restrict__  gm_dtheta           = kernelParams.atoms.d_dtheta;
232     const int * __restrict__    gm_gridlineIndices  = kernelParams.atoms.d_gridlineIndices;
233     float * __restrict__        gm_forces           = kernelParams.atoms.d_forces;
234
235     /* Some sizes */
236     const int    atomsPerBlock  = (c_gatherMaxThreadsPerBlock / c_pmeSpreadGatherThreadsPerAtom);
237     const int    atomDataSize   = c_pmeSpreadGatherThreadsPerAtom; /* Number of data components and threads for a single atom */
238     const int    atomsPerWarp   = c_pmeSpreadGatherAtomsPerWarp;
239
240     const int    blockSize      = atomsPerBlock * atomDataSize;
241     assert(blockSize == blockDim.x * blockDim.y * blockDim.z);
242     const int    blockIndex = blockIdx.y * gridDim.x + blockIdx.x;
243
244     /* These are the atom indices - for the shared and global memory */
245     const int         atomIndexLocal    = threadIdx.z;
246     const int         atomIndexOffset   = blockIndex * atomsPerBlock;
247     const int         atomIndexGlobal   = atomIndexOffset + atomIndexLocal;
248
249     /* Early return for fully empty blocks at the end
250      * (should only happen for billions of input atoms)
251      */
252     if (atomIndexOffset >= kernelParams.atoms.nAtoms)
253     {
254         return;
255     }
256
257     const int         splineParamsSize             = atomsPerBlock * DIM * order;
258     const int         gridlineIndicesSize          = atomsPerBlock * DIM;
259     __shared__ int    sm_gridlineIndices[gridlineIndicesSize];
260     __shared__ float2 sm_splineParams[splineParamsSize]; /* Theta/dtheta pairs  as .x/.y */
261
262     /* Spline Y/Z coordinates */
263     const int ithy = threadIdx.y;
264     const int ithz = threadIdx.x;
265
266     /* These are the spline contribution indices in shared memory */
267     const int splineIndex = threadIdx.y * blockDim.x + threadIdx.x;                  /* Relative to the current particle , 0..15 for order 4 */
268     const int lineIndex   = (threadIdx.z * (blockDim.x * blockDim.y)) + splineIndex; /* And to all the block's particles */
269
270     int       threadLocalId = (threadIdx.z * (blockDim.x * blockDim.y))
271         + (threadIdx.y * blockDim.x)
272         + threadIdx.x;
273
274     /* Staging the atom gridline indices, DIM * atomsPerBlock threads */
275     const int localGridlineIndicesIndex  = threadLocalId;
276     const int globalGridlineIndicesIndex = blockIndex * gridlineIndicesSize + localGridlineIndicesIndex;
277     const int globalCheckIndices         = pme_gpu_check_atom_data_index(globalGridlineIndicesIndex, kernelParams.atoms.nAtoms * DIM);
278     if ((localGridlineIndicesIndex < gridlineIndicesSize) & globalCheckIndices)
279     {
280         sm_gridlineIndices[localGridlineIndicesIndex] = gm_gridlineIndices[globalGridlineIndicesIndex];
281         assert(sm_gridlineIndices[localGridlineIndicesIndex] >= 0);
282     }
283     /* Staging the spline parameters, DIM * order * atomsPerBlock threads */
284     const int localSplineParamsIndex  = threadLocalId;
285     const int globalSplineParamsIndex = blockIndex * splineParamsSize + localSplineParamsIndex;
286     const int globalCheckSplineParams = pme_gpu_check_atom_data_index(globalSplineParamsIndex, kernelParams.atoms.nAtoms * DIM * order);
287     if ((localSplineParamsIndex < splineParamsSize) && globalCheckSplineParams)
288     {
289         sm_splineParams[localSplineParamsIndex].x = gm_theta[globalSplineParamsIndex];
290         sm_splineParams[localSplineParamsIndex].y = gm_dtheta[globalSplineParamsIndex];
291         assert(isfinite(sm_splineParams[localSplineParamsIndex].x));
292         assert(isfinite(sm_splineParams[localSplineParamsIndex].y));
293     }
294     __syncthreads();
295
296     float           fx = 0.0f;
297     float           fy = 0.0f;
298     float           fz = 0.0f;
299
300     const int       globalCheck = pme_gpu_check_atom_data_index(atomIndexGlobal, kernelParams.atoms.nAtoms);
301     const int       chargeCheck = pme_gpu_check_atom_charge(gm_coefficients[atomIndexGlobal]);
302
303     if (chargeCheck & globalCheck)
304     {
305         const int    nx        = kernelParams.grid.realGridSize[XX];
306         const int    ny        = kernelParams.grid.realGridSize[YY];
307         const int    nz        = kernelParams.grid.realGridSize[ZZ];
308         const int    pny       = kernelParams.grid.realGridSizePadded[YY];
309         const int    pnz       = kernelParams.grid.realGridSizePadded[ZZ];
310
311         const int    atomWarpIndex = atomIndexLocal % atomsPerWarp;
312         const int    warpIndex     = atomIndexLocal / atomsPerWarp;
313
314         const int    splineIndexBase = getSplineParamIndexBase<order, atomsPerWarp>(warpIndex, atomWarpIndex);
315         const int    splineIndexY    = getSplineParamIndex<order, atomsPerWarp>(splineIndexBase, YY, ithy);
316         const float2 tdy             = sm_splineParams[splineIndexY];
317         const int    splineIndexZ    = getSplineParamIndex<order, atomsPerWarp>(splineIndexBase, ZZ, ithz);
318         const float2 tdz             = sm_splineParams[splineIndexZ];
319
320         const int    ixBase         = sm_gridlineIndices[atomIndexLocal * DIM + XX];
321         int          iy             = sm_gridlineIndices[atomIndexLocal * DIM + YY] + ithy;
322         if (wrapY & (iy >= ny))
323         {
324             iy -= ny;
325         }
326         int iz  = sm_gridlineIndices[atomIndexLocal * DIM + ZZ] + ithz;
327         if (iz >= nz)
328         {
329             iz -= nz;
330         }
331         const int constOffset    = iy * pnz + iz;
332
333 #pragma unroll
334         for (int ithx = 0; (ithx < order); ithx++)
335         {
336             int ix = ixBase + ithx;
337             if (wrapX & (ix >= nx))
338             {
339                 ix -= nx;
340             }
341             const int     gridIndexGlobal  = ix * pny * pnz + constOffset;
342             assert(gridIndexGlobal >= 0);
343             const float   gridValue    = gm_grid[gridIndexGlobal];
344             assert(isfinite(gridValue));
345             const int     splineIndexX = getSplineParamIndex<order, atomsPerWarp>(splineIndexBase, XX, ithx);
346             const float2  tdx          = sm_splineParams[splineIndexX];
347             const float   fxy1         = tdz.x * gridValue;
348             const float   fz1          = tdz.y * gridValue;
349             fx += tdx.y * tdy.x * fxy1;
350             fy += tdx.x * tdy.y * fxy1;
351             fz += tdx.x * tdy.x * fz1;
352         }
353     }
354
355     // Reduction of partial force contributions
356     __shared__ float3 sm_forces[atomsPerBlock];
357     reduce_atom_forces<order, atomDataSize, blockSize>(sm_forces,
358                                                        atomIndexLocal, splineIndex, lineIndex,
359                                                        kernelParams.grid.realGridSizeFP,
360                                                        fx, fy, fz);
361     __syncthreads();
362
363     /* Calculating the final forces with no component branching, atomsPerBlock threads */
364     const int forceIndexLocal  = threadLocalId;
365     const int forceIndexGlobal = atomIndexOffset + forceIndexLocal;
366     const int calcIndexCheck   = pme_gpu_check_atom_data_index(forceIndexGlobal, kernelParams.atoms.nAtoms);
367     if ((forceIndexLocal < atomsPerBlock) & calcIndexCheck)
368     {
369         const float3  atomForces               = sm_forces[forceIndexLocal];
370         const float   negCoefficient           = -gm_coefficients[forceIndexGlobal];
371         float3        result;
372         result.x                   = negCoefficient * kernelParams.current.recipBox[XX][XX] * atomForces.x;
373         result.y                   = negCoefficient * (kernelParams.current.recipBox[XX][YY] * atomForces.x + kernelParams.current.recipBox[YY][YY] * atomForces.y);
374         result.z                   = negCoefficient * (kernelParams.current.recipBox[XX][ZZ] * atomForces.x + kernelParams.current.recipBox[YY][ZZ] * atomForces.y + kernelParams.current.recipBox[ZZ][ZZ] * atomForces.z);
375         sm_forces[forceIndexLocal] = result;
376     }
377
378     gmx_syncwarp();
379     assert(atomsPerBlock <= warp_size);
380
381     /* Writing or adding the final forces component-wise, single warp */
382     const int blockForcesSize = atomsPerBlock * DIM;
383     const int numIter         = (blockForcesSize + warp_size - 1) / warp_size;
384     const int iterThreads     = blockForcesSize / numIter;
385     if (threadLocalId < iterThreads)
386     {
387 #pragma unroll
388         for (int i = 0; i < numIter; i++)
389         {
390             int         outputIndexLocal  = i * iterThreads + threadLocalId;
391             int         outputIndexGlobal = blockIndex * blockForcesSize + outputIndexLocal;
392             const int   globalOutputCheck = pme_gpu_check_atom_data_index(outputIndexGlobal, kernelParams.atoms.nAtoms * DIM);
393             if (globalOutputCheck)
394             {
395                 const float outputForceComponent = ((float *)sm_forces)[outputIndexLocal];
396                 if (overwriteForces)
397                 {
398                     gm_forces[outputIndexGlobal] = outputForceComponent;
399                 }
400                 else
401                 {
402                     gm_forces[outputIndexGlobal] += outputForceComponent;
403                 }
404             }
405         }
406     }
407 }
408
409 //! Kernel instantiations
410 template __global__ void pme_gather_kernel<4, true, true, true>(const PmeGpuCudaKernelParams);
411 template __global__ void pme_gather_kernel<4, false, true, true>(const PmeGpuCudaKernelParams);