Speed and quality¶
Import and cold-start cost are where fastumap differs; per-call latency is the trade-off. Every
table on this page is regenerated by make bench-report (see Benchmark reports).
fastumap stays within 0.01 neighbour overlap of umap-learn. Measured on MNIST (784-dimensional,
seed 42). These numbers are generated rather than transcribed: make bench-report regenerates them
into a dated report under
docs/bench/, which records the
machine, the load average and every package version they were measured on.
| n | method | wall time | overlap@15 | global corr |
|---|---|---|---|---|
| 5000 | fastumap | 22 s | 0.341 | 0.320 |
| 5000 | umap-learn | 79 s | 0.344 | 0.329 |
| 5000 | pca | 87 s | 0.058 | 0.523 |
| 10000 | fastumap | 46 s | 0.271 | 0.361 |
| 10000 | umap-learn | 50 s | 0.268 | 0.387 |
| 10000 | pca | 120 s | 0.038 | 0.487 |
| 20000 | fastumap | 57 s | 0.207 | 0.358 |
| 20000 | umap-learn | 69 s | 0.204 | 0.368 |
| 20000 | pca | 237 s | 0.024 | 0.504 |
The quality columns are the comparable ones. overlap@15 and global corr are deterministic; the wall times came from a loaded machine (recorded in the report) and are inflated for every method. PCA is included as a control: it preserves global structure best and local structure worst, which is the trade-off UMAP addresses.
umap-learn's times reuse the numba compilation from its first fit in the same process. A fresh
process pays roughly 20 s of compilation on every invocation, which fastumap does not pay. Above
about 50,000 points fastumap's exact O(n²) neighbour search becomes the bottleneck;
pip install fastumap[ann] addresses that, described below. Raising chunk_count (default 1)
recovers most of the remaining local-overlap gap at proportional cost, and remains deterministic.
overlap@15 is the fraction of each point's 15 input-space neighbours retained after projection, read against a random baseline. global corr is the Spearman correlation of all pairwise distances, before against after.
Import and cold-start cost are where fastumap differs. Per-call latency is close to umap-learn rather than better than it.
- The native kernel ships by default, so per-call speed on large batches is close to umap-learn. It runs umap-learn's in-place SGD, in Rust.
- The numpy fallback, which only an unbuilt source checkout uses, is roughly 2× slower at 1024 dimensions and n=5000 (about 40 s against 20 s). A vectorised numpy SGD cannot match numba's compiled loop.
fastumap is the appropriate choice when import and cold-start cost dominate. On per-call latency for large, high-dimensional batches it is roughly even with umap-learn on the kernel path.
BLAS thread cap under a CPU quota¶
Some containers cap CPU (docker --cpus, Kubernetes and EKS limits, EC2 cgroups) while still
reporting the host's full core count. BLAS then starts more threads than the quota supports and
thrashes under load.
fastumap caps the BLAS pool to the quota automatically, with no configuration. Measured under
docker --cpus=0.5 on a 16-core host with 4 workers, per-call time drops from about 70 s to about
43 s (roughly 1.6×). Where no quota is visible, nothing is capped.
This is a no-op on AWS Fargate, which meters CPU outside the cgroup, leaving the quota invisible. Fargate also reports a low core count, so BLAS does not oversubscribe there in the first place.
Next¶
- The neighbour search is the bottleneck at scale: Large inputs.
- Where the speed comes from: The native accelerator.