fastumap
A UMAP implementation in pure NumPy and SciPy, built so that importing it costs milliseconds rather than seconds.
| cold import | added to image | |
|---|---|---|
import umap (umap-learn) |
~21 s | ~172 MB |
import fastumap |
~12 ms | 0 MB |
umap-learn's kernels are compiled by numba with LLVM the first time the package is imported — seconds on a laptop, minutes on a small CPU-throttled container. NumPy and SciPy are equally compiled, but they ship their machine code precompiled in the wheel, so they load immediately. fastumap reimplements the UMAP algorithm on top of them: the same mathematics, with nothing left to compile at import time.
Installation
pip install fastumap # NumPy and SciPy only
pip install fastumap[accel] # adds the optional native accelerator (see below)
pip install fastumap[ann] # adds an approximate kNN backend for large inputs (see below)
Usage
from fastumap import umap_project, spectral_project
xy = umap_project(x, 2) # (n, 2)
xyz = umap_project(x, 3) # (n, 3)
cos = umap_project(x, 2, metric="cosine") # text / CLS embeddings
metric="cosine"is recommended for encoder embeddings; Euclidean distance on unnormalised vectors is dominated by magnitude rather than the direction that carries meaning.pca_dim=100pre-reduces very wide inputs (e.g. 1024-dimensional embeddings) before the nearest-neighbour search. Neighbour overlap is preserved to within ~0.01. Off by default.
umap_project also accepts n_neighbors, min_dist, spread, n_epochs,
negative_sample_rate, random_state, and chunk_count.
Quality relative to umap-learn
fastumap stays within 0.01–0.02 neighbour overlap of umap-learn and is marginally ahead on
global structure. Measured on MNIST (784-dimensional, single-threaded, make bench):
| n | method | wall time | overlap@15 | global corr |
|---|---|---|---|---|
| 5000 | fastumap | 26 s | 0.333 | 0.310 |
| 5000 | umap-learn | 73 s | 0.344 | 0.329 |
| 10000 | fastumap | 72 s | 0.267 | 0.279 |
| 10000 | umap-learn | 83 s | 0.274 | 0.286 |
| 20000 | fastumap | 110 s | 0.188 | 0.323 |
| 20000 | umap-learn | 31 s | 0.205 | 0.316 |
fastumap is faster at 5k and 10k and slower at 20k, where its exact O(n²) neighbour search
becomes the bottleneck (pip install fastumap[ann] addresses this — see below). umap-learn's times
reuse the numba compilation from its first fit — a fresh process pays roughly 20 s of
compilation on every invocation. Raising chunk_count (default 1) recovers most of the
remaining local-overlap gap at proportional cost and stays 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 versus after.
Performance characteristics
At 1024 dimensions and n=5000, fastumap is roughly 2× slower per call than umap-learn (~40 s versus ~20 s). This is inherent rather than a missing optimisation: the layout SGD dominates runtime, and a vectorised NumPy SGD cannot match numba's compiled in-place optimiser. fastumap is therefore the appropriate choice when import and cold-start cost dominate, and less so when per-call latency on large, high-dimensional batches is the constraint. The optional accelerator narrows this gap.
On a CPU-throttled container (a cgroup CFS quota below the host core count — Fargate, ECS, Lambda), fastumap caps its BLAS thread pool to the quota automatically. Otherwise an unconstrained pool sized to the host's core count oversubscribes the quota and thrashes under concurrent load: measured at 0.5 vCPU with four worker processes, per-call time is ~70 s uncapped versus ~43 s capped (≈1.6× faster). No configuration is required, and nothing is capped on an unconstrained host.
Server usage
umap_project is thread-safe — it holds no module-level mutable state and seeds a fresh RNG
per call — so it may be called from a worker thread (await asyncio.to_thread(umap_project, x, 2)).
For long-running services, avoid recomputing the full layout on every request. Fit once and place new points into the existing layout:
from fastumap import fit, transform
model = fit(window, 2) # UMAPModel; picklable, cache it
xy = transform(model, pts) # inexpensive; the layout stays fixed across requests
transform approximates a full refit, retaining roughly 72% of the local overlap that
refitting would produce while keeping the embedding stable across requests. Refit when the
input distribution shifts; a practical trigger is the distance from new points to their nearest
training neighbour rising to 2–3× the training mean. A cached 5000×1024 model occupies about
20 MB (training data stored as float32). Rolling windows and sparse input are not supported —
densify sparse input first, and refit when the window slides.
The optional accelerator
fastumap-accel is a small Rust reimplementation of the SGD, distributed as a separate abi3
wheel (Python 3.11+; x86_64 and aarch64/Graviton, with an sdist for other platforms). When it
is installed, fastumap uses it automatically; the base package remains pure NumPy and SciPy and
serves as the fallback. It is roughly 1.7–1.9× faster with slightly higher overlap.
Because it performs umap-learn's true in-place walk rather than the fallback's per-epoch approximation, it produces a different — higher-quality — layout for the same seed. Query the active path with:
import fastumap
fastumap.accelerator_active() # True if the native kernel is installed and will be used
Large inputs (approximate kNN)
The exact neighbour search is O(n²) and dominates runtime above ~20k points. pip install fastumap[ann] adds an approximate backend — faiss HNSW, which ships
prebuilt wheels (Linux x86_64/aarch64, macOS, Windows), so it installs without a compiler.
xy = umap_project(x, 2, knn="auto") # default: exact for small n, approximate above 16k
xy = umap_project(x, 2, knn="approx") # force approximate (needs fastumap[ann])
xy = umap_project(x, 2, knn="exact") # force the exact brute force
"auto" (the default) only switches to approximate when the extra is installed and n ≥
16384, so small inputs stay bit-identical. It's deterministic and keeps ≥0.86 neighbour recall
against exact. Measured on the neighbour search alone (256-dim):
| n | exact | approx | speedup | recall@15 |
|---|---|---|---|---|
| 20000 | 71 s | 29 s | 2.4× | 0.91 |
| 30000 | 142 s | 50 s | 2.8× | 0.86 |
fastumap.ann_available() reports whether the backend is installed.
For a given input and seed, output is bit-identical across processes and machines within one
environment. Two optional packages change the layout, so reproducibility across environments
requires pinning them: fastumap-accel (a different optimiser) and fastumap[ann] (an
approximate neighbour graph above 16k points). Treat them as part of the environment's
dependency set; accelerator_active() and ann_available() report which paths a run used, so a
stored projection can record how it was produced.
Guarantees
Each is enforced by a test:
- No JIT or compiled stack at runtime — NumPy, SciPy, and the small pure-Python
threadpoolctl; never numba, llvmlite, scikit-learn, or first-party compiled code. - Import under 200 ms, deterministic (bit-identical) output, and thread-safe.
- Bounded memory — the full n×n distance matrix is never materialised (blocked kNN); under 200 MB at 5000×1024.
- 2-D and 3-D first-class; fully type-checked under pyright strict.
Development
make check # lint, type-check, and tests
make tox # the suite across Python 3.11, 3.12, and 3.13
make fargate # import and fit timing under a constrained CPU cap (docker --cpus=0.5 --memory=2g)
License and attribution
fastumap is an independent reimplementation of the UMAP algorithm (McInnes, Healy & Melville, arXiv:1802.03426). It is not affiliated with or endorsed by the UMAP authors and is not a drop-in replacement; the public API is intentionally small. MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastumap-0.1.20.tar.gz.
File metadata
- Download URL: fastumap-0.1.20.tar.gz
- Upload date:
- Size: 41.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06e2f5c091ebe29a5e9df782e34548dd422a20af82aef926027c548500518fc1
|
|
| MD5 |
3c32591177530d3348e2cd6fa9115eba
|
|
| BLAKE2b-256 |
462ad5eb9ea7b8ea25c172d3783549597ca7990259b6134860306546a602dc2c
|
File details
Details for the file fastumap-0.1.20-py3-none-any.whl.
File metadata
- Download URL: fastumap-0.1.20-py3-none-any.whl
- Upload date:
- Size: 27.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b27b3fdb8817dd3afaa44206a4cb045ce884bb5cb9f71dbf848fdced13ebf8db
|
|
| MD5 |
89254bd74125d8dca57350d1b0fffb20
|
|
| BLAKE2b-256 |
a1541161c920aa71a764dfb9cabdcc1ef48d874d4abb9b2a5501427aed4085da
|