@flaxia/node 0.5.0 → 0.5.1

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.
Files changed (44) hide show
  1. package/dist/assets/ai-inference.js +241 -85
  2. package/dist/assets/webgpuProbe.js +357 -0
  3. package/dist/client/SignalingClient.d.ts.map +1 -1
  4. package/dist/client/__tests__/mobile-lifecycle.test.d.ts +2 -0
  5. package/dist/client/__tests__/mobile-lifecycle.test.d.ts.map +1 -0
  6. package/dist/client/mobile-lifecycle.d.ts +20 -0
  7. package/dist/client/mobile-lifecycle.d.ts.map +1 -0
  8. package/dist/executor/WorkerPool.d.ts +7 -1
  9. package/dist/executor/WorkerPool.d.ts.map +1 -1
  10. package/dist/executor/__tests__/resourceReporter.test.d.ts +2 -0
  11. package/dist/executor/__tests__/resourceReporter.test.d.ts.map +1 -0
  12. package/dist/executor/memoryProbe.d.ts +5 -0
  13. package/dist/executor/memoryProbe.d.ts.map +1 -1
  14. package/dist/executor/resourceReporter.d.ts +15 -0
  15. package/dist/executor/resourceReporter.d.ts.map +1 -0
  16. package/dist/executor/webgpuProbe.d.ts +16 -5
  17. package/dist/executor/webgpuProbe.d.ts.map +1 -1
  18. package/dist/index.js +943 -291
  19. package/dist/swarm/__tests__/peer-quality.test.d.ts +2 -0
  20. package/dist/swarm/__tests__/peer-quality.test.d.ts.map +1 -0
  21. package/dist/swarm/__tests__/peer-transport.test.d.ts +2 -0
  22. package/dist/swarm/__tests__/peer-transport.test.d.ts.map +1 -0
  23. package/dist/swarm/adapter.d.ts +1 -1
  24. package/dist/swarm/adapter.d.ts.map +1 -1
  25. package/dist/swarm/controller.d.ts +4 -0
  26. package/dist/swarm/controller.d.ts.map +1 -1
  27. package/dist/swarm/messages.d.ts +2 -2
  28. package/dist/swarm/messages.d.ts.map +1 -1
  29. package/dist/swarm/peer-quality.d.ts +14 -0
  30. package/dist/swarm/peer-quality.d.ts.map +1 -0
  31. package/dist/swarm/peer-transport.d.ts +81 -0
  32. package/dist/swarm/peer-transport.d.ts.map +1 -0
  33. package/dist/swarm/runtime.d.ts +5 -1
  34. package/dist/swarm/runtime.d.ts.map +1 -1
  35. package/dist/swarm-engine.js +39 -20
  36. package/dist/worker.js +331 -208
  37. package/dist/workloads/__tests__/model-pipeline-cache.test.d.ts +2 -0
  38. package/dist/workloads/__tests__/model-pipeline-cache.test.d.ts.map +1 -0
  39. package/dist/workloads/ai-inference.d.ts +6 -0
  40. package/dist/workloads/ai-inference.d.ts.map +1 -1
  41. package/dist/workloads/model-pipeline-cache.d.ts +48 -0
  42. package/dist/workloads/model-pipeline-cache.d.ts.map +1 -0
  43. package/package.json +1 -1
  44. package/dist/assets/dist.js +0 -209
@@ -1,27 +1,28 @@
1
- import { TextStreamer as e, env as t, pipeline as n } from "./transformers.web.js";
1
+ import { j as e } from "./webgpuProbe.js";
2
+ import { TextStreamer as t, env as n, pipeline as r } from "./transformers.web.js";
2
3
  //#region src/workloads/ai-model-registry.ts
