Majid Al-RaimiTorchSparse: regular computation from irregular sparsity

COE 592Lecture 4.2Part 12

TorchSparse: regular computation from irregular sparsity

The gather, matmul, scatter pipeline on GPUs, why separate small matmuls waste the GPU, and how TorchSparse trades a little padding for regularity with adaptive grouping, locality-aware access and, in TorchSparse++, overlapped memory and compute.

Concepts
3
Slides
97-109
Reading
18 min
Understood
0/3 concepts

Why this part matters

Your research on embedded machine learning will meet sparse, irregular workloads long before it meets a textbook dense matrix: LiDAR sweeps, radar returns, event cameras, pruned weights. This part is about a systems pattern rather than a library. Irregular work is made regular by paying a controlled amount of redundant computation, and the right amount is found by measurement, not by assuming zero.

Part 11 ended with the map: the list of (input, output, weight offset) tuples that turns sparse convolution into a sparse set of dense matrix multiplies. Here we follow how a GPU actually executes that list, why the obvious execution wastes most of the device, and how TorchSparse and its successor recover the loss. The same reasoning returns whenever you schedule uneven work on an edge GPU such as a Jetson Orin, and the exam angle is direct: name the phases, explain the waste, describe the fix.

By the end you can

  1. Trace one weight offset through gather, matmul and scatter using the map, giving the buffer and partial-sum shapes.
  2. Explain why 27 small matmuls and per-offset gathers underuse a GPU, quoting the paper's utilization figure.
  3. Place separate computation, dense convolution and adaptive grouping on the overhead versus regularity spectrum and compute pad rows for a small example.
  4. Read the matmul ablation and say why TFLOP/s and speedup can disagree.
  5. Describe what TorchSparse++ overlaps and name its two components.

Keep the small example from part 11 in view. Five input points P0 to P4, five output points Q0 to Q4, a 3 x 3 kernel, so nine weight offsets from W(-1,-1) to W(1,1). The map for this layer has eleven tuples. Two of them belong to W(-1,-1), one to W(-1,0), five to W(0,0), one to W(1,0), two to W(1,1), and the other four offsets have no entry at all in this tiny cloud.

The existing GPU implementation walks that list one weight at a time. It is weight-stationary: pick an offset, hold its C_in x C_out matrix still, and stream every map entry for that offset through it. Take W(-1,0). Its only entry is (P1, Q3), so the engine copies the single feature row f1 into a buffer of shape 1 x C_in, multiplies it by W(-1,0) to get one partial sum of shape 1 x C_out, and adds that row into Q3. Now take W(0,0). With stride 1 every input point sits on top of its own output, so the centre map contains all five entries. The buffer is the whole 5 x C_in input, the matmul is full height, and five partial sums land one to one in Q0 to Q4. Then W(1,0) with only (P3, Q1), then W(1,1) with (P1, Q0) and (P4, Q3), a 2 x C_in buffer whose two results go to two different output rows.

One offset, one launch. Two of five input rows are gathered into a buffer, multiplied by W(-1,-1), and the two partial sums are scattered into Q1 and Q4. Three rows sit idle for this launch.

That is the whole rule, and it is the same three phases every time. The names are the ones the lecture and the paper use: gather, matmul, scatter. For each weight offset delta, gather the |M_delta| input rows named by the map into a contiguous buffer, run one dense matrix multiplication of the |M_delta| x C_in buffer by the C_in x C_out weight, and scatter-accumulate the |M_delta| x C_out partial sums into the output rows named by the map. Written as an update rule over the whole map:

fout[q]+=fin[p]Wδfor every (p,q,δ)mapsf_{\text{out}}[q] \mathrel{+}= f_{\text{in}}[p] \, W_{\delta} \quad \text{for every } (p, q, \delta) \in \text{maps}
Weight-stationary sparse convolution: one accumulate per map entry

The three phases and what bounds each one

