69 lines
3.4 KiB
JavaScript
69 lines
3.4 KiB
JavaScript
/* 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();
|