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
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
- Trace one weight offset through gather, matmul and scatter using the map, giving the buffer and partial-sum shapes.
- Explain why 27 small matmuls and per-offset gathers underuse a GPU, quoting the paper's utilization figure.
- Place separate computation, dense convolution and adaptive grouping on the overhead versus regularity spectrum and compute pad rows for a small example.
- Read the matmul ablation and say why TFLOP/s and speedup can disagree.
- 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.
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:
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
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.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.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.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.
Recall
In a 3D 3x3x3 layer, how many separate matmuls does the baseline launch, and why do they underuse the GPU?
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.
| Strategy | Kernel launches | Padded rows | Computation overhead | Computation regularity |
|---|---|---|---|---|
| Separate computation | 7 | 0 | Best (none) | Worst (many tiny kernels) |
| Dense convolution | 1 | Every column padded to the tallest | Worst | Best (one bmm, batch 7) |
| Computation with grouping | 3 | 2 pad rows in the batch-4 group (2 / 28) | Small | High (mm, bmm x4, bmm x2) |
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.
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
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.Separate computation
Nine launches, 302 rows computed, zero pad rows, overhead 0 percent.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.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.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.
| Strategy | Launches | Rows computed | Pad rows | Overhead |
|---|---|---|---|---|
| Separate (one mm per offset) | 9 | 302 | 0 | 0% |
| Dense (one bmm, batch 9, padded to 100) | 1 | 900 | 598 | 66.4% |
| Adaptive (epsilon 0.05, S = 50) | 3 | 308 | 6 | 1.9% |
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.
- mm100
- bmm x440, 40, 38, 38 +4 pad
- bmm x412, 12, 11, 11 +2 pad
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).
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?
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.
| Dataset | Strategy | TFLOP/s | Normalized speedup |
|---|---|---|---|
| SemanticKITTI | Baseline (separate) | 8.1 | 1.00x |
| SemanticKITTI | Fixed grouping | 8.7 | 0.87x |
| SemanticKITTI | Adaptive grouping | 11.9 | 1.39x |
| nuScenes | Baseline (separate) | 10.4 | 1.00x |
| nuScenes | Fixed grouping | 21.1 | 1.50x |
| nuScenes | Adaptive grouping | 16.9 | 1.54x |
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?
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.
| Dataflow | Stationary | Kernel structure | Memory and compute | Redundant computation |
|---|---|---|---|---|
| Gather-GEMM-scatter | Weight | Three kernels per offset, vendor GEMM | No overlap between memory access and compute | Zero |
| Fetch-on-demand | Weight (fused per offset) | One fused kernel, rows fetched into shared memory | Loads overlap with on-chip multiply | Zero, but 4x to 10x more output writes |
| Implicit GEMM | Output | One fused kernel, im2col style, pipelined tiles | Loads for the next tile overlap the current one | Lockstep redundancy inside each warp |
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).
| GPU | MinkowskiEngine | SpConv 1.2.1 | TorchSparse | SpConv 2.3.5 | TorchSparse++ |
|---|---|---|---|---|---|
| A100 | 0.34 | 0.30 | 0.45 | 0.60 | 1.00 |
| Jetson Orin | 0.19 | 0.29 | 0.40 | 0.80 | 1.00 |
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?
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
- TorchSparse: Efficient Point Cloud Inference EnginePaperProceedings of MLSys 2022, Tang, Liu, Li, Lin and HanSection 3 bottleneck analysis, section 4.2 adaptive grouping with epsilon and S, Table 2 ablation, Figures 7 and 12.(opens in a new tab)
- TorchSparse: Efficient Point Cloud Inference Engine (arXiv abstract)PaperarXiv1.4 to 1.5x matmul speedup, 2.7x lower memory movement cost, 1.6x and 1.5x end to end over MinkowskiEngine and SpConv.(opens in a new tab)
- TorchSparse++: Efficient Training and Inference Framework for Sparse Convolution on GPUsPaperMICRO 2023, Tang, Yang, Liu, Tang, Zhu, Lu, Ren, Wang, Xu, HanThree dataflows and their overlap (section 2.2, Figure 3), row reordering and column splitting (Figures 5, 6, 10), kernel generator and autotuner, 2.9x, 3.3x, 2.2x, 1.7x on A100 and 1.25x on Jetson Orin.(opens in a new tab)
- TorchSparse++ (ACM DL record)PaperACM, MICRO 2023Publisher's version of the MICRO 2023 paper.(opens in a new tab)
- TorchSparse repositoryDocsMIT HAN Lab, GitHubReference implementation of both engines.(opens in a new tab)
- MIT 6.5940 TinyML and Efficient Deep Learning Computing, Fall 2024, Lecture 4: Pruning and Sparsity Part IIDocsMIT HAN LabSource deck for slides 101 to 109, including the 12, 10, 8 redundancy example on slide 109.(opens in a new tab)
- MIT 6.5940 Lecture 4 recordingVideoMIT HAN Lab, YouTubeSong Han walks through the TorchSparse slides.(opens in a new tab)
- cuBLAS Strided Batched Matrix MultiplyArticleNVIDIA Developer BlogSmall GEMMs cannot fill the GPU, and sequential launches of many small kernels are slow; the motivation for batched matmul.(opens in a new tab)
- cuBLAS documentation: batched GEMMDocsNVIDIAThe bmm primitive that grouped offsets are executed with.(opens in a new tab)
- CUDA C++ Best Practices GuideDocsNVIDIACoalesced memory access and latency hiding, the principles behind locality-aware access and pipelined fused kernels.(opens in a new tab)