Paper Introduces the Frst Compiler Technique to Automatically Generate Kernels for Any Compound Tensor Algebra Operation on Dense and Sparse Tensors
Total Page:16
File Type:pdf, Size:1020Kb
The Tensor Algebra Compiler FREDRIK KJOLSTAD, Massachusetts Institute of Technology, USA SHOAIB KAMIL, Adobe Research, USA STEPHEN CHOU, Massachusetts Institute of Technology, USA DAVID LUGATO, French Alternative Energies and Atomic Energy Commission, France SAMAN AMARASINGHE, Massachusetts Institute of Technology, USA Tensor algebra is a powerful tool with applications in machine learning, data analytics, engineering and the physical sciences. Tensors are often sparse and compound operations must frequently be computed in a single kernel for performance and to save memory. Programmers are left to write kernels for every operation of interest, with diferent mixes of dense and sparse tensors in diferent formats. The combinations are infnite, which makes it impossible to manually implement and optimize them all. This paper introduces the frst compiler technique to automatically generate kernels for any compound tensor algebra operation on dense and sparse tensors. The technique is implemented in a C++ library called taco. Its performance is competitive with best-in-class hand-optimized kernels in popular libraries, while supporting far more tensor operations. CCS Concepts: • Software and its engineering → Source code generation; Domain specifc languages; Software libraries and repositories;•Mathematics of computing → Mathematical software performance; Additional Key Words and Phrases: tensors, tensor algebra, linear algebra, sparse data structures, performance, parallelism, code generation, merge lattices, iteration graphs ACM Reference Format: Fredrik Kjolstad, Shoaib Kamil, Stephen Chou, David Lugato, and Saman Amarasinghe. 2017. The Tensor Algebra Compiler. Proc. ACM Program. Lang. 1, OOPSLA, Article 77 (October 2017), 29 pages. https://doi.org/10.1145/3133901 1 INTRODUCTION 77 Tensor algebra is a powerful tool for computing on multidimensional data and has many applications in machine learning, data analytics, engineering, and science [Abadi et al. 2016; Anandkumar et al. 2014; Bader et al. 2008; Einstein 1916; Feynman et al. 1963; Kolecki 2002]. Tensors generalize vectors and matrices to any number of dimensions and permit multilinear computations. Tensors of interest are often sparse, which means they mostly contain zeros that can be compressed away. For example, large real-world datasets used in data analytics, such as Netfix Ratings [Bennett et al. 2007], Facebook Activities [Viswanath et al. 2009], and Amazon Reviews [McAuley and Leskovec 2013], are conveniently cast as sparse tensors. The Amazon Reviews tensor, in particular, contains 1.5 × 1019 components corresponding to 107 exabytes of data (assuming 8 bytes are used per component), but only 1.7 × 109 of the components (13 gigabytes) are non-zero. Authors’ addresses: F. Kjolstad, 32 Vassar St, 32-G778, Cambridge, MA 02139; email: [email protected]; S. Kamil, 104 Fifth Ave, 4th Floor, New York, NY 10011; email: [email protected]; S. Chou, 32 Vassar St, 32-G778, Cambridge, MA 02139; email: [email protected]; D. Lugato, CEA-CESTA, 33114 Le Barp, France; email: [email protected]; S. Amarasinghe, 32 Vassar St, 32-G744, Cambridge, MA 02139; email: [email protected]. Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for proft or commercial advantage and that copies bear this notice and the full citation on the frst page. Copyrights for third-party components of this work must be honored. For all other uses, Thiscontact work the is owner/author(s). licensed under a Creative Commons Attribution 4.0 International License. © 2017 Copyright held by the owner/author(s). 2475-1421/2017/10-ART77 https://doi.org/10.1145/3133901 Proc. ACM Program. Lang., Vol. 1, No. OOPSLA, Article 77. Publication date: October 2017. 77:2 Fredrik Kjolstad, Shoaib Kamil, Stephen Chou, David Lugato, and Saman Amarasinghe Optimized kernels that compute compound tensor operations can be very complex. First, sparse input tensors are compressed into indexed data structures that the kernels must operate on. Secondly, only non-zero output values should be produced, and the calculations difer between computed components depending on the input tensors that contribute non-zero values. Finally, sparse tensor data structures typically do not support constant-time random access, so kernels must carefully orchestrate co-iteration over multiple tensors. The current approach is to manually write high-performance code for tensor operations. Libraries provide a limited set of hand-optimized operations and programmers compute compound operations through a sequence of supported operations using temporary tensors. This reduces locality and efciency, and for some compound operations the temporaries are much larger than the kernel’s inputs and output. On the other hand, it is infeasible to write optimized code for every compound operation by hand because of the combinatorial explosion of all possible compound operations, tensor orders, and tensor formats. A compiler approach is therefore needed to generate fast kernels from a high-level notation such as the ubiquitous tensor index notation for tensor expressions [Ricci- Curbastro and Levi-Civita 1901]. Prior to this work, there existed no general mechanism that can generate code for compound tensor operations with sparse tensors. To the best of our knowledge, we present the frst compiler technique that can generate kernels for all sparse and dense tensor index notation expressions. This includes dense and sparse linear algebra expressions. Our technique generates code entirely from tensor index notation and simple format descriptors that fully specify the compressed data structures. Thus, we do not perform pointer alias or dependency analysis at compile time, nor at runtime as in inspector-executor systems. The technique can be used in libraries such as TensorFlow [Abadi et al. 2016] and Eigen [Guennebaud et al. 2010], or with languages such as MATLAB [MATLAB 2014], Julia [Bezanson et al. 2012] and Simit [Kjolstad et al. 2016]. The contributions of this paper are: tensor storage formats that separately designate each dimension as dense or sparse and specify the order in which dimensions are stored, which can describe several widely used formats but generalizes to many more (Section 3); iteration graphs that describe how to co-iterate through the multi-level index data structures of the sparse operands in a compound tensor expression (Section 4); merge lattices that describe how to merge the index data structures of sparse tensors that are used in the same tensor expression (Section 5); and a code generation algorithm that uses the above concepts to generate efcient code that computes a tensor expression with dense, sparse, and mixed operands (Section 6). To demonstrate our approach we have developed a C++ library called taco, short for Tensor Algebra COmpiler (Section 7).1 We compare taco-generated code to hand-written implementations from state-of-the-art widely used libraries. The comparison shows that taco generates efcient code for simpler kernels such as sparse matrix-vector multiplication as well as complex kernels like the matricized tensor times Khatri-Rao product [Bader and Kolda 2007] (Section 8). 2 EXAMPLE Tensors generalize matrices to any number of dimensions (also often called modes, though we will use ‘dimensions’ in the rest of this paper). The number of dimensions that a tensor has is its order. Thus, scalars are 0th-order tensors, vectors are 1st-order tensors, and matrices are 2nd-order tensors. Consider a simple 3rd-order tensor kernel, the tensor-times-vector multiplication (TTV): = Aij ! Bijkck k 1The taco library and tools are available under the MIT license at http://tensor-compiler.org. Proc. ACM Program. Lang., Vol. 1, No. OOPSLA, Article 77. Publication date: October 2017. The Tensor Algebra Compiler 77:3 1 for (int i = 0; i < m; i++) { for (int pB1 = B1_pos[0]; for (int pB1 = B1_pos[0]; 2 pB1 < B1_pos[1]; pB1 < B1_pos[1]; 3 pB1++) { pB1++) { 4 int i = B1_idx[pB1]; int i = B1_idx[pB1]; 5 for (int j = 0; j < n; j++) { for (int pB2 = B2_pos[pB1]; for (int pB2 = B2_pos[pB1]; 6 int pB2 = i * n + j; pB2 < B2_pos[pB1+1]; pB2 < B2_pos[pB1+1]; 7 int pA2 = i * n + j; pB2++) { pB2++) { 8 int j = B2_idx[pB2]; int j = B2_idx[pB2]; 9 int pA2 = i * n + j; int pA2 = i * n + j; 10 for (int k = 0; k < p; k++) { for (int pB3 = B3.pos[pB2]; int pB3 = B3_pos[pB2]; 11 int pB3 = pB2 * p + k; pB3 < B3.pos[pB2+1]; int pc1 = c1_pos[0]; 12 pB3++) { while (pB3 < B3_pos[pB2+1] && 13 int k = B3_idx[pB3]; pc1 < c1_pos[1]) { 14 int kB = B3_idx[pB3]; 15 int kc = c1_idx[pc1]; 16 int k = min(kB, kc); 17 if (kB == k && kc == k) { 18 A[pA2] += B[pB3] * c[k]; A[pA2] += B[pB3] * c[k]; A[pA2] += B[pB3] * c[pc1]; 19 } 20 if (kB == k) pB3++; 21 if (kc == k) pc1++; 22 } } } 23 } } } 24 } } } Fig. 1. A = B c Fig. 2. A = B c (sparse B) Fig. 3. A = B c (sparse B, c) ij "k ijk k ij "k ijk k ij "k ijk k This notation, which we use both in our presentation and as the input to our technique, is a variant of tensor index notation developed by Ricci-Curbastro and Levi-Civita [1901]. The notation uses index variables to relate each component in the result to the components in the operands that are combined to produce its value. In this example, every component Aij of the output is the inner product of a vector from the last dimension of B with c. The kernel that evaluates this expression depends entirely on the formats of the three tensors. The simplest case is when all of the tensors are dense, and the kernel for this is shown in Fig. 1. The loops iterate over the m × n × p rectangular iteration space of the tensor dimensions and, since the tensors are dense, the physical location of every component can be computed.