feat: add MIND-small (distilled 6.4M student) weights + deploy graphs + card section
43bd70f verified | license: mit | |
| tags: [location-encoder, geospatial, earth-observation] | |
| # MIND | |
| Matryoshka Implicit Neural Distillation: a lat/lon location encoder distilled from four geospatial | |
| foundation models (AlphaEarth, Climplicit, GeoCLIP, SINR). Given a coordinate it returns an embedding | |
| with no imagery or labels at inference; the embedding is Matryoshka-structured, so a 64-d prefix is the | |
| deploy width. | |
| ## Files | |
| - `mind.safetensors` - recommended weights, fp16 (227 MB, safe / no-pickle). Load with `mind_standalone.load_mind`. | |
| - `mind.pt` - fp32 weights (454 MB) for exact reproduction. | |
| - `mind.onnx` (+ `mind.onnx.data`) - ONNX graph for onnxruntime / TensorRT (input `latlon` [N,2], dynamic batch). | |
| - `mind.pt2` - torch ExportedProgram; load with `torch.export.load`. | |
| - `mind_small.safetensors` / `mind_small.onnx` / `mind_small.pt2` - **MIND-small**, the distilled 6.4 M student (see below). | |
| ## Usage | |
| ```python | |
| from mind_standalone import load_mind, embed # github.com/taylor-geospatial/mind | |
| emb = embed(load_mind("mind.safetensors"), lats, lons) # [N, 64]; half=True for ~9x on GPU | |
| ``` | |
| Build a TensorRT engine from the ONNX with `trtexec --onnx=mind.onnx --saveEngine=mind.trt --fp16`. | |
| Code and paper: https://github.com/taylor-geospatial/mind | |
| ## MIND-small (distilled, deployable) | |
| A small student self-distilled from MIND's 128-d prefix: a width-1024, depth-6 ReSIREN (6.4 M params, | |
| **18x smaller**, ~18x fewer FLOPs) trained to match the teacher's first 128 trunk dims (the frozen | |
| teacher generates targets on sampled coordinates, so there is no dataset). It emits a **128-d** embedding. | |
| ```python | |
| from mind_standalone import load_mind, embed | |
| m = load_mind("mind_small.safetensors") | |
| emb = embed(m, lats, lons, dim=128, feature="head") # [N, 128]; the head output IS the embedding | |
| ``` | |
| - **Fidelity:** 0.99 cosine to the teacher's 128-d prefix. | |
| - **Downstream:** retains **~92%** of the teacher prefix's macro transfer on the spatial-CV linear-probe | |
| suite -- and *beats* the full 3072-d trunk, which overfits under spatial holdout. | |
| - **Throughput:** ~11x the teacher on GPU (TensorRT fp16: 29.5M pts/s on an H100) and ~14x on CPU | |
| (79k vs 5.6k pts/s on 16 cores), at ~half the memory. Makes CPU-only / edge inference practical. | |
| | | teacher (3072) | MIND-small (128) | | |
| |---|---:|---:| | |
| | params | 113.5 M | 6.4 M | | |
| | H100 TensorRT fp16 | 2.64M pts/s | **29.5M pts/s** | | |
| | 16-core CPU (torch) | 5.6k pts/s | **79k pts/s** | | |
| | cosine to teacher[:128] | 1.00 | 0.99 | | |
| ## Deployment: throughput & memory (which format to use) | |
| 500k coordinates, batch 65,536, full 3072-d trunk output. Inputs/outputs are GPU-resident (ONNX/TensorRT | |
| use `IOBinding`), so throughput reflects compute, not host<->device transfer. Peak memory is the NVML | |
| device maximum on the H100. | |
| | Format | Precision | A100 | H100 | Peak mem (H100) | | |
| |---|---|---:|---:|---:| | |
| | **ONNX -> TensorRT** | fp16 | 0.84M | **2.64M pts/s** | 5.5 GB | | |
| | torch (autocast) | fp16 | 0.70M | 1.99M pts/s | **3.8 GB** | | |
| | torch (torchao) | fp8 e4m3 | n/a | 0.93M pts/s | 6.2 GB | | |
| | ONNX Runtime (CUDA EP) | fp32 (TF32) | 0.39M | 0.91M pts/s | 10.6 GB | | |
| | torch (eager) | fp32 | 0.08M | 0.21M pts/s | 5.0 GB | | |
| | ExportedProgram (`.pt2`) | fp32 | 0.08M | 0.21M pts/s | 4.2 GB | | |
| **What to use** | |
| - **Max throughput / production serving -> ONNX + TensorRT fp16.** Fastest on both GPUs; pay a one-time | |
| engine build. | |
| - **PyTorch app -> torch fp16** (`half=True`). Lowest memory, zero extra deps, ~25% behind TensorRT. | |
| - **Outside Python / no TensorRT build -> ONNX Runtime (CUDA EP).** Portable and cross-language. | |
| - **Torch graph without the package -> ExportedProgram** (`torch.export.load("mind.pt2")`). | |
| - **Tight compute / CPU / edge -> MIND-small** (above): ~half the memory, an order of magnitude faster. | |
| Notes on precision: | |
| - **fp16 is the sweet spot**: ~1% change to the 64-d embedding, large speedup, no extra work. | |
| - **fp8 is not worth it here.** Off-the-shelf dynamic-activation fp8 (torchao, H100) ran *slower* than | |
| fp16 -- the model is small (113M params), so per-layer quant overhead isn't amortized -- and dropped | |
| trunk cosine to ~0.96 because the SIREN `sin` activations are quantization-sensitive. It would need | |
| quant-aware distillation to be competitive, and TensorRT fp16 already wins without it. | |
| - **Low-rank factorization does not help**: the 12 wide SIREN blocks are near-full-rank (they encode | |
| high-frequency content), so any FLOP-saving truncation wrecks the embedding. Distillation (MIND-small) | |
| is the compression that works. | |
| - The CUDA-EP "fp32" row uses TF32 tensor-core GEMMs (ORT default), hence ~5x over torch *eager* fp32. | |
| Reproduce with `scripts/bench_deploy.py` (A100/H100) in the repo. | |