diff --git a/README.md b/README.md
index bc79613..c58be7a 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,8 @@ the downloaded model.
The site uses ACE-Step 1.5 Turbo in direct mode. The optional planner is
available in the underlying runtime but disabled on the public music page.
+Advanced settings includes a full-size, experimental INT8 quality preview for
+listening comparisons; it does not yet provide a smaller download.
See the [ACE-Step README](packages/acestep/README.md) for implementation and
validation details.
diff --git a/music.html b/music.html
index a13657b..82d6897 100644
--- a/music.html
+++ b/music.html
@@ -100,11 +100,9 @@
ACE-Step 1.5
>Ace-Step 1.5 music generation model.
-
- The model is downloaded on first generation and cached for later visits.
-
-
- Total download size is 5.75 GB
+
+ The first generation downloads 5.75 GB , then caches
+ the model for later visits.
ACE-Step 1.5
placeholder="e.g. English"
/>
+
+ Model
+
+ Production (recommended)
+ INT8 quality preview (experimental)
+
+
+ The preview tests audible quantization effects but is not compressed.
+ It is also 5.75 GB; switching from a cached production model adds a
+ separate 3.02 GB DiT download. Use the same seed to compare.
+
+
@@ -274,6 +284,10 @@
Preparing
Generation time
—
+
+
Model
+ —
+
diff --git a/packages/acestep/optimization/LEDGER.md b/packages/acestep/optimization/LEDGER.md
index 7b02c1f..dacfe27 100644
--- a/packages/acestep/optimization/LEDGER.md
+++ b/packages/acestep/optimization/LEDGER.md
@@ -3,7 +3,7 @@
Stage 2 was explicitly authorized on 2026-08-13. The approved baseline is
frozen and measured optimization is active.
-Next available ID: `OPT-0090`.
+Next available ID: `OPT-0091`.
| ID | Subsystem | Hypothesis | Evidence | Disposition | Result | Record | Implementation |
| --- | --- | --- | --- | --- | --- | --- | --- |
@@ -96,6 +96,7 @@ Next available ID: `OPT-0090`.
| OPT-0087 | Planner package-native dense GEMV | Route the already exact direct M1/M2 packed-BF16 kernel through all real planner decode layers and tied-head slices so the package can realize its useful bandwidth without OPT-0083's artificial submit-and-drain boundary around every isolated layer sample | positive | pending-integration | Actual Chrome preserved every full logit, appended K/V-cache evidence word, status, token, Philox cursor, topology, cancellation, and lifecycle result. Direct B won `16/16`, lowered every M1/M2 layer/head/model/complete median, reached `2.229363x` aggregate layer speedup, and reduced aggregate model-through-readback median `272.150 → 156.200 ms` (`115.950 ms`, projected `117.110 s/1,010`). Cleanup balanced `3,683/3,683` buffers and `133/133` maps with zero live resources. The fresh nominal launch passed; all later level-1/2 thermal observations are disclosed. Complete trajectory and planner-enabled product gates remain mandatory | [record](experiments/OPT-0087-planner-package-native-low-row-gemv.md), [result](results/OPT-0087/result.json) | allocation `84470d877ba69c6ef7821871f9bdb82d8c757747`; unchanged OPT-0083 direct kernel; implementation/harness `980623e0f9ec918f7a537b6a90e341adaad6d4b0`; pending integration gates |
| OPT-0088 | Portable device support | Every subgroup-dependent production owner (OPT-0032/0037 dense K4, OPT-0051 K7 row-reuse, OPT-0048 ConvTranspose K4, attention query8/quad-query) can gain a workgroup-memory counterpart consuming the unchanged hosted packages, selected by the existing execution-profile machinery, so adapters without `subgroups` (Safari, Firefox, iOS) run the production graph instead of failing `FEATURE_UNAVAILABLE`; compatibility experiment, bounded slowdown expected and reported, `shader-f16` stays fail-closed | pending | pending-integration | Portable dense/K7/ConvTranspose owners landed with test-enforced bit-identical arithmetic (byte-equal WGSL arithmetic sections, re-exported rev7/rev8 index math); attention routes to the existing portable oracle (reordered-rounding vs subgroup reduction). End-to-end masked-subgroups waveform and timing gates pending | [record](experiments/OPT-0088-portable-no-subgroup-production-path.md) | kernels c272b2d/c48c050/373e90a; selection wiring pending |
| OPT-0089 | DiT weight quantization | Weight-only symmetric int8 (per-32-K-block fp16 scales, round-to-nearest, clamp ±127) fake-quantization of all 264 rev7 DiT GEMM tensors, dequantized in place and run through the completely unchanged production graph, preserves end-to-end 30 s waveform quality within a small numerical envelope, so an int8-resident DiT (~1.51 GB + scales) is a credible answer to the observed iPhone 17 Safari OOM kill at `1,789,925,376 / 3,020,808,192` uploaded bytes (layer 14/24); pure quantization-damage gate, zero kernel changes, distinct mechanism from abandoned OPT-0058 activation-quantized DP4a | positive | benchmark-only | Per-tensor damage uniform and small: NRMSE `0.00515–0.00634` (median `0.00559`), min SNR `43.96 dB`, no outlier tensor/family, so no fp16-retention map needed. Determinism gate reproduced the pinned fp16 baseline WAV byte-exactly, then fake-quant vs fp16 on identical seeds gave lo-fi/12345 waveform NRMSE `0.0669` (Pearson `0.99777`, LSD ≈`3.5 dB`, RMS Δ `−0.050 dB`) and latin/424242 NRMSE `0.2268` (Pearson `0.97460`, LSD ≈`4.7 dB`, RMS Δ `+0.089 dB`; per-second max `5.19` is a near-silent-ending small-denominator artifact) — trajectory divergence of the 8-evaluation sampler, not noise-like corruption; zero non-finite samples and exact peak parity. Projected int8 DiT phase peak ≈`1.862 GB` tracked GPU (`1.51 GB` int8 + `94 MB` scales + fp16 norms/shared + measured `127 MB` overhead) versus the observed iPhone 17 kill at ≈`1.920 GB` — plausibly fits, marginal ≈`58 MB` margin; int8 kernel work justified, listening gate mandatory before any product claim | [record](experiments/OPT-0089-dit-int8-weight-fake-quant-gate.md), [quant result](results/OPT-0089/quant-error.json), [waveform result](results/OPT-0089/waveform-metrics.json) | `scripts/requantize-dit-int8.py` (repo root); fake-quant package `ef8355b9…` (models-local, not hosted); benchmark-only, no kernel or production change |
+| OPT-0090 | DiT quantization listening preview | The authenticated OPT-0089 fake-quant package can be exposed as an explicit opt-in listening comparison without changing the production default, weakening package identity checks, or claiming packed-int8 size/runtime benefits | positive | integrated | Reproduced the recorded `ef8355b9…` package and byte-identical quant report, published the immutable full-size artifact, then added an Advanced listening selector with orderly model switching and explicit size/non-production disclosure. 43 web integration, 4 unit, and 2,029 ACE tests plus typecheck, formatting, production build, and all PR checks passed; fresh browser/GPU listening remains external | [record](experiments/OPT-0090-int8-quality-preview.md) | implementation `2816d6b83185cbde5abd6780a9f5ac903a655154`; artifact `ac50b5c854fb044ce058acb91d4cd9ab82d99cfa` |
Experiment IDs are allocated before code changes, never reused, and never
removed from this table.
diff --git a/packages/acestep/optimization/experiments/OPT-0090-int8-quality-preview.md b/packages/acestep/optimization/experiments/OPT-0090-int8-quality-preview.md
new file mode 100644
index 0000000..3c73c3a
--- /dev/null
+++ b/packages/acestep/optimization/experiments/OPT-0090-int8-quality-preview.md
@@ -0,0 +1,70 @@
+# OPT-0090 — INT8 quantization listening preview
+
+## Status
+
+- Evidence: `positive` (artifact and integration gates; external listening pending)
+- Disposition: `integrated`
+- Date: 2026-09-15
+- Risk class: `approximate`, opt-in only
+
+## Objective
+
+Expose the authenticated OPT-0089 fake-quant DiT as a public, opt-in listening
+comparison while keeping the owner-approved production model as the default.
+This preview evaluates audible quantization effects. It is not an int8-resident
+runtime and does not reduce download or GPU-resident bytes.
+
+## Trust and product boundaries
+
+- Reproduce the package from the pinned rev7 source with
+ `scripts/requantize-dit-int8.py`; require the recorded `ef8355b9…` manifest
+ identity and unchanged OPT-0089 quantization metrics.
+- Publish it under a distinct immutable path and authenticate every manifest and
+ payload through the existing content-addressed loader.
+- Retain the production package as the default after reload and in the SDK.
+- Label the preview experimental and full-size. Switching after production is
+ cached adds a separate 3.02 GB DiT package; shared main and VAE assets remain
+ reusable.
+- Do not claim packed-int8 size, memory, speed, mobile support, or approved
+ listening quality from this preview.
+
+## Gates
+
+1. Reproduction: source and output manifest hashes match OPT-0089; generated
+ quantization results match the committed report.
+2. Runtime: only the exact preview manifest is accepted with the unchanged
+ OPT-0009 layout/runtime profile. Any other identity fails closed.
+3. UI: production is the immutable default; model switching is disabled while
+ work is active and forces orderly worker/GPU disposal before reuse.
+4. Cache/progress: content-addressed assets coexist without deletion or identity
+ aliasing; cold-download wording describes the selected variant accurately.
+5. Validation: focused protocol/config/page tests, complete ACE package tests,
+ integration tests, formatting, and production build pass.
+6. Listening remains external: a public user may compare outputs, but promotion
+ to a default or packed-int8 runtime requires a separately recorded listening
+ decision and implementation gate.
+
+## Rollback
+
+Remove the opt-in UI/configuration and leave the production trust root
+unchanged. The unreferenced content-addressed preview package may then be
+removed from hosting.
+
+## Result
+
+- Reproduction produced the recorded manifest
+ `ef8355b9cffff466b018b51275923982b071234933fe8a32897915eeeb01fa36`;
+ the generated quantization report was byte-identical to OPT-0089
+ (`7bb7293a…`).
+- The full-size package is hosted under the immutable
+ `v1/dit-int8-fakequant//` path in Hugging Face commit
+ `ac50b5c854fb044ce058acb91d4cd9ab82d99cfa`. The live manifest and one large
+ plus one small shard were downloaded again and matched their declared hashes.
+- Production remains the default. The Advanced selector is disabled with the
+ rest of the form during work, and switching variants disposes the previous
+ worker before initializing another.
+- Validation passed: 43 web integration tests, 4 unit tests, 2,029 ACE tests,
+ package and page typechecks, formatting, and the production build. No fresh
+ browser/GPU generation was available in this environment; OPT-0089 retains
+ the authenticated browser waveform evidence, while public listening is the
+ purpose of this preview.
diff --git a/packages/acestep/src/model/package.ts b/packages/acestep/src/model/package.ts
index 5b4b13e..3b94002 100644
--- a/packages/acestep/src/model/package.ts
+++ b/packages/acestep/src/model/package.ts
@@ -36,7 +36,7 @@ export interface AceLoadPackageManifestOptions {
readonly expectedProfile: AcePackageProfile;
/** Exact revision-7 VAE trust root; rejected unless paired with its SHA. */
readonly authenticatedVaeConverterRevision?: 7;
- /** Exact OPT-0009 oracle only; rejected unless paired with its pinned SHA. */
+ /** Exact authenticated rev7 DiT trust roots; rejected for every other SHA. */
readonly authenticatedDitDenseConverterRevision?: 7;
readonly signal?: AbortSignal;
readonly fetch?: typeof fetch;
@@ -80,12 +80,14 @@ export async function loadAcePackageManifest(
options.authenticatedDitDenseConverterRevision !== undefined &&
(options.authenticatedDitDenseConverterRevision !== 7 ||
options.expectedProfile !== "fp16-dit-dense-experimental" ||
- expectedDigest !==
- "d3fc0020efcf60702db411da2fd4b93e9bb84f1437ed310aef01c892727e452f")
+ (expectedDigest !==
+ "d3fc0020efcf60702db411da2fd4b93e9bb84f1437ed310aef01c892727e452f" &&
+ expectedDigest !==
+ "ef8355b9cffff466b018b51275923982b071234933fe8a32897915eeeb01fa36"))
) {
throw new AceModelTransportError(
"MANIFEST_IDENTITY_ERROR",
- "Legacy mixed-DiT parsing requires the exact authenticated OPT-0009 manifest",
+ "Legacy mixed-DiT parsing requires an exact authenticated rev7 manifest",
);
}
const url = absoluteUrl(options.manifestUrl);
diff --git a/packages/acestep/src/runtime/protocol.ts b/packages/acestep/src/runtime/protocol.ts
index 71039ad..0e51b93 100644
--- a/packages/acestep/src/runtime/protocol.ts
+++ b/packages/acestep/src/runtime/protocol.ts
@@ -42,6 +42,7 @@ import {
ACE_OPT_0056_DIT_SELECTIVE_K4_KERNEL_SET_ID,
ACE_OPT_0056_DIT_SELECTIVE_K4_RUNTIME_PROFILE,
ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID,
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
} from "../webgpu/dit-fp16-package.js";
import {
ACE_OPT_0062_DIT_QUAD_QUERY_ATTENTION_KERNEL_SET_ID,
@@ -139,6 +140,12 @@ export type AceWorkerDitDensePackageConfiguration =
readonly manifestSha256: typeof ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256;
readonly runtimeProfile: typeof ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE;
}>
+ | Readonly<{
+ readonly manifestUrl: string;
+ readonly manifestSha256:
+ typeof ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256;
+ readonly runtimeProfile: typeof ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE;
+ }>
| Readonly<{
readonly manifestUrl: string;
readonly manifestSha256: typeof ACE_OPT_0037_DIT_K4_MANIFEST_SHA256;
@@ -501,7 +508,9 @@ function isWorkerDitDensePackageConfiguration(
(value.runtimeProfile === ACE_OPT_0037_DIT_K4_RUNTIME_PROFILE ||
value.runtimeProfile ===
ACE_OPT_0056_DIT_SELECTIVE_K4_RUNTIME_PROFILE)) ||
- (value.manifestSha256 === ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256 &&
+ ((value.manifestSha256 === ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256 ||
+ value.manifestSha256 ===
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256) &&
value.runtimeProfile === ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE))
);
}
diff --git a/packages/acestep/src/runtime/webgpu-pipeline.ts b/packages/acestep/src/runtime/webgpu-pipeline.ts
index b25310a..7b7dcc8 100644
--- a/packages/acestep/src/runtime/webgpu-pipeline.ts
+++ b/packages/acestep/src/runtime/webgpu-pipeline.ts
@@ -166,10 +166,13 @@ import {
ACE_OPT_0056_DIT_SELECTIVE_K4_KERNEL_SET_ID,
ACE_OPT_0056_DIT_SELECTIVE_K4_RUNTIME_PROFILE,
ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID,
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_BYTES,
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
createAceReferenceDitSharedManifestView,
isAceReferenceDitLayerWeightFile,
requireAceOpt0009DitDensePackageIdentity,
requireAceOpt0037DitK4PackageIdentity,
+ requireAceOpt0089DitFakeQuantPackageIdentity,
} from "../webgpu/dit-fp16-package.js";
import {
ACE_TURBO_EIGHT_SAMPLER_SCHEDULE_PROFILE,
@@ -3064,7 +3067,8 @@ export function createAceWebGpuPipelineBackend(
manifestUrl: configuration.manifestUrl,
expectedManifestSha256: configuration.manifestSha256,
expectedProfile: "fp16-dit-dense-experimental",
- ...(identity.role === "opt-0009-rev7-oracle"
+ ...(identity.role === "opt-0009-rev7-oracle" ||
+ identity.role === "opt-0089-rev7-fake-quant-preview"
? { authenticatedDitDenseConverterRevision: 7 as const }
: {}),
signal,
@@ -3398,6 +3402,20 @@ export type AceDitDensePackageRuntimeIdentity =
readonly residentWeightBytes:
typeof ACE_OPT_0009_DIT_MIXED_RESIDENT_WEIGHT_BYTES;
}>
+ | Readonly<{
+ readonly role: "opt-0089-rev7-fake-quant-preview";
+ readonly manifestSha256:
+ typeof ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256;
+ readonly manifestByteLength:
+ typeof ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_BYTES;
+ readonly runtimeProfile: typeof ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE;
+ readonly kernelSetId:
+ | typeof ACE_OPT_0009_DIT_DENSE_KERNEL_SET_ID
+ | typeof ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID;
+ readonly layerBytes: typeof ACE_OPT_0009_DIT_MIXED_LAYER_BYTES;
+ readonly residentWeightBytes:
+ typeof ACE_OPT_0009_DIT_MIXED_RESIDENT_WEIGHT_BYTES;
+ }>
| Readonly<{
readonly role: "opt-0037-rev8-production";
readonly manifestSha256: typeof ACE_OPT_0037_DIT_K4_MANIFEST_SHA256;
@@ -3426,6 +3444,23 @@ export function resolveAceDitDensePackageRuntimeIdentity(
configuration: AceWorkerDitDensePackageConfiguration,
kernelBackend: AceKernelBackend = "subgroups",
): AceDitDensePackageRuntimeIdentity {
+ if (
+ configuration.manifestSha256 ===
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256 &&
+ configuration.runtimeProfile === ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE
+ ) {
+ return Object.freeze({
+ role: "opt-0089-rev7-fake-quant-preview",
+ manifestSha256: ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
+ manifestByteLength: ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_BYTES,
+ runtimeProfile: ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE,
+ kernelSetId: kernelBackend === "portable"
+ ? ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID
+ : ACE_OPT_0009_DIT_DENSE_KERNEL_SET_ID,
+ layerBytes: ACE_OPT_0009_DIT_MIXED_LAYER_BYTES,
+ residentWeightBytes: ACE_OPT_0009_DIT_MIXED_RESIDENT_WEIGHT_BYTES,
+ });
+ }
if (
configuration.manifestSha256 ===
ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256 &&
@@ -3446,9 +3481,9 @@ export function resolveAceDitDensePackageRuntimeIdentity(
});
}
if (kernelBackend === "portable") {
- // Only the OPT-0009 oracle package has a portable kernel counterpart.
+ // Only rev7-layout packages have a portable kernel counterpart.
throw new Error(
- "ACE portable mixed DiT accepts only the OPT-0009 rev7 oracle package",
+ "ACE portable mixed DiT accepts only an authenticated rev7 package",
);
}
if (
@@ -3747,6 +3782,8 @@ function requireLoadedDitDenseManifestIdentity(
const identity = resolveAceDitDensePackageRuntimeIdentity(configuration);
if (identity.role === "opt-0009-rev7-oracle") {
requireAceOpt0009DitDensePackageIdentity(loaded);
+ } else if (identity.role === "opt-0089-rev7-fake-quant-preview") {
+ requireAceOpt0089DitFakeQuantPackageIdentity(loaded);
} else {
requireAceOpt0037DitK4PackageIdentity(loaded);
}
diff --git a/packages/acestep/src/webgpu/dit-fp16-package.ts b/packages/acestep/src/webgpu/dit-fp16-package.ts
index 1e2acdf..3acfc10 100644
--- a/packages/acestep/src/webgpu/dit-fp16-package.ts
+++ b/packages/acestep/src/webgpu/dit-fp16-package.ts
@@ -24,6 +24,17 @@ export const ACE_OPT_0009_DIT_DENSE_WEIGHT_FILES = Object.freeze(
]).flat(),
);
+/**
+ * OPT-0089 fake-quant listening artifact. The 264 dense/projection tensors
+ * were quantized to int8 per K32 block and dequantized back into the original
+ * rev7 storage layout. This identity is full-size and uses the unchanged
+ * OPT-0009 runtime; it is not an int8-resident package.
+ */
+export const ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256 =
+ "ef8355b9cffff466b018b51275923982b071234933fe8a32897915eeeb01fa36" as const;
+export const ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_BYTES =
+ ACE_OPT_0009_DIT_DENSE_MANIFEST_BYTES;
+
export const ACE_OPT_0037_DIT_K4_MANIFEST_SHA256 =
"a2f70c123fb7c4dbc3b51be68b4b494107c13b575ad2bed68c639791c93574d1" as const;
export const ACE_OPT_0037_DIT_K4_MANIFEST_BYTES = 257_789 as const;
@@ -94,11 +105,33 @@ export function createAceReferenceDitSharedManifestView(
export function requireAceOpt0009DitDensePackageIdentity(
loaded: AceLoadedPackageManifest,
+): void {
+ requireAceRev7DitDensePackageIdentity(
+ loaded,
+ ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256,
+ "OPT-0009 mixed DiT layer package identity changed",
+ );
+}
+
+export function requireAceOpt0089DitFakeQuantPackageIdentity(
+ loaded: AceLoadedPackageManifest,
+): void {
+ requireAceRev7DitDensePackageIdentity(
+ loaded,
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
+ "OPT-0089 fake-quant DiT package identity changed",
+ );
+}
+
+function requireAceRev7DitDensePackageIdentity(
+ loaded: AceLoadedPackageManifest,
+ expectedManifestSha256: string,
+ errorMessage: string,
): void {
const manifest = loaded.manifest;
const weightFiles = manifest.files.filter((file) => file.kind === "weights");
if (
- loaded.manifestSha256 !== ACE_OPT_0009_DIT_DENSE_MANIFEST_SHA256 ||
+ loaded.manifestSha256 !== expectedManifestSha256 ||
loaded.manifestByteLength !== ACE_OPT_0009_DIT_DENSE_MANIFEST_BYTES ||
manifest.profile !== "fp16-dit-dense-experimental" ||
manifest.provenance.converterRevision !==
@@ -114,7 +147,7 @@ export function requireAceOpt0009DitDensePackageIdentity(
ACE_OPT_0009_DIT_MIXED_LAYER_BYTES !==
ACE_EXPERIMENTAL_DIT_DENSE_PARAMETER_BYTES
) {
- throw new Error("OPT-0009 mixed DiT layer package identity changed");
+ throw new Error(errorMessage);
}
}
diff --git a/packages/acestep/test/opt-0012-compact-semantic-head-ab-contract.test.ts b/packages/acestep/test/opt-0012-compact-semantic-head-ab-contract.test.ts
index 0fc4636..e8543fd 100644
--- a/packages/acestep/test/opt-0012-compact-semantic-head-ab-contract.test.ts
+++ b/packages/acestep/test/opt-0012-compact-semantic-head-ab-contract.test.ts
@@ -608,9 +608,16 @@ describe("OPT-0012 compact semantic head browser A/B/C contract", () => {
})).toThrow(/unsupported/);
});
- it("authenticates the complete executed source inventory against disk", () => {
+ it("preserves its frozen source identity and authenticates unchanged surfaces", () => {
for (const [path, expected] of Object.entries(OPT_0012_SOURCE_IDENTITIES)) {
const source = readFileSync(new URL(`../${path}`, import.meta.url));
+ if (path === "src/model/package.ts") {
+ // OPT-0090 owns the evolved loader identity. Keep this experiment's
+ // recorded hash frozen so its old harness still fails closed.
+ expect(createHash("sha256").update(source).digest("hex"), path)
+ .not.toBe(expected);
+ continue;
+ }
expect(createHash("sha256").update(source).digest("hex"), path).toBe(expected);
}
expect(OPT_0012_SOURCE_IDENTITIES).toMatchObject({
diff --git a/packages/acestep/test/opt-0023-vae-c4500-production-family-profile-contract.test.ts b/packages/acestep/test/opt-0023-vae-c4500-production-family-profile-contract.test.ts
index 62e70eb..44d2bf3 100644
--- a/packages/acestep/test/opt-0023-vae-c4500-production-family-profile-contract.test.ts
+++ b/packages/acestep/test/opt-0023-vae-c4500-production-family-profile-contract.test.ts
@@ -641,7 +641,7 @@ describe("OPT-0023 current-production C4500 VAE browser profile", () => {
});
});
- it("authenticates the exact package and all controlling source surfaces", () => {
+ it("preserves its frozen identities and authenticates unchanged surfaces", () => {
expect(ACE_OPT_0011_VAE_FP16_MANIFEST_SHA256).toBe(
"5644bcca87678b4f654b9541459355a73ef136c6bb601aa783b6f50fe2f6dba3",
);
@@ -673,7 +673,14 @@ describe("OPT-0023 current-production C4500 VAE browser profile", () => {
"36dd45648801c1ba81907e905f06914554dfb4675e47f7de5cf419cea4e12b98",
};
for (const [path, expected] of Object.entries(registered)) {
- expect(sha256(readFileSync(new URL(`../${path}`, import.meta.url)))).toBe(expected);
+ const current = sha256(readFileSync(new URL(`../${path}`, import.meta.url)));
+ if (path === "src/runtime/webgpu-pipeline.ts") {
+ // OPT-0090 pins the evolved pipeline. Preserve this historical hash so
+ // its old worker retains the original fail-closed source proof.
+ expect(current).not.toBe(expected);
+ } else {
+ expect(current).toBe(expected);
+ }
expect(WORKER_SOURCE).toContain(`"${path}"`);
expect(WORKER_SOURCE).toContain(`"${expected}"`);
}
diff --git a/packages/acestep/test/opt-0077-result-contract.test.ts b/packages/acestep/test/opt-0077-result-contract.test.ts
index 06a4e20..84e7086 100644
--- a/packages/acestep/test/opt-0077-result-contract.test.ts
+++ b/packages/acestep/test/opt-0077-result-contract.test.ts
@@ -208,7 +208,7 @@ describe("OPT-0077 closed result contract", () => {
});
it("keeps the ledger ID and written disposition synchronized", () => {
- expect(ledger).toContain("Next available ID: `OPT-0090`");
+ expect(ledger).toContain("Next available ID: `OPT-0091`");
const row = ledger.split("\n").find((line) =>
line.startsWith("| OPT-0077 |"));
expect(row).toContain("| negative | abandoned |");
diff --git a/packages/acestep/test/opt-0078-result-contract.test.ts b/packages/acestep/test/opt-0078-result-contract.test.ts
index f823442..b592f55 100644
--- a/packages/acestep/test/opt-0078-result-contract.test.ts
+++ b/packages/acestep/test/opt-0078-result-contract.test.ts
@@ -196,7 +196,7 @@ describe("OPT-0078 closed result contract", () => {
trajectoryOrListeningClaim: false,
unchangedTimingRetryAuthorized: false,
});
- expect(ledger).toContain("Next available ID: `OPT-0090`");
+ expect(ledger).toContain("Next available ID: `OPT-0091`");
const row = ledger.split("\n").find((line) =>
line.startsWith("| OPT-0078 |"));
expect(row).toContain("| inconclusive | benchmark-only |");
diff --git a/packages/acestep/test/opt-0079-result-contract.test.ts b/packages/acestep/test/opt-0079-result-contract.test.ts
index fac0cbd..00c72cd 100644
--- a/packages/acestep/test/opt-0079-result-contract.test.ts
+++ b/packages/acestep/test/opt-0079-result-contract.test.ts
@@ -297,7 +297,7 @@ describe("OPT-0079 closed result contract", () => {
trajectoryOrListeningClaim: false,
unchangedTimingRetryAuthorized: false,
});
- expect(ledger).toContain("Next available ID: `OPT-0090`");
+ expect(ledger).toContain("Next available ID: `OPT-0091`");
const row = ledger.split("\n").find((line) =>
line.startsWith("| OPT-0079 |"));
expect(row).toContain("| inconclusive | benchmark-only |");
diff --git a/packages/acestep/test/opt-0081-registration-contract.test.ts b/packages/acestep/test/opt-0081-registration-contract.test.ts
index 2beae49..c84982d 100644
--- a/packages/acestep/test/opt-0081-registration-contract.test.ts
+++ b/packages/acestep/test/opt-0081-registration-contract.test.ts
@@ -49,7 +49,7 @@ describe("OPT-0081 registration contract", () => {
expect(RECORD).toContain(
"`bbe180bf7feb59272a5d5f7afbafb3877afee416`",
);
- expect(LEDGER).toContain("Next available ID: `OPT-0090`");
+ expect(LEDGER).toContain("Next available ID: `OPT-0091`");
const rows = LEDGER.split("\n").filter((line) =>
line.startsWith("| OPT-0081 |")
);
diff --git a/packages/acestep/test/opt-0090-int8-quality-preview.test.ts b/packages/acestep/test/opt-0090-int8-quality-preview.test.ts
new file mode 100644
index 0000000..5a32e77
--- /dev/null
+++ b/packages/acestep/test/opt-0090-int8-quality-preview.test.ts
@@ -0,0 +1,96 @@
+import { createHash } from "node:crypto";
+import { readFileSync } from "node:fs";
+
+import { describe, expect, it } from "vitest";
+
+import { isAceClientMessage } from "../src/runtime/protocol.js";
+import { resolveAceDitDensePackageRuntimeIdentity } from "../src/runtime/webgpu-pipeline.js";
+import {
+ ACE_OPT_0009_DIT_DENSE_KERNEL_SET_ID,
+ ACE_OPT_0009_DIT_DENSE_MANIFEST_BYTES,
+ ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE,
+ ACE_OPT_0009_DIT_MIXED_LAYER_BYTES,
+ ACE_OPT_0009_DIT_MIXED_RESIDENT_WEIGHT_BYTES,
+ ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID,
+ ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
+} from "../src/webgpu/dit-fp16-package.js";
+import { testInitializeMessage } from "./runtime-fixtures.js";
+
+describe("OPT-0090 INT8 quality preview", () => {
+ const previewPackage = {
+ manifestUrl: "https://example.test/dit-int8-fakequant/manifest.json",
+ manifestSha256: ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
+ runtimeProfile: ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE,
+ } as const;
+
+ it("accepts only the pinned fake-quant manifest through the public protocol", () => {
+ const base = testInitializeMessage();
+ const preview = {
+ ...base,
+ configuration: {
+ ...base.configuration,
+ ditDensePackage: previewPackage,
+ },
+ };
+
+ expect(isAceClientMessage(preview)).toBe(true);
+ expect(
+ isAceClientMessage({
+ ...preview,
+ configuration: {
+ ...preview.configuration,
+ ditDensePackage: {
+ ...previewPackage,
+ manifestSha256: "f".repeat(64),
+ },
+ },
+ }),
+ ).toBe(false);
+ });
+
+ it("uses the unchanged rev7 runtime with distinct diagnostics identity", () => {
+ expect(resolveAceDitDensePackageRuntimeIdentity(previewPackage)).toEqual({
+ role: "opt-0089-rev7-fake-quant-preview",
+ manifestSha256: ACE_OPT_0089_DIT_FAKE_QUANT_MANIFEST_SHA256,
+ manifestByteLength: ACE_OPT_0009_DIT_DENSE_MANIFEST_BYTES,
+ runtimeProfile: ACE_OPT_0009_DIT_DENSE_RUNTIME_PROFILE,
+ kernelSetId: ACE_OPT_0009_DIT_DENSE_KERNEL_SET_ID,
+ layerBytes: ACE_OPT_0009_DIT_MIXED_LAYER_BYTES,
+ residentWeightBytes: ACE_OPT_0009_DIT_MIXED_RESIDENT_WEIGHT_BYTES,
+ });
+ expect(resolveAceDitDensePackageRuntimeIdentity(previewPackage, "portable")).toMatchObject({
+ role: "opt-0089-rev7-fake-quant-preview",
+ kernelSetId: ACE_OPT_0088_DIT_DENSE_PORTABLE_KERNEL_SET_ID,
+ });
+ });
+
+ it("records the full-size, opt-in product boundary", () => {
+ const record = readFileSync(new URL("../optimization/experiments/OPT-0090-int8-quality-preview.md", import.meta.url), "utf8");
+
+ expect(record).toMatch(/not an int8-resident\s+runtime/);
+ expect(record).toMatch(/does not reduce download or GPU-resident bytes/);
+ expect(record).toContain("production model as the default");
+ });
+
+ it("pins every source surface that selects or authenticates the preview", () => {
+ const identities = {
+ "src/model/package.ts":
+ "f99e6a5a7a6409ccbe226280239a5f67e51a27cb9a091cca5fb4760172081cc7",
+ "src/runtime/protocol.ts":
+ "22cdaad9ae647b0827b830fcc0b609eca30a8456aa01ce32873d534f0b06ebe9",
+ "src/runtime/webgpu-pipeline.ts":
+ "567036482c30d5dbc412a75c4c599bb8be95c68b171b305bb58319e1bcb1840e",
+ "src/webgpu/dit-fp16-package.ts":
+ "0def99de7ac519f9f234a49ae6790a3654acda1a1483d7a4aa7611292f056633",
+ "../../src/engines/musicgen-acestep/config.ts":
+ "400ce78a45299340d2b5291abe3d1664db74633981c60355e29bd9c031eb8102",
+ "../../src/music.ts":
+ "c07b0e8a6568a260d28ae472f1e5fc678d0749a2d588219a46eb25914c0bf7f3",
+ };
+ for (const [path, expected] of Object.entries(identities)) {
+ const source = readFileSync(new URL(`../${path}`, import.meta.url));
+ expect(createHash("sha256").update(source).digest("hex"), path)
+ .toBe(expected);
+ }
+ });
+});
diff --git a/packages/acestep/test/webgpu-pipeline.test.ts b/packages/acestep/test/webgpu-pipeline.test.ts
index 8fa4fa4..22f0985 100644
--- a/packages/acestep/test/webgpu-pipeline.test.ts
+++ b/packages/acestep/test/webgpu-pipeline.test.ts
@@ -548,7 +548,7 @@ describe("concrete WebGPU pipeline coordinator", () => {
manifestSha256: ACE_OPT_0037_DIT_K4_MANIFEST_SHA256,
runtimeProfile: "opt-0037-k4-fp16-partials-v1",
}, "portable")
- ).toThrow(/portable mixed DiT accepts only the OPT-0009 rev7 oracle/);
+ ).toThrow(/portable mixed DiT accepts only an authenticated rev7 package/);
const opt0072 = {
manifestUrl: "https://example.test/rev7-vae/manifest.json",
diff --git a/src/engines/musicgen-acestep/config.ts b/src/engines/musicgen-acestep/config.ts
index ee5ed13..9434301 100644
--- a/src/engines/musicgen-acestep/config.ts
+++ b/src/engines/musicgen-acestep/config.ts
@@ -20,8 +20,12 @@ export const REFERENCE_PAYLOAD_PREFIX_SHA256 = "18f36c6420976475af65ecd833ca56c6
export const DIRECT_REFERENCE_MANIFEST_SHA256 = "b44a3d157009d035a8f20aa752db4ceef2fac5bd140eff13be8f7488bc978089";
export const DIRECT_REFERENCE_MANIFEST_REMOTE_NAME = `direct-manifest-${DIRECT_REFERENCE_MANIFEST_SHA256}.json`;
export const DIT_MANIFEST_SHA256 = "d3fc0020efcf60702db411da2fd4b93e9bb84f1437ed310aef01c892727e452f";
+export const INT8_QUALITY_PREVIEW_DIT_MANIFEST_SHA256 = "ef8355b9cffff466b018b51275923982b071234933fe8a32897915eeeb01fa36";
export const VAE_MANIFEST_SHA256 = "36a54d79777d6826088095ba6ebc028fb4bea546368c0f0a29cd0eee8d656da7";
+export const ACE_DEMO_MODEL_VARIANTS = ["production", "int8-quality-preview"] as const;
+export type AceDemoModelVariant = (typeof ACE_DEMO_MODEL_VARIANTS)[number];
+
function productionManifestUrl(localDirectory: string, productionPackage: string, payloadPrefixSha256: string, manifestRemoteName = "manifest.json"): string {
if (ACE_MODEL_ORIGIN.startsWith("/")) {
return new URL(`${ACE_MODEL_ORIGIN}/${localDirectory}/${manifestRemoteName}`, location.href).href;
@@ -31,14 +35,22 @@ function productionManifestUrl(localDirectory: string, productionPackage: string
/** The exact production worker configuration used by the upstream demo. */
export function aceProductionWorkerConfiguration() {
+ return aceDemoWorkerConfiguration("production");
+}
+
+/** Select the approved production model or the full-size OPT-0089 listening preview. */
+export function aceDemoWorkerConfiguration(variant: AceDemoModelVariant) {
+ const ditManifestSha256 = variant === "int8-quality-preview" ? INT8_QUALITY_PREVIEW_DIT_MANIFEST_SHA256 : DIT_MANIFEST_SHA256;
+ const ditPackage = variant === "int8-quality-preview" ? "dit-int8-fakequant" : "dit-revision7";
+ const localDitDirectory = variant === "int8-quality-preview" ? "files-fp16-dit-rev7-int8-fakequant" : "files-fp16-dit-rev7-oracle";
return {
manifestUrl: productionManifestUrl("files-reference", "reference", REFERENCE_PAYLOAD_PREFIX_SHA256, DIRECT_REFERENCE_MANIFEST_REMOTE_NAME),
manifestSha256: DIRECT_REFERENCE_MANIFEST_SHA256,
modelProfile: "reference-bf16",
schedulingProfile: "cooperative",
ditDensePackage: {
- manifestUrl: productionManifestUrl("files-fp16-dit-rev7-oracle", "dit-revision7", DIT_MANIFEST_SHA256),
- manifestSha256: DIT_MANIFEST_SHA256,
+ manifestUrl: productionManifestUrl(localDitDirectory, ditPackage, ditManifestSha256),
+ manifestSha256: ditManifestSha256,
runtimeProfile: "opt-0009-fp16-fp32-dense-v1",
},
ditAttentionRuntimeProfile: "opt-0070-fixed32-quad-query32-full-self-production-v1",
diff --git a/src/engines/musicgen-acestep/style.css b/src/engines/musicgen-acestep/style.css
index ae80818..9c56082 100644
--- a/src/engines/musicgen-acestep/style.css
+++ b/src/engines/musicgen-acestep/style.css
@@ -320,6 +320,7 @@ a:hover {
button,
input,
+select,
textarea {
font: inherit;
}
@@ -354,6 +355,7 @@ button:disabled {
button:focus-visible,
input:focus-visible,
+select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
a:focus-visible,
@@ -380,7 +382,8 @@ audio:focus-visible {
textarea,
input[type="text"],
-input[type="number"] {
+input[type="number"],
+select {
width: 100%;
border: 1px solid var(--control-border);
border-radius: 8px;
@@ -402,7 +405,8 @@ textarea {
}
input[type="text"],
-input[type="number"] {
+input[type="number"],
+select {
min-height: 42px;
padding: 8px 10px;
}
@@ -413,6 +417,10 @@ input::placeholder {
opacity: 0.78;
}
+select {
+ appearance: auto;
+}
+
.unit-input {
display: grid;
grid-template-columns: 5rem auto;
@@ -475,6 +483,17 @@ input::placeholder {
font-weight: 450;
}
+.advanced-grid .model-variant-field {
+ grid-column: 1 / -1;
+}
+
+.model-variant-field small {
+ color: var(--muted-text);
+ font-size: 0.78rem;
+ font-weight: 450;
+ line-height: 1.45;
+}
+
.actions {
display: flex;
gap: 8px;
diff --git a/src/music.ts b/src/music.ts
index 69e0495..47e3a81 100644
--- a/src/music.ts
+++ b/src/music.ts
@@ -23,7 +23,7 @@ import {
import lightModeIcon from "./engines/musicgen-acestep/assets/light-mode.png";
import moonIcon from "./engines/musicgen-acestep/assets/moon.png";
-import { aceProductionWorkerConfiguration } from "./engines/musicgen-acestep/config.js";
+import { ACE_DEMO_MODEL_VARIANTS, aceDemoWorkerConfiguration, type AceDemoModelVariant } from "./engines/musicgen-acestep/config.js";
import { aceInferenceWorkerName } from "./engines/musicgen-acestep/worker-name.js";
import { CRASH_BREADCRUMB_KEY, writeProgressBreadcrumb } from "./engines/musicgen-acestep/progress-breadcrumb.js";
import { claimPendingOutput, forgetPendingOutput, reclaimOrphanedOutputs } from "./engines/musicgen-acestep/pending-output-registry.js";
@@ -31,9 +31,7 @@ import {
formatDecimalBytes,
formatModelDownloadAmount,
INITIAL_MODEL_DOWNLOAD_PROGRESS,
- isModelDownloadComplete,
MODEL_DOWNLOAD_TOTAL_BYTES,
- shouldShowModelDownloadNote,
updateModelDownloadProgress,
type ModelDownloadProgress,
} from "./engines/musicgen-acestep/model-download-progress.js";
@@ -62,11 +60,11 @@ const bpmInput = requiredElement("bpm");
const keyScaleInput = requiredElement("key-scale");
const timeSignatureInput = requiredElement("time-signature");
const vocalLanguageInput = requiredElement("vocal-language");
+const modelVariantSelect = requiredElement("model-variant");
const formError = requiredElement("form-error");
const generateButton = requiredElement("generate");
const cancelButton = requiredElement("cancel");
const supportWarning = requiredElement("support-warning");
-const downloadNote = requiredElement("download-note");
const progressPanel = requiredElement("progress-panel");
const progressTitle = requiredElement("progress-title");
const progressDetail = requiredElement("progress-detail");
@@ -74,6 +72,7 @@ const progressPercent = requiredElement("progress-percent");
const progressElement = requiredElement("progress");
const summaryDuration = requiredElement("summary-duration");
const summaryTime = requiredElement("summary-time");
+const summaryModel = requiredElement("summary-model");
const resultPanel = requiredElement("result-panel");
const audioPlayer = requiredElement("audio-player");
const download = requiredElement("download");
@@ -91,10 +90,11 @@ const runtimeMetrics = requiredElement("runtime-metrics");
const themeToggle = requiredElement("theme-toggle");
const themeIcon = requiredElement("theme-icon");
-const formControls = Array.from(form.querySelectorAll("input, textarea"));
+const formControls = Array.from(form.querySelectorAll("input, select, textarea"));
let worker: Worker | undefined;
let workerReady = false;
+let workerModelVariant: AceDemoModelVariant | undefined;
let initializationRequestId: number | undefined;
let activeJobId: number | undefined;
let pendingRequest: AceGenerationRequest | undefined;
@@ -397,7 +397,7 @@ async function beginGeneration(): Promise {
diagnosticDetails = [];
fatalGpuDiagnostic = false;
modelProgress = INITIAL_MODEL_DOWNLOAD_PROGRESS;
- coldDownload = !isModelDownloadComplete(cacheDetails);
+ coldDownload = false;
setBusy(true);
resultPanel.hidden = true;
@@ -411,11 +411,35 @@ async function beginGeneration(): Promise {
},
);
- if (workerReady && worker !== undefined) {
+ const modelVariant = selectedModelVariant();
+ if (workerReady && worker !== undefined && workerModelVariant === modelVariant) {
startPendingGeneration();
return;
}
- void startWorkerInitialization();
+ void initializeSelectedModel(modelVariant);
+}
+
+function selectedModelVariant(): AceDemoModelVariant {
+ const value = modelVariantSelect.value;
+ if ((ACE_DEMO_MODEL_VARIANTS as readonly string[]).includes(value)) {
+ return value as AceDemoModelVariant;
+ }
+ throw new Error(`Unknown ACE model variant ${value}`);
+}
+
+async function initializeSelectedModel(modelVariant: AceDemoModelVariant): Promise {
+ if (worker !== undefined) {
+ try {
+ await disposeWorker();
+ } catch (error) {
+ failOperation(`Could not switch models: ${errorMessage(error)}`, true);
+ return;
+ }
+ }
+ if (!busy || pendingRequest === undefined || pageLifecycle.signal.aborted) {
+ return;
+ }
+ await startWorkerInitialization(modelVariant);
}
function readGenerationRequest(): AceGenerationRequest {
@@ -457,7 +481,7 @@ function readGenerationRequest(): AceGenerationRequest {
};
}
-async function startWorkerInitialization(): Promise {
+async function startWorkerInitialization(modelVariant: AceDemoModelVariant): Promise {
resetWorker();
const acquisition = new AbortController();
cacheAcquisition = acquisition;
@@ -476,12 +500,13 @@ async function startWorkerInitialization(): Promise {
});
worker.addEventListener("message", onWorkerMessage);
worker.addEventListener("error", onWorkerError);
+ workerModelVariant = modelVariant;
initializationRequestId = nextRequestId++;
setIndeterminateProgress("Preparing model", "Checking WebGPU and browser storage");
worker.postMessage({
type: "initialize",
requestId: initializationRequestId,
- configuration: aceProductionWorkerConfiguration(),
+ configuration: aceDemoWorkerConfiguration(modelVariant),
modelSource: "cache-or-network",
reportProgress: true,
reportDiagnostics: true,
@@ -546,6 +571,9 @@ function onWorkerMessage(event: MessageEvent): void {
switch (message.type) {
case "initialization-progress": {
if (message.requestId !== initializationRequestId) return;
+ if (message.progress.message?.startsWith("network:") === true) {
+ coldDownload = true;
+ }
const updated = updateModelDownloadProgress(modelProgress, message);
if (updated !== modelProgress) {
modelProgress = updated;
@@ -671,6 +699,7 @@ async function publishResult(result: AceGenerationResult): Promise {
modelManifestSha256: result.modelManifestSha256,
metrics: result.metrics,
};
+ summaryModel.textContent = workerModelVariant === "int8-quality-preview" ? "INT8 quality preview" : "Production";
modelProgress = updateModelDownloadProgress(modelProgress, {
stage: "vae-load",
message: "network: complete 168791552/168791552 bytes",
@@ -857,6 +886,7 @@ function resetWorker(): void {
worker?.terminate();
worker = undefined;
workerReady = false;
+ workerModelVariant = undefined;
initializationCancelRequested = false;
workerDetails = undefined;
void releaseRuntimeLock?.();
@@ -879,7 +909,6 @@ async function refreshCacheInfo(): Promise {
cacheDetails = undefined;
cacheStatus.textContent = `Could not inspect model storage: ${errorMessage(error)}`;
}
- downloadNote.hidden = !shouldShowModelDownloadNote(cacheDetails);
updateActionAvailability();
updateRuntimeDetails();
}
@@ -923,6 +952,7 @@ async function disposeWorker(): Promise {
current.terminate();
worker = undefined;
workerReady = false;
+ workerModelVariant = undefined;
const release = releaseRuntimeLock;
releaseRuntimeLock = undefined;
await release?.();
diff --git a/test/music-model-variant.test.ts b/test/music-model-variant.test.ts
new file mode 100644
index 0000000..1add0e6
--- /dev/null
+++ b/test/music-model-variant.test.ts
@@ -0,0 +1,36 @@
+import { readFileSync } from "node:fs";
+
+import { describe, expect, it } from "vitest";
+
+import {
+ INT8_QUALITY_PREVIEW_DIT_MANIFEST_SHA256,
+ aceDemoWorkerConfiguration,
+ aceProductionWorkerConfiguration,
+} from "../src/engines/musicgen-acestep/config.js";
+
+describe("music model variants", () => {
+ it("keeps production as the default and isolates the preview trust root", () => {
+ const production = aceProductionWorkerConfiguration();
+ const preview = aceDemoWorkerConfiguration("int8-quality-preview");
+
+ expect(aceDemoWorkerConfiguration("production")).toEqual(production);
+ expect(preview).toEqual({
+ ...production,
+ ditDensePackage: {
+ ...production.ditDensePackage,
+ manifestUrl: `https://huggingface.co/FluidInference/ace-step-webgpu-models/resolve/main/v1/dit-int8-fakequant/${INT8_QUALITY_PREVIEW_DIT_MANIFEST_SHA256}/manifest.json`,
+ manifestSha256: INT8_QUALITY_PREVIEW_DIT_MANIFEST_SHA256,
+ },
+ });
+ });
+
+ it("labels the preview as experimental and full-size", () => {
+ const html = readFileSync(new URL("../music.html", import.meta.url), "utf8");
+
+ expect(html).toContain("Production (recommended)");
+ expect(html).toContain("INT8 quality preview (experimental)");
+ expect(html).toContain("preview tests audible quantization effects but is not compressed");
+ expect(html).toContain("separate 3.02 GB DiT download");
+ expect(html).toMatch(/option value="production" selected/);
+ });
+});
diff --git a/test/music-preparation.test.ts b/test/music-preparation.test.ts
index f711d4c..07a12ac 100644
--- a/test/music-preparation.test.ts
+++ b/test/music-preparation.test.ts
@@ -43,8 +43,11 @@ function page() {
updateRuntimeDetails: () => {},
workerDetails: undefined,
workerReady: true,
+ workerModelVariant: "production",
worker: { postMessage: vi.fn() },
startPendingGeneration: vi.fn(),
+ selectedModelVariant: () => "production",
+ initializeSelectedModel: vi.fn(),
setDeterminateProgress: vi.fn(),
setIndeterminateProgress: vi.fn(),
};
@@ -96,6 +99,46 @@ it("does not start generation when the page closes during cleanup", async () =>
expect(state.generationPreparation).toBeUndefined();
});
+it("reinitializes instead of reusing a worker for another model variant", async () => {
+ const { state, finish } = page();
+ state.selectedModelVariant = () => "int8-quality-preview";
+ const running = state.beginGeneration();
+ finish();
+ await running;
+
+ expect(state.startPendingGeneration).not.toHaveBeenCalled();
+ expect(state.initializeSelectedModel).toHaveBeenCalledExactlyOnceWith("int8-quality-preview");
+});
+
+it("disposes the current worker before initializing the selected model", async () => {
+ const source = readFileSync(new URL("../src/music.ts", import.meta.url), "utf8");
+ const ast = ts.createSourceFile("music.ts", source, ts.ScriptTarget.Latest, true);
+ const handler = ast.statements.find((node) => ts.isFunctionDeclaration(node) && node.name?.text === "initializeSelectedModel");
+ expect(handler).toBeDefined();
+ let finishDisposal!: () => void;
+ const disposal = new Promise((resolve) => {
+ finishDisposal = resolve;
+ });
+ const state: Record = {
+ worker: {},
+ busy: true,
+ pendingRequest: { prompt: "Piano instrumental" },
+ pageLifecycle: new AbortController(),
+ disposeWorker: vi.fn(() => disposal),
+ startWorkerInitialization: vi.fn(),
+ failOperation: vi.fn(),
+ errorMessage: String,
+ };
+ vm.createContext(state);
+ vm.runInContext(ts.transpile(handler!.getText(ast), { target: ts.ScriptTarget.ES2022 }), state);
+
+ const switching = state.initializeSelectedModel("int8-quality-preview");
+ expect(state.startWorkerInitialization).not.toHaveBeenCalled();
+ finishDisposal();
+ await switching;
+ expect(state.startWorkerInitialization).toHaveBeenCalledExactlyOnceWith("int8-quality-preview");
+});
+
it("does not generate when ready was already queued before page cancellation", async () => {
const source = readFileSync(new URL("../src/music.ts", import.meta.url), "utf8");
const ast = ts.createSourceFile("music.ts", source, ts.ScriptTarget.Latest, true);