libs/voicefocus/decider.js (136 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.decideModel = exports.measureAndDecideExecutionApproach = void 0;
const loader_js_1 = require("./loader.js");
const support_js_1 = require("./support.js");
const DEFAULT_EXECUTION_QUANTA = 3;
const SIMD_SCORE_FIXED_POINT = 2.50;
const WASM_SCORE_FIXED_POINT = 2.63;
const SINGLE_INLINE_SCORE_MULTIPLIER = 0.6;
const QUALITY_MULTIPLE_QUANTA_SCORE_MULTIPLIER = 0.65;
const INTERACTIVITY_MULTIPLE_QUANTA_SCORE_MULTIPLIER = 0.5;
const WORKER_SCORE_MULTIPLIER = 0.7;
const PERFORMANCE_THRESHOLDS = {
wasm: {
noSupport: 0.07,
inline: {
c100: 1,
c50: 0.36,
c20: 0.16,
c10: 0.07,
},
worker: {
c100: 0.5,
c50: 0.18,
c20: 0.08,
c10: 0.06,
},
},
simd: {
noSupport: 0.10,
inline: {
c100: 1,
c50: 0.43,
c20: 0.3,
c10: 0.2,
},
worker: {
c100: 0.5,
c50: 0.21,
c20: 0.15,
c10: 0.10,
},
},
};
class Estimator {
constructor(fetchConfig, logger) {
this.fetchConfig = fetchConfig;
this.logger = logger;
const workerURL = `${fetchConfig.paths.workers}estimator-v1.js`;
this.fetchBehavior = { headers: fetchConfig.headers, escapedQueryString: fetchConfig.escapedQueryString };
this.worker = loader_js_1.loadWorker(workerURL, 'VoiceFocusEstimator', this.fetchBehavior, logger);
}
roundtrip(toSend, receive, expectedKey) {
return new Promise((resolve, reject) => {
this.worker.then(worker => {
let listener;
listener = (event) => {
const { message, key } = event.data;
if (message === receive && key === expectedKey) {
worker.removeEventListener('message', listener);
resolve(event.data);
}
};
worker.addEventListener('message', listener);
worker.postMessage(toSend);
}).catch(e => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error('Failed to load worker.', e);
reject(e);
});
});
}
supportsSIMD(url) {
const key = 'simd';
const path = url || `${this.fetchConfig.paths.wasm}simd-v1.wasm`;
const toSend = {
message: 'supports-simd',
fetchBehavior: this.fetchBehavior,
path,
key,
};
return this.roundtrip(toSend, 'simd-support', key)
.then(data => data.supports);
}
measure(simd, budget) {
const benchWASM = `${this.fetchConfig.paths.wasm}bench-v1.wasm`;
const benchSIMD = `${this.fetchConfig.paths.wasm}bench-v1_simd.wasm`;
const path = simd ? benchSIMD : benchWASM;
const key = `bench:${simd}`;
const toSend = {
message: 'measure',
fetchBehavior: this.fetchBehavior,
budget,
path,
key,
};
return this.roundtrip(toSend, 'measurement', key)
.then(data => {
if (data.measurement) {
return data.measurement;
}
throw new Error('Failed to measure.');
});
}
stop() {
this.worker.then(worker => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug('Stopping estimator worker.');
worker.terminate();
}).catch(e => {
});
}
}
const inlineScoreMultiplier = (executionQuanta, usagePreference) => {
if (executionQuanta === 1) {
return SINGLE_INLINE_SCORE_MULTIPLIER;
}
if (usagePreference === 'quality') {
return QUALITY_MULTIPLE_QUANTA_SCORE_MULTIPLIER * executionQuanta;
}
return INTERACTIVITY_MULTIPLE_QUANTA_SCORE_MULTIPLIER * executionQuanta;
};
const decideExecutionApproach = ({ supportsSIMD, supportsSAB, duration, executionPreference = 'auto', simdPreference, variantPreference = 'auto', namePreference = 'default', usagePreference, executionQuantaPreference = DEFAULT_EXECUTION_QUANTA, }, allThresholds = PERFORMANCE_THRESHOLDS, logger) => {
const forceSIMD = (simdPreference === 'force');
const useSIMD = forceSIMD || (simdPreference !== 'disable' && supportsSIMD);
const checkScores = duration !== -1;
const baseScore = checkScores ? (useSIMD ? SIMD_SCORE_FIXED_POINT : WASM_SCORE_FIXED_POINT) / duration : 0;
const thresholds = useSIMD ? allThresholds.simd : allThresholds.wasm;
const inlineScore = checkScores ? inlineScoreMultiplier(executionQuantaPreference, usagePreference) * baseScore : 0;
const workerScore = checkScores ? WORKER_SCORE_MULTIPLIER * baseScore : 0;
const name = namePreference;
const unsupported = (reason) => {
return {
supported: false,
reason,
};
};
if (checkScores) {
if (baseScore < thresholds.noSupport) {
return unsupported(`Performance score ${baseScore} worse than threshold ${thresholds.noSupport}.`);
}
}
else {
if ((executionPreference === 'auto') ||
(variantPreference === 'auto')) {
return unsupported(`Missing explicit execution (${executionPreference}) or variant (${variantPreference}) preference, but no scoring performed.`);
}
}
logger === null || logger === void 0 ? void 0 : logger.debug(`Bench duration ${duration} yields inline score ${inlineScore} and worker score ${workerScore}.`);
const succeed = (processor, executionApproach, variant) => {
return {
supported: true,
useSIMD,
processor,
executionApproach,
variant,
executionQuanta: (executionApproach === 'inline' ? executionQuantaPreference : undefined),
name,
};
};
const resolveVariant = (score, variant, lookup) => {
if (variant !== 'auto') {
if (!checkScores || score > lookup[variant]) {
return variant;
}
return 'failed';
}
if (score > lookup.c100) {
return 'c100';
}
if (score > lookup.c50) {
return 'c50';
}
if (score > lookup.c20) {
return 'c20';
}
if (score > lookup.c10) {
return 'c10';
}
return 'failed';
};
const reducePreference = (preference) => {
switch (preference || 'auto') {
case 'auto': {
let inlineOption = reducePreference('inline');
let workerOption = reducePreference('worker');
logger === null || logger === void 0 ? void 0 : logger.debug(`Reducing auto preference: ${JSON.stringify(inlineOption)} vs ${JSON.stringify(workerOption)}`);
if (inlineOption.supported === false) {
return workerOption;
}
if (workerOption.supported === false) {
return workerOption;
}
if (inlineOption.variant === workerOption.variant || inlineOption.variant === 'c50') {
return inlineOption;
}
return workerOption;
}
case 'worker': {
if (support_js_1.supportsSharedArrayBuffer(globalThis, window, logger)) {
return reducePreference('worker-sab');
}
return reducePreference('worker-postMessage');
}
case 'inline': {
const variant = resolveVariant(inlineScore, variantPreference, thresholds.inline);
if (variant === 'failed') {
return unsupported(`Performance score ${inlineScore} not sufficient for inline use with variant preference ${variantPreference}.`);
}
;
return succeed('voicefocus-inline-processor', 'inline', variant);
}
case 'worker-sab': {
if (!supportsSAB) {
const reason = 'Requested worker-sab but no SharedArrayBuffer support.';
logger === null || logger === void 0 ? void 0 : logger.warn(reason);
return { supported: false, reason };
}
const variant = resolveVariant(workerScore, variantPreference, thresholds.worker);
if (variant === 'failed') {
return unsupported(`Performance score ${workerScore} not sufficient for worker use with variant preference ${variantPreference}.`);
}
;
return succeed('voicefocus-worker-sab-processor', 'worker-sab', variant);
}
case 'worker-postMessage': {
const variant = resolveVariant(workerScore, variantPreference, thresholds.worker);
if (variant === 'failed') {
return unsupported(`Performance score ${workerScore} not sufficient for worker use.`);
}
;
if (name === 'ns_es') {
const reason = 'Requested echo suppression but postMessage executor does not support it.';
logger === null || logger === void 0 ? void 0 : logger.warn(reason);
return { supported: false, reason };
}
;
return succeed('voicefocus-worker-postMessage-processor', 'worker-postMessage', variant);
}
}
};
return reducePreference(executionPreference);
};
const featureCheck = (forceSIMD, fetchConfig, logger, estimator) => __awaiter(void 0, void 0, void 0, function* () {
const supports = {
supportsSIMD: forceSIMD,
supportsSAB: support_js_1.supportsSharedArrayBuffer(globalThis, window, logger),
duration: -1,
};
if (forceSIMD) {
logger === null || logger === void 0 ? void 0 : logger.info('Supports SIMD: true (force)');
return supports;
}
const cleanup = !estimator;
const e = estimator || new Estimator(fetchConfig, logger);
try {
const useSIMD = !support_js_1.isOldChrome(window, logger) && (yield e.supportsSIMD());
logger === null || logger === void 0 ? void 0 : logger.info(`Supports SIMD: ${useSIMD} (force: ${forceSIMD})`);
supports.supportsSIMD = useSIMD;
return supports;
}
finally {
if (cleanup) {
e.stop();
}
}
});
const estimateAndFeatureCheck = (forceSIMD, fetchConfig, estimatorBudget, logger) => __awaiter(void 0, void 0, void 0, function* () {
const estimator = new Estimator(fetchConfig, logger);
try {
const supports = yield featureCheck(forceSIMD, fetchConfig, logger, estimator);
if (supports.supportsSIMD) {
try {
supports.duration = yield estimator.measure(true, estimatorBudget);
logger === null || logger === void 0 ? void 0 : logger.info('SIMD timing:', supports.duration);
return supports;
}
catch (e) {
logger === null || logger === void 0 ? void 0 : logger.warn('Failed SIMD estimation; falling back to non-SIMD.');
supports.supportsSIMD = false;
}
}
supports.duration = yield estimator.measure(false, estimatorBudget);
logger === null || logger === void 0 ? void 0 : logger.info('No-SIMD timing:', supports.duration);
return supports;
}
catch (e) {
logger === null || logger === void 0 ? void 0 : logger.error('Could not feature check.', e);
throw e;
}
finally {
estimator.stop();
}
});
const measureAndDecideExecutionApproach = (spec, fetchConfig, logger, thresholds = PERFORMANCE_THRESHOLDS) => __awaiter(void 0, void 0, void 0, function* () {
let executionPreference = spec.executionPreference;
const { usagePreference, variantPreference, namePreference, simdPreference, estimatorBudget, executionQuantaPreference, } = spec;
if (usagePreference === 'interactivity' && executionPreference !== 'inline') {
logger === null || logger === void 0 ? void 0 : logger.debug(`Overriding execution preference ${executionPreference} to reflect interactivity preference.`);
executionPreference = 'inline';
}
const forceSIMD = simdPreference === 'force';
const knownModel = variantPreference !== 'auto';
const knownExecution = executionPreference !== 'auto';
let supports;
try {
if (knownModel && knownExecution) {
supports = yield featureCheck(forceSIMD, fetchConfig, logger);
}
else {
supports = yield estimateAndFeatureCheck(forceSIMD, fetchConfig, estimatorBudget, logger);
}
}
catch (e) {
logger === null || logger === void 0 ? void 0 : logger.error('Could not load estimator.', e);
throw new Error('Could not load Voice Focus estimator.');
}
return decideExecutionApproach(Object.assign(Object.assign({}, supports), { simdPreference,
executionPreference,
variantPreference,
namePreference,
usagePreference,
executionQuantaPreference }), thresholds, logger);
});
exports.measureAndDecideExecutionApproach = measureAndDecideExecutionApproach;
const decideModel = ({ category, name, variant, simd, url }) => {
return `${category}-${name}-${variant}-v1${simd ? '_simd' : ''}`;
};
exports.decideModel = decideModel;