This commit is contained in:
2026-09-11 14:09:04 -04:00
parent 68d7a40605
commit 5c6c94713a
16 changed files with 539 additions and 16 deletions
+51 -1
View File
@@ -8,6 +8,8 @@ const Agent = require(path.join(harnessPath, 'index.js'));
let loadPromise = null;
let ownerCount = 0;
let operationTail = Promise.resolve();
const auxiliaryModels = new Map();
export const QVAC_MASTER = Object.freeze({
configPath: process.env.QVAC_CONFIG_PATH,
@@ -55,10 +57,58 @@ export async function acquireQvac() {
return loadPromise;
}
export async function qvacSdk() {
return Agent.engine.ensureInit();
}
export function withQvacMaster(task) {
const operation = operationTail.then(task, task);
operationTail = operation.catch(() => {});
return operation;
}
function resolveSdkAsset(sdk, name) {
if (name && typeof name !== 'string') return name;
return sdk[name] || sdk.models?.[name] || name;
}
function resolveModelConfigAssets(sdk, config) {
const copy = { ...config };
for (const key of ['vadModelSrc', 'projectionModelSrc', 'vocabModelSrc']) {
if (typeof copy[key] === 'string') copy[key] = resolveSdkAsset(sdk, copy[key]);
}
return copy;
}
/** Load ASR/TTS models in the same SDK worker and under the same master lock.
* These are auxiliary model IDs; they do not create another QVAC runtime. */
export async function loadAuxiliaryModel(name, modelConfig = {}) {
if (!name) throw new Error('auxiliary QVAC model name is required');
const existing = auxiliaryModels.get(String(name));
if (existing) return existing;
const sdk = await qvacSdk();
if (typeof sdk.loadModel !== 'function') throw new Error('QVAC SDK does not expose loadModel()');
const modelId = await withQvacMaster(() => sdk.loadModel({
modelSrc: resolveSdkAsset(sdk, name),
modelConfig: { ...resolveModelConfigAssets(sdk, modelConfig), device: 'gpu', gpu_layers: QVAC_MASTER.gpuLayers, 'mmproj-use-gpu': true },
}));
auxiliaryModels.set(String(name), modelId);
return modelId;
}
export async function unloadAuxiliaryModel(modelId) {
if (!modelId) return;
const sdk = await qvacSdk();
if (typeof sdk.unloadModel === 'function') await withQvacMaster(() => sdk.unloadModel({ modelId }));
for (const [name, id] of auxiliaryModels) if (id === modelId) auxiliaryModels.delete(name);
}
export function releaseQvac() { ownerCount = Math.max(0, ownerCount - 1); }
export async function closeQvac() {
if (ownerCount > 0) return;
for (const modelId of auxiliaryModels.values()) await unloadAuxiliaryModel(modelId).catch(() => {});
auxiliaryModels.clear();
loadPromise = null;
await Agent.engine.close();
}
@@ -101,7 +151,7 @@ export async function callQvac(method, input) {
}
export function qvacStatus() {
return { ...QVAC_MASTER, owners: ownerCount, loaded: Agent.engine.getLoaded() };
return { ...QVAC_MASTER, owners: ownerCount, loaded: Agent.engine.getLoaded(), auxiliaryModels: auxiliaryModels.size };
}
export { Agent };