gemm.hpp
Go to the documentation of this file.
1 //===========================================================================
2 /*!
3  *
4  *
5  * \brief -
6  *
7  * \author O. Krause
8  * \date 2016
9  *
10  *
11  * \par Copyright 1995-2015 Shark Development Team
12  *
13  * <BR><HR>
14  * This file is part of Shark.
15  * <http://image.diku.dk/shark/>
16  *
17  * Shark is free software: you can redistribute it and/or modify
18  * it under the terms of the GNU Lesser General Public License as published
19  * by the Free Software Foundation, either version 3 of the License, or
20  * (at your option) any later version.
21  *
22  * Shark is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25  * GNU Lesser General Public License for more details.
26  *
27  * You should have received a copy of the GNU Lesser General Public License
28  * along with Shark. If not, see <http://www.gnu.org/licenses/>.
29  *
30  */
31 //===========================================================================
32 #ifndef REMORA_KERNELS_GPU_GEMM_HPP
33 #define REMORA_KERNELS_GPU_GEMM_HPP
34 
35 #include "../../expression_types.hpp"
36 #include "../../detail/traits.hpp"
37 #include <boost/compute/kernel.hpp>
38 #include <boost/compute/detail/meta_kernel.hpp>
39 #include <boost/compute/functional/operator.hpp> //for multiplies
40 
41 namespace remora{ namespace kernels{
42 
43 // C <- alpha * A * B + beta * C
44 template <typename MatA, typename MatB, typename MatC>
45 void gemm(
46  matrix_expression<MatA, gpu_tag> const& A,
47  matrix_expression<MatB, gpu_tag> const& B,
48  matrix_expression<MatC, gpu_tag>& C,
49  typename MatC::value_type const& alpha
50 ) {
51  REMORA_SIZE_CHECK(A().size1() == C().size1());
52  REMORA_SIZE_CHECK(B().size2() == C().size2());
53  REMORA_SIZE_CHECK(A().size2()== B().size1());
54 
55  // TUNING VARIABLES:
56  // TILE_SIZE: width and height of a tile computed in C
57  // TILE_SIZE_K: length of Tile loaded from A and C, i.e A and C are loaded as (TILE_SIZE, TILE_SIZE_K) matrices
58  // BLOCK_SIZE: size of a subblock computed by a single work item (must divide TILE_SIZE)
59  // it holds TILE_SIZE = get_local_size(i) * BLOCK_SIZE for i=0,1
60  // note that BLOCK_SIZE increases the number of registers needed per thread.
61  // roughly O(BLOCK_SIZE^2) registers are needed per thread (+ overhead). Note that register spill might be deadly to performance.
62  // local memory is TILE_SIZE*TILE_SIZE_K*2*sizeof(MatC::value_type)
63  // increasing TILE_SIZE improves the trade-off "computations per memory access"
64  std::size_t BLOCK_SIZE = 4;
65  std::size_t TILE_SIZE = 32;
66  std::size_t NUM_WORKERS = TILE_SIZE / BLOCK_SIZE;
67  //~ std::size_t TILE_SIZE_K = 16;
68  char const* options ="-DTILE_SIZE=32ul -DBLOCK_SIZE=4ul -DTILE_SIZE_K=16ul";
69  typedef typename MatC::value_type value_type;
70 
71  boost::compute::detail::meta_kernel k("blas_gemm");
72  std::size_t M_index = k.add_arg<std::size_t>("M");
73  std::size_t N_index = k.add_arg<std::size_t>("N");
74  std::size_t K_index = k.add_arg<std::size_t>("K");
75  std::size_t alpha_index = k.add_arg<value_type>("alpha");
76  // Local memory to fit a tile of A and B
77  // we transpose A locally in memory
78  k << "__local " <<k.decl<value_type>("Asub")<< "[TILE_SIZE_K][TILE_SIZE+2];\n";//+2 to avoid bank conflicts
79  k << "__local " <<k.decl<value_type>("Bsub")<< "[TILE_SIZE_K][TILE_SIZE+2];\n";//+2 to avoid bank conflicts
80  k << " const ulong numWorkers = get_local_size(0);\n";
81  // Initialise the accumulation registers
82  // here the subblock of C for this thread is stored
83  // blocks ae not continuous but strided so that
84  // coalesced write to C is possible
85  // e.g. with 8x8 threads and BLOCK_SIZE 2x2 thread 1 has local tile elements
86  //(0,0) (0,8) (8,0), (8,8). all other blocks are calculated
87  // by adding (local_id(0), local_id(1)) to the coordinates
88  k << k.decl<value_type>("acc") <<"[BLOCK_SIZE][BLOCK_SIZE];\n";
89  k << "for (ulong wm=0; wm<BLOCK_SIZE; wm++){\n";
90  k << " for (ulong wn=0; wn<BLOCK_SIZE; wn++){\n";
91  k << " acc[wm][wn] = 0.0f;\n";
92  k << " }\n";
93  k << "}\n";
94 
95 
96  // Loop over all tiles
97  k << "ulong numTiles = (K+TILE_SIZE_K-1)/TILE_SIZE_K;\n";
98  k << "for (ulong t=0; t<numTiles; t++){\n";
99 
100  //ensure we are not reading out of bounds in K.
101  k << " const ulong curTileK = min(TILE_SIZE_K, K - t*TILE_SIZE_K);\n";
102 
103  // Load one tile of A and B transposed ulongo local memory using padding
104  k << " for(ulong i = get_local_id(0); i < TILE_SIZE; i += numWorkers){\n";
105  k << " for(ulong k = get_local_id(1); k < curTileK; k += numWorkers){\n";
106  k << " ulong ktile = t * TILE_SIZE_K + k;\n";
107  k << " Asub[k][i] ="<< A()(k.expr<cl_ulong>("min(M-1,TILE_SIZE * get_group_id(0)+i)"),k.expr<cl_ulong>("ktile"))<<";\n";
108  k << " Bsub[k][i] ="<< B()(k.expr<cl_ulong>("ktile"),k.expr<cl_ulong>("min(N-1,TILE_SIZE * get_group_id(1)+i)"))<<";\n";
109  k << " }\n";
110  k << " }\n";
111 
112  // Synchronise to make sure the tile is loaded
113  k << " barrier(CLK_LOCAL_MEM_FENCE);\n";
114 
115  // Loop over the values of a single tile
116  // by computing outer products ulongo the local accumulation registers acc
117  k << " for (ulong k=0; k<curTileK; k++){\n";
118  // Cache the values of Bsub in registers to save local memory lookups
119  k << k.decl<value_type>("Breg")<<"[BLOCK_SIZE];\n";
120  k << " for (ulong wn=0; wn<BLOCK_SIZE; wn++){\n";
121  k << " Breg[wn] = Bsub[k][get_local_id(1) + wn * numWorkers];\n";
122  k << " }\n";
123 
124  // Perform the computation
125  k << " for (ulong wm = 0; wm<BLOCK_SIZE; wm++){\n";
126  k << k.decl<value_type>("Areg") << "= Asub[k][get_local_id(0) + wm * numWorkers];\n";
127  k << " for (ulong wn=0; wn<BLOCK_SIZE; wn++){\n";
128  k << " acc[wm][wn] += Areg * Breg[wn];\n";
129  k << " }\n";
130  k << " }\n";
131  k << " }\n";
132 
133  // Synchronise before loading the next tile
134  k << " barrier(CLK_LOCAL_MEM_FENCE);\n";
135  k << "}\n";
136 
137  // Store the final results in C
138  k << "const ulong maxCi = min(TILE_SIZE, M - get_group_id(0) * TILE_SIZE);\n";
139  k << "const ulong maxCj = min(TILE_SIZE, N - get_group_id(1) * TILE_SIZE);\n";
140  k << "const ulong offTileCi = TILE_SIZE * get_group_id(0);\n";
141  k << "const ulong offTileCj = TILE_SIZE * get_group_id(1);\n";
142  k << "ulong wm = 0;\n";
143  k << "for (ulong i = get_local_id(0); i < maxCi; i += numWorkers, wm++){\n";
144  k << " ulong wn = 0;\n";
145  k << " for (ulong j =get_local_id(1); j < maxCj; j += numWorkers, wn++){\n";
146  k << C()(k.expr<cl_ulong>("(offTileCi + i)"), k.expr<cl_ulong>("(offTileCj + j)")) << "+= alpha * acc[wm][wn];\n";
147  k << " }\n";
148  k << "}\n";
149 
150  boost::compute::kernel kernel = k.compile(C().queue().get_context(), options);
151 
152  //enqueue kernel with kernel args
153  kernel.set_arg(M_index, C().size1());
154  kernel.set_arg(N_index, C().size2());
155  kernel.set_arg(K_index, A().size2());
156  kernel.set_arg(alpha_index, alpha);
157 
158  std::size_t global_work_size[2] = {
159  (C().size1()+TILE_SIZE-1)/ TILE_SIZE * NUM_WORKERS,
160  (C().size2()+TILE_SIZE-1)/ TILE_SIZE * NUM_WORKERS
161  };
162  std::size_t local_work_size[2] = {NUM_WORKERS, NUM_WORKERS};
163  C().queue().enqueue_nd_range_kernel(kernel, 2,nullptr, global_work_size, local_work_size);
164 }
165 
166 }}
167 
168 #endif