From bf23cb23a4ab49443e07ba000104981714b03c81 Mon Sep 17 00:00:00 2001 From: Freddy Martinez Garcia Date: Tue, 10 Feb 2026 08:21:44 -0300 Subject: [PATCH 1/4] Fix/whisper silence hallucination (#160) * Refactor `WhisperModel` to remove unnecessary `state_` handling and streamline `whisper_full` usage. Update workflows for cross-platform compatibility and bump package version to 0.3.18. * Add v0.3.18 release notes detailing API updates, Windows CI/CD enhancements, prebuild package renaming, and bug fixes * Update workflow for Whispercpp integration tests: rename workflow, tweak inputs/env variables, and remove redundant comments * Refactor `WhisperModel` to streamline formatting of function calls and simplify method implementation. * Simplify integration test workflow by unifying Unix/Windows steps, updating OS matrix, and removing redundant configurations. * Enhance integration test workflow: add Windows-specific steps, configure scoped registries, improve prebuild handling, and update test execution logic. * Update integration test workflow: adjust OS matrix, add `working-directory` to steps, and refine platform-specific configurations. * Update integration test workflow: specify test file paths under `tests/` directory for Android and iOS configurations. --- ...-mobile-test-qvac-lib-infer-whispercpp.yml | 4 +- ...gration-test-qvac-lib-infer-whispercpp.yml | 86 ++++++++++++++++--- .../whisper.cpp/WhisperModel.cpp | 23 +---- .../whisper.cpp/WhisperModel.hpp | 9 -- .../qvac-lib-infer-whispercpp/package.json | 2 +- .../release-notes/v0.3.18.md | 25 ++++++ 6 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 packages/qvac-lib-infer-whispercpp/release-notes/v0.3.18.md diff --git a/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml b/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml index 446d1c9ca4..da029fcdbe 100644 --- a/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml @@ -931,7 +931,7 @@ jobs: BUNDLE_ID="${{ env.APP_BUNDLE_ID }}" # Android wdio config with crash detection (bail:0 = continue on test failures, crash = process.exit) # Timeout set to 15 minutes (900000ms) for audio transcription tests (whisper models can be slow) - WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["*.spec.js","*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"Android","appium:automationName":"UiAutomator2","appium:appPackage":"'${{ env.APP_BUNDLE_ID }}'","appium:appActivity":"'${{ env.APP_BUNDLE_ID }}'.MainActivity","appium:newCommandTimeout":300,"appium:autoGrantPermissions":true,"appium:autoAcceptAlerts":true,"appium:noReset":true,"appium:dontStopAppOnReset":true,"appium:forceAppLaunch":false}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("android=new UiSelector().textContains(\\"INITIALIZED\\")");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("android=new UiSelector().textContains(\\"Run Automated Tests\\")");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' + WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["tests/*.spec.js","tests/*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"Android","appium:automationName":"UiAutomator2","appium:appPackage":"'${{ env.APP_BUNDLE_ID }}'","appium:appActivity":"'${{ env.APP_BUNDLE_ID }}'.MainActivity","appium:newCommandTimeout":300,"appium:autoGrantPermissions":true,"appium:autoAcceptAlerts":true,"appium:noReset":true,"appium:dontStopAppOnReset":true,"appium:forceAppLaunch":false}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("android=new UiSelector().textContains(\\"INITIALIZED\\")");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("android=new UiSelector().textContains(\\"Run Automated Tests\\")");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' else PLATFORM="iOS" AUTOMATION="XCUITest" @@ -941,7 +941,7 @@ jobs: # iOS wdio config with crash detection (bail:0 = continue on test failures, crash = process.exit) # usePrebuiltWDA uses Device Farm's pre-built WebDriverAgent # Timeout set to 15 minutes (900000ms) for audio transcription tests (whisper models can be slow) - WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["*.spec.js","*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"iOS","appium:automationName":"XCUITest","appium:bundleId":"'${{ env.APP_BUNDLE_ID }}'","appium:newCommandTimeout":300,"appium:noReset":true,"appium:forceAppLaunch":false,"appium:usePrebuiltWDA":true,"appium:wdaLocalPort":8100,"appium:showIOSLog":true,"appium:realDeviceLogger":"/usr/local/lib/node_modules/appium/node_modules/deviceconsole/deviceconsole"}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("-ios predicate string:label CONTAINS \\"INITIALIZED\\"");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("-ios predicate string:label CONTAINS \\"Run Automated Tests\\"");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' + WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["tests/*.spec.js","tests/*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"iOS","appium:automationName":"XCUITest","appium:bundleId":"'${{ env.APP_BUNDLE_ID }}'","appium:newCommandTimeout":300,"appium:noReset":true,"appium:forceAppLaunch":false,"appium:usePrebuiltWDA":true,"appium:wdaLocalPort":8100,"appium:showIOSLog":true,"appium:realDeviceLogger":"/usr/local/lib/node_modules/appium/node_modules/deviceconsole/deviceconsole"}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("-ios predicate string:label CONTAINS \\"INITIALIZED\\"");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("-ios predicate string:label CONTAINS \\"Run Automated Tests\\"");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' fi # Base64 encode the wdio config to safely embed in YAML diff --git a/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml b/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml index 7956b4d8a9..5dbf9f81d4 100644 --- a/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml @@ -1,4 +1,4 @@ -name: Integration Tests (Whispercpp) +name: Run Integration Tests on: workflow_dispatch: @@ -46,7 +46,7 @@ jobs: - os: macos-14-xlarge platform: darwin arch: arm64 - - os: windows-2022 + - os: ai-run-windows-gpu platform: win32 arch: x64 @@ -68,7 +68,8 @@ jobs: repository: ${{ inputs.repository || github.repository }} token: ${{ secrets.PAT_TOKEN }} - - name: Configure scoped registry for @tetherto and @qvac packages + - name: Configure scoped registry for @tetherto and @qvac packages (Unix) + if: ${{ matrix.platform != 'win32' }} working-directory: ${{ env.WORKDIR }} env: GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -80,6 +81,7 @@ jobs: set -eu echo "Writing .npmrc for dual registry install…" cat > .npmrc <$null + continue-on-error: true \ No newline at end of file diff --git a/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.cpp b/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.cpp index b818619c4a..72a2b3a371 100644 --- a/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.cpp +++ b/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.cpp @@ -68,13 +68,6 @@ void WhisperModel::load() { throw std::runtime_error("Failed to initialize Whisper context"); } - state_.reset(whisper_init_state(ctx_.get())); - if (state_ == nullptr) { - QLOG( - qvac_lib_inference_addon_cpp::logger::Priority::ERROR, - "Failed to initialize Whisper state"); - throw std::runtime_error("Failed to initialize Whisper state"); - } is_loaded_ = true; QLOG( qvac_lib_inference_addon_cpp::logger::Priority::INFO, @@ -238,9 +231,8 @@ void WhisperModel::warmup() { params.new_segment_callback_user_data = nullptr; // Run warmup inference to "heat up" the model - whisper_full_with_state( + whisper_full( ctx_.get(), - state_.get(), params, silentAudio.data(), static_cast(silentAudio.size())); @@ -282,12 +274,8 @@ void WhisperModel::process(const Input& input) { params.new_segment_callback = onNewSegment; params.new_segment_callback_user_data = &ud; - int result = whisper_full_with_state( - ctx_.get(), - state_.get(), - params, - input.data(), - static_cast(input.size())); + int result = whisper_full( + ctx_.get(), params, input.data(), static_cast(input.size())); const auto t1 = std::chrono::steady_clock::now(); totalWallMs_ += std::chrono::duration(t1 - t0).count(); @@ -371,10 +359,7 @@ bool WhisperModel::configContextIsChanged( return false; } -void WhisperModel::resetContext() { - ctx_.reset(); - state_.reset(); -} +void WhisperModel::resetContext() { ctx_.reset(); } void WhisperModel::setConfig(const WhisperConfig& config) { bool contextChanged = configContextIsChanged(cfg_, config); diff --git a/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.hpp b/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.hpp index 8a66d28785..9c5eec426b 100644 --- a/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.hpp +++ b/packages/qvac-lib-infer-whispercpp/addon/src/model-interface/whisper.cpp/WhisperModel.hpp @@ -121,16 +121,7 @@ class WhisperModel { } }; - struct WhisperStateDeleter { - void operator()(whisper_state* state) const noexcept { - if (state != nullptr) { - whisper_free_state(state); - } - } - }; - std::unique_ptr ctx_{nullptr}; - std::unique_ptr state_{nullptr}; bool stream_ended_ = false; bool is_loaded_ = false; bool is_warmed_up_ = false; diff --git a/packages/qvac-lib-infer-whispercpp/package.json b/packages/qvac-lib-infer-whispercpp/package.json index 69c6c15e2c..3041a2fd0d 100644 --- a/packages/qvac-lib-infer-whispercpp/package.json +++ b/packages/qvac-lib-infer-whispercpp/package.json @@ -1,6 +1,6 @@ { "name": "@qvac/transcription-whispercpp", - "version": "0.3.17", + "version": "0.3.18", "description": "transcription addon for qvac", "addon": true, "engines": { diff --git a/packages/qvac-lib-infer-whispercpp/release-notes/v0.3.18.md b/packages/qvac-lib-infer-whispercpp/release-notes/v0.3.18.md new file mode 100644 index 0000000000..77e98d18f6 --- /dev/null +++ b/packages/qvac-lib-infer-whispercpp/release-notes/v0.3.18.md @@ -0,0 +1,25 @@ +# QVAC Transcription Whisper Addon v0.3.18 Release Notes + +This release updates the whisper.cpp integration to use the latest API and improves Windows platform support for CI/CD workflows. + +## Features + +### Enhanced Windows Platform Support + +The integration test workflow now includes comprehensive Windows support with PowerShell-specific configurations. This includes separate registry configuration steps for Unix and Windows platforms, ensuring proper package authentication across all environments. The workflow now uses the `ai-run-windows-gpu` runner for better GPU-accelerated testing on Windows. + +### Prebuild Package Renaming Support + +Added automatic renaming of prebuild packages from `tetherto__*` to `qvac__*` format during the package download process. This ensures consistency across all platforms and simplifies the build artifact management. + +## Bug Fixes + +### Whisper.cpp API Compatibility + +Updated the integration with whisper.cpp to use the new 4-parameter API for `whisper_full()`. The previous 5-parameter version that included an explicit state parameter has been deprecated. The state is now managed internally by the whisper.cpp context, simplifying the code and removing unnecessary state management overhead. + +Removed the obsolete `whisper_state` member variable and related initialization code, as the new whisper.cpp API handles state management automatically through the context object. + +## Other + +Updated the integration test workflow to use a specific version of `bare@1.26.0` for better build consistency. Added the `always-auth=true` configuration for npm registry authentication to improve reliability when downloading private packages. From 2191c8f7c67c24c92e65906517c1afb5c7022bb4 Mon Sep 17 00:00:00 2001 From: GustavoA1604 <54457676+GustavoA1604@users.noreply.github.com> Date: Tue, 10 Feb 2026 09:02:39 -0300 Subject: [PATCH 2/4] Merge main into fix/whispercpp-vad (#201) * update prebuild download location for addon workflows * Use S3 cache for Vulkan SDK on Linux arm64 in prebuilds (#200) Reuse the same S3 Vulkan SDK cache (bucket tether-ai-dev) in prebuilds-qvac-lib-infer-whispercpp and prebuilds-qvac-lib-infer-nmtcpp. Replaces the previous GitHub Actions cache in nmtcpp and adds S3 download-or-build-and-upload in whispercpp so arm64 builds match the LLM/embed workflows. --------- Co-authored-by: Oluwaseun Ismaila Co-authored-by: Juan Pablo Garibotti Arias --- .gitattributes | 1 + ...on-merge-qvac-lib-infer-llamacpp-embed.yml | 3 ++ .../on-merge-qvac-lib-infer-llamacpp-llm.yml | 3 ++ .../on-merge-qvac-lib-infer-whispercpp.yml | 3 ++ ...rebuilds-qvac-lib-infer-llamacpp-embed.yml | 5 +- .../prebuilds-qvac-lib-infer-llamacpp-llm.yml | 8 ++-- .../prebuilds-qvac-lib-infer-nmtcpp.yml | 38 +++++++++++---- .../prebuilds-qvac-lib-infer-whispercpp.yml | 46 +++++++++++++++---- 8 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..94f480de94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/workflows/on-merge-qvac-lib-infer-llamacpp-embed.yml b/.github/workflows/on-merge-qvac-lib-infer-llamacpp-embed.yml index 694bde74a1..a49139ea65 100644 --- a/.github/workflows/on-merge-qvac-lib-infer-llamacpp-embed.yml +++ b/.github/workflows/on-merge-qvac-lib-infer-llamacpp-embed.yml @@ -84,6 +84,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-embed" publish-feature-gpr: needs: publish-logic @@ -97,6 +98,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-embed" publish-tmp-gpr: needs: publish-logic @@ -110,6 +112,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-embed" publish-release-npm: needs: publish-logic diff --git a/.github/workflows/on-merge-qvac-lib-infer-llamacpp-llm.yml b/.github/workflows/on-merge-qvac-lib-infer-llamacpp-llm.yml index 2f388eea1e..0572cc5053 100644 --- a/.github/workflows/on-merge-qvac-lib-infer-llamacpp-llm.yml +++ b/.github/workflows/on-merge-qvac-lib-infer-llamacpp-llm.yml @@ -92,6 +92,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-llm" publish-feature-gpr: needs: publish-logic @@ -105,6 +106,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-llm" publish-tmp-gpr: needs: publish-logic @@ -118,6 +120,7 @@ jobs: with: tag: ${{ inputs.tag || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-llamacpp-llm" publish-release-npm: needs: publish-logic diff --git a/.github/workflows/on-merge-qvac-lib-infer-whispercpp.yml b/.github/workflows/on-merge-qvac-lib-infer-whispercpp.yml index 3f4e7d0bf6..d81ef38819 100644 --- a/.github/workflows/on-merge-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/on-merge-qvac-lib-infer-whispercpp.yml @@ -91,6 +91,7 @@ jobs: # workflow_dispatch -> github.event.inputs.tag is set; push -> fallback to computed gpr_tag tag: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.tag) || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-whispercpp" publish-feature-gpr: needs: publish-logic @@ -104,6 +105,7 @@ jobs: with: tag: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.tag) || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-whispercpp" publish-tmp-gpr: needs: publish-logic @@ -117,6 +119,7 @@ jobs: with: tag: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.tag) || needs.publish-logic.outputs.gpr_tag }} publish_target: "gpr" + workdir: "packages/qvac-lib-infer-whispercpp" publish-release-npm: needs: publish-logic diff --git a/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-embed.yml b/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-embed.yml index 5bc04c6832..8b5a3adee2 100644 --- a/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-embed.yml +++ b/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-embed.yml @@ -468,7 +468,7 @@ jobs: uses: actions/download-artifact@v4 with: name: prebuilds - path: prebuilds + path: ${{ env.WORKDIR }}/prebuilds - name: Publish to GitHub Package Registry id: publish @@ -510,7 +510,7 @@ jobs: uses: actions/download-artifact@v4 with: name: prebuilds - path: prebuilds + path: ${{ env.WORKDIR }}/prebuilds - name: Publish to NPM Package Registry id: publish @@ -518,3 +518,4 @@ jobs: with: secret-token: ${{ secrets.NPM_TOKEN }} tag: "latest" + workdir: ${{ env.WORKDIR }} diff --git a/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-llm.yml b/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-llm.yml index 63d4792e06..bdfdd651c4 100644 --- a/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-llm.yml +++ b/.github/workflows/prebuilds-qvac-lib-infer-llamacpp-llm.yml @@ -495,7 +495,7 @@ jobs: uses: actions/download-artifact@v4 with: name: prebuilds - path: prebuilds + path: ${{ env.WORKDIR }}/prebuilds - name: Scope package as @tetherto for GPR shell: bash @@ -549,10 +549,11 @@ jobs: uses: actions/download-artifact@v4 with: name: prebuilds - path: prebuilds + path: ${{ env.WORKDIR }}/prebuilds # Re-assert scope for public npm (fresh checkout) - name: Scope package as @qvac for npm + working-directory: ${{ env.WORKDIR }} shell: bash run: | set -euo pipefail @@ -564,4 +565,5 @@ jobs: uses: tetherto/qvac-devops/.github/actions/publish-library-to-npm@monorepo_update with: secret-token: ${{ secrets.NPM_TOKEN }} - tag: 'latest' \ No newline at end of file + tag: 'latest' + workdir: ${{ env.WORKDIR }} \ No newline at end of file diff --git a/.github/workflows/prebuilds-qvac-lib-infer-nmtcpp.yml b/.github/workflows/prebuilds-qvac-lib-infer-nmtcpp.yml index 038f7a7157..8001c266f0 100644 --- a/.github/workflows/prebuilds-qvac-lib-infer-nmtcpp.yml +++ b/.github/workflows/prebuilds-qvac-lib-infer-nmtcpp.yml @@ -292,15 +292,11 @@ jobs: sudo apt install -y vulkan-sdk - if: ${{ matrix.os == 'ubuntu-24.04-arm64-private' }} - name: Cache Vulkan SDK for linux arm64 - id: cache-vulkan-arm64 - uses: actions/cache@v4 - with: - path: ~/vulkan - key: vulkan-sdk-linux-arm64-v1 - - - if: ${{ matrix.os == 'ubuntu-24.04-arm64-private' && steps.cache-vulkan-arm64.outputs.cache-hit != 'true' }} - name: Build Vulkan SDK for linux arm64 + name: Build Vulkan SDK for linux arm64 (with S3 cache) + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 run: | sudo apt install -y xz-utils wget -q -O /tmp/vulkansdk.tar.xz https://sdk.lunarg.com/sdk/download/latest/linux/vulkan_sdk.tar.xz @@ -308,7 +304,29 @@ jobs: mkdir -p vulkan cd vulkan tar xf /tmp/vulkansdk.tar.xz --strip-components=1 - ./vulkansdk --maxjobs + + # Extract SDK major.minor version from README.txt (e.g., "1.4" from "1.4.341.0") + SDK_VERSION=$(grep -o 'sdk/[0-9]*\.[0-9]*' README.txt | head -1 | sed 's|sdk/||') + S3_BUCKET="tether-ai-dev" + S3_KEY="vulkan-sdk-cache/linux-arm64-${SDK_VERSION}.tar.gz" + + echo "Vulkan SDK version: ${SDK_VERSION}" + + # Try to download cached build from S3 + if aws s3 cp "s3://${S3_BUCKET}/${S3_KEY}" /tmp/vulkan-arm64-cache.tar.gz 2>/dev/null; then + echo "Found cached Vulkan SDK, extracting..." + tar xzf /tmp/vulkan-arm64-cache.tar.gz -C ~/vulkan + rm /tmp/vulkan-arm64-cache.tar.gz + else + echo "No cache found, building Vulkan SDK for ARM64..." + ./vulkansdk --maxjobs + + # Upload the compiled SDK to S3 for future runs + echo "Uploading compiled SDK to S3..." + tar czf /tmp/vulkan-arm64-cache.tar.gz aarch64 + aws s3 cp /tmp/vulkan-arm64-cache.tar.gz "s3://${S3_BUCKET}/${S3_KEY}" + rm /tmp/vulkan-arm64-cache.tar.gz + fi - if: ${{ matrix.os == 'ubuntu-24.04-arm64-private' }} name: Setup Vulkan SDK environment for linux arm64 diff --git a/.github/workflows/prebuilds-qvac-lib-infer-whispercpp.yml b/.github/workflows/prebuilds-qvac-lib-infer-whispercpp.yml index 2803cb768d..96eee4bd46 100644 --- a/.github/workflows/prebuilds-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/prebuilds-qvac-lib-infer-whispercpp.yml @@ -323,11 +323,38 @@ jobs: - if: ${{ matrix.platform == 'linux' && matrix.arch == 'arm64' }} name: Setup vulkan sdk path in linux arm64 + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 run: | VULKAN_SDK=~/vulkan/aarch64 echo "VULKAN_SDK=$VULKAN_SDK" >> $GITHUB_ENV + cd ~/vulkan - ./vulkansdk --maxjobs + + # Extract SDK major.minor version from README.txt (e.g., "1.4" from "1.4.341.0") + SDK_VERSION=$(grep -o 'sdk/[0-9]*\.[0-9]*' README.txt | head -1 | sed 's|sdk/||') + S3_BUCKET="tether-ai-dev" + S3_KEY="vulkan-sdk-cache/linux-arm64-${SDK_VERSION}.tar.gz" + + echo "Vulkan SDK version: ${SDK_VERSION}" + + # Try to download cached build from S3 + if aws s3 cp "s3://${S3_BUCKET}/${S3_KEY}" /tmp/vulkan-arm64-cache.tar.gz 2>/dev/null; then + echo "Found cached Vulkan SDK, extracting..." + tar xzf /tmp/vulkan-arm64-cache.tar.gz -C ~/vulkan + rm /tmp/vulkan-arm64-cache.tar.gz + else + echo "No cache found, building Vulkan SDK for ARM64..." + ./vulkansdk --maxjobs + + # Upload the compiled SDK to S3 for future runs + echo "Uploading compiled SDK to S3..." + tar czf /tmp/vulkan-arm64-cache.tar.gz aarch64 + aws s3 cp /tmp/vulkan-arm64-cache.tar.gz "s3://${S3_BUCKET}/${S3_KEY}" + rm /tmp/vulkan-arm64-cache.tar.gz + fi - if: ${{ matrix.platform == 'linux' }} name: Export rest of vulkan sdk variables in linux @@ -462,15 +489,7 @@ jobs: uses: actions/download-artifact@v4 with: name: prebuilds - path: /tmp/prebuilds - - - name: Place merged prebuilds into package workdir - shell: bash - run: | - set -euo pipefail - mkdir -p "${WORKDIR}/prebuilds" - rm -rf "${WORKDIR}/prebuilds"/* - cp -R /tmp/prebuilds/* "${WORKDIR}/prebuilds/" + path: ${WORKDIR}/prebuilds - name: Rename prebuild files from qvac_ to tetherto_ prefix shell: bash @@ -545,12 +564,19 @@ jobs: token: ${{ secrets.PAT_TOKEN }} fetch-depth: 0 + - name: Download prebuild artifacts + uses: actions/download-artifact@v4 + with: + name: prebuilds + path: ${WORKDIR}/prebuilds + - name: Publish to NPM Package Registry id: publish uses: tetherto/qvac-devops/.github/actions/publish-library-to-npm@monorepo_update with: secret-token: ${{ secrets.NPM_TOKEN }} tag: "latest" + workdir: $((WORKDIR)) - name: Capture published version id: capture_version From 65d03357475a2d726db27aae329cf7ae56ad609f Mon Sep 17 00:00:00 2001 From: Gustavo Araujo Date: Tue, 10 Feb 2026 10:50:24 -0300 Subject: [PATCH 3/4] Fix integration tests name in whispercpp --- .../workflows/integration-test-qvac-lib-infer-whispercpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml b/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml index 5dbf9f81d4..ba99ed6ee0 100644 --- a/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/integration-test-qvac-lib-infer-whispercpp.yml @@ -1,4 +1,4 @@ -name: Run Integration Tests +name: Integration Tests (Whispercpp) on: workflow_dispatch: From 3004b8076f369fb79071e4203f833831ef0a6aa2 Mon Sep 17 00:00:00 2001 From: Gustavo Araujo Date: Tue, 10 Feb 2026 10:52:44 -0300 Subject: [PATCH 4/4] Revert wrong change in mobile tests for whispercpp --- .../integration-mobile-test-qvac-lib-infer-whispercpp.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml b/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml index da029fcdbe..446d1c9ca4 100644 --- a/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml +++ b/.github/workflows/integration-mobile-test-qvac-lib-infer-whispercpp.yml @@ -931,7 +931,7 @@ jobs: BUNDLE_ID="${{ env.APP_BUNDLE_ID }}" # Android wdio config with crash detection (bail:0 = continue on test failures, crash = process.exit) # Timeout set to 15 minutes (900000ms) for audio transcription tests (whisper models can be slow) - WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["tests/*.spec.js","tests/*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"Android","appium:automationName":"UiAutomator2","appium:appPackage":"'${{ env.APP_BUNDLE_ID }}'","appium:appActivity":"'${{ env.APP_BUNDLE_ID }}'.MainActivity","appium:newCommandTimeout":300,"appium:autoGrantPermissions":true,"appium:autoAcceptAlerts":true,"appium:noReset":true,"appium:dontStopAppOnReset":true,"appium:forceAppLaunch":false}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("android=new UiSelector().textContains(\\"INITIALIZED\\")");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("android=new UiSelector().textContains(\\"Run Automated Tests\\")");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' + WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["*.spec.js","*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"Android","appium:automationName":"UiAutomator2","appium:appPackage":"'${{ env.APP_BUNDLE_ID }}'","appium:appActivity":"'${{ env.APP_BUNDLE_ID }}'.MainActivity","appium:newCommandTimeout":300,"appium:autoGrantPermissions":true,"appium:autoAcceptAlerts":true,"appium:noReset":true,"appium:dontStopAppOnReset":true,"appium:forceAppLaunch":false}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("android=new UiSelector().textContains(\\"INITIALIZED\\")");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("android=new UiSelector().textContains(\\"Run Automated Tests\\")");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' else PLATFORM="iOS" AUTOMATION="XCUITest" @@ -941,7 +941,7 @@ jobs: # iOS wdio config with crash detection (bail:0 = continue on test failures, crash = process.exit) # usePrebuiltWDA uses Device Farm's pre-built WebDriverAgent # Timeout set to 15 minutes (900000ms) for audio transcription tests (whisper models can be slow) - WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["tests/*.spec.js","tests/*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"iOS","appium:automationName":"XCUITest","appium:bundleId":"'${{ env.APP_BUNDLE_ID }}'","appium:newCommandTimeout":300,"appium:noReset":true,"appium:forceAppLaunch":false,"appium:usePrebuiltWDA":true,"appium:wdaLocalPort":8100,"appium:showIOSLog":true,"appium:realDeviceLogger":"/usr/local/lib/node_modules/appium/node_modules/deviceconsole/deviceconsole"}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("-ios predicate string:label CONTAINS \\"INITIALIZED\\"");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("-ios predicate string:label CONTAINS \\"Run Automated Tests\\"");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' + WDIO_CONFIG='exports.config={runner:"local",hostname:"127.0.0.1",port:4723,path:"/wd/hub",specs:["*.spec.js","*.test.js"],maxInstances:1,bail:0,capabilities:[{platformName:"iOS","appium:automationName":"XCUITest","appium:bundleId":"'${{ env.APP_BUNDLE_ID }}'","appium:newCommandTimeout":300,"appium:noReset":true,"appium:forceAppLaunch":false,"appium:usePrebuiltWDA":true,"appium:wdaLocalPort":8100,"appium:showIOSLog":true,"appium:realDeviceLogger":"/usr/local/lib/node_modules/appium/node_modules/deviceconsole/deviceconsole"}],logLevel:"debug",waitforTimeout:120000,connectionRetryTimeout:30000,connectionRetryCount:3,services:[],framework:"mocha",reporters:["spec"],mochaOpts:{ui:"bdd",timeout:900000},before:async function(capabilities,specs,browser){const BUNDLE_ID="'${{ env.APP_BUNDLE_ID }}'";global.appCrashed=false;global.checkAppCrash=async(stage)=>{try{const state=await browser.queryAppState(BUNDLE_ID);console.log("["+stage+"] App state: "+state+" (4=foreground,3=background,1=not running)");if(state<3){console.error("\\nπŸ›‘ APP CRASHED at "+stage+"! State="+state);console.error("Check device logs for BareKit/native errors.\\n");global.appCrashed=true;process.exit(1);}return state;}catch(e){console.log("["+stage+"] queryAppState error: "+e.message);return-1;}};console.log("Checking initial app state...");await global.checkAppCrash("startup");console.log("Waiting for app to initialize...");await browser.pause(5000);await global.checkAppCrash("after-pause");const initText=await browser.$("-ios predicate string:label CONTAINS \\"INITIALIZED\\"");await initText.waitForDisplayed({timeout:60000});await global.checkAppCrash("after-init");console.log("App initialized, clicking Run Automated Tests...");const button=await browser.$("-ios predicate string:label CONTAINS \\"Run Automated Tests\\"");await button.waitForDisplayed({timeout:15000});await button.click();console.log("Button clicked!");await browser.pause(5000);await global.checkAppCrash("after-click");},afterTest:async function(test,context,{error}){if(global.appCrashed)return;await global.checkAppCrash("after-test:"+test.title);}};' fi # Base64 encode the wdio config to safely embed in YAML