]> AND Private Git Repository - book_gpu.git/blob - BookGPU/Chapters/chapter19/ch19.tex
Logo AND Algorithmique Numérique Distribuée

Private GIT Repository
fdf533d46bb0d01853770689c12ec0ccea95e697
[book_gpu.git] / BookGPU / Chapters / chapter19 / ch19.tex
1 \chapterauthor{Bertil Schmidt and Hoang-Vu Dang}{Johannes Gutenberg University of Mainz, Germany}
2 %\chapterauthor{Hoang-Vu Dang}{Johannes Gutenberg University of Mainz}
3
4 \chapter{Solving large sparse linear systems for integer factorization on GPUs}
5 \label{chapter19}
6
7 \section{Introduction}
8 \label{ch19:intro}
9
10 The Number Field Sieve (NFS)\index{iterative methods!Number Field Sieve} is the current state-of-the-art integer factorization method. It requires the solution of a large sparse linear system over GF(2) (called the linear algebra step). The Block Wiedemann\index{Number Field Sieve!Block Wiedemann} (BW)\cite{ch19:bw} algorithm can be used to solve such a large sparse linear system efficiently using iterative sparse matrix vector multiplication (SpMV).
11
12 Recent integer factorization efforts have been using CPU clusters to solve the large sparse linear system \cite{ch19:kilobit,ch19:rsa768}. The RSA-768 factorization \cite{ch19:rsa768}, for example, reported a runtime of 3 months for the linear algebra step on a cluster with 48 AMD dual hex-core CPUs. Previous work on parallelizing the linear algebra step focused on using CPU clusters and grids \cite{ch19:aoki,ch19:hwang,ch19:grid,ch19:hetero768}. In this chapter, we present a CUDA approach that can be used to accelerate the costly iterative SpMV operation for matrices derived from NFS.
13
14 The memory access pattern in the SpMV operation generally consists of regular access patterns over the matrix and irregular access patterns over the vector. The irregular access pattern over the vector is a challenge that is pronounced more on the GPU than on the CPU, because of the smaller cache and the restrictive memory access pattern requirement to achieve maximum performance. However, a high-end GPU has an order-of-magnitude higher bandwidth than a high-end CPU; e.g. a GeForce GTX 580 has 192.4 GB/s memory bandwidth, while an Intel Core-i7 has a maximum of 25.6 GB/s memory bandwidth. 
15
16 SpMV on the GPU has been explored previously in several papers \cite{ch19:nvidia,ch19:exactspmv,ch19:bellpack,ch19:sle} for matrices derived from scientific computing applications. However, sparse matrices derived from NFS have generally different properties, i.e. they are larger, have a few dense rows and have many extremely sparse rows. The large size of the matrix causes the BL and BW algorithms to require a large number of SpMV iterations. This means that the time spent for matrix preprocessing and matrix data transfer to the GPU memory are negligible compared to the total runtime. Thus, approaches to SpMV on GPUs for NFS matrices may be different from previously published GPU SpMV approaches. We further present an extension of our SpMV method for binary-valued NFS matrices to single-precision floating-point matrices.
17
18 \section{Block Wiedemann Algorithm}
19 \label{ch19:block-wiedemann}
20
21 The BW algorithm heuristically finds $n$ vectors in the kernel space \index{Number Field Sieve!kernel space}of a $d \times d$ binary matrix $B$, $n$ is one of two parameters $m, n$, called blocking factors\index{Number Field Sieve!blocking factors}. BW consists of the following steps:
22
23 \begin{itemize}
24 \item \textbf{Step 1 (BW1):} Compute the matrix sequence 
25
26 \begin{equation}
27 A_i = x \cdot B^i \cdot y, \forall i=1,...,{\frac{d}{m}}+{ \frac{d}{n} }+O(1)
28 \end{equation}
29
30  where $x,y$ are randomly chosen binary matrices of size $m \times d$ and $d \times n$ respectively.
31 \item \textbf{Step 2 (BW2):} The Berlekamp-Massey\index{Number Field Sieve!Berlekamp-Massey} algorithm \cite{ch19:Thome:subqad} is used to compute a generating polynomial of the matrix sequence $A$ from BW1 in the form
32 \begin{equation}
33 F(X)= \sum_{i=1}^{{ \frac{d}{n} }+O(1)} {C_i \cdot X^i}
34 \end{equation}
35 where $C_i$ is a $n \times n$ binary matrix
36
37 \item \textbf{Step 3 (BW3):} Compute the sequence of matrices $S$ of size $d \times n$ such that
38 \begin{equation}
39 S_i = B^i \cdot y \cdot C_i, \forall i=1,...,{\frac{d}{n}}+O(1)
40 \end{equation}
41
42 With high probability, $B \cdot \sum{S_i} = 0$ for which $\sum{S_i}$ is output as a solution.
43 \end{itemize}
44
45 We can treat $x$, $y$ and $S_i$ as vectors of block width $m$ or $n$. Assuming $B$ is a sparse matrix with $\gamma$ non-zeros per row on average and ignoring $m$ and $n$ as constant, the complexity of BW1 and BW3 is $O(\gamma d^2)$. Using the subquadratic algorithm by Thome \cite{ch19:Thome:subqad}, BW2 has a complexity of $O(d \log^2{d})$. Thus our approach to accelerate BW is based on accelerating BW1 and BW3 on a GPU while BW2 is still done on a CPU.
46
47 \section{SpMV OVER GF(2) for NFS matrices using existing formats on GPUs}
48 \label{ch19:existingspmv}
49         In this section, we review a few relevant previously published sparse matrix formats on GPUs and study their performance when applied to sparse matrices over GF(2) derived from integer factorization with NFS.
50         
51         We consider the sparse binary matrix $B$ of size $d \times d$ and a dense vector $X$ of size $d \times n$ bit, where $n$ is called the \emph{blocking factor}. Mordern processors generally support 64-bit operations. Thus, typical blocking factors are of the form of $64 \cdot k, \; k \in \mathbb{N}$. Note that doubling the blocking factor roughly halves the number of SpMV iterations required but doubles the input vector size. 
52
53         For all $0 \leq i \leq d-1$ let $c\_index[i]$ column index array of $B[i]$ which contains the indices of the non-zero entries of row $i$. Then, the following pseudocode shows a single SpMV iteration of $B$ with input vector $X$ and result vector $Y$.
54
55 %% \begin{algorithm}
56 %% \caption{SpMV-COLUMN-INDEX}
57 %%      \begin{algorithmic}
58 %%      \REQUIRE $c\_index[i]$: column array of rows $i$ of $B$;\\
59 %%                       $X$: input vector
60 %%      \ENSURE  $Y=B \cdot X$
61 %%      \FOR{$i=0$ to $d-1$}
62 %%      \STATE $Y[i]=0$
63 %%              \FORALL{$ind$ $\in$ $c\_index[i]$}
64 %%                      \STATE $Y[i] = Y[i] \oplus X[ind]$
65 %%                      \COMMENT{$\oplus:$ bitwise XOR operation}
66 %%              \ENDFOR
67 %%      \ENDFOR
68 %%      \end{algorithmic}
69 %% \end{algorithm}
70
71
72 \begin{algorithm}
73 \caption{SpMV-COLUMN-INDEX}
74 %       \begin{algorithmic}
75         \KwIn{$c\_index[i]$: column array of rows $i$ of $B$}
76         \KwIn{ $X$: input vector}
77         \KwOut{  $Y=B \cdot X$}
78         \For{$i=0$ to $d-1$}{
79         $Y[i]=0$\;   
80    \ForAll {$ind$ $\in$ $c\_index[i]$} {
81                 $Y[i] = Y[i] \oplus X[ind]$\;
82                 \CommentSty{$\oplus:$ bitwise XOR operation}
83 }
84 }
85 %       \end{algorithmic}
86 \end{algorithm}
87
88         
89         The costly operations in the SpMV pseudocode are the memory accesses for loading $ind$, $X[ind]$, $Y[i]$ and storing $Y[i]$. To speed up those operations on any architecture a common approach is to design a cache-friendly order of accessing the memory. The order is especially important for the vectors $X$ and $Y$, since their memory locations might be accessed multiple times. Accesses to $Y$ can be minimized by storing the intermediate results in fast memory and only write the accumulated result to $Y$.
90         
91         CUDA implementations of SpMV generally store both matrix and vectors in \emph{global memory}. Memory accesses to $B$ and $Y$ can usually be coalesced. Memory accesses to $X$ are random and non-coalesced thus having higher latency, but \emph{texture memory} can be used to take advantage of the \emph{texture cache}. Intermediate results could utilize \emph{shared memory} which has low latency however is very limited in size and only shared between threads of the same block. Shared memory is further divided into \emph{banks}. When threads in a warp access the shared memory, \emph{bank conflicts} should be avoided otherwise the access will be serialized.
92         
93         We now briefly review the CUDA implementation of a number of SpMV matrix formats published in previous papers \cite{ch19:nvidia,ch19:sle}. We also describe the data structure in which the matrix in Figure \ref{fig:ex_matrix} is stored using the format as an example. Please note that when storing a binary matrix, the $value$ array can be removed.
94         
95 \begin{figure}[t]
96         \centering
97         \includegraphics[width=4cm]{Chapters/chapter19/fig/ex_matrix.pdf} 
98         \caption{An example square matrix of size $6 \times 6$ (zero values are not shown)}
99         \label{fig:ex_matrix}
100 \end{figure}
101
102         \subsubsection*{Coordinate list (COO)\index{Compressed storage format!COO}}
103 For each non-zero, both its column and row indices are explicitly stored. The Cusp implementation \cite{ch19:cusp} stores elements in sorted order of row indices ensuring that entries with the same row index are stored contiguously. 
104
105         \begin{lstlisting}[caption={}]
106         coo.row_index = {0, 1, 1, 2, 2, 3, 3,  3, 4, 5,  5}
107         coo.col_index = {2, 1, 4, 1, 3, 0, 2,  4, 2, 0,  5}
108         coo.value     = {3, 1, 5, 2, 4, 6, 8, 10, 9, 7, 11}
109         \end{lstlisting}
110
111         \subsubsection*{Compressed Sparse Row (CSR)\index{Compressed storage format!CSR}} Non-zeros are sorted by the row index, and only their column indices are explicitly stored in a column array. Additionally, the vector $row\_start$ stores indices of the first non-zero element of each row in the column array.
112         
113         \begin{lstlisting}[caption={}]
114         csr.row_start = {0, 1, 3, 5, 8, 9, 12}
115         csr.col_index = {2, 1, 4, 1, 3, 0, 2,  4, 2, 0,  5}
116         csr.value     = {3, 1, 5, 2, 4, 6, 8, 10, 9, 7, 11}
117         \end{lstlisting}
118
119         \subsubsection*{Ellpack (ELL)\index{Compressed storage format!ELL}} Let $K$ be the maximum number of non-zero elements in any row of the matrix. Then, for each row, ELL stores exactly $K$ elements (extra padding is required for rows that contain less than $K$ non-zero elements). Only column indices are required to store in an array, the row index can be implied since exactly $K$ elements are stored per row. The Cusp implementation store the column indices in a transposed manner so that consecutive threads can access consecutive memory addresses.
120         
121         \begin{lstlisting}[caption={}]
122         ell.col_index = {
123                                         2, 1, 1, 0, 2, 0, 
124                                         *, 4, 3, 2, *, 5, 
125                                         *, *, *, 4, *, *}
126         ell.value     = {
127                                         3, 1, 2,  6, 9,  7, 
128                                         *, 5, 4,  8, *, 11, 
129                                         *, *, *, 10, *,  *}
130         \end{lstlisting}
131
132         \subsubsection*{Hybrid (HYB)\index{Compressed storage format!HYB}} The HYB format heuristically computes an value $K$ and store $K$ non-zeros per rows in the ELL format. When a row has more than $K$ non-zeros, the trailing non-zeros are stored in COO. This design decreases the storage overhead due to ELL padding elements and thus improve the overall performance.
133         \begin{lstlisting}[caption={}]
134         hyb.nnz_per_row   = 2
135         hyb.ell.col_index = {2, 1, 1, 0, 2, 0, *, 4, 3, 2, *,  5}
136         hyb.ell.value     = {3, 1, 2, 6, 9, 7, *, 5, 4, 8, *, 11}
137         hyb.coo.row_index = {3}
138         hyb.coo.col_index = {4}
139         hyb.coo.value     = {10}
140         \end{lstlisting}
141
142         \subsubsection*{Sliced Ellpack (SLE)\index{Compressed storage format!SLE}} This format partitions the matrix into horizontal slices of $S$ adjacent rows \cite{ch19:sle}. Each slice is stored in ELLPACK format. The maximum number of non-zeros may be different for each slice. An additional array $slice\_start$ is used to index the first element in each slice. The matrix rows are usually sorted by the number of non-zeros per row in order to move rows with similar number of non-zeros together. 
143         \begin{lstlisting}[caption={}]
144         sle.slice_size  = 2
145         sle.col_index   = {
146                                         2, 1, *, 4,
147                                         1, 0, 3, 2, *, 4,
148                                         2, 0, *, 5}
149         sle.value       = {
150                                         3, 1, *, 5,
151                                         2, 6, 4, 8, *, 10
152                                         9, 7, *, 11}
153         sle.slice_start = {0, 4, 10, 14}
154         \end{lstlisting}        
155         
156         \begin{table}
157         \caption{Properties of some NFS matrices}
158         \centering
159         \begin{tabular}
160         {| l || c | c | c | c | c | c |}        
161         \hline  
162         &   RSA-170 & RSA-190 & KILOBIT & RSA-768 \\
163         \hline            
164           Max dimension & 10.4M & 26.1M & 66.7M & 192M \\
165           Non-zeros & 994.7M & 2,451M & 9,538M & 27,797M\\
166           Max row weight & 5.5M & 14M & 28.2M & 82.6M\\
167           Min row weight & 3 & 3 & 2 & 2\\
168           Avg row weight & 95.08 & 93.6 & 143 & 144\\
169         \hline
170         \end{tabular}
171                 
172         \label{table:rsa_matrix}        
173         \end{table}
174         
175 The existing formats do not achieve good performance due to the special structure of NFS matrices. Row weights of an NFS matrix have a very wide range (see Table \ref{table:rsa_matrix}). Hence using one warp or one thread per row (as in CSR or ELLPACK) results in an unbalanced workload. Moreover, the NFS matrices are highly unstructured, which does not facilitate cache-friendly vector access patterns using existing formats. These reasons have motivated our design of a new format.
176         
177 \section{A hybrid format for SpMV on GPUs}
178 \label{Implementation}
179
180 As a preprocessing step, we reorder the rows of the matrix by their \emph{row weight}, in non-increasing order. The row weight of row $j$ of $B$ is defined as the total number of non-zero elements in row $j$. We then partition the sorted matrix rows into at most four consecutive parts. Each part uses a different format. The different formats are optimized for the sparseness properties of each partition as shown in Figure \ref{fig:partitioning}. For the densest part, we use a dense format. When the matrix gets less dense, we switch to another format which we call \index{Compressed storage format!Sliced COO} Sliced COO (SCOO). SCOO has three variants, small, medium, and large. Our formats are now described in more detail.
181
182 \begin{figure}[t]
183         \centering
184         \includegraphics[scale=1.3]{Chapters/chapter19/fig/partitioning.pdf} 
185         \caption{Partitioning of a row-sorted NFS matrix into four formats}
186         \label{fig:partitioning}
187 \end{figure}
188
189     \subsection{Dense Format}
190     The dense format is used for the dense part of the matrix. This format uses 1 bit per matrix entry. Within a column, 32 matrix entries are stored as a 32 bit integer. Thus, 32 rows are stored as $N$ consecutive integers.
191        
192     Each CUDA thread works on a column. Each thread fetches one element from the input vector in coalesced fashion. Then, each thread checks the 32 matrix entries one by one. When the matrix entry is a non-zero, the thread performs a XOR operation between the element from the input vector and the partial result for the row. This means each thread only accesses the input vector once to do work on up to 32 non-zeros. The partial result from each thread needs to be stored and combined to get the final result for the 32 rows. These operations are performed in CUDA shared memory.
193
194     The 32 threads in a warp share 32 shared memory entries to store the partial results from the 32 rows. Since all threads in a warp execute a common instruction at a time, access to these 32 entries can be made exclusive. The result from each warp in a thread block is combined using p-reduction on shared memory. The result from each thread block is combined using an atomic XOR operation on global memory.
195
196     When the blocking factor is larger than 64, access to the shared memory needs to be reorganized to avoid bank conflicts. Each thread can read/write up to 64 bit data at a time to the shared memory. If a thread is accessing 128 bit data for example, two read/write operations need to be performed. Thus, there will be bank conflicts if we store 128 bit data on contiguous addresses. We can avoid bank conflicts by having the threads in a warp first access consecutive 64 bit elements representing the first halves of the 128 bit elements. Then, the threads again access consecutive 64 bit elements representing the second halves of the 128 bit elements. The same modification can be applied to other formats as well.
197
198         \subsection{Sliced COO}
199         The SCOO format is adapted from the CADO-NFS software for CPUs \cite{ch19:cadonfs}. The aim is to reduce irregular accesses to the input vector and increase the texture cache hit rate. SCOO stores the column index and the row index of each non-zero. A number of consecutive rows form a slice. Non-zeros within a slice are sorted by their column index. We give an example of SCOO in Figure \ref{fig:scoo-1}, threads working on a slice can access contiguous elements from the input vector. In the figure, S1, S2 are two slices. Entries in the input vector denote the corresponding non-zero elements that the input vector element is multiplied with.
200         
201         For each non-zero, two bytes are used to store the column index. However, two bytes are not enough for large RSA matrices. Thus, we further divide a slice into groups. Group $i$ contains non-zeros with column index between $i \times 2^{16}$ and $(i+1) \times 2^{16} - 1$. An additional array stores the starting position of each group in the slice. 
202         
203         \begin{figure}[t]
204         \centering
205         \includegraphics[height=5cm]{Chapters/chapter19/fig/scoo.pdf} 
206         \caption{Example of the memory access pattern for a $6 \times 6$ matrix stored in Sliced COO format (Slice Size = 3 rows).}
207         \label{fig:scoo-1}
208         \end{figure}
209         
210
211 \lstinputlisting[label=ch19:lst:spmv,caption=SpMV with SCOO format]{Chapters/chapter19/code.cu}
212
213         One thread block works on a slice, one group at a time. Neighboring threads work on neighboring non-zeros in the group in parallel. Each thread works on more than one row of the matrix. Thus, each thread needs some storage to store the partial result and combine them with the result from the other threads to generate the final output. Each entry costs equally as the element of the vector i.e. the blocking factor in byte. Shared memory is used as intermediate storage as they have low latency however it is limited to 48 KB per SM in Fermi. The global memory is only accessed once at the end to write the final output. The CUDA kernel for SpMV with Sliced COO format is shown in Listing \ref{ch19:lst:spmv}. 
214         
215         Since neighboring non-zeros may or may not come from the same row (recall that we sorted them by column index), many threads may access the same entry in the shared memory if it is used to store result of the same rows. Thus, shared memory entries are either assigned exclusively to a single thread or shared by using atomic XOR operations.    Based on the way we allocate the shared memory, we further divide the Sliced COO format into three different subformats: small, medium, and large. 
216         
217         \begin{table}
218         \caption{Sliced COO subformat comparison (\# rows per slices is based on $n=64$) }
219         \centering
220         \begin{tabular}
221         { | c || c | c | c |}   
222         \hline  
223         Sliced COO subformats & Small & Medium & Large \\
224         \hline            
225           Memory Sharing & No sharing & Among Warp & Among Block\\
226           Access method & Direct & Atomic XOR & Atomic XOR\\
227           Bank conflict & No & No & Yes\\
228           \# Rows per Slice & 12 & 192 & 6144 \\
229         \hline
230         \end{tabular}
231         \label{table:scoo}
232         \end{table}
233
234         \subsubsection*{Small Sliced (SS) COO}
235         In this subformat, each thread has one exclusive entry in shared memory to store the partial result for each row. The assignment of the shared memory is organized such that each thread in a warp accesses only one bank and there is no bank accessed by more than one thread. Thus, there is no bank conflict. A p-reduction operation on shared memory is required to combine partial results from each thread.
236
237         The maximum number of rows per slice is calculated as \emph{size of shared memory per SM in bits / (number of threads per block * blocking factor)}. We use 512 threads per block for 64 bit blocking factor which gives 12 rows, and 256 threads per block for 128 and 256 bit blocking factor, which gives 12 and 6 rows, respectively. Hence, one byte per row index is sufficient for this subformat.
238         
239         \subsubsection*{Medium Sliced (MS) COO}
240         In this subformat, each thread in a warp gets an entry in the shared memory to store the partial result for each row. However, this entry is shared with the threads in other warps. Access to the shared memory uses an atomic XOR operation. Each thread in a warp accesses only one bank, avoiding bank conflicts. A p-reduction operation on shared memory is required to combine the 32 partial results.
241         
242         The maximum number of rows per slice is calculated as \emph{size of shared memory per SM in bits / (32 * blocking factor)} where 32 is the number of threads in a warp. This translates to 192, 96, and 48 rows per slice for blocking factor of 64, 128, and 256 bit, respectively. Hence, one byte per row index is sufficient for this format.       
243         
244         \subsubsection*{Large Sliced (LS) COO}
245         In this subformat, the result for each row gets one entry in shared memory, which is shared among all threads in the thread block. Access to shared memory uses an atomic XOR operation. Thus, there will be bank conflicts. However, this drawback can be compensated by a higher texture cache hit rate. There is no p-reduction on shared memory required.
246
247         The maximum number of rows per slice is calculated as \emph{size of shared memory per SM in bits / blocking factor}. This translates to 6144, 3072, and 1536 rows per slice for blocking factor of 64, 128, and 256 bit, respectively. We use two bytes for the row index.
248         \linebreak
249         \linebreak
250         \noindent Table \ref{table:scoo} summarizes the differences between the three subformats.
251         \begin{itemize}
252         \item[a)] Each thread in a block is allocated one memory entry for the result of a row and works on consecutive 12 rows. A p-reduction to Thread\#1 is needed to get the final result of one row.
253         \item[b)] Each warp in a block is allocated one memory entry per row and works on 192 consecutive rows, threads in each warp use atomic XOR to update the value in a given entry. A p-reduction for each row to memory in Warp\#1 is needed to obtain the final result of that row.
254         \item[c)] A thread block works on 6144 consecutive rows and share the memory entry of each row. Any update to the memory has to use atomic XOR.
255         \end{itemize}
256
257         \subsection{Determining the cut-off point of each format}
258         \label{ch19:mat-partition-1}
259         To determine which format to use, we compare the performance of two consecutive formats in terms of giga non-zeros (\emph{gnnz}) per second for a given matrix, starting with the dense format and the SS-COO format. The two formats start from the same row (starting from the first row) and work on the minimum number of rows possible. For the dense format, the minimum number of rows is 32. For the SS-COO format (and its variants), the minimum number of rows is \emph{the number of rows in a slice} times \emph{the number of multiprocessors in the GPU}, since one thread block works on one slice and one thread block is assigned to one multiprocessor. 
260         
261         The next comparison depends on the result of the current comparison. If the dense format performs better, we decide to use it for rows 1 to 32, and we continue comparing the dense format and the SS-COO format starting from row 33. However, if the SS-COO format performs better, we compare its performance with the next format, MS-COO, starting from the same row, and so on. The idea is to stop considering the denser format once the sparser format outperforms it. Once we get to the comparison between MS-COO and LS-COO, and LS-COO performs better, we don't need to do any further comparisons. LS-COO should be used for the rest of the matrix.
262         
263         For Sliced COO format, it is essential to note that when one slice is assigned to each multiprocessor, the load for one multiprocessor may be much higher than that of the other multiprocessors. This is because the matrix rows have been reordered by their weight in a non-increasing order, so the first slice contains more non-zero entries than the rest. Thus, we need to further reorder the rows such that each multiprocessor gets the same level of load.
264         
265         The cut-off point determination is performed once per matrix and as a preprocessing step. Its runtime is equivalent to less than five iterations of SpMV.
266         
267         
268 \section{SCOO for Single-Precision Floating-Point Matrices}
269 The design of SCOO depends on performing atomic operations to combine the partia results. Atomic operations for double-precision are not available on current CUDA-enabled devices. Thus, we only present the SCOO extension for single-precision floating-point matrices.
270
271 To extend SCOO for genereal matrices in real numbers, we first adapt to the variety of sparsity in real number matrices. Unlike NFS matrices, the sparsity is not predictable. Hence instead of using three subformats (SS-COO, MS-COO, LS-COO) with a fixed slice size (12, 192, 6144), we make the slice size a variable. Let $h$ denote the slice size, $S$ denote the size in byte of shared memory per thread block ($S=49152$ for Fermi) and $b$ denote the size of each matrix value in byte ($b=4$ for single-precision floating point). $h$ must be a positive integer value of the form of $h = \frac{S}{2^{i} b}$, $\forall{0 \leq i \leq T}$ where $T=\log_2{\textit{(maximum number of thread per block)}}$. 
272
273 For example in Fermi GPUs, single-precision floating point, $h \in \{12288, 6144, 3072, 1536, 768, 384, 192, 96, 48, 24, 12\}$ for $S=49152, b=4, T=10$. The algorithm in Section \ref{ch19:mat-partition-1} can be used to determine the cut-off point for each sub-formats. Figure \ref{fig:final-scoo} illustrates the final matrix in SCOO format assuming the number of multiprocessors in the GPU is $5$ and the row index is ranging from $0..d-1$. $H_i$ and $r_i$ is the slice size and starting row index of the sub-matrix $i$.
274
275 \begin{figure}[t]
276         \centering
277         \includegraphics[width=8cm]{Chapters/chapter19/fig/final-scoo-book.pdf} 
278         \caption{Partitioning of a row-sorted floating-point matrix into SCOO format.}
279         \label{fig:final-scoo}
280 \end{figure}
281
282 More details of the SCOO format for floating-point matrices can be found in \cite{ch19:spmv-iccs}
283
284 \section{Performance Evaluation}
285 \label{ch19:result}
286
287 \subsection{Experimental Setup}
288 \begin{table}
289 \caption{Overview of hardware used in the experiments}
290 \begin{center}
291 \begin{tabular}{|l|l|l|l|}
292 \hline
293 Hardware & C2075 & GTX-580 & Core-i7 2700K\\
294 \hline
295 \# Cores &448& 512 & 4\\
296 \hline
297 Clock speed (Ghz)&1.15& 1.57 &3.5\\
298 \hline
299 Memory type & GDDR5 & GDDR5 & DDR3-1600 \\
300 \hline
301 Memory size (GB)&6 & 3 &16\\
302 \hline
303 Max Memory bandwidth (GB/s)& 144 & 192 & 21 \\
304 \hline
305 \end{tabular}
306 \end{center}
307 \label{table:hardware}
308 \end{table} 
309
310 Table \ref{table:hardware} specifies the GPUs and the CPU workstation used for performance evaluation. The performance is measured in terms of $ggnz$ and $Gflop/s$ for binary and floating-point matrices. Measured GPU performance does neither include PCIe data transfers nor matrix preprocessing. These are realistic assumptions since SpMV applications usually consist of a large number of iterations where the sparse matrix is iteratively multiplied by the input/output vectors.
311
312 Our source code is compiled and executed using CUDA toolkit and driver version $4.2$ under Linux Ubuntu 12.04.
313
314 \subsection{Experimental results of SpMV on NFS matrix}
315 We have evaluated our implementation on an NVIDIA Tesla C2075 (ECC disabled) with 6 GB RAM. We have compared the GPU performance with the open-source CADO-NFS \cite{ch19:cadonfs} program running on Intel Core i7-920 CPU with 12 GB DDR3-1066 memory. The RSA-170 matrix (see Table \ref{table:rsa_matrix}) is used for performance evaluation. 
316
317 The speedups compared to the multi-threaded CADO-NFS bucket implementation on an Intel Core i7-920 are given in brackets. The GPU memory required to store the sparse matrix and the corresponding bytes per non-zero (nnz) are also reported. CADO-NFS contains several CPU optimized SpMV implementations using multi-threading and SSE instructions. In this experiment, we compare the performance of our implementation to the CPU cache-optimized bucket format of CADO-NFS using 8 threads.
318
319 \begin{table}[t]
320         \caption{Performance of SpMV on RSA-170 matrix}
321         \label{table:rsa170}
322         \centering
323         \begin{tabular}
324         { | c || c | c | c | c |}       
325         \hline  
326         Blocking & C2075     & 2 x C2075 & Core i7-920 & GPU memory \\ 
327         factor   & (speedup) & (speedup) &  CADO-NFS  & (bytes/nnz) \\
328         \hline            
329           64 & 4.28 (4.9) & 8.38 (9.5) & 0.88 & 2748 MB (2.90)\\
330           128 & 2.78 (5.6) & 5.37 (10.7) & 0.5 & 2967 MB (3.13)\\
331           256 & 1.88 (7.0) & 3.68 (13.6) & 0.27 & 3000 MB (3.16)\\
332
333         \hline
334         \end{tabular}
335 \end{table}
336                         
337         Table \ref{table:rsa170} shows the result. The dual-GPU implementation achieves speedups between 1.93 and 1.96 compared to the single-GPU performance.
338         
339         Table \ref{table:individual} show the individual performance of each sub-format (in \emph{gnnz/s}), the percentage of non-zeros are included in bracket. The result show that the performance decreases when the matrix gets sparser. The MS-COO and LS-COO performance degrade when the blocking factor is increased from 64 to 128 and 256 bit. This is caused by the increased number of bank conflicts and serialization of atomic XOR operations on larger blocking factors. Thus, the SS-COO format gets a higher percentage of non-zeros with 128 and 256 bit blocking factor. 
340         
341         More detail results of our full Block Wiedemann CUDA implementation as well as a multi-GPU implementation can be found in \cite{ch19:spmv-ccpe}
342
343 \begin{table}[h]
344         \caption{Performance for each of the four sub-format partitions of the RSA-170 matrix on a C2075.}
345         \label{table:individual}
346         \centering
347         \begin{tabular}
348         { | c || c | c | c | c | c |}   
349         \hline  
350          Blocking factor & Dense & SS-COO & MS-COO & LS-COO \\
351         \hline            
352           64     & 13.66 (24\%) & 9.66 (11\%) & 8.13 (13\%) & 2.77 (52\%)  \\
353           128    & 9.66 (15\%) & 7.53 (24\%) & 3.23 (6\%) & 1.86 (55\%) \\
354           256    & 7.00 (15\%) & 4.21 (21\%) & 2.34 (6\%) & 1.33 (58 \%) \\               
355         \hline
356         \end{tabular}
357 \end{table}
358
359 \subsection{Experimental results of SpMV on floating-point matrices}
360 \label{ch19:result-2}
361 \npthousandsep{,}
362
363 \begin{table}
364 \caption{Overview of sparse matrices used for performance evaluation}
365 \begin{center}
366 \begin{tabular}{|l|n{8}{0}|n{9}{0}|n{3}{2}|l|}
367 \hline
368 {Name} & row & column & nz/row & Description \\
369 \hline
370 GL7d19 &1911130&1955309&19.53& combinatorial problem \\
371 \hline
372 relat9 &12360060&549336&3.15& combinatorial problem \\
373 \hline
374 wikipedia-20070206 &3566907&3566907&12.62& directed graph \\
375 \hline
376 wb-edu &9845725&9845725&5.81& directed graph\\
377 \hline
378 road\_usa &23947347&23947347&2.41& undirected graph \\
379 \hline
380 hugebubbles-00010 &19458087&19458087&3.00& undirected graph \\
381 \hline
382 circuit5M &5558326&5558326&10.71&circuit simulation\\
383 \hline
384 nlpkkt120 &3542400&3542400&26.85&optimization problem\\
385 \hline
386 cage15 &5154859&5154859&19.24&directed weighted\\
387 \hline
388 kron\_g500-logn21 &2097152&2097152&86.82&undirected multigraph\\
389 \hline
390 indochina-2004&7414866&7414866&26.18& directed graph \\
391 \hline
392 nlpkkt160 &8345600&8345600&27.01&optimization problem\\
393 \hline
394 rgg\_n\_2\_24\_s0 &16777216&16777216&15.80&undirected random\\
395 \hline
396 uk-2002 &18520486&18520486&16.10&directed graph\\
397 \hline
398 \end{tabular}
399 \end{center}
400 \label{table:matrices}
401 \end{table}
402
403 \begin{figure}
404 \label{fig:mat-visual}
405 \centering
406         \subfigure[nlpkkt120] {
407                 \includegraphics[width=100pt]{Chapters/chapter19/fig/matrix-str.pdf}
408                 \label{fig:mat-str}
409         }
410         \subfigure[Relat9 - first 10000 rows] {
411                 \includegraphics[width=100pt]{Chapters/chapter19/fig/matrix-mid.pdf}
412                 \label{fig:mat-mid}
413         }
414         \subfigure[GL7d19 - first 500 rows and columns] {
415                 \includegraphics[width=100pt]{Chapters/chapter19/fig/matrix-uns.pdf}
416                 \label{fig:mat-unstr}
417         }
418   
419   \caption{Visualization of \emph{nlpkkt120}, \emph{Relat9}, and \emph{GL7d19} matrix.}
420 \end{figure}
421
422 \begin{figure}
423 \centering
424 \includegraphics[height=5cm]{Chapters/chapter19/fig/scoo-a.pdf}
425 \caption{Performance comparison of SCOO and other GPU formats for each test matrix on a Fermi Tesla C2075 (ECC disabled).}
426 \label{fig:scoo-vs-gpu}
427 \end{figure}
428
429 \subsubsection{Performance comparison to existing GPU formats}
430 We compare the performance of our SCOO format to available SpMV implementations on both GPU and CPU. The set of selected test matrices are collected from the University of Florida Sparse Matrix Collection \cite{ch19:matrix-collection}. We have chosen the biggest matrices from different areas that with their corresponding input and output vector can still fit into the 6GB global memory of a C2075 GPU. Table \ref{table:matrices} gives an overview of those matrices. 
431
432 We compare the SCOO format to the CSR, COO and HYB format of Cusp 0.3.0. Other Cusp formats are not able to run on the large tested matrices that we selected. The results are shown in Figure \ref{fig:scoo-vs-gpu}. The performance are in terms of Gflop/s which is based on the assumption of two flops per non-zero entry of the matrix \cite{ch19:nvidia-spmv, ch19:bellpack}.
433
434 The SCOO format achieves a stable performance for different matrices in single-precision mode. In most cases a performance of over 10 Gflop/s can be sustained. For some highly unstructured matrices such as \emph{GL7d19}, \emph{wikipedia-20070206}, \emph{rgg\_n\_2\_24\_s0} and \emph{kron\_g500-logn21} SCOO achieves high speedups ranging from 3 to 6 compared to the best performaning Cusp format. 
435
436 For most matrices, HYB produces the best performance among the tested Cusp formats. HYB is able to outperform SCOO only for two matrices: \emph{nlpkkt120} and \emph{nlpkkt160}. Both matrices have a similar structure i.e. they consist of consecutive rows that have a very similar number of non-zero coefficients which is suitable to be stored in the ELL section of the HYB format. Moreover the non-zeros are close to each other facilitating coaleasing and cache-friendly access patterns by nature. SCOO is able to outperform COO and CSR for all tested matrices.
437
438 In matrix $Relat9$ we observe some patterns but the matrix is still generally unstructured, thus SCOO is able to achieve about 2 times speed up compared to HYB which is the best among tested Cusp formats in this case. The average speedup of SCOO for the tested matrices is 3.0 compared to CSR, 5.02 compared to COO, 2.15 compared to HYB.
439
440 We show the visualization of sparse matrices \emph{nlpkkt120}, \emph{Relat9}, \emph{GL7d19} in Figure \ref{fig:mat-str}, \ref{fig:mat-mid}, {fig:mat-unstr} using MatView \cite{ch19:matview}. The white color represents zero entries, gray color represents non-zero entries.
441
442 \subsubsection{Performance comparison to a CPU implementation}
443 \begin{figure}
444 \centering
445 \includegraphics[width=200pt]{Chapters/chapter19/fig/gpu-cpu.pdf}
446
447 \caption{Performance of the SCOO on a GTX-580 and a CPU implementation using MKL performed on a Core-i7 2700K using 8 threads.}
448 \label{fig:scoo-vs-cpu}
449 \end{figure}
450
451 We use the Intel MKL library 10.3 in order to compare SCOO performance to an optimized CPU implementation. MKL SpMV receives the input matrices in CSR format. The results are shown in Figure \ref{fig:scoo-vs-cpu}. Using a GTX-580, we achieve speedups ranging between 5.5 and 18 over MKL on a 4-core CPU with hyper-threading using 8 threads. Also note that the SCOO performance on a GTX-580 is around 1.5 times faster than on the C2075 due to the increased memory bandwidth and clock speed. The storage requirement for the \emph{rgg\_n\_2\_24\_s0} and \emph{uk-2002} matrices and associated input/output vectors slightly exceeds the 3 GB global memory of the GTX-580 and thus are not included.
452
453 \section{Conclusion}
454 \label{ch19:conclusion}
455 In this chapter, we have presented our implementation of iterative SpMV for NFS matrices on GPUs with the CUDA programming language. Our GPU implementation takes advantage of the variety of sparseness properties in NFS matrices to produce suitable formats for different parts. The GPU implementation shows promising improvement over an optimized CPU implementation. As the size of integers in factorization projects is expected to increase further, the linear algebrea step of NFS will become an even bigger bottleneck. The size and sparseness of matrices generated by the NFS sieving step are growing significantly with the size of the integer to be factored. Thus, a big GPU cluster is required to accelerate the linear algebra step. However, in order to achieve scalability for bigger problem sizes, the amount of GPU RAM and data transfer bandwidth need to be increased in addition to the number of GPUs. 
456
457 We further adapted the proposed Sliced COO format to single-precision floating-point numbers and evaluated it with large and sparse matrices derived from other computational science applications. We have published our code at https://github.com/danghvu/cudaSpmv.
458
459 \putbib[Chapters/chapter19/biblio]
460