+39
-21
@@ -16,6 +16,8 @@ let loadPromise = null;
|
||||
let ownerCount = 0;
|
||||
let operationTail = Promise.resolve();
|
||||
const auxiliaryModels = new Map();
|
||||
const auxiliaryLoads = new Map();
|
||||
const auxiliaryOwners = new Map();
|
||||
|
||||
export const QVAC_MASTER = Object.freeze({
|
||||
configPath: process.env.QVAC_CONFIG_PATH,
|
||||
@@ -92,36 +94,51 @@ function resolveModelConfigAssets(sdk, config) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
function retainAuxiliaryModel(modelId) {
|
||||
auxiliaryOwners.set(modelId, (auxiliaryOwners.get(modelId) || 0) + 1);
|
||||
return modelId;
|
||||
}
|
||||
|
||||
/** 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 = {}, modelType = undefined) {
|
||||
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 auxiliaryConfig = resolveModelConfigAssets(sdk, modelConfig);
|
||||
// GPU placement belongs to the LLM model configuration. ASR and TTS have
|
||||
// their own validated schemas and reject llama.cpp-only keys.
|
||||
if (!modelType || modelType === 'llm') {
|
||||
auxiliaryConfig.device = 'gpu';
|
||||
auxiliaryConfig.gpu_layers = QVAC_MASTER.gpuLayers;
|
||||
auxiliaryConfig['mmproj-use-gpu'] = true;
|
||||
}
|
||||
const loadOptions = {
|
||||
modelSrc: resolveSdkAsset(sdk, name),
|
||||
modelConfig: auxiliaryConfig,
|
||||
};
|
||||
if (modelType) loadOptions.modelType = modelType;
|
||||
const modelId = await withQvacMaster(() => sdk.loadModel(loadOptions));
|
||||
auxiliaryModels.set(String(name), modelId);
|
||||
return modelId;
|
||||
if (existing) return retainAuxiliaryModel(existing);
|
||||
const key = String(name);
|
||||
if (auxiliaryLoads.has(key)) return retainAuxiliaryModel(await auxiliaryLoads.get(key));
|
||||
const pending = Promise.resolve().then(async () => {
|
||||
const sdk = await qvacSdk();
|
||||
if (typeof sdk.loadModel !== 'function') throw new Error('QVAC SDK does not expose loadModel()');
|
||||
const auxiliaryConfig = resolveModelConfigAssets(sdk, modelConfig);
|
||||
// GPU placement belongs to the LLM model configuration. ASR and TTS have
|
||||
// their own validated schemas and reject llama.cpp-only keys.
|
||||
if (!modelType || modelType === 'llm') {
|
||||
auxiliaryConfig.device = 'gpu';
|
||||
auxiliaryConfig.gpu_layers = QVAC_MASTER.gpuLayers;
|
||||
auxiliaryConfig['mmproj-use-gpu'] = true;
|
||||
}
|
||||
const loadOptions = {
|
||||
modelSrc: resolveSdkAsset(sdk, name),
|
||||
modelConfig: auxiliaryConfig,
|
||||
};
|
||||
if (modelType) loadOptions.modelType = modelType;
|
||||
const modelId = await withQvacMaster(() => sdk.loadModel(loadOptions));
|
||||
auxiliaryModels.set(String(name), modelId);
|
||||
return modelId;
|
||||
});
|
||||
auxiliaryLoads.set(key, pending);
|
||||
try { return retainAuxiliaryModel(await pending); }
|
||||
finally { if (auxiliaryLoads.get(key) === pending) auxiliaryLoads.delete(key); }
|
||||
}
|
||||
|
||||
export async function unloadAuxiliaryModel(modelId) {
|
||||
export async function unloadAuxiliaryModel(modelId, { force = false } = {}) {
|
||||
if (!modelId) return;
|
||||
const owners = auxiliaryOwners.get(modelId) || 0;
|
||||
if (!force && owners > 1) { auxiliaryOwners.set(modelId, owners - 1); return; }
|
||||
const sdk = await qvacSdk();
|
||||
if (typeof sdk.unloadModel === 'function') await withQvacMaster(() => sdk.unloadModel({ modelId }));
|
||||
auxiliaryOwners.delete(modelId);
|
||||
for (const [name, id] of auxiliaryModels) if (id === modelId) auxiliaryModels.delete(name);
|
||||
}
|
||||
|
||||
@@ -138,8 +155,9 @@ export function qvacBusy() {
|
||||
|
||||
export async function closeQvac() {
|
||||
if (ownerCount > 0) return;
|
||||
for (const modelId of auxiliaryModels.values()) await unloadAuxiliaryModel(modelId).catch(() => {});
|
||||
for (const modelId of auxiliaryModels.values()) await unloadAuxiliaryModel(modelId, { force: true }).catch(() => {});
|
||||
auxiliaryModels.clear();
|
||||
auxiliaryOwners.clear();
|
||||
loadPromise = null;
|
||||
await Agent.engine.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user