Gather
Read the |M_delta| input rows named by the map into a contiguous buffer. Random reads, bound by memory bandwidth.
Matmul
Multiply the |M_delta| x C_in buffer by the C_in x C_out weight. Dense FLOPs, but small and uneven per offset.
Scatter-accumulate
Add the |M_delta| x C_out partial sums into the output rows named by the map. Random writes with accumulation, bound by memory bandwidth.

In three dimensions the loop is longer. A 3 x 3 x 3 kernel has 27 offsets, and slide 105 notes that the centre (0,0,0) is usually computed separately because, at stride 1, its map is simply the identity over all points, leaving 26 irregular offsets. Because point-cloud occupancy is dynamic sparsity, the maps are built at run time for every scan, so the engine cannot precompute a fixed schedule the way it could for pruned weights.

Why this wastes most of the GPU

The baseline is correct and simple, and Tang et al. measured exactly where it loses. Two bottlenecks share the blame. First, the matmuls are non-uniform. The paper reports that map sizes for different weights can differ by an order of magnitude and that most map sizes are small, so each launch is a short, narrow matrix that cannot fill the device: MinkUNet 0.5x on an RTX 2080 Ti in FP16 reaches 8.1 TFLOP/s, only about 30 percent device utilization, with the matmul phase taking 20 to 50 percent of runtime (Tang et al., MLSys 2022, section 3). NVIDIA's own cuBLAS guidance says the same thing from the other side: when the matrices are small, the small matrix size prevents the GPU from being fully utilized, and launching them one after another produces many kernels launched in sequence (NVIDIA Developer Blog).

Second, and larger, gather and scatter are memory-bound. Each one is a random access pattern over the feature tensor, bottlenecked by GPU memory bandwidth rather than by computation resources, and together they take 40 to 50 percent of runtime. Worse, because every offset gathers and scatters on its own, the data movement is completely separated from one offset to the next: a row like f1 that appears in the maps of W(-1,0), W(0,0) and W(1,1) is fetched three times from off-chip memory with nothing reused (Tang et al., MLSys 2022, section 3).

Worked example

Nine offsets, eleven map entries

  1. Group the map by offset

    W(-1,-1): (P0, Q1), (P3, Q4). W(-1,0): (P1, Q3). W(0,0): all five. W(1,0): (P3, Q1). W(1,1): (P1, Q0), (P4, Q3). Four offsets are empty.
  2. Shape each launch

    Buffers are 2 x C_in, 1 x C_in, 5 x C_in, 1 x C_in and 2 x C_in. Each matmul has the same C_in x C_out weight but a different row count, and each writes exactly as many partial-sum rows as it read.
  3. Count the kernels

    Five non-empty offsets means five gathers, five matmuls and five scatters, fifteen launches for eleven accumulations. In a real layer with 26 non-centre offsets and maps of thousands of rows the count is 78 launches per layer, most of them short.
  4. Where the time goes

    Roughly half of it in gather and scatter, memory-bound and unshared between offsets, and the rest in matmuls that run the tensor cores at a fraction of capacity because the rows per launch are few and uneven.

Recall

Name the three phases of the baseline GPU sparse convolution and say which two are memory-bound.

Gather, matmul and scatter-accumulate. Gather and scatter are random accesses bound by memory bandwidth, together 40 to 50 percent of runtime. The matmul is dense compute, but small and uneven.

Recall

In a 3D 3x3x3 layer, how many separate matmuls does the baseline launch, and why do they underuse the GPU?

27, or 26 plus a separately handled centre. Map sizes differ by an order of magnitude and most are small, so each launch cannot fill the device; TorchSparse measured about 30 percent utilization for MinkUNet 0.5x on an RTX 2080 Ti.

Quick check

Why does the baseline gather-matmul-scatter pipeline underuse a GPU?

