|
| 1 | +// V4-4: Real-data convergence — pick fixture parquet+tokenizer via |
| 2 | +// UI, set 8 steps, assert tokenized path activated AND losses fell. |
| 3 | +// Closes G10 from the V4 audit: V3-6 convergence used synthetic |
| 4 | +// random targets; this is the real-corpus version. |
| 5 | + |
| 6 | +import { test, expect } from "@playwright/test"; |
| 7 | +import { gotoApp, selectPreset, clickTab, closeModal } from "../fixtures"; |
| 8 | +import { loadMatrix } from "../utils/matrix"; |
| 9 | +import { readTrainExtras } from "../utils/train_extras"; |
| 10 | + |
| 11 | +const MATRIX = loadMatrix(); |
| 12 | + |
| 13 | +// Tokenizer + parquet pair from the existing E-1 matrix fixture |
| 14 | +// (parquet has an 'original_text' column the V4-2 helper recognises). |
| 15 | +const REAL_PARQUET = MATRIX.parquets.T2_gpt2_small__P1_minimal.path; |
| 16 | +const REAL_TOKENIZER = MATRIX.tokenizers.T2_gpt2_small.path; |
| 17 | + |
| 18 | +// Deep presets that produced loss-fall in V3-6 synthetic test. |
| 19 | +const CONVERGENCE_PRESETS = ["llama3_8b", "mistral_small_3_1"]; |
| 20 | + |
| 21 | +for (const preset of CONVERGENCE_PRESETS) { |
| 22 | + test(`V4-4: real-data convergence on ${preset}`, async ({ page }) => { |
| 23 | + test.setTimeout(150_000); |
| 24 | + await gotoApp(page); |
| 25 | + await selectPreset(page, preset); |
| 26 | + |
| 27 | + // Wire parquet |
| 28 | + await clickTab(page, "data"); |
| 29 | + await page.getByTestId("data-inspector").waitFor(); |
| 30 | + await page.getByTestId("data-path").fill(REAL_PARQUET); |
| 31 | + await page.getByTestId("data-load").click(); |
| 32 | + await page.getByTestId("data-metrics").waitFor({ timeout: 8_000 }); |
| 33 | + await page.getByTestId("data-use-for-train").click(); |
| 34 | + |
| 35 | + // Wire tokenizer |
| 36 | + await clickTab(page, "tokenizer"); |
| 37 | + await page.getByTestId("tokenizer-playground").waitFor(); |
| 38 | + await page.getByTestId("add-panel").click(); |
| 39 | + await page.getByTestId("tokenizer-source-0").fill(REAL_TOKENIZER); |
| 40 | + await page.getByTestId("tokenizer-encode-0").click(); |
| 41 | + await page.getByTestId("tokenizer-metrics-0").waitFor({ timeout: 8_000 }); |
| 42 | + await page.getByTestId("tokenizer-use-for-train-0").click(); |
| 43 | + |
| 44 | + // Train N=8 |
| 45 | + await clickTab(page, "canvas"); |
| 46 | + await page.getByTestId("run-pipeline-toggle").click(); |
| 47 | + await page.getByTestId("train-num-steps").fill("8"); |
| 48 | + await page.getByTestId("run-pipeline-train").click(); |
| 49 | + const modal = page.getByTestId("run-result-modal"); |
| 50 | + await modal.waitFor({ timeout: 90_000 }); |
| 51 | + |
| 52 | + const extras = await readTrainExtras(page); |
| 53 | + |
| 54 | + // Real-data path activated |
| 55 | + const dataSource = await page.getByTestId( |
| 56 | + "run-result-extras-train-data_source").textContent(); |
| 57 | + expect(dataSource?.trim()).toBe("parquet_tokenized"); |
| 58 | + |
| 59 | + // tokenizer_used reports a path basename (not 'null') |
| 60 | + const tokUsed = await page.getByTestId( |
| 61 | + "run-result-extras-train-tokenizer_used").textContent(); |
| 62 | + expect(tokUsed?.trim()).toContain(".json"); |
| 63 | + |
| 64 | + // 8 steps actually executed |
| 65 | + expect(extras.num_steps).toBe(8); |
| 66 | + expect(extras.losses.length).toBe(8); |
| 67 | + expect(extras.losses.every(l => Number.isFinite(l))).toBe(true); |
| 68 | + |
| 69 | + // Convergence floor: at least one of {strict last<first, |
| 70 | + // tailAvg<headAvg} must hold. Real tokens give signal; if neither |
| 71 | + // direction holds the loss kernel is broken. |
| 72 | + const first = extras.losses[0]; |
| 73 | + const last = extras.losses[extras.losses.length - 1]; |
| 74 | + const head = extras.losses.slice(0, 3); |
| 75 | + const tail = extras.losses.slice(-3); |
| 76 | + const headAvg = head.reduce((a, b) => a + b, 0) / head.length; |
| 77 | + const tailAvg = tail.reduce((a, b) => a + b, 0) / tail.length; |
| 78 | + expect(last < first || tailAvg < headAvg).toBe(true); |
| 79 | + |
| 80 | + // Weights actually moved |
| 81 | + expect(extras.weight_delta_norm).toBeGreaterThan(1e-4); |
| 82 | + |
| 83 | + await closeModal(page); |
| 84 | + }); |
| 85 | +} |
0 commit comments