Bartholomheow commited on
Commit
72ecd4e
·
verified ·
1 Parent(s): f307571

Upload example_web.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. example_web.js +170 -0
example_web.js ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Minimal browser inference for Bartholomheow/Supra2-IMG-ONNX — no build step.
2
+ // Serve this folder over http(s) and open index.html (modules + WebGPU need secure context).
3
+ //
4
+ // npm i onnxruntime-web @huggingface/transformers (or use the import map below in index.html)
5
+ /*
6
+ <script type="importmap">
7
+ {"imports":{
8
+ "onnxruntime-web": "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.30.0/dist/ort.web.min.mjs",
9
+ "@huggingface/transformers": "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1"
10
+ }}
11
+ </script>
12
+ */
13
+
14
+ const REPO = 'Bartholomheow/Supra2-IMG-ONNX';
15
+ const fileUrl = (path) => `https://huggingface.co/${REPO}/resolve/main/${path}`;
16
+
17
+ function mulberry32(seed) {
18
+ let a = seed >>> 0;
19
+ return () => {
20
+ a = (a + 0x6d2b79f5) | 0;
21
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
22
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
23
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
24
+ };
25
+ }
26
+
27
+ function gaussian(seed, n) {
28
+ const rand = mulberry32(seed);
29
+ const out = new Float32Array(n);
30
+ for (let i = 0; i < n; i += 2) {
31
+ const r = Math.sqrt(-2 * Math.log(Math.max(rand(), 1e-12)));
32
+ const a = 2 * Math.PI * rand();
33
+ out[i] = r * Math.cos(a);
34
+ if (i + 1 < n) out[i + 1] = r * Math.sin(a);
35
+ }
36
+ return out;
37
+ }
38
+
39
+ async function download(url, file, onProgress) {
40
+ const cache = await caches.open('supra2-img-v1');
41
+ const hit = await cache.match(url);
42
+ if (hit) return hit.arrayBuffer();
43
+ const res = await fetch(url);
44
+ if (!res.ok) throw new Error(`download failed (${res.status}): ${file}`);
45
+ const total = Number(res.headers.get('content-length')) || null;
46
+ const reader = res.body.getReader();
47
+ const chunks = [];
48
+ let loaded = 0;
49
+ for (;;) {
50
+ const { done, value } = await reader.read();
51
+ if (done) break;
52
+ chunks.push(value);
53
+ loaded += value.byteLength;
54
+ onProgress?.({ file, loaded, total });
55
+ }
56
+ const buf = new Uint8Array(loaded);
57
+ let off = 0;
58
+ for (const c of chunks) { buf.set(c, off); off += c.byteLength; }
59
+ await cache.put(url, new Response(buf.slice(0)));
60
+ return buf.buffer;
61
+ }
62
+
63
+ function halfToFloat(h) {
64
+ const s = (h & 0x8000) << 16;
65
+ const e = (h >> 10) & 0x1f;
66
+ const m = h & 0x3ff;
67
+ const f = new Float32Array(1);
68
+ const u = new Uint32Array(f.buffer);
69
+ if (e === 0) {
70
+ if (m === 0) { u[0] = s; return f[0]; }
71
+ let mm = m, ee = -14;
72
+ while ((mm & 0x400) === 0) { mm <<= 1; ee -= 1; }
73
+ u[0] = s | ((ee + 127) << 23) | ((mm & 0x3ff) << 13);
74
+ return f[0];
75
+ }
76
+ if (e === 31) { u[0] = s | 0x7f800000 | (m << 13); return f[0]; }
77
+ u[0] = s | ((e + 112) << 23) | (m << 13);
78
+ return f[0];
79
+ }
80
+
81
+ export async function loadSupra({ onProgress } = {}) {
82
+ const ort = await import('onnxruntime-web');
83
+ const { AutoTokenizer } = await import('@huggingface/transformers');
84
+ const cfg = await (await fetch(fileUrl('pipeline_config.json'))).json();
85
+ const [ditBuf, encBuf, vaeBuf] = await Promise.all([
86
+ download(fileUrl(cfg.dit), cfg.dit, onProgress),
87
+ download(fileUrl(cfg.text_encoder), cfg.text_encoder, onProgress),
88
+ download(fileUrl(cfg.vae_decoder), cfg.vae_decoder, onProgress),
89
+ ]);
90
+ const opts = { executionProviders: ['webgpu'] };
91
+ const [dit, enc, vae] = await Promise.all([
92
+ ort.InferenceSession.create(ditBuf, opts),
93
+ ort.InferenceSession.create(encBuf, opts),
94
+ ort.InferenceSession.create(vaeBuf, opts),
95
+ ]);
96
+ const tokenize = await AutoTokenizer.from_pretrained(REPO);
97
+ return { ort, cfg, dit, enc, vae, tokenize };
98
+ }
99
+
100
+ export async function generate(model, prompt, { seed = 1, steps, cfg: guide } = {}) {
101
+ const { ort, cfg, dit, enc, vae, tokenize } = model;
102
+ steps ??= cfg.default_steps;
103
+ guide ??= cfg.default_cfg;
104
+ const N = cfg.latent_ch * cfg.latent_size ** 2;
105
+
106
+ async function encode(text) {
107
+ const t = await tokenize([text], { padding: 'max_length', truncation: true, max_length: cfg.ctx_len, return_attention_mask: true });
108
+ const toBig = (a) => BigInt64Array.from(a.data, (v) => BigInt(v));
109
+ const ids = toBig(t.input_ids);
110
+ const am = toBig(t.attention_mask);
111
+ const out = await enc.run({
112
+ input_ids: new ort.Tensor('int64', ids, [1, cfg.ctx_len]),
113
+ attention_mask: new ort.Tensor('int64', am, [1, cfg.ctx_len]),
114
+ });
115
+ const hidden = Object.values(out)[0];
116
+ // Encoder output may be fp16 halves or fp32 — normalize to float32.
117
+ const raw = hidden.data;
118
+ const data = raw instanceof Uint16Array ? Float32Array.from(raw, halfToFloat) : Float32Array.from(raw);
119
+ const mask = new Float32Array(cfg.ctx_len);
120
+ for (let i = 0; i < cfg.ctx_len; i++) mask[i] = am[i] === 0n ? 0 : 1;
121
+ return { ctx: data, mask };
122
+ }
123
+
124
+ // NOTE: encoder output here is decoded as fp32 for clarity. The shipped
125
+ // encoder_model.onnx is fp16 — for exact halves use a proper f16->f32 cast.
126
+ const cond = await encode(prompt);
127
+ const uncond = await encode('');
128
+ const shape = [1, cfg.latent_ch, cfg.latent_size, cfg.latent_size];
129
+ const toCtx = (c) => new ort.Tensor('float32', c.ctx, [1, cfg.ctx_len, c.ctx.length / cfg.ctx_len]);
130
+ let z = gaussian([...prompt].reduce((a, c) => a + c.codePointAt(0), seed), N);
131
+ const dt = 1 / steps;
132
+ for (let i = 0; i < steps; i++) {
133
+ const t = new ort.Tensor('float32', new Float32Array([i * dt]), [1]);
134
+ const vs = [];
135
+ for (const c of [cond, uncond]) {
136
+ const out = await dit.run({
137
+ z: new ort.Tensor('float32', z, shape), t,
138
+ ctx: toCtx(c), ctx_mask: new ort.Tensor('float32', c.mask, [1, cfg.ctx_len]),
139
+ });
140
+ vs.push(Object.values(out)[0].data);
141
+ }
142
+ const [vc, vu] = vs;
143
+ const next = new Float32Array(N);
144
+ for (let j = 0; j < N; j++) next[j] = z[j] + dt * (vu[j] + guide * (vc[j] - vu[j]));
145
+ z = next;
146
+ }
147
+ const scaled = new Float32Array(N);
148
+ for (let i = 0; i < N; i++) scaled[i] = z[i] / cfg.vae_scale;
149
+ const img = await vae.run({ z: new ort.Tensor('float32', scaled, shape) });
150
+ return { pixels: Object.values(img)[0].data, size: cfg.image_size }; // float NCHW in [-1, 1]
151
+ }
152
+
153
+ export function paint(pixels, size, canvas) {
154
+ canvas.width = size;
155
+ canvas.height = size;
156
+ const ctx = canvas.getContext('2d');
157
+ const img = ctx.createImageData(size, size);
158
+ for (let i = 0; i < size * size; i++) {
159
+ img.data[i * 4] = Math.round(Math.min(1, Math.max(0, (pixels[i] + 1) / 2)) * 255);
160
+ img.data[i * 4 + 1] = Math.round(Math.min(1, Math.max(0, (pixels[size * size + i] + 1) / 2)) * 255);
161
+ img.data[i * 4 + 2] = Math.round(Math.min(1, Math.max(0, (pixels[2 * size * size + i] + 1) / 2)) * 255);
162
+ img.data[i * 4 + 3] = 255;
163
+ }
164
+ ctx.putImageData(img, 0, 0);
165
+ }
166
+
167
+ // Usage:
168
+ // const model = await loadSupra({ onProgress: (p) => console.log(p.file, p.loaded) });
169
+ // const { pixels, size } = await generate(model, 'a lighthouse above violet clouds at dusk');
170
+ // paint(pixels, size, document.querySelector('canvas'));