Look at the same five features one more time, now the way TorchSparse runs them. The gather step still builds one buffer per offset, but adaptive grouping pads those buffers to a shared height, so the four small offsets become four buffers of exactly two rows each: {F0, F3} for W(-1,-1), {F1, pad} for W(-1,0), {F3, pad} for W(1,0) and {F1, F4} for W(1,1). Two of those rows are dashed: a pad, a row of zeros inserted so that every buffer has the same height. Because the four buffers now share a shape, one batched matrix multiplication (bmm, batch 4) runs all four at once. The centre weight W(0,0), whose buffer holds all five rows, runs as an ordinary matrix multiplication (mm). Partial sums PSUM 0 to PSUM 4 are scattered back with the same locality-aware access, and the two pad rows produce two blank partial-sum slots that nobody reads.

Those two dashed rows are the price. Two zero rows were multiplied for nothing so that five matmul launches became two. That is the entire idea of this concept, and the paper names it in the slide title: trading computation for regularity. The overview on slide 101 lists three techniques, locality-aware gather, adaptive grouping of the matmuls, and locality-aware scatter-accumulate. The first and last attack the memory-bound half of the runtime by reading and writing feature rows in an order that keeps neighbouring accesses close and by fusing and vectorizing them; the paper reports the cost of memory movement cut by 2.7x that way (Tang et al., MLSys 2022, abstract). The middle technique, grouping, is what the rest of this concept builds up.

The spectrum: separate, dense, grouped

Slides 102 to 104 draw seven weight offsets as seven columns of unequal height, one row per map entry, and put each execution strategy on two sliders: computation overhead and computation regularity. Separate computation, the baseline of the previous concept, launches one mm per column. Nothing is padded, so its overhead slider sits at best, but seven uneven kernels put its regularity at worst: many kernel calls, low device utilization. Dense convolution goes to the other end. Pad every column up to the tallest and run a single bmm with batch 7. Regularity is perfect, one launch, but the dashed rows now outnumber the real ones in the short columns, and overhead sits at worst. Computation with grouping refuses both extremes. The tallest column keeps its own mm. The four columns of similar height are padded by a total of two rows and run as one bmm with batch 4. The two shortest, equal already, form a bmm with batch 2. Slide 104 counts the cost: extra computation of 2 / 28, about 7 percent, for cutting seven launches to three.

Seven uneven offsets become three launches. The tallest stays a plain mm, four similar columns are levelled with two dashed pad rows into one bmm, and the two shortest pair up with no padding at all.
StrategyKernel launchesPadded rowsComputation overheadComputation regularity
Separate computation70Best (none)Worst (many tiny kernels)
Dense convolution1Every column padded to the tallestWorstBest (one bmm, batch 7)
Computation with grouping32 pad rows in the batch-4 group (2 / 28)SmallHigh (mm, bmm x4, bmm x2)
Three strategies on the seven-column figure of slides 102 to 104

How the groups are chosen

Grouping only pays when the columns in a group really are similar, so the question becomes how similar is similar enough. TorchSparse answers with two auto-tuned parameters. The first, epsilon, is the tolerance of redundant computation. Walk the offsets in kernel order and scan them once with two pointers, extending the current group while the redundant computation ratio stays at or below epsilon and starting a new group the moment adding the next offset would exceed it (Tang et al., MLSys 2022, section 4.2.3). The groups are therefore contiguous runs of weight indices, which is why the red boxes on slide 105 sit side by side along the weight axis; the tuner below and the slide 104 figure instead order the offsets by map size first, a simplification that makes the pad rows easier to see.

r=1theoretical FLOPsactual FLOPs=1δGMδGmaxδGMδr = 1 - \frac{\text{theoretical FLOPs}}{\text{actual FLOPs}} = 1 - \frac{\sum_{\delta \in G} |M_\delta|}{|G| \cdot \max_{\delta \in G} |M_\delta|}
Redundant computation ratio of a group G; adaptive grouping keeps r at or below epsilon

