Wire the runtime-shaped WASM MLP (one-core-engine P2) through the manifold: - WasmIML.reshape(dims, spread): calls nisps_ml_reshape, re-describes the instance, reallocates every dim-dependent heap buffer, refreshes weightCount, clears the TS Dataset mirror (C-side resets), drops the stale training worker, and pushes the new shape through the sink. - EngineApi.reshape exposes it and re-ticks the spine so outputs/audio reflect the new net. Spine already tolerates the arity change (buffers resize, version bumps); documented. - Training worker protocol carries the current hidden dims; the worker ensureNet()s its mirror net to match after a reshape. - ConsoleApp offers the reshape behind ReshapeModal on an active-layout CHANGE (never on load; default 32-input over-provisioned head + zero-padding preserved when declined). British copy, reset-on-reshape. - Drawers: delete the stale even/odd blending note; honest dedicated-dimensions line + net-arity chip. - Probe: __nisps.reshape(nIn) / .describe(); e2e reshape.spec (default 32/126, reshape to 4, describe reports 4, bounded outputs, weight count 3148→2868, spine still propagates). All 25 e2e pass (20 existing + 5 new). - ONBOARDING: refresh the reshape status + stale hardwired-arity gotcha.
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
/**
|
|
* Runtime-shaped net reshape (one-core-engine P2.3).
|
|
*
|
|
* The WASM MLP is runtime-shaped: it boots at the default over-provisioned
|
|
* 32→[10,14,18]→126 head, and `engine.reshape({ inputSize })` swaps in a new net
|
|
* at the requested arity, warm-started from the overlapping weights. This spec
|
|
* drives the reshape through the debug probe (`__nisps.reshape`) and asserts:
|
|
*
|
|
* 1. default dims are 32 inputs / 126 outputs (unchanged by P2.3);
|
|
* 2. reshape to 4 inputs succeeds and `describe()` reports 4/126;
|
|
* 3. post-ML outputs stay bounded in [0,1] after the reshape;
|
|
* 4. getWeights length shrinks by (32-4)*10 = 280 → 2868 (biases unchanged);
|
|
* 5. the spine still propagates — distinct inputs → distinct bounded outputs.
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
import { loadProbe, getOutputs, settleInputs, countChanged, allWithin } from './helpers';
|
|
|
|
// Default: 32*10 + 10*14 + 14*18 + 18*126 weights + (10+14+18+126) biases = 3148.
|
|
const DEFAULT_WEIGHT_COUNT = 3148;
|
|
// Reshaping to 4 inputs only shrinks the first layer: 3148 - (32-4)*10 = 2868.
|
|
const RESHAPED_WEIGHT_COUNT = 2868;
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await loadProbe(page);
|
|
});
|
|
|
|
test.describe('reshape — runtime-shaped MLP', () => {
|
|
test('boots at the default 32 / 126 shape', async ({ page }) => {
|
|
const arch = await page.evaluate(() => window.__nisps!.describe());
|
|
expect(arch.inputSize).toBe(32);
|
|
expect(arch.outputSize).toBe(126);
|
|
|
|
const len = await page.evaluate(() => window.__nisps!.getWeights().length);
|
|
expect(len).toBe(DEFAULT_WEIGHT_COUNT);
|
|
});
|
|
|
|
test('reshape to 4 inputs succeeds and describe reports 4 / 126', async ({ page }) => {
|
|
const result = await page.evaluate(() => window.__nisps!.reshape(4));
|
|
expect(result).not.toBeNull();
|
|
expect(result!.inputSize).toBe(4);
|
|
expect(result!.outputSize).toBe(126);
|
|
|
|
const arch = await page.evaluate(() => window.__nisps!.describe());
|
|
expect(arch.inputSize).toBe(4);
|
|
expect(arch.outputSize).toBe(126);
|
|
});
|
|
|
|
test('getWeights length changes with the new arity', async ({ page }) => {
|
|
const before = await page.evaluate(() => window.__nisps!.getWeights().length);
|
|
expect(before).toBe(DEFAULT_WEIGHT_COUNT);
|
|
|
|
await page.evaluate(() => window.__nisps!.reshape(4));
|
|
|
|
const after = await page.evaluate(() => window.__nisps!.getWeights().length);
|
|
expect(after).toBe(RESHAPED_WEIGHT_COUNT);
|
|
expect(after).not.toBe(before);
|
|
});
|
|
|
|
test('outputs stay bounded after reshape', async ({ page }) => {
|
|
await page.evaluate(() => window.__nisps!.reshape(4));
|
|
await page.evaluate(() => window.__nisps!.setInputs(0.3, 0.7));
|
|
const outs = await getOutputs(page);
|
|
expect(outs.length).toBe(126);
|
|
expect(allWithin(outs, 0, 1)).toBe(true);
|
|
});
|
|
|
|
test('spine still propagates after reshape', async ({ page }) => {
|
|
await page.evaluate(() => window.__nisps!.reshape(4));
|
|
|
|
const a = await settleInputs(page, 0.2, 0.8);
|
|
const b = await settleInputs(page, 0.9, 0.1);
|
|
expect(a.length).toBe(126);
|
|
expect(b.length).toBe(126);
|
|
expect(allWithin(a, 0, 1)).toBe(true);
|
|
expect(allWithin(b, 0, 1)).toBe(true);
|
|
// Distinct inputs must still move the mapping through the reshaped net.
|
|
expect(countChanged(a, b, 1e-3)).toBeGreaterThan(0);
|
|
});
|
|
});
|