Spaces:
Running
Running
Update js/agate.js
Browse files- js/agate.js +161 -31
js/agate.js
CHANGED
|
@@ -24,6 +24,12 @@ export const VERSIONS = {
|
|
| 24 |
const countUrl = (v) => `https://huggingface.co/Logolabs/agate-preview-${v}/resolve/main/config.json`;
|
| 25 |
const FILES_T2 = ["tokenizer.json", "tokenizer_config.json", "text_encoder.onnx", "generator.onnx", "taesd_decoder.onnx"];
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
export async function webgpuStatus() {
|
| 28 |
if (!("gpu" in navigator)) return { ok: false, why: "This browser does not expose WebGPU (navigator.gpu is missing)." };
|
| 29 |
try {
|
|
@@ -75,6 +81,7 @@ export async function hasCachedModel(version = "001") {
|
|
| 75 |
export async function clearCache() {
|
| 76 |
let ok = true;
|
| 77 |
for (const v of Object.values(VERSIONS)) { try { ok = (await caches.delete(v.cache)) && ok; } catch { ok = false; } }
|
|
|
|
| 78 |
return ok;
|
| 79 |
}
|
| 80 |
|
|
@@ -244,6 +251,80 @@ export class Agate {
|
|
| 244 |
return out;
|
| 245 |
}
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
// SD3's resolution shift in Agate's convention (t = 0 noise), as agate/pipeline.py shift_t
|
| 248 |
static shiftT(t, shift) {
|
| 249 |
if (shift === 1) return t;
|
|
@@ -304,7 +385,16 @@ export class Agate {
|
|
| 304 |
|
| 305 |
// onPreview({ step, steps, plan, x1, hw }) after every step, when given and the graph outputs the plan: plan =
|
| 306 |
// the thinker output for the conditional branch (Float32Array 640 x 16 x 16), x1 = z + (1 - t) v (guided).
|
| 307 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
const C = this.manifest.latent_ch, D = this.manifest.ctx_dim;
|
| 309 |
const rc = this.mr ? this.manifest.resolutions[String(this.resolution)] : { latent_hw: this.manifest.latent_hw, shift: 1 };
|
| 310 |
const hw = rc.latent_hw, shift = Number(rc.shift || 1);
|
|
@@ -329,42 +419,82 @@ export class Agate {
|
|
| 329 |
feeds.counts = new ort.Tensor("float32", cnt, [2, L]);
|
| 330 |
}
|
| 331 |
|
| 332 |
-
|
| 333 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
t0 = performance.now();
|
| 335 |
const stepMs = [];
|
| 336 |
-
if (
|
|
|
|
|
|
|
| 337 |
z = await this.sampleFast({ z, grid, cfg, hw, C, L, D, ctx, mask, cnt: feeds.counts?.data, onStep, onPreview, previewReady, shouldStop, stepMs });
|
| 338 |
} else {
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
}
|
| 357 |
-
out.v.dispose?.();
|
| 358 |
-
if (out.plan) { const plan = out.plan.data.slice(); out.plan.dispose?.(); onPreview({ step: i + 1, steps, plan, x1, hw }); }
|
| 359 |
-
z = zn;
|
| 360 |
-
stepMs.push(performance.now() - ts);
|
| 361 |
-
onStep(i + 1, steps, z);
|
| 362 |
-
await new Promise((r) => setTimeout(r, 0)); // let the progress bar paint
|
| 363 |
-
}
|
| 364 |
}
|
| 365 |
T.samplerMs = performance.now() - t0;
|
| 366 |
-
T.
|
| 367 |
-
T.
|
|
|
|
| 368 |
|
| 369 |
t0 = performance.now();
|
| 370 |
const img = await this.sessions.vae.run({ latent: new ort.Tensor("float32", z, [1, C, hw, hw]) });
|
|
@@ -385,4 +515,4 @@ export class Agate {
|
|
| 385 |
T.bucket = L;
|
| 386 |
return { rgba, width: W, height: H, latent: z, timings: T, prepared: { ...prep, countsNonzero }, watermarked: watermark };
|
| 387 |
}
|
| 388 |
-
}
|
|
|
|
| 24 |
const countUrl = (v) => `https://huggingface.co/Logolabs/agate-preview-${v}/resolve/main/config.json`;
|
| 25 |
const FILES_T2 = ["tokenizer.json", "tokenizer_config.json", "text_encoder.onnx", "generator.onnx", "taesd_decoder.onnx"];
|
| 26 |
|
| 27 |
+
// Image-to-image: the SD 1.5 VAE encoder lives in its own Cache Storage bucket. It must NOT share a model
|
| 28 |
+
// cache: Agate.load() deletes every entry of a release's cache that is not one of that release's files.
|
| 29 |
+
const IMG_ENCODER_URL = "./models/vae_encoder/model_sd15.onnx";
|
| 30 |
+
const IMG_ENCODER_CACHE = "agate-img-encoder";
|
| 31 |
+
const SD_LATENT_SCALE = 0.18215;
|
| 32 |
+
|
| 33 |
export async function webgpuStatus() {
|
| 34 |
if (!("gpu" in navigator)) return { ok: false, why: "This browser does not expose WebGPU (navigator.gpu is missing)." };
|
| 35 |
try {
|
|
|
|
| 81 |
export async function clearCache() {
|
| 82 |
let ok = true;
|
| 83 |
for (const v of Object.values(VERSIONS)) { try { ok = (await caches.delete(v.cache)) && ok; } catch { ok = false; } }
|
| 84 |
+
try { await caches.delete(IMG_ENCODER_CACHE); } catch { ok = false; }
|
| 85 |
return ok;
|
| 86 |
}
|
| 87 |
|
|
|
|
| 251 |
return out;
|
| 252 |
}
|
| 253 |
|
| 254 |
+
// Loads the SD 1.5 VAE encoder on first use (own cache bucket, see IMG_ENCODER_CACHE).
|
| 255 |
+
async loadImageEncoder() {
|
| 256 |
+
if (this.sessions.imgEncoder) return this.sessions.imgEncoder;
|
| 257 |
+
const key = new Request(new URL(IMG_ENCODER_URL, location.href));
|
| 258 |
+
let buf;
|
| 259 |
+
try {
|
| 260 |
+
({ buf } = await fetchCached(IMG_ENCODER_CACHE, IMG_ENCODER_URL, key, 0, () => {}));
|
| 261 |
+
} catch (e) {
|
| 262 |
+
throw new Error(`Could not load the image encoder (${IMG_ENCODER_URL}): ${e.message || e}`);
|
| 263 |
+
}
|
| 264 |
+
this.sessions.imgEncoder = await ort.InferenceSession.create(buf, {
|
| 265 |
+
executionProviders: this.ep === "webgpu" ? ["webgpu", "wasm"] : ["wasm"],
|
| 266 |
+
});
|
| 267 |
+
return this.sessions.imgEncoder;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
// File -> scaled SD-VAE latent (Float32Array, C * (res/8)^2) at the CURRENT resolution. Re-run it after a
|
| 271 |
+
// resolution switch: a 512 px latent is 64x64, a 256 px one is 32x32.
|
| 272 |
+
async encodeReferenceImage(file) {
|
| 273 |
+
if (!file) throw new Error("Please select a reference image.");
|
| 274 |
+
const encoder = await this.loadImageEncoder();
|
| 275 |
+
|
| 276 |
+
// Read the image and center-crop it to a square.
|
| 277 |
+
const bitmap = await createImageBitmap(file);
|
| 278 |
+
const size = this.resolution;
|
| 279 |
+
const canvas = document.createElement("canvas");
|
| 280 |
+
canvas.width = size;
|
| 281 |
+
canvas.height = size;
|
| 282 |
+
|
| 283 |
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
| 284 |
+
const scale = Math.max(size / bitmap.width, size / bitmap.height);
|
| 285 |
+
const sw = size / scale;
|
| 286 |
+
const sh = size / scale;
|
| 287 |
+
const sx = (bitmap.width - sw) / 2;
|
| 288 |
+
const sy = (bitmap.height - sh) / 2;
|
| 289 |
+
|
| 290 |
+
ctx.drawImage(bitmap, sx, sy, sw, sh, 0, 0, size, size);
|
| 291 |
+
bitmap.close();
|
| 292 |
+
|
| 293 |
+
const pixels = ctx.getImageData(0, 0, size, size).data;
|
| 294 |
+
const plane = size * size;
|
| 295 |
+
const rgb = new Float32Array(3 * plane);
|
| 296 |
+
|
| 297 |
+
// RGB, channel-first, normalized to [-1, 1].
|
| 298 |
+
for (let i = 0; i < plane; i++) {
|
| 299 |
+
rgb[i] = pixels[i * 4] / 127.5 - 1;
|
| 300 |
+
rgb[plane + i] = pixels[i * 4 + 1] / 127.5 - 1;
|
| 301 |
+
rgb[2 * plane + i] = pixels[i * 4 + 2] / 127.5 - 1;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
const inputName = encoder.inputNames[0];
|
| 305 |
+
const outputName = encoder.outputNames[0];
|
| 306 |
+
const result = await encoder.run({
|
| 307 |
+
[inputName]: new ort.Tensor("float32", rgb, [1, 3, size, size]),
|
| 308 |
+
});
|
| 309 |
+
|
| 310 |
+
const latent = result[outputName];
|
| 311 |
+
const C = this.manifest.latent_ch;
|
| 312 |
+
const perLatent = C * (size / 8) * (size / 8);
|
| 313 |
+
let data = latent.data;
|
| 314 |
+
|
| 315 |
+
// Some SD VAE exports return "moments" (mean + logvar, 2*C channels) instead of a sampled latent.
|
| 316 |
+
// The mean is the first C channels, which are contiguous in NCHW layout.
|
| 317 |
+
if (data.length === 2 * perLatent) data = data.subarray(0, perLatent);
|
| 318 |
+
if (data.length !== perLatent) {
|
| 319 |
+
throw new Error(`Unexpected encoder output: ${latent.dims.join(" × ")} (expected ${perLatent} values)`);
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
// Agate samples in scaled SD-VAE latent space.
|
| 323 |
+
const out = Float32Array.from(data, (value) => value * SD_LATENT_SCALE);
|
| 324 |
+
latent.dispose?.();
|
| 325 |
+
return out;
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
// SD3's resolution shift in Agate's convention (t = 0 noise), as agate/pipeline.py shift_t
|
| 329 |
static shiftT(t, shift) {
|
| 330 |
if (shift === 1) return t;
|
|
|
|
| 385 |
|
| 386 |
// onPreview({ step, steps, plan, x1, hw }) after every step, when given and the graph outputs the plan: plan =
|
| 387 |
// the thinker output for the conditional branch (Float32Array 640 x 16 x 16), x1 = z + (1 - t) v (guided).
|
| 388 |
+
//
|
| 389 |
+
// Image-to-image: pass referenceLatent (from encodeReferenceImage) and denoise in [0, 1]. denoise = 1 is plain
|
| 390 |
+
// text-to-image noise; lower values start closer to the reference. The sampler then runs round(steps * denoise)
|
| 391 |
+
// steps (at least 1; none at all for denoise 0, which returns the reference latent decoded), so the step size and the cost per unit of change match a full text-to-image run.
|
| 392 |
+
async generate({
|
| 393 |
+
prompt, negative = "", seed = 0, steps = 50, cfg = 3.0,
|
| 394 |
+
noise = null, referenceLatent = null, denoise = 1.0,
|
| 395 |
+
watermark = true, onStep = () => {}, onPreview = null,
|
| 396 |
+
previewReady = () => true, shouldStop = () => false
|
| 397 |
+
}) {
|
| 398 |
const C = this.manifest.latent_ch, D = this.manifest.ctx_dim;
|
| 399 |
const rc = this.mr ? this.manifest.resolutions[String(this.resolution)] : { latent_hw: this.manifest.latent_hw, shift: 1 };
|
| 400 |
const hw = rc.latent_hw, shift = Number(rc.shift || 1);
|
|
|
|
| 419 |
feeds.counts = new ort.Tensor("float32", cnt, [2, L]);
|
| 420 |
}
|
| 421 |
|
| 422 |
+
// ---- starting point: pure noise (text-to-image) or a noised reference latent (image-to-image) ----
|
| 423 |
+
const dn = Number(denoise);
|
| 424 |
+
denoise = Number.isFinite(dn) ? Math.max(0, Math.min(1, dn)) : 1;
|
| 425 |
+
|
| 426 |
+
if (referenceLatent && referenceLatent.length !== per) {
|
| 427 |
+
throw new Error(
|
| 428 |
+
`Reference latent has ${referenceLatent.length} values; expected ${per}. ` +
|
| 429 |
+
`Re-encode the reference image after changing resolution.`
|
| 430 |
+
);
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
const zNoise = noise
|
| 434 |
+
? Float32Array.from(noise)
|
| 435 |
+
: Agate.gaussianNoise(seed, per);
|
| 436 |
+
|
| 437 |
+
let z = zNoise;
|
| 438 |
+
const startT = referenceLatent ? 1 - denoise : 0;
|
| 439 |
+
|
| 440 |
+
if (referenceLatent && denoise === 0) {
|
| 441 |
+
z = Float32Array.from(referenceLatent);
|
| 442 |
+
} else if (referenceLatent) {
|
| 443 |
+
const ts = Agate.shiftT(startT, shift);
|
| 444 |
+
z = new Float32Array(per);
|
| 445 |
+
|
| 446 |
+
for (let k = 0; k < per; k++) {
|
| 447 |
+
z[k] = (1 - ts) * zNoise[k] + ts * referenceLatent[k];
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
// denoise 0 means "return the reference as is": no sampling. Otherwise at least one step.
|
| 452 |
+
const nSteps = !referenceLatent ? steps : denoise === 0 ? 0 : Math.max(1, Math.round(steps * denoise));
|
| 453 |
+
const grid = nSteps === 0 ? [1] : Array.from(
|
| 454 |
+
{ length: nSteps + 1 },
|
| 455 |
+
(_, i) => Agate.shiftT(
|
| 456 |
+
startT + (1 - startT) * i / nSteps,
|
| 457 |
+
shift
|
| 458 |
+
)
|
| 459 |
+
);
|
| 460 |
+
|
| 461 |
t0 = performance.now();
|
| 462 |
const stepMs = [];
|
| 463 |
+
if (nSteps === 0) {
|
| 464 |
+
// z is already the reference latent: skip the sampler
|
| 465 |
+
} else if (this.fast) {
|
| 466 |
z = await this.sampleFast({ z, grid, cfg, hw, C, L, D, ctx, mask, cnt: feeds.counts?.data, onStep, onPreview, previewReady, shouldStop, stepMs });
|
| 467 |
} else {
|
| 468 |
+
const zz = new Float32Array(2 * per), tt = new Float32Array(2);
|
| 469 |
+
for (let i = 0; i < nSteps; i++) {
|
| 470 |
+
if (shouldStop()) throw new Error("stopped");
|
| 471 |
+
const ts = performance.now();
|
| 472 |
+
zz.set(z, 0); zz.set(z, per);
|
| 473 |
+
tt[0] = tt[1] = grid[i];
|
| 474 |
+
const want = onPreview && this.hasPlan ? ["v", "plan"] : ["v"];
|
| 475 |
+
const out = await this.sessions.gen.run({ z: new ort.Tensor("float32", zz, [2, C, hw, hw]), t: new ort.Tensor("float32", tt, [2]), ...feeds }, want);
|
| 476 |
+
const v = out.v.data;
|
| 477 |
+
const dt = grid[i + 1] - grid[i];
|
| 478 |
+
const zn = new Float32Array(per);
|
| 479 |
+
let x1 = null;
|
| 480 |
+
if (out.plan) x1 = new Float32Array(per);
|
| 481 |
+
for (let k = 0; k < per; k++) {
|
| 482 |
+
const vc = v[k], vu = v[per + k], g = vu + cfg * (vc - vu);
|
| 483 |
+
zn[k] = z[k] + dt * g;
|
| 484 |
+
if (x1) x1[k] = z[k] + (1 - grid[i]) * g;
|
| 485 |
+
}
|
| 486 |
+
out.v.dispose?.();
|
| 487 |
+
if (out.plan) { const plan = out.plan.data.slice(); out.plan.dispose?.(); onPreview({ step: i + 1, steps: nSteps, plan, x1, hw }); }
|
| 488 |
+
z = zn;
|
| 489 |
+
stepMs.push(performance.now() - ts);
|
| 490 |
+
onStep(i + 1, nSteps, z);
|
| 491 |
+
await new Promise((r) => setTimeout(r, 0)); // let the progress bar paint
|
| 492 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
}
|
| 494 |
T.samplerMs = performance.now() - t0;
|
| 495 |
+
T.steps = nSteps;
|
| 496 |
+
T.firstStepMs = stepMs[0] ?? 0;
|
| 497 |
+
T.stepMs = stepMs.length > 1 ? (T.samplerMs - stepMs[0]) / (stepMs.length - 1) : (stepMs[0] ?? 0);
|
| 498 |
|
| 499 |
t0 = performance.now();
|
| 500 |
const img = await this.sessions.vae.run({ latent: new ort.Tensor("float32", z, [1, C, hw, hw]) });
|
|
|
|
| 515 |
T.bucket = L;
|
| 516 |
return { rgba, width: W, height: H, latent: z, timings: T, prepared: { ...prep, countsNonzero }, watermarked: watermark };
|
| 517 |
}
|
| 518 |
+
}
|