The second parameter, S, is a workload threshold. A group runs as bmm only if its largest map is below S; otherwise it runs as mm, because bmm improves device utilization for small workloads but has little benefit once a single matmul is already large enough to fill the GPU. The two knobs contain the whole spectrum as special cases: epsilon = 1 with S unbounded is dense convolution, S = 0 is separate computation, and epsilon = 0 with S unbounded is what the paper calls symmetric grouping. That last case is free: for an odd kernel size at stride 1 the map for offset (a, b, c) has exactly the same size as the map for (-a, -b, -c), since every pair of neighbours appears once in each direction, so 26 offsets collapse into 13 groups of two with no padding at all, worth up to about 1.2x (Tang et al., MLSys 2022, section 4.2.2).

Worked example

Nine offsets on a 100-point cloud

  1. Map sizes

    N = 100 points, stride 1, so W(0,0) maps all 100. Symmetric pairs share sizes: W(-1,0) = W(1,0) = 40, W(0,-1) = W(0,1) = 38, W(-1,-1) = W(1,1) = 12, W(-1,1) = W(1,-1) = 11. Real rows: 100 + 2(40 + 38 + 12 + 11) = 302.
  2. Separate computation

    Nine launches, 302 rows computed, zero pad rows, overhead 0 percent.
  3. Dense convolution

    One bmm of batch 9 with every offset padded to 100: 900 rows computed, 598 of them pad, overhead 598 / 900 = 66.4 percent. The redundant ratio is 1 - 302 / 900, the same number.
  4. Adaptive grouping with epsilon 0.05 and S = 50

    W(0,0) exceeds S, so it runs alone as mm: 100 rows. Group A {40, 40, 38, 38} padded to 40: 160 rows, 4 pad, ratio 1 - 156 / 160 = 0.025. Adding a 12 would push the ratio to 0.16, so a new group starts. Group B {12, 12, 11, 11} padded to 12: 48 rows, 2 pad, ratio 0.042.
  5. Three launches for two percent

    3 launches, 308 rows computed, 6 pad rows, overhead 6 / 308 = 1.9 percent. Nine launches shrink to three at a cost that rounds to nothing.
StrategyLaunchesRows computedPad rowsOverhead
Separate (one mm per offset)930200%
Dense (one bmm, batch 9, padded to 100)190059866.4%
Adaptive (epsilon 0.05, S = 50)330861.9%
The three strategies on the 100-point example

Try the knobs yourself. The tuner below starts from exactly this example, and rerolling draws new map sizes so you can watch which groups form as epsilon and S move.

SimulatorGrouping tuner: nine weight offsets, one tolerance, one threshold
100 input points, stride 1, so symmetric offsets share a map size and the centre maps every point.
0.05
50 rows
10 rows
  • mm100
  • bmm x440, 40, 38, 38 +4 pad
  • bmm x412, 12, 11, 11 +2 pad
Kernel launches3one per group, mm or bmm
Rows computed308rows302 real rows
Pad rows6rowsoverhead 1.9%
Illustrative time338launches x cost + rows
Separate computation (S = 0)9 launches, 302 rows, 0 pad
Current grouping3 launches, 308 rows, 6 pad
Dense convolution (epsilon = 1)1 launches, 900 rows, 598 pad
At this threshold and launch cost the fastest tolerance is epsilon = 0.05 with 3 launches and time 338.

The tuner sorts the offsets by map size for clarity; the paper's Algorithm 4 scans them in kernel order. Either way the scan is a single pass. A new group starts whenever adding the next offset would push the redundant computation ratio, 1 minus real rows over computed rows, above epsilon. Any offset whose map is larger than S runs alone as a plain mm, because batching only helps small workloads. Dashed teal blocks are pad rows: real multiplications on zero rows, bought to turn several launches into one. The time model is a teaching device, not a measurement; the shape it produces (zero padding is not the optimum, and neither is one group) is what slide 105 measured on a real GPU.

