runntime 0.0.1-alpha.0 → 0.0.1-beta.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.
- package/README.md +169 -2
- package/dist/classifier-ByH6OI6h.mjs +45830 -0
- package/dist/config-UyxIidZc.d.mts +206 -0
- package/dist/zoo/index.d.mts +671 -0
- package/dist/zoo/index.mjs +3012 -0
- package/dist/zoo/transformers/index.d.mts +65 -0
- package/dist/zoo/transformers/index.mjs +674 -0
- package/package.json +68 -5
- package/index.js +0 -1
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { $ as initRunntime, D as V2_NER_CLASS_NAMES, E as loadPrivacyFilterModel, F as createOpfsCache, G as inGpuErrorScopes, I as createReplayCache, N as MINILM_L6, O as MinilmModel, P as models, S as configFromCheckpoint, X as httpRangeSource, Y as fromSafetensors, b as MOONSHINE_TINY, c as createDetector, et as toArray, ht as supportsF16, i as MOBILENETV4_CONV_S, j as fillBucketInputs, k as MINILM_BUCKETS, l as createTranscriber, mt as defaultRoot, q as bufferSource, s as createEstimator, t as createClassifier, x as assertConfigMatches, y as MOONSHINE_BASE } from "../../classifier-ByH6OI6h.mjs";
|
|
2
|
+
import tgpu from "typegpu";
|
|
3
|
+
import { AutoConfig, PreTrainedModel, Tensor } from "@huggingface/transformers";
|
|
4
|
+
//#region src/zoo/transformers/minilm/model.ts
|
|
5
|
+
function fillReplayInputs(T, ids, out) {
|
|
6
|
+
fillBucketInputs(T, ids, {
|
|
7
|
+
...out,
|
|
8
|
+
selector: new Float32Array(T)
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
var RunntimeBertModel = class extends PreTrainedModel {
|
|
12
|
+
runntime;
|
|
13
|
+
replays;
|
|
14
|
+
constructor(config, runntime) {
|
|
15
|
+
super(config, {}, {});
|
|
16
|
+
this.runntime = runntime;
|
|
17
|
+
this.replays = createReplayCache({
|
|
18
|
+
buckets: MINILM_BUCKETS,
|
|
19
|
+
inputs: (T) => ({
|
|
20
|
+
ids: [T, 1],
|
|
21
|
+
positions: [T, 1],
|
|
22
|
+
segments: [T, 2]
|
|
23
|
+
}),
|
|
24
|
+
captureFill: (T, scratch) => fillReplayInputs(T, new Array(T).fill(0), scratch),
|
|
25
|
+
build: (T, inp) => this.runntime.forward(inp.ids, {
|
|
26
|
+
positions: inp.positions,
|
|
27
|
+
segments: inp.segments,
|
|
28
|
+
maxSegment: T
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** transformers.js's release hook: frees the recorded replays and the
|
|
33
|
+
* GPU weights. Returns the (empty) ONNX-session list callers may await. */
|
|
34
|
+
async dispose() {
|
|
35
|
+
this.replays.dispose();
|
|
36
|
+
this.runntime.dispose();
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
warm() {
|
|
40
|
+
return this.replays.warmAll();
|
|
41
|
+
}
|
|
42
|
+
async forward(modelInputs) {
|
|
43
|
+
const inputIds = modelInputs["input_ids"];
|
|
44
|
+
if (!inputIds) throw new Error("runntime backend: model inputs are missing input_ids");
|
|
45
|
+
const mask = modelInputs["attention_mask"];
|
|
46
|
+
const [batch, seqLen] = inputIds.dims;
|
|
47
|
+
const idsData = inputIds.data;
|
|
48
|
+
const maskData = mask?.data;
|
|
49
|
+
const rows = [];
|
|
50
|
+
const slots = [];
|
|
51
|
+
for (let b = 0; b < batch; b++) {
|
|
52
|
+
const row = [];
|
|
53
|
+
const slot = [];
|
|
54
|
+
for (let t = 0; t < seqLen; t++) {
|
|
55
|
+
const i = b * seqLen + t;
|
|
56
|
+
if (maskData === void 0 || Number(maskData[i]) !== 0) {
|
|
57
|
+
row.push(Number(idsData[i]));
|
|
58
|
+
slot.push(t);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
rows.push(row);
|
|
62
|
+
slots.push(slot);
|
|
63
|
+
}
|
|
64
|
+
const hidden = MINILM_L6.hidden;
|
|
65
|
+
const out = new Float32Array(batch * seqLen * hidden);
|
|
66
|
+
const writeRow = (flat, srcRow, b) => {
|
|
67
|
+
for (const [k, t] of slots[b].entries()) out.set(flat.subarray((srcRow + k) * hidden, (srcRow + k + 1) * hidden), (b * seqLen + t) * hidden);
|
|
68
|
+
};
|
|
69
|
+
const bucket = batch === 1 ? this.replays.pick(rows[0].length) : void 0;
|
|
70
|
+
if (bucket !== void 0) {
|
|
71
|
+
const row = rows[0];
|
|
72
|
+
writeRow(await this.replays.run(bucket, (T, scratch) => fillReplayInputs(T, row, scratch)), 0, 0);
|
|
73
|
+
return { last_hidden_state: new Tensor("float32", out, [
|
|
74
|
+
batch,
|
|
75
|
+
seqLen,
|
|
76
|
+
hidden
|
|
77
|
+
]) };
|
|
78
|
+
}
|
|
79
|
+
const packed = rows.flat();
|
|
80
|
+
const lengths = rows.map((r) => r.length);
|
|
81
|
+
const device = defaultRoot().device;
|
|
82
|
+
const flat = await inGpuErrorScopes(device, "inference", () => toArray(this.runntime.forward(packed, batch > 1 ? lengths : void 0)));
|
|
83
|
+
let srcRow = 0;
|
|
84
|
+
rows.forEach((row, b) => {
|
|
85
|
+
writeRow(flat, srcRow, b);
|
|
86
|
+
srcRow += row.length;
|
|
87
|
+
});
|
|
88
|
+
return { last_hidden_state: new Tensor("float32", out, [
|
|
89
|
+
batch,
|
|
90
|
+
seqLen,
|
|
91
|
+
hidden
|
|
92
|
+
]) };
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/zoo/transformers/weights.ts
|
|
97
|
+
/** The cache key of a weights URL: the whole path without the scheme, so
|
|
98
|
+
* same-named files of two sizes or precisions stay apart. */
|
|
99
|
+
function cacheKey(url) {
|
|
100
|
+
return url.replace(/^https?:\/\//, "");
|
|
101
|
+
}
|
|
102
|
+
/** Loads a safetensors file too big to hold in memory: byte ranges are
|
|
103
|
+
* fetched per tensor and saved in OPFS under `cacheId`. */
|
|
104
|
+
async function loadStreamedWeights(url, cacheId) {
|
|
105
|
+
const cache = await createOpfsCache("runntime-weights");
|
|
106
|
+
return fromSafetensors(url, {
|
|
107
|
+
cache,
|
|
108
|
+
cacheId
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function cachedWeightsSource(url, key) {
|
|
112
|
+
const cache = await createOpfsCache("runntime-weights");
|
|
113
|
+
if (!cache) return httpRangeSource(url);
|
|
114
|
+
const fetchAll = async () => {
|
|
115
|
+
const res = await fetch(url);
|
|
116
|
+
if (!res.ok) throw new Error(`runntime backend: fetching weights failed (${res.status})`);
|
|
117
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
118
|
+
};
|
|
119
|
+
const fromCache = async () => bufferSource(await cache.getOrCompute(key, fetchAll));
|
|
120
|
+
let source = await fromCache();
|
|
121
|
+
try {
|
|
122
|
+
await fromSafetensors(source);
|
|
123
|
+
} catch {
|
|
124
|
+
await cache.deletePrefix(key);
|
|
125
|
+
source = await fromCache();
|
|
126
|
+
try {
|
|
127
|
+
await fromSafetensors(source);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
throw new Error(`runntime backend: ${url} is not a safetensors file`, { cause: err });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return source;
|
|
133
|
+
}
|
|
134
|
+
async function loadCachedWeights(url, key) {
|
|
135
|
+
return fromSafetensors(await cachedWeightsSource(url, key));
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/zoo/transformers/minilm/loader.ts
|
|
139
|
+
/** The hosted f16 export; loadStateDict fuses qkv and folds the token-type
|
|
140
|
+
* embedding while loading it. */
|
|
141
|
+
const MINILM_WEIGHTS_URL = models.textEmbedding.ALL_MINILM_L6_V2.DEFAULT.modelPath;
|
|
142
|
+
const MINILM_MODEL_IDS = ["Xenova/all-MiniLM-L6-v2", "sentence-transformers/all-MiniLM-L6-v2"];
|
|
143
|
+
function minilmLoader(weightsUrl = MINILM_WEIGHTS_URL) {
|
|
144
|
+
return { async load(_modelId, config) {
|
|
145
|
+
defaultRoot();
|
|
146
|
+
const sd = await loadCachedWeights(weightsUrl, `whole/minilm-v1/${cacheKey(weightsUrl)}`);
|
|
147
|
+
const model = new MinilmModel(MINILM_L6);
|
|
148
|
+
if (supportsF16()) model.half();
|
|
149
|
+
await model.loadStateDict(sd);
|
|
150
|
+
const wrapped = new RunntimeBertModel(config, model);
|
|
151
|
+
await inGpuErrorScopes(defaultRoot().device, "plugin warm-up", () => wrapped.warm());
|
|
152
|
+
return wrapped;
|
|
153
|
+
} };
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/zoo/transformers/privacy-filter/model.ts
|
|
157
|
+
/** Labels per token: 'O' plus 8 span types × 4 boundary tags. */
|
|
158
|
+
const PRIVACY_FILTER_LABELS = V2_NER_CLASS_NAMES.length;
|
|
159
|
+
var RunntimePrivacyFilterForTokenClassification = class extends PreTrainedModel {
|
|
160
|
+
runntime;
|
|
161
|
+
constructor(config, runntime) {
|
|
162
|
+
super(config, {}, {});
|
|
163
|
+
this.runntime = runntime;
|
|
164
|
+
}
|
|
165
|
+
/** transformers.js's release hook (`await model.dispose()`): frees the
|
|
166
|
+
* GPU weights. The ONNX-session array it normally returns is empty here. */
|
|
167
|
+
async dispose() {
|
|
168
|
+
this.runntime.dispose();
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
async forward(modelInputs) {
|
|
172
|
+
const inputIds = modelInputs["input_ids"];
|
|
173
|
+
if (!inputIds) throw new Error("runntime backend: model inputs are missing input_ids");
|
|
174
|
+
const mask = modelInputs["attention_mask"];
|
|
175
|
+
const [batch, seqLen] = inputIds.dims;
|
|
176
|
+
const idsData = inputIds.data;
|
|
177
|
+
const maskData = mask?.data;
|
|
178
|
+
const L = PRIVACY_FILTER_LABELS;
|
|
179
|
+
const out = new Float32Array(batch * seqLen * L);
|
|
180
|
+
const device = defaultRoot().device;
|
|
181
|
+
await inGpuErrorScopes(device, "inference", async () => {
|
|
182
|
+
for (let b = 0; b < batch; b++) {
|
|
183
|
+
const row = [];
|
|
184
|
+
const slots = [];
|
|
185
|
+
for (let t = 0; t < seqLen; t++) {
|
|
186
|
+
const i = b * seqLen + t;
|
|
187
|
+
if (maskData === void 0 || Number(maskData[i]) !== 0) {
|
|
188
|
+
row.push(Number(idsData[i]));
|
|
189
|
+
slots.push(t);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (row.length === 0) continue;
|
|
193
|
+
const logits = await toArray(this.runntime.forward(row));
|
|
194
|
+
checkLogits(logits);
|
|
195
|
+
for (const [k, t] of slots.entries()) out.set(logits.subarray(k * L, (k + 1) * L), (b * seqLen + t) * L);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return { logits: new Tensor("float32", out, [
|
|
199
|
+
batch,
|
|
200
|
+
seqLen,
|
|
201
|
+
L
|
|
202
|
+
]) };
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
function checkLogits(logits) {
|
|
206
|
+
if (!logits.every(Number.isFinite)) throw new Error("runntime backend: GPU returned non-finite logits — inference failed on this device");
|
|
207
|
+
if (logits.every((v) => v === 0)) throw new Error("runntime backend: GPU returned all-zero logits — inference failed on this device");
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/zoo/transformers/privacy-filter/loader.ts
|
|
211
|
+
/** The hosted int8 export, 1.55 GB. */
|
|
212
|
+
const PRIVACY_FILTER_WEIGHTS_URL = models.privacyFilter.PRIVACY_FILTER.DEFAULT.modelPath;
|
|
213
|
+
const PRIVACY_FILTER_MODEL_IDS = ["openai/privacy-filter"];
|
|
214
|
+
const EXPECTED_CONFIG = {
|
|
215
|
+
hidden_size: 640,
|
|
216
|
+
num_hidden_layers: 8,
|
|
217
|
+
num_local_experts: 128,
|
|
218
|
+
num_experts_per_tok: 4
|
|
219
|
+
};
|
|
220
|
+
function assertPrivacyFilterConfig(config) {
|
|
221
|
+
const cfg = config;
|
|
222
|
+
for (const [key, expected] of Object.entries(EXPECTED_CONFIG)) if (cfg[key] !== expected) throw new Error(`runntime backend: privacy-filter config ${key} is ${String(cfg[key])}, expected ${expected}`);
|
|
223
|
+
const id2label = cfg["id2label"];
|
|
224
|
+
if (!id2label) throw new Error("runntime backend: privacy-filter config is missing id2label");
|
|
225
|
+
V2_NER_CLASS_NAMES.forEach((name, i) => {
|
|
226
|
+
if (id2label[String(i)] !== name) throw new Error(`runntime backend: privacy-filter label ${i} is '${id2label[String(i)]}', expected '${name}'`);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function privacyFilterLoader(weightsUrl = PRIVACY_FILTER_WEIGHTS_URL, opts = {}) {
|
|
230
|
+
return { async load(_modelId, config) {
|
|
231
|
+
defaultRoot();
|
|
232
|
+
assertPrivacyFilterConfig(config);
|
|
233
|
+
const sd = await loadStreamedWeights(weightsUrl, `range/privacy-filter-v1/${cacheKey(weightsUrl)}`);
|
|
234
|
+
const { model } = await loadPrivacyFilterModel(sd, { onProgress: opts.onProgress });
|
|
235
|
+
return new RunntimePrivacyFilterForTokenClassification(config, model);
|
|
236
|
+
} };
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/zoo/transformers/moonshine/model.ts
|
|
240
|
+
/** Speech-to-text stand-in running the zoo moonshine transcriber. The
|
|
241
|
+
* pipeline hands over 16 kHz audio as `input_values` [1, samples] and
|
|
242
|
+
* gets back the generated ids as int64 [1, L], BOS first, EOS last when
|
|
243
|
+
* reached; it decodes the text with the hub tokenizer. The transcriber
|
|
244
|
+
* does everything from audio to ids: encoder, greedy burst decode, and
|
|
245
|
+
* splitting clips longer than its chunk limit. */
|
|
246
|
+
var RunntimeMoonshineForConditionalGeneration = class extends PreTrainedModel {
|
|
247
|
+
runntime;
|
|
248
|
+
tokens;
|
|
249
|
+
/** Encode and decode wall time of the last generate() call, for
|
|
250
|
+
* benchmarks; the pipeline itself never reads it. */
|
|
251
|
+
lastTimings;
|
|
252
|
+
constructor(config, runntime, tokens) {
|
|
253
|
+
super(config, {}, {});
|
|
254
|
+
this.runntime = runntime;
|
|
255
|
+
this.tokens = tokens;
|
|
256
|
+
}
|
|
257
|
+
/** transformers.js's release hook: frees the weights, KV caches and rope
|
|
258
|
+
* tables. Returns the (empty) ONNX-session list callers may await. */
|
|
259
|
+
async dispose() {
|
|
260
|
+
this.runntime.dispose();
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
async generate(options) {
|
|
264
|
+
const audio = options["input_values"] ?? options.inputs;
|
|
265
|
+
if (!audio) throw new Error("runntime backend: moonshine generate() needs input_values");
|
|
266
|
+
if (audio.dims.length !== 2 || audio.dims[0] !== 1) throw new Error(`runntime backend: moonshine takes one clip per call, got input_values [${audio.dims}]`);
|
|
267
|
+
const samples = audio.data;
|
|
268
|
+
let ids = [this.tokens.bos, this.tokens.eos];
|
|
269
|
+
if (samples.length >= 895) {
|
|
270
|
+
const result = await inGpuErrorScopes(defaultRoot().device, "inference", () => this.runntime.transcribe(samples));
|
|
271
|
+
ids = result.ids;
|
|
272
|
+
this.lastTimings = result.timings;
|
|
273
|
+
}
|
|
274
|
+
return new Tensor("int64", BigInt64Array.from(ids.map(BigInt)), [1, ids.length]);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/zoo/transformers/moonshine/loader.ts
|
|
279
|
+
/** The hosted f16 exports, picked by the hub config's size. */
|
|
280
|
+
const MOONSHINE_WEIGHTS_URLS = {
|
|
281
|
+
tiny: models.speechToText.MOONSHINE.TINY.DEFAULT.modelPath,
|
|
282
|
+
base: models.speechToText.MOONSHINE.BASE.DEFAULT.modelPath
|
|
283
|
+
};
|
|
284
|
+
const MOONSHINE_MODEL_IDS = ["onnx-community/moonshine-tiny-ONNX", "onnx-community/moonshine-base-ONNX"];
|
|
285
|
+
function moonshinePreset(config) {
|
|
286
|
+
const parsed = configFromCheckpoint(config);
|
|
287
|
+
const size = parsed.dModel === MOONSHINE_TINY.dModel ? "tiny" : parsed.dModel === MOONSHINE_BASE.dModel ? "base" : void 0;
|
|
288
|
+
if (!size) throw new Error(`runntime backend: moonshine hidden_size ${parsed.dModel} is neither tiny nor base`);
|
|
289
|
+
const cfg = size === "tiny" ? MOONSHINE_TINY : MOONSHINE_BASE;
|
|
290
|
+
assertConfigMatches({
|
|
291
|
+
...parsed,
|
|
292
|
+
maxPositions: cfg.maxPositions
|
|
293
|
+
}, cfg);
|
|
294
|
+
return {
|
|
295
|
+
size,
|
|
296
|
+
cfg
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function idsOnlyTokenizer(cfg) {
|
|
300
|
+
return {
|
|
301
|
+
version: 0,
|
|
302
|
+
bosId: cfg.bos,
|
|
303
|
+
eosId: cfg.eos,
|
|
304
|
+
specialIds: [cfg.bos, cfg.eos],
|
|
305
|
+
vocab: new Array(cfg.vocab).fill("")
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function moonshineLoader(weightsUrl, opts = {}) {
|
|
309
|
+
return { async load(_modelId, config) {
|
|
310
|
+
defaultRoot();
|
|
311
|
+
const { size, cfg } = moonshinePreset(config);
|
|
312
|
+
const url = weightsUrl ?? MOONSHINE_WEIGHTS_URLS[size];
|
|
313
|
+
const source = await cachedWeightsSource(url, `whole/moonshine-v1/${cacheKey(url)}`);
|
|
314
|
+
const sd = await fromSafetensors(source);
|
|
315
|
+
const transcriber = await createTranscriber(sd, idsOnlyTokenizer(cfg), {
|
|
316
|
+
cfg,
|
|
317
|
+
weightDtype: opts.weightDtype ?? (supportsF16() ? "f16" : "f32"),
|
|
318
|
+
onProgress: opts.onProgress
|
|
319
|
+
});
|
|
320
|
+
await inGpuErrorScopes(defaultRoot().device, "plugin warm-up", () => transcriber.transcribe(/* @__PURE__ */ new Float32Array(16e3)));
|
|
321
|
+
return new RunntimeMoonshineForConditionalGeneration(config, transcriber, cfg);
|
|
322
|
+
} };
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/zoo/transformers/mobilenetv4/model.ts
|
|
326
|
+
var RunntimeMobileNetV4ForImageClassification = class extends PreTrainedModel {
|
|
327
|
+
runntime;
|
|
328
|
+
constructor(config, runntime) {
|
|
329
|
+
super(config, {}, {});
|
|
330
|
+
this.runntime = runntime;
|
|
331
|
+
}
|
|
332
|
+
/** transformers.js's release hook; the ONNX-session array it normally
|
|
333
|
+
* returns is empty here. */
|
|
334
|
+
async dispose() {
|
|
335
|
+
this.runntime.dispose();
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
async forward(inputs) {
|
|
339
|
+
const pixels = inputs["pixel_values"];
|
|
340
|
+
if (!pixels) throw new Error("runntime backend: mobilenetv4 forward() needs pixel_values");
|
|
341
|
+
const { inputSize: size, numClasses } = this.runntime;
|
|
342
|
+
if (pixels.dims.length !== 4 || pixels.dims[1] !== 3) throw new Error(`runntime backend: mobilenetv4 takes pixel_values [N, 3, ${size}, ${size}], got [${pixels.dims}]`);
|
|
343
|
+
const [n, , h, w] = pixels.dims;
|
|
344
|
+
if (h !== size || w !== size) throw new Error(`runntime backend: mobilenetv4 takes ${size}×${size} images, got pixel_values [${pixels.dims}]`);
|
|
345
|
+
const out = new Float32Array(n * numClasses);
|
|
346
|
+
const data = pixels.data;
|
|
347
|
+
const stride = 3 * size * size;
|
|
348
|
+
for (let i = 0; i < n; i++) {
|
|
349
|
+
const logits = await inGpuErrorScopes(defaultRoot().device, "inference", () => this.runntime.run(data.subarray(i * stride, (i + 1) * stride)));
|
|
350
|
+
out.set(logits, i * numClasses);
|
|
351
|
+
}
|
|
352
|
+
return { logits: new Tensor("float32", out, [n, numClasses]) };
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/zoo/transformers/mobilenetv4/loader.ts
|
|
357
|
+
/** The timm checkpoint (f32, 15 MB), loaded as f16 with the batch norms
|
|
358
|
+
* folded into their convs. */
|
|
359
|
+
const MOBILENETV4_WEIGHTS_URL = models.imageClassification.MOBILENETV4.DEFAULT.modelPath;
|
|
360
|
+
const MOBILENETV4_MODEL_IDS = ["onnx-community/mobilenetv4_conv_small.e2400_r224_in1k"];
|
|
361
|
+
function assertMobilenetv4Config(config) {
|
|
362
|
+
const cfg = config;
|
|
363
|
+
const id2label = cfg["id2label"];
|
|
364
|
+
const numClasses = id2label ? Object.keys(id2label).length : cfg["num_classes"];
|
|
365
|
+
if (numClasses !== void 0 && numClasses !== MOBILENETV4_CONV_S.numClasses) throw new Error(`runntime backend: mobilenetv4 config has ${String(numClasses)} classes, expected ${MOBILENETV4_CONV_S.numClasses}`);
|
|
366
|
+
const inputSize = cfg["input_size"];
|
|
367
|
+
const side = MOBILENETV4_CONV_S.inputSize;
|
|
368
|
+
if (inputSize && (inputSize[1] !== side || inputSize[2] !== side)) throw new Error(`runntime backend: mobilenetv4 config input_size is [${inputSize}], expected [3, ${side}, ${side}]`);
|
|
369
|
+
}
|
|
370
|
+
function mobilenetv4Loader(weightsUrl = MOBILENETV4_WEIGHTS_URL, opts = {}) {
|
|
371
|
+
return { async load(_modelId, config) {
|
|
372
|
+
defaultRoot();
|
|
373
|
+
assertMobilenetv4Config(config);
|
|
374
|
+
const source = await cachedWeightsSource(weightsUrl, `whole/mobilenetv4-v1/${cacheKey(weightsUrl)}`);
|
|
375
|
+
return new RunntimeMobileNetV4ForImageClassification(config, await createClassifier(await fromSafetensors(source), { onProgress: opts.onProgress }));
|
|
376
|
+
} };
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/zoo/transformers/yolo26/output.ts
|
|
380
|
+
/** `a` ranks above `b`: higher score, index breaks ties. */
|
|
381
|
+
const beats = (score, a, b) => score[a] > score[b] || score[a] === score[b] && a < b;
|
|
382
|
+
function topIndices(score, k) {
|
|
383
|
+
const n = score.length;
|
|
384
|
+
const size = Math.min(k, n);
|
|
385
|
+
const heap = new Int32Array(size);
|
|
386
|
+
let len = 0;
|
|
387
|
+
const siftDown = (start) => {
|
|
388
|
+
let i = start;
|
|
389
|
+
for (;;) {
|
|
390
|
+
const l = 2 * i + 1;
|
|
391
|
+
const r = l + 1;
|
|
392
|
+
let worst = i;
|
|
393
|
+
if (l < len && beats(score, heap[worst], heap[l])) worst = l;
|
|
394
|
+
if (r < len && beats(score, heap[worst], heap[r])) worst = r;
|
|
395
|
+
if (worst === i) return;
|
|
396
|
+
const t = heap[i];
|
|
397
|
+
heap[i] = heap[worst];
|
|
398
|
+
heap[worst] = t;
|
|
399
|
+
i = worst;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
for (let i = 0; i < n; i++) if (len < size) {
|
|
403
|
+
heap[len] = i;
|
|
404
|
+
let j = len++;
|
|
405
|
+
while (j > 0) {
|
|
406
|
+
const parent = j - 1 >> 1;
|
|
407
|
+
if (!beats(score, heap[parent], heap[j])) break;
|
|
408
|
+
const t = heap[j];
|
|
409
|
+
heap[j] = heap[parent];
|
|
410
|
+
heap[parent] = t;
|
|
411
|
+
j = parent;
|
|
412
|
+
}
|
|
413
|
+
} else if (beats(score, i, heap[0])) {
|
|
414
|
+
heap[0] = i;
|
|
415
|
+
siftDown(0);
|
|
416
|
+
}
|
|
417
|
+
return Array.from(heap.subarray(0, len)).sort((a, b) => beats(score, a, b) ? -1 : 1);
|
|
418
|
+
}
|
|
419
|
+
function selectTopAnchors(levels, opts) {
|
|
420
|
+
const { numClasses, maxDet, inputSize } = opts;
|
|
421
|
+
const total = levels.reduce((n, l) => n + l.h * l.w, 0);
|
|
422
|
+
const best = new Float32Array(total).fill(-Infinity);
|
|
423
|
+
let base = 0;
|
|
424
|
+
for (const { data, h, w } of levels) {
|
|
425
|
+
const hw = h * w;
|
|
426
|
+
for (let c = 0; c < numClasses; c++) {
|
|
427
|
+
const plane = (4 + c) * hw;
|
|
428
|
+
for (let i = 0; i < hw; i++) {
|
|
429
|
+
const v = data[plane + i];
|
|
430
|
+
if (v > best[base + i]) best[base + i] = v;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
base += hw;
|
|
434
|
+
}
|
|
435
|
+
const order = topIndices(best, maxDet);
|
|
436
|
+
const count = Math.min(maxDet, total);
|
|
437
|
+
const outClasses = numClasses + 1;
|
|
438
|
+
const logits = new Float32Array(count * outClasses);
|
|
439
|
+
const boxes = new Float32Array(count * 4);
|
|
440
|
+
for (let k = 0; k < count; k++) {
|
|
441
|
+
let idx = order[k];
|
|
442
|
+
let level = 0;
|
|
443
|
+
while (idx >= levels[level].h * levels[level].w) {
|
|
444
|
+
idx -= levels[level].h * levels[level].w;
|
|
445
|
+
level++;
|
|
446
|
+
}
|
|
447
|
+
const { data, h, w, stride } = levels[level];
|
|
448
|
+
const hw = h * w;
|
|
449
|
+
for (let c = 0; c < numClasses; c++) logits[k * outClasses + c] = data[(4 + c) * hw + idx];
|
|
450
|
+
const ax = idx % w + .5;
|
|
451
|
+
const ay = Math.floor(idx / w) + .5;
|
|
452
|
+
const l = data[idx];
|
|
453
|
+
const t = data[hw + idx];
|
|
454
|
+
const r = data[2 * hw + idx];
|
|
455
|
+
const b = data[3 * hw + idx];
|
|
456
|
+
const scale = stride / inputSize;
|
|
457
|
+
boxes[k * 4] = (ax + (r - l) / 2) * scale;
|
|
458
|
+
boxes[k * 4 + 1] = (ay + (b - t) / 2) * scale;
|
|
459
|
+
boxes[k * 4 + 2] = (l + r) * scale;
|
|
460
|
+
boxes[k * 4 + 3] = (t + b) * scale;
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
logits,
|
|
464
|
+
boxes,
|
|
465
|
+
count,
|
|
466
|
+
outClasses
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
//#endregion
|
|
470
|
+
//#region src/zoo/transformers/yolo26/model.ts
|
|
471
|
+
var RunntimeYolosForObjectDetection = class extends PreTrainedModel {
|
|
472
|
+
runntime;
|
|
473
|
+
constructor(config, runntime) {
|
|
474
|
+
super(config, {}, {});
|
|
475
|
+
this.runntime = runntime;
|
|
476
|
+
}
|
|
477
|
+
/** transformers.js's release hook (`await model.dispose()`): frees the
|
|
478
|
+
* GPU weights. The ONNX-session array it normally returns is empty here. */
|
|
479
|
+
async dispose() {
|
|
480
|
+
this.runntime.dispose();
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
async forward(inputs) {
|
|
484
|
+
const pixels = inputs["pixel_values"];
|
|
485
|
+
if (!pixels) throw new Error("runntime backend: yolo26 forward() needs pixel_values");
|
|
486
|
+
const { inputSize: size, numClasses } = this.runntime;
|
|
487
|
+
const [n, c, h, w] = pixels.dims;
|
|
488
|
+
if (n !== 1 || c !== 3 || h !== size || w !== size) throw new Error(`runntime backend: yolo26 takes one ${size}×${size} RGB image per call, got pixel_values [${pixels.dims}]`);
|
|
489
|
+
const { levels } = await inGpuErrorScopes(defaultRoot().device, "inference", () => this.runntime.run(pixels.data));
|
|
490
|
+
const top = selectTopAnchors(levels, {
|
|
491
|
+
numClasses,
|
|
492
|
+
maxDet: this.runntime.maxDet,
|
|
493
|
+
inputSize: size
|
|
494
|
+
});
|
|
495
|
+
return {
|
|
496
|
+
logits: new Tensor("float32", top.logits, [
|
|
497
|
+
1,
|
|
498
|
+
top.count,
|
|
499
|
+
top.outClasses
|
|
500
|
+
]),
|
|
501
|
+
pred_boxes: new Tensor("float32", top.boxes, [
|
|
502
|
+
1,
|
|
503
|
+
top.count,
|
|
504
|
+
4
|
|
505
|
+
])
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region src/zoo/transformers/yolo26/loader.ts
|
|
511
|
+
/** The variant letter in a hub id such as `onnx-community/yolo26s-ONNX`. */
|
|
512
|
+
function yolo26Variant(modelId) {
|
|
513
|
+
const m = /yolo26([nsmlx])(?![a-z])/i.exec(modelId);
|
|
514
|
+
if (!m) throw new Error(`runntime backend: cannot tell the yolo26 variant from '${modelId}'`);
|
|
515
|
+
return m[1].toLowerCase();
|
|
516
|
+
}
|
|
517
|
+
/** The hosted f16 exports, by variant. */
|
|
518
|
+
const YOLO26_WEIGHTS_URLS = {
|
|
519
|
+
n: models.objectDetection.YOLO26.N.modelPath,
|
|
520
|
+
s: models.objectDetection.YOLO26.S.modelPath,
|
|
521
|
+
m: models.objectDetection.YOLO26.M.modelPath
|
|
522
|
+
};
|
|
523
|
+
/** Every hub id with a hosted export. */
|
|
524
|
+
const YOLO26_MODEL_IDS = [
|
|
525
|
+
"onnx-community/yolo26n-ONNX",
|
|
526
|
+
"onnx-community/yolo26s-ONNX",
|
|
527
|
+
"onnx-community/yolo26m-ONNX"
|
|
528
|
+
];
|
|
529
|
+
/** `weightsUrl` defaults to the hosted export of the variant in the hub
|
|
530
|
+
* id; other variants need one. */
|
|
531
|
+
function yolo26Loader(weightsUrl, opts = {}) {
|
|
532
|
+
return { async load(modelId, config) {
|
|
533
|
+
defaultRoot();
|
|
534
|
+
const variant = opts.variant ?? yolo26Variant(modelId);
|
|
535
|
+
const url = weightsUrl ?? YOLO26_WEIGHTS_URLS[variant];
|
|
536
|
+
if (!url) throw new Error(`runntime backend: no hosted yolo26${variant} weights, pass a weightsUrl`);
|
|
537
|
+
const source = await cachedWeightsSource(url, `whole/yolo26-v1/${cacheKey(url)}`);
|
|
538
|
+
return new RunntimeYolosForObjectDetection(config, {
|
|
539
|
+
...await createDetector(await fromSafetensors(source), {
|
|
540
|
+
task: "detect",
|
|
541
|
+
variant,
|
|
542
|
+
onProgress: opts.onProgress
|
|
543
|
+
}),
|
|
544
|
+
maxDet: 300
|
|
545
|
+
});
|
|
546
|
+
} };
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/zoo/transformers/depthart/model.ts
|
|
550
|
+
/** Depth-estimation stand-in running the zoo DepthART model. Takes the
|
|
551
|
+
* processor's normalized `pixel_values` [N, 3, H, W] and returns
|
|
552
|
+
* `predicted_depth` [N, S, S] at the model's own size; the stock
|
|
553
|
+
* post-processing resizes it to the image. Images run one at a time. */
|
|
554
|
+
var RunntimeDepthartForDepthEstimation = class extends PreTrainedModel {
|
|
555
|
+
runntime;
|
|
556
|
+
constructor(config, runntime) {
|
|
557
|
+
super(config, {}, {});
|
|
558
|
+
this.runntime = runntime;
|
|
559
|
+
}
|
|
560
|
+
async forward(inputs) {
|
|
561
|
+
const pixels = inputs["pixel_values"];
|
|
562
|
+
if (!pixels) throw new Error("runntime backend: depthart forward() needs pixel_values");
|
|
563
|
+
if (pixels.dims.length !== 4 || pixels.dims[1] !== 3) throw new Error(`runntime backend: depthart takes pixel_values [N, 3, H, W], got [${pixels.dims}]`);
|
|
564
|
+
const [n, , h, w] = pixels.dims;
|
|
565
|
+
const size = this.runntime.inputSize;
|
|
566
|
+
const plane = size * size;
|
|
567
|
+
const out = new Float32Array(n * plane);
|
|
568
|
+
const data = pixels.data;
|
|
569
|
+
const stride = 3 * h * w;
|
|
570
|
+
for (let i = 0; i < n; i++) {
|
|
571
|
+
const depth = await inGpuErrorScopes(defaultRoot().device, "inference", () => this.runntime.run(data.subarray(i * stride, (i + 1) * stride), h, w));
|
|
572
|
+
out.set(depth, i * plane);
|
|
573
|
+
}
|
|
574
|
+
return { predicted_depth: new Tensor("float32", out, [
|
|
575
|
+
n,
|
|
576
|
+
size,
|
|
577
|
+
size
|
|
578
|
+
]) };
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/zoo/transformers/depthart/loader.ts
|
|
583
|
+
/** The hosted f16 exports, keyed by the depth-estimation hub id they stand
|
|
584
|
+
* in for: one whose image processor normalizes with ImageNet statistics,
|
|
585
|
+
* since the pipeline keeps using that id's config and processor. */
|
|
586
|
+
const DEPTHART_WEIGHTS_URLS = {
|
|
587
|
+
"onnx-community/depth-anything-v2-small": models.depthEstimation.DEPTHART.S.modelPath,
|
|
588
|
+
"onnx-community/depth-anything-v2-base": models.depthEstimation.DEPTHART.B.modelPath
|
|
589
|
+
};
|
|
590
|
+
const DEPTHART_MODEL_IDS = Object.keys(DEPTHART_WEIGHTS_URLS);
|
|
591
|
+
/** `weightsUrl`: a safetensors export of a Fengxue93/DepthART relative
|
|
592
|
+
* checkpoint (tools/export_weights_depthart.py); defaults to the hosted
|
|
593
|
+
* export for the hub id. The variant is read from the file. */
|
|
594
|
+
function depthartLoader(weightsUrl, opts = {}) {
|
|
595
|
+
return { async load(modelId, config) {
|
|
596
|
+
const root = defaultRoot();
|
|
597
|
+
const url = weightsUrl ?? DEPTHART_WEIGHTS_URLS[modelId];
|
|
598
|
+
if (!url) throw new Error(`runntime backend: no hosted DepthART weights for '${modelId}'`);
|
|
599
|
+
const source = await cachedWeightsSource(url, `whole/depthart-v1/${cacheKey(url)}`);
|
|
600
|
+
const sd = await fromSafetensors(source);
|
|
601
|
+
return new RunntimeDepthartForDepthEstimation(config, await inGpuErrorScopes(root.device, "plugin warm-up", () => createEstimator(sd, { onProgress: opts.onProgress })));
|
|
602
|
+
} };
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/zoo/transformers/registry.ts
|
|
606
|
+
/** registerRunntimeBackend() patches PreTrainedModel.from_pretrained, the one
|
|
607
|
+
* loader that pipeline() and the AutoModel classes call, so registered hub
|
|
608
|
+
* ids load as TypeGPU zoo models. Tokenizers, processors and pipeline
|
|
609
|
+
* pre/post-processing stay stock transformers.js. Unregistered ids and
|
|
610
|
+
* loader failures throw; fallbackToOnnx warns and uses ONNX instead. */
|
|
611
|
+
const registry = /* @__PURE__ */ new Map();
|
|
612
|
+
let fallbackToOnnx = false;
|
|
613
|
+
let original;
|
|
614
|
+
function registerRunntimeBackend(opts = {}) {
|
|
615
|
+
registry.clear();
|
|
616
|
+
for (const id of MINILM_MODEL_IDS) registry.set(id, minilmLoader());
|
|
617
|
+
for (const id of PRIVACY_FILTER_MODEL_IDS) registry.set(id, privacyFilterLoader());
|
|
618
|
+
for (const id of MOONSHINE_MODEL_IDS) registry.set(id, moonshineLoader());
|
|
619
|
+
for (const id of MOBILENETV4_MODEL_IDS) registry.set(id, mobilenetv4Loader());
|
|
620
|
+
for (const id of YOLO26_MODEL_IDS) registry.set(id, yolo26Loader());
|
|
621
|
+
for (const id of DEPTHART_MODEL_IDS) registry.set(id, depthartLoader());
|
|
622
|
+
if (opts.models) for (const [id, loader] of Object.entries(opts.models)) registry.set(id, loader);
|
|
623
|
+
fallbackToOnnx = opts.fallbackToOnnx ?? false;
|
|
624
|
+
if (original) return;
|
|
625
|
+
original = PreTrainedModel.from_pretrained;
|
|
626
|
+
PreTrainedModel.from_pretrained = async function(...args) {
|
|
627
|
+
const [modelId, options = {}] = args;
|
|
628
|
+
const loader = typeof modelId === "string" ? registry.get(modelId) : void 0;
|
|
629
|
+
if (loader) try {
|
|
630
|
+
const config = await AutoConfig.from_pretrained(modelId, options);
|
|
631
|
+
return await loader.load(modelId, config);
|
|
632
|
+
} catch (e) {
|
|
633
|
+
if (!fallbackToOnnx) throw new Error(`runntime backend: loading '${modelId}' failed. Pass fallbackToOnnx: true to load it on ONNX instead.`, { cause: e });
|
|
634
|
+
console.warn(`runntime backend: loading '${modelId}' failed, falling back to ONNX`, e);
|
|
635
|
+
}
|
|
636
|
+
else if (!fallbackToOnnx) {
|
|
637
|
+
const ids = [...registry.keys()].join(", ");
|
|
638
|
+
throw new Error(`runntime backend: '${String(modelId)}' is not supported (registered ids: ${ids}). Pass fallbackToOnnx: true to load unsupported models on ONNX.`);
|
|
639
|
+
}
|
|
640
|
+
return original.call(this, ...args);
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function unregisterRunntimeBackend() {
|
|
644
|
+
if (original) {
|
|
645
|
+
PreTrainedModel.from_pretrained = original;
|
|
646
|
+
original = void 0;
|
|
647
|
+
}
|
|
648
|
+
registry.clear();
|
|
649
|
+
fallbackToOnnx = false;
|
|
650
|
+
}
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/zoo/transformers/init.ts
|
|
653
|
+
/** One-call plugin setup: creates the WebGPU device, points runntime/core at it
|
|
654
|
+
* and patches transformers.js. Requests subgroups, which the fast matmul
|
|
655
|
+
* and attention kernels need, and shader-f16, which yolo26 and depthart
|
|
656
|
+
* require and the other models use when present. Both are optional: a
|
|
657
|
+
* device lacking either still loads the f32-capable models. Options go
|
|
658
|
+
* straight to registerRunntimeBackend(), so `models` and `fallbackToOnnx` work
|
|
659
|
+
* here too. Returns the root for apps that also use TypeGPU directly. */
|
|
660
|
+
async function initRunntimeBackend(opts = {}) {
|
|
661
|
+
const root = await tgpu.init({ device: { optionalFeatures: ["subgroups", "shader-f16"] } });
|
|
662
|
+
initRunntime(root);
|
|
663
|
+
registerRunntimeBackend(opts);
|
|
664
|
+
return root;
|
|
665
|
+
}
|
|
666
|
+
//#endregion
|
|
667
|
+
//#region src/zoo/transformers/runntimeModel.ts
|
|
668
|
+
/** True when a model is one of the plugin's TypeGPU ones, not the ONNX
|
|
669
|
+
* fallback. */
|
|
670
|
+
function isRunntimeModel(model) {
|
|
671
|
+
return model instanceof RunntimeBertModel || model instanceof RunntimePrivacyFilterForTokenClassification || model instanceof RunntimeMoonshineForConditionalGeneration || model instanceof RunntimeYolosForObjectDetection || model instanceof RunntimeDepthartForDepthEstimation || model instanceof RunntimeMobileNetV4ForImageClassification;
|
|
672
|
+
}
|
|
673
|
+
//#endregion
|
|
674
|
+
export { depthartLoader, initRunntimeBackend, isRunntimeModel, minilmLoader, mobilenetv4Loader, moonshineLoader, privacyFilterLoader, registerRunntimeBackend, unregisterRunntimeBackend, yolo26Loader };
|