Table of Contents
Fetching ...

Fully-Automated Code Generation for Efficient Computation of Sparse Matrix Permanents on GPUs

Deniz Elbek, Kamer Kaya

TL;DR

This work addresses the challenge of computing the permanent of sparse matrices on GPUs by introducing fully-automated code generation that tailors matrix-specific kernels to sparsity patterns. It presents two complementary techniques: a register-centric approach that stores per-row sums $x$ in registers via inclusion/exclusion kernels, and a hybrid memory approach that combines registers with global memory guided by matrix ordering and partitioning. The use of Gray-code structure and a recursive signed changed-bit sequence minimizes control divergence, while permanent ordering and partitioning optimize register usage. Experimental results show substantial speedups over CPU and prior GPU baselines on both synthetic and real-life sparse matrices, with CodeGen-Hybrid delivering up to ~$31\times$ (CPU) and ~ $8\times$ (GPU) improvements on synthetic data, and ~$24.9\times$ and ~$4.9\times$ on real-life data. The work demonstrates the practicality of matrix-specific GPU code generation for sparse permanents and outlines directions for multi-GPU scalability and smarter memory spill strategies.

Abstract

Registers are the fastest memory components within the GPU's complex memory hierarchy, accessed by names rather than addresses. They are managed entirely by the compiler through a process called register allocation, during which the compiler attempts to cache predictable data from thread-local memory into thread-private registers. Computing the permanent of a sparse matrix poses a challenge for compilers, as optimizing this process is hindered by the unpredictable distribution of nonzero elements, which only become known at runtime. In this work, we employ fully-automated code generation to address this, producing highly optimized kernels tailored to the matrix's sparsity pattern. State-of-the-art permanent computation algorithms require each thread to store a private array, denoted x, of size n. We first propose a technique that fully stores these arrays in registers, with inclusion and exclusion kernels generated for each column. To minimize control divergence and reduce the number of unique kernels within a warp, we exploit the internal structure of Gray codes, which are also used in the state-of-the-art algorithm. Our second technique reduces register pressure by utilizing both registers and global memory and introduces a matrix ordering and partitioning strategy for greater efficiency. On synthetic matrices, this approach achieves a 31x speedup over state-of-the-art CPU implementations on 112 cores, and an 8x speedup compared to our traditional GPU implementation. For real-world matrices, these speedups are 24.9x and 4.9x.

Fully-Automated Code Generation for Efficient Computation of Sparse Matrix Permanents on GPUs

TL;DR

This work addresses the challenge of computing the permanent of sparse matrices on GPUs by introducing fully-automated code generation that tailors matrix-specific kernels to sparsity patterns. It presents two complementary techniques: a register-centric approach that stores per-row sums in registers via inclusion/exclusion kernels, and a hybrid memory approach that combines registers with global memory guided by matrix ordering and partitioning. The use of Gray-code structure and a recursive signed changed-bit sequence minimizes control divergence, while permanent ordering and partitioning optimize register usage. Experimental results show substantial speedups over CPU and prior GPU baselines on both synthetic and real-life sparse matrices, with CodeGen-Hybrid delivering up to ~ (CPU) and ~ (GPU) improvements on synthetic data, and ~ and ~ on real-life data. The work demonstrates the practicality of matrix-specific GPU code generation for sparse permanents and outlines directions for multi-GPU scalability and smarter memory spill strategies.

Abstract

Registers are the fastest memory components within the GPU's complex memory hierarchy, accessed by names rather than addresses. They are managed entirely by the compiler through a process called register allocation, during which the compiler attempts to cache predictable data from thread-local memory into thread-private registers. Computing the permanent of a sparse matrix poses a challenge for compilers, as optimizing this process is hindered by the unpredictable distribution of nonzero elements, which only become known at runtime. In this work, we employ fully-automated code generation to address this, producing highly optimized kernels tailored to the matrix's sparsity pattern. State-of-the-art permanent computation algorithms require each thread to store a private array, denoted x, of size n. We first propose a technique that fully stores these arrays in registers, with inclusion and exclusion kernels generated for each column. To minimize control divergence and reduce the number of unique kernels within a warp, we exploit the internal structure of Gray codes, which are also used in the state-of-the-art algorithm. Our second technique reduces register pressure by utilizing both registers and global memory and introduces a matrix ordering and partitioning strategy for greater efficiency. On synthetic matrices, this approach achieves a 31x speedup over state-of-the-art CPU implementations on 112 cores, and an 8x speedup compared to our traditional GPU implementation. For real-world matrices, these speedups are 24.9x and 4.9x.
Paper Structure (17 sections, 3 theorems, 9 equations, 5 figures, 4 tables, 4 algorithms)

This paper contains 17 sections, 3 theorems, 9 equations, 5 figures, 4 tables, 4 algorithms.

Key Result

Theorem 1

For $1 \leq i \leq 2^{n-1} - 1$, the $i$th entry of $\textsc{Scbs}(n-1)$, has the changed bit ID $j$ if $2^j$ is the largest power of 2 dividing $i$. Furthermore, the sign of the same entry is + (indicating 0$\rightarrow$1) when is even, and - (indicating 1$\rightarrow$0) otherwise.

Figures (5)

  • Figure 1: A 6x6 matrix (left), with a permanent value 54531.03, and the Gray-code change bit sequence (right) in the iterations of Algorithm \ref{['alg:sparyser']} where each prefix '$+$', indicating column inclusion, or '$-$', indicating exclusion, correspond to the $s$ value, 1 or -1, at line 10 of Alg. \ref{['alg:sparyser']}. Each number on the right is the changing bit index corresponding to the $j$ value at line 9 of Alg. \ref{['alg:sparyser']}.
  • Figure 2: Simplified PTX codes generated for Listing \ref{['lst:locReduce']}. In the last version, the indices parameter is assumed to be not presented and the reduction is performed sequentially over localArr.
  • Figure 3: 3-bit Gray code construction from 2-bit sequences using the reverse, concatenate, and prefix method.
  • Figure 4: The desired sparsity pattern from permanent ordering (left). The matrix from Fig. \ref{['fig:gray_code_change']} ordered via permanent ordering (right).
  • Figure 5: The number of registers used per thread ($y$-axes) to store ${\bf x}\xspace$ values, i.e., $2 \times k$, for hybrid-memory kernel generation when permanent ordering is not applied (red bars) and when it is applied (blue bars) before partitioning the matrix with Algorithm \ref{['alg:partitioning']}. The values over the bars show the number of columns performing only register updates, i.e., $c$, also set by the partitioning algorithm.

Theorems & Definitions (6)

  • Theorem 1
  • proof
  • Lemma 1
  • proof
  • Lemma 2
  • proof