Measured: neither zero padding nor one group wins

Slide 105 is the experiment that settles the question. On the first sparse convolution layer of MinkUNet on SemanticKITTI, the paper sweeps the number of groups from 26 (no padding, the separate baseline at 1.0x) down to 1 (fully dense). Going from 26 to 13 groups gives about 1.2x, the symmetric pairing. At 6 groups the speedup peaks near 1.5x. Then it collapses: 3 groups fall below the baseline and 1 group runs at about 0.35x, worse than doing nothing. The slide annotates the two slopes as increasing regularity helps improve latency, then padding overhead hurts latency (Tang et al., MLSys 2022, Figure 7).

Speedup over the 26-group baseline as groups are merged. The curve climbs to about 1.5x at 6 groups, then padding overhead drags 3 groups below 1.0x and 1 group to about 0.35x.

The two bar charts under the curve explain why the answer is adaptive rather than fixed. They plot map size against weight index 1 to 27 for a MinkUNet layer on two datasets, with red boxes marking the groups the tuner chose. SemanticKITTI maps are large, mostly in the thousands, and the boxes are narrow: 10 groups. nuScenes maps are an order of magnitude smaller, hundreds rather than thousands, so each launch is even less able to fill the GPU and the tuner groups more aggressively: 8 groups with wider boxes (Tang et al., MLSys 2022, Figure 12). The right amount of padding is a property of the data, which is why it is searched per model and per dataset instead of fixed in code.

Recall

What do epsilon and S control in adaptive grouping?

epsilon is the tolerated redundant computation ratio, 1 - theoretical / actual FLOPs, at which the scan starts a new group. S is the workload threshold: a group whose largest map exceeds S runs as mm instead of bmm, because batching no longer helps a workload that already fills the device.

Quick check

In adaptive grouping, what happens when you raise the tolerance epsilon?

Slides 106 and 107 isolate the matmul phase and ask a sharp question: on an RTX 2080 Ti in FP16, how much did grouping actually buy? Three strategies are compared, the separate baseline, a fixed grouping with three hand-made groups per layer type padded to their maximum, and adaptive grouping. Each is reported twice, as throughput in TFLOP/s and as normalized speedup, and the two columns disagree in an instructive way.

DatasetStrategyTFLOP/sNormalized speedup
SemanticKITTIBaseline (separate)8.11.00x
SemanticKITTIFixed grouping8.70.87x
SemanticKITTIAdaptive grouping11.91.39x
nuScenesBaseline (separate)10.41.00x
nuScenesFixed grouping21.11.50x
nuScenesAdaptive grouping16.91.54x
Matmul-phase ablation, RTX 2080 Ti, FP16 (slides 106 and 107, paper Table 2)

On SemanticKITTI (MinkUNet 0.5x) adaptive grouping lifts throughput from 8.1 to 11.9 TFLOP/s and finishes 1.39x faster. Fixed grouping is the trap: it posts a higher throughput than the baseline, 8.7 TFLOP/s, yet is slower, 0.87x. On nuScenes (MinkUNet, three frames) the trap is sharper still. Fixed grouping reaches the best throughput of the whole table, 21.1 TFLOP/s, and adaptive grouping only 16.9, yet adaptive is the faster of the two at 1.54x against 1.50x. Slide 107 gives the reason in one line: fixed grouping introduced a large amount of redundant computation. TFLOP/s counts every multiply the tensor cores performed, pad rows included. A strategy can therefore look excellent on throughput while spending that throughput on zeros. Latency counts only the clock, and the paper's caption puts it precisely: as we trade FLOPs for regularity, TFLOP/s and speedup are non-proportional (Tang et al., MLSys 2022, Table 2). Device utilization for the matmul phase rose from about 30 percent to 44.2 percent with adaptive grouping.

