Files
church-translate/app/_test-multi.mjs
T

69 lines
3.4 KiB
JavaScript
Raw Normal View History

/* Integration test: fork the REAL mt-service.js the way main.js does and prove
the batched path returns the same text as the one-at-a-time path. */
import { fork } from 'child_process';
import os from 'os'; import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const MODELS_DIR = path.join(os.homedir(), 'Library/Application Support/Church Translate Models');
const LANGS = ['spa_Latn','por_Latn','fra_Latn','zho_Hans','hat_Latn','hin_Deva','kor_Hang','vie_Latn','arb_Arab'];
const TEXTS = [
'And I want you to understand that God has a plan for your life',
'Turn with me to the book of Romans chapter eight',
'He was faithful even when it cost him everything, and that is the kind of faith we are called to',
'Amen',
];
const child = fork(path.join(__dirname, 'mt-service.js'), [], { stdio: ['ignore','pipe','pipe','ipc'] });
child.stderr.on('data', d => process.stderr.write('[mt] ' + d));
let seq = 0; const pending = new Map();
child.on('message', (m) => {
if (m.type === 'ready') { pending.get('ready')?.(); return; }
if (m.type === 'note') { console.log(' note: ' + m.message); return; }
if (m.type === 'progress') return;
const w = pending.get(m.id); if (!w) return; pending.delete(m.id);
if (m.type === 'error') w.reject(new Error(m.message));
else w.resolve(m.type === 'multi' ? m.texts : m.text);
});
const send = (payload) => new Promise((resolve, reject) => {
const id = ++seq; pending.set(id, { resolve, reject });
child.send({ ...payload, id });
});
const ready = new Promise(r => pending.set('ready', r));
child.send({ type: 'load', model: 'Xenova/nllb-200-distilled-600M', dtype: 'fp32', cacheDir: MODELS_DIR });
const t0 = Date.now();
await ready;
console.log('model ready in ' + ((Date.now()-t0)/1000).toFixed(1) + 's\n');
const one = (text, tgt) => send({ type:'translate', text, src:'eng_Latn', tgt });
const many = (text, tgts) => send({ type:'translateMulti', text, src:'eng_Latn', tgts });
await one(TEXTS[0], 'spa_Latn'); await many(TEXTS[0], ['spa_Latn','fra_Latn']); // warm
let mismatches = 0, checks = 0;
console.log('text N | serial | batched | speedup | match');
console.log('-----------------------------+----------+----------+---------+------');
for (const text of TEXTS) {
for (const n of [3, 9]) {
const langs = LANGS.slice(0, n);
let s = Date.now(); const so = {}; for (const l of langs) so[l] = await one(text, l); s = Date.now()-s;
let b = Date.now(); const bo = await many(text, langs); b = Date.now()-b;
let same = 0; for (const l of langs) { checks++; if (so[l] === bo[l]) same++; else { mismatches++; } }
console.log(
(text.slice(0,26)).padEnd(27) + String(n).padStart(2) + ' | ' +
(s+'ms').padStart(8) + ' | ' + (b+'ms').padStart(8) + ' | ' +
((s/b).toFixed(2)+'x').padStart(7) + ' | ' + same + '/' + n);
if (same !== n) for (const l of langs) if (so[l] !== bo[l])
console.log(' ' + l + '\n serial : ' + so[l] + '\n batched: ' + bo[l]);
}
}
console.log('\n' + (checks - mismatches) + '/' + checks + ' identical across ' + TEXTS.length + ' texts');
/* Edge cases */
console.log('\nedge cases:');
console.log(' one target :', JSON.stringify(await many('God is good', ['spa_Latn'])));
console.log(' zero targets :', JSON.stringify(await many('God is good', [])));
console.log(' duplicate :', JSON.stringify(await many('God is good', ['spa_Latn','spa_Latn'])));
child.kill();