Migrating from TorchSparse¶
Torch Lattice started from MIT HAN Lab’s TorchSparse codebase, but it is not a pure drop-in rename. The fork keeps the useful CUDA sparse operator foundation while tightening semantics for two goals: reliable Torch/CUDA training and stable artifact export for MLX/Metal replay.
The practical rule is simple: migrate model intent, not just class names. Original TorchSparse often encoded intent through historical defaults. Torch Lattice makes that intent explicit in module names and artifact operations.
What remains familiar¶
The authoring model is still close to TorchSparse:
sparse tensors pair coordinates with feature rows;
modules are normal
torch.nn.Moduleobjects;features are dense Torch tensors with shape
(N, C);coordinates use integer sparse rows and batch identity is part of each row;
CUDA kernels cover sparse convolution, pooling, hashing, voxelization, devoxelization, coordinate query, and dense materialization;
models train with standard PyTorch optimizers, losses, autocast policies, and state dicts.
What is intentionally different¶
Area |
Original TorchSparse behavior |
Torch Lattice behavior |
|---|---|---|
Package identity |
Imported as |
Imported as |
Deployment target |
CUDA runtime is the primary endpoint. |
CUDA is the training/export endpoint; MLX Lattice is the deployment endpoint for exported artifacts. |
Convolution intent |
Some stride-1 spatial |
Support behavior is explicit: |
Artifact export |
No stable MLX artifact contract. |
Exports |
Graph capture |
Model execution is normal PyTorch; export was not a first-class contract. |
Export uses explicit FX lowering and records graph topology, parameters, and sparse op semantics. |
Tooling |
Examples and scripts were mostly TorchSparse-specific. |
Benchmarks, fuzz fixtures, E2E fixtures, and migration checks are workspace scripts with comparable MLX-side outputs. |
Convolution mapping¶
The most important migration step is choosing the correct sparse support
semantics. Do not mechanically replace every old torchsparse.nn.Conv3d with
torch_lattice.nn.Conv3d.
Original TorchSparse usage |
Torch Lattice replacement |
Reason |
|---|---|---|
|
|
Preserve the input coordinate support. |
|
|
Pointwise convolution does not expand spatial support. |
|
|
Strided forward convolution intentionally creates a downsampled support. |
Transposed sparse convolution |
|
Pick based on whether the operation consumes an existing relation or generates output support. |
Convolution at known output coordinates |
|
The target support is part of the caller’s graph state. |
A useful check while porting is to compare coordinate counts before and after a
layer. If the original layer was intended to keep exactly the same coordinate set,
it should normally become SubmConv3d in Torch Lattice.
Sparse tensor expectations¶
Torch Lattice documents the coordinate row convention as (batch, x, y, z).
This convention matters because crop, BEV conversion, hashing, kernel-map
construction, and artifact replay all assume that the first column is batch and
the remaining columns are spatial axes.
import torch
import torch_lattice as tl
coords = torch.tensor(
[[0, 4, 8, 2], [0, 5, 8, 2]],
dtype=torch.int,
device='cuda',
)
feats = torch.randn(coords.shape[0], 16, device='cuda')
x = tl.SparseTensor(coords=coords, feats=feats, stride=1)
When migrating data pipelines, verify these invariants first:
coordinate dtype is integer and coordinate shape is
(N, 4);feature shape is
(N, C);coordinate row
iowns feature rowi;batch IDs are not stored separately from coordinates;
transforms that crop or reshape sparse data operate on spatial columns, not the batch column.
Import and module namespace mapping¶
Original pattern |
Torch Lattice pattern |
|---|---|
|
|
|
|
|
|
ad-hoc benchmark/example scripts |
|
implicit export conventions |
|
The top-level names are intentionally close enough for migration, but the new artifact and conformance packages are part of the supported workflow. Prefer using those over carrying old local scripts forward.
Artifact export differences¶
A Torch Lattice model can be trained in PyTorch and exported as a lattice artifact
for MLX replay. The artifact is not a serialized torch.nn.Module and should
not be treated as a Torch checkpoint.
File |
Meaning |
|---|---|
|
Sparse graph structure, operation names, attributes, inputs, and outputs. |
|
Named parameter tensors consumed by the graph. |
metadata |
Loader-facing artifact identity and IO bookkeeping. |
For exportable models, avoid depending on Python control flow that FX cannot see from example inputs. Use explicit modules, stable state-dict names, and supported sparse ops. Branches, adds, cats, pooling, activations, and supported convolution families should be represented as graph operations rather than hidden side effects.
Backend and configuration differences¶
Torch Lattice keeps CUDA dataflow controls for performance work, but those flags are not semantic knobs. Implicit GEMM, Fetch-on-Demand, and Gather-Scatter must compute the same sparse result for a fixed relation and weight tensor within normal floating-point tolerance.
When porting tuned models or scripts:
keep backend tuning as a performance step after correctness is established;
do not rely on a specific dataflow to change sparse support;
keep
torch_lattice.backends.hash_rsv_ratiochanges close to the workload that requires them;run migration/conformance checks after changing kernel-map or convolution configuration.
Validation workflow¶
Use three levels of validation while migrating:
Layer-level semantic checks: verify coordinate support before and after each migrated convolution or pooling layer.
Migration compatibility checks: run the migration CLI against the covered original TorchSparse subset.
Artifact replay checks: export fixtures on CUDA and replay them with MLX Lattice, then inspect absolute and relative error distributions.
Commands:
uv run migration all --device cuda
uv run e2e-fixtures --device cuda --archive /tmp/lattice-e2e.tar.gz
uv run fuzz --cases 32 --device cuda --archive /tmp/lattice-fuzz.tar.gz
On the MLX side, replay the exported archive:
uv run conformance replay /tmp/lattice-fuzz.tar.gz \
--report /tmp/lattice-fuzz-report.json
Common migration mistakes¶
Symptom |
Likely cause |
|---|---|
Coordinate count grows after a layer that should be support-preserving. |
A legacy stride-1 spatial convolution was mapped to |
MLX replay cannot resolve a weight name. |
The exported model used unstable module naming or mutated parameters outside the traced module state. |
CUDA and MLX outputs differ only at small floating-point scale. |
Accumulation order differs across CUDA and Metal. Check percentile error statistics before treating this as semantic drift. |
Crop or BEV output uses the wrong axis. |
The data pipeline is treating the batch column as a spatial coordinate or using a mismatched coordinate convention. |
A benchmark script works but artifact export fails. |
The model path contains Python behavior that runs in eager mode but is not represented in the supported FX/lattice graph. |
What not to preserve¶
Do not preserve old local wrappers only to keep historical TorchSparse script shape. If a wrapper only hides whether an operation is support-preserving, support-generating, or target-aligned, remove it during migration. The long-term contract is explicit sparse semantics plus artifact replay, not maximum source compatibility with every old example.