Specification

Design Space 03: Compositional Sparsity and Structured Tensors

Series: AI-Native Programming Language Research — Design Space Memos Document 3 of 6 (planned: shapes, AD-through-effects, sparsity, distribution, verification, hardware abstraction)


1. Problem statement

A "tensor" in modern ML is rarely just a dense block of floats. It is, increasingly:

Each of these is a structure on the tensor that the compiler should know about and exploit. None of them are language-level concepts in any current system. They are library types, kernel implementations, or runtime metadata, glued together by hand-written conversion code at every boundary.

This is the third large class of "bolted-on" features in the Python ML stack. The first two (shape, AD over effects) at least have a clean theory waiting to be implemented; sparsity has a clean theory for static patterns (TACO, MLIR's sparse_tensor dialect, Finch) and almost no theory for the dynamic and composed cases that dominate modern workloads.

This memo: maps the taxonomy, surveys what exists, identifies where compositionality breaks, and proposes that sparsity-as-type is the right framing — with sparsity patterns composing under tensor operations the way effects compose under sequencing in memo 2.


2. Taxonomy of sparsity

Two orthogonal axes: when the pattern is known (static vs. dynamic) and what kind of structure it has (point, block, low-rank, conditional). The cross product produces eight or so cells, of which six matter:

Static (compile-time) Dynamic (runtime)
Point sparsity CSR/CSC/COO formats with fixed pattern (graph adjacency) Activation sparsity (post-ReLU); top-K activations
Block / structured Block-sparse attention masks (Longformer); Ampere 2:4 Variable-sized blocks (ragged batches, packed sequences)
Low-rank / factorized LoRA, IA³, tensor-train decomposition Adaptive-rank methods, SVD with dynamic threshold
Conditional / routed (rare — usually dynamic) MoE routing; mixture density networks; speculative decoding
Pattern-imposed Triangular (causal masks); Toeplitz (convolutions); banded (rare)

Real workloads compose multiple cells. Mixtral does dynamic conditional routing into experts whose weights are dense but whose backward gradient flow is sparse. LoRA is static low-rank applied as a delta on top of dense pretrained weights. Long-context attention with KV cache uses block sparsity (sliding window) plus dynamic eviction.

The compositionality problem is exactly that the cells don't compose cleanly under tensor operations. Multiplying a block-sparse matrix by a low-rank matrix does not produce a clean type — the result depends on which dimensions overlap with which structure. Current systems handle this by giving up: convert one side to dense, do the operation, hope the result has discoverable structure.


3. The static end: TACO and its descendants

The static-sparsity story is, comparatively, in good shape.

TACO (Kjolstad et al., OOPSLA 2017) is the foundational work. It generates code for sparse tensor operations from a tensor index notation, given format specifications for each operand. The user writes A(i,j) = B(i,k) * C(k,j) and declares whether each tensor is dense, CSR, CSC, COO, or one of many other formats. TACO's compiler generates the appropriate iteration code, including the awkward inner loops where you intersect the nonzero patterns of two operands.

The key contribution is format abstraction (Chou, Kjolstad & Amarasinghe, OOPSLA 2018): a sparse format is a sequence of level formats (dense, compressed, singleton, etc.) along each dimension. This factors the format design space and makes it possible to compose formats — block-CSR is just a CSR with a dense inner level. This is the first piece of compositional theory that actually works.

MLIR's sparse_tensor dialect (Bik, Lewis, et al.) is the production realization of TACO's ideas inside an industrial compiler. It accepts annotated tensor types, generates iteration code, and integrates with the rest of MLIR's optimization infrastructure. Mojo's sparsity story will lower through this.

SparseTIR (Ye et al., ASPLOS 2023) brings format composability into TVM, with a focus on deep-learning workloads. It supports format hierarchies and integrates with auto-scheduling.

Finch (Ahrens et al.) is a Julia-based sparse array language that extends the TACO model with control flow — it can express algorithms that branch, conditionally compute, and combine sparse and dense computations in one program. This is the most mature work on integrating sparse arrays with general programming language constructs.

What these get right:

What they don't address:


4. The dynamic end: MoE and routed computation

Mixture of Experts is the production case of dynamic sparsity, and it is currently a mess.

The standard implementation: a gating network produces routing weights for each token; the top-K experts are selected per token; tokens are dispatched to expert weight matrices via scatter operations; results are gathered back via a sum. This is conceptually a sparse matrix multiplication where the sparsity pattern is determined at runtime per batch.

The implementation reality (Switch Transformer, GShard, Mixtral) is that this is implemented as a series of dense kernels with explicit scatter/gather, plus all-to-all communication if experts are sharded across devices. The kernels don't know about sparsity; they see dense ragged inputs.

MegaBlocks (Gale, Narayanan, Young & Zaharia, MLSys 2023) is the breakthrough: it reformulates MoE as a single block-sparse matrix multiplication where each token-to-expert assignment is a block, and uses dense GPUs to compute it efficiently via specialized block-sparse kernels. This is the closest existing work has come to treating MoE as sparsity rather than as routing.

The language-design observation: MoE routing is a conditional operation — for each token, multiply by the expert it routes to. Conditional computation is exactly what the if statement and the match expression are for in normal languages. The reason MoE doesn't look like an if is that the if is over batched data, and current frameworks have no batched-conditional construct. JAX has vmap over lax.cond, but it has to materialize both branches; what you want is vmap over a router that picks one branch per element without materializing the unused ones.

This is a research-language opportunity. A first-class route or mux construct, where the type captures "this is a sparse selection," would let the compiler generate MegaBlocks-style code automatically and would compose with AD via the effect machinery from memo 2 (the routing decision is the stochastic effect; the gradient through it is the estimator).


5. Hardware-imposed sparsity

A separate category, because the constraints are different.

NVIDIA Ampere structured sparsity (2:4 pattern): 2 of every 4 contiguous values must be zero. Hardware tensor cores accelerate matmul with this pattern by 2× via dedicated metadata paths. The pattern is rigid; you can't choose 3:8 or 4:8.

AMD CDNA, Intel AMX, and various dataflow architectures (Cerebras, Graphcore, Tenstorrent) each have their own structured-sparsity stories with their own constraints. None of them are expressible as TACO level formats — the constraint is which positions can be nonzero, not how many.

The language-design problem: hardware structured sparsity is a predicate on the tensor (positions satisfying a constraint), not a format. This is closer to a refinement type than to a sparse format. Tensor f16 [M, K] satisfying TwoOfFourSparse is a refinement on the dense type, not a different storage scheme.

The cleanest framing I know: separate the logical structure (what positions are zero) from the physical representation (how the nonzeros are stored). TACO conflates these for good engineering reasons, but the hardware-sparsity case wants them separated so that the compiler can match a logical refinement to a physical layout.

This is also where quantization fits. INT4 + scale + zero-point is a compressed representation of a tensor whose logical type is still f32 (with quantization error). The compositionality question — can a tensor be both 2:4 sparse and INT4 quantized? — is yes in principle, used in production, and currently implemented as bespoke kernel code with no type-system support whatsoever.


6. Comparison across the design space

TACO / MLIR sparse_tensor SparseTIR Finch MegaBlocks PyTorch sparse Hardware (Ampere 2:4)
Static formats ✓ (composable levels) Limited Few formats Fixed
Dynamic patterns Partial ✓ (control flow) ✓ (MoE only)
Hardware-structured N/A ✓ (only)
Low-rank
Quantization Separate Separate
AD support Library Limited Some Manual Manual Manual
Composes with dense
Composes with itself ✓ (formats)
Composes with shape (memo 1) Limited Limited Limited
Composes with distribution (memo 4) Hand-coded

The composition row is the key observation. Every system handles dense + sparse, and most handle their own internal composition. None handles cross-cutting composition (sparse + low-rank + quantized + sharded), which is exactly what production LLM workloads need.


7. Where compositionality actually breaks

The mathematical core: the result type of a tensor operation depends on the input types in non-obvious ways.

For dense × dense → dense, this is trivial. For sparse-format compositions:

The pattern: sparse-style operations don't form a closed algebra. Most of them lose structure under composition, and the lost structure is exactly what makes the next operation slow.

This has a precise analogy in algebra. Sparse matrices form a category but not a group — composition is defined but doesn't preserve the special structure. The right type-system response is to allow the result type to widen (to a less-structured supertype) when composition doesn't preserve structure, and to narrow (to a more-structured subtype) when the user can prove it does.

This is exactly the territory of subtyping with refinement. A 2:4Sparse Tensor is a refinement of Tensor. Multiplication of two 2:4Sparse Tensors produces a Tensor (widened). The user can opt back into 2:4Sparse Tensor via an explicit cast (which checks the refinement at runtime, or the compiler proves it statically in special cases like masked operations).

This is, again, not a current feature of any production system.


8. AD interactions

Sparsity and AD interact in three distinct ways:

8.1 Sparse gradients

If the loss is sparse in its dependence on a parameter (e.g., embedding lookups), the gradient is naturally sparse. PyTorch handles this via torch.sparse.SparseTensor for embeddings; nobody handles it generally. A compiler that knew about sparsity in the type could propagate this through the backward pass automatically.

8.2 Differentiating through routing

MoE routing is a hard-decision (top-K) that breaks differentiability. Workarounds:

This is where memo 2's effect framework pays direct dividends. Routing is a stochastic effect; the gradient through it is determined by the handler. Sparsemax is one handler, straight-through is another. The user picks (or the compiler picks) based on numerical and statistical considerations.

8.3 Sparse Jacobians for second-order methods

Hessians are usually sparse for structured problems (banded, block-diagonal). Newton's method, K-FAC, and other second-order methods need the sparsity exposed. Current frameworks compute dense Hessians and lose the structure. Sparse-aware AD (TACO has parts of this) is a research area in itself.


9. Open research problems

  1. A unified type for sparsity. Static formats, dynamic routing, hardware-imposed patterns, and low-rank factorization are currently four separate concepts. Is there a single type-level abstraction (perhaps a structure predicate on the tensor's value space) that covers all four? My guess: yes, via refinement types parameterized by a predicate language, with TACO-style level formats as one specialization.

  2. Composition rules under widening. When sparse × sparse loses structure, the type system should track that — and ideally let the user prove preservation in special cases (e.g., elementwise operations preserve all sparsity patterns, masked operations preserve structured sparsity). This is a theorem-proving problem dressed as a type-checking problem.

  3. Compiler-chosen physical layout. Given a logical sparsity refinement, the compiler should pick the storage format. Currently the user picks (TACO requires format specifications). With enough information, this is autoschedulable.

  4. MoE-as-sparse-matmul as a language feature. MegaBlocks shows the implementation works; nobody has packaged it as a language construct (route, mux, or dispatch). Doing so would let the compiler generate the kernel automatically and compose with AD via the routing-as-effect framing.

  5. Sparse + distributed. Sharding a sparse tensor across devices interacts non-trivially with the sparsity pattern. If the nonzeros aren't distributed evenly, you get load imbalance. If you partition by nonzero count, you lose the spatial structure that made the sparsity useful. This is open even for static patterns and wide open for dynamic ones.

  6. Sparse + quantized. As above, but with different metadata (scales, zero-points). Production systems implement specific combinations (e.g., GPTQ + 2:4) as bespoke kernels. A compositional theory is missing.

  7. Verification of sparse code. Sparse kernels are notoriously bug-prone (off-by-one in level traversal, missed cancellations, format conversion errors). Verified sparse code generation would be a real contribution. TACO has some correctness arguments; full verification is open. (This connects directly to memo 5.)

  8. Sparsity-aware AD as a first-class transformation. Currently AD systems either drop sparsity (Zygote) or handle it with special cases (PyTorch's sparse autograd). A from-scratch design where AD propagates sparsity types through the backward pass is missing.


10. Recommendation for an AI-native language design

Concrete positions:

The thesis: sparsity is currently fragmented because we lack a type-level abstraction over "structure on a tensor." TACO solved this for one specific kind of structure (storage format). Everything since has been point solutions for other kinds (hardware, routing, low-rank). A from-scratch language can unify them via refinement-typed tensors with a predicate-based structure language.


11. References

Sparse tensor compilation foundations

MoE and dynamic sparsity

Differentiable sparsity

Sparse attention

Low-rank methods

Hardware structured sparsity

Quantization (composes with sparsity)

Adjacent — refinement types and structure


12. Connections to the rest of the series

13. Next memo

Design Space 04: Distributed Programming Model. Modern training and inference run across hundreds to thousands of devices. The current model (MPI primitives, all-reduce, pipeline/tensor/data parallelism, ZeRO) is cargo-culted from HPC and bolted onto framework code. The question for an AI-native language: should sharding be in the type system, what does a mesh look like as a language construct, and how does this compose with shapes (memo 1), effects (memo 2), and sparsity (this memo)?