In a server¶
umap_project is thread-safe. It holds no module-level mutable state and seeds a fresh RNG per
call, so it can be called from a worker thread (await asyncio.to_thread(umap_project, x, 2)).
Recomputing the whole layout on every request is unnecessary. Fit once, then place new points into the existing layout:
from fastumap import fit, transform
from fastumap.projection import UMAPModel
model = fit(window, 2) # cache it
xy, fit_distance = transform(model, pts, return_distances=True) # coords + per-point fit
model.to_npz("layout.npz") # persist across restarts (versioned)
model = UMAPModel.from_npz("layout.npz")
transformplaces new points without a refit. It retains about 72% of a full fit's local overlap, and the layout stays stable across requests.return_distances=Truereturns each point's distance to its nearest training neighbour, which serves as a fit score. A point 2 to 3× further out than the training mean is an extrapolation. A rising batch mean indicates that a refit is due.to_npzandfrom_npzpersist the model in a versioned numpy format. It survives releases, where a rawpicklewould break on any dataclass change.init=previousis an alternative:umap_project(window, 2, init=previous)reuses the previous coordinates so carried-over points start where they were, and new rows are placed by the caller. It refreshes a view without the fit/transform split.
A cached 5000×1024 model is about 20 MB, with training data stored as float32. Rolling windows and sparse input are not supported: densify sparse input first, and refit when the window slides.
Next¶
- What stays identical across processes and machines: Guarantees.
- Thread behaviour under a CPU quota: Speed and quality.