vmayoral commited on
Commit
2ab2e60
·
verified ·
1 Parent(s): 8119e95

alias3-web: 4-bit WebGPU graph, readout files, library and demo

Browse files
.gitattributes CHANGED
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/bis-overview.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/demo.png filter=lfs diff=lfs merge=lfs -text
38
+ model/model.onnx.data filter=lfs diff=lfs merge=lfs -text
39
+ model/tok/tokenizer.json filter=lfs diff=lfs merge=lfs -text
40
+ model/tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ base_model:
6
+ - aliasrobotics/alias3-micro-1026
7
+ base_model_relation: finetune
8
+ pipeline_tag: text-generation
9
+ library_name: onnx
10
+ tags:
11
+ - cybersecurity
12
+ - security
13
+ - onnx
14
+ - onnxruntime-genai
15
+ - webgpu
16
+ - browser
17
+ - int4
18
+ - dual-system
19
+ - decision-head
20
+ - system-1
21
+ - system-2
22
+ - phishing
23
+ - malware-detection
24
+ ---
25
+
26
+ # alias3-web
27
+
28
+ **A cybersecurity transformer that runs in the browser.** It answers a typed security question in about
29
+ 150 ms and writes an explanation on request, on the user's GPU through WebGPU. The page under test never
30
+ leaves the machine.
31
+
32
+ <p align="center">
33
+ <img src="assets/bis-overview.png" alt="alias3-web inside the Browser Immune System: a page is reduced to typed observations, the model answers one directed question per threat class, and explains a verdict on request" width="920">
34
+ </p>
35
+
36
+ alias3-web is the model behind the **Browser Immune System (BIS)**, a browser extension that screens every
37
+ page a user visits. Both are described in the paper *Browser Immune System (BIS): Innate and Adaptive
38
+ Threat Detection in the Browser with a Cybersecurity Transformer* (Mayoral-Vilches and Worrel, 2026), where
39
+ the model is called `alias-web`. The figure above is the paper's overview: the model sits in the centre lane.
40
+
41
+ It serves two regimes from one loaded set of 4-bit weights:
42
+
43
+ | Regime | What it does | Cost on a laptop GPU |
44
+ |---|---|---|
45
+ | **System 1** | One typed question, one forward pass, no token decoded: a probability per option. | 152 ms per question |
46
+ | **System 2** | Ordinary text generation from the same weights, on request. | 10.5 tokens/s |
47
+
48
+ > **Research artefact.** Released to make the paper reproducible. Read the limits below before relying on
49
+ > its answers.
50
+
51
+ ## What is in this repository
52
+
53
+ | Path | What it is |
54
+ |---|---|
55
+ | `model/model.onnx`, `model/model.onnx.data` | The 4-bit graph for WebGPU (ONNX Runtime GenAI, 2,440,274,944 bytes of weights). It returns logits and the hidden states at requested positions. |
56
+ | `model/tokenizer.json`, `model/tokenizer_config.json`, `model/chat_template.jinja`, `model/genai_config.json` | Tokenizer and runtime configuration. `model/tok/` and `model/heads/` hold copies where the runner looks for them. |
57
+ | `heads/head.bin` | The complete pointer head (1.3 M parameters, fp16, 2,622,545 bytes): weights and biases of both projections. |
58
+ | `heads/norm.bin` | The weight of the model's final RMSNorm (10,289 bytes). The graph exports hidden states before that norm and the head was trained after it. |
59
+ | `aliasweb.mjs` | The library: the typed encoding, the norm, the head, and the calls `ask()` and `generate()`. |
60
+ | `index.html`, `serve.py` | A demo page and a local server with HTTP Range support. |
61
+ | `vendor/ct0.js`, `vendor/ct0-edge.LICENSE` | The prefill engine: the distribution of [ct0-edge](https://github.com/eduardoworrel/ct0-edge) by Eduardo Worrel (MIT), unchanged. |
62
+
63
+ ## Quick start
64
+
65
+ ```bash
66
+ hf download aliasrobotics/alias3-web --local-dir alias3-web
67
+ cd alias3-web && python3 serve.py 8924
68
+ # open http://127.0.0.1:8924 in Chrome or Edge and press "Load model"
69
+ ```
70
+
71
+ The first load reads 2.44 GB from the local server and takes a few seconds. The demo page fetches its two
72
+ runtime modules (ONNX Runtime Web 1.30.0 and Transformers.js 3.7.5) from a public CDN; the model, the
73
+ material you paste and the answers stay on your machine.
74
+
75
+ From your own page, with the same import map as `index.html`:
76
+
77
+ ```js
78
+ import { AliasWeb } from "./aliasweb.mjs";
79
+ const model = await AliasWeb.load({ base: "./" });
80
+
81
+ // System 1: a directed yes/no question about a piece of evidence
82
+ const r = await model.ask({
83
+ kind: "noul",
84
+ state: `const q = new URLSearchParams(location.search).get("q");
85
+ document.getElementById("out").innerHTML = "result: " + q;`,
86
+ q: "Is user input written into the DOM without escaping?",
87
+ });
88
+ console.log(r.p, r.ms); // 0.822, about 150 ms
89
+
90
+ // System 2: an explanation, only when it is worth seconds
91
+ const t = await model.generate({
92
+ state: "...the same evidence...",
93
+ prompt: "You are a security reviewer. Name the vulnerability in this code and the fix, in 2-3 sentences.",
94
+ maxNewTokens: 90,
95
+ });
96
+ console.log(t.text, t.tokPerS);
97
+ ```
98
+
99
+ ## The typed interface
100
+
101
+ A decision is one causal sequence, `<state> s <q> q <opt> o1 </opt> … <opt> oK </opt> <decide>`. The markers
102
+ are not new vocabulary: they are five special tokens of the base tokenizer, and the library writes them by
103
+ id.
104
+
105
+ | Marker | Token | Id | Role |
106
+ |---|---|---|---|
107
+ | `<state>` | `<\|fim_prefix\|>` | 248060 | opens the evidence |
108
+ | `<q>` | `<\|fim_middle\|>` | 248061 | opens the question |
109
+ | `<opt>` | `<\|box_start\|>` | 248049 | opens an option |
110
+ | `</opt>` | `<\|box_end\|>` | 248050 | closes an option: the key is read here |
111
+ | `<decide>` | `<\|fim_suffix\|>` | 248062 | closes the row: the query is read here |
112
+
113
+ The head scores option *i* as `q(h_decide) · k(h_opt_i) / (sqrt(256) · T)` on the normalised hidden states
114
+ and a softmax over the options gives the probabilities. Text supplied by the caller is sanitised (a literal
115
+ `<|name|>` becomes `<¦name¦>`), so that the evidence cannot forge an option boundary.
116
+
117
+ | `kind` | Options | Returns | Status |
118
+ |---|---|---|---|
119
+ | `noul` | Yes, No | `p`, the probability of yes | the kind to use |
120
+ | `choice` | your list | `choice`, `top` | answers in the categories of this checkpoint's training, see the limits |
121
+ | `score` | 1 to 5 | `level`, `expected` | orders severity only weakly, see the limits |
122
+
123
+ ## Measurements
124
+
125
+ All on one Apple M3 Max laptop (128 GB) in Chrome with WebGPU, the weights served from the local server.
126
+
127
+ | Quantity | Value |
128
+ |---|---|
129
+ | Model ready, second load in the same browser | 2.33 s |
130
+ | System 1, per question on short code snippets | 152 ms mean of twelve (110 to 200 ms); the head itself takes 1.18 ms |
131
+ | System 2 | 10.5 tokens/s, about 96 ms per token |
132
+ | A generated answer against a decision on the same snippet | 6 to 39 times the cost |
133
+
134
+ One directed yes/no question per snippet:
135
+
136
+ | Snippet | Question | P(yes) |
137
+ |---|---|---|
138
+ | DOM XSS: `innerHTML` with a query parameter | Is user input written into the DOM without escaping? | **0.822** |
139
+ | SQL injection: string concatenation in `db.query` | Does this code concatenate user input into a SQL query? | **1.000** |
140
+ | Command injection: `exec("curl " + url)` | Does this code pass user input to a shell command? | **1.000** |
141
+ | Path traversal: `sendFile("/var/data/" + f)` | Can user input escape the intended directory in this file path? | 0.428 |
142
+ | Hard-coded secret: an API key in the source | Does this code contain a hardcoded secret? | **1.000** |
143
+ | Safe: pure arithmetic | Is user input written into the DOM without escaping? | **0.000** |
144
+
145
+ Before publication the files of this repository were loaded in Chrome for Testing 157 and asked the same
146
+ six questions: every probability above was reproduced to the third decimal. A first load in a fresh
147
+ browser profile took 2.5 to 4.0 s.
148
+
149
+ The question is the lever. On the DOM XSS snippet the generic question "Does this code contain a security
150
+ vulnerability?" returns 0.040 and the directed one 0.822: same head, same evidence. Ask one directed
151
+ question per class.
152
+
153
+ In BIS, a battery of seven such questions returned the expected page verdict on 7 of the 8 sites of the
154
+ paper's benchmark, in 0.9 to 6.4 s per page, with identical verdicts across samples and across Chrome and
155
+ Firefox.
156
+
157
+ ## Limits
158
+
159
+ - **WebGPU only.** The 4-bit graph has no CPU path. Measured in Chrome; Firefox Nightly with a window runs
160
+ it 5.9 times slower; Safari and Windows are unmeasured.
161
+ - **Text inside the evidence can move the answer.** In the paper's benchmark a comment that declared a
162
+ script harmless took a miner detection from 1.00 to 0.00. Treat the state as untrusted input.
163
+ - **Questions need calibration before they filter anything.** Two of the seven BIS questions, one about
164
+ scripts loaded from another server and one about keystroke capture asked of a login form, answer yes on
165
+ ordinary pages.
166
+ - **`choice` and `score` speak the language of this checkpoint's training,** the context categories of
167
+ capture-the-flag campaigns. Asked to pick among weakness classes, `choice` returns the same label for any
168
+ injectable snippet, and `score` is not a calibrated severity. Use `noul`.
169
+ - **System 2 needs a forced prefix.** The 4-bit checkpoint does not close its deliberation block on its
170
+ own; `generate()` starts the answer after the closing marker. A directive prompt and a temperature of 0.3
171
+ avoid narrated pseudo-deliberation.
172
+ - **Agreement with the full-precision weights was checked on one prompt,** where the 4-bit graph returned
173
+ the same decision direction. How often the two precisions agree is not measured.
174
+ - **One machine.** There is no measurement on a modest integrated GPU.
175
+
176
+ ## Provenance
177
+
178
+ - **Parent.** [alias3-micro](https://huggingface.co/aliasrobotics/alias3-micro-1026), a 4B cybersecurity
179
+ transformer on a `Qwen3.5-4B` backbone (32 layers, hidden width 2560, a 248,320-token vocabulary),
180
+ described in the first paper cited below.
181
+ - **What alias3-web adds to the weights.** Two rank-16 adapters, fused: the parent's decision adapter,
182
+ trained together with the pointer head, and a context-category sensor trained for two epochs on 12,576
183
+ replayed decision examples and 4,172 context-category examples, with the head frozen.
184
+ - **The graph.** ONNX Runtime GenAI model builder 0.18 with `-p int4 -e webgpu` and
185
+ `include_hidden_states=true exclude_mtp=true`, run on Linux x86_64, following the recipe of the edge
186
+ study by Eduardo Worrel that this work reproduces and extends.
187
+ - **Not included.** The fused full-precision checkpoint (8.4 GB) and the BIS extension are not part of this
188
+ repository.
189
+
190
+ ## License
191
+
192
+ Apache-2.0 for the model files, the library, the demo page and the server, as for the parent model. The
193
+ vendored runner in `vendor/` is MIT; its licence file is beside it.
194
+
195
+ ## Citation
196
+
197
+ ```bibtex
198
+ @article{mayoralvilches2026bis,
199
+ author = {Mayoral-Vilches, V{\'i}ctor and Worrel, Eduardo},
200
+ title = {Browser Immune System ({BIS}): Innate and Adaptive Threat Detection in the Browser with a Cybersecurity Transformer},
201
+ year = {2026}
202
+ }
203
+
204
+ @article{mayoralvilches2026transformer,
205
+ author = {Mayoral-Vilches, V{\'i}ctor},
206
+ title = {{CT0}: Cybersecurity Transformer},
207
+ journal = {arXiv preprint arXiv:2610.16550},
208
+ year = {2026}
209
+ }
210
+ ```
aliasweb.mjs ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // alias3-web (alias-web in the paper) · a cybersecurity transformer in the browser (WebGPU).
2
+ //
3
+ // Two regimes from one loaded 4-bit graph:
4
+ // System 1 ask() one typed question, one prefill, no token decoded: a probability per option
5
+ // System 2 generate() text from the same weights, token by token, on request
6
+ //
7
+ // The prefill engine is the vendored runner (vendor/ct0.js: ct0-edge by Eduardo Worrel, MIT, see
8
+ // vendor/ct0-edge.LICENSE), unchanged. This file adds the readout the model was trained with:
9
+ //
10
+ // · the System 1 markers as the model's own special tokens, by id:
11
+ // <state> = <|fim_prefix|> <q> = <|fim_middle|> <opt> = <|box_start|>
12
+ // </opt> = <|box_end|> <decide> = <|fim_suffix|>
13
+ // · the final RMSNorm (heads/norm.bin): the graph exports hidden states before it
14
+ // · the complete pointer head (heads/head.bin), read as in training:
15
+ // z_i = q(h_<decide>) · k(h_</opt>_i) / (sqrt(256) · T), softmax over the options
16
+ // · yes/no and severity as choices over ("Yes", "No") and ("1" … "5"): one mechanism for every question
17
+ //
18
+ // The page that imports this module needs an import map for the runner and its two runtimes; see index.html.
19
+
20
+ export { AliasWeb };
21
+
22
+ const MARKERS = {
23
+ state: "<|fim_prefix|>",
24
+ q: "<|fim_middle|>",
25
+ opt: "<|box_start|>",
26
+ optEnd: "<|box_end|>",
27
+ decide: "<|fim_suffix|>",
28
+ };
29
+ const LEVELS = ["1", "2", "3", "4", "5"];
30
+ export const DEFAULT_SCORE_Q = "How severe is the security risk in this content?";
31
+
32
+ // ── head.bin: [u32 lenJSON][json][fp16 q][fp16 qb][fp16 k][fp16 kb] ────────
33
+ function f16(h) {
34
+ const s = (h & 0x8000) >> 15, e = (h & 0x7c00) >> 10, f = h & 0x03ff;
35
+ if (e === 0) return (s ? -1 : 1) * Math.pow(2, -14) * (f / 1024);
36
+ if (e === 0x1f) return f ? NaN : s ? -Infinity : Infinity;
37
+ return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / 1024);
38
+ }
39
+ export function loadHead(buf) {
40
+ const dv = new DataView(buf);
41
+ const jl = dv.getUint32(0, true);
42
+ const meta = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, jl)));
43
+ let off = 4 + jl;
44
+ const take = (n) => { const f = new Float32Array(n); for (let i = 0; i < n; i++) f[i] = f16(dv.getUint16(off + i * 2, true)); off += n * 2; return f; };
45
+ const d = meta.headDim, h = meta.hiddenSize;
46
+ return { q: take(d * h), qb: take(d), k: take(d * h), kb: take(d), headDim: d, hiddenSize: h,
47
+ temperature: meta.temperature ?? 1.0, scale: meta.scale ?? 1 / Math.sqrt(d), scoreOffset: meta.score_offset ?? 0 };
48
+ }
49
+ // norm.bin: [u32 lenJSON][json {hiddenSize}][fp32 hiddenSize]
50
+ async function loadNorm(buf) {
51
+ const dv = new DataView(buf);
52
+ const jl = dv.getUint32(0, true);
53
+ const meta = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, jl)));
54
+ const n = meta.hiddenSize, off = 4 + jl, f = new Float32Array(n);
55
+ for (let i = 0; i < n; i++) f[i] = dv.getFloat32(off + i * 4, true);
56
+ return f;
57
+ }
58
+ function proj(W, b, x, dim, hidden) {
59
+ const y = new Float32Array(dim);
60
+ for (let d = 0; d < dim; d++) { let a = b[d]; const r = d * hidden; for (let i = 0; i < hidden; i++) a += W[r + i] * x[i]; y[d] = a; }
61
+ return y;
62
+ }
63
+ const dot = (a, b) => { let s = 0; for (let i = 0; i < a.length; i++) s += a[i] * b[i]; return s; };
64
+ const headLogit = (hd, hD, hO) => dot(proj(hd.q, hd.qb, hD, hd.headDim, hd.hiddenSize), proj(hd.k, hd.kb, hO, hd.headDim, hd.hiddenSize)) * hd.scale / hd.temperature + (hd.scoreOffset || 0);
65
+ const softmax = (xs) => {
66
+ const mx = Math.max(...xs.filter(Number.isFinite));
67
+ const e = xs.map((x) => (Number.isFinite(x) ? Math.exp(x - mx) : 0));
68
+ const z = e.reduce((a, b) => a + b, 0) || 1;
69
+ return e.map((v) => v / z);
70
+ };
71
+
72
+ // text supplied by the caller can never produce a delimiter: <|name|> becomes <¦name¦>, as in training
73
+ const SPECIAL_RE = /<\|([A-Za-z0-9_]+)\|>/g;
74
+ const sanitize = (t) => t.replace(SPECIAL_RE, "<¦$1¦>");
75
+
76
+ class AliasWeb {
77
+ constructor(tok, prefill, head, mid, normW) { this.tok = tok; this.prefill = prefill; this.head = head; this.mid = mid; this.normW = normW; }
78
+
79
+ // the graph exports hidden_states before the final norm; the head was trained on the normalised
80
+ // state, so the model's final RMSNorm is applied here
81
+ norm(h) {
82
+ if (!this.normW) return h;
83
+ let s = 0; for (let i = 0; i < h.length; i++) s += h[i] * h[i];
84
+ const inv = 1 / Math.sqrt(s / h.length);
85
+ const o = new Float32Array(h.length);
86
+ for (let i = 0; i < h.length; i++) o[i] = h[i] * inv * this.normW[i];
87
+ return o;
88
+ }
89
+
90
+ static async load(opts = {}) {
91
+ const base = opts.base ?? "./";
92
+ const status = opts.status ?? (() => { });
93
+ status("loading the runner and the 4-bit graph (2.4 GB)…");
94
+ const edge = await import("ct0-edge");
95
+ const inner = await edge.CT0.load({
96
+ runtime: "genai",
97
+ genaiBaseUrl: base + "model/",
98
+ genaiModel: "model.onnx",
99
+ baseUrl: base + "model/",
100
+ status: () => { },
101
+ verifyEp: false, // placement was verified once against this graph in Chrome
102
+ skipVersionCheck: true, // the import map pins the ONNX Runtime version
103
+ genai: { warmup: false },
104
+ });
105
+ const be = inner.backend ?? inner.be;
106
+ if (!be?.prefill) throw new Error("the runner did not expose its prefill backend");
107
+ status("loading the head and the final norm…");
108
+ const head = loadHead(await (await fetch(base + "heads/head.bin")).arrayBuffer());
109
+ const normW = await loadNorm(await (await fetch(base + "heads/norm.bin")).arrayBuffer());
110
+ // marker ids from the tokenizer's added_tokens
111
+ const tk = await (await fetch(base + "model/tokenizer.json")).json();
112
+ const added = new Map((tk.added_tokens ?? []).map((t) => [t.content, t.id]));
113
+ const mid = {};
114
+ for (const [k, m] of Object.entries(MARKERS)) {
115
+ const id = added.get(m);
116
+ if (typeof id !== "number") throw new Error("marker without a token id: " + m);
117
+ mid[k] = id;
118
+ }
119
+ const model = new AliasWeb(inner.tok?.tokenizer ?? inner.tok, (ids, positions) => be.prefill(ids, { positions }), head, mid, normW);
120
+ model.be = be; model.base = base;
121
+ status("warm-up…");
122
+ const w = model.encode("warmup", "ok?", ["Yes", "No"]);
123
+ await model.prefill(w.ids, []);
124
+ status("ready");
125
+ return model;
126
+ }
127
+
128
+ encode(state, question, options) {
129
+ const t = this.tok;
130
+ const idsOf = (text) => Array.from(t.encode(sanitize(text), { add_special_tokens: false }), Number);
131
+ const ids = [this.mid.state, ...idsOf(state), this.mid.q, ...idsOf(question)];
132
+ const optEnds = [];
133
+ for (const o of options) {
134
+ ids.push(this.mid.opt, ...idsOf(o));
135
+ optEnds.push(ids.length);
136
+ ids.push(this.mid.optEnd);
137
+ }
138
+ ids.push(this.mid.decide);
139
+ return { ids, decide: ids.length - 1, optEnds };
140
+ }
141
+
142
+ async ask(q) {
143
+ const t0 = performance.now();
144
+ let options, question, labels;
145
+ if (q.kind === "noul") { options = ["Yes", "No"]; question = q.q ?? ""; labels = ["yes", "no"]; }
146
+ else if (q.kind === "score") { options = LEVELS; question = q.q ?? DEFAULT_SCORE_Q; labels = LEVELS; }
147
+ else { options = q.options; question = q.q ?? ""; labels = options; }
148
+ const enc = this.encode(q.state, question, options);
149
+ const p0 = performance.now();
150
+ const { hiddens } = await this.prefill(enc.ids, [enc.decide, ...enc.optEnds]);
151
+ const prefillMs = performance.now() - p0;
152
+ const hDecide = this.norm(hiddens[0]), perOpt = hiddens.slice(1).map((h) => this.norm(h));
153
+ const raws = perOpt.map((h) => headLogit(this.head, hDecide, h));
154
+ const ps = softmax(raws);
155
+ const out = { kind: q.kind, ms: performance.now() - t0, prefillMs, nTokens: enc.ids.length, raws: {} };
156
+ labels.forEach((l, i) => out.raws[l] = raws[i]);
157
+ if (q.kind === "noul") out.p = ps[0];
158
+ else if (q.kind === "score") {
159
+ out.per = Object.fromEntries(labels.map((l, i) => [l, ps[i]]));
160
+ out.level = 1 + ps.indexOf(Math.max(...ps));
161
+ out.expected = ps.reduce((a, p, i) => a + p * (i + 1), 0);
162
+ } else {
163
+ out.choice = Object.fromEntries(labels.map((l, i) => [l, ps[i]]));
164
+ out.top = labels[ps.indexOf(Math.max(...ps))];
165
+ out.p = Math.max(...ps);
166
+ }
167
+ return out;
168
+ }
169
+
170
+ // ── System 2: text from the same 4-bit weights ─────────────────────────
171
+ // The graph exposes logits; the runner's backend samples from them (end tokens <|im_end|> and
172
+ // <|endoftext|>).
173
+ async generate(q) {
174
+ const t0 = performance.now();
175
+ const state = q.state ?? "";
176
+ const prompt = (q.prompt ?? "").trim();
177
+ const user = state ? prompt + "\n\n" + state : prompt;
178
+ const t = this.tok;
179
+ const idsOf = (text) => Array.from(t.encode(sanitize(text), { add_special_tokens: false }), Number);
180
+ if (!this._added) {
181
+ const tk = await (await fetch(this.base + "model/tokenizer.json")).json();
182
+ this._added = new Map((tk.added_tokens ?? []).map((x) => [x.content, x.id]));
183
+ }
184
+ const imS = this._added.get("<|im_start|>"), imE = this._added.get("<|im_end|>");
185
+ // The base family deliberates by default and this checkpoint in 4-bit does not close the block:
186
+ // the assistant turn is forced to start after the closing marker. The special tokens go in by id,
187
+ // never through idsOf(): the sanitiser would rewrite them.
188
+ const body = idsOf("user\n" + user);
189
+ const asst = q.forceNoThink === false
190
+ ? [imS, ...idsOf("assistant\n")]
191
+ : [imS, ...idsOf("assistant\n</think>\n\n")];
192
+ const ids = [imS, ...body, imE, ...asst];
193
+ const out = [];
194
+ const tt0 = performance.now();
195
+ for await (const tokId of this.be.generate(ids, {
196
+ maxNewTokens: q.maxNewTokens ?? 96,
197
+ temperature: q.temperature ?? 0.7,
198
+ topP: q.topP ?? 0.9,
199
+ })) {
200
+ out.push(tokId);
201
+ if (q.onToken) q.onToken(t.decode([tokId], { skip_special_tokens: true }), out.length);
202
+ }
203
+ const genMs = performance.now() - tt0;
204
+ return {
205
+ text: t.decode(out, { skip_special_tokens: true, clean_up_tokenization_spaces: false }).trim(),
206
+ tokens: out.length, genMs,
207
+ tokPerS: +(out.length / (genMs / 1000)).toFixed(1),
208
+ ms: performance.now() - t0,
209
+ };
210
+ }
211
+ }
assets/bis-overview.png ADDED