End to end, with locality-aware gather and scatter included, the paper reports 1.6x over MinkowskiEngine and 1.5x over SpConv across seven models and three datasets, measured on GTX 1080 Ti, RTX 2080 Ti and RTX 3090, with up to 2.16x on segmentation models over MinkowskiEngine on the RTX 3090 (Tang et al., MLSys 2022, abstract and section 5). These figures are not on the slides; the slides show only the matmul ablation, so quote them as the paper's, and note that the MLSys 2022 evaluation used desktop GPUs only. The edge numbers come with the successor.

Recall

Fixed grouping reached 21.1 TFLOP/s on nuScenes and adaptive grouping only 16.9. Which was faster, and why?

Adaptive grouping, 1.54x against 1.50x. TFLOP/s counts padded multiplies, so fixed grouping earned its throughput on redundant work; latency only counts time.

Quick check

On nuScenes, fixed grouping posted the highest TFLOP/s yet adaptive grouping finished faster. Why?

TorchSparse++: overlap memory with computation

Grouping fixed the matmul phase, but the baseline still has a structural flaw that no grouping removes: the three phases of gather, matmul and scatter run one after another. Gather must finish before the matmul starts, and the matmul must finish before scatter starts, three separate CUDA kernel calls in each iteration of the host loop. While the memory system is busy gathering, the tensor cores wait; while the tensor cores multiply, the memory system idles. The MICRO 2023 paper calls gather-GEMM-scatter fundamentally inefficient due to the lack of overlap between computation and memory access (Tang, Yang et al., MICRO 2023, section 2.2). The remedy in TorchSparse++ is to stop treating the phases as separate kernels.

Two fused dataflows already existed in other engines, and TorchSparse++ builds on both. Fetch-on-demand, used by MinkowskiEngine, merges gather, multiply and scatter into a single kernel that loads only the input rows it needs straight into shared memory, multiplies on chip and scatters results from registers, with zero redundant computation but heavy output write traffic, 4x to 10x more than the theoretical minimum of one write per output row, since each point has 4 to 10 neighbours and every partial sum is written back individually. Implicit GEMM, used by SpConv v2, is output-stationary: each thread block owns a tile of output rows, walks the nine (or 27) weights, and fetches the matching input rows on demand, the way im2col does for dense convolution. It writes each output once and hides memory latency by pipelining, so the loads for the next tile are already in flight while the tensor cores work on the current tile (Tang, Yang et al., MICRO 2023, section 2.2 and Figure 3). That in-kernel double buffering is the overlap the slide title refers to.

Above, the baseline runs gather, matmul and scatter strictly in series for each offset. Below, one fused kernel keeps the memory lane loading the next tile while the compute lane multiplies the current one, and finishes the same work sooner.
DataflowStationaryKernel structureMemory and computeRedundant computation
Gather-GEMM-scatterWeightThree kernels per offset, vendor GEMMNo overlap between memory access and computeZero
Fetch-on-demandWeight (fused per offset)One fused kernel, rows fetched into shared memoryLoads overlap with on-chip multiplyZero, but 4x to 10x more output writes
Implicit GEMMOutputOne fused kernel, im2col style, pipelined tilesLoads for the next tile overlap the current oneLockstep redundancy inside each warp
Three GPU dataflows for sparse convolution (Tang, Yang et al., MICRO 2023, section 2.2)

The catch with implicit GEMM is a new kind of redundancy, and slide 109 shows it. Rows are output points B0 to B7, columns are the nine weights, and a grey cell is a real multiply because the input neighbour A_j exists. Threads in a warp execute in lockstep, so if any row in a thread block needs a weight, every row in that block runs it. The red cells are those wasted lockstep multiplies: 12 in the vanilla layout. Row reordering sorts the outputs by their neighbour bitmask so rows with similar patterns share a block, cutting the count to 10. Column splitting then divides the loop over the nine weights into three parts, each sorted on its own, so a row is grouped with different neighbours for different weight subsets: 8. The paper's own example runs 34 to 26 to 22 multiplies and reports that bitmask sorting can cut redundancy by up to 3x (Tang, Yang et al., MICRO 2023, Figures 5, 6 and 10). The fused kernel keeps its overlap; these two reorderings trim what it pays for it.

