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.15.tar.gz (35.8 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.15-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fastumap-0.1.15.tar.gz
  • Upload date:
  • Size: 35.8 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.15.tar.gz
Algorithm Hash digest
SHA256 9b2f3a85a511af422496fa707ebb4bae4d10d8b723cd5dcff0a66245c855da13
MD5 f6d1f98554c865c205a1bb0102e41f3a
BLAKE2b-256 133b33e98e90a75ff61dd0b39907161ff59d7844fba355935a07aa1c03e1bc6c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fastumap-0.1.15-py3-none-any.whl
  • Upload date:
  • Size: 23.6 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.15-py3-none-any.whl
Algorithm Hash digest
SHA256 a03b50bc8f93f0af920dac9f8742ee6564c28a2d7c12243412c07f321bec8ec3
MD5 501678dac66fcd34d6a4bc88a7535c8f
BLAKE2b-256 612381107edd701b0c3a9dbf55ddec6bc8cfcf5c4f1d4861313442b5cd7d6401

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

0.1.16

2 files

This release

0.1.15 This release

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