Git LFS Details

  • SHA256: 0847882fe90742ac01fddb720c85546ea16691a5c092c5cac8e8e5d46bbb9a79
  • Pointer size: 131 Bytes
  • Size of remote file: 318 kB
assets/demo.png ADDED

Git LFS Details

  • SHA256: ac77a1bb8abb75ba44b5805997609b135219e6a8df2fb74a8d44b9a96eb5e744
  • Pointer size: 131 Bytes
  • Size of remote file: 204 kB
heads/head.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f586cb80f9f369c0d2ca00763e9402a883d7c759b712d2f81434a6a2e7e7495b
3
+ size 2622545
heads/norm.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a0ae161d2fa1902a031434126a904fe665a5ebff488b8740d0de495b93b90ce
3
+ size 10289
index.html ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>alias3-web · a cybersecurity transformer in your browser</title>
7
+ <style>
8
+ :root { --ink:#16202A; --muted:#5B6875; --rule:#D6DCE2; --bg:#F4F6F8; --card:#FFF;
9
+ --accent:#1F4E8C; --accent-soft:#E3EBF6; --good:#0ca30c; --mono:ui-monospace,Menlo,Consolas,monospace; }
10
+ * { box-sizing:border-box }
11
+ body { margin:0; background:var(--bg); color:var(--ink);
12
+ font:15px/1.5 -apple-system,"Segoe UI",Roboto,sans-serif; }
13
+ main { max-width:900px; margin:0 auto; padding:28px 18px 80px }
14
+ h1 { font-size:1.5rem; margin:.2em 0 .3em }
15
+ h1 small { color:var(--muted); font-weight:400; font-size:.95rem }
16
+ .sub { color:var(--muted); margin:0 0 18px }
17
+ .card { background:var(--card); border:1px solid var(--rule); border-radius:12px; padding:16px 18px; margin:14px 0 }
18
+ label { font-size:.85rem; color:var(--muted); display:block; margin-bottom:4px }
19
+ textarea, input[type=text] { width:100%; border:1px solid var(--rule); border-radius:8px; padding:8px 10px;
20
+ font:12.5px/1.45 var(--mono); background:#fbfcfd; color:var(--ink) }
21
+ textarea { min-height:120px; resize:vertical }
22
+ .row { display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end }
23
+ .row > div { flex:1; min-width:180px }
24
+ button { background:var(--accent); color:#fff; border:0; border-radius:8px; padding:10px 18px;
25
+ font-size:.95rem; cursor:pointer; margin:12px 8px 0 0 }
26
+ button.secondary { background:var(--accent-soft); color:var(--accent) }
27
+ button:disabled { opacity:.45; cursor:wait }
28
+ #status { font-size:.9rem; color:var(--muted); margin-top:10px }
29
+ .result { margin-top:14px }
30
+ .bar-row { display:flex; align-items:center; gap:10px; margin:5px 0 }
31
+ .bar-label { width:190px; font-size:.85rem; text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
32
+ .bar-track { flex:1; background:#eef1f4; border-radius:6px; height:20px; position:relative }
33
+ .bar-fill { height:100%; border-radius:6px; background:var(--accent); transition:width .25s }
34
+ .bar-fill.win { background:var(--good) }
35
+ .bar-val { width:64px; font-size:.85rem; font-family:var(--mono) }
36
+ .kpis { display:flex; gap:12px; flex-wrap:wrap; margin-top:12px }
37
+ .kpi { background:var(--accent-soft); border-radius:10px; padding:10px 14px; min-width:120px }
38
+ .kpi b { display:block; font-size:1.5rem; color:var(--accent) }
39
+ .kpi span { font-size:.8rem; color:var(--muted) }
40
+ .pill { display:inline-block; background:var(--accent-soft); color:var(--accent); border-radius:20px;
41
+ padding:2px 10px; font-size:.78rem; margin:2px 4px 2px 0; font-family:var(--mono) }
42
+ details { margin-top:10px } summary { cursor:pointer; color:var(--muted); font-size:.85rem }
43
+ pre { background:#eef1f4; border-radius:8px; padding:10px; font-size:.75rem; overflow:auto }
44
+ .err { color:#b00020; font-family:var(--mono); font-size:.8rem; margin-top:8px; white-space:pre-wrap }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <main>
49
+ <h1>alias3-web <small>· typed security decisions and explanations, on your GPU (WebGPU)</small></h1>
50
+ <p class="sub">A 4B cybersecurity transformer quantised to 4 bits (ONNX Runtime GenAI, 2.44 GB) with the
51
+ <b>complete trained head</b> and the <b>model's own marker tokens</b>. Nothing leaves this page.
52
+ Prefill engine: <a href="https://github.com/eduardoworrel/ct0-edge">ct0-edge</a> by Eduardo Worrel (MIT), vendored.</p>
53
+
54
+ <div class="card">
55
+ <div class="row">
56
+ <div><label>State (the material to judge: code, a log, the source of a page…)</label>
57
+ <textarea id="state">const q = new URLSearchParams(location.search).get("q");
58
+ document.getElementById("out").innerHTML = "result: " + q;</textarea></div>
59
+ </div>
60
+ <div class="row" style="margin-top:10px">
61
+ <div><label>Question (yes/no and choice)</label>
62
+ <input type="text" id="q" value="Is user input written into the DOM without escaping?"></div>
63
+ <div><label>Options (choice, separated by ;)</label>
64
+ <input type="text" id="opts" value="dom-xss; sql-injection; ssrf; path-traversal"></div>
65
+ </div>
66
+ <button id="btn-load" class="secondary">1 · Load model (2.4 GB)</button>
67
+ <button id="btn-noul" disabled>Yes / No</button>
68
+ <button id="btn-choice" disabled>Choice</button>
69
+ <button id="btn-score" disabled>Severity 1-5</button>
70
+ <button id="btn-s2" disabled>Explanation (System 2)</button>
71
+ <div id="status">Press “Load model”. Needs Chrome or Edge with WebGPU.</div>
72
+ <div id="err" class="err"></div>
73
+ <details>
74
+ <summary>the marker tokens and the readout</summary>
75
+ <span class="pill">&lt;state&gt;→&lt;|fim_prefix|&gt;</span><span class="pill">&lt;q&gt;→&lt;|fim_middle|&gt;</span>
76
+ <span class="pill">&lt;opt&gt;→&lt;|box_start|&gt;</span><span class="pill">&lt;/opt&gt;→&lt;|box_end|&gt;</span>
77
+ <span class="pill">&lt;decide&gt;→&lt;|fim_suffix|&gt;</span>
78
+ <p style="font-size:.82rem;color:var(--muted)">readout: q(h&lt;decide&gt;)·k(h&lt;/opt&gt;ᵢ)·√256⁻¹/T, softmax over the options.
79
+ A yes/no question is a choice over (Yes, No) and severity a choice over (1..5): one mechanism, as trained.</p>
80
+ </details>
81
+ </div>
82
+
83
+ <div class="card result" id="result" style="display:none">
84
+ <div class="kpis">
85
+ <div class="kpi"><b id="k-top">·</b><span id="k-top-lbl">answer</span></div>
86
+ <div class="kpi"><b id="k-ms">·</b><span>total latency</span></div>
87
+ <div class="kpi"><b id="k-pref">·</b><span>prefill</span></div>
88
+ <div class="kpi"><b id="k-tok">·</b><span>tokens</span></div>
89
+ </div>
90
+ <div id="bars"></div>
91
+ <details><summary>raw</summary><pre id="raw"></pre></details>
92
+ </div>
93
+ </main>
94
+
95
+ <script type="importmap">
96
+ { "imports": {
97
+ "ct0-edge": "./vendor/ct0.js",
98
+ "@huggingface/transformers": "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.5",
99
+ "onnxruntime-web/webgpu": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.30.0/dist/ort.webgpu.bundle.min.mjs",
100
+ "onnxruntime-web": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.30.0/dist/ort.webgpu.bundle.min.mjs"
101
+ } }
102
+ </script>
103
+ <script type="module">
104
+ import { AliasWeb } from "./aliasweb.mjs";
105
+ const $ = (id) => document.getElementById(id);
106
+ let model = null;
107
+
108
+ function bars(entries) {
109
+ $("bars").innerHTML = "";
110
+ const max = Math.max(...entries.map(([, v]) => v));
111
+ for (const [label, v] of entries) {
112
+ const row = document.createElement("div"); row.className = "bar-row";
113
+ const l = document.createElement("div"); l.className = "bar-label"; l.textContent = label; l.title = label;
114
+ const t = document.createElement("div"); t.className = "bar-track";
115
+ const f = document.createElement("div"); f.className = "bar-fill" + (v === max ? " win" : "");
116
+ f.style.width = (v * 100).toFixed(1) + "%";
117
+ const val = document.createElement("div"); val.className = "bar-val"; val.textContent = (v * 100).toFixed(1) + "%";
118
+ t.appendChild(f); row.append(l, t, val); $("bars").appendChild(row);
119
+ }
120
+ }
121
+
122
+ function show(r, topLabel, tok) {
123
+ $("result").style.display = "";
124
+ $("k-top").textContent = topLabel;
125
+ $("k-top-lbl").textContent = r.kind === "noul" ? "P(yes)" : r.kind === "score" ? "level" : "top";
126
+ $("k-ms").textContent = r.ms.toFixed(0) + " ms";
127
+ $("k-pref").textContent = r.prefillMs.toFixed(0) + " ms";
128
+ $("k-tok").textContent = tok;
129
+ const entries = r.kind === "noul" ? [["Yes", r.p], ["No", 1 - r.p]]
130
+ : r.kind === "score" ? Object.entries(r.per).map(([l, p]) => ["level " + l, p])
131
+ : Object.entries(r.choice);
132
+ bars(entries);
133
+ $("raw").textContent = JSON.stringify({ ...r, lastLogits: undefined }, null, 1).slice(0, 2000);
134
+ }
135
+
136
+ async function guard(fn) {
137
+ $("err").textContent = "";
138
+ if (!model) { $("err").textContent = "load the model first"; return; }
139
+ try { await fn(); } catch (e) { $("err").textContent = String(e?.stack || e); }
140
+ }
141
+
142
+ $("btn-load").onclick = async () => {
143
+ $("btn-load").disabled = true; $("err").textContent = "";
144
+ try {
145
+ model = await AliasWeb.load({ base: "./", status: (s) => $("status").textContent = s });
146
+ window.aliasweb = model;
147
+ for (const b of ["btn-noul", "btn-choice", "btn-score", "btn-s2"]) $(b).disabled = false;
148
+ $("status").textContent = "model ready · WebGPU active";
149
+ } catch (e) { $("err").textContent = String(e?.stack || e); $("btn-load").disabled = false; }
150
+ };
151
+
152
+ $("btn-noul").onclick = () => guard(async () => {
153
+ const state = $("state").value, q = $("q").value;
154
+ const r = await model.ask({ kind: "noul", state, q });
155
+ const enc = model.encode(state, q, ["Yes", "No"]);
156
+ show(r, (r.p * 100).toFixed(1) + "%", enc.ids.length);
157
+ });
158
+
159
+ $("btn-choice").onclick = () => guard(async () => {
160
+ const state = $("state").value;
161
+ const options = $("opts").value.split(";").map((s) => s.trim()).filter(Boolean);
162
+ const q = "Which vulnerability class does this code contain?";
163
+ const r = await model.ask({ kind: "choice", state, q, options });
164
+ const enc = model.encode(state, q, options);
165
+ show(r, r.top, enc.ids.length);
166
+ });
167
+
168
+ $("btn-s2").onclick = () => guard(async () => {
169
+ const state = $("state").value;
170
+ $("status").textContent = "System 2 is writing (about 10 tokens/s)…";
171
+ const r = await model.generate({
172
+ state,
173
+ prompt: "You are a security reviewer. Name the vulnerability in this code and the fix, in 2-3 sentences.",
174
+ maxNewTokens: 90, temperature: 0.7,
175
+ });
176
+ $("status").textContent = "model ready · WebGPU active";
177
+ $("result").style.display = "";
178
+ $("k-top").textContent = r.tokens + " tok";
179
+ $("k-top-lbl").textContent = "S2 · " + r.tokPerS + " tok/s";
180
+ $("k-ms").textContent = (r.ms / 1000).toFixed(1) + " s";
181
+ $("k-pref").textContent = "≈" + (r.ms / r.tokens).toFixed(0) + " ms/tok";
182
+ $("k-tok").textContent = "vs 0.15 s S1";
183
+ $("bars").innerHTML = "";
184
+ const p = document.createElement("p");
185
+ p.style.cssText = "font-size:.95rem;line-height:1.6";
186
+ p.textContent = r.text;
187
+ $("bars").appendChild(p);
188
+ $("raw").textContent = JSON.stringify({ tokens: r.tokens, tokPerS: r.tokPerS, ms: Math.round(r.ms) }, null, 1);
189
+ });
190
+
191
+ $("btn-score").onclick = () => guard(async () => {
192
+ const state = $("state").value;
193
+ const r = await model.ask({ kind: "score", state });
194
+ const enc = model.encode(state, "", ["1", "2", "3", "4", "5"]);
195
+ show(r, r.level + " / 5", enc.ids.length);
196
+ });
197
+ </script>
198
+ </body>
199
+ </html>
model/chat_template.jinja ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- set num_sys = 0 %}
46
+ {%- set merged_system = '' %}
47
+ {%- if messages[0].role == 'system' or messages[0].role == 'developer' %}
48
+ {%- set first = render_content(messages[0].content, false, true)|trim %}
49
+ {%- if messages|length > 1 and (messages[1].role == 'system' or messages[1].role == 'developer') %}
50
+ {%- set second = render_content(messages[1].content, false, true)|trim %}
51
+ {%- set merged_system = first + '\n' + second %}
52
+ {%- set num_sys = 2 %}
53
+ {%- else %}
54
+ {%- set merged_system = first %}
55
+ {%- set num_sys = 1 %}
56
+ {%- endif %}
57
+ {%- endif %}
58
+ {%- if tools and tools is iterable and tools is not mapping %}
59
+ {{- '<|im_start|>system\n' }}
60
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
61
+ {%- for tool in tools %}
62
+ {{- "\n" }}
63
+ {{- tool | tojson }}
64
+ {%- endfor %}
65
+ {{- "\n</tools>" }}
66
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
67
+ {%- if merged_system %}
68
+ {{- '\n\n' + merged_system }}
69
+ {%- endif %}
70
+ {{- '<|im_end|>\n' }}
71
+ {%- else %}
72
+ {%- if merged_system %}
73
+ {{- '<|im_start|>system\n' + merged_system + '<|im_end|>\n' }}
74
+ {%- endif %}
75
+ {%- endif %}
76
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
77
+ {%- for message in messages[::-1] %}
78
+ {%- set index = (messages|length - 1) - loop.index0 %}
79
+ {%- if ns.multi_step_tool and message.role == "user" %}
80
+ {%- set content = render_content(message.content, false)|trim %}
81
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
82
+ {%- set ns.multi_step_tool = false %}
83
+ {%- set ns.last_query_index = index %}
84
+ {%- endif %}
85
+ {%- endif %}
86
+ {%- endfor %}
87
+ {%- for message in messages %}
88
+ {%- if loop.index0 >= num_sys and message.role != "system" and message.role != "developer" %}
89
+ {%- set content = render_content(message.content, true)|trim %}
90
+ {%- if message.role == "user" %}
91
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
92
+ {%- elif message.role == "assistant" %}
93
+ {%- set reasoning_content = '' %}
94
+ {%- if message.reasoning_content is string %}
95
+ {%- set reasoning_content = message.reasoning_content %}
96
+ {%- else %}
97
+ {%- if '</think>' in content %}
98
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
99
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
100
+ {%- endif %}
101
+ {%- endif %}
102
+ {%- set reasoning_content = reasoning_content|trim %}
103
+ {%- if loop.index0 > ns.last_query_index %}
104
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
105
+ {%- else %}
106
+ {{- '<|im_start|>' + message.role + '\n' + content }}
107
+ {%- endif %}
108
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
109
+ {%- for tool_call in message.tool_calls %}
110
+ {%- if tool_call.function is defined %}
111
+ {%- set tool_call = tool_call.function %}
112
+ {%- endif %}
113
+ {%- if loop.first %}
114
+ {%- if content|trim %}
115
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
116
+ {%- else %}
117
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
118
+ {%- endif %}
119
+ {%- else %}
120
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
121
+ {%- endif %}
122
+ {%- if tool_call.arguments is mapping %}
123
+ {%- for args_name in tool_call.arguments %}
124
+ {%- set args_value = tool_call.arguments[args_name] %}
125
+ {{- '<parameter=' + args_name + '>\n' }}
126
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
127
+ {{- args_value }}
128
+ {{- '\n</parameter>\n' }}
129
+ {%- endfor %}
130
+ {%- endif %}
131
+ {{- '</function>\n</tool_call>' }}
132
+ {%- endfor %}
133
+ {%- endif %}
134
+ {{- '<|im_end|>\n' }}
135
+ {%- elif message.role == "tool" %}
136
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
137
+ {{- '<|im_start|>user' }}
138
+ {%- endif %}
139
+ {{- '\n<tool_response>\n' }}
140
+ {{- content }}
141
+ {{- '\n</tool_response>' }}
142
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
143
+ {{- '<|im_end|>\n' }}
144
+ {%- elif loop.last %}
145
+ {{- '<|im_end|>\n' }}
146
+ {%- endif %}
147
+ {%- endif %}
148
+ {%- endif %}
149
+ {%- endfor %}
150
+ {%- if add_generation_prompt %}
151
+ {{- '<|im_start|>assistant\n' }}
152
+ {%- if enable_thinking is defined and enable_thinking is false %}
153
+ {{- '<think>\n\n</think>\n\n' }}
154
+ {%- else %}
155
+ {{- '<think>\n' }}
156
+ {%- endif %}
157
+ {%- endif %}
158
+ {#- Unsloth fixes - developer role, tool calling #}
model/genai_config.json ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": {
3
+ "bos_token_id": 1,
4
+ "context_length": 262144,
5
+ "decoder": {
6
+ "session_options": {
7
+ "log_id": "onnxruntime-genai",
8
+ "provider_options": [
9
+ {
10
+ "webgpu": {
11
+ "enableGraphCapture": "0",
12
+ "validationMode": "basic"
13
+ }
14
+ }
15
+ ]
16
+ },
17
+ "filename": "model.onnx",
18
+ "head_size": 256,
19
+ "hidden_size": 2560,
20
+ "inputs": {
21
+ "input_ids": "input_ids",
22
+ "attention_mask": "attention_mask",
23
+ "position_ids": "position_ids",
24
+ "past_key_names": "past_key_values.%d.key",
25
+ "past_value_names": "past_key_values.%d.value",
26
+ "past_conv_names": "past.%d.conv",
27
+ "past_recurrent_names": "past.%d.recurrent"
28
+ },
29
+ "outputs": {
30
+ "logits": "logits",
31
+ "present_key_names": "present.%d.key",
32
+ "present_value_names": "present.%d.value",
33
+ "present_conv_names": "present.%d.conv",
34
+ "present_recurrent_names": "present.%d.recurrent"
35
+ },
36
+ "num_attention_heads": 16,
37
+ "num_hidden_layers": 32,
38
+ "num_key_value_heads": 4
39
+ },
40
+ "eos_token_id": [
41
+ 248046,
42
+ 248044
43
+ ],
44
+ "pad_token_id": 248055,
45
+ "type": "qwen3_5_text",
46
+ "vocab_size": 248320,
47
+ "bot_token_id": 248058,
48
+ "eot_token_id": 248059,
49
+ "bor_token_id": 248068,
50
+ "eor_token_id": 248069
51
+ },
52
+ "search": {
53
+ "diversity_penalty": 0.0,
54
+ "do_sample": false,
55
+ "early_stopping": true,
56
+ "length_penalty": 1.0,
57
+ "max_length": 262144,
58
+ "min_length": 0,
59
+ "no_repeat_ngram_size": 0,
60
+ "num_beams": 1,
61
+ "num_return_sequences": 1,
62
+ "past_present_share_buffer": true,
63
+ "repetition_penalty": 1.0,
64
+ "temperature": 1.0,
65
+ "top_k": 50,
66
+ "top_p": 1.0
67
+ }
68
+ }
model/heads/head.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f586cb80f9f369c0d2ca00763e9402a883d7c759b712d2f81434a6a2e7e7495b
3
+ size 2622545
model/model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:94d67f6e1777aa45e6e99ef39df9c2b568801ab8c5eda91c697b18d8a8f0885d
3
+ size 489574
model/model.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6563d99a1dc847ea4ce02e0ff51da15b88fe532fae4cb3b2f6a24f0d988e0a0e
3
+ size 2440274944
model/tok/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
3
+ size 19989325
model/tok/tokenizer_config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": null,
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "image_token": "<|image_pad|>",
12
+ "is_local": true,
13
+ "local_files_only": false,
14
+ "model_max_length": 262144,
15
+ "model_specific_special_tokens": {
16
+ "audio_bos_token": "<|audio_start|>",
17
+ "audio_eos_token": "<|audio_end|>",
18
+ "audio_token": "<|audio_pad|>",
19
+ "image_token": "<|image_pad|>",
20
+ "video_token": "<|video_pad|>",
21
+ "vision_bos_token": "<|vision_start|>",
22
+ "vision_eos_token": "<|vision_end|>"
23
+ },
24
+ "pad_token": "<|vision_pad|>",
25
+ "padding_side": "left",
26
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
27
+ "processor_class": "Qwen3VLProcessor",
28
+ "split_special_tokens": false,
29
+ "tokenizer_class": "Qwen2Tokenizer",
30
+ "unk_token": null,
31
+ "video_token": "<|video_pad|>",
32
+ "vision_bos_token": "<|vision_start|>",
33
+ "vision_eos_token": "<|vision_end|>"
34
+ }
model/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
3
+ size 19989325
model/tokenizer_config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": null,
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "image_token": "<|image_pad|>",
12
+ "is_local": true,
13
+ "local_files_only": false,
14
+ "model_max_length": 262144,
15
+ "model_specific_special_tokens": {
16
+ "audio_bos_token": "<|audio_start|>",
17
+ "audio_eos_token": "<|audio_end|>",
18
+ "audio_token": "<|audio_pad|>",
19
+ "image_token": "<|image_pad|>",
20
+ "video_token": "<|video_pad|>",
21
+ "vision_bos_token": "<|vision_start|>",
22
+ "vision_eos_token": "<|vision_end|>"
23
+ },
24
+ "pad_token": "<|vision_pad|>",
25
+ "padding_side": "left",
26
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
27
+ "processor_class": "Qwen3VLProcessor",
28
+ "split_special_tokens": false,
29
+ "tokenizer_class": "Qwen2Tokenizer",
30
+ "unk_token": null,
31
+ "video_token": "<|video_pad|>",
32
+ "vision_bos_token": "<|vision_start|>",
33
+ "vision_eos_token": "<|vision_end|>"
34
+ }
serve.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Static server for the alias-web bundle, with HTTP Range support for the 2.4 GB weight file.
3
+
4
+ python3 serve.py [port] # default 8924; then open http://127.0.0.1:<port> in Chrome or Edge
5
+
6
+ Binds to 127.0.0.1 only. Responses are marked no-store so that a changed module is never served stale;
7
+ CORS is open so that a local browser extension can fetch the model from this server.
8
+ """
9
+ import http.server, os, re, socketserver, sys
10
+
11
+ ROOT = os.path.dirname(os.path.abspath(__file__))
12
+
13
+
14
+ class Handler(http.server.SimpleHTTPRequestHandler):
15
+ def __init__(self, *a, **kw):
16
+ super().__init__(*a, directory=ROOT, **kw)
17
+
18
+ def send_head(self):
19
+ self._remaining = None
20
+ path = self.translate_path(self.path)
21
+ rng = self.headers.get("Range")
22
+ if not rng or not os.path.isfile(path):
23
+ return super().send_head()
24
+ size = os.path.getsize(path)
25
+ m = re.match(r"bytes=(\d*)-(\d*)$", rng.strip())
26
+ if not m or not (m.group(1) or m.group(2)):
27
+ self.send_error(416)
28
+ return None
29
+ if m.group(1):
30
+ start, end = int(m.group(1)), min(int(m.group(2)) if m.group(2) else size - 1, size - 1)
31
+ else: # suffix form: the last N bytes
32
+ start, end = max(0, size - int(m.group(2))), size - 1
33
+ if start > end:
34
+ self.send_error(416)
35
+ return None
36
+ f = open(path, "rb")
37
+ f.seek(start)
38
+ self._remaining = end - start + 1
39
+ self.send_response(206)
40
+ self.send_header("Content-Type", self.guess_type(path))
41
+ self.send_header("Content-Range", f"bytes {start}-{end}/{size}")
42
+ self.send_header("Content-Length", str(self._remaining))
43
+ self.end_headers()
44
+ return f
45
+
46
+ def copyfile(self, source, outputfile):
47
+ if self._remaining is None:
48
+ return super().copyfile(source, outputfile)
49
+ left = self._remaining
50
+ while left > 0:
51
+ chunk = source.read(min(1 << 20, left))
52
+ if not chunk:
53
+ break
54
+ outputfile.write(chunk)
55
+ left -= len(chunk)
56
+
57
+ def end_headers(self):
58
+ self.send_header("Accept-Ranges", "bytes")
59
+ self.send_header("Cache-Control", "no-store")
60
+ self.send_header("Access-Control-Allow-Origin", "*")
61
+ super().end_headers()
62
+
63
+
64
+ socketserver.TCPServer.allow_reuse_address = True
65
+ if __name__ == "__main__":
66
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8924
67
+ with socketserver.ThreadingTCPServer(("127.0.0.1", port), Handler) as s:
68
+ print(f"serving {ROOT} on http://127.0.0.1:{port}")
69
+ s.serve_forever()
vendor/ct0-edge.LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eduardoworrel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
vendor/ct0.js ADDED
@@ -0,0 +1,2246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/backend.ts
2
+ var GENERATE_DEFAULTS = { maxNewTokens: 512, temperature: 0.7, topP: 0.9, topK: 40, repetitionPenalty: 1.1, seed: 0 };
3
+ function resolveGenerateOptions(o = {}) {
4
+ const num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
5
+ const D = GENERATE_DEFAULTS;
6
+ const temperature = num(o.temperature, D.temperature);
7
+ return {
8
+ maxNewTokens: Math.max(0, Math.floor(num(o.maxNewTokens, D.maxNewTokens))),
9
+ temperature,
10
+ topP: num(o.topP, D.topP),
11
+ topK: num(o.topK, D.topK),
12
+ repetitionPenalty: num(o.repetitionPenalty, temperature <= 0 ? 1 : D.repetitionPenalty),
13
+ seed: num(o.seed, D.seed),
14
+ signal: o.signal
15
+ };
16
+ }
17
+ function checkContinuation(name, h, ids, positions) {
18
+ const k = h.ids.length;
19
+ if (ids.length <= k || h.ids.some((x, i) => ids[i] !== x)) throw new Error(`${name}: prefillFrom ids must extend the snapshot prefix (${k} tokens) by at least one token`);
20
+ for (const p of positions) if (!Number.isInteger(p) || p < k || p >= ids.length) throw new Error(`${name}: position ${p} outside the continuation [${k},${ids.length})`);
21
+ return k;
22
+ }
23
+ function createGate(name) {
24
+ let tail = Promise.resolve();
25
+ let disposed = false;
26
+ let disposing = null;
27
+ const err = () => new Error(`${name}: backend already disposed`);
28
+ const lock = async () => {
29
+ const prev = tail;
30
+ let rel;
31
+ tail = new Promise((r) => {
32
+ rel = r;
33
+ });
34
+ await prev;
35
+ return rel;
36
+ };
37
+ return {
38
+ get disposed() {
39
+ return disposed;
40
+ },
41
+ assertLive() {
42
+ if (disposed) throw err();
43
+ },
44
+ async acquire() {
45
+ if (disposed) throw err();
46
+ return lock();
47
+ },
48
+ dispose(release) {
49
+ if (!disposing) {
50
+ disposed = true;
51
+ disposing = (async () => {
52
+ const rel = await lock();
53
+ try {
54
+ await release();
55
+ } finally {
56
+ rel();
57
+ }
58
+ })();
59
+ }
60
+ return disposing;
61
+ }
62
+ };
63
+ }
64
+ var gates = /* @__PURE__ */ new WeakMap();
65
+ function gateFor(shared, name) {
66
+ let g = gates.get(shared);
67
+ if (!g) {
68
+ g = createGate(name);
69
+ gates.set(shared, g);
70
+ }
71
+ return g;
72
+ }
73
+
74
+ // src/ortVersion.ts
75
+ function checkOrtVersion(v, min = [1, 30]) {
76
+ const m = /^(\d+)\.(\d+)/.exec(v ?? "");
77
+ if (!m) return;
78
+ if (Number(m[1]) < min[0] || Number(m[1]) === min[0] && Number(m[2]) < min[1])
79
+ throw new Error(`ct0-edge: onnxruntime-web ${v} is too old; requires >= ${min.join(".")} (WebGPU returns NaN below that; the genai backend needs the LinearAttention/CausalConvWithState kernels). Pass skipVersionCheck to ignore.`);
80
+ }
81
+
82
+ // src/backends/decodeOnly.ts
83
+ var EOS = /* @__PURE__ */ new Set([248046, 248044]);
84
+ var HIDDEN_NAMES = ["/model/norm/Mul_1_output_0", "mul_13450", "hidden_states", "type_as_80"];
85
+ var F16 = globalThis.Float16Array;
86
+ function mulberry32(seed) {
87
+ let a = seed >>> 0 || 1;
88
+ return () => {
89
+ a |= 0;
90
+ a = a + 1831565813 | 0;
91
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
92
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
93
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
94
+ };
95
+ }
96
+ function halfToNumber(h) {
97
+ const s = h & 32768 ? -1 : 1, e = (h & 31744) >> 10, f = h & 1023;
98
+ return e === 0 ? s * Math.pow(2, -14) * (f / 1024) : e === 31 ? f ? NaN : s * Infinity : s * Math.pow(2, e - 15) * (1 + f / 1024);
99
+ }
100
+ function tensorToF32(data, type, off = 0, n = data.length - off) {
101
+ const out = new Float32Array(n);
102
+ const isF16Arr = typeof F16 !== "undefined" && data instanceof F16;
103
+ if (data instanceof Float32Array || isF16Arr) {
104
+ for (let i = 0; i < n; i++) out[i] = Number(data[off + i]);
105
+ } else if (type === "float16" || data instanceof Uint16Array) {
106
+ for (let i = 0; i < n; i++) out[i] = halfToNumber(data[off + i]);
107
+ } else for (let i = 0; i < n; i++) out[i] = Number(data[off + i]);
108
+ return out;
109
+ }
110
+ function lastRow(L) {
111
+ const V = L.dims.at(-1), off = (L.dims.at(-2) - 1) * V;
112
+ return tensorToF32(L.data, L.type, off, V);
113
+ }
114
+ function sample(logits, gen, rand, o) {
115
+ const { temperature: temp, topP, topK, repetitionPenalty: rep } = o;
116
+ if (rep !== 1) for (const t of new Set(gen)) logits[t] = logits[t] > 0 ? logits[t] / rep : logits[t] * rep;
117
+ if (temp <= 0) {
118
+ let bi = 0, bv = -1e30;
119
+ for (let i = 0; i < logits.length; i++) if (logits[i] > bv) {
120
+ bv = logits[i];
121
+ bi = i;
122
+ }
123
+ return bi;
124
+ }
125
+ let idx = Array.from(logits.keys());
126
+ idx.sort((a, b) => logits[b] - logits[a]);
127
+ if (topK > 0) idx = idx.slice(0, topK);
128
+ const mx = logits[idx[0]];
129
+ let probs = idx.map((i) => Math.exp((logits[i] - mx) / temp));
130
+ let s = probs.reduce((a, b) => a + b, 0);
131
+ probs = probs.map((p) => p / s);
132
+ if (topP < 1) {
133
+ let c = 0, cut = idx.length;
134
+ for (let i = 0; i < idx.length; i++) {
135
+ c += probs[i];
136
+ if (c >= topP) {
137
+ cut = i + 1;
138
+ break;
139
+ }
140
+ }
141
+ idx = idx.slice(0, cut);
142
+ probs = probs.slice(0, cut);
143
+ const z = probs.reduce((a, b) => a + b, 0);
144
+ probs = probs.map((p) => p / z);
145
+ }
146
+ let r = rand(), acc = 0;
147
+ for (let i = 0; i < idx.length; i++) {
148
+ acc += probs[i];
149
+ if (r <= acc) return idx[i];
150
+ }
151
+ return idx[idx.length - 1];
152
+ }
153
+ function pastNameFor(out) {
154
+ let m;
155
+ if (m = out.match(/^present_conv\.(\d+)$/)) return "past_conv." + m[1];
156
+ if (m = out.match(/^present_recurrent\.(\d+)$/)) return "past_recurrent." + m[1];
157
+ if (m = out.match(/^present\.(\d+)\.(key|value)$/)) return `past_key_values.${m[1]}.${m[2]}`;
158
+ if (m = out.match(/^present\.(\d+)\.(conv|recurrent)$/)) return `past.${m[1]}.${m[2]}`;
159
+ if (m = out.match(/^so(\d+)$/)) return "si" + m[1];
160
+ return null;
161
+ }
162
+ function cacheDims(name, shape, pastLen) {
163
+ const isKV = /^past_key_values\./.test(name) || shape.length === 4 && !/conv|recurrent/.test(name) && typeof shape[2] !== "number";
164
+ return shape.map((d, i) => typeof d === "number" && d >= 0 ? d : isKV && i === 2 ? pastLen : 1);
165
+ }
166
+ async function createDecodeOnly(session, tok, opts) {
167
+ if (!opts.skipVersionCheck) checkOrtVersion(opts.ort.env?.versions?.web);
168
+ const T = opts.ort.Tensor;
169
+ const pastLen = opts.pastLen ?? 1;
170
+ const inNames = session.inputNames, outNames = session.outputNames;
171
+ if (inNames.includes("attention_mask")) throw new Error("decode-only: a graph with attention_mask is not decode-only");
172
+ const meta = Array.isArray(session.inputMetadata) ? session.inputMetadata : Object.entries(session.inputMetadata ?? {}).map(([name, m]) => ({ name, ...m }));
173
+ const metaBy = new Map(meta.map((m) => [m.name, m]));
174
+ const specs = [];
175
+ for (const n of inNames) {
176
+ if (n === "input_ids" || n === "cache_position") continue;
177
+ const m = metaBy.get(n);
178
+ if (!m?.shape) throw new Error("decode-only: no shape metadata for input " + n);
179
+ specs.push({ name: n, type: m.type, dims: cacheDims(n, m.shape, pastLen) });
180
+ }
181
+ const pastOf = /* @__PURE__ */ new Map();
182
+ for (const o of outNames) {
183
+ const p = pastNameFor(o);
184
+ if (p && inNames.includes(p)) pastOf.set(o, p);
185
+ }
186
+ const missing = specs.filter((s) => ![...pastOf.values()].includes(s.name));
187
+ if (missing.length) throw new Error("decode-only: cache inputs without a matching present output: " + missing.slice(0, 3).map((s) => s.name).join(", "));
188
+ const hiddenName = HIDDEN_NAMES.find((n) => outNames.includes(n)) ?? null;
189
+ const hasCachePos = inNames.includes("cache_position");
190
+ const zeros = (s) => {
191
+ const n = s.dims.reduce((a, b) => a * b, 1);
192
+ if (s.type === "float32") return new T("float32", new Float32Array(n), s.dims);
193
+ if (s.type === "float16") return new T("float16", F16 ? new F16(n) : new Uint16Array(n), s.dims);
194
+ throw new Error(`decode-only: unsupported cache dtype: ${s.type} (${s.name})`);
195
+ };
196
+ const initState = () => {
197
+ const st = {};
198
+ for (const s of specs) st[s.name] = zeros(s);
199
+ return st;
200
+ };
201
+ const fetchBase = ["logits", ...pastOf.keys()];
202
+ async function step(tid, pos, st, wantHidden) {
203
+ const feed2 = { input_ids: new T("int64", BigInt64Array.from([BigInt(tid)]), [1, 1]), ...st };
204
+ if (hasCachePos) feed2.cache_position = new T("int64", BigInt64Array.from([BigInt(pos)]), [1]);
205
+ const names = wantHidden && hiddenName ? [...fetchBase, hiddenName] : fetchBase;
206
+ const r = await session.run(feed2, names);
207
+ const ns = {};
208
+ for (const [o, p] of pastOf) ns[p] = r[o];
209
+ const hidden = wantHidden && hiddenName ? tensorToF32(r[hiddenName].data, r[hiddenName].type) : void 0;
210
+ return { logits: r.logits, st: ns, hidden };
211
+ }
212
+ async function feed(ids, from, st, want) {
213
+ const wanted = new Set(want.positions), at = /* @__PURE__ */ new Map();
214
+ let last = null;
215
+ for (let i = from; i < ids.length; i++) {
216
+ const r = await step(ids[i], i, st, wanted.has(i));
217
+ st = r.st;
218
+ last = r.logits;
219
+ if (r.hidden) at.set(i, r.hidden);
220
+ }
221
+ return { hiddens: want.positions.map((p) => at.get(p)), lastLogits: lastRow(last) };
222
+ }
223
+ const checkHidden = (want) => {
224
+ if (want.positions.length && !hiddenName) throw new Error("decode-only: the graph does not expose a hidden state (outputs: " + outNames.slice(-3).join(", ") + ")");
225
+ };
226
+ const gate = createGate("decode-only");
227
+ const snaps = /* @__PURE__ */ new WeakMap();
228
+ return {
229
+ kind: "decode-only",
230
+ hiddenName,
231
+ dispose() {
232
+ return gate.dispose(() => session.release?.());
233
+ },
234
+ async prefill(ids, want) {
235
+ gate.assertLive();
236
+ checkHidden(want);
237
+ for (const p of new Set(want.positions)) if (p < 0 || p >= ids.length) throw new Error(`prefill: position ${p} out of [0,${ids.length})`);
238
+ if (!ids.length) throw new Error("prefill: empty ids");
239
+ const rel = await gate.acquire();
240
+ try {
241
+ return await feed(ids, 0, initState(), want);
242
+ } finally {
243
+ rel();
244
+ }
245
+ },
246
+ async snapshot(ids) {
247
+ gate.assertLive();
248
+ if (!ids.length) throw new Error("decode-only: snapshot of empty ids");
249
+ const rel = await gate.acquire();
250
+ try {
251
+ let st = initState();
252
+ for (let i = 0; i < ids.length; i++) st = (await step(ids[i], i, st, false)).st;
253
+ const h = { ids: ids.slice(), dispose() {
254
+ snaps.delete(h);
255
+ } };
256
+ snaps.set(h, st);
257
+ return h;
258
+ } finally {
259
+ rel();
260
+ }
261
+ },
262
+ async prefillFrom(h, ids, want) {
263
+ gate.assertLive();
264
+ const st = snaps.get(h);
265
+ if (!st) throw new Error("decode-only: unknown or released snapshot handle");
266
+ const k = checkContinuation("decode-only", h, ids, want.positions);
267
+ checkHidden(want);
268
+ const rel = await gate.acquire();
269
+ try {
270
+ return await feed(ids, k, st, want);
271
+ } finally {
272
+ rel();
273
+ }
274
+ },
275
+ async *generate(ids, o) {
276
+ gate.assertLive();
277
+ const g = resolveGenerateOptions(o);
278
+ const rand = mulberry32(g.seed);
279
+ if (!ids.length) throw new Error("generate: empty ids");
280
+ if (g.maxNewTokens === 0) return;
281
+ const rel = await gate.acquire();
282
+ try {
283
+ let st = initState(), pos = 0, last = null;
284
+ for (const t of ids) {
285
+ if (g.signal?.aborted) return;
286
+ ({ logits: last, st } = await step(t, pos, st, false));
287
+ pos++;
288
+ }
289
+ const gen = [];
290
+ for (let s = 0; s < g.maxNewTokens; s++) {
291
+ if (g.signal?.aborted) break;
292
+ const tid = sample(lastRow(last), gen, rand, g);
293
+ if (EOS.has(tid)) break;
294
+ gen.push(tid);
295
+ yield tid;
296
+ if (s + 1 >= g.maxNewTokens || g.signal?.aborted) break;
297
+ ({ logits: last, st } = await step(tid, pos, st, false));
298
+ pos++;
299
+ }
300
+ } finally {
301
+ rel();
302
+ }
303
+ }
304
+ };
305
+ }
306
+
307
+ // src/backends/genai.ts
308
+ function parsePlacement(lines) {
309
+ const out = {};
310
+ for (const l of lines) {
311
+ const m = /Node\(s\) placed on \[(\w+)\]\.\s*Number of nodes:\s*(\d+)/.exec(l);
312
+ if (m) out[m[1]] = (out[m[1]] ?? 0) + Number(m[2]);
313
+ }
314
+ return out;
315
+ }
316
+ function assertWebGpuPlacement(p, maxCpuNodes = 32) {
317
+ const gpu = Object.entries(p).filter(([k]) => /webgpu/i.test(k)).reduce((a, [, n]) => a + n, 0);
318
+ const cpu = p.CPUExecutionProvider ?? 0;
319
+ if (!Object.keys(p).length) throw new Error("genai: could not verify the EP placement (no 'Node(s) placed on' logs); reject a bundle without WebGPU");
320
+ if (gpu === 0) throw new Error(`genai: no node on the WebGPU EP (placement: ${JSON.stringify(p)}); LinearAttention does not run on GPU`);
321
+ if (cpu > maxCpuNodes) throw new Error(`genai: ${cpu} nodes on CPU (> ${maxCpuNodes}); probably a bundle without the WebGPU kernels (use onnxruntime-web/webgpu). Placement: ${JSON.stringify(p)}`);
322
+ }
323
+ function genaiFromSession(session, ort, opts = {}) {
324
+ const T = ort.Tensor;
325
+ const eos = opts.eos ?? EOS;
326
+ const inNames = session.inputNames, outNames = session.outputNames;
327
+ for (const need of ["input_ids"]) if (!inNames.includes(need)) throw new Error(`genai: graph without input ${need}`);
328
+ if (!outNames.includes("logits")) throw new Error("genai: graph without a logits output");
329
+ const hasMask = inNames.includes("attention_mask"), hasPos = inNames.includes("position_ids");
330
+ const meta = Array.isArray(session.inputMetadata) ? session.inputMetadata : Object.entries(session.inputMetadata ?? {}).map(([name, m]) => ({ name, ...m }));
331
+ const metaBy = new Map(meta.map((m) => [m.name, m]));
332
+ const specs = [];
333
+ for (const n of inNames) {
334
+ if (n === "input_ids" || n === "attention_mask" || n === "position_ids") continue;
335
+ const m = metaBy.get(n);
336
+ if (!m?.shape) throw new Error("genai: no shape metadata for input " + n);
337
+ const dims = cacheDims(n, m.shape, 0);
338
+ if (/^past_key_values\./.test(n) && typeof m.shape[3] !== "number") dims[3] = opts.kvHeadDim ?? 256;
339
+ specs.push({ name: n, type: m.type, dims });
340
+ }
341
+ const pastOf = /* @__PURE__ */ new Map();
342
+ for (const o of outNames) {
343
+ const p = pastNameFor(o);
344
+ if (p && inNames.includes(p)) pastOf.set(o, p);
345
+ }
346
+ const covered = new Set(pastOf.values());
347
+ const missing = specs.filter((s) => !covered.has(s.name));
348
+ if (missing.length) throw new Error("genai: cache inputs without a present output: " + missing.slice(0, 3).map((s) => s.name).join(", "));
349
+ const hiddenName = HIDDEN_NAMES.find((n) => outNames.includes(n)) ?? null;
350
+ const posRows = (() => {
351
+ const sh = metaBy.get("position_ids")?.shape;
352
+ return typeof sh?.[0] === "number" ? sh[0] : 3;
353
+ })();
354
+ const zeros = (s) => {
355
+ const n = s.dims.reduce((a, b) => a * b, 1);
356
+ if (s.type === "float16") return new T("float16", new Uint16Array(n), s.dims);
357
+ if (s.type === "float32") return new T("float32", new Float32Array(n), s.dims);
358
+ throw new Error(`genai: unsupported cache dtype: ${s.type}`);
359
+ };
360
+ const initState = () => {
361
+ const st = {};
362
+ for (const s of specs) st[s.name] = zeros(s);
363
+ return st;
364
+ };
365
+ const free = (st) => {
366
+ if (st) {
367
+ for (const t of Object.values(st)) if (t?.location === "gpu-buffer") {
368
+ try {
369
+ t.dispose?.();
370
+ } catch {
371
+ }
372
+ }
373
+ }
374
+ };
375
+ const gate = createGate("genai");
376
+ async function run(chunk, past, st, wantLogits, wantHidden, keep = false) {
377
+ const S = chunk.length;
378
+ const feed = { input_ids: new T("int64", BigInt64Array.from(chunk.map(BigInt)), [1, S]), ...st };
379
+ if (hasMask) feed.attention_mask = new T("int64", new BigInt64Array(past + S).fill(1n), [1, past + S]);
380
+ if (hasPos) {
381
+ const pos = new BigInt64Array(posRows * S);
382
+ for (let r2 = 0; r2 < posRows; r2++) for (let i = 0; i < S; i++) pos[r2 * S + i] = BigInt(past + i);
383
+ feed.position_ids = new T("int64", pos, [posRows, 1, S]);
384
+ }
385
+ const fetches = [...pastOf.keys()];
386
+ if (wantLogits) fetches.unshift("logits");
387
+ if (wantHidden && hiddenName) fetches.push(hiddenName);
388
+ const r = await session.run(feed, fetches, { logSeverityLevel: 3 });
389
+ const ns = {};
390
+ for (const [o, p] of pastOf) ns[p] = r[o];
391
+ if (!keep) free(st);
392
+ const out = {};
393
+ if (wantLogits) out.logits = r.logits;
394
+ if (wantHidden && hiddenName) out.hidden = r[hiddenName];
395
+ return { st: ns, out };
396
+ }
397
+ const read = async (t) => typeof t.getData === "function" ? await t.getData(true) : t.data;
398
+ async function feedAll(ids, wanted, wantLast, signal, from) {
399
+ let st = from?.st ?? initState(), past = from?.past ?? 0;
400
+ const kept = from?.st;
401
+ const hid = /* @__PURE__ */ new Map();
402
+ let lastLogits;
403
+ try {
404
+ while (past < ids.length) {
405
+ if (signal?.aborted) return { st, past, hid, lastLogits, aborted: true };
406
+ const chunk = ids.slice(past, past + self.chunk), last = past + chunk.length >= ids.length;
407
+ const need = [...wanted].filter((p) => p >= past && p < past + chunk.length);
408
+ const r = await run(chunk, past, st, last && wantLast, need.length > 0, st === kept);
409
+ st = r.st;
410
+ if (r.out.hidden) {
411
+ const H = r.out.hidden, D = H.dims.at(-1), d = await read(H);
412
+ for (const p of need) hid.set(p, tensorToF32(d, H.type, (p - past) * D, D));
413
+ }
414
+ if (r.out.logits) {
415
+ const L = r.out.logits, V = L.dims.at(-1), d = await read(L);
416
+ lastLogits = tensorToF32(d, L.type, (L.dims.at(-2) - 1) * V, V);
417
+ }
418
+ past += chunk.length;
419
+ }
420
+ } catch (e) {
421
+ if (st !== kept) free(st);
422
+ throw e;
423
+ }
424
+ return { st, past, hid, lastLogits, aborted: false };
425
+ }
426
+ const snaps = /* @__PURE__ */ new WeakMap();
427
+ const self = {
428
+ kind: "genai",
429
+ chunk: Math.max(1, opts.chunk ?? 128),
430
+ hiddenName,
431
+ dispose() {
432
+ return gate.dispose(() => session.release?.());
433
+ },
434
+ async prefill(ids, want) {
435
+ gate.assertLive();
436
+ for (const p of want.positions) if (!Number.isInteger(p) || p < 0 || p >= ids.length) throw new Error(`genai: position ${p} out of [0,${ids.length})`);
437
+ if (!ids.length) throw new Error("prefill: empty ids");
438
+ if (want.positions.length && !hiddenName) throw new Error("genai: the graph does not expose a hidden state; use method 'logits'");
439
+ const rel = await gate.acquire();
440
+ try {
441
+ const f = await feedAll(ids, new Set(want.positions), true);
442
+ free(f.st);
443
+ return { hiddens: want.positions.map((p) => f.hid.get(p)), lastLogits: f.lastLogits };
444
+ } finally {
445
+ rel();
446
+ }
447
+ },
448
+ async snapshot(ids) {
449
+ gate.assertLive();
450
+ if (!ids.length) throw new Error("genai: snapshot of empty ids");
451
+ const rel = await gate.acquire();
452
+ try {
453
+ const f = await feedAll(ids, /* @__PURE__ */ new Set(), false);
454
+ let st = f.st;
455
+ const h = { ids: ids.slice(), dispose: async () => {
456
+ if (!st) return;
457
+ const s0 = st;
458
+ st = null;
459
+ snaps.delete(h);
460
+ let r = null;
461
+ try {
462
+ r = await gate.acquire();
463
+ } catch {
464
+ }
465
+ try {
466
+ free(s0);
467
+ } finally {
468
+ r?.();
469
+ }
470
+ } };
471
+ snaps.set(h, st);
472
+ return h;
473
+ } finally {
474
+ rel();
475
+ }
476
+ },
477
+ async prefillFrom(h, ids, want) {
478
+ gate.assertLive();
479
+ const st = snaps.get(h);
480
+ if (!st) throw new Error("genai: unknown or released snapshot handle");
481
+ const k = checkContinuation("genai", h, ids, want.positions);
482
+ if (want.positions.length && !hiddenName) throw new Error("genai: the graph does not expose a hidden state; use method 'logits'");
483
+ const rel = await gate.acquire();
484
+ try {
485
+ const f = await feedAll(ids, new Set(want.positions), true, void 0, { st, past: k });
486
+ free(f.st);
487
+ return { hiddens: want.positions.map((p) => f.hid.get(p)), lastLogits: f.lastLogits };
488
+ } finally {
489
+ rel();
490
+ }
491
+ },
492
+ async *generate(ids, o) {
493
+ gate.assertLive();
494
+ const g = resolveGenerateOptions(o);
495
+ const rand = mulberry32(g.seed);
496
+ if (!ids.length) throw new Error("generate: empty ids");
497
+ if (g.maxNewTokens === 0) return;
498
+ const rel = await gate.acquire();
499
+ let st = null;
500
+ try {
501
+ if (o.signal?.aborted) return;
502
+ const f = await feedAll(ids, /* @__PURE__ */ new Set(), true, o.signal);
503
+ st = f.st;
504
+ if (f.aborted) return;
505
+ let past = f.past, logits = f.lastLogits;
506
+ const gen = [];
507
+ for (let s = 0; s < g.maxNewTokens; s++) {
508
+ if (o.signal?.aborted) break;
509
+ const tid = sample(logits, gen, rand, g);
510
+ if (eos.has(tid)) break;
511
+ gen.push(tid);
512
+ yield tid;
513
+ if (o.signal?.aborted || s + 1 >= g.maxNewTokens) break;
514
+ const r = await run([tid], past, st, true, false);
515
+ st = r.st;
516
+ past++;
517
+ const L = r.out.logits, V = L.dims.at(-1);
518
+ logits = tensorToF32(await read(L), L.type, (L.dims.at(-2) - 1) * V, V);
519
+ }
520
+ } finally {
521
+ free(st);
522
+ rel();
523
+ }
524
+ }
525
+ };
526
+ return self;
527
+ }
528
+ function hashStr(x) {
529
+ let h = 2166136261;
530
+ for (let i = 0; i < x.length; i++) {
531
+ h ^= x.charCodeAt(i);
532
+ h = Math.imul(h, 16777619) >>> 0;
533
+ }
534
+ return h.toString(16).padStart(8, "0");
535
+ }
536
+ async function adapterTag(opts) {
537
+ if (typeof opts.adapterTag === "string") return opts.adapterTag;
538
+ if (typeof opts.adapterTag === "function") return opts.adapterTag();
539
+ try {
540
+ const gpu = globalThis.navigator?.gpu;
541
+ const ad = await gpu?.requestAdapter?.();
542
+ const i = ad?.info ?? await ad?.requestAdapterInfo?.();
543
+ if (!i) return "?";
544
+ return hashStr([i.vendor, i.architecture, i.device, i.description].join("|"));
545
+ } catch {
546
+ return "?";
547
+ }
548
+ }
549
+ function epStore(opts) {
550
+ if (opts.epCache !== void 0) return opts.epCache;
551
+ try {
552
+ return globalThis.localStorage ?? null;
553
+ } catch {
554
+ return null;
555
+ }
556
+ }
557
+ async function probePlacement(ort, modelUrl, sessionOpts, maxCpuNodes) {
558
+ const captured = [];
559
+ const names = ["log", "info", "warn", "error", "debug"];
560
+ const saved = {};
561
+ const prevLevel = ort.env?.logLevel;
562
+ if (ort.env) ort.env.logLevel = "verbose";
563
+ for (const n of names) {
564
+ saved[n] = console[n];
565
+ console[n] = (...a) => {
566
+ const s = a.join(" ");
567
+ if (s.includes("Node(s) placed on")) captured.push(s);
568
+ else if (!s.includes("[V:onnxruntime") && !/WebGPU Shader code|^\s*$/.test(s)) saved[n].apply(console, a);
569
+ };
570
+ }
571
+ let probe;
572
+ try {
573
+ probe = await ort.InferenceSession.create(modelUrl, sessionOpts(0));
574
+ } finally {
575
+ for (const n of names) console[n] = saved[n];
576
+ if (ort.env) ort.env.logLevel = prevLevel;
577
+ }
578
+ try {
579
+ assertWebGpuPlacement(parsePlacement(captured), maxCpuNodes);
580
+ } finally {
581
+ await probe.release?.();
582
+ }
583
+ }
584
+ async function createGenai(modelUrl, dataUrl, opts = {}) {
585
+ const providers = opts.providers ?? ["webgpu"];
586
+ if (providers[0] !== "webgpu") throw new Error("genai: the graph requires the webgpu EP as first provider (the LinearAttention/CausalConvWithState kernels only exist on WebGPU)");
587
+ const ort = opts.ort ?? await import("onnxruntime-web/webgpu");
588
+ const verify = opts.verifyEp ?? true;
589
+ const sessionOpts = (level) => ({
590
+ executionProviders: providers.map((n) => n === "webgpu" ? { name: "webgpu", validationMode: "disabled" } : n),
591
+ externalData: [{ path: "model.onnx.data", data: dataUrl }],
592
+ graphOptimizationLevel: "all",
593
+ enableGraphCapture: false,
594
+ preferredOutputLocation: "gpu-buffer",
595
+ logSeverityLevel: level
596
+ });
597
+ if (!opts.skipVersionCheck) checkOrtVersion(ort.env?.versions?.web);
598
+ if (verify) {
599
+ const store = epStore(opts);
600
+ const maxCpu = opts.maxCpuNodes ?? 32;
601
+ const key = store ? `ct0-edge:genai-ep:${ort.env?.versions?.web ?? "?"}:${await adapterTag(opts)}:${modelUrl}:${dataUrl}:${maxCpu}` : "";
602
+ let cached = false;
603
+ if (verify !== "force" && store) {
604
+ try {
605
+ cached = store.getItem(key) === "ok";
606
+ } catch {
607
+ }
608
+ }
609
+ if (!cached) {
610
+ await probePlacement(ort, modelUrl, sessionOpts, maxCpu);
611
+ if (store) {
612
+ try {
613
+ store.setItem(key, "ok");
614
+ } catch {
615
+ }
616
+ }
617
+ }
618
+ }
619
+ const session = await ort.InferenceSession.create(modelUrl, sessionOpts(3));
620
+ try {
621
+ const be = genaiFromSession(session, ort, opts);
622
+ if (opts.warmup ?? true) await be.prefill([248045], { positions: [] });
623
+ return be;
624
+ } catch (e) {
625
+ try {
626
+ await session.release?.();
627
+ } catch {
628
+ }
629
+ throw e;
630
+ }
631
+ }
632
+
633
+ // src/backends/transformersjs.ts
634
+ function transformersJsFromModel(model, Tensor) {
635
+ const sess = model.sessions?.model ?? Object.values(model.sessions ?? {})[0];
636
+ const outs = sess?.outputNames ?? [];
637
+ const hiddenName = HIDDEN_NAMES.find((n) => outs.includes(n)) ?? null;
638
+ const toTensors = (ids) => ({
639
+ input_ids: new Tensor("int64", BigInt64Array.from(ids.map(BigInt)), [1, ids.length]),
640
+ attention_mask: new Tensor("int64", new BigInt64Array(ids.length).fill(1n), [1, ids.length])
641
+ });
642
+ const gate = createGate("scan");
643
+ return {
644
+ kind: "scan",
645
+ hiddenName,
646
+ async prefill(ids, want) {
647
+ gate.assertLive();
648
+ const n = ids.length;
649
+ if (!n) throw new Error("prefill: empty ids");
650
+ for (const p of want.positions) if (!Number.isInteger(p) || p < 0 || p >= n) throw new Error(`transformers.js: position ${p} out of range [0,${n})`);
651
+ if (want.positions.length && !hiddenName) throw new Error("transformers.js: graph has no hidden output; use method 'logits'");
652
+ const rel = await gate.acquire();
653
+ try {
654
+ const out = await model.forward(toTensors(ids));
655
+ const L = out.logits, V = L.dims.at(-1);
656
+ const lastLogits = tensorToF32(L.data, L.type, (L.dims.at(-2) - 1) * V, V);
657
+ const hiddens = [];
658
+ if (want.positions.length) {
659
+ const H = out[hiddenName], D = H.dims.at(-1);
660
+ if (H.dims.at(-2) !== n) throw new Error(`transformers.js: hidden seq ${H.dims.at(-2)} != ${n}`);
661
+ for (const p of want.positions) hiddens.push(tensorToF32(H.data, H.type, p * D, D));
662
+ }
663
+ return { hiddens, lastLogits };
664
+ } finally {
665
+ rel();
666
+ }
667
+ },
668
+ dispose() {
669
+ return gate.dispose(() => model.dispose?.());
670
+ },
671
+ async *generate(ids, o) {
672
+ gate.assertLive();
673
+ const g = resolveGenerateOptions(o);
674
+ const max = g.maxNewTokens, temp = g.temperature;
675
+ if (!ids.length) throw new Error("generate: empty ids");
676
+ if (max === 0) return;
677
+ const rel = await gate.acquire();
678
+ const queue = [];
679
+ let done = false, wake = null, err = null, first = true, stop = false;
680
+ const ping = () => {
681
+ const w = wake;
682
+ wake = null;
683
+ w?.();
684
+ };
685
+ const streamer = {
686
+ put(v) {
687
+ if (first) {
688
+ first = false;
689
+ return;
690
+ }
691
+ if (stop) return;
692
+ for (const t of v.flat ? v.flat() : v) queue.push(Number(t));
693
+ ping();
694
+ },
695
+ end() {
696
+ done = true;
697
+ ping();
698
+ }
699
+ };
700
+ const criterion = (batch) => batch.map(() => stop || !!o.signal?.aborted);
701
+ const args = { ...toTensors(ids), max_new_tokens: max, streamer, stopping_criteria: [criterion], do_sample: temp > 0 };
702
+ if (temp > 0) {
703
+ args.temperature = temp;
704
+ args.top_p = g.topP;
705
+ args.top_k = g.topK;
706
+ }
707
+ args.repetition_penalty = g.repetitionPenalty;
708
+ const run = Promise.resolve().then(() => model.generate(args)).then(() => {
709
+ done = true;
710
+ ping();
711
+ }, (e) => {
712
+ err = e;
713
+ done = true;
714
+ ping();
715
+ });
716
+ let emitted = 0;
717
+ try {
718
+ while (true) {
719
+ if (o.signal?.aborted) break;
720
+ if (queue.length) {
721
+ const t = queue.shift();
722
+ if (EOS.has(t)) break;
723
+ yield t;
724
+ if (++emitted >= max) break;
725
+ } else if (done) break;
726
+ else await new Promise((r) => {
727
+ wake = r;
728
+ if (queue.length || done) ping();
729
+ });
730
+ }
731
+ } finally {
732
+ stop = true;
733
+ try {
734
+ await run;
735
+ } finally {
736
+ rel();
737
+ }
738
+ }
739
+ if (err && !o.signal?.aborted) throw err;
740
+ }
741
+ };
742
+ }
743
+
744
+ // src/positions.ts
745
+ var CHOICE_Q = "Which vulnerability class does this code contain?";
746
+ var SEV_Q = "How severe is the security risk in this content? (1=none, 5=critical)";
747
+ function renderPrompt(state, question) {
748
+ return "<|im_start|>system\nYou are a security decision function. Answer with exactly one word: Yes or No.<|im_end|>\n<|im_start|>user\n<state>" + state + "</state>\n<q>" + question + "</q><decide><|im_end|>\n<|im_start|>assistant\n";
749
+ }
750
+ var KINDS = ["noul", "choice", "score"];
751
+ function assertProbe(p) {
752
+ const q = p;
753
+ if (!q || typeof q !== "object") throw new Error("probe: expected an object {kind, state, ...}");
754
+ if (!KINDS.includes(q.kind)) {
755
+ const hint = q.kind === void 0 && "type" in q ? ` (the field is \`kind\`, not \`type\`: {kind: ${JSON.stringify(q.type)}, state, q})` : "";
756
+ throw new Error(`probe: invalid kind ${JSON.stringify(q.kind)}; use "noul", "choice" or "score"${hint}`);
757
+ }
758
+ if (typeof q.state !== "string") throw new Error("probe: state must be a string");
759
+ if (q.kind === "choice") {
760
+ const o = q.options;
761
+ if (!Array.isArray(o) || !o.length) throw new Error("probe: choice requires options (non-empty list)");
762
+ const seen = /* @__PURE__ */ new Set();
763
+ for (const x of o) {
764
+ if (typeof x !== "string" || !x.trim()) throw new Error("probe: option label empty or not a string: " + JSON.stringify(x));
765
+ if (/<\/?opt>/.test(x)) throw new Error("probe: option label cannot contain <opt> / </opt>: " + JSON.stringify(x));
766
+ if (seen.has(x)) throw new Error("probe: duplicate option label: " + JSON.stringify(x));
767
+ seen.add(x);
768
+ }
769
+ }
770
+ }
771
+ function promptFor(p) {
772
+ assertProbe(p);
773
+ if (p.kind === "noul") return renderPrompt(p.state, p.q ?? "");
774
+ if (p.kind === "choice") {
775
+ return renderPrompt(p.state, CHOICE_Q + " " + (p.options ?? []).map((o) => "<opt>" + o + "</opt>").join(""));
776
+ }
777
+ return renderPrompt(p.state, SEV_Q);
778
+ }
779
+ function decidePos(tokens) {
780
+ const imEnd = [];
781
+ tokens.forEach((s, i) => {
782
+ if (s === "<|im_end|>") imEnd.push(i);
783
+ });
784
+ if (!imEnd.length) throw new Error("decidePos: no <|im_end|> in " + JSON.stringify(tokens.slice(-8)));
785
+ let lastAsst = -1;
786
+ tokens.forEach((s, i) => {
787
+ if (s.trim() === "assistant") lastAsst = i;
788
+ });
789
+ const cand = lastAsst >= 0 ? imEnd.filter((i) => i < lastAsst) : [];
790
+ const end = cand.length ? cand[cand.length - 1] : imEnd[imEnd.length - 1];
791
+ if (end === 0) throw new Error("decidePos: <|im_end|> at position 0, no preceding token");
792
+ if (tokens[end - 1] !== ">") {
793
+ throw new Error("decidePos: token before <|im_end|> is " + JSON.stringify(tokens[end - 1]) + ", expected '>' (end of <decide>)");
794
+ }
795
+ return end - 1;
796
+ }
797
+ function optPositions(prompt, offsets) {
798
+ const out = [];
799
+ const from = Math.max(0, prompt.lastIndexOf("<q>"));
800
+ for (const m of prompt.slice(from).matchAll(/<\/opt>/g)) {
801
+ const c = from + m.index + m[0].length - 1;
802
+ const hit = offsets.findIndex(([s, e]) => s <= c && c < e);
803
+ if (hit < 0) throw new Error("optPositions: no token covers char " + c);
804
+ out.push(hit);
805
+ }
806
+ return out;
807
+ }
808
+ function optPositionsFor(prompt, offsets, nOptions) {
809
+ const pos = optPositions(prompt, offsets);
810
+ if (nOptions === void 0) nOptions = (prompt.slice(Math.max(0, prompt.lastIndexOf("<q>"))).match(/<opt>/g) ?? []).length;
811
+ if (pos.length !== nOptions) throw new Error(`positions: ${pos.length} </opt> markers after <q> for ${nOptions} options (label containing </opt>?)`);
812
+ return pos;
813
+ }
814
+ function tokenOffsets(tok, prompt) {
815
+ const ids = Array.from(tok.encode(prompt, { add_special_tokens: false }), (x) => Number(x));
816
+ const dec = (a) => tok.decode(a, { skip_special_tokens: false, clean_up_tokenization_spaces: false });
817
+ const tokens = ids.map((i) => dec([i]));
818
+ const offsets = [];
819
+ let prev = 0;
820
+ let full = "";
821
+ for (let i = 0; i < ids.length; i++) {
822
+ full = dec(ids.slice(0, i + 1));
823
+ let end = full.length;
824
+ while (end > prev && full.charCodeAt(end - 1) === 65533) end--;
825
+ offsets.push([prev, end]);
826
+ prev = end;
827
+ }
828
+ if (full !== prompt) throw new Error("tokenOffsets: reconstruction != prompt (offsets not reliable)");
829
+ return { ids, tokens, offsets };
830
+ }
831
+
832
+ // src/systemone.ts
833
+ function loadHead(buf) {
834
+ const dv = new DataView(buf);
835
+ const jsonLen = dv.getUint32(0, true);
836
+ const meta = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, jsonLen)));
837
+ let off = 4 + jsonLen;
838
+ const take = (n) => {
839
+ const avail = Math.floor((buf.byteLength - off) / 2);
840
+ if (n > avail) throw new Error(`head binary too short: needs ${n} fp16, ${avail} available`);
841
+ const f32 = new Float32Array(n);
842
+ for (let i = 0; i < n; i++) f32[i] = f16ToF32(dv.getUint16(off + i * 2, true));
843
+ off += n * 2;
844
+ return f32;
845
+ };
846
+ return {
847
+ q: take(meta.headDim * meta.hiddenSize),
848
+ qb: take(meta.headDim),
849
+ k: take(meta.headDim * meta.hiddenSize),
850
+ kb: take(meta.headDim),
851
+ headDim: meta.headDim,
852
+ hiddenSize: meta.hiddenSize,
853
+ temperature: meta.temperature,
854
+ scoreOffset: meta.score_offset ?? 0
855
+ };
856
+ }
857
+ function f16ToF32(h) {
858
+ const s = (h & 32768) >> 15, e = (h & 31744) >> 10, f = h & 1023;
859
+ if (e === 0) return (s ? -1 : 1) * Math.pow(2, -14) * (f / 1024);
860
+ if (e === 31) return f ? NaN : s ? -Infinity : Infinity;
861
+ return (s ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / 1024);
862
+ }
863
+ function proj(W, b, x, dim, hidden) {
864
+ const y = new Float32Array(dim);
865
+ for (let d = 0; d < dim; d++) {
866
+ let acc = b[d];
867
+ const row = d * hidden;
868
+ for (let i = 0; i < hidden; i++) acc += W[row + i] * x[i];
869
+ y[d] = acc;
870
+ }
871
+ return y;
872
+ }
873
+ function dot(a, b) {
874
+ let s = 0;
875
+ for (let i = 0; i < a.length; i++) s += a[i] * b[i];
876
+ return s;
877
+ }
878
+ var sigmoid = (z) => 1 / (1 + Math.exp(-z));
879
+ function headScore(hd, hq, hk) {
880
+ if (!hq || !hk || hq.length < hd.hiddenSize || hk.length < hd.hiddenSize) return NaN;
881
+ const a = proj(hd.q, hd.qb, hq, hd.headDim, hd.hiddenSize);
882
+ const b = proj(hd.k, hd.kb, hk, hd.headDim, hd.hiddenSize);
883
+ const s = dot(a, b) / hd.temperature + (hd.scoreOffset || 0);
884
+ return Number.isFinite(s) ? s : NaN;
885
+ }
886
+ function answer(hd, q, hiddens) {
887
+ if (q.type === "noul") {
888
+ const raw2 = headScore(hd, hiddens.atDecide, hiddens.atLast);
889
+ return { type: "noul", noul: sigmoid(raw2), raw: raw2 };
890
+ }
891
+ if (q.type === "choice") {
892
+ const opts = q.options || [];
893
+ const per = hiddens.perOption || [];
894
+ if (!per.length) return { type: "choice", choice: {} };
895
+ const choice = {};
896
+ const raws = [];
897
+ for (let i = 0; i < opts.length; i++) {
898
+ const h = per[i];
899
+ if (!h) {
900
+ choice[opts[i]] = 0;
901
+ raws.push(-Infinity);
902
+ continue;
903
+ }
904
+ const r = headScore(hd, hiddens.atDecide, h);
905
+ raws.push(r);
906
+ choice[opts[i]] = r;
907
+ }
908
+ const mx = Math.max(...raws.filter(Number.isFinite));
909
+ let z = 0;
910
+ const exps = raws.map((r) => Number.isFinite(r) ? Math.exp(r - mx) : 0);
911
+ z = exps.reduce((a, b) => a + b, 0) || 1;
912
+ for (let i = 0; i < opts.length; i++) choice[opts[i]] = exps[i] / z;
913
+ return { type: "choice", choice };
914
+ }
915
+ const K = q.levels || 5;
916
+ const raw = headScore(hd, hiddens.atDecide, hiddens.atLast);
917
+ const p = sigmoid(raw);
918
+ const score = Math.max(1, Math.min(K, Math.round(p * (K - 1) + 1)));
919
+ return { type: "score", score, raw };
920
+ }
921
+
922
+ // src/ask.ts
923
+ var YES_ID = 9175;
924
+ var NO_ID = 2665;
925
+ var DIGIT_IDS = [16, 17, 18, 19, 20];
926
+ var DEFAULT_METHOD = { noul: "logits", choice: "head", score: "head" };
927
+ var sigmoid2 = (z) => 1 / (1 + Math.exp(-z));
928
+ function softmax(xs) {
929
+ const mx = Math.max(...xs);
930
+ const e = xs.map((x) => Math.exp(x - mx));
931
+ const z = e.reduce((a, b) => a + b, 0) || 1;
932
+ return e.map((v) => v / z);
933
+ }
934
+ function argmax(xs) {
935
+ let b = 0;
936
+ for (let i = 1; i < xs.length; i++) if (xs[i] > xs[b]) b = i;
937
+ return b;
938
+ }
939
+ function toChoice(options, probs) {
940
+ const choice = {};
941
+ options.forEach((o, i) => {
942
+ choice[o] = probs[i];
943
+ });
944
+ return { choice, top: options[argmax(probs)] };
945
+ }
946
+ function levelFromCuts(x, cuts) {
947
+ let l = 1;
948
+ for (const c of cuts) if (x >= c) l++;
949
+ return l;
950
+ }
951
+ function checkCuts(cuts, n, read) {
952
+ if (cuts === void 0) return void 0;
953
+ if (!Array.isArray(cuts) || cuts.length !== n || !cuts.every(Number.isFinite) || cuts.some((c, i) => i > 0 && c <= cuts[i - 1]))
954
+ throw new Error(`ask: scoreCuts for the ${read} read must be ${n} finite ascending numbers, got ${JSON.stringify(cuts)}`);
955
+ return cuts;
956
+ }
957
+ function planAsk(be, tok, heads, q, opts) {
958
+ const maxSafe = opts.maxSafeTokens ?? 2048;
959
+ const prompt = promptFor(q);
960
+ const type = q.kind;
961
+ const method = opts.method ?? DEFAULT_METHOD[type];
962
+ if (method !== "head" && method !== "logits" && method !== "both") throw new Error(`ask: unknown method ${JSON.stringify(method)}; use "head", "logits" or "both"`);
963
+ const options = q.options ?? [];
964
+ const t = tokenOffsets(tok, prompt);
965
+ if (t.ids.length > maxSafe) throw new Error(`ask: prompt has ${t.ids.length} tokens, over maxSafeTokens (${maxSafe})`);
966
+ let headCuts, logitCuts;
967
+ if (type === "score" && opts.scoreCuts !== void 0) {
968
+ const sc = opts.scoreCuts;
969
+ if (Array.isArray(sc)) {
970
+ if (method === "both") throw new Error('ask: method "both" reads two scales; pass scoreCuts as { head, logits }');
971
+ if (method === "head") headCuts = sc;
972
+ else logitCuts = sc;
973
+ } else ({ head: headCuts, logits: logitCuts } = sc ?? {});
974
+ headCuts = checkCuts(headCuts, (q.levels || 5) - 1, "head");
975
+ logitCuts = checkCuts(logitCuts, DIGIT_IDS.length - 1, "logits");
976
+ }
977
+ const optFirst = type === "choice" ? options.map((o) => Number(tok.encode(o, { add_special_tokens: false })[0])) : [];
978
+ const fromLogits = (L) => {
979
+ if (type === "noul") {
980
+ const raw = L[YES_ID] - L[NO_ID];
981
+ return { type, method: "logits", raw, p: sigmoid2(raw) };
982
+ }
983
+ if (type === "choice") return { type, method: "logits", ...toChoice(options, softmax(optFirst.map((id) => L[id]))) };
984
+ const d = DIGIT_IDS.map((i) => L[i]);
985
+ const expected = softmax(d).reduce((a, p, i) => a + (i + 1) * p, 0);
986
+ return { type, method: "logits", level: logitCuts ? levelFromCuts(expected, logitCuts) : argmax(d) + 1, expected };
987
+ };
988
+ if (method === "logits") return { ids: t.ids, positions: [], read: (r) => fromLogits(r.lastLogits) };
989
+ if (!be.hiddenName) throw new Error(`ask: method '${method}' needs a graph with a hidden output (backend.hiddenName is null); use method 'logits'`);
990
+ const hd = type === "score" ? heads.score : heads.noulChoice;
991
+ if (!hd) throw new Error(type === "score" ? `ask: method '${method}' for score needs the score head (head_score.bin); load it via loadHeads({score})` : `ask: method '${method}' needs a loaded head for '${type}'`);
992
+ const dp = decidePos(t.tokens);
993
+ const withLogits = (r, L) => method === "both" ? { ...r, logits: fromLogits(L) } : r;
994
+ if (type === "choice") {
995
+ const op = optPositionsFor(prompt, t.offsets, options.length);
996
+ return { ids: t.ids, positions: [dp, ...op], read: ({ hiddens, lastLogits }) => {
997
+ const perOption = hiddens.slice(1);
998
+ const a = answer(hd, { type: "choice", options }, { atDecide: hiddens[0], atLast: perOption[perOption.length - 1], perOption });
999
+ return withLogits({ type, method: "head", ...toChoice(options, options.map((o) => a.choice[o])) }, lastLogits);
1000
+ } };
1001
+ }
1002
+ return { ids: t.ids, positions: [dp, t.ids.length - 1], read: ({ hiddens, lastLogits }) => {
1003
+ const a = answer(hd, { type, levels: q.levels }, { atDecide: hiddens[0], atLast: hiddens[1] });
1004
+ if (type === "noul") return withLogits({ type, method: "head", raw: a.raw, p: a.noul }, lastLogits);
1005
+ const level = headCuts && Number.isFinite(a.raw) ? levelFromCuts(a.raw, headCuts) : a.score;
1006
+ return withLogits({ type, method: "head", raw: a.raw, level }, lastLogits);
1007
+ } };
1008
+ }
1009
+ async function ask(be, tok, heads, q, opts = {}) {
1010
+ const p = planAsk(be, tok, heads, q, opts);
1011
+ return p.read(await be.prefill(p.ids, { positions: p.positions }));
1012
+ }
1013
+ function sharedPrefix(plans) {
1014
+ let k = Math.min(...plans.map((p) => p.ids.length - 1), ...plans.flatMap((p) => p.positions));
1015
+ const a = plans[0].ids;
1016
+ for (const p of plans.slice(1)) {
1017
+ let i = 0;
1018
+ while (i < k && p.ids[i] === a[i]) i++;
1019
+ k = i;
1020
+ }
1021
+ return k;
1022
+ }
1023
+ function askMany(be, tok, heads, state, qs, opts = {}) {
1024
+ return askManyWith(be, tok, heads, state, qs, () => opts);
1025
+ }
1026
+ async function askManyWith(be, tok, heads, state, qs, optsFor) {
1027
+ if (typeof state !== "string") throw new Error("askMany: state must be a string");
1028
+ const plans = qs.map((q0) => {
1029
+ if (q0?.state !== void 0 && q0.state !== state) throw new Error("askMany: a question carries a state different from the shared state");
1030
+ const q = { ...q0, state };
1031
+ return planAsk(be, tok, heads, q, optsFor(q));
1032
+ });
1033
+ const out = [];
1034
+ const k = plans.length > 1 && be.snapshot && be.prefillFrom ? sharedPrefix(plans) : 0;
1035
+ if (k < 1) {
1036
+ for (const p of plans) out.push(p.read(await be.prefill(p.ids, { positions: p.positions })));
1037
+ return out;
1038
+ }
1039
+ const h = await be.snapshot(plans[0].ids.slice(0, k));
1040
+ try {
1041
+ for (const p of plans) out.push(p.read(await be.prefillFrom(h, p.ids, { positions: p.positions })));
1042
+ } finally {
1043
+ await h.dispose();
1044
+ }
1045
+ return out;
1046
+ }
1047
+
1048
+ // src/webllm.ts
1049
+ function logitCapture() {
1050
+ let armed = false, cap = null, sampled = [];
1051
+ return {
1052
+ processor: {
1053
+ processLogits(l) {
1054
+ if (armed) {
1055
+ cap = new Float32Array(l);
1056
+ armed = false;
1057
+ }
1058
+ return l;
1059
+ },
1060
+ processSampledToken(t) {
1061
+ sampled.push(t);
1062
+ },
1063
+ resetState() {
1064
+ }
1065
+ },
1066
+ arm() {
1067
+ armed = true;
1068
+ cap = null;
1069
+ },
1070
+ take() {
1071
+ const c = cap;
1072
+ cap = null;
1073
+ armed = false;
1074
+ return c;
1075
+ },
1076
+ drain() {
1077
+ const s = sampled;
1078
+ sampled = [];
1079
+ return s;
1080
+ },
1081
+ reset() {
1082
+ armed = false;
1083
+ cap = null;
1084
+ sampled = [];
1085
+ }
1086
+ };
1087
+ }
1088
+ function normalizeRecovery(r) {
1089
+ const N = r.rows.length;
1090
+ if (!N || r.P.length % N) throw new Error(`webllm: P (${r.P.length}) is not a multiple of rows (${N})`);
1091
+ let P;
1092
+ if (r.P instanceof Uint16Array) {
1093
+ P = new Float32Array(r.P.length);
1094
+ for (let i = 0; i < P.length; i++) P[i] = halfToNumber(r.P[i]);
1095
+ } else P = r.P;
1096
+ return { rows: r.rows, P, D: P.length / N };
1097
+ }
1098
+ function recoverHidden(rec, logits) {
1099
+ const r = "D" in rec ? rec : normalizeRecovery(rec);
1100
+ const N = r.rows.length, sub = new Float32Array(N), h = new Float32Array(r.D);
1101
+ for (let n = 0; n < N; n++) sub[n] = logits[r.rows[n]];
1102
+ for (let d = 0; d < r.D; d++) {
1103
+ let acc = 0;
1104
+ const off = d * N;
1105
+ for (let n = 0; n < N; n++) acc += r.P[off + n] * sub[n];
1106
+ h[d] = acc;
1107
+ }
1108
+ return h;
1109
+ }
1110
+ async function loadRecovery(rowsUrl, pUrl, format = pUrl.endsWith(".f16") ? "f16" : "f32", fetchFn = fetch) {
1111
+ const [rb, pb] = await Promise.all([
1112
+ fetchFn(rowsUrl).then((r) => {
1113
+ if (!r.ok) throw new Error("webllm: " + rowsUrl + " " + r.status);
1114
+ return r.arrayBuffer();
1115
+ }),
1116
+ fetchFn(pUrl).then((r) => {
1117
+ if (!r.ok) throw new Error("webllm: " + pUrl + " " + r.status);
1118
+ return r.arrayBuffer();
1119
+ })
1120
+ ]);
1121
+ return { rows: new Int32Array(rb), P: format === "f16" ? new Uint16Array(pb) : new Float32Array(pb) };
1122
+ }
1123
+ async function tryLoadRecovery(base, files, fetchFn = fetch) {
1124
+ try {
1125
+ const rec = await loadRecovery(new URL(files.rows, base).href, new URL(files.P, base).href, files.P.endsWith(".f16") ? "f16" : "f32", fetchFn);
1126
+ const N = rec.rows.length;
1127
+ if (!N || rec.P.length % N) throw new Error(`P (${rec.P.length}) is not a multiple of rows (${N})`);
1128
+ return { recovery: rec };
1129
+ } catch (e) {
1130
+ return { reason: e?.message ?? String(e) };
1131
+ }
1132
+ }
1133
+ function webllmBackend(engine, capture, opts) {
1134
+ if (!opts.tok?.decode) throw new Error("webllm: opts.tok (tokenizer with decode) is required; WebLLM only accepts text");
1135
+ const eos = opts.eos ?? EOS;
1136
+ let rec = opts.recovery ? normalizeRecovery(opts.recovery) : null;
1137
+ const gate = gateFor(engine, "webllm");
1138
+ const decode = (ids) => opts.tok.decode(ids, { skip_special_tokens: false, clean_up_tokenization_spaces: false });
1139
+ async function logitsAt(ids, k) {
1140
+ await engine.resetChat();
1141
+ capture.reset();
1142
+ capture.arm();
1143
+ const r = await engine.completions.create({ prompt: decode(ids.slice(0, k + 1)), max_tokens: 1, temperature: 0 });
1144
+ const L = capture.take();
1145
+ if (!L) throw new Error("webllm: the LogitProcessor captured no logits (engine created without this capture's logitProcessorRegistry, or a Web Worker?)");
1146
+ const n = r?.usage?.prompt_tokens;
1147
+ if (n !== void 0 && n !== k + 1) throw new Error(`webllm: the prompt truncated at position ${k} re-tokenized to ${n} tokens (expected ${k + 1}); the position read would be wrong`);
1148
+ return L;
1149
+ }
1150
+ return {
1151
+ kind: "webllm",
1152
+ hiddenName: rec ? "recovered-from-logits" : null,
1153
+ dispose() {
1154
+ return gate.dispose(async () => {
1155
+ rec = null;
1156
+ await engine.unload?.();
1157
+ });
1158
+ },
1159
+ async prefill(ids, want) {
1160
+ gate.assertLive();
1161
+ if (!ids.length) throw new Error("prefill: empty ids");
1162
+ for (const p of want.positions) if (!Number.isInteger(p) || p < 0 || p >= ids.length) throw new Error(`webllm: position ${p} out of [0,${ids.length})`);
1163
+ if (want.positions.length && !rec) throw new Error("webllm: without opts.recovery the backend cannot recover hidden states; use method 'logits'");
1164
+ const rel = await gate.acquire();
1165
+ try {
1166
+ const last = ids.length - 1;
1167
+ const lastLogits = await logitsAt(ids, last);
1168
+ const at = /* @__PURE__ */ new Map();
1169
+ for (const p of new Set(want.positions)) at.set(p, recoverHidden(rec, p === last ? lastLogits : await logitsAt(ids, p)));
1170
+ return { hiddens: want.positions.map((p) => at.get(p).slice()), lastLogits };
1171
+ } finally {
1172
+ rel();
1173
+ }
1174
+ },
1175
+ async *generate(ids, o) {
1176
+ gate.assertLive();
1177
+ const g = resolveGenerateOptions(o);
1178
+ if (!ids.length) throw new Error("generate: empty ids");
1179
+ if (g.maxNewTokens === 0) return;
1180
+ const rel = await gate.acquire();
1181
+ let iter = null, finished = false;
1182
+ try {
1183
+ if (o.signal?.aborted) return;
1184
+ await engine.resetChat();
1185
+ capture.reset();
1186
+ const stream = await engine.completions.create({
1187
+ prompt: decode(ids),
1188
+ stream: true,
1189
+ max_tokens: g.maxNewTokens,
1190
+ temperature: g.temperature,
1191
+ top_p: g.topP,
1192
+ seed: g.seed,
1193
+ repetition_penalty: g.repetitionPenalty
1194
+ });
1195
+ iter = stream[Symbol.asyncIterator]();
1196
+ let n = 0, stop = false;
1197
+ while (!stop) {
1198
+ if (o.signal?.aborted) break;
1199
+ const nx = await iter.next();
1200
+ const toks = capture.drain();
1201
+ if (nx.done) finished = true;
1202
+ for (const t of toks) {
1203
+ if (eos.has(t)) {
1204
+ stop = true;
1205
+ break;
1206
+ }
1207
+ if (n >= g.maxNewTokens) {
1208
+ stop = true;
1209
+ break;
1210
+ }
1211
+ n++;
1212
+ yield t;
1213
+ if (o.signal?.aborted) {
1214
+ stop = true;
1215
+ break;
1216
+ }
1217
+ }
1218
+ if (nx.done) break;
1219
+ }
1220
+ } finally {
1221
+ if (iter) {
1222
+ try {
1223
+ if (!finished) {
1224
+ await engine.interruptGenerate();
1225
+ for (let i = 0; i < 1e5; i++) {
1226
+ const nx = await iter.next();
1227
+ if (nx.done) break;
1228
+ }
1229
+ }
1230
+ } catch {
1231
+ }
1232
+ try {
1233
+ await iter.return?.();
1234
+ } catch {
1235
+ }
1236
+ }
1237
+ capture.reset();
1238
+ rel();
1239
+ }
1240
+ }
1241
+ };
1242
+ }
1243
+ async function createWebllm(webllm, opts) {
1244
+ const capture = logitCapture();
1245
+ const appConfig = opts.appConfig ?? { model_list: [{ model: opts.modelUrl, model_id: opts.modelId, model_lib: opts.libUrl }] };
1246
+ const recovery = opts.recovery ?? (opts.recoveryUrls ? await loadRecovery(opts.recoveryUrls.rows, opts.recoveryUrls.P, opts.recoveryUrls.format) : void 0);
1247
+ const engine = await webllm.CreateMLCEngine(opts.modelId, {
1248
+ appConfig,
1249
+ logitProcessorRegistry: /* @__PURE__ */ new Map([[opts.modelId, capture.processor]]),
1250
+ initProgressCallback: opts.initProgressCallback
1251
+ });
1252
+ return Object.assign(webllmBackend(engine, capture, { tok: opts.tok, recovery, eos: opts.eos }), { engine, capture });
1253
+ }
1254
+
1255
+ // src/backends/litert.ts
1256
+ var LITERTJS_VERSION = "2.6.0";
1257
+ var LITERT_WARNING = `ct0-edge: runtime "litert" is opt-in (LiteRT.js @litertjs/core ${LITERTJS_VERSION}, WebGPU with streamed weights; about 10x slower per decision than "genai"). Pass quiet: true to silence this note.`;
1258
+ function h2f(h) {
1259
+ const s = h & 32768 ? -1 : 1, e = h >> 10 & 31, f = h & 1023;
1260
+ return e === 0 ? s * 2 ** -14 * (f / 1024) : e === 31 ? f ? NaN : s * Infinity : s * 2 ** (e - 15) * (1 + f / 1024);
1261
+ }
1262
+ function embedFromF16Reader(read, hidden) {
1263
+ return {
1264
+ async rows(ids) {
1265
+ const out = new Float32Array(ids.length * hidden), got = /* @__PURE__ */ new Map();
1266
+ await Promise.all([...new Set(ids)].map(async (id) => {
1267
+ const b = await read(id * hidden * 2, hidden * 2);
1268
+ const c = b.byteOffset % 2 ? b.slice() : b;
1269
+ got.set(id, new Uint16Array(c.buffer, c.byteOffset, hidden));
1270
+ }));
1271
+ ids.forEach((id, r) => {
1272
+ const q = got.get(id);
1273
+ for (let j = 0; j < hidden; j++) out[r * hidden + j] = h2f(q[j]);
1274
+ });
1275
+ return out;
1276
+ }
1277
+ };
1278
+ }
1279
+ function httpRangeReader(url, fetchFn = fetch) {
1280
+ return async (offset, length) => {
1281
+ const r = await fetchFn(url, { headers: { Range: `bytes=${offset}-${offset + length - 1}` } });
1282
+ if (!r.ok) throw new Error(`litert: ${url} -> HTTP ${r.status}`);
1283
+ const b = new Uint8Array(await r.arrayBuffer());
1284
+ return r.status === 206 ? b : b.subarray(offset, offset + length);
1285
+ };
1286
+ }
1287
+ var MASK_OFF = globalThis.__CT0_MASK_OFF ?? -1e30;
1288
+ function buildMask(pos, cache, dtype) {
1289
+ const rows = pos.length;
1290
+ if (dtype === "bool") {
1291
+ const m2 = new Uint8Array(rows * cache);
1292
+ for (let r = 0; r < rows; r++) m2.fill(1, r * cache, r * cache + pos[r] + 1);
1293
+ return m2;
1294
+ }
1295
+ const m = new Float32Array(rows * cache).fill(MASK_OFF);
1296
+ for (let r = 0; r < rows; r++) m.fill(0, r * cache, r * cache + pos[r] + 1);
1297
+ return m;
1298
+ }
1299
+ function prefillChunks(n, P) {
1300
+ const out = [];
1301
+ for (let s = 0; s < n; s += P) out.push({ start: s, len: Math.min(P, n - s) });
1302
+ return out;
1303
+ }
1304
+ var isState = (n) => n.startsWith("kv_cache_");
1305
+ var zerosOf = (io) => {
1306
+ const n = io.shape.reduce((a, b) => a * b, 1);
1307
+ return io.dtype === "int32" ? new Int32Array(n) : new Float32Array(n);
1308
+ };
1309
+ async function createLiteRt(lrt, graph, manifest, embed, opts = {}) {
1310
+ if (!opts.quiet) console.warn(LITERT_WARNING);
1311
+ const accelerator = opts.accelerator ?? "webgpu";
1312
+ const sigNames = Object.keys(manifest.signatures);
1313
+ const prefillName = sigNames.filter((s) => /^prefill_\d+$/.test(s)).sort((a, b) => +b.split("_")[1] - +a.split("_")[1])[0];
1314
+ if (!prefillName || !manifest.signatures.decode) throw new Error(`litert: the graph needs prefill_<N> and decode signatures (has ${sigNames.join(", ")})`);
1315
+ const P = +prefillName.split("_")[1];
1316
+ const dec = manifest.signatures.decode;
1317
+ const stateIO = dec.inputs.filter((i) => isState(i.name));
1318
+ const maskIO = dec.inputs.find((i) => i.name === "mask");
1319
+ if (!maskIO) throw new Error("litert: decode signature has no mask input");
1320
+ const cache = maskIO.shape[maskIO.shape.length - 1];
1321
+ const H = manifest.hidden;
1322
+ const paramIO = dec.inputs.find((i) => i.name === "param_tensor");
1323
+ const inplace = !!paramIO;
1324
+ const hiddenOut = (dec.outputs.find((o) => o.name === "hidden") ?? dec.outputs.find((o) => /(^|[_/])hidden(_states)?$/.test(o.name)))?.name ?? null;
1325
+ const gpuOptions = { precision: opts.gpuPrecision ?? "fp32" };
1326
+ const copyState = opts.copyState ?? accelerator === "webgpu";
1327
+ const model = accelerator === "webgpu" && opts.weights ? await lrt.loadModelAndWeights(graph, { getReader: () => opts.weights().getReader() }, { accelerator: "webgpu", gpuOptions }) : await lrt.loadAndCompile(graph, accelerator === "webgpu" ? { accelerator, gpuOptions } : { accelerator });
1328
+ const T = (data, shape, dtype) => dtype ? new lrt.Tensor(data, shape, dtype) : new lrt.Tensor(data, shape);
1329
+ const del = (t) => {
1330
+ try {
1331
+ t?.delete?.();
1332
+ } catch {
1333
+ }
1334
+ };
1335
+ const zeroStateHost = () => {
1336
+ const st = {};
1337
+ for (const io of stateIO) st[io.name] = T(zerosOf(io), io.shape);
1338
+ return st;
1339
+ };
1340
+ async function freshState() {
1341
+ const st = zeroStateHost();
1342
+ if (inplace && accelerator === "webgpu") for (const k in st) {
1343
+ const g = await st[k].moveTo("webgpu");
1344
+ st[k] = g;
1345
+ }
1346
+ return st;
1347
+ }
1348
+ const freeState = (st) => {
1349
+ for (const k in st) del(st[k]);
1350
+ };
1351
+ async function run(sig, emb, rows, pos, st, valid = rows) {
1352
+ const feed = { ...st, embeddings: T(emb, [1, rows, H]), input_pos: T(pos, [rows]), mask: T(buildMask(pos, cache, maskIO.dtype), [1, 1, rows, cache], maskIO.dtype === "bool" ? "bool" : void 0) };
1353
+ if (paramIO) {
1354
+ const p = new Int32Array(paramIO.shape.reduce((a, b) => a * b, 1));
1355
+ p[0] = pos[0];
1356
+ p[1] = pos[0] + valid;
1357
+ p[2] = pos[0] + valid;
1358
+ feed.param_tensor = T(p, paramIO.shape);
1359
+ }
1360
+ const out = await model.run(sig, feed);
1361
+ del(feed.embeddings);
1362
+ del(feed.input_pos);
1363
+ del(feed.mask);
1364
+ del(feed.param_tensor);
1365
+ if (inplace) return { st, logits: out.logits, hidden: hiddenOut ? out[hiddenOut] : void 0 };
1366
+ const ns = {};
1367
+ for (const io of stateIO) {
1368
+ const t = out[io.name];
1369
+ if (!t) throw new Error(`litert: ${sig} did not return ${io.name}`);
1370
+ if (copyState && t.copyTo) {
1371
+ ns[io.name] = await t.copyTo("wasm");
1372
+ del(t);
1373
+ } else ns[io.name] = t;
1374
+ }
1375
+ freeState(st);
1376
+ return { st: ns, logits: out.logits, hidden: hiddenOut ? out[hiddenOut] : void 0 };
1377
+ }
1378
+ async function readLogits(L) {
1379
+ const c = L.moveTo ? await L.moveTo("wasm") : L;
1380
+ const a = Float32Array.from(c.toTypedArray());
1381
+ if (c !== L) del(c);
1382
+ del(L);
1383
+ return a.subarray(a.length - manifest.vocab);
1384
+ }
1385
+ async function collectHidden(hid, firstPos, rows, want, at) {
1386
+ if (!hid || !want.size) {
1387
+ del(hid);
1388
+ return;
1389
+ }
1390
+ const c = hid.moveTo ? await hid.moveTo("wasm") : hid;
1391
+ const a = c.toTypedArray();
1392
+ for (let r = 0; r < rows; r++) {
1393
+ const p = firstPos + r;
1394
+ if (want.has(p)) at.set(p, Float32Array.from(a.subarray(r * H, r * H + H)));
1395
+ }
1396
+ if (c !== hid) del(c);
1397
+ del(hid);
1398
+ }
1399
+ async function prefillTokens(ids, from, end, st, want, at) {
1400
+ const grab = async (r, firstPos, rows) => {
1401
+ if (want && at) await collectHidden(r.hidden, firstPos, rows, want, at);
1402
+ else del(r.hidden);
1403
+ };
1404
+ if (opts.decodeOnlyPrefill) {
1405
+ for (let i = from; i < end; i++) {
1406
+ const r = await run("decode", await embed.rows([ids[i]]), 1, Int32Array.from([i]), st);
1407
+ st = r.st;
1408
+ del(r.logits);
1409
+ await grab(r, i, 1);
1410
+ }
1411
+ return st;
1412
+ }
1413
+ const remDec = opts.remainderViaDecode ?? accelerator === "webgpu";
1414
+ for (const c of prefillChunks(end - from, P)) {
1415
+ if (remDec && c.len < P) {
1416
+ for (let i = from + c.start; i < from + c.start + c.len; i++) {
1417
+ const r2 = await run("decode", await embed.rows([ids[i]]), 1, Int32Array.from([i]), st);
1418
+ st = r2.st;
1419
+ del(r2.logits);
1420
+ await grab(r2, i, 1);
1421
+ }
1422
+ continue;
1423
+ }
1424
+ const toks = ids.slice(from + c.start, from + c.start + c.len);
1425
+ const emb = new Float32Array(P * H);
1426
+ emb.set(await embed.rows(toks));
1427
+ const pos = new Int32Array(P);
1428
+ for (let i = 0; i < c.len; i++) pos[i] = from + c.start + i;
1429
+ const r = await run(prefillName, emb, P, pos, st, c.len);
1430
+ st = r.st;
1431
+ await grab(r, from + c.start, c.len);
1432
+ }
1433
+ return st;
1434
+ }
1435
+ async function decodeOne(tid, p, st) {
1436
+ const r = await run("decode", await embed.rows([tid]), 1, Int32Array.from([p]), st);
1437
+ return { st: r.st, logits: await readLogits(r.logits), hidden: r.hidden };
1438
+ }
1439
+ async function feedFrom(ids, from, st, positions) {
1440
+ const want = new Set(positions);
1441
+ const at = /* @__PURE__ */ new Map();
1442
+ const last = ids.length - 1;
1443
+ st = await prefillTokens(ids, from, last, st, want, at);
1444
+ const r = await decodeOne(ids[last], last, st);
1445
+ await collectHidden(r.hidden, last, 1, want, at);
1446
+ freeState(r.st);
1447
+ return { lastLogits: r.logits, hiddens: positions.map((p) => at.get(p)) };
1448
+ }
1449
+ const checkHidden = (want) => {
1450
+ if (want.positions.length && !hiddenOut) throw new Error('litert: this graph has no hidden-state output; use ask() with method "logits"');
1451
+ };
1452
+ const gate = createGate("litert");
1453
+ const snaps = /* @__PURE__ */ new WeakMap();
1454
+ const snapshotState = async (st) => Promise.all(stateIO.map(async (io) => {
1455
+ const t = st[io.name];
1456
+ const c = t.moveTo ? await t.moveTo("wasm") : t;
1457
+ const a = Float32Array.from(c.toTypedArray());
1458
+ if (c !== t) del(c);
1459
+ return a;
1460
+ }));
1461
+ const fromCopy = async (arrs) => {
1462
+ const st = {};
1463
+ stateIO.forEach((io, i) => {
1464
+ st[io.name] = T(arrs[i].slice(), io.shape);
1465
+ });
1466
+ if (inplace && accelerator === "webgpu") for (const k in st) st[k] = await st[k].moveTo("webgpu");
1467
+ return st;
1468
+ };
1469
+ return {
1470
+ kind: "litert",
1471
+ hiddenName: hiddenOut,
1472
+ dispose() {
1473
+ return gate.dispose(() => model.delete?.());
1474
+ },
1475
+ async prefill(ids, want) {
1476
+ gate.assertLive();
1477
+ checkHidden(want);
1478
+ if (!ids.length) throw new Error("prefill: empty ids");
1479
+ const rel = await gate.acquire();
1480
+ try {
1481
+ return await feedFrom(ids, 0, await freshState(), want.positions);
1482
+ } finally {
1483
+ rel();
1484
+ }
1485
+ },
1486
+ async snapshot(ids) {
1487
+ gate.assertLive();
1488
+ if (!ids.length) throw new Error("litert: snapshot of empty ids");
1489
+ const rel = await gate.acquire();
1490
+ try {
1491
+ const st = await prefillTokens(ids, 0, ids.length, await freshState());
1492
+ const h = { ids: ids.slice(), dispose() {
1493
+ snaps.delete(h);
1494
+ } };
1495
+ snaps.set(h, await snapshotState(st));
1496
+ freeState(st);
1497
+ return h;
1498
+ } finally {
1499
+ rel();
1500
+ }
1501
+ },
1502
+ async prefillFrom(h, ids, want) {
1503
+ gate.assertLive();
1504
+ checkHidden(want);
1505
+ const arrs = snaps.get(h);
1506
+ if (!arrs) throw new Error("litert: unknown or released snapshot handle");
1507
+ const k = checkContinuation("litert", h, ids, want.positions);
1508
+ const rel = await gate.acquire();
1509
+ try {
1510
+ return await feedFrom(ids, k, await fromCopy(arrs), want.positions);
1511
+ } finally {
1512
+ rel();
1513
+ }
1514
+ },
1515
+ async *generate(ids, o) {
1516
+ gate.assertLive();
1517
+ const g = resolveGenerateOptions(o), rand = mulberry32(g.seed);
1518
+ if (!ids.length) throw new Error("generate: empty ids");
1519
+ if (g.maxNewTokens === 0) return;
1520
+ const rel = await gate.acquire();
1521
+ let st = null;
1522
+ try {
1523
+ st = await prefillTokens(ids, 0, ids.length - 1, await freshState());
1524
+ let r = await decodeOne(ids[ids.length - 1], ids.length - 1, st);
1525
+ st = r.st;
1526
+ const gen = [];
1527
+ for (let s = 0, pos = ids.length; s < g.maxNewTokens; s++, pos++) {
1528
+ if (g.signal?.aborted || pos >= cache) break;
1529
+ const tid = sample(r.logits, gen, rand, g);
1530
+ if (EOS.has(tid)) break;
1531
+ gen.push(tid);
1532
+ yield tid;
1533
+ if (s + 1 >= g.maxNewTokens || g.signal?.aborted) break;
1534
+ r = await decodeOne(tid, pos, st);
1535
+ st = r.st;
1536
+ }
1537
+ } finally {
1538
+ if (st) freeState(st);
1539
+ rel();
1540
+ }
1541
+ }
1542
+ };
1543
+ }
1544
+
1545
+ // src/runtime.ts
1546
+ var DEFAULT_REPOS = {
1547
+ genai: "eduardoworrel/alias3-micro-1026-ONNX-genai",
1548
+ decodeOnly: "eduardoworrel/alias3-micro-1026-ONNX-decode-only",
1549
+ onnx: "eduardoworrel/alias3-micro-1026-ONNX",
1550
+ mlcQ4f16: "eduardoworrel/alias3-micro-1026-MLC-q4f16_1",
1551
+ mlcQ4f32: "eduardoworrel/alias3-micro-1026-MLC-q4f32_1",
1552
+ litert: "eduardoworrel/alias3-micro-1026-LiteRT"
1553
+ };
1554
+ var LITERT_QUANTS = ["int4", "int8", "int4-b64"];
1555
+ function litertBundleBase(o = {}) {
1556
+ if (o.litertBaseUrl) return o.litertBaseUrl;
1557
+ const q = o.litertQuant ?? "int4";
1558
+ if (!LITERT_QUANTS.includes(q)) throw new Error(`ct0-edge: litertQuant must be one of ${LITERT_QUANTS.join(", ")} (got ${JSON.stringify(q)})`);
1559
+ return `https://huggingface.co/${o.litertRepo ?? DEFAULT_REPOS.litert}/resolve/main/litert/${q}/`;
1560
+ }
1561
+ var DEFAULT_FILES = {
1562
+ head: "heads/head.bin",
1563
+ headScore: "heads/head_score.bin"
1564
+ };
1565
+ async function detectWebGPU(nav = globalThis.navigator) {
1566
+ try {
1567
+ const gpu = nav?.gpu;
1568
+ if (!gpu?.requestAdapter) return false;
1569
+ return !!await gpu.requestAdapter();
1570
+ } catch {
1571
+ return false;
1572
+ }
1573
+ }
1574
+ var RUNTIMES = ["genai", "decode-only", "transformers.js", "webllm", "litert"];
1575
+ var DEVICES = ["webgpu", "wasm", "cpu", "cuda"];
1576
+ var RUNTIME_LIMITS = {
1577
+ genai: { maxSafeTokens: 2048, byDevice: { webgpu: 7200 } },
1578
+ "decode-only": { maxSafeTokens: 2048, byDevice: { webgpu: 3600, cpu: 7200 } },
1579
+ "transformers.js": { maxSafeTokens: 2048 },
1580
+ webllm: { maxSafeTokens: 2048 },
1581
+ litert: { maxSafeTokens: 2e3 }
1582
+ };
1583
+ function maxSafeTokensFor(runtime, device) {
1584
+ const l = RUNTIME_LIMITS[runtime];
1585
+ return l.byDevice?.[device] ?? l.maxSafeTokens;
1586
+ }
1587
+ var MLC_Q4F32_LIB = "https://raw.githubusercontent.com/mlc-ai/binary-mlc-llm-libs/main/web-llm-models/v0_2_86/base/Qwen3.5-4B-q4f32_1_cs1k-webgpu.wasm";
1588
+ var RUNTIME_DEFAULTS = {
1589
+ genai: {
1590
+ repo: DEFAULT_REPOS.genai,
1591
+ files: { webgpu: "webgpu/int4/model.onnx", cuda: "cuda/int4/model.onnx" },
1592
+ heads: true,
1593
+ methods: DEFAULT_METHOD
1594
+ },
1595
+ "decode-only": {
1596
+ repo: DEFAULT_REPOS.decodeOnly,
1597
+ file: "onnx/model_q4.onnx",
1598
+ heads: true,
1599
+ methods: DEFAULT_METHOD
1600
+ },
1601
+ "transformers.js": {
1602
+ repo: DEFAULT_REPOS.onnx,
1603
+ dtype: "q4",
1604
+ heads: true,
1605
+ methods: DEFAULT_METHOD
1606
+ },
1607
+ webllm: {
1608
+ builds: {
1609
+ q4f16_1: {
1610
+ repo: DEFAULT_REPOS.mlcQ4f16,
1611
+ modelId: "alias3-micro-1026-q4f16_1",
1612
+ lib: "model-lib.wasm",
1613
+ recovery: { rows: "recovery/P_q4f16_rows.i32", P: "recovery/P_q4f16_8192.f16" }
1614
+ },
1615
+ q4f32_1: {
1616
+ repo: DEFAULT_REPOS.mlcQ4f32,
1617
+ modelId: "alias3-micro-1026-q4f32_1",
1618
+ lib: MLC_Q4F32_LIB,
1619
+ recovery: { rows: "recovery/P_q4f32_rows.i32", P: "recovery/P_q4f32_8192.f16" }
1620
+ }
1621
+ },
1622
+ build: "q4f32_1",
1623
+ heads: true,
1624
+ methods: DEFAULT_METHOD,
1625
+ logitsOnlyMethods: { noul: "logits", choice: "logits", score: "logits" }
1626
+ },
1627
+ litert: {
1628
+ repo: DEFAULT_REPOS.litert,
1629
+ files: { manifest: "litert/manifest.json", graph: "litert/model.graph.tflite", weights: "litert/model.weights.bin", embed: "litert/embed.f16.bin" },
1630
+ wasm: "https://cdn.jsdelivr.net/npm/@litertjs/core@2.6.0/wasm/",
1631
+ heads: false,
1632
+ methods: { noul: "logits", choice: "logits", score: "logits" }
1633
+ }
1634
+ };
1635
+ function defaultMethods(runtime, opts = {}) {
1636
+ if (runtime === "webllm" && !opts.webllmRecovery) return RUNTIME_DEFAULTS.webllm.logitsOnlyMethods;
1637
+ if (runtime === "litert") return RUNTIME_DEFAULTS.litert.methods;
1638
+ return DEFAULT_METHOD;
1639
+ }
1640
+ function resolveRuntimeChoice(o, fallback = "auto") {
1641
+ if (o.runtime === void 0) return { choice: fallback };
1642
+ if (o.runtime !== "auto" && !RUNTIMES.includes(o.runtime))
1643
+ throw new Error(`ct0-edge: unknown runtime "${o.runtime}". Use "auto", ${RUNTIMES.map((r) => `"${r}"`).join(", ")}.`);
1644
+ return { choice: o.runtime };
1645
+ }
1646
+ function detectPlatform(g = globalThis) {
1647
+ const isNode = !!g.process?.versions?.node;
1648
+ const hasDom = typeof g.window !== "undefined" && typeof g.document !== "undefined";
1649
+ const isWorker = typeof g.WorkerGlobalScope !== "undefined" && typeof g.importScripts === "function";
1650
+ return isNode && !hasDom && !isWorker ? "node" : "browser";
1651
+ }
1652
+ var GENAI_NODE_CPU_ERROR = 'ct0-edge: runtime "genai" does not run on onnxruntime-node CPU (the int4 graph fails with "not enough space: expected 49152, got 0"). In Node use runtime "decode-only" (CPU) or "transformers.js", or run genai on a CUDA machine (device "cuda").';
1653
+ function checkDevice(platform, device) {
1654
+ if (!DEVICES.includes(device)) throw new Error(`ct0-edge: unknown device "${device}". Use "auto", ${DEVICES.map((d) => `"${d}"`).join(", ")}.`);
1655
+ if (platform === "browser" && device === "cuda") throw new Error('ct0-edge: device "cuda" is Node-only. In the browser use "webgpu" or "wasm".');
1656
+ if (platform === "node" && (device === "webgpu" || device === "wasm")) throw new Error(`ct0-edge: device "${device}" is browser-only. In Node use "cpu" or "cuda".`);
1657
+ }
1658
+ function deviceFor(runtime, want, caps) {
1659
+ const { platform } = caps;
1660
+ const dev = platform === "browser" && want === "cpu" ? "wasm" : want;
1661
+ if (dev !== "auto") checkDevice(platform, dev);
1662
+ const needGpu = (d) => {
1663
+ if (d === "webgpu" && !caps.webgpu) throw new Error(`ct0-edge: runtime "${runtime}" on WebGPU, but WebGPU is not available in this browser.`);
1664
+ return d;
1665
+ };
1666
+ const needCuda = (d) => {
1667
+ if (d === "cuda" && !caps.cuda) throw new Error(`ct0-edge: runtime "${runtime}" on CUDA, but onnxruntime-node lists no CUDA execution provider here.`);
1668
+ return d;
1669
+ };
1670
+ switch (runtime) {
1671
+ case "genai":
1672
+ if (platform === "browser") {
1673
+ if (dev === "wasm") throw new Error('ct0-edge: runtime "genai" needs WebGPU in the browser (its LinearAttention kernels have no WASM build). Use runtime "decode-only" on "wasm".');
1674
+ return needGpu("webgpu");
1675
+ }
1676
+ if (dev === "cpu") throw new Error(GENAI_NODE_CPU_ERROR);
1677
+ if (!caps.cuda) throw new Error(GENAI_NODE_CPU_ERROR + " No CUDA execution provider was found.");
1678
+ return "cuda";
1679
+ case "webllm":
1680
+ if (platform === "node") throw new Error('ct0-edge: runtime "webllm" runs only in the browser (WebGPU). In Node use "decode-only" or "transformers.js".');
1681
+ if (dev === "wasm") throw new Error('ct0-edge: runtime "webllm" needs WebGPU.');
1682
+ return needGpu("webgpu");
1683
+ case "litert":
1684
+ if (platform === "node") throw new Error('ct0-edge: runtime "litert" runs in the browser (LiteRT.js streams the weights into WebGPU buffers). In Node use "decode-only" or "transformers.js".');
1685
+ if (dev === "wasm") throw new Error('ct0-edge: runtime "litert" needs WebGPU (the CT0 weights exceed the 2 GiB LiteRT.js Wasm heap).');
1686
+ return needGpu("webgpu");
1687
+ case "decode-only":
1688
+ case "transformers.js":
1689
+ if (dev !== "auto") return platform === "browser" ? needGpu(dev) : needCuda(dev);
1690
+ return platform === "browser" ? caps.webgpu ? "webgpu" : "wasm" : "cpu";
1691
+ }
1692
+ }
1693
+ function planRuntime(choice, device = "auto", caps) {
1694
+ if (choice !== "auto") {
1695
+ if (!RUNTIMES.includes(choice)) throw new Error(`ct0-edge: unknown runtime "${String(choice)}".`);
1696
+ return [{ runtime: choice, device: deviceFor(choice, device, caps) }];
1697
+ }
1698
+ const { platform } = caps;
1699
+ const dev = platform === "browser" && device === "cpu" ? "wasm" : device;
1700
+ if (dev !== "auto") checkDevice(platform, dev);
1701
+ if (platform === "browser") {
1702
+ if (dev === "wasm") return [{ runtime: "decode-only", device: "wasm" }];
1703
+ if (dev === "webgpu" && !caps.webgpu) throw new Error('ct0-edge: device "webgpu" requested, but WebGPU is not available in this browser.');
1704
+ if (caps.webgpu) return [{ runtime: "genai", device: "webgpu" }, { runtime: "decode-only", device: "webgpu" }];
1705
+ return [{ runtime: "decode-only", device: "wasm" }];
1706
+ }
1707
+ const nodeCpu = [{ runtime: "transformers.js", device: "cpu" }, { runtime: "decode-only", device: "cpu" }];
1708
+ if (dev === "cpu") return nodeCpu;
1709
+ if (dev === "cuda") {
1710
+ if (!caps.cuda) throw new Error('ct0-edge: device "cuda" requested, but onnxruntime-node lists no CUDA execution provider here.');
1711
+ return [{ runtime: "genai", device: "cuda" }, { runtime: "transformers.js", device: "cuda" }];
1712
+ }
1713
+ if (caps.cuda) return [{ runtime: "genai", device: "cuda" }, { runtime: "transformers.js", device: "cuda" }, ...nodeCpu];
1714
+ return nodeCpu;
1715
+ }
1716
+ async function selectRuntime(choice, device, deps) {
1717
+ const status = deps.onStatus ?? (() => {
1718
+ });
1719
+ const plans = planRuntime(choice, device, deps.caps);
1720
+ if (choice !== "auto") return { ...plans[0], backend: await deps.load(plans[0]) };
1721
+ const reasons = [];
1722
+ if (plans[0].runtime !== "genai") {
1723
+ const { platform, webgpu, cuda } = deps.caps;
1724
+ reasons.push(platform === "browser" ? webgpu ? `device "${plans[0].device}" requested` : "WebGPU not available" : cuda ? `device "${plans[0].device}" requested` : "no CUDA execution provider (genai does not run on onnxruntime-node CPU)");
1725
+ }
1726
+ for (const [i, plan] of plans.entries()) {
1727
+ if (i > 0) status(`${reasons.at(-1)} -> ${plan.runtime} (${plan.device})`);
1728
+ try {
1729
+ const backend = await deps.load(plan);
1730
+ return { ...plan, backend, fallbackReason: reasons.length ? reasons.join("; ") : void 0 };
1731
+ } catch (e) {
1732
+ reasons.push(`${plan.runtime} (${plan.device}) unavailable: ${e?.message ?? String(e)}`);
1733
+ if (i === plans.length - 1) {
1734
+ const err = new Error(`ct0-edge: no runtime loaded. ${reasons.join("; ")}`);
1735
+ err.cause = e;
1736
+ throw err;
1737
+ }
1738
+ }
1739
+ }
1740
+ throw new Error("ct0-edge: no runtime candidate");
1741
+ }
1742
+ function packagesFor(runtime, platform) {
1743
+ const ort = platform === "node" ? "onnxruntime-node" : "onnxruntime-web";
1744
+ if (runtime === "genai" || runtime === "decode-only") return ["@huggingface/transformers", ort];
1745
+ if (runtime === "webllm") return ["@huggingface/transformers", "@mlc-ai/web-llm"];
1746
+ if (runtime === "litert") return ["@huggingface/transformers", "@litertjs/core"];
1747
+ return ["@huggingface/transformers"];
1748
+ }
1749
+ function isNotFound(e, pkg) {
1750
+ const msg = String(e?.message ?? e);
1751
+ if (e?.code === "ERR_MODULE_NOT_FOUND" || e?.code === "MODULE_NOT_FOUND" || /Cannot find (package|module)/.test(msg)) return msg.includes(pkg);
1752
+ return /Failed to resolve module specifier|Failed to fetch dynamically imported module|error loading dynamically imported module|Importing a module script failed/i.test(msg);
1753
+ }
1754
+ async function importOptional(pkg, importer, why) {
1755
+ try {
1756
+ return await importer();
1757
+ } catch (e) {
1758
+ if (!isNotFound(e, pkg)) throw e;
1759
+ const err = new Error(`ct0-edge: ${why} needs the package "${pkg}", which is not installed. Install it: npm i ${pkg}`);
1760
+ err.cause = e;
1761
+ throw err;
1762
+ }
1763
+ }
1764
+ function detectCuda(ortNode) {
1765
+ try {
1766
+ return !!ortNode?.listSupportedBackends?.().some((b) => b?.name === "cuda");
1767
+ } catch {
1768
+ return false;
1769
+ }
1770
+ }
1771
+
1772
+ // src/nodeFiles.ts
1773
+ var nodeImport = (spec) => import(spec);
1774
+ async function defaultCacheDir() {
1775
+ const env = globalThis.process?.env ?? {};
1776
+ if (env.CT0_CACHE) return env.CT0_CACHE;
1777
+ const os = await nodeImport("node:os"), path = await nodeImport("node:path");
1778
+ return path.join(env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache"), "ct0-edge");
1779
+ }
1780
+ async function localFile(url, cacheDir, onStatus = () => {
1781
+ }) {
1782
+ const { fileURLToPath } = await nodeImport("node:url");
1783
+ if (url.startsWith("file:")) return fileURLToPath(url);
1784
+ if (!/^https?:/i.test(url)) return url;
1785
+ const fs = await nodeImport("node:fs"), path = await nodeImport("node:path");
1786
+ const u = new URL(url);
1787
+ const dest = path.join(cacheDir, u.hostname, ...decodeURIComponent(u.pathname).split("/").filter((s) => s && s !== ".."));
1788
+ if (fs.existsSync(dest)) return dest;
1789
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
1790
+ const part = `${dest}.part-${globalThis.process?.pid ?? 0}`;
1791
+ const name = path.basename(dest);
1792
+ try {
1793
+ const res = await get(url);
1794
+ const total = Number(res.headers["content-length"]) || 0;
1795
+ let got = 0, shown = -1;
1796
+ res.on("data", (c) => {
1797
+ got += c.length;
1798
+ const pct = total ? Math.floor(100 * got / total / 10) * 10 : -1;
1799
+ if (pct !== shown && pct >= 0) {
1800
+ shown = pct;
1801
+ onStatus(`downloading ${name}: ${pct}% of ${(total / 1e9).toFixed(2)} GB`);
1802
+ }
1803
+ });
1804
+ const { pipeline } = await nodeImport("node:stream/promises");
1805
+ await pipeline(res, fs.createWriteStream(part));
1806
+ if (total && got !== total) throw new Error(`ct0-edge: download truncated: ${url} (${got} of ${total} bytes)`);
1807
+ fs.renameSync(part, dest);
1808
+ } catch (e) {
1809
+ try {
1810
+ fs.rmSync(part, { force: true });
1811
+ } catch {
1812
+ }
1813
+ throw e;
1814
+ }
1815
+ return dest;
1816
+ }
1817
+ async function get(url, hops = 0) {
1818
+ const mod = await nodeImport(url.startsWith("https:") ? "node:https" : "node:http");
1819
+ const res = await new Promise((resolve, reject) => mod.get(url, { headers: { "user-agent": "ct0-edge" } }, resolve).on("error", reject));
1820
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location && hops < 10) {
1821
+ res.resume();
1822
+ return get(new URL(res.headers.location, url).href, hops + 1);
1823
+ }
1824
+ if (res.statusCode !== 200) {
1825
+ res.resume();
1826
+ throw new Error(`ct0-edge: download failed: ${url} -> HTTP ${res.statusCode}`);
1827
+ }
1828
+ return res;
1829
+ }
1830
+ async function readLocalBytes(url) {
1831
+ const { fileURLToPath } = await nodeImport("node:url");
1832
+ const fs = await nodeImport("node:fs");
1833
+ const b = fs.readFileSync(url.startsWith("file:") ? fileURLToPath(url) : url);
1834
+ return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
1835
+ }
1836
+
1837
+ // src/ct0.ts
1838
+ var CT0 = class _CT0 {
1839
+ maxContext = 262144;
1840
+ maxSafeTokens = 2048;
1841
+ tok;
1842
+ backend;
1843
+ yesIds = [];
1844
+ noIds = [];
1845
+ constructor(tok, backend) {
1846
+ this.tok = tok;
1847
+ this.backend = backend;
1848
+ }
1849
+ heads = {};
1850
+ methods = defaultMethods("decode-only");
1851
+ runtime;
1852
+ device;
1853
+ fallbackReason;
1854
+ dispose() {
1855
+ return this.backend.dispose();
1856
+ }
1857
+ get hiddenOutput() {
1858
+ return this.backend.hiddenName;
1859
+ }
1860
+ get defaultMethod() {
1861
+ return this.methods;
1862
+ }
1863
+ static async load(opts = {}) {
1864
+ const status = opts.onStatus ?? (() => {
1865
+ });
1866
+ const platform = detectPlatform();
1867
+ const { choice } = resolveRuntimeChoice(opts, opts.baseUrl && !opts.genaiBaseUrl && !opts.genaiRepo ? "decode-only" : "auto");
1868
+ const device = opts.device ?? "auto";
1869
+ const webgpu = platform === "browser" ? await detectWebGPU() : false;
1870
+ planRuntime(choice, device, { platform, webgpu, cuda: true });
1871
+ const mods = opts.modules ?? {};
1872
+ const needsOrt = choice === "auto" || choice === "genai" || choice === "decode-only";
1873
+ const loadOrt = async () => {
1874
+ if (mods.ort) return mods.ort;
1875
+ if (platform === "node") return importOptional("onnxruntime-node", () => import(NODE_ORT), `runtime "${choice}" in Node`);
1876
+ return importOptional("onnxruntime-web", () => import("onnxruntime-web/webgpu"), `runtime "${choice}" in the browser`);
1877
+ };
1878
+ let ort = null;
1879
+ let cuda = false;
1880
+ if (platform === "node" && (needsOrt || device === "cuda")) {
1881
+ try {
1882
+ ort = await loadOrt();
1883
+ cuda = detectCuda(ort);
1884
+ } catch (e) {
1885
+ if (needsOrt) throw e;
1886
+ }
1887
+ }
1888
+ const caps = { platform, webgpu, cuda };
1889
+ status("loading tokenizer\u2026");
1890
+ const tjs = mods.transformers ?? await importOptional("@huggingface/transformers", () => import("@huggingface/transformers"), "ct0-edge (tokenizer)");
1891
+ const env = tjs.env;
1892
+ const pageBase = () => {
1893
+ if (typeof location !== "undefined") return location.href;
1894
+ const cwd = globalThis.process?.cwd?.();
1895
+ return cwd ? "file://" + encodeURI(cwd.replace(/\\/g, "/").replace(/^(?!\/)/, "/")) + "/" : void 0;
1896
+ };
1897
+ const absBase = (b) => new URL(b, pageBase()).href;
1898
+ const at = (p, b) => new URL(p, absBase(b)).href;
1899
+ const mlc = opts.mlc ?? {};
1900
+ const mlcBuild = RUNTIME_DEFAULTS.webllm.builds[mlc.build ?? RUNTIME_DEFAULTS.webllm.build];
1901
+ if (choice === "webllm" && !mlcBuild) throw new Error(`ct0-edge: unknown MLC build "${mlc.build}" (use "q4f32_1" or "q4f16_1")`);
1902
+ const repo = opts.repo ?? (choice === "webllm" ? mlcBuild.repo : opts.baseUrl ? void 0 : DEFAULT_REPOS.decodeOnly);
1903
+ let tok, base;
1904
+ if (repo) {
1905
+ env.allowRemoteModels = true;
1906
+ env.allowLocalModels = false;
1907
+ tok = await tjs.AutoTokenizer.from_pretrained(repo, { local_files_only: false });
1908
+ base = opts.baseUrl ?? `https://huggingface.co/${repo}/resolve/main/`;
1909
+ } else {
1910
+ base = absBase(opts.baseUrl);
1911
+ env.allowRemoteModels = false;
1912
+ env.allowLocalModels = true;
1913
+ const bu = new URL(base);
1914
+ if (bu.protocol === "file:") {
1915
+ const { fileURLToPath } = await import(NODE_URL);
1916
+ env.localModelPath = fileURLToPath(bu);
1917
+ } else env.localModelPath = typeof location !== "undefined" && bu.origin === location.origin ? bu.pathname : base;
1918
+ tok = await tjs.AutoTokenizer.from_pretrained("tok");
1919
+ }
1920
+ const genaiBase = opts.genaiBaseUrl ?? `https://huggingface.co/${opts.genaiRepo ?? DEFAULT_REPOS.genai}/resolve/main/`;
1921
+ const tjsRepo = opts.transformersRepo ?? RUNTIME_DEFAULTS["transformers.js"].repo;
1922
+ const tjsBase = `https://huggingface.co/${tjsRepo}/resolve/main/`;
1923
+ let cacheDir;
1924
+ const fileFor = async (url) => {
1925
+ if (platform !== "node") return url;
1926
+ cacheDir ??= opts.cacheDir ?? await defaultCacheDir();
1927
+ return localFile(url, cacheDir, status);
1928
+ };
1929
+ const loadDecodeOnly = async (dev) => {
1930
+ ort ??= await loadOrt();
1931
+ if (platform === "browser" && !opts.skipVersionCheck) checkOrtVersion(ort.env?.versions?.web);
1932
+ const file = opts.decodeModel ?? RUNTIME_DEFAULTS["decode-only"].file;
1933
+ const dataPath = opts.externalDataFile ?? file + "_data";
1934
+ const providers = opts.providers ?? [dev];
1935
+ const isLocal = /^(\.\/|\/)/.test(dataPath) || /^(\.\/|\/)/.test(file);
1936
+ const modelUrl = isLocal ? at(file, pageBase()) : at(file, base);
1937
+ const dataUrl = isLocal ? at(dataPath, pageBase()) : at(dataPath, base);
1938
+ status(`loading decode-only graph (${providers.join(",")})\u2026`);
1939
+ let S;
1940
+ if (platform === "node") {
1941
+ await fileFor(dataUrl);
1942
+ S = await ort.InferenceSession.create(await fileFor(modelUrl), { executionProviders: providers, logSeverityLevel: 3 });
1943
+ } else {
1944
+ S = await ort.InferenceSession.create(modelUrl, {
1945
+ executionProviders: providers,
1946
+ externalData: [{ path: dataPath.split("/").pop(), data: dataUrl }]
1947
+ });
1948
+ }
1949
+ try {
1950
+ return await createDecodeOnly(S, tok, { ort, pastLen: opts.pastLen, skipVersionCheck: opts.skipVersionCheck });
1951
+ } catch (e) {
1952
+ try {
1953
+ await S.release?.();
1954
+ } catch {
1955
+ }
1956
+ throw e;
1957
+ }
1958
+ };
1959
+ const loadGenai = async (dev) => {
1960
+ ort ??= await loadOrt();
1961
+ const file = opts.genaiModel ?? (dev === "cuda" ? RUNTIME_DEFAULTS.genai.files.cuda : RUNTIME_DEFAULTS.genai.files.webgpu);
1962
+ status(`loading genai int4 graph (${dev})\u2026`);
1963
+ if (platform === "browser")
1964
+ return createGenai(
1965
+ at(file, genaiBase),
1966
+ at(file + ".data", genaiBase),
1967
+ { ...opts.genai, verifyEp: opts.verifyEp ?? opts.genai?.verifyEp ?? false, ort, skipVersionCheck: opts.skipVersionCheck ?? opts.genai?.skipVersionCheck }
1968
+ );
1969
+ await fileFor(at(file + ".data", genaiBase));
1970
+ const S = await ort.InferenceSession.create(await fileFor(at(file, genaiBase)), { executionProviders: [dev], graphOptimizationLevel: "all", logSeverityLevel: 3 });
1971
+ try {
1972
+ const be = genaiFromSession(S, ort, opts.genai);
1973
+ if (opts.genai?.warmup ?? true) await be.prefill([248045], { positions: [] });
1974
+ return be;
1975
+ } catch (e) {
1976
+ try {
1977
+ await S.release?.();
1978
+ } catch {
1979
+ }
1980
+ throw e;
1981
+ }
1982
+ };
1983
+ const loadTransformers = async (dev) => {
1984
+ const dtype = opts.dtype ?? RUNTIME_DEFAULTS["transformers.js"].dtype;
1985
+ status(`loading transformers.js graph (${dtype}, ${dev})\u2026`);
1986
+ env.allowRemoteModels = true;
1987
+ env.allowLocalModels = false;
1988
+ if (platform === "node" && opts.cacheDir) env.cacheDir = opts.cacheDir;
1989
+ const model = await tjs.AutoModelForCausalLM.from_pretrained(tjsRepo, { dtype, device: dev });
1990
+ return transformersJsFromModel(model, tjs.Tensor);
1991
+ };
1992
+ let mlcBase = null, mlcNote, webllmRecovery = false;
1993
+ const loadWebllm = async () => {
1994
+ const webllm = mods.webllm ?? await importOptional("@mlc-ai/web-llm", () => import(WEBLLM_PKG), 'runtime "webllm"');
1995
+ const build = mlcBuild;
1996
+ const modelUrl = mlc.modelUrl ?? `https://huggingface.co/${build.repo}/`;
1997
+ const modelLib = mlc.modelLib ?? (/^https?:/.test(build.lib) ? build.lib : new URL("resolve/main/" + build.lib, modelUrl).href);
1998
+ mlcBase = new URL("resolve/main/", modelUrl).href;
1999
+ let recovery = mlc.recovery || void 0;
2000
+ if (!recovery && mlc.recovery !== false) {
2001
+ status("loading WebLLM hidden-state recovery\u2026");
2002
+ const files = mlc.recoveryUrls ?? build.recovery;
2003
+ const r = await tryLoadRecovery(mlcBase, files);
2004
+ recovery = r.recovery;
2005
+ if (!recovery) {
2006
+ mlcNote = `webllm recovery unavailable (${r.reason}): every question type reads the logits`;
2007
+ status(mlcNote);
2008
+ }
2009
+ }
2010
+ webllmRecovery = !!recovery;
2011
+ status(`loading WebLLM build ${mlc.build ?? RUNTIME_DEFAULTS.webllm.build}\u2026`);
2012
+ return createWebllm(webllm, {
2013
+ modelId: mlc.modelId ?? build.modelId,
2014
+ modelUrl,
2015
+ libUrl: modelLib,
2016
+ appConfig: mlc.appConfig,
2017
+ tok,
2018
+ recovery,
2019
+ initProgressCallback: mlc.initProgressCallback
2020
+ });
2021
+ };
2022
+ const loadLitert = async (dev) => {
2023
+ const lrt = mods.litert ?? await importOptional("@litertjs/core", () => import(LITERT_PKG), 'runtime "litert"');
2024
+ const D = RUNTIME_DEFAULTS.litert;
2025
+ const lbase = absBase(litertBundleBase(opts));
2026
+ const name = (f) => f.replace(/^litert\//, "");
2027
+ status(`loading LiteRT.js ${LITERTJS_VERSION} runtime\u2026`);
2028
+ try {
2029
+ await lrt.loadLiteRt(opts.litertWasmUrl ?? D.wasm, { jspi: !!globalThis.WebAssembly?.Suspending });
2030
+ } catch (e) {
2031
+ if (!/already loading|already loaded/i.test(String(e?.message ?? e))) throw e;
2032
+ }
2033
+ const manifest = await (await fetch(at(name(D.files.manifest), lbase))).json();
2034
+ const graph = new Uint8Array(await (await fetch(at(name(D.files.graph), lbase))).arrayBuffer());
2035
+ const weightsUrl = at(name(D.files.weights), lbase);
2036
+ status("streaming LiteRT weights into WebGPU\u2026");
2037
+ return createLiteRt(lrt, graph, manifest, embedFromF16Reader(httpRangeReader(at(name(D.files.embed), lbase)), manifest.hidden), {
2038
+ accelerator: dev === "webgpu" ? "webgpu" : "wasm",
2039
+ gpuPrecision: opts.litertPrecision,
2040
+ weights: manifest.externalized === false ? void 0 : () => {
2041
+ let body = null;
2042
+ return new ReadableStream({
2043
+ async pull(c) {
2044
+ body ??= (await fetch(weightsUrl)).body.getReader();
2045
+ const { done, value } = await body.read();
2046
+ if (done) c.close();
2047
+ else c.enqueue(value);
2048
+ },
2049
+ cancel() {
2050
+ body?.cancel();
2051
+ }
2052
+ });
2053
+ }
2054
+ });
2055
+ };
2056
+ const load = (p) => {
2057
+ switch (p.runtime) {
2058
+ case "genai":
2059
+ return loadGenai(p.device);
2060
+ case "decode-only":
2061
+ return loadDecodeOnly(p.device);
2062
+ case "transformers.js":
2063
+ return loadTransformers(p.device);
2064
+ case "webllm":
2065
+ return loadWebllm();
2066
+ case "litert":
2067
+ return loadLitert(p.device);
2068
+ }
2069
+ };
2070
+ const sel = await selectRuntime(choice, device, { caps, load, onStatus: status });
2071
+ const ct0 = new _CT0(tok, sel.backend);
2072
+ ct0.runtime = sel.runtime;
2073
+ ct0.device = sel.device;
2074
+ ct0.fallbackReason = [sel.fallbackReason, sel.runtime === "webllm" ? mlcNote : void 0].filter(Boolean).join("; ") || void 0;
2075
+ ct0.methods = defaultMethods(sel.runtime, { webllmRecovery });
2076
+ ct0.maxSafeTokens = opts.maxSafeTokens ?? maxSafeTokensFor(sel.runtime, sel.device);
2077
+ status(`runtime: ${sel.runtime} (${sel.device})`);
2078
+ const headsBase = sel.runtime === "genai" ? genaiBase : sel.runtime === "decode-only" ? base : sel.runtime === "transformers.js" ? tjsBase : sel.runtime === "webllm" && webllmRecovery ? mlcBase : null;
2079
+ if ((opts.heads ?? true) && headsBase) {
2080
+ try {
2081
+ await ct0.loadHeads({ noul: at(DEFAULT_FILES.head, headsBase), score: at(DEFAULT_FILES.headScore, headsBase) });
2082
+ } catch (e) {
2083
+ status("heads not loaded (" + (e?.message ?? e) + "); ask() with method head will throw");
2084
+ }
2085
+ }
2086
+ const firstId = (s) => {
2087
+ const a = ct0.ids(s);
2088
+ return a.length ? a[0] : null;
2089
+ };
2090
+ ct0.yesIds = [...new Set(["Yes", " Yes", "yes", " yes", "YES"].map(firstId).filter((x) => x != null))];
2091
+ ct0.noIds = [...new Set(["No", " No", "no", " no", "NO"].map(firstId).filter((x) => x != null))];
2092
+ status("ready.");
2093
+ return ct0;
2094
+ }
2095
+ ids(text) {
2096
+ return this.tok(text, { add_special_tokens: false }).input_ids.tolist()[0].map(Number);
2097
+ }
2098
+ countTokens(text) {
2099
+ return this.ids(text).length;
2100
+ }
2101
+ fitContext(text, opts = {}) {
2102
+ const reserve = opts.reserve ?? 300;
2103
+ const budget = Math.max(64, this.maxSafeTokens - reserve);
2104
+ const toks = this.ids(text);
2105
+ if (toks.length <= budget) return text;
2106
+ const head = Math.floor(budget * 0.6), tail = budget - head;
2107
+ const kept = [...toks.slice(0, head), ...toks.slice(toks.length - tail)];
2108
+ opts.onTrim?.({ original: toks.length, kept: budget });
2109
+ return this.tok.decode(kept, { skip_special_tokens: true });
2110
+ }
2111
+ async scanLong(text, question, opts = {}) {
2112
+ const win = opts.windowTokens ?? this.maxSafeTokens - 400;
2113
+ const overlap = opts.overlapTokens ?? 128;
2114
+ const thr = opts.threshold ?? 0.5;
2115
+ const toks = this.ids(text);
2116
+ const hits = [];
2117
+ if (toks.length <= win) {
2118
+ const p = await this.decide(text, question);
2119
+ opts.onChunk?.(0, p, 1);
2120
+ hits.push({ i: 0, start: 0, p, preview: text.slice(0, 100) });
2121
+ } else {
2122
+ const total = Math.ceil((toks.length - overlap) / (win - overlap));
2123
+ let i = 0, idx = 0;
2124
+ while (i < toks.length) {
2125
+ const chunk = this.tok.decode(toks.slice(i, i + win), { skip_special_tokens: true });
2126
+ const p = await this.decide(chunk, question);
2127
+ hits.push({ i: idx, start: i, p, preview: chunk.slice(0, 100) });
2128
+ opts.onChunk?.(idx, p, total);
2129
+ idx++;
2130
+ i += win - overlap;
2131
+ }
2132
+ }
2133
+ return { chunks: hits.length, max: Math.max(...hits.map((h) => h.p)), hits, flagged: hits.filter((h) => h.p >= thr) };
2134
+ }
2135
+ static inspectPage(doc) {
2136
+ const q = (s, f) => [...doc.querySelectorAll(s)].map(f);
2137
+ const comments = [];
2138
+ const w = doc.createTreeWalker(doc.body || doc.documentElement, 128);
2139
+ while (w.nextNode()) comments.push((w.currentNode.nodeValue || "").trim().slice(0, 180));
2140
+ const store = {};
2141
+ try {
2142
+ const ls = (doc.defaultView || window).localStorage;
2143
+ for (let i = 0; i < ls.length; i++) {
2144
+ const k = ls.key(i);
2145
+ store[k] = (ls.getItem(k) || "").slice(0, 80);
2146
+ }
2147
+ } catch {
2148
+ }
2149
+ return {
2150
+ title: doc.title,
2151
+ url: (doc.defaultView || {}).location?.search || "/",
2152
+ forms: q("form", (f) => ({ action: f.getAttribute("action"), method: f.method, inputs: [...f.querySelectorAll("input")].map((i) => ({ name: i.name, type: i.type, value: i.type === "hidden" ? i.value : void 0, autocomplete: i.autocomplete })) })),
2153
+ inputs: q("input,textarea", (i) => ({ sel: i.id ? "#" + i.id : i.name ? `[name=${i.name}]` : i.tagName.toLowerCase(), type: i.type })),
2154
+ buttons: q("button", (b) => ({ sel: b.id ? "#" + b.id : null, text: b.textContent.trim() })),
2155
+ links: q("a[href]", (a) => a.getAttribute("href")),
2156
+ texts: q("pre,code,[class*='debug'],[class*='error'],[class*='stack']", (e) => (e.textContent || "").trim().slice(0, 300)).filter(Boolean),
2157
+ comments,
2158
+ inlineScripts: q("script:not([src])", (s) => s.textContent.trim().slice(0, 220)).filter(Boolean),
2159
+ localStorage: store
2160
+ };
2161
+ }
2162
+ static pageSource(doc, max = 2500) {
2163
+ return (doc.body || doc.documentElement).outerHTML.slice(0, max);
2164
+ }
2165
+ turn(msg, system, think) {
2166
+ let t = "";
2167
+ if (system) t += "<|im_start|>system\n" + system + "<|im_end|>\n";
2168
+ t += "<|im_start|>user\n" + msg + "<|im_end|>\n<|im_start|>assistant\n";
2169
+ return t + (think ? "<think>\n" : "<think>\n\n</think>\n\n");
2170
+ }
2171
+ async *generate(prompt, opts = {}) {
2172
+ yield* this.stream(this.turn(prompt, opts.system, opts.think ?? true), opts);
2173
+ }
2174
+ async *stream(prompt, opts = {}) {
2175
+ for await (const tid of this.backend.generate(this.ids(prompt), opts)) yield this.tok.decode([tid], { skip_special_tokens: true });
2176
+ }
2177
+ async decide(state, question) {
2178
+ const sys = "You are a security decision function. Given a STATE and a yes/no QUESTION, answer with exactly one word: Yes or No.";
2179
+ const text = "<|im_start|>system\n" + sys + "<|im_end|>\n<|im_start|>user\nSTATE:\n" + state + "\n\nQUESTION: " + question + "\nAnswer Yes or No.<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n";
2180
+ const { lastLogits: L } = await this.backend.prefill(this.ids(text), { positions: [] });
2181
+ const all = [...this.yesIds, ...this.noIds];
2182
+ if (!all.length) return NaN;
2183
+ const mx = Math.max(...all.map((i) => L[i]));
2184
+ const Z = all.reduce((a, i) => a + Math.exp(L[i] - mx), 0);
2185
+ return this.yesIds.reduce((a, i) => a + Math.exp(L[i] - mx), 0) / Z;
2186
+ }
2187
+ async loadHeads(src) {
2188
+ const get2 = async (x) => {
2189
+ if (typeof x !== "string") return loadHead(x);
2190
+ if (x.startsWith("file:")) return loadHead(await readLocalBytes(x));
2191
+ const r = await fetch(x);
2192
+ if (!r.ok) throw new Error(`loadHeads: ${x} -> HTTP ${r.status}`);
2193
+ return loadHead(await r.arrayBuffer());
2194
+ };
2195
+ const nc = src.noul ?? src.choice;
2196
+ if (nc) this.heads.noulChoice = await get2(nc);
2197
+ if (src.score) this.heads.score = await get2(src.score);
2198
+ }
2199
+ ask(q, opts = {}) {
2200
+ return ask(this.backend, this.tok, this.heads, q, this.askOpts(q, opts));
2201
+ }
2202
+ askMany(state, qs, opts = {}) {
2203
+ return askManyWith(this.backend, this.tok, this.heads, state, qs, (q) => this.askOpts(q, opts));
2204
+ }
2205
+ askOpts(q, opts) {
2206
+ return { ...opts, method: opts.method ?? this.methods[q.kind], maxSafeTokens: opts.maxSafeTokens ?? this.maxSafeTokens };
2207
+ }
2208
+ async prefillHiddens(prompt) {
2209
+ if (!this.backend.hiddenName) return null;
2210
+ const t = tokenOffsets(this.tok, prompt);
2211
+ const dp = decidePos(t.tokens), lp = t.ids.length - 1, op = optPositionsFor(prompt, t.offsets);
2212
+ const { hiddens } = await this.backend.prefill(t.ids, { positions: [dp, lp, ...op] });
2213
+ return { atDecide: hiddens[0], atLast: hiddens[1], atOpts: hiddens.slice(2) };
2214
+ }
2215
+ };
2216
+ var NODE_ORT = "onnxruntime-node";
2217
+ var NODE_URL = "node:url";
2218
+ var WEBLLM_PKG = "@mlc-ai/web-llm";
2219
+ var LITERT_PKG = "@litertjs/core";
2220
+ export {
2221
+ CT0,
2222
+ DEFAULT_FILES,
2223
+ DEFAULT_METHOD,
2224
+ DEFAULT_REPOS,
2225
+ GENAI_NODE_CPU_ERROR,
2226
+ GENERATE_DEFAULTS,
2227
+ LITERT_QUANTS,
2228
+ MLC_Q4F32_LIB,
2229
+ RUNTIMES,
2230
+ RUNTIME_DEFAULTS,
2231
+ RUNTIME_LIMITS,
2232
+ askMany,
2233
+ defaultMethods,
2234
+ detectCuda,
2235
+ detectPlatform,
2236
+ detectWebGPU,
2237
+ importOptional,
2238
+ levelFromCuts,
2239
+ litertBundleBase,
2240
+ maxSafeTokensFor,
2241
+ packagesFor,
2242
+ planRuntime,
2243
+ resolveGenerateOptions,
2244
+ resolveRuntimeChoice,
2245
+ selectRuntime
2246
+ };