Writing such kernels by hand is expensive. SpConv v2 needed more than 40,000 lines re-implementing CUTLASS. TorchSparse++ therefore contributes two components, both drawn on slide 108. The Sparse Kernel Generator produces the fused, pipelined kernels automatically at under a tenth of that engineering cost, adapting dense GEMM templates to sparse gather (dense-to-sparse adaptation) and static shapes to run-time maps (static-to-dynamic adaptation). The Sparse Autotuner widens the design space (gather-GEMM-scatter with grouping, fetch-on-demand, implicit GEMM, and their tile and split parameters) and tunes a configuration per group of layers rather than per layer, so the search stays cheap (Tang, Yang et al., MICRO 2023, sections 3 and 4).

GPUMinkowskiEngineSpConv 1.2.1TorchSparseSpConv 2.3.5TorchSparse++
A1000.340.300.450.601.00
Jetson Orin0.190.290.400.801.00
Geomean speed normalized to TorchSparse++, read from the chart on slide 108

Reading the A100 column the other way round, TorchSparse++ is 2.9x faster than MinkowskiEngine, 3.3x faster than SpConv 1.2.1, 2.2x faster than TorchSparse and 1.7x faster than SpConv 2.3.5, the four numbers the paper's abstract reports. The Orin column is the one that matters for this course: on the Jetson Orin edge GPU, the strongest baseline, SpConv 2.3.5, sits at 0.80, so TorchSparse++ is 1.25x faster on average, with a consistent 1.3x to 1.4x on detection workloads (Tang, Yang et al., MICRO 2023, section 5). The chart also shows training columns and a TF32 column; the same engine covers all of them.

One more step remains beyond software. Every engine in this part still spends time building the maps themselves, a hashing or sorting problem the GPU is not shaped for. Part 13 moves that step into hardware with PointAcc, whose mapping unit produces the tuples with merge sort.

Recall

What does TorchSparse++ overlap, and with which two components?

Memory access with computation, inside a fused, pipelined kernel (fetch-on-demand or implicit GEMM) rather than three serial kernels. Its components are the Sparse Kernel Generator, which writes those kernels, and the Sparse Autotuner, which chooses the dataflow and parameters per layer group.

Quick check

What does TorchSparse++ change about the three phases?

Recap

If you remember nothing else

  • The baseline runs gather, matmul and scatter once per weight offset: 27 offsets in 3D, each a separate small kernel.
  • Gather and scatter are memory-bandwidth bound (40 to 50 percent of runtime); the matmuls are dense but tiny and uneven (about 30 percent utilization).
  • Padding offsets of similar size into one batched matmul trades FLOPs for regularity; dense batching pads everything and loses.
  • Adaptive grouping scans map sizes with a tolerance epsilon and a threshold S; 6 groups beat both 26 and 1 on SemanticKITTI, about 1.5x.
  • Matmul ablation: adaptive grouping 1.39x on SemanticKITTI and 1.54x on nuScenes; fixed grouping can post higher TFLOP/s and still be slower.
  • TorchSparse end to end: 1.6x over MinkowskiEngine and 1.5x over SpConv, with 2.7x less memory movement cost.
  • TorchSparse++ fuses the phases so memory access overlaps computation, generates the kernels automatically and autotunes the dataflow: 2.9x, 3.3x, 2.2x and 1.7x on A100, 1.25x over SpConv v2 on Jetson Orin.
  • Row reordering and column splitting cut implicit-GEMM redundancy from 12 to 10 to 8 on the slide's example.

Sources