diff --git a/README.md b/README.md index e3b28a2..a092141 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ See the Speedometer repo for a more detailed explanation, e.g., in which phases - Workloads are in `resources/transformers-js/` and `resources/litert-js`. - Shared files are in `resources/shared/`, which is depended-upon as a local package. - The default suite / tests to run are in `resources/default-tests.mjs`. +- Cleaning build artifacts and cached models (optional): Run `npm run clean` in the root directory. This will delete the `dist` directories and clear cached models for all workloads (while preserving the large `gemma` model to avoid unnecessary re-downloads). ## How to Run Individual Workload diff --git a/package.json b/package.json index d3e98a3..4d61400 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "license": "SEE LICENSE IN LICENSE", "scripts": { "build": "node script/build.mjs", + "clean": "node script/clean.mjs", "dev": "node tests/server.mjs", "lint:check": "eslint **/*.{js,mjs,jsx,ts,tsx}", "lint:fix": "eslint \"**/*.{js,mjs,jsx,ts,tsx}\" --fix", diff --git a/resources/experimental/package-lock.json b/resources/experimental/package-lock.json index c0acdda..ba96235 100644 --- a/resources/experimental/package-lock.json +++ b/resources/experimental/package-lock.json @@ -908,6 +908,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -941,6 +942,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1064,6 +1066,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3163,6 +3166,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.1.tgz", "integrity": "sha512-Gdj3X74CLJJ8zy4URmK42W7wTZUJrqL+z8nyGEr4dTN0kb3nVs+ZvjbTOqRYPD7qX4tUmwyHL9Q9K6T1seW6Yw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -3212,6 +3216,7 @@ "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/resources/experimental/src/download-models.mjs b/resources/experimental/src/download-models.mjs index e6dafde..b27d04b 100644 --- a/resources/experimental/src/download-models.mjs +++ b/resources/experimental/src/download-models.mjs @@ -32,29 +32,11 @@ async function downloadModels() { env.allowRemoteModels = true; try { - // Download models that work with pipeline - for (const modelInfo of MODELS_TO_DOWNLOAD) { - const { id: modelId, task: modelTask, dtype: modelDType } = modelInfo; - - const cacheKey = `${modelId}-${modelTask}-${modelDType}`; - if (cache.has(cacheKey)) { - console.log(`Model ${modelId} (${modelTask}, dtype: ${modelDType}) already cached. Skipping.`); - continue; - } - - console.log(`Downloading files for ${modelId} (${modelTask}, dtype: ${modelDType})...`); - - await retry(() => pipeline( - modelTask, - modelId, - { - cache_dir: env.localModelPath, - dtype: modelDType - })); - - console.log(`Successfully downloaded and cached ${modelId}`); - cache.put(cacheKey); - } + console.log(`Downloading all experimental models in parallel...`); + await Promise.all( + MODELS_TO_DOWNLOAD.map(modelInfo => downloadPipelineModel(modelInfo, cache)) + ); + console.log(`Successfully checked and downloaded all models.`); } catch (err) { console.error("Model download failed:", err); @@ -64,6 +46,29 @@ async function downloadModels() { env.allowRemoteModels = originalAllowRemote; } +async function downloadPipelineModel(modelInfo, cache) { + const { id: modelId, task: modelTask, dtype: modelDType } = modelInfo; + + const cacheKey = `${modelId}-${modelTask}-${modelDType}`; + if (cache.has(cacheKey)) { + console.log(`Model ${modelId} (${modelTask}, dtype: ${modelDType}) already cached. Skipping.`); + return; + } + + console.log(`Downloading files for ${modelId} (${modelTask}, dtype: ${modelDType})...`); + + await retry(() => pipeline( + modelTask, + modelId, + { + cache_dir: env.localModelPath, + dtype: modelDType + })); + + console.log(`Successfully downloaded and cached ${modelId}`); + cache.put(cacheKey); +} + downloadModels().catch(err => { console.error("Download process terminated."); process.exit(1); diff --git a/resources/litert-js/package-lock.json b/resources/litert-js/package-lock.json index b76b633..4031c07 100644 --- a/resources/litert-js/package-lock.json +++ b/resources/litert-js/package-lock.json @@ -283,6 +283,7 @@ "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz", "integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/long": "^4.0.1", "@types/offscreencanvas": "~2019.7.0", @@ -713,6 +714,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -757,6 +759,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -923,6 +926,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2526,6 +2530,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2755,7 +2760,8 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "7.7.3", @@ -3169,6 +3175,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -3217,6 +3224,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/resources/litert-js/src/download-models.mjs b/resources/litert-js/src/download-models.mjs index 0da3dc1..2a2961d 100644 --- a/resources/litert-js/src/download-models.mjs +++ b/resources/litert-js/src/download-models.mjs @@ -11,17 +11,17 @@ const CACHE_VERSION = 1; const MODELS_TO_DOWNLOAD = [ { - repo: 'qualcomm/MediaPipe-Selfie-Segmentation', + repo: 'qualcomm/MediaPipe-Selfie-Segmentation', filename: 'mediapipe_selfie-tflite-float.zip', url: 'https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/mediapipe_selfie/releases/v0.46.0/mediapipe_selfie-tflite-float.zip' }, - { - repo: 'qualcomm/MobileNet-v3-Small', + { + repo: 'qualcomm/MobileNet-v3-Small', filename: 'mobilenet_v3_small-tflite-float.zip', url: 'https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/mobilenet_v3_small/releases/v0.46.0/mobilenet_v3_small-tflite-float.zip' }, - { - repo: 'qualcomm/MediaPipe-Hand-Detection', + { + repo: 'qualcomm/MediaPipe-Hand-Detection', filename: 'mediapipe_hand-tflite-float.zip', url: 'https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/mediapipe_hand/releases/v0.46.0/mediapipe_hand-tflite-float.zip' } @@ -34,59 +34,68 @@ async function downloadModels() { if (!fs.existsSync(MODEL_DIR)) { console.log(`Creating directory: **${MODEL_DIR}**`); - fs.mkdirSync(MODEL_DIR, { recursive: true }); + fs.mkdirSync(MODEL_DIR, { recursive: true }); } console.log(`Starting TFLite model downloads to: **${MODEL_DIR}**`); - for (const modelInfo of MODELS_TO_DOWNLOAD) { - const { repo, filename, url } = modelInfo; + try { + await Promise.all( + MODELS_TO_DOWNLOAD.map(modelInfo => downloadModel(modelInfo, cache)) + ); + } catch (err) { + console.error("TFLite model download failed:", err); + throw err; + } + console.log('TFLite download process finished.'); +} - const cacheKey = `${repo}-${filename}`; - if (cache.has(cacheKey)) { - console.log(`Model ${filename} from ${repo} already cached. Skipping.`); - continue; - } +async function downloadModel(modelInfo, cache) { + const { repo, filename, url } = modelInfo; - const modelUrl = url; - const outputPath = path.join(MODEL_DIR, path.basename(filename)); + const cacheKey = `${repo}-${filename}`; + if (cache.has(cacheKey)) { + console.log(`Model ${filename} from ${repo} already cached. Skipping.`); + return; + } - console.log(`\nAttempting to download **${filename}** from **${repo}**...`); - console.log(`URL: ${modelUrl}`); + const modelUrl = url; + const outputPath = path.join(MODEL_DIR, path.basename(filename)); - try { - await retry(async () => { - const response = await fetch(modelUrl); + console.log(`\nAttempting to download **${filename}** from **${repo}**...`); + console.log(`URL: ${modelUrl}`); - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.statusText} (${response.status})`); - } + try { + await retry(async () => { + const response = await fetch(modelUrl); - const fileStream = fs.createWriteStream(outputPath); - await new Promise((resolve, reject) => { - response.body.pipe(fileStream); - response.body.on('error', reject); - fileStream.on('finish', resolve); - }); - }); - - console.log(`Successfully downloaded **${filename}** to **${outputPath}**`); - - if (path.extname(filename) === '.zip') { - console.log(`Extracting **${filename}**...`); - const zip = new AdmZip(outputPath); - zip.extractAllTo(MODEL_DIR, true); - console.log(`Successfully extracted **${filename}** to **${MODEL_DIR}**`); - fs.unlinkSync(outputPath); - console.log(`Deleted zip file **${outputPath}**`); + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.statusText} (${response.status})`); } - cache.put(cacheKey); - } catch (err) { - console.error(`Model download failed for ${repo}/${filename} after retries:`, err.message); + const fileStream = fs.createWriteStream(outputPath); + await new Promise((resolve, reject) => { + response.body.pipe(fileStream); + response.body.on('error', reject); + fileStream.on('finish', resolve); + }); + }); + + console.log(`Successfully downloaded **${filename}** to **${outputPath}**`); + + if (path.extname(filename) === '.zip') { + console.log(`Extracting **${filename}**...`); + const zip = new AdmZip(outputPath); + zip.extractAllTo(MODEL_DIR, true); + console.log(`Successfully extracted **${filename}** to **${MODEL_DIR}**`); + fs.unlinkSync(outputPath); + console.log(`Deleted zip file **${outputPath}**`); } + + cache.put(cacheKey); + } catch (err) { + console.error(`Model download failed for ${repo}/${filename} after retries:`, err.message); } - console.log('TFLite download process finished.'); } downloadModels().catch(err => { diff --git a/resources/transformers-js/package-lock.json b/resources/transformers-js/package-lock.json index 64a262d..176b35b 100644 --- a/resources/transformers-js/package-lock.json +++ b/resources/transformers-js/package-lock.json @@ -876,6 +876,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -908,6 +909,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1026,6 +1028,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3039,6 +3042,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -3087,6 +3091,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", diff --git a/resources/transformers-js/src/download-models.mjs b/resources/transformers-js/src/download-models.mjs index 3d84c31..f427ea7 100644 --- a/resources/transformers-js/src/download-models.mjs +++ b/resources/transformers-js/src/download-models.mjs @@ -82,112 +82,31 @@ async function downloadModels() { env.allowRemoteModels = true; try { - // Download models that work with pipeline - for (const modelInfo of MODELS_TO_DOWNLOAD) { - const { id: modelId, task: modelTask, dtype: modelDType } = modelInfo; - - const cacheKey = `${modelId}-${modelTask}-${modelDType}`; - if (cache.has(cacheKey)) { - console.log(`Model ${modelId} (${modelTask}, dtype: ${modelDType}) already cached. Skipping.`); - continue; - } - - console.log(`Downloading files for ${modelId} (${modelTask}, dtype: ${modelDType})...`); - - await retry(() => pipeline( - modelTask, - modelId, - { - cache_dir: env.localModelPath, - dtype: modelDType - })); - - console.log(`Successfully downloaded and cached ${modelId}`); - cache.put(cacheKey); - } - - // Download Xenova/mobileclip_s0 models via components - console.log(`Checking Xenova/mobileclip_s0 models...`); - for (const modelInfo of MOBILECLIP_MODELS_TO_DOWNLOAD) { - const className = modelInfo.modelClass.name; - const cacheKey = `mobileclip-${className}-${modelInfo.dtype || ''}`; - if (cache.has(cacheKey)) { - console.log(`Model ${className} (dtype: ${modelInfo.dtype}) already cached. Skipping.`); - continue; - } - - console.log(`Downloading Xenova/mobileclip_s0 (${className}${modelInfo.dtype ? `, dtype: ${modelInfo.dtype}` : ''})...`); - await retry(() => modelInfo.modelClass.from_pretrained("Xenova/mobileclip_s0", { - cache_dir: env.localModelPath, - dtype: modelInfo.dtype - })); - - cache.put(cacheKey); - } - console.log(`Successfully checked Xenova/mobileclip_s0`); - - // Download Xenova/sam-vit-base models - console.log(`Checking Xenova/sam-vit-base models...`); - if (!cache.has('SAM-SamModel-fp32')) { - console.log(`Downloading Xenova/sam-vit-base (SamModel, fp32)...`); - await retry(() => SamModel.from_pretrained("Xenova/sam-vit-base", { cache_dir: env.localModelPath, dtype: 'fp32' })); - cache.put('SAM-SamModel-fp32'); - } - if (!cache.has('SAM-SamProcessor-default')) { - console.log(`Downloading Xenova/sam-vit-base (SamProcessor)...`); - await retry(() => SamProcessor.from_pretrained("Xenova/sam-vit-base", { cache_dir: env.localModelPath })); - cache.put('SAM-SamProcessor-default'); - } - console.log(`Successfully checked Xenova/sam-vit-base`); - - // Download onnx-community/Kokoro-82M-v1.0-ONNX model - console.log(`Starting manual download check for ${KOKORO_REPO}...`); const kokoroModelPath = path.join(MODEL_DIR, KOKORO_REPO); if (!fs.existsSync(kokoroModelPath)) { fs.mkdirSync(kokoroModelPath, { recursive: true }); } + const onnxDir = path.join(kokoroModelPath, 'onnx'); + if (!fs.existsSync(onnxDir)) { + fs.mkdirSync(onnxDir, { recursive: true }); + } - for (const filename of KOKORO_FILES) { - const cacheKey = `${KOKORO_REPO}-${filename}`; - if (cache.has(cacheKey)) { - console.log(` ${filename} already exists, skipping.`); - continue; - } - const isOnnxFile = filename.endsWith('.onnx') || filename.endsWith('.onnx_data'); - const modelUrl = getHuggingFaceUrl(KOKORO_REPO, filename); - let outputPath; - - if (isOnnxFile) { - const onnxDir = path.join(kokoroModelPath, 'onnx'); - if (!fs.existsSync(onnxDir)) { - fs.mkdirSync(onnxDir, { recursive: true }); - } - outputPath = path.join(onnxDir, filename); - } else { - outputPath = path.join(kokoroModelPath, filename); - } + console.log(`Downloading all Transformers.js models in parallel...`); + await Promise.all([ + // Download models that work with pipeline + ...MODELS_TO_DOWNLOAD.map(modelInfo => downloadPipelineModel(modelInfo, cache)), - console.log(` Downloading ${filename}...`); - try { - await retry(async () => { - const response = await fetch(modelUrl); - if (!response.ok) { - throw new Error(`Failed to fetch ${filename}: ${response.statusText}`); - } - const fileStream = fs.createWriteStream(outputPath); - await new Promise((resolve, reject) => { - response.body.pipe(fileStream); - response.body.on('error', reject); - fileStream.on('finish', resolve); - }); - }); - - cache.put(cacheKey); - } catch (err) { - console.error(` Failed to download ${filename} after retries:`, err.message); - } - } - console.log(`Successfully checked all files for ${KOKORO_REPO}`); + // Download Xenova/mobileclip_s0 models via components + ...MOBILECLIP_MODELS_TO_DOWNLOAD.map(modelInfo => downloadMobileClipModel(modelInfo, cache)), + + // Download Xenova/sam-vit-base models + downloadSamModel(cache), + downloadSamProcessor(cache), + + // Download onnx-community/Kokoro-82M-v1.0-ONNX model + ...KOKORO_FILES.map(filename => downloadKokoroFile(filename, cache, kokoroModelPath, onnxDir)) + ]); + console.log(`Successfully checked and downloaded all models.`); } catch (err) { console.error("Model download failed:", err); @@ -197,6 +116,93 @@ async function downloadModels() { env.allowRemoteModels = originalAllowRemote; } +async function downloadPipelineModel(modelInfo, cache) { + const { id: modelId, task: modelTask, dtype: modelDType } = modelInfo; + + const cacheKey = `${modelId}-${modelTask}-${modelDType}`; + if (cache.has(cacheKey)) { + console.log(`Model ${modelId} (${modelTask}, dtype: ${modelDType}) already cached. Skipping.`); + return; + } + + console.log(`Downloading files for ${modelId} (${modelTask}, dtype: ${modelDType})...`); + + await retry(() => pipeline( + modelTask, + modelId, + { + cache_dir: env.localModelPath, + dtype: modelDType + })); + + console.log(`Successfully downloaded and cached ${modelId}`); + cache.put(cacheKey); +} + +async function downloadMobileClipModel(modelInfo, cache) { + const className = modelInfo.modelClass.name; + const cacheKey = `mobileclip-${className}-${modelInfo.dtype || ''}`; + if (cache.has(cacheKey)) { + console.log(`Model ${className} (dtype: ${modelInfo.dtype}) already cached. Skipping.`); + return; + } + + console.log(`Downloading Xenova/mobileclip_s0 (${className}${modelInfo.dtype ? `, dtype: ${modelInfo.dtype}` : ''})...`); + await retry(() => modelInfo.modelClass.from_pretrained("Xenova/mobileclip_s0", { + cache_dir: env.localModelPath, + dtype: modelInfo.dtype + })); + + cache.put(cacheKey); +} + +async function downloadSamModel(cache) { + if (!cache.has('SAM-SamModel-fp32')) { + console.log(`Downloading Xenova/sam-vit-base (SamModel, fp32)...`); + await retry(() => SamModel.from_pretrained("Xenova/sam-vit-base", { cache_dir: env.localModelPath, dtype: 'fp32' })); + cache.put('SAM-SamModel-fp32'); + } +} + +async function downloadSamProcessor(cache) { + if (!cache.has('SAM-SamProcessor-default')) { + console.log(`Downloading Xenova/sam-vit-base (SamProcessor)...`); + await retry(() => SamProcessor.from_pretrained("Xenova/sam-vit-base", { cache_dir: env.localModelPath })); + cache.put('SAM-SamProcessor-default'); + } +} + +async function downloadKokoroFile(filename, cache, kokoroModelPath, onnxDir) { + const cacheKey = `${KOKORO_REPO}-${filename}`; + if (cache.has(cacheKey)) { + console.log(` ${filename} already exists, skipping.`); + return; + } + const isOnnxFile = filename.endsWith('.onnx') || filename.endsWith('.onnx_data'); + const modelUrl = getHuggingFaceUrl(KOKORO_REPO, filename); + let outputPath = isOnnxFile ? path.join(onnxDir, filename) : path.join(kokoroModelPath, filename); + + console.log(` Downloading ${filename}...`); + try { + await retry(async () => { + const response = await fetch(modelUrl); + if (!response.ok) { + throw new Error(`Failed to fetch ${filename}: ${response.statusText}`); + } + const fileStream = fs.createWriteStream(outputPath); + await new Promise((resolve, reject) => { + response.body.pipe(fileStream); + response.body.on('error', reject); + fileStream.on('finish', resolve); + }); + }); + + cache.put(cacheKey); + } catch (err) { + console.error(` Failed to download ${filename}:`, err.message); + } +} + downloadModels().catch(err => { console.error("Download process terminated.", err); process.exit(1); diff --git a/script/clean.mjs b/script/clean.mjs new file mode 100644 index 0000000..38ba8cd --- /dev/null +++ b/script/clean.mjs @@ -0,0 +1,45 @@ +// Copyright 2026 Google LLC +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +import {defaultSuites} from "../resources/default-tests.mjs"; +import {logInfo} from "./helper.mjs"; +import fs from "node:fs"; +import path from "node:path"; + +const workloadDirs = new Set(); + +for (const suite of defaultSuites) { + const parts = suite.url.split("/"); + const workloadDir = parts.slice(0, parts.indexOf("dist")).join("/"); + workloadDirs.add(workloadDir); +} + +logInfo(`CLEANING ${workloadDirs.size} WORKLOADS`); + +for (const workloadDir of workloadDirs) { + const modelsDir = path.join(workloadDir, "models"); + const distDir = path.join(workloadDir, "dist"); + + if (fs.existsSync(distDir)) { + logInfo(` Removing ${distDir}...`); + fs.rmSync(distDir, { recursive: true, force: true }); + } + + if (fs.existsSync(modelsDir)) { + logInfo(` Cleaning ${modelsDir}...`); + for (const item of fs.readdirSync(modelsDir)) { + if (item === "gemma") { + logInfo(` Preserving ${path.join(modelsDir, item)}...`); + continue; + } + const itemPath = path.join(modelsDir, item); + logInfo(` Removing ${itemPath}...`); + fs.rmSync(itemPath, { recursive: true, force: true }); + } + } +} + +logInfo("Clean successfully completed.");