COE 592Lecture 4.2Part 11
Sparse inputs and sparse convolution
Point clouds are mostly empty space, so convolution should compute only where inputs exist: sparse convolution keeps output sparsity equal to input sparsity and is a sparse set of dense matrix multiplies driven by an input, output and weight map.
- Concepts
- 3
- Slides
- 84-96
- Reading
- 18 min
Why this part matters
The sparsity in this part is not something you create by pruning or by passing activations through ReLU. It is handed to you by the physical world. A LiDAR scanner on a car returns a few hundred thousand points inside a volume of billions of voxels, and the winning entries on SemanticKITTI, nuScenes and Waymo all process those points with sparse convolution (Tang et al., 2022). If your research project puts 3D perception on an embedded board, this is the operator you will be optimizing.
The part has one story with three turns. First, why an ordinary convolution is the wrong tool for a point cloud: it dilates the empty space into a dense blur within a few layers. Second, the fix, which computes outputs only where inputs exist, and the small bookkeeping structure that describes exactly which multiplications remain. Third, how a GPU actually runs that bookkeeping as a handful of dense matrix multiplies, which is where parts 12 and 13 pick up with TorchSparse and PointAcc. Every idea rests on one list of tuples, so by the end you should be able to write that list for a small grid by hand.
By the end you can
- Explain the submanifold dilation problem with the 1, then 3^d, then 5^d rule and the 17-of-20 example from the slides.
- Define submanifold sparse convolution and state why the output point set equals the input point set.
- Write the update rule f_out = f_out + f_in x W_(dx,dy) and build the (In, Out, Wgt) map for a small grid by hand.
- Group a map by weight offset and cost the resulting gather, matmul, scatter pipeline in MACs.
- Distinguish point-cloud sparsity from ReLU activation sparsity and from pruned weight sparsity.
Start with a number. Voxelize an outdoor LiDAR frame into a regular 3D grid and count how many cells hold at least one point. PointAcc (Lin et al., MICRO 2021) reports that outdoor point clouds usually have a density below 0.01 percent, while a conventional CNN takes in a 100 percent dense image. That is the ~0.01% written above the sparse column of the slide. Indoor scans and single objects sit below 1 percent. Even after ReLU, an ImageNet network still keeps about half of its activations nonzero, so an image is up to four orders of magnitude denser than a LiDAR scan.
How dense the input really is (Lin et al., 2021)
- ImageNet image at the input
- 100 percent of pixels carry a value
- Same network after ReLU
- about 50 percent nonzero on average
- Indoor scene or single object, voxelized
- below 1 percent of voxels occupied
- Outdoor LiDAR frame, voxelized
- below 0.01 percent of voxels occupied
The roadmap places this under activation sparsity, next to EIE and the M:N tensor cores, and that is correct as far as it goes: the zeros are in the data, not the weights. But the source is different in a way that changes everything downstream. ReLU zeros and pruned weight zeros land wherever the arithmetic puts them. Point-cloud zeros are the empty air between surfaces. PointAcc calls this sparsity "fundamentally different" because "the sparsity pattern is constrained by the physical objects in the real world", and draws the consequence that matters here: the nonzero points should never dilate during computation. In the vocabulary of part 07 it is still dynamic, since every frame has a new pattern, but it is a pattern with geometric meaning.
What a dense convolution does to empty space
Now apply an ordinary 3 x 3 convolution to a grid with a single active cell. The kernel window touches that cell from nine different positions, so nine outputs become nonzero. Apply a second layer and the nine become twenty-five. Graham, Engelcke and van der Maaten (CVPR 2018) state the rule in d dimensions: a single active site becomes 3^d active sites after one convolution and 5^d after two. In 3D that is 1, then 27, then 125. They name it the submanifold dilation problem, because a surface (a 2D submanifold in 3D space) thickens into a slab and the sparsity that made the input cheap is gone within a few layers.
The slide shows the same effect on a small grid you can check by eye. The input is 4 rows by 5 columns with four active cells at rows and columns (1, 1), (2, 2), (2, 4) and (3, 3). Each active cell activates every output its 3 x 3 window touches, clipped to the grid: nine for the first two points, six each for the two that sit against an edge. The union of those windows is 17 of 20 cells, exactly the orange region drawn on the conventional side. Only (0, 3), (0, 4) and (3, 0) stay empty, and a second layer would fill those too.
| Stage | Active cells | Density | Why |
|---|---|---|---|
| Input | 4 of 20 | 20 percent | The four teal cells |
| After one dense 3 by 3 layer | 17 of 20 | 85 percent | Union of the four 3 by 3 windows, clipped to the grid |
| After one submanifold 3 by 3 layer | 4 of 20 | 20 percent | Only the four input positions |
| After a second dense layer | 20 of 20 | 100 percent | Every cell now sits within one step of an active cell |
The fix: compute only where an input exists
Sparse convolution, in the submanifold form the deck uses, changes one rule. An output site is computed if and only if the input site at the same coordinate is active. Graham et al. define it exactly that way: the filter size is odd, the input is padded so the output keeps the input's size, and an output is active only when the central site of its receptive field is active. TorchSparse writes the same fact as a set equation, P_in = P_out, against the dense case where P_in is only a subset of P_out. On the slide grid the sparse side stays at four orange cells, and after a hundred layers it would still be four.
The pair of ring images at the bottom of the slide comes from two different figures of the Graham papers. The left ring is Figure 2 of the CVPR 2018 paper (Figure 1 of the 2017 arXiv version): a thin curve pushed through two ordinary 3 x 3 convolutions has smeared into a grey band. The right ring is Figure 3 of the CVPR 2018 paper: the submanifold receptive field centred on one active site, drawn in green, with the empty sites it ignores in red, so the ring stays a ring. That is the whole motivation in one picture: the operator must respect the geometry, or the geometry disappears.
Why a whole part on one operator? TorchSparse's introduction lists the stakes. All top five segmentation submissions on SemanticKITTI and nine of the top ten on nuScenes are built on sparse convolution, yet the operator is not supported by TensorRT or TVM, and the best library of the day ran MinkowskiNet at 8 FPS on a GTX 1080Ti, a desktop GPU far above anything on an embedded board. The gap between what the algorithm avoids and what the hardware delivers is the research territory of parts 12 and 13.
Recall
Why is the output of a submanifold sparse convolution exactly as sparse as its input, and what happens to one active site under two dense 3 by 3 layers instead?
Quick check
A 3 by 3 submanifold sparse convolution runs over four active cells of a 4 by 5 grid. How many output cells are nonzero?
Follow the boxed point P0 at row 1, column 1 through a 3 x 3 convolution. A 3 x 3 kernel is not one weight; it is nine weight matrices, one per tap, each of shape C_in x C_out. TorchSparse writes them as W_δ for each offset δ in {-1, 0, 1}^2. The deck names them W_(dx,dy), with dx the row offset and dy the column offset, and the visual and simulator below use the same labels. Every time P0 lands in an output's window, exactly one of those nine matrices multiplies its feature vector, and which one depends only on where the output sits relative to P0.
The deck's convention, consistent across all nine slides, is that the input P sits at the output Q shifted by the offset: P = Q + (dx, dy) in (row, column). The first entry on the slide makes it concrete. Output Q0 is the top-left corner of P0's window at (0, 0), so P0 = Q0 + (1, 1) and the weight is W_(1,1). The centre entry, where Q4 is P0's own position, uses W_(0,0). The bottom-right corner at (2, 2) uses W_(-1,-1).
The map is the computation
Each such relation is one tuple, and the list of all of them is what the slides call the maps, written (In, Out, Wgt). TorchSparse defines the map for a layer as the set M = {(p_j, q_k, W_δ)} and describes the whole layer as one loop: iterate over the map and, for each entry, accumulate the input feature vector times the entry's weight matrix into the output feature vector. That is the update rule printed at the bottom-left of every slide in this run.
Read the indicator as the map-building test. For each output q_k and each offset δ, look up the coordinate s·q_k + δ. If an input point p_j sits there, the triple (p_j, q_k, W_δ) joins the map; if not, nothing is added and nothing is computed. TorchSparse's Algorithm 1 is exactly this double loop with a hash-table lookup on coordinates. The heading of the slides now reads correctly: a sparse convolution is a sparse set of dense matrix multiply-accumulates, and the sparsity lives entirely in which tuples exist.
Nine entries become two
Now compare the two columns of the slide for P0. The conventional column lists one entry per tap, nine in all, because every cell of P0's window is an output that a dense layer computes. The sparse column applies the submanifold rule from the previous concept: an output must itself be an input position. Inside P0's window only two cells qualify, P0's own position and (2, 2), where P1 sits. So the map keeps exactly two tuples and marks the other seven "No compute".
| Conventional entry | Output position | Conventional | Submanifold sparse |
|---|---|---|---|
| (P0, Q0, W1,1) | (0, 0) | computed | no compute |
| (P0, Q1, W1,0) | (0, 1) | computed | no compute |
| (P0, Q2, W1,-1) | (0, 2) | computed | no compute |
| (P0, Q3, W0,1) | (1, 0) | computed | no compute |
| (P0, Q4, W0,0) | (1, 1) | computed | kept as (P0, Q0, W0,0) |
| (P0, Q5, W0,-1) | (1, 2) | computed | no compute |
| (P0, Q8, W-1,1) | (2, 0) | computed | no compute |
| (P0, Q9, W-1,0) | (2, 1) | computed | no compute |
| (P0, Q10, W-1,-1) | (2, 2) | computed | kept as (P0, Q1, W-1,-1) |
Costing the whole grid, not just P0
The slide follows one point, so extend it to all four. Conventionally each point contributes one entry per in-grid tap: 9 for P0, 9 for the point at (2, 2), 6 for (2, 4) against the right edge and 6 for (3, 3) against the bottom, 30 in total. The submanifold map has one centre entry per point plus one entry for every ordered pair of points within one step of each other. Three such pairs exist, (1, 1) with (2, 2), (2, 2) with (3, 3) and (2, 4) with (3, 3), each counted in both directions, giving 4 + 6 = 10 entries. Each entry is a (1 x C_in)(C_in x C_out) product, so with C_in = C_out = 64 an entry costs 4,096 MACs.
| Method | Entries | MACs | What is counted |
|---|---|---|---|
| Fully dense 3 by 3 over 20 outputs | 20 x 9 = 180 | 737,280 | Every output visits every tap, zeros included |
| Conventional, skipping zero inputs | 9 + 9 + 6 + 6 = 30 | 122,880 | Each point feeds every in-grid output its window touches |
| Submanifold sparse | 4 + 3 x 2 = 10 | 40,960 | Four centre entries plus three neighbouring pairs in both directions |
Two lessons hide in that table. Zero-skipping alone, the trick EIE relied on, already removes most of the work on this grid, but it still writes the dilated 17-cell output and so the next layer starts from 17 points instead of 4. The submanifold map is the only row whose cost stays proportional to the number of points layer after layer. On a real LiDAR frame with hundreds of thousands of points in a grid of billions of voxels, the fully dense row is not merely slow; it does not fit in memory.
Symmetry check: W-1,-1 has 2 entries and W1,1 has 2.
Click cells to place or remove points, then pick a kernel offset. The list shows every map entry for that weight using the deck's convention: input P sits at output Q shifted by the row and column offset (dx, dy), and the output must itself be an input position. The counts in the offset picker are the row counts of the gathered matrices on slide 96, so the number of non-empty offsets is the number of separate matmuls the existing GPU implementation launches. Compare the three MAC readouts: fully dense visits every cell, conventional zero-skipping still fills the dilated set, and submanifold pays only for entries whose output is a real point.
Recall
Write the sparse convolution update rule and name every symbol in it.
Recall
On slide 95, how many map entries does P0 have conventionally and sparsely, and why?
Quick check
In the entry (P0, Q1, W-1,-1) with the deck's convention P = Q + (dx, dy), where is Q1 relative to P0?
Quick check
Which statement about submanifold sparse convolution is correct?
Slide 95 closes the P0 story with a count, nine matrix multiplications against two, and slide 96 asks the practical question: how does a GPU run a list of tuples? Take its workload, a 5 x 5 grid with five points P0 at (1, 1), P1 at (2, 2), P2 at (2, 4), P3 at (3, 2) and P4 at (4, 3). Its map has eleven entries, and the box on the slide lists them not in point order but sorted by weight offset. That ordering is the whole idea.
Running the update rule one entry at a time means eleven vector-matrix products, each a single row of C_in values against a C_in x C_out matrix. TorchSparse notes that the utilization of matrix-vector multiplication is rather low on a GPU, whose tensor cores want tall matrices on both sides. The escape is to notice that entries sharing the same offset share the same matrix. Stack their input rows into one contiguous matrix, multiply it by that one W_δ, and you have turned several vector-matrix products into one matrix-matrix product. Grouping by output or by input would not work: entries with the same output use different weights, and so do entries with the same input.
| Weight | Entries (In, Out) | Rows gathered | Note |
|---|---|---|---|
| W-1,-1 | (P0, Q1), (P3, Q4) | 2 | highlighted on the slide |
| W-1,0 | (P1, Q3) | 1 | mirror of W1,0 |
| W0,0 | (P0, Q0), (P1, Q1), (P2, Q2), (P3, Q3), (P4, Q4) | 5 | one entry per point, no data movement |
| W1,0 | (P3, Q1) | 1 | mirror of W-1,0 |
| W1,1 | (P1, Q0), (P4, Q3) | 2 | mirror of W-1,-1 |
| W-1,1, W0,1, W0,-1, W1,-1 | none | 0 | no kernel launch at all |
Five of the nine offsets have entries, so this layer launches five matmuls with 2, 1, 5, 1 and 2 rows. The other four offsets have empty maps and launch nothing. Notice too that P2 at (2, 4) has no neighbour within one step, so it appears only in the W_(0,0) group: an isolated point costs exactly one entry.
Gather, matmul, scatter
The pipeline on slide 96 is the gather, matmul, scatter flow that every existing GPU implementation follows, written out for the highlighted offset W_(-1,-1). Its two entries are (P0, Q1) and (P3, Q4). Gather copies rows P0 and P3 out of the 5 x C_in input feature matrix into a 2 x C_in buffer. Matmul multiplies that buffer by the C_in x C_out weight to produce a 2 x C_out partial sum. Scatter adds row one into output Q1 and row two into Q4, which is the pair of equations under the figure: f1 = f1 + f0 x W_(-1,-1) and f4 = f4 + f3 x W_(-1,-1). Then the next offset takes its turn with its own rows.
Worked example
Costing the W(-1,-1) group with C_in = C_out = 64
Gather
Read rows P0 and P3 of the 5 x 64 input matrix into a 2 x 64 buffer: 128 values moved, no arithmetic.Matmul
Multiply the 2 x 64 buffer by the 64 x 64 matrix W_(-1,-1): 2 x 64 x 64 = 8,192 MACs, producing a 2 x 64 partial sum.Scatter-add
Add partial-sum row one into f1 and row two into f4: 128 additions into the 5 x 64 output matrix.Repeat for the other four non-empty offsets
Rows gathered are 1, 5, 1 and 2, so the layer does 11 row-matrix products in five kernel launches.Layer cost
11 x 4,096 = 45,056 MACs, against 159,744 for a zero-skipping conventional convolution on the same five points and 921,600 for a fully dense 3 x 3 over all 25 cells.
| Method | Entries | MACs |
|---|---|---|
| Fully dense 3 by 3 over 25 outputs | 25 x 9 = 225 | 921,600 |
| Conventional, skipping zero inputs | 9 + 9 + 6 + 9 + 6 = 39 | 159,744 |
| Submanifold sparse, 5 matmuls | 2 + 1 + 5 + 1 + 2 = 11 | 45,056 |
The slide's heading calls this weight-stationary: the weight matrix for one offset stays put while the gathered rows stream through it, and a separate matmul runs for each different weight. TorchSparse's Algorithm 2 is literally a loop over offsets that performs gather, matmul and scatter for each. The rule book in Graham et al. is the same design described from the sparse-convolution side: for each row (j, k) in the rule for offset i, multiply row j of the input matrix by W^i and add it to row k of the output, which they note runs efficiently on a GPU precisely because it is a matrix-matrix multiply-add. SECOND (Yan et al., 2018) moved the rule generation itself from a CPU hash table to the GPU, since the CPU version was slow and required data transfer between CPU and GPU on every layer.
A symmetry you can check on the slide
Look at the row counts again: W_(-1,-1) and W_(1,1) both have two rows, W_(-1,0) and W_(1,0) both have one. That is not luck. If (P, Q, W_δ) is an entry, then P and Q are both points and Q = P + (-δ), so (Q, P, W_(-δ)) is also an entry. TorchSparse section 4.2.1 proves this one-to-one correspondence for any odd kernel at stride 1, and the (0, 0) map is special for a second reason: every point maps to itself, so it needs no gather and no scatter at all. Part 12 builds its first optimization on exactly this pairing, and the simulator above reports the check on every grid you draw.
Recall
Name the three stages of the existing GPU implementation and explain why entries are grouped by weight offset.
Recall
From the slide 96 list, how many matmuls does the layer launch and with how many rows each?
Quick check
Why does the GPU implementation group map entries by weight offset before multiplying?
- Entries: for stride 1 with an odd kernel, count(δ) = count(-δ), and the (0, 0) map has exactly one entry per point.
- Matmuls per layer: the number of offsets whose map is non-empty, at most K^D.
- MACs per layer: total entries times C_in x C_out, independent of how the entries are grouped. Grouping changes utilization and data movement, never the arithmetic count.
Recap
If you remember nothing else
- Outdoor LiDAR voxel grids are below 0.01 percent dense; ImageNet inputs are 100 percent dense and about 50 percent after ReLU.
- Dense convolution dilates: one active site becomes 3^d, then 5^d; on the slide grid 4 of 20 cells become 17 of 20 after one 3 by 3 layer.
- Submanifold sparse convolution computes outputs only at input positions, so P_out = P_in and nothing dilates.
- The computation is a map of (In, Out, Wgt) entries with P = Q + (dx, dy); each entry does f_out = f_out + f_in x W_(dx,dy).
- For P0 on slide 95: 9 conventional entries, 2 sparse entries. Whole 4 by 5 grid: 30 versus 10.
- Entries sharing W_(dx,dy) are gathered into one matrix, multiplied once, then scatter-added (weight-stationary). Slide 96: 11 entries, five matmuls with 2, 1, 5, 1, 2 rows.
- At stride 1 with an odd kernel, the maps for an offset and its mirror always have equal size.
- Per-offset matmuls are irregular and gather plus scatter can take up to 50 percent of runtime: the problems TorchSparse and PointAcc attack in parts 12 and 13.
Sources
- TorchSparse: Efficient Point Cloud Inference EnginePaperMLSys 2022, Tang, Liu, Li, Lin and HanEq. 1, the map definition, Algorithms 1 and 2, gather-matmul-scatter, symmetric offsets in section 4.2.1, the 8 FPS and 50 percent figures.(opens in a new tab)
- 3D Semantic Segmentation with Submanifold Sparse Convolutional NetworksPaperCVPR 2018, Graham, Engelcke and van der MaatenSubmanifold dilation problem, 3^d then 5^d, the SSC definition, the receptive-field figure on the right of slide 86 and the rule book.(opens in a new tab)
- Submanifold Sparse Convolutional NetworksPaperarXiv 2017, Graham and van der MaatenOrigin of the ring-dilation figure reproduced on the left of slide 86.(opens in a new tab)
- Sparse 3D convolutional neural networksPaperBMVC 2015, GrahamThe paper the slide credit actually points to; its convolutions still dilate.(opens in a new tab)
- PointAcc: Efficient Point Cloud AcceleratorPaperMICRO 2021, Lin, Zhang, Tang, Wang and HanDensity below 0.01 percent outdoors, sparsity constrained by physical objects, nonzero points never dilate.(opens in a new tab)
- SECOND: Sparsely Embedded Convolutional DetectionPaperSensors 2018, Yan, Mao and LiGPU rule generation replacing the CPU hash table, and the gather, GEMM, scatter flow.(opens in a new tab)
- 4D Spatio-Temporal ConvNets: Minkowski Convolutional Neural NetworksPaperCVPR 2019, Choy, Gwak and SavareseGeneralized sparse convolution and the MinkowskiNet model that TorchSparse benchmarks at 8 FPS on a GTX 1080Ti.(opens in a new tab)
- SparseConvNetDocsFacebook Research, GitHubStates that the set of active sites is unchanged by submanifold convolution and that inactive sites carry no computational overhead.(opens in a new tab)
- TorchSparse repositoryDocsMIT HAN Lab, GitHubReference implementation of the maps and the gather-matmul-scatter pipeline.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Lecture 4DocsMIT HAN LabThe lecture whose slides 84 to 96 this deck mirrors, including the BMVC 2015 credit line.(opens in a new tab)