umap-gpu 0.2.6 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,7 @@ export interface CPUSgdParams {
9
9
  * CPU fallback SGD optimizer for environments without WebGPU.
10
10
  * Mirrors the GPU shader logic: per-edge attraction + negative-sample repulsion.
11
11
  */
12
- export declare function cpuSgd(embedding: Float32Array, graph: FuzzyGraph, epochsPerSample: Float32Array, nVertices: number, nComponents: number, nEpochs: number, params: CPUSgdParams): Float32Array;
12
+ export declare function cpuSgd(embedding: Float32Array, graph: FuzzyGraph, epochsPerSample: Float32Array, nVertices: number, nComponents: number, nEpochs: number, params: CPUSgdParams, onProgress?: (epoch: number, nEpochs: number) => void): Float32Array;
13
13
  /**
14
14
  * CPU SGD for UMAP.transform(): optimizes only the new-point embeddings.
15
15
  * The training embedding is read-only; attraction pulls new points toward
@@ -26,4 +26,4 @@ export declare function cpuSgd(embedding: Float32Array, graph: FuzzyGraph, epoch
26
26
  * @param nEpochs - Number of optimization epochs
27
27
  * @param params - UMAP curve parameters
28
28
  */
29
- export declare function cpuSgdTransform(embeddingNew: Float32Array, embeddingTrain: Float32Array, graph: FuzzyGraph, epochsPerSample: Float32Array, nNew: number, nTrain: number, nComponents: number, nEpochs: number, params: CPUSgdParams): Float32Array;
29
+ export declare function cpuSgdTransform(embeddingNew: Float32Array, embeddingTrain: Float32Array, graph: FuzzyGraph, epochsPerSample: Float32Array, nNew: number, nTrain: number, nComponents: number, nEpochs: number, params: CPUSgdParams, onProgress?: (epoch: number, nEpochs: number) => void): Float32Array;
package/dist/gpu/sgd.d.ts CHANGED
@@ -25,6 +25,6 @@ export declare class GPUSgd {
25
25
  * @param params - UMAP curve parameters and repulsion settings
26
26
  * @returns Optimized embedding as Float32Array
27
27
  */
28
- optimize(embedding: Float32Array, head: Uint32Array, tail: Uint32Array, epochsPerSample: Float32Array, nVertices: number, nComponents: number, nEpochs: number, params: SGDParams): Promise<Float32Array>;
28
+ optimize(embedding: Float32Array, head: Uint32Array, tail: Uint32Array, epochsPerSample: Float32Array, nVertices: number, nComponents: number, nEpochs: number, params: SGDParams, onProgress?: (epoch: number, nEpochs: number) => void): Promise<Float32Array>;
29
29
  private makeBuffer;
30
30
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { fit, UMAP } from './umap';
2
- export type { UMAPOptions } from './umap';
2
+ export type { UMAPOptions, ProgressCallback } from './umap';
3
3
  export type { KNNResult, HNSWOptions, HNSWSearchableIndex } from './hnsw-knn';
4
4
  export type { FuzzyGraph } from './fuzzy-set';
5
5
  export { isWebGPUAvailable } from './gpu/device';
package/dist/index.js CHANGED
@@ -1,98 +1,98 @@
1
- var K = Object.defineProperty;
2
- var j = (n, e, a) => e in n ? K(n, e, { enumerable: !0, configurable: !0, writable: !0, value: a }) : n[e] = a;
3
- var S = (n, e, a) => j(n, typeof e != "symbol" ? e + "" : e, a);
4
- import { loadHnswlib as T } from "hnswlib-wasm";
5
- async function H(n, e, a = {}) {
6
- const { M: f = 16, efConstruction: o = 200, efSearch: p = 50 } = a, c = await T(), l = n[0].length, u = n.length, s = new c.HierarchicalNSW("l2", l, "");
7
- s.initIndex(u, f, o, 200), s.setEfSearch(Math.max(p, e)), s.addItems(n, !1);
8
- const t = [], i = [];
9
- for (let r = 0; r < u; r++) {
10
- const d = s.searchKnn(n[r], e + 1, void 0), _ = d.neighbors.map((g, h) => ({ idx: g, dist: d.distances[h] })).filter(({ idx: g }) => g !== r).slice(0, e);
11
- t.push(_.map(({ idx: g }) => g)), i.push(_.map(({ dist: g }) => g));
1
+ var H = Object.defineProperty;
2
+ var V = (t, e, i) => e in t ? H(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i;
3
+ var E = (t, e, i) => V(t, typeof e != "symbol" ? e + "" : e, i);
4
+ import { loadHnswlib as C } from "hnswlib-wasm";
5
+ async function Y(t, e, i = {}) {
6
+ const { M: a = 16, efConstruction: s = 200, efSearch: p = 50 } = i, f = await C(), h = t[0].length, c = t.length, o = new f.HierarchicalNSW("l2", h, "");
7
+ o.initIndex(c, a, s, 200), o.setEfSearch(Math.max(p, e)), o.addItems(t, !1);
8
+ const n = [], r = [];
9
+ for (let l = 0; l < c; l++) {
10
+ const d = o.searchKnn(t[l], e + 1, void 0), u = d.neighbors.map((g, _) => ({ idx: g, dist: d.distances[_] })).filter(({ idx: g }) => g !== l).slice(0, e);
11
+ n.push(u.map(({ idx: g }) => g)), r.push(u.map(({ dist: g }) => g));
12
12
  }
13
- return { indices: t, distances: i };
13
+ return { indices: n, distances: r };
14
14
  }
15
- async function V(n, e, a = {}) {
16
- const { M: f = 16, efConstruction: o = 200, efSearch: p = 50 } = a, c = await T(), l = n[0].length, u = n.length, s = new c.HierarchicalNSW("l2", l, "");
17
- s.initIndex(u, f, o, 200), s.setEfSearch(Math.max(p, e)), s.addItems(n, !1);
18
- const t = [], i = [];
19
- for (let d = 0; d < u; d++) {
20
- const _ = s.searchKnn(n[d], e + 1, void 0), g = _.neighbors.map((h, y) => ({ idx: h, dist: _.distances[y] })).filter(({ idx: h }) => h !== d).slice(0, e);
21
- t.push(g.map(({ idx: h }) => h)), i.push(g.map(({ dist: h }) => h));
15
+ async function J(t, e, i = {}) {
16
+ const { M: a = 16, efConstruction: s = 200, efSearch: p = 50 } = i, f = await C(), h = t[0].length, c = t.length, o = new f.HierarchicalNSW("l2", h, "");
17
+ o.initIndex(c, a, s, 200), o.setEfSearch(Math.max(p, e)), o.addItems(t, !1);
18
+ const n = [], r = [];
19
+ for (let d = 0; d < c; d++) {
20
+ const u = o.searchKnn(t[d], e + 1, void 0), g = u.neighbors.map((_, w) => ({ idx: _, dist: u.distances[w] })).filter(({ idx: _ }) => _ !== d).slice(0, e);
21
+ n.push(g.map(({ idx: _ }) => _)), r.push(g.map(({ dist: _ }) => _));
22
22
  }
23
- return { knn: { indices: t, distances: i }, index: {
24
- searchKnn(d, _) {
25
- const g = [], h = [];
26
- for (const y of d) {
27
- const w = s.searchKnn(y, _, void 0), U = w.neighbors.map((b, x) => ({ idx: b, dist: w.distances[x] })).sort((b, x) => b.dist - x.dist).slice(0, _);
28
- g.push(U.map(({ idx: b }) => b)), h.push(U.map(({ dist: b }) => b));
23
+ return { knn: { indices: n, distances: r }, index: {
24
+ searchKnn(d, u) {
25
+ const g = [], _ = [];
26
+ for (const w of d) {
27
+ const y = o.searchKnn(w, u, void 0), b = y.neighbors.map((M, x) => ({ idx: M, dist: y.distances[x] })).sort((M, x) => M.dist - x.dist).slice(0, u);
28
+ g.push(b.map(({ idx: M }) => M)), _.push(b.map(({ dist: M }) => M));
29
29
  }
30
- return { indices: g, distances: h };
30
+ return { indices: g, distances: _ };
31
31
  }
32
32
  } };
33
33
  }
34
- function C(n, e, a, f = 1) {
35
- const o = n.length, { sigmas: p, rhos: c } = I(e, a), l = [], u = [], s = [];
36
- for (let i = 0; i < o; i++)
37
- for (let r = 0; r < n[i].length; r++) {
38
- const d = e[i][r], _ = d <= c[i] ? 1 : Math.exp(-((d - c[i]) / p[i]));
39
- l.push(i), u.push(n[i][r]), s.push(_);
34
+ function D(t, e, i, a = 1) {
35
+ const s = t.length, { sigmas: p, rhos: f } = L(e, i), h = [], c = [], o = [];
36
+ for (let r = 0; r < s; r++)
37
+ for (let l = 0; l < t[r].length; l++) {
38
+ const d = e[r][l], u = d <= f[r] ? 1 : Math.exp(-((d - f[r]) / p[r]));
39
+ h.push(r), c.push(t[r][l]), o.push(u);
40
40
  }
41
- return { ...J(l, u, s, o, f), nVertices: o };
41
+ return { ...X(h, c, o, s, a), nVertices: s };
42
42
  }
43
- function Y(n, e, a) {
44
- const f = n.length, { sigmas: o, rhos: p } = I(e, a), c = [], l = [], u = [];
45
- for (let s = 0; s < f; s++)
46
- for (let t = 0; t < n[s].length; t++) {
47
- const i = e[s][t], r = i <= p[s] ? 1 : Math.exp(-((i - p[s]) / o[s]));
48
- c.push(s), l.push(n[s][t]), u.push(r);
43
+ function Q(t, e, i) {
44
+ const a = t.length, { sigmas: s, rhos: p } = L(e, i), f = [], h = [], c = [];
45
+ for (let o = 0; o < a; o++)
46
+ for (let n = 0; n < t[o].length; n++) {
47
+ const r = e[o][n], l = r <= p[o] ? 1 : Math.exp(-((r - p[o]) / s[o]));
48
+ f.push(o), h.push(t[o][n]), c.push(l);
49
49
  }
50
50
  return {
51
- rows: new Float32Array(c),
52
- cols: new Float32Array(l),
53
- vals: new Float32Array(u),
54
- nVertices: f
51
+ rows: new Float32Array(f),
52
+ cols: new Float32Array(h),
53
+ vals: new Float32Array(c),
54
+ nVertices: a
55
55
  };
56
56
  }
57
- function I(n, e) {
58
- const f = n.length, o = new Float32Array(f), p = new Float32Array(f);
59
- for (let c = 0; c < f; c++) {
60
- const l = n[c];
61
- p[c] = l.find((r) => r > 0) ?? 0;
62
- let u = 0, s = 1 / 0, t = 1;
63
- const i = Math.log2(e);
64
- for (let r = 0; r < 64; r++) {
57
+ function L(t, e) {
58
+ const a = t.length, s = new Float32Array(a), p = new Float32Array(a);
59
+ for (let f = 0; f < a; f++) {
60
+ const h = t[f];
61
+ p[f] = h.find((l) => l > 0) ?? 0;
62
+ let c = 0, o = 1 / 0, n = 1;
63
+ const r = Math.log2(e);
64
+ for (let l = 0; l < 64; l++) {
65
65
  let d = 0;
66
- for (let _ = 1; _ < l.length; _++)
67
- d += Math.exp(-Math.max(0, l[_] - p[c]) / t);
68
- if (Math.abs(d - i) < 1e-5) break;
69
- d > i ? (s = t, t = (u + s) / 2) : (u = t, t = s === 1 / 0 ? t * 2 : (u + s) / 2);
66
+ for (let u = 1; u < h.length; u++)
67
+ d += Math.exp(-Math.max(0, h[u] - p[f]) / n);
68
+ if (Math.abs(d - r) < 1e-5) break;
69
+ d > r ? (o = n, n = (c + o) / 2) : (c = n, n = o === 1 / 0 ? n * 2 : (c + o) / 2);
70
70
  }
71
- o[c] = t;
71
+ s[f] = n;
72
72
  }
73
- return { sigmas: o, rhos: p };
73
+ return { sigmas: s, rhos: p };
74
74
  }
75
- function J(n, e, a, f, o) {
76
- const p = /* @__PURE__ */ new Map(), c = (t, i, r) => {
77
- const d = t * f + i;
78
- p.set(d, (p.get(d) ?? 0) + r);
75
+ function X(t, e, i, a, s) {
76
+ const p = /* @__PURE__ */ new Map(), f = (n, r, l) => {
77
+ const d = n * a + r;
78
+ p.set(d, (p.get(d) ?? 0) + l);
79
79
  };
80
- for (let t = 0; t < n.length; t++)
81
- c(n[t], e[t], a[t]), c(e[t], n[t], a[t]);
82
- const l = [], u = [], s = [];
83
- for (const [t, i] of p.entries()) {
84
- const r = Math.floor(t / f), d = t % f;
85
- l.push(r), u.push(d), s.push(
86
- i > 1 ? o * (2 - i) + (1 - o) * (i - 1) : i
80
+ for (let n = 0; n < t.length; n++)
81
+ f(t[n], e[n], i[n]), f(e[n], t[n], i[n]);
82
+ const h = [], c = [], o = [];
83
+ for (const [n, r] of p.entries()) {
84
+ const l = Math.floor(n / a), d = n % a;
85
+ h.push(l), c.push(d), o.push(
86
+ r > 1 ? s * (2 - r) + (1 - s) * (r - 1) : r
87
87
  );
88
88
  }
89
89
  return {
90
- rows: new Float32Array(l),
91
- cols: new Float32Array(u),
92
- vals: new Float32Array(s)
90
+ rows: new Float32Array(h),
91
+ cols: new Float32Array(c),
92
+ vals: new Float32Array(o)
93
93
  };
94
94
  }
95
- const Q = `// UMAP SGD compute shader — processes one graph edge per GPU thread.
95
+ const Z = `// UMAP SGD compute shader — processes one graph edge per GPU thread.
96
96
  // Applies attraction forces between connected nodes and repulsion forces
97
97
  // against negative samples.
98
98
 
@@ -195,10 +195,10 @@ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
195
195
  epochs_per_sample[edge_idx] / f32(params.negative_sample_rate);
196
196
  }
197
197
  `;
198
- class D {
198
+ class W {
199
199
  constructor() {
200
- S(this, "device");
201
- S(this, "pipeline");
200
+ E(this, "device");
201
+ E(this, "pipeline");
202
202
  }
203
203
  async init() {
204
204
  const e = await navigator.gpu.requestAdapter();
@@ -206,7 +206,7 @@ class D {
206
206
  this.device = await e.requestDevice(), this.pipeline = this.device.createComputePipeline({
207
207
  layout: "auto",
208
208
  compute: {
209
- module: this.device.createShaderModule({ code: Q }),
209
+ module: this.device.createShaderModule({ code: Z }),
210
210
  entryPoint: "main"
211
211
  }
212
212
  });
@@ -224,216 +224,218 @@ class D {
224
224
  * @param params - UMAP curve parameters and repulsion settings
225
225
  * @returns Optimized embedding as Float32Array
226
226
  */
227
- async optimize(e, a, f, o, p, c, l, u) {
228
- const { device: s } = this, t = a.length, i = this.makeBuffer(
227
+ async optimize(e, i, a, s, p, f, h, c, o) {
228
+ const { device: n } = this, r = i.length, l = this.makeBuffer(
229
229
  e,
230
230
  GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
231
- ), r = this.makeBuffer(a, GPUBufferUsage.STORAGE), d = this.makeBuffer(f, GPUBufferUsage.STORAGE), _ = this.makeBuffer(o, GPUBufferUsage.STORAGE), g = new Float32Array(t).fill(0), h = this.makeBuffer(g, GPUBufferUsage.STORAGE), y = new Float32Array(t);
232
- for (let m = 0; m < t; m++)
233
- y[m] = o[m] / u.negativeSampleRate;
234
- const w = this.makeBuffer(y, GPUBufferUsage.STORAGE), U = new Uint32Array(t);
235
- for (let m = 0; m < t; m++)
236
- U[m] = Math.random() * 4294967295 | 0;
237
- const b = this.makeBuffer(U, GPUBufferUsage.STORAGE), x = s.createBuffer({
231
+ ), d = this.makeBuffer(i, GPUBufferUsage.STORAGE), u = this.makeBuffer(a, GPUBufferUsage.STORAGE), g = this.makeBuffer(s, GPUBufferUsage.STORAGE), _ = new Float32Array(r).fill(0), w = this.makeBuffer(_, GPUBufferUsage.STORAGE), y = new Float32Array(r);
232
+ for (let m = 0; m < r; m++)
233
+ y[m] = s[m] / c.negativeSampleRate;
234
+ const b = this.makeBuffer(y, GPUBufferUsage.STORAGE), M = new Uint32Array(r);
235
+ for (let m = 0; m < r; m++)
236
+ M[m] = Math.random() * 4294967295 | 0;
237
+ const x = this.makeBuffer(M, GPUBufferUsage.STORAGE), B = n.createBuffer({
238
238
  size: 40,
239
239
  usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
240
240
  });
241
- for (let m = 0; m < l; m++) {
242
- const G = 1 - m / l, A = new ArrayBuffer(40), M = new Uint32Array(A), P = new Float32Array(A);
243
- M[0] = t, M[1] = p, M[2] = c, M[3] = m, M[4] = l, P[5] = G, P[6] = u.a, P[7] = u.b, P[8] = u.gamma, M[9] = u.negativeSampleRate, s.queue.writeBuffer(x, 0, A);
244
- const B = s.createBindGroup({
241
+ for (let m = 0; m < h; m++) {
242
+ const F = 1 - m / h, A = new ArrayBuffer(40), v = new Uint32Array(A), N = new Float32Array(A);
243
+ v[0] = r, v[1] = p, v[2] = f, v[3] = m, v[4] = h, N[5] = F, N[6] = c.a, N[7] = c.b, N[8] = c.gamma, v[9] = c.negativeSampleRate, n.queue.writeBuffer(B, 0, A);
244
+ const S = n.createBindGroup({
245
245
  layout: this.pipeline.getBindGroupLayout(0),
246
246
  entries: [
247
- { binding: 0, resource: { buffer: _ } },
248
- { binding: 1, resource: { buffer: r } },
249
- { binding: 2, resource: { buffer: d } },
250
- { binding: 3, resource: { buffer: i } },
251
- { binding: 4, resource: { buffer: h } },
252
- { binding: 5, resource: { buffer: w } },
253
- { binding: 6, resource: { buffer: x } },
254
- { binding: 7, resource: { buffer: b } }
247
+ { binding: 0, resource: { buffer: g } },
248
+ { binding: 1, resource: { buffer: d } },
249
+ { binding: 2, resource: { buffer: u } },
250
+ { binding: 3, resource: { buffer: l } },
251
+ { binding: 4, resource: { buffer: w } },
252
+ { binding: 5, resource: { buffer: b } },
253
+ { binding: 6, resource: { buffer: B } },
254
+ { binding: 7, resource: { buffer: x } }
255
255
  ]
256
- }), N = s.createCommandEncoder(), v = N.beginComputePass();
257
- v.setPipeline(this.pipeline), v.setBindGroup(0, B), v.dispatchWorkgroups(Math.ceil(t / 256)), v.end(), s.queue.submit([N.finish()]), m % 10 === 0 && await s.queue.onSubmittedWorkDone();
256
+ }), k = n.createCommandEncoder(), U = k.beginComputePass();
257
+ U.setPipeline(this.pipeline), U.setBindGroup(0, S), U.dispatchWorkgroups(Math.ceil(r / 256)), U.end(), n.queue.submit([k.finish()]), m % 10 === 0 && (await n.queue.onSubmittedWorkDone(), o == null || o(m, h));
258
258
  }
259
- const E = s.createBuffer({
259
+ const G = n.createBuffer({
260
260
  size: e.byteLength,
261
261
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
262
- }), F = s.createCommandEncoder();
263
- F.copyBufferToBuffer(i, 0, E, 0, e.byteLength), s.queue.submit([F.finish()]), await E.mapAsync(GPUMapMode.READ);
264
- const R = new Float32Array(E.getMappedRange().slice(0));
265
- return E.unmap(), i.destroy(), r.destroy(), d.destroy(), _.destroy(), h.destroy(), w.destroy(), b.destroy(), x.destroy(), E.destroy(), R;
262
+ }), R = n.createCommandEncoder();
263
+ R.copyBufferToBuffer(l, 0, G, 0, e.byteLength), n.queue.submit([R.finish()]), await G.mapAsync(GPUMapMode.READ);
264
+ const O = new Float32Array(G.getMappedRange().slice(0));
265
+ return G.unmap(), l.destroy(), d.destroy(), u.destroy(), g.destroy(), w.destroy(), b.destroy(), x.destroy(), B.destroy(), G.destroy(), O;
266
266
  }
267
- makeBuffer(e, a) {
268
- const f = this.device.createBuffer({
267
+ makeBuffer(e, i) {
268
+ const a = this.device.createBuffer({
269
269
  size: e.byteLength,
270
- usage: a,
270
+ usage: i,
271
271
  mappedAtCreation: !0
272
272
  });
273
- return e instanceof Float32Array ? new Float32Array(f.getMappedRange()).set(e) : new Uint32Array(f.getMappedRange()).set(e), f.unmap(), f;
273
+ return e instanceof Float32Array ? new Float32Array(a.getMappedRange()).set(e) : new Uint32Array(a.getMappedRange()).set(e), a.unmap(), a;
274
274
  }
275
275
  }
276
- function O(n) {
277
- return Math.max(-4, Math.min(4, n));
276
+ function P(t) {
277
+ return Math.max(-4, Math.min(4, t));
278
278
  }
279
- function z(n, e, a, f, o, p, c) {
280
- const { a: l, b: u, gamma: s = 1, negativeSampleRate: t = 5 } = c, i = e.rows.length, r = new Uint32Array(e.rows), d = new Uint32Array(e.cols), _ = new Float32Array(i).fill(0), g = new Float32Array(i);
281
- for (let h = 0; h < i; h++)
282
- g[h] = a[h] / t;
283
- for (let h = 0; h < p; h++) {
284
- const y = 1 - h / p;
285
- for (let w = 0; w < i; w++) {
286
- if (_[w] > h) continue;
287
- const U = r[w], b = d[w];
288
- let x = 0;
289
- for (let m = 0; m < o; m++) {
290
- const G = n[U * o + m] - n[b * o + m];
291
- x += G * G;
279
+ function q(t, e, i, a, s, p, f, h) {
280
+ const { a: c, b: o, gamma: n = 1, negativeSampleRate: r = 5 } = f, l = e.rows.length, d = new Uint32Array(e.rows), u = new Uint32Array(e.cols), g = new Float32Array(l).fill(0), _ = new Float32Array(l);
281
+ for (let w = 0; w < l; w++)
282
+ _[w] = i[w] / r;
283
+ for (let w = 0; w < p; w++) {
284
+ h == null || h(w, p);
285
+ const y = 1 - w / p;
286
+ for (let b = 0; b < l; b++) {
287
+ if (g[b] > w) continue;
288
+ const M = d[b], x = u[b];
289
+ let B = 0;
290
+ for (let m = 0; m < s; m++) {
291
+ const F = t[M * s + m] - t[x * s + m];
292
+ B += F * F;
292
293
  }
293
- const E = Math.pow(x, u), F = -2 * l * u * (x > 0 ? E / x : 0) / (l * E + 1);
294
- for (let m = 0; m < o; m++) {
295
- const G = n[U * o + m] - n[b * o + m], A = O(F * G);
296
- n[U * o + m] += y * A;
294
+ const G = Math.pow(B, o), R = -2 * c * o * (B > 0 ? G / B : 0) / (c * G + 1);
295
+ for (let m = 0; m < s; m++) {
296
+ const F = t[M * s + m] - t[x * s + m], A = P(R * F);
297
+ t[M * s + m] += y * A;
297
298
  }
298
- _[w] += a[w];
299
- const R = g[w] > 0 ? Math.floor(a[w] / g[w]) : 0;
300
- for (let m = 0; m < R; m++) {
301
- const G = Math.floor(Math.random() * f);
302
- if (G === U) continue;
299
+ g[b] += i[b];
300
+ const O = _[b] > 0 ? Math.floor(i[b] / _[b]) : 0;
301
+ for (let m = 0; m < O; m++) {
302
+ const F = Math.floor(Math.random() * a);
303
+ if (F === M) continue;
303
304
  let A = 0;
304
- for (let B = 0; B < o; B++) {
305
- const N = n[U * o + B] - n[G * o + B];
306
- A += N * N;
305
+ for (let S = 0; S < s; S++) {
306
+ const k = t[M * s + S] - t[F * s + S];
307
+ A += k * k;
307
308
  }
308
- const M = Math.pow(A, u), P = 2 * s * u / ((1e-3 + A) * (l * M + 1));
309
- for (let B = 0; B < o; B++) {
310
- const N = n[U * o + B] - n[G * o + B], v = O(P * N);
311
- n[U * o + B] += y * v;
309
+ const v = Math.pow(A, o), N = 2 * n * o / ((1e-3 + A) * (c * v + 1));
310
+ for (let S = 0; S < s; S++) {
311
+ const k = t[M * s + S] - t[F * s + S], U = P(N * k);
312
+ t[M * s + S] += y * U;
312
313
  }
313
314
  }
314
- g[w] += a[w] / t;
315
+ _[b] += i[b] / r;
315
316
  }
316
317
  }
317
- return n;
318
+ return t;
318
319
  }
319
- function X(n, e, a, f, o, p, c, l, u) {
320
- const { a: s, b: t, gamma: i = 1, negativeSampleRate: r = 5 } = u, d = a.rows.length, _ = new Uint32Array(a.rows), g = new Uint32Array(a.cols), h = new Float32Array(d).fill(0), y = new Float32Array(d);
321
- for (let w = 0; w < d; w++)
322
- y[w] = f[w] / r;
323
- for (let w = 0; w < l; w++) {
324
- const U = 1 - w / l;
325
- for (let b = 0; b < d; b++) {
326
- if (h[b] > w) continue;
327
- const x = _[b], E = g[b];
328
- let F = 0;
329
- for (let A = 0; A < c; A++) {
330
- const M = n[x * c + A] - e[E * c + A];
331
- F += M * M;
320
+ function $(t, e, i, a, s, p, f, h, c, o) {
321
+ const { a: n, b: r, gamma: l = 1, negativeSampleRate: d = 5 } = c, u = i.rows.length, g = new Uint32Array(i.rows), _ = new Uint32Array(i.cols), w = new Float32Array(u).fill(0), y = new Float32Array(u);
322
+ for (let b = 0; b < u; b++)
323
+ y[b] = a[b] / d;
324
+ for (let b = 0; b < h; b++) {
325
+ const M = 1 - b / h;
326
+ for (let x = 0; x < u; x++) {
327
+ if (w[x] > b) continue;
328
+ const B = g[x], G = _[x];
329
+ let R = 0;
330
+ for (let A = 0; A < f; A++) {
331
+ const v = t[B * f + A] - e[G * f + A];
332
+ R += v * v;
332
333
  }
333
- const R = Math.pow(F, t), m = -2 * s * t * (F > 0 ? R / F : 0) / (s * R + 1);
334
- for (let A = 0; A < c; A++) {
335
- const M = n[x * c + A] - e[E * c + A];
336
- n[x * c + A] += U * O(m * M);
334
+ const O = Math.pow(R, r), m = -2 * n * r * (R > 0 ? O / R : 0) / (n * O + 1);
335
+ for (let A = 0; A < f; A++) {
336
+ const v = t[B * f + A] - e[G * f + A];
337
+ t[B * f + A] += M * P(m * v);
337
338
  }
338
- h[b] += f[b];
339
- const G = y[b] > 0 ? Math.floor(f[b] / y[b]) : 0;
340
- for (let A = 0; A < G; A++) {
341
- const M = Math.floor(Math.random() * p);
342
- if (M === E) continue;
343
- let P = 0;
344
- for (let v = 0; v < c; v++) {
345
- const k = n[x * c + v] - e[M * c + v];
346
- P += k * k;
339
+ w[x] += a[x];
340
+ const F = y[x] > 0 ? Math.floor(a[x] / y[x]) : 0;
341
+ for (let A = 0; A < F; A++) {
342
+ const v = Math.floor(Math.random() * p);
343
+ if (v === G) continue;
344
+ let N = 0;
345
+ for (let U = 0; U < f; U++) {
346
+ const z = t[B * f + U] - e[v * f + U];
347
+ N += z * z;
347
348
  }
348
- const B = Math.pow(P, t), N = 2 * i * t / ((1e-3 + P) * (s * B + 1));
349
- for (let v = 0; v < c; v++) {
350
- const k = n[x * c + v] - e[M * c + v];
351
- n[x * c + v] += U * O(N * k);
349
+ const S = Math.pow(N, r), k = 2 * l * r / ((1e-3 + N) * (n * S + 1));
350
+ for (let U = 0; U < f; U++) {
351
+ const z = t[B * f + U] - e[v * f + U];
352
+ t[B * f + U] += M * P(k * z);
352
353
  }
353
354
  }
354
- y[b] += f[b] / r;
355
+ y[x] += a[x] / d;
355
356
  }
356
357
  }
357
- return n;
358
+ return t;
358
359
  }
359
- function L() {
360
+ function K() {
360
361
  return typeof navigator < "u" && !!navigator.gpu;
361
362
  }
362
- async function te(n, e = {}) {
363
+ async function ae(t, e = {}, i) {
363
364
  const {
364
365
  nComponents: a = 2,
365
- nNeighbors: f = 15,
366
- minDist: o = 0.1,
367
- spread: p = 1,
368
- hnsw: c = {}
369
- } = e, l = e.nEpochs ?? (n.length > 1e4 ? 200 : 500);
366
+ nNeighbors: s = 15,
367
+ minDist: p = 0.1,
368
+ spread: f = 1,
369
+ hnsw: h = {}
370
+ } = e, c = e.nEpochs ?? (t.length > 1e4 ? 200 : 500);
370
371
  console.time("knn");
371
- const { indices: u, distances: s } = await H(n, f, {
372
- M: c.M ?? 16,
373
- efConstruction: c.efConstruction ?? 200,
374
- efSearch: c.efSearch ?? 50
372
+ const { indices: o, distances: n } = await Y(t, s, {
373
+ M: h.M ?? 16,
374
+ efConstruction: h.efConstruction ?? 200,
375
+ efSearch: h.efSearch ?? 50
375
376
  });
376
377
  console.timeEnd("knn"), console.time("fuzzy-set");
377
- const t = C(u, s, f);
378
+ const r = D(o, n, s);
378
379
  console.timeEnd("fuzzy-set");
379
- const { a: i, b: r } = W(o, p), d = q(t.vals, l), _ = n.length, g = new Float32Array(_ * a);
380
- for (let y = 0; y < g.length; y++)
381
- g[y] = Math.random() * 20 - 10;
380
+ const { a: l, b: d } = j(p, f), u = I(r.vals, c), g = t.length, _ = new Float32Array(g * a);
381
+ for (let y = 0; y < _.length; y++)
382
+ _[y] = Math.random() * 20 - 10;
382
383
  console.time("sgd");
383
- let h;
384
- if (L())
384
+ let w;
385
+ if (K())
385
386
  try {
386
- const y = new D();
387
- await y.init(), h = await y.optimize(
388
- g,
389
- new Uint32Array(t.rows),
390
- new Uint32Array(t.cols),
391
- d,
387
+ const y = new W();
388
+ await y.init(), w = await y.optimize(
392
389
  _,
390
+ new Uint32Array(r.rows),
391
+ new Uint32Array(r.cols),
392
+ u,
393
+ g,
393
394
  a,
394
- l,
395
- { a: i, b: r, gamma: 1, negativeSampleRate: 5 }
395
+ c,
396
+ { a: l, b: d, gamma: 1, negativeSampleRate: 5 },
397
+ i
396
398
  );
397
399
  } catch (y) {
398
- console.warn("WebGPU SGD failed, falling back to CPU:", y), h = z(g, t, d, _, a, l, { a: i, b: r });
400
+ console.warn("WebGPU SGD failed, falling back to CPU:", y), w = q(_, r, u, g, a, c, { a: l, b: d }, i);
399
401
  }
400
402
  else
401
- h = z(g, t, d, _, a, l, { a: i, b: r });
402
- return console.timeEnd("sgd"), h;
403
+ w = q(_, r, u, g, a, c, { a: l, b: d }, i);
404
+ return console.timeEnd("sgd"), w;
403
405
  }
404
- function W(n, e) {
405
- if (Math.abs(e - 1) < 1e-6 && Math.abs(n - 0.1) < 1e-6)
406
+ function j(t, e) {
407
+ if (Math.abs(e - 1) < 1e-6 && Math.abs(t - 0.1) < 1e-6)
406
408
  return { a: 1.9292, b: 0.7915 };
407
- if (Math.abs(e - 1) < 1e-6 && Math.abs(n - 0) < 1e-6)
409
+ if (Math.abs(e - 1) < 1e-6 && Math.abs(t - 0) < 1e-6)
408
410
  return { a: 1.8956, b: 0.8006 };
409
- if (Math.abs(e - 1) < 1e-6 && Math.abs(n - 0.5) < 1e-6)
411
+ if (Math.abs(e - 1) < 1e-6 && Math.abs(t - 0.5) < 1e-6)
410
412
  return { a: 1.5769, b: 0.8951 };
411
- const a = Z(n, e);
412
- return { a: $(n, e, a), b: a };
413
+ const i = ee(t, e);
414
+ return { a: te(t, e, i), b: i };
413
415
  }
414
- function Z(n, e) {
416
+ function ee(t, e) {
415
417
  return 1 / (e * 1.2);
416
418
  }
417
- function $(n, e, a) {
418
- return n < 1e-6 ? 1.8956 : (1 / (1 + 1e-3) - 1) / -Math.pow(n, 2 * a);
419
+ function te(t, e, i) {
420
+ return t < 1e-6 ? 1.8956 : (1 / (1 + 1e-3) - 1) / -Math.pow(t, 2 * i);
419
421
  }
420
- class se {
422
+ class re {
421
423
  constructor(e = {}) {
422
- S(this, "_nComponents");
423
- S(this, "_nNeighbors");
424
- S(this, "_minDist");
425
- S(this, "_spread");
426
- S(this, "_nEpochs");
427
- S(this, "_hnswOpts");
428
- S(this, "_a");
429
- S(this, "_b");
424
+ E(this, "_nComponents");
425
+ E(this, "_nNeighbors");
426
+ E(this, "_minDist");
427
+ E(this, "_spread");
428
+ E(this, "_nEpochs");
429
+ E(this, "_hnswOpts");
430
+ E(this, "_a");
431
+ E(this, "_b");
430
432
  /** The low-dimensional embedding produced by the last fit() call. */
431
- S(this, "embedding", null);
432
- S(this, "_hnswIndex", null);
433
- S(this, "_nTrain", 0);
433
+ E(this, "embedding", null);
434
+ E(this, "_hnswIndex", null);
435
+ E(this, "_nTrain", 0);
434
436
  this._nComponents = e.nComponents ?? 2, this._nNeighbors = e.nNeighbors ?? 15, this._minDist = e.minDist ?? 0.1, this._spread = e.spread ?? 1, this._nEpochs = e.nEpochs, this._hnswOpts = e.hnsw ?? {};
435
- const { a, b: f } = W(this._minDist, this._spread);
436
- this._a = a, this._b = f;
437
+ const { a: i, b: a } = j(this._minDist, this._spread);
438
+ this._a = i, this._b = a;
437
439
  }
438
440
  /**
439
441
  * Train UMAP on `vectors`.
@@ -441,44 +443,45 @@ class se {
441
443
  * index so that transform() can project new points later.
442
444
  * Returns `this` for chaining.
443
445
  */
444
- async fit(e) {
445
- const a = e.length, f = this._nEpochs ?? (a > 1e4 ? 200 : 500), { M: o = 16, efConstruction: p = 200, efSearch: c = 50 } = this._hnswOpts;
446
+ async fit(e, i) {
447
+ const a = e.length, s = this._nEpochs ?? (a > 1e4 ? 200 : 500), { M: p = 16, efConstruction: f = 200, efSearch: h = 50 } = this._hnswOpts;
446
448
  console.time("knn");
447
- const { knn: l, index: u } = await V(e, this._nNeighbors, {
448
- M: o,
449
- efConstruction: p,
450
- efSearch: c
449
+ const { knn: c, index: o } = await J(e, this._nNeighbors, {
450
+ M: p,
451
+ efConstruction: f,
452
+ efSearch: h
451
453
  });
452
- this._hnswIndex = u, this._nTrain = a, console.timeEnd("knn"), console.time("fuzzy-set");
453
- const s = C(l.indices, l.distances, this._nNeighbors);
454
+ this._hnswIndex = o, this._nTrain = a, console.timeEnd("knn"), console.time("fuzzy-set");
455
+ const n = D(c.indices, c.distances, this._nNeighbors);
454
456
  console.timeEnd("fuzzy-set");
455
- const t = q(s.vals, f), i = new Float32Array(a * this._nComponents);
456
- for (let r = 0; r < i.length; r++)
457
- i[r] = Math.random() * 20 - 10;
458
- if (console.time("sgd"), L())
457
+ const r = I(n.vals, s), l = new Float32Array(a * this._nComponents);
458
+ for (let d = 0; d < l.length; d++)
459
+ l[d] = Math.random() * 20 - 10;
460
+ if (console.time("sgd"), K())
459
461
  try {
460
- const r = new D();
461
- await r.init(), this.embedding = await r.optimize(
462
- i,
463
- new Uint32Array(s.rows),
464
- new Uint32Array(s.cols),
465
- t,
462
+ const d = new W();
463
+ await d.init(), this.embedding = await d.optimize(
464
+ l,
465
+ new Uint32Array(n.rows),
466
+ new Uint32Array(n.cols),
467
+ r,
466
468
  a,
467
469
  this._nComponents,
468
- f,
469
- { a: this._a, b: this._b, gamma: 1, negativeSampleRate: 5 }
470
+ s,
471
+ { a: this._a, b: this._b, gamma: 1, negativeSampleRate: 5 },
472
+ i
470
473
  );
471
- } catch (r) {
472
- console.warn("WebGPU SGD failed, falling back to CPU:", r), this.embedding = z(i, s, t, a, this._nComponents, f, {
474
+ } catch (d) {
475
+ console.warn("WebGPU SGD failed, falling back to CPU:", d), this.embedding = q(l, n, r, a, this._nComponents, s, {
473
476
  a: this._a,
474
477
  b: this._b
475
- });
478
+ }, i);
476
479
  }
477
480
  else
478
- this.embedding = z(i, s, t, a, this._nComponents, f, {
481
+ this.embedding = q(l, n, r, a, this._nComponents, s, {
479
482
  a: this._a,
480
483
  b: this._b
481
- });
484
+ }, i);
482
485
  return console.timeEnd("sgd"), this;
483
486
  }
484
487
  /**
@@ -487,59 +490,81 @@ class se {
487
490
  *
488
491
  * The training embedding is kept fixed; only the new-point positions are
489
492
  * optimised. Returns a Float32Array of shape [vectors.length × nComponents].
493
+ *
494
+ * @param normalize - When `true`, min-max normalise each dimension of the
495
+ * returned embedding to [0, 1]. The stored training embedding is never
496
+ * mutated. Defaults to `false`.
490
497
  */
491
- async transform(e) {
498
+ async transform(e, i = !1) {
492
499
  if (!this._hnswIndex || !this.embedding)
493
500
  throw new Error("UMAP.transform() must be called after fit()");
494
- const a = e.length, f = this._nEpochs ?? (this._nTrain > 1e4 ? 200 : 500), o = Math.max(100, Math.floor(f / 4)), p = this._hnswIndex.searchKnn(e, this._nNeighbors), c = Y(p.indices, p.distances, this._nNeighbors), l = new Uint32Array(c.rows), u = new Uint32Array(c.cols), s = new Float32Array(a), t = new Float32Array(a * this._nComponents);
495
- for (let r = 0; r < l.length; r++) {
496
- const d = l[r], _ = u[r], g = c.vals[r];
497
- s[d] += g;
498
- for (let h = 0; h < this._nComponents; h++)
499
- t[d * this._nComponents + h] += g * this.embedding[_ * this._nComponents + h];
501
+ const a = e.length, s = this._nEpochs ?? (this._nTrain > 1e4 ? 200 : 500), p = Math.max(100, Math.floor(s / 4)), f = this._hnswIndex.searchKnn(e, this._nNeighbors), h = Q(f.indices, f.distances, this._nNeighbors), c = new Uint32Array(h.rows), o = new Uint32Array(h.cols), n = new Float32Array(a), r = new Float32Array(a * this._nComponents);
502
+ for (let u = 0; u < c.length; u++) {
503
+ const g = c[u], _ = o[u], w = h.vals[u];
504
+ n[g] += w;
505
+ for (let y = 0; y < this._nComponents; y++)
506
+ r[g * this._nComponents + y] += w * this.embedding[_ * this._nComponents + y];
500
507
  }
501
- for (let r = 0; r < a; r++)
502
- if (s[r] > 0)
503
- for (let d = 0; d < this._nComponents; d++)
504
- t[r * this._nComponents + d] /= s[r];
508
+ for (let u = 0; u < a; u++)
509
+ if (n[u] > 0)
510
+ for (let g = 0; g < this._nComponents; g++)
511
+ r[u * this._nComponents + g] /= n[u];
505
512
  else
506
- for (let d = 0; d < this._nComponents; d++)
507
- t[r * this._nComponents + d] = Math.random() * 20 - 10;
508
- const i = q(c.vals, o);
509
- return X(
510
- t,
513
+ for (let g = 0; g < this._nComponents; g++)
514
+ r[u * this._nComponents + g] = Math.random() * 20 - 10;
515
+ const l = I(h.vals, p), d = $(
516
+ r,
511
517
  this.embedding,
512
- c,
513
- i,
518
+ h,
519
+ l,
514
520
  a,
515
521
  this._nTrain,
516
522
  this._nComponents,
517
- o,
523
+ p,
518
524
  { a: this._a, b: this._b }
519
525
  );
526
+ return i ? T(d, a, this._nComponents) : d;
520
527
  }
521
528
  /**
522
529
  * Convenience method equivalent to `fit(vectors)` followed by
523
530
  * `transform(vectors)` — but more efficient because the training embedding
524
531
  * is returned directly without a second optimization pass.
532
+ *
533
+ * @param normalize - When `true`, min-max normalise each dimension of the
534
+ * returned embedding to [0, 1]. `this.embedding` is never mutated.
535
+ * Defaults to `false`.
525
536
  */
526
- async fit_transform(e) {
527
- return await this.fit(e), this.embedding;
537
+ async fit_transform(e, i, a = !1) {
538
+ return await this.fit(e, i), a ? T(this.embedding, e.length, this._nComponents) : this.embedding;
539
+ }
540
+ }
541
+ function T(t, e, i) {
542
+ const a = new Float32Array(t.length);
543
+ for (let s = 0; s < i; s++) {
544
+ let p = 1 / 0, f = -1 / 0;
545
+ for (let c = 0; c < e; c++) {
546
+ const o = t[c * i + s];
547
+ o < p && (p = o), o > f && (f = o);
548
+ }
549
+ const h = f - p;
550
+ for (let c = 0; c < e; c++)
551
+ a[c * i + s] = h > 0 ? (t[c * i + s] - p) / h : 0;
528
552
  }
553
+ return a;
529
554
  }
530
- function q(n, e) {
531
- let a = -1 / 0;
532
- for (let o = 0; o < n.length; o++)
533
- n[o] > a && (a = n[o]);
534
- const f = new Float32Array(n.length);
535
- for (let o = 0; o < n.length; o++) {
536
- const p = n[o] / a;
537
- f[o] = p > 0 ? e / p : -1;
555
+ function I(t, e) {
556
+ let i = -1 / 0;
557
+ for (let s = 0; s < t.length; s++)
558
+ t[s] > i && (i = t[s]);
559
+ const a = new Float32Array(t.length);
560
+ for (let s = 0; s < t.length; s++) {
561
+ const p = t[s] / i;
562
+ a[s] = p > 0 ? e / p : -1;
538
563
  }
539
- return f;
564
+ return a;
540
565
  }
541
566
  export {
542
- se as UMAP,
543
- te as fit,
544
- L as isWebGPUAvailable
567
+ re as UMAP,
568
+ ae as fit,
569
+ K as isWebGPUAvailable
545
570
  };
package/dist/umap.d.ts CHANGED
@@ -16,6 +16,14 @@ export interface UMAPOptions {
16
16
  efSearch?: number;
17
17
  };
18
18
  }
19
+ /**
20
+ * Called after each completed SGD epoch (or every 10 epochs on the GPU path,
21
+ * piggybacking on the existing GPU synchronisation point to avoid extra stalls).
22
+ *
23
+ * @param epoch - Zero-based index of the epoch that just finished.
24
+ * @param nEpochs - Total number of epochs.
25
+ */
26
+ export type ProgressCallback = (epoch: number, nEpochs: number) => void;
19
27
  /**
20
28
  * Fit UMAP to the given high-dimensional vectors and return a low-dimensional embedding.
21
29
  *
@@ -24,7 +32,7 @@ export interface UMAPOptions {
24
32
  * 2. Fuzzy simplicial set construction (graph weights)
25
33
  * 3. SGD optimization (WebGPU accelerated, with CPU fallback)
26
34
  */
27
- export declare function fit(vectors: number[][], opts?: UMAPOptions): Promise<Float32Array>;
35
+ export declare function fit(vectors: number[][], opts?: UMAPOptions, onProgress?: ProgressCallback): Promise<Float32Array>;
28
36
  /**
29
37
  * Compute the a, b parameters for the UMAP curve 1/(1 + a*d^(2b)).
30
38
  *
@@ -74,21 +82,29 @@ export declare class UMAP {
74
82
  * index so that transform() can project new points later.
75
83
  * Returns `this` for chaining.
76
84
  */
77
- fit(vectors: number[][]): Promise<this>;
85
+ fit(vectors: number[][], onProgress?: ProgressCallback): Promise<this>;
78
86
  /**
79
87
  * Project new (unseen) `vectors` into the embedding space learned by fit().
80
88
  * Must be called after fit().
81
89
  *
82
90
  * The training embedding is kept fixed; only the new-point positions are
83
91
  * optimised. Returns a Float32Array of shape [vectors.length × nComponents].
92
+ *
93
+ * @param normalize - When `true`, min-max normalise each dimension of the
94
+ * returned embedding to [0, 1]. The stored training embedding is never
95
+ * mutated. Defaults to `false`.
84
96
  */
85
- transform(vectors: number[][]): Promise<Float32Array>;
97
+ transform(vectors: number[][], normalize?: boolean): Promise<Float32Array>;
86
98
  /**
87
99
  * Convenience method equivalent to `fit(vectors)` followed by
88
100
  * `transform(vectors)` — but more efficient because the training embedding
89
101
  * is returned directly without a second optimization pass.
102
+ *
103
+ * @param normalize - When `true`, min-max normalise each dimension of the
104
+ * returned embedding to [0, 1]. `this.embedding` is never mutated.
105
+ * Defaults to `false`.
90
106
  */
91
- fit_transform(vectors: number[][]): Promise<Float32Array>;
107
+ fit_transform(vectors: number[][], onProgress?: ProgressCallback, normalize?: boolean): Promise<Float32Array>;
92
108
  }
93
109
  /**
94
110
  * Compute per-edge epoch sampling periods based on edge weights.
package/package.json CHANGED
@@ -1,30 +1,60 @@
1
1
  {
2
2
  "name": "umap-gpu",
3
- "version": "0.2.6",
3
+ "version": "0.2.9",
4
4
  "description": "UMAP with HNSW kNN and WebGPU-accelerated SGD",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
7
+ "types": "dist/umap-gpu.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/umap-gpu.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ }
16
+ },
8
17
  "files": [
9
18
  "dist"
10
19
  ],
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": [
24
+ "umap",
25
+ "dimensionality-reduction",
26
+ "webgpu",
27
+ "hnsw",
28
+ "machine-learning",
29
+ "gpu"
30
+ ],
11
31
  "repository": {
12
32
  "type": "git",
13
33
  "url": "https://github.com/Achuttarsing/umap-gpu"
14
34
  },
35
+ "bugs": {
36
+ "url": "https://github.com/Achuttarsing/umap-gpu/issues"
37
+ },
38
+ "homepage": "https://achuttarsing.github.io/umap-gpu",
15
39
  "scripts": {
16
40
  "build": "vite build && tsc",
17
41
  "dev": "vite",
18
42
  "test": "vitest run",
19
- "prepublishOnly": "npm test && npm run build"
43
+ "prepublishOnly": "bun test && bun run build",
44
+ "docs:dev": "vitepress dev docs",
45
+ "docs:build": "vitepress build docs",
46
+ "docs:generate": "bun run build && bunx api-extractor run && bun run docs:build"
20
47
  },
21
48
  "dependencies": {
22
49
  "hnswlib-wasm": "^0.8.2"
23
50
  },
24
51
  "devDependencies": {
52
+ "@microsoft/api-extractor": "^7.57.6",
25
53
  "@webgpu/types": "^0.1.40",
26
54
  "typescript": "^5.4.0",
27
55
  "vite": "^5.0.0",
56
+ "vitepress": "^1.6.4",
57
+ "vitepress-plugin-llms": "^1.11.0",
28
58
  "vitest": "^4.0.18"
29
59
  },
30
60
  "license": "MIT"