Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -83,6 +83,30 @@ async () => {
|
|
| 83 |
let previousUrls = [];
|
| 84 |
function setLog(message) { $("sam-log").textContent = message; }
|
| 85 |
function setStatus(message) { $("sam-status").textContent = message; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
function errorDetails(error) {
|
| 87 |
const values = [];
|
| 88 |
for (const key of ["name", "message", "code", "stack"]) {
|
|
@@ -187,6 +211,9 @@ async () => {
|
|
| 187 |
runtime.env.wasm.numThreads = 0; // automatic, subject to browser support
|
| 188 |
}
|
| 189 |
runtimeProvider = provider;
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
| 191 |
return runtime;
|
| 192 |
}
|
|
@@ -332,7 +359,14 @@ async () => {
|
|
| 332 |
maskDims: [1, mask.length]
|
| 333 |
};
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
async function runDit(noisy, timeValue, audioFeatures, text, sequenceLength) {
|
|
|
|
| 336 |
const batch = 1;
|
| 337 |
const zerosInt = new BigInt64Array(sequenceLength);
|
| 338 |
const mask = new Uint8Array(sequenceLength).fill(1);
|
|
@@ -350,9 +384,11 @@ async () => {
|
|
| 350 |
};
|
| 351 |
const outputs = await sessions.dit.run(feeds);
|
| 352 |
const tensor = outputs.velocity || outputs[sessions.dit.outputNames[0]];
|
|
|
|
| 353 |
return new Float32Array(tensor.data);
|
| 354 |
}
|
| 355 |
async function runOde(initial, audioFeatures, text, steps, sequenceLength) {
|
|
|
|
| 356 |
let x = new Float32Array(initial);
|
| 357 |
const dt = 1 / steps;
|
| 358 |
for (let i = 0; i < steps; i++) {
|
|
@@ -368,9 +404,11 @@ async () => {
|
|
| 368 |
// Yield so status/progress updates can paint between long CPU steps.
|
| 369 |
await new Promise(resolve => setTimeout(resolve, 0));
|
| 370 |
}
|
|
|
|
| 371 |
return x;
|
| 372 |
}
|
| 373 |
async function decodeLatent(latent, latentChannels, timeSteps) {
|
|
|
|
| 374 |
const session = sessions.decoder;
|
| 375 |
const chunkSize = 25;
|
| 376 |
const hopLength = 1920;
|
|
@@ -396,6 +434,7 @@ async () => {
|
|
| 396 |
const output = new Float32Array(totalLength);
|
| 397 |
let offset = 0;
|
| 398 |
for (const chunk of chunks) { output.set(chunk, offset); offset += chunk.length; }
|
|
|
|
| 399 |
return output;
|
| 400 |
}
|
| 401 |
function wavBlob(samples, sampleRate) {
|
|
@@ -433,20 +472,28 @@ async () => {
|
|
| 433 |
const expectedSamples = audio.samples.length;
|
| 434 |
if (expectedSamples > 48000 * 20) throw new Error("For this first prototype, use audio no longer than 20 seconds.");
|
| 435 |
setStatus("Loading models for " + deviceLabel + "…");
|
|
|
|
|
|
|
| 436 |
if (provider === "wasm") {
|
| 437 |
setLog("CPU mode selected. ONNX Runtime will use WebAssembly in a background worker where supported, keeping the page responsive. This may take longer than GPU inference.");
|
| 438 |
}
|
| 439 |
await loadModels(provider);
|
|
|
|
|
|
|
| 440 |
const startedAt = performance.now();
|
| 441 |
setStatus("Encoding audio on " + deviceLabel + "…");
|
|
|
|
| 442 |
const encoded = await encodeAudio(audio.samples);
|
|
|
|
| 443 |
if (encoded.dims.length !== 3) throw new Error("Unexpected encoder output dimensions: " + JSON.stringify(encoded.dims));
|
| 444 |
const [batch, latentChannels, timeSteps] = encoded.dims;
|
| 445 |
if (batch !== 1 || latentChannels !== 128) {
|
| 446 |
throw new Error("Unexpected audio latent shape " + JSON.stringify(encoded.dims) + "; expected [1, 128, time].");
|
| 447 |
}
|
| 448 |
setStatus("Encoding text…");
|
|
|
|
| 449 |
const text = await encodeText(prompt);
|
|
|
|
| 450 |
const mixture = new Float32Array(timeSteps * 256);
|
| 451 |
for (let t = 0; t < timeSteps; t++) {
|
| 452 |
for (let c = 0; c < latentChannels; c++) {
|
|
@@ -474,21 +521,44 @@ async () => {
|
|
| 474 |
}
|
| 475 |
}
|
| 476 |
setStatus("Decoding target…");
|
|
|
|
| 477 |
const target = await decodeLatent(targetLatent, latentChannels, timeSteps);
|
|
|
|
| 478 |
setStatus("Decoding residual…");
|
|
|
|
| 479 |
const residual = await decodeLatent(residualLatent, latentChannels, timeSteps);
|
|
|
|
| 480 |
if (audio.samples.length !== expectedSamples) {
|
| 481 |
throw new Error("Internal audio buffer was unexpectedly detached or resized: expected " + expectedSamples + " samples, got " + audio.samples.length + ".");
|
| 482 |
}
|
| 483 |
showOutput("sam-target-player", "sam-target-download", target.slice(0, expectedSamples), 48000, "target.wav");
|
| 484 |
showOutput("sam-residual-player", "sam-residual-download", residual.slice(0, expectedSamples), 48000, "residual.wav");
|
| 485 |
setStatus("Finished");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
setLog("Finished browser inference.\nInput: " + file.name + "\nPrompt: " + prompt +
|
| 487 |
-
"\nDevice: " + deviceLabel + "\nProcessing time: " +
|
| 488 |
"\nODE steps: " + steps + "\nInput samples: " + expectedSamples +
|
| 489 |
"\nLatent shape: " + JSON.stringify(encoded.dims) +
|
| 490 |
"\nTarget samples: " + Math.min(target.length, expectedSamples) +
|
| 491 |
"\nResidual samples: " + Math.min(residual.length, expectedSamples) +
|
|
|
|
| 492 |
"\n\nThis is an experimental port. Compare quality and tensor shapes against the Python reference before relying on results.");
|
| 493 |
}
|
| 494 |
$("sam-clear-cache").addEventListener("click", async () => {
|
|
|
|
| 83 |
let previousUrls = [];
|
| 84 |
function setLog(message) { $("sam-log").textContent = message; }
|
| 85 |
function setStatus(message) { $("sam-status").textContent = message; }
|
| 86 |
+
function diagnosticEnvironment(provider) {
|
| 87 |
+
const runtime = ort;
|
| 88 |
+
const isolated = window.crossOriginIsolated === true;
|
| 89 |
+
const sharedArrayBuffer = typeof SharedArrayBuffer !== "undefined";
|
| 90 |
+
const requestedThreads = runtime?.env?.wasm?.numThreads ?? "not initialized";
|
| 91 |
+
const proxy = runtime?.env?.wasm?.proxy ?? "not initialized";
|
| 92 |
+
const report = {
|
| 93 |
+
browser: navigator.userAgent,
|
| 94 |
+
hardwareConcurrency: navigator.hardwareConcurrency ?? "unknown",
|
| 95 |
+
crossOriginIsolated: isolated,
|
| 96 |
+
sharedArrayBufferAvailable: sharedArrayBuffer,
|
| 97 |
+
wasmMultithreadingLikelyAvailable: isolated && sharedArrayBuffer,
|
| 98 |
+
provider,
|
| 99 |
+
wasmThreadsRequested: requestedThreads,
|
| 100 |
+
wasmProxyWorker: proxy,
|
| 101 |
+
note: isolated && sharedArrayBuffer
|
| 102 |
+
? "WASM multithreading prerequisites appear available; actual thread use depends on ONNX Runtime/browser support."
|
| 103 |
+
: "WASM multithreading is likely unavailable because cross-origin isolation or SharedArrayBuffer is missing."
|
| 104 |
+
};
|
| 105 |
+
return report;
|
| 106 |
+
}
|
| 107 |
+
function logDiagnostic(label, details) {
|
| 108 |
+
console.info("[SAM Audio diagnostics] " + label, details);
|
| 109 |
+
}
|
| 110 |
function errorDetails(error) {
|
| 111 |
const values = [];
|
| 112 |
for (const key of ["name", "message", "code", "stack"]) {
|
|
|
|
| 211 |
runtime.env.wasm.numThreads = 0; // automatic, subject to browser support
|
| 212 |
}
|
| 213 |
runtimeProvider = provider;
|
| 214 |
+
const diagnostics = diagnosticEnvironment(provider);
|
| 215 |
+
logDiagnostic("Runtime configured", diagnostics);
|
| 216 |
+
setLog("Runtime diagnostics\n" + JSON.stringify(diagnostics, null, 2));
|
| 217 |
}
|
| 218 |
return runtime;
|
| 219 |
}
|
|
|
|
| 359 |
maskDims: [1, mask.length]
|
| 360 |
};
|
| 361 |
}
|
| 362 |
+
const stageMetrics = {};
|
| 363 |
+
function addMetric(name, milliseconds) {
|
| 364 |
+
if (!stageMetrics[name]) stageMetrics[name] = {totalMs: 0, calls: 0};
|
| 365 |
+
stageMetrics[name].totalMs += milliseconds;
|
| 366 |
+
stageMetrics[name].calls += 1;
|
| 367 |
+
}
|
| 368 |
async function runDit(noisy, timeValue, audioFeatures, text, sequenceLength) {
|
| 369 |
+
const started = performance.now();
|
| 370 |
const batch = 1;
|
| 371 |
const zerosInt = new BigInt64Array(sequenceLength);
|
| 372 |
const mask = new Uint8Array(sequenceLength).fill(1);
|
|
|
|
| 384 |
};
|
| 385 |
const outputs = await sessions.dit.run(feeds);
|
| 386 |
const tensor = outputs.velocity || outputs[sessions.dit.outputNames[0]];
|
| 387 |
+
addMetric("DiT session.run", performance.now() - started);
|
| 388 |
return new Float32Array(tensor.data);
|
| 389 |
}
|
| 390 |
async function runOde(initial, audioFeatures, text, steps, sequenceLength) {
|
| 391 |
+
const started = performance.now();
|
| 392 |
let x = new Float32Array(initial);
|
| 393 |
const dt = 1 / steps;
|
| 394 |
for (let i = 0; i < steps; i++) {
|
|
|
|
| 404 |
// Yield so status/progress updates can paint between long CPU steps.
|
| 405 |
await new Promise(resolve => setTimeout(resolve, 0));
|
| 406 |
}
|
| 407 |
+
addMetric("ODE denoising total", performance.now() - started);
|
| 408 |
return x;
|
| 409 |
}
|
| 410 |
async function decodeLatent(latent, latentChannels, timeSteps) {
|
| 411 |
+
const started = performance.now();
|
| 412 |
const session = sessions.decoder;
|
| 413 |
const chunkSize = 25;
|
| 414 |
const hopLength = 1920;
|
|
|
|
| 434 |
const output = new Float32Array(totalLength);
|
| 435 |
let offset = 0;
|
| 436 |
for (const chunk of chunks) { output.set(chunk, offset); offset += chunk.length; }
|
| 437 |
+
addMetric("Decoder total", performance.now() - started);
|
| 438 |
return output;
|
| 439 |
}
|
| 440 |
function wavBlob(samples, sampleRate) {
|
|
|
|
| 472 |
const expectedSamples = audio.samples.length;
|
| 473 |
if (expectedSamples > 48000 * 20) throw new Error("For this first prototype, use audio no longer than 20 seconds.");
|
| 474 |
setStatus("Loading models for " + deviceLabel + "…");
|
| 475 |
+
const totalStartedAt = performance.now();
|
| 476 |
+
const modelLoadStartedAt = performance.now();
|
| 477 |
if (provider === "wasm") {
|
| 478 |
setLog("CPU mode selected. ONNX Runtime will use WebAssembly in a background worker where supported, keeping the page responsive. This may take longer than GPU inference.");
|
| 479 |
}
|
| 480 |
await loadModels(provider);
|
| 481 |
+
const modelLoadMs = performance.now() - modelLoadStartedAt;
|
| 482 |
+
for (const key of Object.keys(stageMetrics)) delete stageMetrics[key];
|
| 483 |
const startedAt = performance.now();
|
| 484 |
setStatus("Encoding audio on " + deviceLabel + "…");
|
| 485 |
+
const encoderStartedAt = performance.now();
|
| 486 |
const encoded = await encodeAudio(audio.samples);
|
| 487 |
+
const encoderMs = performance.now() - encoderStartedAt;
|
| 488 |
if (encoded.dims.length !== 3) throw new Error("Unexpected encoder output dimensions: " + JSON.stringify(encoded.dims));
|
| 489 |
const [batch, latentChannels, timeSteps] = encoded.dims;
|
| 490 |
if (batch !== 1 || latentChannels !== 128) {
|
| 491 |
throw new Error("Unexpected audio latent shape " + JSON.stringify(encoded.dims) + "; expected [1, 128, time].");
|
| 492 |
}
|
| 493 |
setStatus("Encoding text…");
|
| 494 |
+
const textStartedAt = performance.now();
|
| 495 |
const text = await encodeText(prompt);
|
| 496 |
+
const textMs = performance.now() - textStartedAt;
|
| 497 |
const mixture = new Float32Array(timeSteps * 256);
|
| 498 |
for (let t = 0; t < timeSteps; t++) {
|
| 499 |
for (let c = 0; c < latentChannels; c++) {
|
|
|
|
| 521 |
}
|
| 522 |
}
|
| 523 |
setStatus("Decoding target…");
|
| 524 |
+
const targetStartedAt = performance.now();
|
| 525 |
const target = await decodeLatent(targetLatent, latentChannels, timeSteps);
|
| 526 |
+
const targetDecodeMs = performance.now() - targetStartedAt;
|
| 527 |
setStatus("Decoding residual…");
|
| 528 |
+
const residualStartedAt = performance.now();
|
| 529 |
const residual = await decodeLatent(residualLatent, latentChannels, timeSteps);
|
| 530 |
+
const residualDecodeMs = performance.now() - residualStartedAt;
|
| 531 |
if (audio.samples.length !== expectedSamples) {
|
| 532 |
throw new Error("Internal audio buffer was unexpectedly detached or resized: expected " + expectedSamples + " samples, got " + audio.samples.length + ".");
|
| 533 |
}
|
| 534 |
showOutput("sam-target-player", "sam-target-download", target.slice(0, expectedSamples), 48000, "target.wav");
|
| 535 |
showOutput("sam-residual-player", "sam-residual-download", residual.slice(0, expectedSamples), 48000, "residual.wav");
|
| 536 |
setStatus("Finished");
|
| 537 |
+
const runtimeDiagnostics = diagnosticEnvironment(provider);
|
| 538 |
+
const stageTimings = {
|
| 539 |
+
modelLoadingSeconds: +(modelLoadMs / 1000).toFixed(2),
|
| 540 |
+
audioEncoderSeconds: +(encoderMs / 1000).toFixed(2),
|
| 541 |
+
textEncoderIncludingTokenizerSeconds: +(textMs / 1000).toFixed(2),
|
| 542 |
+
denoisingTotalSeconds: +((stageMetrics["ODE denoising total"]?.totalMs || 0) / 1000).toFixed(2),
|
| 543 |
+
ditSessionRunSeconds: +((stageMetrics["DiT session.run"]?.totalMs || 0) / 1000).toFixed(2),
|
| 544 |
+
ditCalls: stageMetrics["DiT session.run"]?.calls || 0,
|
| 545 |
+
averageDitCallSeconds: stageMetrics["DiT session.run"]?.calls
|
| 546 |
+
? +((stageMetrics["DiT session.run"].totalMs / stageMetrics["DiT session.run"].calls) / 1000).toFixed(3) : 0,
|
| 547 |
+
targetDecoderSeconds: +(targetDecodeMs / 1000).toFixed(2),
|
| 548 |
+
residualDecoderSeconds: +(residualDecodeMs / 1000).toFixed(2),
|
| 549 |
+
inferenceAfterModelsSeconds: +((performance.now() - startedAt) / 1000).toFixed(2),
|
| 550 |
+
totalSecondsIncludingModelLoad: +((performance.now() - totalStartedAt) / 1000).toFixed(2)
|
| 551 |
+
};
|
| 552 |
+
const diagnosticsText = "Runtime diagnostics\n" + JSON.stringify(runtimeDiagnostics, null, 2) +
|
| 553 |
+
"\n\nStage timings\n" + JSON.stringify(stageTimings, null, 2);
|
| 554 |
+
logDiagnostic("Inference completed", {runtime: runtimeDiagnostics, timings: stageTimings});
|
| 555 |
setLog("Finished browser inference.\nInput: " + file.name + "\nPrompt: " + prompt +
|
| 556 |
+
"\nDevice: " + deviceLabel + "\nProcessing time after model load: " + stageTimings.inferenceAfterModelsSeconds + " s" +
|
| 557 |
"\nODE steps: " + steps + "\nInput samples: " + expectedSamples +
|
| 558 |
"\nLatent shape: " + JSON.stringify(encoded.dims) +
|
| 559 |
"\nTarget samples: " + Math.min(target.length, expectedSamples) +
|
| 560 |
"\nResidual samples: " + Math.min(residual.length, expectedSamples) +
|
| 561 |
+
"\n\n" + diagnosticsText +
|
| 562 |
"\n\nThis is an experimental port. Compare quality and tensor shapes against the Python reference before relying on results.");
|
| 563 |
}
|
| 564 |
$("sam-clear-cache").addEventListener("click", async () => {
|