3
- var r = 512 * 1024 * 1024, i = 4 * 1024 ** 3, a = new Map(Object.entries({
4
+ var i = 512 * 1024 * 1024, a = 4 * 1024 ** 3, o = new Map(Object.entries({
4
5
  "Xenova/bert-base-multilingual-uncased-sentiment": {
5
6
  repo: "Xenova/bert-base-multilingual-uncased-sentiment",
6
7
  revision: "main",
7
- maxBytes: r
8
+ maxBytes: i
8
9
  },
9
10
  "onnx-community/Qwen3-Embedding-0.6B-ONNX": {
10
11
  repo: "onnx-community/Qwen3-Embedding-0.6B-ONNX",
11
12
  revision: "main",
12
- maxBytes: r
13
+ maxBytes: i
13
14
  }
14
- }).map(([e, t]) => [e, { ...t }])), o = [
15
+ }).map(([e, t]) => [e, { ...t }])), s = [
15
16
  "wasm",
16
17
  "cpu",
17
18
  "webgpu"
18
- ], s = [
19
+ ], c = [
19
20
  "q4",
20
21
  "q4f16",
21
22
  "q8",
22
23
  "int8",
23
24
  "uint8"
24
- ], c = {
25
+ ], l = {
25
26
  max_new_tokens: {
26
27
  min: 1,
27
28
  max: 1024
@@ -42,45 +43,45 @@ var r = 512 * 1024 * 1024, i = 4 * 1024 ** 3, a = new Map(Object.entries({
42
43
  min: 0,
43
44
  max: 4
44
45
  }
45
- }, l = 1e5;
46
- function u() {
47
- return [...a.keys()];
46
+ }, u = 1e5;
47
+ function d() {
48
+ return [...o.keys()];
48
49
  }
49
- function d(e) {
50
+ function f(e) {
50
51
  if (typeof e != "string" || !e) throw Error(`ai-inference: model must be a non-empty string, got ${JSON.stringify(e)}`);
51
- let t = a.get(e);
52
- if (!t) throw Error(`ai-inference: model not allowed: ${e} (allowed: ${u().join(", ") || "none"})`);
52
+ let t = o.get(e);
53
+ if (!t) throw Error(`ai-inference: model not allowed: ${e} (allowed: ${d().join(", ") || "none"})`);
53
54
  return t;
54
55
  }
55
- function f(e) {
56
+ function p(e) {
56
57
  if (e != null) {
57
- if (typeof e != "string" || !o.includes(e)) throw Error(`ai-inference: device not allowed: ${JSON.stringify(e)} (allowed: ${o.join(", ")})`);
58
+ if (typeof e != "string" || !s.includes(e)) throw Error(`ai-inference: device not allowed: ${JSON.stringify(e)} (allowed: ${s.join(", ")})`);
58
59
  return e;
59
60
  }
60
61
  }
61
- function p(e) {
62
+ function m(e) {
62
63
  if (e != null) {
63
- if (typeof e != "string" || !s.includes(e)) throw Error(`ai-inference: dtype not allowed: ${JSON.stringify(e)} (allowed: ${s.join(", ")})`);
64
+ if (typeof e != "string" || !c.includes(e)) throw Error(`ai-inference: dtype not allowed: ${JSON.stringify(e)} (allowed: ${c.join(", ")})`);
64
65
  return e;
65
66
  }
66
67
  }
67
- function m(e) {
68
+ function h(e) {
68
69
  if (typeof e == "string") {
69
- if (e.length > 1e5) throw Error(`ai-inference: input exceeds ${l} characters`);
70
+ if (e.length > 1e5) throw Error(`ai-inference: input exceeds ${u} characters`);
70
71
  return e;
71
72
  }
72
73
  if (Array.isArray(e)) {
73
74
  if (e.length === 0 || e.length > 64) throw Error("ai-inference: input array must hold 1..64 items");
74
75
  for (let t of e) {
75
76
  if (typeof t != "string") throw Error("ai-inference: input array items must be strings");
76
- if (t.length > 1e5) throw Error(`ai-inference: input exceeds ${l} characters`);
77
+ if (t.length > 1e5) throw Error(`ai-inference: input exceeds ${u} characters`);
77
78
  }
78
79
  return e;
79
80
  }
80
81
  throw Error("ai-inference: input must be a string or an array of strings");
81
82
  }
82
- function h(e, t) {
83
- for (let [n, r] of Object.entries(c)) {
83
+ function g(e, t) {
84
+ for (let [n, r] of Object.entries(l)) {
84
85
  let i = t[n];
85
86
  if (i != null) {
86
87
  if (typeof i != "number" || !Number.isFinite(i) || i < r.min || i > r.max) throw Error(`ai-inference: option ${n} must be a number within ${r.min}..${r.max}, got ${JSON.stringify(i)}`);
@@ -99,9 +100,139 @@ function h(e, t) {
99
100
  }
100
101
  }
101
102
  }
103
+ var _ = class {
104
+ constructor(e = {}) {
105
+ this.options = e, this.entries = /* @__PURE__ */ new Map();
106
+ }
107
+ async acquire(t, n, r, i) {
108
+ let a = this.entries.get(t);
109
+ if (a?.state === "ready" && a.references === 0 && a.expiresAt <= this.now() && (await this.disposeEntry(a), a = this.entries.get(t)), a?.state === "failed" && (this.entries.delete(t), a = void 0), a || (await this.evictExpired(), a = this.entries.get(t), a)) return this.acquireEntry(a);
110
+ let o = Math.max(1, Math.floor(this.options.capacity ?? 2));
111
+ if (this.entries.size >= o && await this.makeRoom(), a = this.entries.get(t), a) return this.acquireEntry(a);
112
+ let s = this.now(), c = {
113
+ key: t,
114
+ descriptor: { ...n },
115
+ state: "loading",
116
+ references: 1,
117
+ updatedAt: s,
118
+ lastUsedAt: s,
119
+ expiresAt: s + this.ttlMs(),
120
+ promise: Promise.resolve(void 0),
121
+ dispose: i
122
+ };
123
+ this.entries.set(t, c), c.promise = (async () => {
124
+ try {
125
+ let e = await r();
126
+ return c.value = e.value, c.descriptor = {
127
+ ...c.descriptor,
128
+ ...e.descriptor
129
+ }, c.state === "loading" && (c.state = "ready"), c.updatedAt = this.now(), c.expiresAt = this.now() + this.ttlMs(), e.value;
130
+ } catch (t) {
131
+ throw c.state = "failed", c.updatedAt = this.now(), c.expiresAt = this.now() + Math.min(this.ttlMs(), e), t;
132
+ }
133
+ })();
134
+ try {
135
+ let e = await c.promise;
136
+ return this.createLease(c, e);
137
+ } catch (e) {
138
+ throw c.references = Math.max(0, c.references - 1), e;
139
+ }
140
+ }
141
+ async acquireEntry(e) {
142
+ if (e.state === "failed") throw this.entries.delete(e.key), Error("model cache entry failed before it could be acquired");
143
+ if (e.state === "evicting") throw Error("model cache entry is being evicted");
144
+ e.references++, e.lastUsedAt = this.now(), e.expiresAt = this.now() + this.ttlMs();
145
+ try {
146
+ let t = await e.promise;
147
+ return this.createLease(e, t);
148
+ } catch (t) {
149
+ throw e.references = Math.max(0, e.references - 1), t;
150
+ }
151
+ }
152
+ getSnapshot(t = this.now()) {
153
+ return {
154
+ version: 1,
155
+ sampledAt: t,
156
+ expiresAt: t + e,
157
+ entries: [...this.entries.values()].slice(0, 8).map((e) => ({
158
+ ...e.descriptor,
159
+ state: e.state,
160
+ referenceCount: e.references
161
+ }))
162
+ };
163
+ }
164
+ async releaseAll() {
165
+ let e = [];
166
+ for (let t of this.entries.values()) {
167
+ if (t.state === "failed") {
168
+ this.entries.delete(t.key);
169
+ continue;
170
+ }
171
+ t.state = "evicting", t.updatedAt = this.now(), t.references === 0 && t.value !== void 0 && e.push(this.disposeEntry(t));
172
+ }
173
+ await Promise.all(e);
174
+ }
175
+ createLease(e, t) {
176
+ let n = !1;
177
+ return {
178
+ value: t,
179
+ updateDescriptor: (t) => {
180
+ n || (e.descriptor = {
181
+ ...e.descriptor,
182
+ ...t
183
+ }, e.updatedAt = this.now());
184
+ },
185
+ invalidate: () => {
186
+ n || (e.state = "evicting", e.updatedAt = this.now());
187
+ },
188
+ release: async () => {
189
+ n || (n = !0, e.references = Math.max(0, e.references - 1), e.lastUsedAt = this.now(), e.updatedAt = this.now(), e.references === 0 && e.state === "evicting" ? await this.disposeEntry(e) : e.references === 0 && e.state === "ready" && (e.expiresAt = this.now() + this.ttlMs()));
190
+ }
191
+ };
192
+ }
193
+ async evictExpired() {
194
+ let e = this.now();
195
+ for (let t of [...this.entries.values()]) t.state === "failed" && t.expiresAt <= e ? this.entries.delete(t.key) : t.state === "ready" && t.references === 0 && t.expiresAt <= e && await this.disposeEntry(t);
196
+ }
197
+ async makeRoom() {
198
+ let e = Math.max(1, Math.floor(this.options.capacity ?? 2));
199
+ for (; this.entries.size >= e;) {
200
+ let e = [...this.entries.values()].filter((e) => e.references === 0 && (e.state === "failed" || e.state === "ready")).sort((e, t) => Number(t.state === "failed") - Number(e.state === "failed") || e.lastUsedAt - t.lastUsedAt)[0];
201
+ if (!e) throw Error("model cache capacity is occupied by active entries");
202
+ e.state === "failed" ? this.entries.delete(e.key) : await this.disposeEntry(e);
203
+ }
204
+ }
205
+ async disposeEntry(e) {
206
+ if (e.references > 0 || e.state === "loading") return;
207
+ e.state = "evicting", e.updatedAt = this.now();
208
+ let t = e.value;
209
+ e.value = void 0;
210
+ try {
211
+ t !== void 0 && await e.dispose(t);
212
+ } finally {
213
+ this.entries.get(e.key) === e && this.entries.delete(e.key);
214
+ }
215
+ }
216
+ now() {
217
+ return this.options.now?.() ?? Date.now();
218
+ }
219
+ ttlMs() {
220
+ return Math.max(1, this.options.ttlMs ?? 18e5);
221
+ }
222
+ };
223
+ function v(e) {
224
+ return JSON.stringify([
225
+ e.task,
226
+ e.modelId,
227
+ e.revision,
228
+ e.dtype,
229
+ e.backend,
230
+ e.layerRange ?? null
231
+ ]);
232
+ }
102
233
  //#endregion
103
234
  //#region src/workloads/ai-inference.ts
104
- var g = [
235
+ var y = [
105
236
  "text-classification",
106
237
  "token-classification",
107
238
  "question-answering",
@@ -127,90 +258,115 @@ var g = [
127
258
  "depth-estimation",
128
259
  "feature-extraction",
129
260
  "image-feature-extraction"
130
- ], _ = /* @__PURE__ */ new Map(), v = 2, y = () => {
131
- _.clear();
132
- }, b = (e, t) => {
133
- let n = "", r = null, i = Promise.resolve(), a, o = !1, s = !1, c = (t) => {
134
- i = i.then(() => {
135
- if (!o) return e(t);
261
+ ], b = new _(), x = () => {
262
+ b.releaseAll();
263
+ }, S = () => b.getSnapshot(), C = (e, t) => {
264
+ let n = "", r = null, i = Promise.resolve(), a = 0, o = 0, s, c = !1, l = !1, u = (t) => {
265
+ if (a >= 64 || o + t.length > 262144) throw c = !0, s = /* @__PURE__ */ Error("ai-inference token consumer is too slow; generation stopped at the bounded stream buffer"), s;
266
+ a++, o += t.length, i = i.then(() => {
267
+ if (!c) return e(t);
136
268
  }).then(() => void 0, (e) => {
137
- o = !0, a = e;
269
+ c = !0, s = e;
270
+ }).finally(() => {
271
+ a--, o -= t.length;
138
272
  });
139
- }, l = () => {
273
+ }, d = () => {
140
274
  r !== null && clearTimeout(r), r = null;
141
275
  let e = n;
142
- n = "", e && !o && c(e);
143
- }, u = (e) => {
144
- if (!s) {
145
- if (o) throw a;
276
+ n = "", e && !c && u(e);
277
+ }, f = (e) => {
278
+ if (!l) {
279
+ if (c) throw s;
146
280
  if (!t.tokenBuffer) {
147
- c(e);
281
+ u(e);
148
282
  return;
149
283
  }
150
- n += e, r === null && (r = setTimeout(l, t.tokenBufferIntervalMs ?? 50));
284
+ if (n.length + e.length > 262144) throw c = !0, s = /* @__PURE__ */ Error("ai-inference token consumer is too slow; generation stopped at the bounded stream buffer"), s;
285
+ n += e, r === null && (r = setTimeout(() => {
286
+ try {
287
+ d();
288
+ } catch (e) {
289
+ c = !0, s = e;
290
+ }
291
+ }, t.tokenBufferIntervalMs ?? 50));
151
292
  }
152
- }, d = async (e) => {
153
- if (s = !0, r !== null && clearTimeout(r), r = null, e && !o ? l() : n = "", await i, o) throw a;
293
+ }, p = async (e) => {
294
+ if (l = !0, r !== null && clearTimeout(r), r = null, e && !c ? d() : n = "", await i, c) throw s;
154
295
  };
155
296
  return {
156
- callback: u,
157
- finish: () => d(!0),
158
- cancel: () => d(!1)
297
+ callback: f,
298
+ finish: () => p(!0),
299
+ cancel: () => p(!1)
159
300
  };
160
301
  };
161
- function x() {
302
+ function w() {
162
303
  let e = globalThis.fetch.bind(globalThis);
163
- t.fetch = async (t, n) => {
304
+ n.fetch = async (t, n) => {
164
305
  let r = new URL(String(t));
165
306
  if (!/(^|\.)huggingface\.co$/.test(r.hostname) && !/(^|\.)hf\.co$/.test(r.hostname)) throw Error(`ai-inference: model download blocked from ${r.hostname}`);
166
- let a = await e(r, n), o = Number(a.headers.get("content-length"));
167
- if (Number.isFinite(o) && o > i) throw Error(`ai-inference: model file too large (${o} bytes > ${i})`);
168
- return a;
307
+ let i = await e(r, n), o = Number(i.headers.get("content-length"));
308
+ if (Number.isFinite(o) && o > a) throw Error(`ai-inference: model file too large (${o} bytes > ${a})`);
309
+ return i;
169
310
  };
170
311
  }
171
- x(), t.backends?.onnx?.wasm && (t.backends.onnx.wasm.numThreads = 1, t.backends.onnx.wasm.proxy = !1);
172
- var S = async (t, r) => {
173
- let { task: i, model: a, input: o, options: s = {} } = t;
174
- if (!g.includes(i)) throw Error(`Invalid or unsupported task: ${i}. Supported tasks are: ${g.join(", ")}`);
175
- let c = d(a), l = f(s.device), u = p(s.dtype), y = m(o), x = {};
176
- h(x, s);
177
- let S = `${i}:${c.repo}:${u ?? "q4f16"}:${l ?? "wasm"}`, C = _.get(S);
178
- if (!C) {
179
- if (_.size >= v) {
180
- let e = _.keys().next().value;
181
- _.delete(e);
182
- }
312
+ w(), n.backends?.onnx?.wasm && (n.backends.onnx.wasm.numThreads = 1, n.backends.onnx.wasm.proxy = !1);
313
+ var T = async (e, n) => {
314
+ let { task: i, model: a, input: o, options: s = {} } = e;
315
+ if (!y.includes(i)) throw Error(`Invalid or unsupported task: ${i}. Supported tasks are: ${y.join(", ")}`);
316
+ let c = f(a), l = p(s.device), u = m(s.dtype), d = h(o), _ = {};
317
+ g(_, s);
318
+ let x = u ?? "q4f16", S = l ?? "wasm", w = v({
319
+ task: i,
320
+ modelId: a,
321
+ revision: c.revision,
322
+ dtype: x,
323
+ backend: S
324
+ }), T = performance.now(), E = await b.acquire(w, {
325
+ modelId: a,
326
+ revision: c.revision,
327
+ dtype: x,
328
+ backend: S
329
+ }, async () => {
183
330
  let e = {
184
- dtype: u ?? "q4f16",
185
- device: l
186
- }, t = performance.now();
331
+ dtype: x,
332
+ device: l,
333
+ revision: c.revision
334
+ };
187
335
  console.log(`[flaxia-node] ai-inference: loading pipeline task=${i} model=${c.repo} revision=${c.revision} dtype=${e.dtype} device=${l ?? "wasm"}`);
336
+ let t;
188
337
  try {
189
- C = await n(i, c.repo, e);
190
- } catch (t) {
191
- if (l && l !== "wasm") console.warn(`[AiInference] ${l} failed, falling back to wasm:`, t), e.device = "wasm", C = await n(i, c.repo, e);
192
- else throw console.error(`[flaxia-node] ai-inference: pipeline load FAILED task=${i} model=${c.repo} error=${t instanceof Error ? t.message : String(t)}`), t;
338
+ t = await r(i, c.repo, e);
339
+ } catch (n) {
340
+ if (l && l !== "wasm") console.warn(`[AiInference] ${l} failed, falling back to wasm:`, n), e.device = "wasm", t = await r(i, c.repo, e);
341
+ else throw console.error(`[flaxia-node] ai-inference: pipeline load FAILED task=${i} model=${c.repo} error=${n instanceof Error ? n.message : String(n)}`), n;
193
342
  }
194
- console.log(`[flaxia-node] ai-inference: pipeline ready task=${i} model=${c.repo} loadMs=${Math.round(performance.now() - t)}`), _.set(S, C);
195
- }
196
- let w = {
197
- max_new_tokens: x.max_new_tokens ?? 128,
198
- do_sample: x.do_sample ?? !1
199
- };
200
- for (let [e, t] of Object.entries(x)) e === "max_new_tokens" || e === "do_sample" || (w[e] = t);
201
- let T = r && C.tokenizer ? b(r, s) : null;
202
- T && (w.streamer = new e(C.tokenizer, {
203
- skip_prompt: !0,
204
- skip_special_tokens: !0,
205
- callback_function: T.callback
206
- }));
207
- let E = performance.now(), D;
343
+ return console.log(`[flaxia-node] ai-inference: pipeline ready task=${i} model=${c.repo} loadMs=${Math.round(performance.now() - T)}`), {
344
+ value: t,
345
+ descriptor: { backend: e.device ?? "wasm" }
346
+ };
347
+ }, (e) => e.dispose?.()), D = E.value;
208
348
  try {
209
- D = await C(y, w);
210
- } catch (e) {
211
- throw T && await T.cancel(), e;
349
+ let e = {
350
+ max_new_tokens: _.max_new_tokens ?? 128,
351
+ do_sample: _.do_sample ?? !1
352
+ };
353
+ for (let [t, n] of Object.entries(_)) t === "max_new_tokens" || t === "do_sample" || (e[t] = n);
354
+ let r = n && D.tokenizer ? C(n, s) : null;
355
+ r && (e.streamer = new t(D.tokenizer, {
356
+ skip_prompt: !0,
357
+ skip_special_tokens: !0,
358
+ callback_function: r.callback
359
+ }));
360
+ let a = performance.now(), o;
361
+ try {
362
+ o = await D(d, e);
363
+ } catch (e) {
364
+ throw /out of memory|oom|allocation failed|could not allocate/i.test(e instanceof Error ? e.message : String(e)) && E.invalidate(), r && await r.cancel(), e;
365
+ }
366
+ return await r?.finish(), console.log(`[flaxia-node] ai-inference: done task=${i} model=${c.repo} execMs=${Math.round(performance.now() - a)} inputLen=${String(d).length}`), { output: o };
367
+ } finally {
368
+ await E.release();
212
369
  }
213
- return await T?.finish(), console.log(`[flaxia-node] ai-inference: done task=${i} model=${c.repo} execMs=${Math.round(performance.now() - E)} inputLen=${String(y).length}`), { output: D };
214
370
  };
215
371
  //#endregion
216
- export { S as handleAiInference, y as releaseCache };
372
+ export { S as getModelCacheSnapshot, T as handleAiInference, x as releaseCache };