Skip to main content

fastumap

I kept needing to drop a pile of embeddings into 2-D for a quick plot, and every time I reached for umap-learn the import alone took about twenty seconds — almost all of it numba compiling its kernels with LLVM. So I wrote the same UMAP in plain numpy and scipy. It imports in a few milliseconds.

cold import added to your image
import umap (umap-learn) ~21 s ~172 MB
import fastumap ~12 ms 0 MB

numpy and scipy are every bit as compiled as numba — they just ship the machine code precompiled in the wheel, so they load instantly. numba compiles on your machine, the first time you import it, and on a small or CPU-throttled box that stretches from seconds into minutes. That's the only thing fastumap really changes: same UMAP math, nothing left to compile when it loads.

Using it

pip install fastumap        # just numpy + scipy
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")    # for text / CLS embeddings
  • Use cosine for encoder embeddings — plain euclidean is dominated by vector length, not the direction that carries the meaning.
  • For very wide inputs (say 1024-dim), pca_dim=100 pre-reduces before the nearest- neighbour search: overlap barely moves and it's cheaper. Off by default.

The same input and seed give bit-identical output every time, across processes — so a plot you regenerate tomorrow looks the same as today's.

How close is it to umap-learn?

Close on local neighbourhoods, a little ahead on global structure. Here's what I get on MNIST (784-dim, one thread, make bench):

n wall 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

A few honest notes:

  • It wins on wall-clock at 5k and 10k and loses at 20k, where the brute-force neighbour search starts to hurt (a faster approximate one is on my list). PCA trails far behind on local structure — a linear method can't fold the space the way UMAP does.
  • umap-learn's times above reuse the numba compile from its first fit; a fresh process pays that ~20 s every single time, which is the cost I was trying to get rid of.
  • If you want the last bit of local overlap back, chunk_count=10 recovers most of it (slower, and still deterministic).

overlap@15 = how many of each point's 15 input-space neighbours are still neighbours after the projection. global corr = how well all the pairwise distances survive (Spearman).

In a long-running server

It's safe to call from a worker thread — a fresh RNG per call and no module-level state — so await asyncio.to_thread(umap_project, x, 2) is fine.

You usually don't want to refit the whole layout on every request. Fit once, then drop new points into the layout you already have:

model = fit(window, 2)          # cache this — it pickles
xy    = transform(model, pts)   # cheap, and the picture stays put between requests

That transform is an approximation of a full refit — it keeps about 72% of the local overlap you'd get by refitting — so it's great for placing in-distribution points, and worth a real refit once the data drifts. (A good "time to refit" signal: watch how far new points land from their nearest training neighbour; 2–3× the usual distance means you've drifted.) A cached 5000×1024 model is about 20 MB. Rolling windows ("add these, drop the old ones") and sparse input aren't supported — densify first, and refit when the window slides.

The honest catch

Per call, at 1024-dim and n=5000, fastumap is about 2× slower than umap-learn (~40 s vs ~20 s). That isn't a missing optimisation — a vectorised numpy SGD just can't match numba's in-place inner loop. So this is the right tool when the import / cold-start cost is what hurts, and the wrong one when you're pushing big high-dimensional batches through it all day.

That gap is also why there's an optional native piece: I rewrote just the SGD in Rust.

pip install fastumap[accel]     # adds the compiled kernel; base stays pure numpy + scipy

It compiles once into a small wheel — no runtime JIT, so the fast import stays — and runs about 1.7–1.9× faster with slightly better overlap. fastumap picks it up automatically when it's installed; without it you get the pure numpy path. Wheels are prebuilt for x86_64 and aarch64 (Graviton).

What I made sure of (there's a test for each)

  • Nothing but numpy and scipy at runtime — no numba, no LLVM, no compiled code of my own.
  • Imports in under 200 ms, deterministic to the byte, safe across threads.
  • Never builds the full n×n distance matrix, so memory stays bounded even at 5000×1024.
  • 2-D and 3-D both work, and the whole thing is type-checked (pyright strict).

Trying it locally

make check     # lint, type-check, tests
make tox       # run the suite on Python 3.11 / 3.12 / 3.13
make fargate   # time import + fit under a tight CPU cap (docker --cpus=0.5 --memory=2g)

About UMAP

This is an independent reimplementation of the UMAP algorithm (McInnes, Healy & Melville, arXiv:1802.03426) — not affiliated with or endorsed by its authors, and not a drop-in replacement; the API here is deliberately 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

fastumap-0.1.16.tar.gz (36.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastumap-0.1.16-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file fastumap-0.1.16.tar.gz.

File metadata

  • Download URL: fastumap-0.1.16.tar.gz
  • Upload date:
  • Size: 36.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

Hashes for fastumap-0.1.16.tar.gz
Algorithm Hash digest
SHA256 95e4f8d0772b19de7a017d1fe6a6f2ea5a44d7a1298832ab4137a5a2f930c62c
MD5 4e8424869e423af36f1f097794ce0ac5
BLAKE2b-256 3f7125983bf363ff53cedb7bcdb1d616bc7f987f1d52cd6cd986a1dd25084999

See more details on using hashes here.

File details

Details for the file fastumap-0.1.16-py3-none-any.whl.

File metadata

  • Download URL: fastumap-0.1.16-py3-none-any.whl
  • Upload date:
  • Size: 23.7 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

Hashes for fastumap-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 c14f4d3a17c83f66973aed6cb5bf24cebb39fad93070f6f68fff894911ca497f
MD5 dd5b93fa59f7495159e9583b764ca177
BLAKE2b-256 c5be07c5746bc442b04d0ffcaca6ff6940cf30b3192ea3fd4032cf8d82db76fb

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.29

1 file

0.2.28

1 file

0.2.27

6 files

0.2.26

1 file

0.2.20

6 files

0.2.19

6 files

0.2.18

6 files

0.2.17

6 files

0.2.16

5 files

0.2.15

5 files

0.2.14

5 files

0.2.13

5 files

0.2.12

5 files

0.2.11

5 files

0.2.10

5 files

0.2.9

5 files

0.2.8

5 files

0.2.7

5 files

0.2.6

3 files

0.2.5

3 files

0.2.4

3 files

0.2.3

3 files

0.2.2

3 files

0.2.1

3 files

0.2.0

2 files

0.1.26

2 files

0.1.25

2 files

0.1.24

2 files

0.1.23

2 files

0.1.22

2 files

0.1.21

2 files

0.1.20

2 files

0.1.19

2 files

0.1.18

2 files

This release

0.1.16 This release

2 files

0.1.15

2 files

0.1.14

2 files

0.1.13

2 files

0.1.12

2 files

0.1.11

2 files

0.1.10

2 files

0.1.9

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page