npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

umap-web

v0.2.0

Published

Standalone TypeScript implementation of UMAP for browsers and Node.js — memory-efficient CPU backend and WebGPU backend for large datasets. Functional parity with umap-learn.

Readme

umap-web

A standalone, production-grade TypeScript implementation of UMAP for browsers and Node.js — functionally at parity with Python's umap-learn (pinned 0.5.9.post2), with a memory-disciplined CPU backend (typed arrays + worker pool) and a WebGPU backend for large datasets. Zero runtime dependencies.

Built for privacy-preserving, in-browser dimensionality reduction: your data never leaves the device. Everything — kNN construction, fuzzy graph, spectral init, SGD layout — runs locally, from a static page if you like.

Quickstart (Node)

import { UMAP } from 'umap-web';

const umap = new UMAP({ nNeighbors: 15, minDist: 0.1, seed: 42 });
const embedding = await umap.fitTransform({
  data: myFloat32Array, // row-major
  rows: nSamples,
  cols: nFeatures,
});
// embedding: { data: Float32Array, rows, cols: 2 }

const more = await umap.transform(newPoints);   // project held-out rows
const blob = umap.serialize();                  // persist the fitted model
const restored = UMAP.deserialize(blob);        // ... and reload it elsewhere

number[][] inputs and CSR sparse inputs ({ data, indices, indptr, rows, cols }) work too. Supervision: await umap.fit(X, labels) (Int32Array; -1 = unlabeled) or a continuous target with targetMetric: 'l2'.

Quickstart (browser)

import { UMAP } from 'umap-web';

// CPU workers (no special headers needed — works on GitHub Pages):
const umap = new UMAP({ concurrency: 'auto', onEpoch: ({ embedding }) => draw(embedding) });

// WebGPU (Chrome/Edge on capable hardware):
const gpu = new UMAP({ backend: 'webgpu' });
const embedding = await gpu.fitTransform(X);
console.log(gpu.backendInfo); // { knn: 'webgpu', layout: 'webgpu' }

backend: 'auto' (default) picks WebGPU when an adapter exists and the configuration is GPU-supported, else CPU workers, else single-thread. Every run reports which backend actually executed each stage via umap.backendInfo.

  • Workers without COOP/COEP: SharedArrayBuffer is used when available (crossOriginIsolated or Node); otherwise a first-class transfer-fallback path runs (that is what executes on GitHub Pages).
  • Node GPU (device injection): pass a GPUDevice (e.g. from a Dawn binding such as the webgpu npm package) via the device option; the backend uses it instead of navigator.gpu. Non-Chromium GPU stacks are best-effort.

Workers & bundlers

The CPU worker ships as a single self-contained ESM file (umap-web/worker, 31.5 KB raw / 8.2 KB gzip — zero bytes added to your main bundle) and is spawned with the inline new Worker(new URL('./worker.js', import.meta.url), { type: 'module' }) pattern that bundlers detect statically:

  • webpack 5, Vite (build), Rollup: zero config — the worker is emitted as an asset automatically.

  • Vite (dev server): esbuild's dependency pre-bundling breaks the pattern. Either add optimizeDeps: { exclude: ['umap-web'] } to vite.config, or pass the URL explicitly:

    import workerUrl from 'umap-web/worker?worker&url';
    const umap = new UMAP({ concurrency: 'auto', workerUrl });
  • esbuild / anything that doesn't rewrite worker URLs: copy node_modules/umap-web/dist/worker.js next to your bundle (it has no imports, so a plain copy works), or point workerUrl at wherever you host it.

  • CDN / import maps: browsers refuse cross-origin workers, so umap-web automatically re-fetches the worker and spawns it from a blob: URL. With a CSP, same-origin needs worker-src 'self'; the CDN path needs worker-src blob:.

  • Node: worker_threads are resolved from the installed package automatically; workerUrl accepts a file:// URL if you relocate the file.

If no worker can be spawned (bad URL, CSP, exotic bundling), fit never hangs: the pool gives up after a 10 s init timeout, a warning explains what failed and points here, and the run completes single-threaded — check umap.backendInfo.layout. An explicit workerUrl is trusted as-is: if it fails, umap-web warns and goes single-threaded rather than guessing other locations.

Parity philosophy

"Looks right" is not a spec. This library is validated against umap-learn 0.5.9.post2 by a two-layer oracle harness:

  1. Deterministic stage parity — every deterministic pipeline stage (metrics, smooth_knn_dist, membership strengths, fuzzy set operations, supervised intersections, make_epochs_per_sample, spectral eigen-invariants, the transform stage) is compared against artifacts generated by the pinned Python package on the same inputs (tolerances: atol 2e-4 / rtol 1e-3 for float32-derived quantities).
  2. Statistical embedding parity — final embeddings are compared against bands generated from 8-seed Python runs (trustworthiness, continuity, kNN-label-accuracy, Spearman of pairwise-distance ranks, Procrustes disparity). The bar: a umap-web embedding must be indistinguishable from another umap-learn run. 14 parity cells (PARITY.md) cover defaults, cosine/sparse inputs, supervision, densMAP, haversine output, transform, inverseTransform, multi-component spectral placement, and AlignedUMAP — on CPU and WebGPU backends.

