项目文件夹

文件
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

223 行
7.8 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env bun
/**
* STT quality benchmark across the published eliza-1-asr GGUF quants (#10726
* scope item: "STT quality benchmarks per model/quant, documented per-device
* selection"). Real weights, real fused-lib transcription — no mocks.
*
* For every `eliza-1-asr-<quant>.gguf` in ELIZA_ASR_QUANT_DIR (paired with the
* dir's shared `eliza-1-asr-mmproj.gguf`), plus optionally the shipped bundle
* ASR (ELIZA_ASR_BUNDLE — what mobile provisioning actually stages), this lane
* transcribes the fixed-transcript Kokoro corpus and reports per-quant:
* WER (vs the known reference texts), transcription latency, RTF, and load
* time. Corpus caveat: the reference audio is TTS speech, so absolute WER
* carries the TTS pronunciation floor; the CROSS-QUANT comparison is the
* signal (identical audio for every quant).
*
* Exit 0 pass / 1 fail / 2 skip. STT_BENCH_REQUIRE=1 turns skips into failures.
*
* Env:
* ELIZA_INFERENCE_LIBRARY / ELIZA_INFERENCE_LIB_DIR — fused lib
* ELIZA_ASR_QUANT_DIR — dir with eliza-1-asr-<quant>.gguf + eliza-1-asr-mmproj.gguf
* ELIZA_ASR_BUNDLE — optional shipped-bundle row (asr/eliza-1-asr.gguf layout)
* ELIZA_KOKORO_MODEL_DIR — Kokoro model for one-time corpus synthesis
* STT_BENCH_MAX_BEST_WER — sanity ceiling for the BEST quant's mean WER (default 0.5)
* ELIZA_VOICE_BENCH_OUT — report dir (default <plugin>/voice-bench-output)
*/
import { existsSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import { wordErrorRate } from "@elizaos/shared/voice-wer";
import { STT_BENCH_CORPUS } from "../src/services/voice/bench-utils";
import {
BENCH_SAMPLE_RATE,
bootFusedFfi,
type CorpusItem,
defaultReportDir,
ensureKokoroCorpus,
makeBenchGates,
makeQuantBundle,
mean,
median,
writeBenchReport,
} from "./voice-bench-shared";
const TAG = "stt-quant-bench";
const gates = makeBenchGates(TAG, "STT_BENCH_REQUIRE");
const log = (msg: string) => console.log(`[${TAG}] ${msg}`);
interface Variant {
name: string;
bundleDir: string;
sizeBytes: number;
cleanup?: () => void;
}
interface VariantResult {
name: string;
sizeBytes: number;
loadMs: number;
meanWer: number;
medianWer: number;
meanTranscribeMs: number;
firstUttMs: number;
/** total transcribe time / total audio time — lower is better, <1 = faster than realtime. */
rtf: number;
/** 1/rtf — "× realtime", higher is better (asr_bench.ts convention). */
xRealtime: number;
utterances: Array<{
id: string;
reference: string;
transcript: string;
wer: number;
ms: number;
audioSeconds: number;
}>;
}
const { ffi, libPath } = bootFusedFfi(gates);
log(`lib=${libPath}`);
// --- discover variants -------------------------------------------------------
const quantDir = process.env.ELIZA_ASR_QUANT_DIR?.trim();
const variants: Variant[] = [];
if (quantDir && existsSync(quantDir)) {
const mmproj = path.join(quantDir, "eliza-1-asr-mmproj.gguf");
if (!existsSync(mmproj)) {
gates.skip(`ELIZA_ASR_QUANT_DIR has no eliza-1-asr-mmproj.gguf (${quantDir})`);
}
const quantFiles = readdirSync(quantDir)
.filter((f) => /^eliza-1-asr-(?!mmproj).+\.gguf$/.test(f))
.sort();
for (const f of quantFiles) {
const quantPath = path.join(quantDir, f);
const { dir, cleanup } = makeQuantBundle(quantPath, mmproj);
variants.push({
name: f.replace(/^eliza-1-asr-/, "").replace(/\.gguf$/, ""),
bundleDir: dir,
sizeBytes: statSync(quantPath).size + statSync(mmproj).size,
cleanup,
});
}
}
const shippedBundle = process.env.ELIZA_ASR_BUNDLE?.trim();
if (shippedBundle && existsSync(path.join(shippedBundle, "asr", "eliza-1-asr.gguf"))) {
const main = path.join(shippedBundle, "asr", "eliza-1-asr.gguf");
const mm = path.join(shippedBundle, "asr", "eliza-1-asr-mmproj.gguf");
variants.push({
name: "bundle-2b (shipped)",
bundleDir: shippedBundle,
sizeBytes: statSync(main).size + (existsSync(mm) ? statSync(mm).size : 0),
});
}
if (variants.length === 0) {
gates.skip(
"no ASR variants found — set ELIZA_ASR_QUANT_DIR (eliza-1-asr-<q>.gguf + mmproj) " +
"and/or ELIZA_ASR_BUNDLE (shipped bundle layout)",
);
}
log(`variants: ${variants.map((v) => v.name).join(", ")}`);
// --- corpus -------------------------------------------------------------------
const corpus: CorpusItem[] = await ensureKokoroCorpus("clean", STT_BENCH_CORPUS, gates, log);
const totalAudioSec = corpus.reduce((a, c) => a + c.seconds, 0);
log(`corpus: ${corpus.length} utterances, ${totalAudioSec.toFixed(1)}s audio total`);
// --- benchmark ----------------------------------------------------------------
const results: VariantResult[] = [];
for (const variant of variants) {
log(`--- ${variant.name} (${(variant.sizeBytes / 1e6).toFixed(0)} MB) ---`);
const tLoad0 = performance.now();
const ctx = ffi.create(variant.bundleDir);
ffi.mmapAcquire(ctx, "asr");
const loadMs = Math.round(performance.now() - tLoad0);
const utterances: VariantResult["utterances"] = [];
try {
for (const item of corpus) {
const t0 = performance.now();
const { text } = ffi.asrTranscribeTimed({
ctx,
pcm: item.pcm,
sampleRateHz: BENCH_SAMPLE_RATE,
});
const ms = Math.round(performance.now() - t0);
const transcript = (text ?? "").trim();
const wer = wordErrorRate(item.text, transcript);
utterances.push({
id: item.id,
reference: item.text,
transcript,
wer,
ms,
audioSeconds: item.seconds,
});
log(` ${item.id} ${ms}ms WER=${wer.toFixed(3)} "${transcript}"`);
}
} finally {
ffi.mmapEvict(ctx, "asr");
ffi.destroy(ctx);
variant.cleanup?.();
}
const totalMs = utterances.reduce((a, u) => a + u.ms, 0);
const rtf = totalMs / 1000 / totalAudioSec;
results.push({
name: variant.name,
sizeBytes: variant.sizeBytes,
loadMs,
meanWer: mean(utterances.map((u) => u.wer)),
medianWer: median(utterances.map((u) => u.wer)),
meanTranscribeMs: mean(utterances.map((u) => u.ms)),
firstUttMs: utterances[0]?.ms ?? 0,
rtf,
xRealtime: rtf > 0 ? 1 / rtf : 0,
utterances,
});
}
// --- report -------------------------------------------------------------------
const header =
"| variant | size (MB) | load (ms) | mean WER | median WER | mean ms/utt | 1st utt (ms) | RTF | × realtime |";
const sep = "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |";
const rows = results.map(
(r) =>
`| ${r.name} | ${(r.sizeBytes / 1e6).toFixed(0)} | ${r.loadMs} | ${r.meanWer.toFixed(3)} | ${r.medianWer.toFixed(3)} | ${Math.round(r.meanTranscribeMs)} | ${r.firstUttMs} | ${r.rtf.toFixed(3)} | ${r.xRealtime.toFixed(1)}× |`,
);
const table = [header, sep, ...rows].join("\n");
console.log(`\n${table}\n`);
const md = [
"# STT quant benchmark — eliza-1-asr (real weights, fused lib, CPU)",
"",
`Host: ${process.platform}-${process.arch}. Corpus: ${corpus.length} fixed-transcript Kokoro utterances (${totalAudioSec.toFixed(1)}s). ` +
"Absolute WER includes the TTS pronunciation floor; cross-quant deltas are the signal.",
"",
table,
"",
].join("\n");
const { jsonPath, mdPath } = writeBenchReport(
defaultReportDir(),
"stt-quant-bench",
{
schemaVersion: 1,
generatedAt: new Date().toISOString(),
host: `${process.platform}-${process.arch}`,
lib: libPath,
corpus: { utterances: corpus.length, totalAudioSec },
results,
},
md,
);
log(`report: ${jsonPath}`);
log(`report: ${mdPath}`);
// --- gate ---------------------------------------------------------------------
// Sanity, not vanity: if even the best quant cannot transcribe the corpus, the
// published weights (or the ASR path) are broken and the lane must go RED.
const maxBestWer = Number(process.env.STT_BENCH_MAX_BEST_WER ?? "0.5");
const bestWer = Math.min(...results.map((r) => r.meanWer));
if (bestWer > maxBestWer) {
gates.fail(
`best-quant mean WER ${bestWer.toFixed(3)} > ${maxBestWer} — published ASR weights or decode path are broken`,
);
}
log(`PASS (best mean WER ${bestWer.toFixed(3)}${maxBestWer})`);