Where the pinned wheel's executed behavior deviates from its source text (e.g. numba fastmath folding in smooth_knn_dist), umap-web matches the executed oracle; every such case is documented in DECISIONS.md.

GPU support matrix

| Feature | WebGPU | Notes | |---------|--------|-------| | kNN metrics | euclidean, sqeuclidean, cosine, correlation, manhattan, hamming, jaccard | others fall back to CPU with a logged notice | | k (nNeighbors) | ≤ 32 | larger k falls back to CPU | | Layout | euclidean output | fixed-point atomicAdd SGD (scale 2^20) | | densMAP / non-euclidean output / supervision graph ops | CPU | layout falls back per-stage | | transform / inverseTransform | CPU | GPU-fitted models build the search structure lazily on first transform() |

All kernels run under default WebGPU limits (≤ 8 storage buffers per stage, 16 KB workgroup memory) and request elevated buffer limits when available; candidate streaming keeps every dispatch watchdog-safe and no n×n buffer ever exists.

GPU failures are typed, never silent: buffer sizes are validated against the granted device limits before allocation, kernels run under error scopes, and device loss is tracked — all surfacing as UmapBackendError. Under backend: 'auto' a GPU failure falls back to the CPU for that stage (seeded results stay reproducible; the rng is restored to its pre-stage state); with explicit backend: 'webgpu' the typed error propagates. Device acquisition is capped at 5 s so a stalled driver cannot hang fit().

Performance (Apple M5 Pro, Chromium/Metal — see BENCHMARKS.md)

| Workload | Backend | Time | |----------|---------|------| | digits 1,797×64 | CPU single-thread | 4.5 s | | MNIST 70,000×784 | CPU workers | 24 s | | MNIST 70,000×784 | WebGPU | 7.8 s | | transform 10k onto 60k model | CPU | 5.3 s | | synthetic 500,000×128 | WebGPU | 78 s | | synthetic 1,000,000×256 | WebGPU | 5.5 min |

API surface

  • UMAPfit, fitTransform, transform (+ 'graph' mode), inverseTransform, update, serialize/UMAP.deserialize, embedding, graph, densities, backendInfo, phaseTimings. Full option set mirrors umap-learn (camelCase).
  • AlignedUMAP — aligned embeddings over dataset slices with relations, alignmentRegularisation, alignmentWindowSize; fit, fitTransform, update.
  • umap-web/core — the low-level functional layer (nearestNeighbors*, smoothKnnDist, computeMembershipStrengths, fuzzySimplicialSet, makeEpochsPerSample, findABParams, initTransform/initGraphTransform internals, spectral/LOBPCG, metrics, CSR helpers, PCG32).
  • umap-web/webgpu — GPU device management + kernels for advanced integration.

Typed errors: UmapValidationError (bad input incl. NaN/Infinity — never silently filtered), UmapBackendError (worker/GPU failures), both extending UmapError; plus AbortError via standard AbortSignal:

const controller = new AbortController();
umap.fit(X, null, { signal: controller.signal }); // controller.abort() stops mid-epoch

fit, fitTransform, transform, inverseTransform, update, and AlignedUMAP.fit/fitTransform/update all accept { signal }; layout aborts per-epoch, GPU kNN per chunk, other phases at stage boundaries. An aborted or failed fit releases its workers/GPU device and restores the previously fitted model (a first fit stays unfitted) — the estimator is never left half-fitted.

Serialization (serialize/UMAP.deserialize) is a single versioned binary blob, little-endian on every platform. deserialize treats the blob as untrusted: every section is bounds- and type-checked and cross-array invariants are validated, so a corrupt or crafted blob throws UmapValidationError instead of producing garbage.

Determinism (§ reproducibility): with seed set and concurrency: 1, two runs are byte-identical (PCG32 with published test vectors; Math.random is banned in the source tree). Parallel CPU and WebGPU paths are documented non-deterministic; deterministic: true forces the reproducible path.

Demo

apps/demo — a static Vite app (GitHub Pages-ready, base: '/umap-web/') with a WebGL2 point renderer, dataset picker (digits / mnist10k / your own CSV — parsed locally), live per-epoch animation, backend selector with actual-backend indicator, phase-timing HUD, and CSV export.

Roadmap (deliberate non-goals today)

  • ParametricUMAP (needs a neural-network framework)
  • umap.plot (the demo app plays this role)
  • scikit-learn API compatibility / estimator plumbing
  • WASM backend (the Backend interface is designed so one can slot in)
  • GPU NN-descent (brute-force GPU kNN is fast through ~1M×256; see BENCHMARKS.md)

License

BSD-3-Clause. Reimplements the algorithms of umap-learn and pynndescent (see NOTICE).