Skip to content

Add C++ QNN support for Zipformer CTC models. - #2809

Merged
csukuangfj merged 6 commits into
k2-fsa:masterfrom
csukuangfj:zipformer-ctc-qnn
Nov 24, 2025
Merged

csukuangfj merged 6 commits into
k2-fsa:masterfrom
csukuangfj:zipformer-ctc-qnn

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Nov 21, 2025 •

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • QNN backend now supports Zipformer‑CTC models for offline speech recognition and exposes QNN options in model configs and Java/Kotlin APIs.
    • New offline recognizer flow for Zipformer‑CTC with QNN, enabling end‑to‑end decoding with QNN models.
  • Bug Fixes

    • Simplified QNN initialization by removing redundant runtime file-existence checks.
  • Chores

    • CI/workflow matrix and packaging scripts updated; common QNN model files added to .gitignore.

✏️ Tip: You can customize this high-level summary in your review settings.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Nov 21, 2025
@coderabbitai

coderabbitai Bot commented Nov 21, 2025 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds QNN-backed Zipformer-CTC support and wiring: new QNN model and recognizer implementations, QnnConfig plumbing into Zipformer config and JNI/Android layers, scoped ParseOptions for per-model QNN settings, removal of some file-existence checks, and Convert() linkage changed to external.

Changes

Cohort / File(s) Summary
QNN Zipformer CTC Model (new)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h, sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc
Adds OfflineZipformerCtcModelQnn: constructors (including templated manager), Run(features) inference, VocabSize(), SubsamplingFactor(), model init/validation, and thread-safe execution.
QNN Zipformer Recognizer (new)
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h
Adds OfflineRecognizerZipformerCtcQnnImpl: Init, CreateStream, DecodeStreams, DecodeStream flow using QNN model + greedy CTC decoder and forward-declared Convert() to produce OfflineRecognitionResult.
Config: Zipformer CTC
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.h, sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc
Adds QnnConfig field to OfflineZipformerCtcModelConfig; registers it under zipformer-ctc ParseOptions; Validate delegates to qnn_config for shared-lib models; ToString appends qnn_config output.
Config: SenseVoice scoping
sherpa-onnx/csrc/offline-sense-voice-model-config.cc
Registers QNN options under a local sense-voice ParseOptions scope instead of top-level options.
Recognizer creation / wiring
sherpa-onnx/csrc/offline-recognizer-impl.cc
Includes new recognizer header; when zipformer_ctc.model present (and sense_voice absent) with QNN provider, returns Zipformer CTC QNN recognizer; updates QNN error message text.
CTC Convert linkage
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h
Removes static from Convert(...), changing it to external linkage for cross-translation-unit use.
QNN config validation
sherpa-onnx/csrc/qnn-config.cc
Removes runtime FileExists checks for backend/system libs (assumes dlopen path resolution); still requires system_lib when context_binary provided but no longer checks file presence.
CMake / build
sherpa-onnx/csrc/CMakeLists.txt
Adds ./qnn/offline-zipformer-ctc-model-qnn.cc to QNN sources and alters the set/order of QNN-related source entries.
JNI / Java / Android
sherpa-onnx/jni/offline-recognizer.cc, sherpa-onnx/java-api/.../OfflineZipformerCtcModelConfig.java, sherpa-onnx/kotlin-api/OfflineRecognizer.kt, android/.../SimulateStreamingAsr.kt
Propagates QnnConfig into JNI config and Java/Kotlin APIs; Android asset-copying and safety checks adjusted for QNN context; added Java Builder/getter for Zipformer QnnConfig.
Misc / tooling
.github/workflows/apk-qnn-vad-asr-simulated-streaming.yaml, .gitignore, scripts/apk/generate-qnn-vad-asr-apk-script.py, sherpa-onnx/csrc/qnn/qnn-model.cc
CI matrix/workflow updated for QNN, .gitignore entries for QNN ZIPFORMER artifacts, script model-list changes (removed lang2 and added many zipformer entries), and minor logging cast in qnn-model.cc.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Factory as OfflineRecognizer::Create
    participant ZipRec as OfflineRecognizerZipformerCtcQnnImpl
    participant QNN as OfflineZipformerCtcModelQnn
    participant Decoder as OfflineCtcGreedySearchDecoderRknn
    participant Stream as OfflineStream

    User->>Factory: Create(config)
    alt config includes zipformer_ctc + provider == "qnn"
        Factory->>ZipRec: construct(config)
        ZipRec->>QNN: initialize model (context/lib)
        ZipRec->>Decoder: create greedy decoder
    end
    Factory-->>User: recognizer

    User->>ZipRec: CreateStream()
    ZipRec-->>User: Stream

    User->>ZipRec: DecodeStreams([Stream])
    loop per stream
        ZipRec->>Stream: read features
        ZipRec->>QNN: Run(features)
        QNN-->>ZipRec: log-probs
        ZipRec->>Decoder: decode(log-probs)
        Decoder-->>ZipRec: OfflineCtcDecoderResult
        ZipRec->>ZipRec: Convert(...) -> OfflineRecognitionResult
        ZipRec->>Stream: store result
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • New QNN model implementation: tensor name/shape validation, thread-safety and manager template specializations.
    • New recognizer: decode pipeline correctness, greedy decoder selection, and integration with Convert() now having external linkage.
    • QnnConfig validation changes: removed FileExists checks — verify packaging and dlopen expectations.
    • JNI / Android asset-copying and safety checks for QNN paths.
    • CMake additions: ensure sources compile and link order is correct across platforms.

Possibly related PRs

"I hopped through headers and CMake trees,
I padded bits of QNN with ease,
A model here, a decoder there,
I left a hopping trail of care 🥕
— Rabbit"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.13% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding C++ QNN support for Zipformer CTC models, which is consistently reflected across the substantial modifications to model implementations, configuration files, and QNN backend integration.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2722b and e55bc15.

📒 Files selected for processing (1)
  • scripts/apk/generate-qnn-vad-asr-apk-script.py (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @csukuangfj, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the sherpa-onnx project by adding comprehensive C++ QNN support for Zipformer CTC models. This integration allows for efficient, hardware-accelerated inference of these models, particularly beneficial for deployment on devices leveraging Qualcomm's NPUs. The changes involve introducing new QNN-specific model and recognizer implementations, updating build configurations, and refining model loading and validation processes to seamlessly incorporate QNN capabilities.

Highlights

  • QNN Support for Zipformer CTC Models: Introduced C++ QNN (Qualcomm Neural Processing SDK) support for Zipformer CTC models, enabling accelerated inference on compatible hardware.
  • New QNN Implementation Files: Added dedicated C++ files (offline-recognizer-zipformer-ctc-qnn-impl.h, offline-zipformer-ctc-model-qnn.h, offline-zipformer-ctc-model-qnn.cc) to encapsulate the QNN-specific logic for Zipformer CTC models.
  • Dynamic Model Loading: Updated the offline recognizer's model creation logic to dynamically instantiate the QNN-enabled Zipformer CTC implementation when QNN is active and a Zipformer CTC model is configured.
  • QNN Configuration Integration: Integrated QNN configuration options directly into the OfflineZipformerCtcModelConfig structure and its validation process.
  • Relaxed QNN Library Checks: Modified QNN configuration validation to no longer strictly check for the existence of backend and system library files, relying instead on dlopen()'s path searching capabilities.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for Zipformer CTC models with the QNN backend. The changes include adding new implementation files for the QNN recognizer and model, integrating them into the existing factory methods, and updating the build configuration. The overall approach is sound and follows the existing structure for other model types. I've identified a few areas for improvement: a potentially confusing condition in the recognizer implementation that seems to be a copy-paste artifact, a hardcoded value that should be read from configuration, and an unfriendly error message and application exit when attempting to load a QNN model from Android assets. Addressing these points will improve code clarity, correctness, and developer experience.

Comment on lines +75 to +79
Impl(Manager *mgr, const OfflineModelConfig &config) : config_(config) {
SHERPA_ONNX_LOGE(
"Please copy all files from assets to SD card and set assetManager to "
"null");
SHERPA_ONNX_EXIT(-1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The constructor for loading from a Manager (like Android's AAssetManager) immediately calls SHERPA_ONNX_EXIT(-1), which will crash the application. While not supporting loading from assets for QNN models is a valid design choice, crashing the app is not user-friendly. Furthermore, the error message is not very helpful for a developer. It should clearly state that loading from assets is not supported for QNN models and suggest using file paths instead. A more informative and less imperative error message would improve the developer experience.

  Impl(Manager *mgr, const OfflineModelConfig &config) : config_(config) {
    SHERPA_ONNX_LOGE(
        "Loading QNN models from assets is not supported. "
        "Please provide absolute paths to model files on the file system.");
    SHERPA_ONNX_EXIT(-1);
  }

Comment on lines +56 to +65
if (!symbol_table_.Contains("<blk>") &&
!symbol_table_.Contains("<eps>") &&
!symbol_table_.Contains("<blank>") &&
config_.model_config.omnilingual.model.empty()) {
// for omnilingual asr, its blank id is 0
SHERPA_ONNX_LOGE(
"We expect that tokens.txt contains "
"the symbol <blk> or <eps> or <blank> and its ID.");
SHERPA_ONNX_EXIT(-1);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The condition config_.model_config.omnilingual.model.empty() and the associated comment seem to be a copy-paste from a more generic CTC recognizer. Since this implementation is specific to Zipformer CTC models, this check is confusing and irrelevant. It would be clearer to remove the check for the omnilingual model. The logic should just verify that one of the blank symbols exists in the symbol table.

      if (!symbol_table_.Contains("<blk>") &&
          !symbol_table_.Contains("<eps>") &&
          !symbol_table_.Contains("<blank>")) {
        SHERPA_ONNX_LOGE(
            "We expect that tokens.txt contains "
            "the symbol <blk> or <eps> or <blank> and its ID.");
        SHERPA_ONNX_EXIT(-1);
      }

auto result =
decoder_->Decode(log_probs.data(), num_out_frames, vocab_size);

int32_t frame_shift_ms = 10;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The frame_shift_ms is hardcoded to 10. This might lead to incorrect timestamps if the feature configuration is changed to use a different frame shift. It's better to use the value from config_.feat_config.frame_shift_ms to ensure correctness and respect the user's configuration.

    int32_t frame_shift_ms = config_.feat_config.frame_shift_ms;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h (1)

26-29: Consider explicit forward declaration or namespace encapsulation.

Changing the Convert function from static to external linkage allows it to be used by the new QNN Zipformer CTC implementation, which is good for code reuse. However, this now exposes the function as part of the public interface without explicit declaration.

Consider adding a forward declaration in a common header if this function is intended to be part of the public API, or document that it's an internal utility shared across implementation units.

sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (1)

7-10: Include <cstdint> so the header is self-contained for int32_t

This header uses int32_t but does not include <cstdint>. Relying on transitive includes from offline-model-config.h can be brittle.

You can make the header self-contained with:

 #include <memory>
 #include <vector>
 
-#include "sherpa-onnx/csrc/offline-model-config.h"
+#include <cstdint>
+
+#include "sherpa-onnx/csrc/offline-model-config.h"

Also applies to: 29-30

sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc (1)

74-80: Clarify unsupported Manager-based construction and silence the unused-parameter warning

Impl(Manager *mgr, const OfflineModelConfig &config) currently just logs an error and exits, and mgr is unused. That’s fine if you really don’t want to support asset/resource manager–based loading for this QNN model yet, but it’s worth making this intent explicit and avoiding the warning.

For example:

   template <typename Manager>
   Impl(Manager *mgr, const OfflineModelConfig &config) : config_(config) {
-    SHERPA_ONNX_LOGE(
-        "Please copy all files from assets to SD card and set assetManager to "
-        "null");
+    (void)mgr;  // Manager-based loading is not supported for this model.
+    SHERPA_ONNX_LOGE(
+        "OfflineZipformerCtcModelQnn does not support loading from assets/"
+        "raw resources. Please copy all files to a filesystem path and pass "
+        "assetManager/resourceManager as null.");
     SHERPA_ONNX_EXIT(-1);
   }

This keeps current behavior but removes ambiguity and the unused-parameter warning.

Also applies to: 237-245

sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (1)

101-108: Use or remove feat_dim and consider a light sanity check on feature layout

feat_dim is read but not used:

int32_t feat_dim = s->FeatureDim();
std::vector<float> f = s->GetFrames();

If you don’t plan to use it, you can drop the variable. Alternatively, it’s a good place for a cheap consistency check between the stream and model expectations, e.g.:

-  void DecodeStream(OfflineStream *s) const {
-    int32_t feat_dim = s->FeatureDim();
-    std::vector<float> f = s->GetFrames();
+  void DecodeStream(OfflineStream *s) const {
+    int32_t feat_dim = s->FeatureDim();
+    std::vector<float> f = s->GetFrames();
+
+    if (!f.empty() &&
+        static_cast<int32_t>(f.size()) % feat_dim != 0) {
+      SHERPA_ONNX_LOGE(
+          "Feature vector size (%d) is not divisible by feat_dim (%d).",
+          static_cast<int32_t>(f.size()), feat_dim);
+      SHERPA_ONNX_EXIT(-1);
+    }

If you prefer not to add the check, at least removing feat_dim avoids an unused-variable warning.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16d62b6 and dba8ab8.

📒 Files selected for processing (11)
  • sherpa-onnx/csrc/CMakeLists.txt (1 hunks)
  • sherpa-onnx/csrc/offline-recognizer-ctc-impl.h (1 hunks)
  • sherpa-onnx/csrc/offline-recognizer-impl.cc (3 hunks)
  • sherpa-onnx/csrc/offline-sense-voice-model-config.cc (1 hunks)
  • sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (2 hunks)
  • sherpa-onnx/csrc/offline-zipformer-ctc-model-config.h (1 hunks)
  • sherpa-onnx/csrc/qnn-config.cc (1 hunks)
  • sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (1 hunks)
  • sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc (1 hunks)
  • sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (1 hunks)
  • sherpa-onnx/jni/offline-recognizer.cc (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-06T04:18:47.981Z
Learnt from: litongjava
Repo: k2-fsa/sherpa-onnx PR: 2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:18:47.981Z
Learning: In sherpa-onnx Java API, the native library names in Core.java (WIN_NATIVE_LIBRARY_NAME = "sherpa-onnx-jni.dll", UNIX_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.so", MACOS_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.dylib") are copied directly from the compiled binary filenames and should not be changed to match other libraries' naming conventions.

Applied to files:

  • sherpa-onnx/csrc/qnn-config.cc
  • sherpa-onnx/csrc/CMakeLists.txt
📚 Learning: 2025-08-06T04:23:50.237Z
Learnt from: litongjava
Repo: k2-fsa/sherpa-onnx PR: 2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:23:50.237Z
Learning: The sherpa-onnx JNI library files are stored in Hugging Face repository at https://huggingface.co/csukuangfj/sherpa-onnx-libs under versioned directories like jni/1.12.7/, and the actual Windows JNI library filename is "sherpa-onnx-jni.dll" as defined in Core.java constants.

Applied to files:

  • sherpa-onnx/csrc/offline-recognizer-impl.cc
  • sherpa-onnx/csrc/CMakeLists.txt
🧬 Code graph analysis (7)
sherpa-onnx/csrc/offline-sense-voice-model-config.cc (1)
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (1)
  • p (19-19)
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h (4)
sherpa-onnx/csrc/offline-recognizer-paraformer-impl.h (1)
  • Convert (25-81)
sherpa-onnx/csrc/offline-recognizer-transducer-impl.h (1)
  • Convert (33-115)
sherpa-onnx/csrc/offline-recognizer-fire-red-asr-impl.h (1)
  • Convert (26-45)
sherpa-onnx/csrc/offline-recognizer-moonshine-impl.h (1)
  • Convert (26-45)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (3)
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.h (1)
  • sherpa_onnx (12-32)
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (1)
  • sherpa_onnx (23-127)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc (16)
  • OfflineZipformerCtcModelQnn (213-215)
  • OfflineZipformerCtcModelQnn (218-220)
  • OfflineZipformerCtcModelQnn (222-222)
  • OfflineZipformerCtcModelQnn (238-239)
  • OfflineZipformerCtcModelQnn (243-244)
  • Run (224-227)
  • Run (224-225)
  • features (82-108)
  • features (82-82)
  • VocabSize (229-231)
  • VocabSize (229-229)
  • SubsamplingFactor (233-235)
  • SubsamplingFactor (233-233)
  • Impl (33-72)
  • Impl (33-33)
  • Impl (75-80)
sherpa-onnx/jni/offline-recognizer.cc (3)
nodejs-addon-examples/test_asr_non_streaming_paraformer_itn.js (1)
  • config (6-22)
nodejs-addon-examples/test_asr_non_streaming_wenet_ctc.js (1)
  • config (6-22)
nodejs-addon-examples/test_asr_non_streaming_zipformer_ctc.js (1)
  • config (6-20)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc (1)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (1)
  • OfflineZipformerCtcModelQnn (14-35)
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (2)
sherpa-onnx/csrc/offline-sense-voice-model-config.cc (3)
  • Register (15-29)
  • Register (15-15)
  • p (26-26)
sherpa-onnx/csrc/qnn-config.cc (2)
  • Register (15-32)
  • Register (15-15)
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (4)
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h (2)
  • Convert (26-316)
  • `` (186-192)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (1)
  • OfflineZipformerCtcModelQnn (14-35)
sherpa-onnx/csrc/offline-recognizer-impl.cc (8)
  • OfflineRecognizerImpl (567-618)
  • OfflineRecognizerImpl (621-671)
  • OfflineRecognizerImpl (700-701)
  • OfflineRecognizerImpl (708-709)
  • ApplyInverseTextNormalization (673-684)
  • ApplyInverseTextNormalization (673-674)
  • ApplyHomophoneReplacer (686-693)
  • ApplyHomophoneReplacer (686-687)
sherpa-onnx/csrc/rknn/offline-ctc-greedy-search-decoder-rknn.h (1)
  • OfflineCtcGreedySearchDecoderRknn (14-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: ubuntu-latest Debug shared tts-OFF
  • GitHub Check: ubuntu-latest Release static tts-OFF
  • GitHub Check: ubuntu-latest Release shared tts-OFF
  • GitHub Check: ubuntu-latest Release static tts-ON
  • GitHub Check: Debug shared-ON tts-OFF
  • GitHub Check: Debug shared-ON tts-ON
  • GitHub Check: Debug shared-OFF tts-ON
  • GitHub Check: Release shared-OFF tts-OFF
  • GitHub Check: Release shared-OFF tts-ON
  • GitHub Check: Release shared-ON tts-OFF
  • GitHub Check: ubuntu-24.04 3.13
  • GitHub Check: Release shared-ON tts-ON
  • GitHub Check: ubuntu-24.04 3.11
  • GitHub Check: rknn shared ON
  • GitHub Check: ubuntu-24.04 3.12
  • GitHub Check: ubuntu-24.04 3.10
  • GitHub Check: ubuntu-24.04 3.9
  • GitHub Check: ubuntu-24.04 3.8
  • GitHub Check: swift (macos-latest)
  • GitHub Check: swift (macos-13)
🔇 Additional comments (14)
sherpa-onnx/jni/offline-recognizer.cc (1)

335-338: LGTM! Improved validation consistency.

The unconditional validation ensures all providers, including QNN, are validated before creating the recognizer. This is more consistent and safer than the previous provider-specific gating.

sherpa-onnx/csrc/offline-zipformer-ctc-model-config.h (1)

10-18: LGTM! QNN config integration looks good.

The addition of QNN configuration support follows the same pattern used in other model configs (e.g., SenseVoice).

sherpa-onnx/csrc/CMakeLists.txt (1)

211-212: LGTM! Build configuration updated correctly.

The new QNN model sources are properly registered under the SHERPA_ONNX_ENABLE_QNN conditional block.

sherpa-onnx/csrc/offline-recognizer-impl.cc (3)

50-50: LGTM! Header inclusion for QNN Zipformer CTC support.


105-110: LGTM! QNN provider logic extended correctly.

The addition of Zipformer CTC support follows the existing pattern for SenseVoice, with proper fallback and error messaging.


361-367: LGTM! Template version matches non-template implementation.

The template version correctly mirrors the QNN provider logic from the non-template Create function.

sherpa-onnx/csrc/offline-sense-voice-model-config.cc (1)

25-28: LGTM! Scoped QNN config registration prevents option collisions.

Using a prefixed ParseOptions scope ensures QNN options for SenseVoice don't conflict with QNN options for other models (e.g., Zipformer CTC). This is the same pattern used in offline-zipformer-ctc-model-config.cc.

sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (3)

11-11: LGTM! Required include for EndsWith utility.


18-21: LGTM! Scoped QNN config registration.

The "zipformer-ctc" prefix ensures QNN options are namespaced, preventing collisions with other models' QNN configurations. This matches the pattern in offline-sense-voice-model-config.cc.


31-33: LGTM! Appropriate validation delegation for QNN models.

The logic correctly identifies QNN models by file extension (.so/.bin) and delegates validation to the QNN config. This allows different validation paths for ONNX vs QNN models.

sherpa-onnx/csrc/qnn-config.cc (1)

40-41: Runtime library loading validation is in place, but verify error messaging meets deployment needs.

The removal of file existence checks is intentional and documented in the code. Runtime validation occurs via dlopen() calls in qnn-backend.cc, qnn-model.cc, and qnn-model.cc, which include error checks and logging using dlerror(). However, no dedicated test files for QNN library loading were found in the codebase, making test coverage unclear.

Consider confirming that the dlerror() messages logged by SHERPA_ONNX_LOGE() are sufficient for your deployment scenario, and verify whether integration tests elsewhere cover library loading failures.

sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (1)

14-35: API and Pimpl structure look consistent with existing offline models

The class surface (constructors, Run, VocabSize, SubsamplingFactor) and Pimpl usage are clean and align with how other offline model wrappers are structured. No functional concerns from the header side.

sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc (1)

31-72: Overall QNN model wiring and concurrency handling look solid

  • Context binary vs model-lib initialization follows the expected QNN pattern, including optional context creation and debug logging.
  • PostInit() / CheckModel() establish max_num_frames_, feat_dim_, vocab_size_, and subsampling_factor_ once at construction.
  • Run() does shape normalization (truncate/zero-pad) and guards QnnModel calls with a mutex, which is appropriate if the model object is shared across threads.

Aside from the output-shape checks mentioned separately, the rest of the implementation reads correctly and matches how the recognizer expects to use it.

Also applies to: 82-112, 114-211, 213-235

sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (1)

31-84: Recognizer wiring and decoding flow are consistent with existing CTC implementations

The new OfflineRecognizerZipformerCtcQnnImpl closely follows the established pattern:

  • Config and SymbolTable initialization mirror other offline recognizers.
  • Init() enforces greedy_search and derives blank_id in the same way as the non-QNN CTC path, with the omnilingual special case.
  • DecodeStream() performs model inference, CTC decoding, and then applies ITN and homophone replacement before storing the result.

Functionally this looks correct and aligned with the rest of the codebase.

Also applies to: 90-120, 122-127

Comment on lines +187 to +196
auto out_shape = model_->TensorShape("log_probs");
vocab_size_ = out_shape[2];

subsampling_factor_ = max_num_frames_ / out_shape[1];
if (config_.debug) {
SHERPA_ONNX_LOGE("max_num_frames: %d", max_num_frames_);
SHERPA_ONNX_LOGE("feat_dim: %d", feat_dim_);
SHERPA_ONNX_LOGE("vocab_size: %d", vocab_size_);
SHERPA_ONNX_LOGE("subsampling_factor: %d", subsampling_factor_);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Harden log_probs shape checks to avoid undefined behavior and bad subsampling factor

CheckModel() assumes log_probs is 3‑D and indexes out_shape[2] without verifying its rank, and also divides by out_shape[1] without checking for zero or consistency with max_num_frames_. A mis-exported or incompatible model could cause out-of-bounds access or a bogus subsampling_factor_.

Consider tightening this block:

-    auto out_shape = model_->TensorShape("log_probs");
-    vocab_size_ = out_shape[2];
-
-    subsampling_factor_ = max_num_frames_ / out_shape[1];
+    auto out_shape = model_->TensorShape("log_probs");
+    if (out_shape.size() != 3) {
+      SHERPA_ONNX_LOGE(
+          "The output tensor 'log_probs' should be 3-d, actual '%d'",
+          static_cast<int32_t>(out_shape.size()));
+      SHERPA_ONNX_EXIT(-1);
+    }
+    if (out_shape[0] != 1) {
+      SHERPA_ONNX_LOGE(
+          "The log_probs.shape[0] should be 1, actual '%d'", out_shape[0]);
+      SHERPA_ONNX_EXIT(-1);
+    }
+
+    vocab_size_ = out_shape[2];
+
+    if (out_shape[1] <= 0 || max_num_frames_ % out_shape[1] != 0) {
+      SHERPA_ONNX_LOGE(
+          "Mismatched time dims: x.shape[1]=%d, log_probs.shape[1]=%d",
+          max_num_frames_, out_shape[1]);
+      SHERPA_ONNX_EXIT(-1);
+    }
+
+    subsampling_factor_ = max_num_frames_ / out_shape[1];

This keeps failure modes explicit and consistent with the earlier input‑shape checks.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto out_shape = model_->TensorShape("log_probs");
vocab_size_ = out_shape[2];
subsampling_factor_ = max_num_frames_ / out_shape[1];
if (config_.debug) {
SHERPA_ONNX_LOGE("max_num_frames: %d", max_num_frames_);
SHERPA_ONNX_LOGE("feat_dim: %d", feat_dim_);
SHERPA_ONNX_LOGE("vocab_size: %d", vocab_size_);
SHERPA_ONNX_LOGE("subsampling_factor: %d", subsampling_factor_);
}
auto out_shape = model_->TensorShape("log_probs");
if (out_shape.size() != 3) {
SHERPA_ONNX_LOGE(
"The output tensor 'log_probs' should be 3-d, actual '%d'",
static_cast<int32_t>(out_shape.size()));
SHERPA_ONNX_EXIT(-1);
}
if (out_shape[0] != 1) {
SHERPA_ONNX_LOGE(
"The log_probs.shape[0] should be 1, actual '%d'", out_shape[0]);
SHERPA_ONNX_EXIT(-1);
}
vocab_size_ = out_shape[2];
if (out_shape[1] <= 0 || max_num_frames_ % out_shape[1] != 0) {
SHERPA_ONNX_LOGE(
"Mismatched time dims: x.shape[1]=%d, log_probs.shape[1]=%d",
max_num_frames_, out_shape[1]);
SHERPA_ONNX_EXIT(-1);
}
subsampling_factor_ = max_num_frames_ / out_shape[1];
if (config_.debug) {
SHERPA_ONNX_LOGE("max_num_frames: %d", max_num_frames_);
SHERPA_ONNX_LOGE("feat_dim: %d", feat_dim_);
SHERPA_ONNX_LOGE("vocab_size: %d", vocab_size_);
SHERPA_ONNX_LOGE("subsampling_factor: %d", subsampling_factor_);
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR extends the QNN (Qualcomm Neural Network) backend to support Zipformer CTC acoustic models for offline speech recognition, complementing the existing SenseVoice model support. The implementation follows established patterns from the SenseVoice QNN integration and removes unnecessary file existence checks that prevented using library search paths.

Key changes:

  • Added complete QNN backend implementation for Zipformer CTC models with model initialization, context binary caching, and inference
  • Refactored QNN configuration validation to allow dlopen() to resolve library paths instead of requiring explicit file paths
  • Made the Convert function non-static to enable code reuse across different recognizer implementations

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h Header defining the QNN-based Zipformer CTC model class with initialization and inference methods
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.cc Implementation of QNN Zipformer CTC model with context binary caching and model validation
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h Recognizer implementation for Zipformer CTC models using QNN backend with CTC greedy search decoder
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.h Added QNN configuration field to Zipformer CTC model configuration
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc Integrated QNN config registration with model-specific prefix and validation for .so/.bin files
sherpa-onnx/csrc/offline-sense-voice-model-config.cc Updated to use model-specific prefix for QNN config registration
sherpa-onnx/csrc/qnn-config.cc Removed file existence checks for backend_lib and system_lib to allow dlopen() path resolution
sherpa-onnx/csrc/offline-recognizer-impl.cc Updated factory methods to instantiate Zipformer CTC QNN recognizer and updated error messages
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h Changed Convert function from static to non-static for reuse in QNN implementations
sherpa-onnx/csrc/CMakeLists.txt Added offline-zipformer-ctc-model-qnn.cc to QNN sources list
sherpa-onnx/jni/offline-recognizer.cc Removed QNN-specific condition from config validation to allow QNN backend validation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Decode a single stream.
// Some models do not support batch size > 1, e.g., WeNet CTC models.
void DecodeStream(OfflineStream *s) const {
int32_t feat_dim = s->FeatureDim();

Copilot AI Nov 21, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable feat_dim is assigned but never used in this function. Consider removing it if it's not needed for validation or debugging purposes.

Suggested change
int32_t feat_dim = s->FeatureDim();

Copilot uses AI. Check for mistakes.
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Nov 24, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (2)

56-65: Remove the confusing omnilingual model check.

The condition config_.model_config.omnilingual.model.empty() and associated comment (line 60) are irrelevant for a Zipformer CTC QNN-specific implementation. This appears to be copy-pasted from the generic CTC recognizer. Since this implementation is exclusively for Zipformer CTC models, simplify the logic to only verify that one of the blank symbols exists.

Apply this diff to clarify the blank symbol validation:

-      if (!symbol_table_.Contains("<blk>") &&
-          !symbol_table_.Contains("<eps>") &&
-          !symbol_table_.Contains("<blank>") &&
-          config_.model_config.omnilingual.model.empty()) {
-        // for omnilingual asr, its blank id is 0
+      if (!symbol_table_.Contains("<blk>") &&
+          !symbol_table_.Contains("<eps>") &&
+          !symbol_table_.Contains("<blank>")) {
         SHERPA_ONNX_LOGE(
             "We expect that tokens.txt contains "
             "the symbol <blk> or <eps> or <blank> and its ID.");

112-112: Use configured frame_shift_ms instead of hardcoded value.

The frame_shift_ms is hardcoded to 10, which may produce incorrect timestamps if the feature configuration specifies a different frame shift. Use config_.feat_config.frame_shift_ms to respect the user's configuration.

Apply this diff:

-    int32_t frame_shift_ms = 10;
+    int32_t frame_shift_ms = config_.feat_config.frame_shift_ms;
🧹 Nitpick comments (4)
android/SherpaOnnxSimulateStreamingAsr/app/src/main/java/com/k2fsa/sherpa/onnx/simulate/streaming/asr/SimulateStreamingAsr.kt (2)

128-131: Clarify QNN backend validation scope and model assumptions

This guard enforces that at least one of senseVoice.qnnConfig.backendLib or zipformerCtc.qnnConfig.backendLib is set when provider == "qnn". That’s good for catching misconfigured SenseVoice/Zipformer‑CTC models, but it also bakes in the assumption that those are the only QNN model families in use.

If you ever introduce QNN support for other offline models (e.g., non‑CTC Zipformer or other types), this condition will cause a hard failure even when a different model’s qnnConfig is correctly set. Consider either:

  • Narrowing the check to “if we are actually using senseVoice/zipformerCtc here, then require backendLib”, or
  • Centralizing QNN config validation in one place that knows which sub‑model is active.

For now this is fine if those are the only supported QNN models, but it’s worth verifying the assumption to avoid future surprises.


135-153: Confirm single‑model assumption and optional contextBinary handling

The new copy logic prefers SenseVoice if senseVoice.model is non‑empty and only falls back to zipformerCtc if SenseVoice is empty. Two things to double‑check:

  1. Mutual exclusivity of models
    If both senseVoice.model and zipformerCtc.model can ever be set simultaneously, only the SenseVoice assets will be copied and initialized, which may not match caller expectations. If they are intended to be mutually exclusive, a small assertion/log would make that contract explicit.

  2. Empty contextBinary semantics
    If qnnConfig.contextBinary is allowed to be empty (e.g., no prebuilt context, runtime builds it), copyAssetToInternalStorage("") will return a directory path (filesDir) and log a misleading “ does not exist” message. It may be safer to guard with an isNotEmpty() check around the copy or explicitly document that contextBinary must be non‑empty in QNN configs.

These are mostly contract/robustness questions; behavior is fine if current configs always set exactly one model and a non‑empty context binary.

sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (1)

11-22: QNN registration/validation path looks good; consider tightening ToString() formatting

The new ParseOptions scope with prefix = "zipformer-ctc" and the .so/.bin gate into qnn_config.Validate() integrate cleanly with existing config/validation.

One small cosmetic improvement: ToString() currently produces a trailing comma before the closing parenthesis when qnn_config is present, e.g. ..., qnn_config=QnnConfig(...), ). You can avoid that with:

-  os << "model=\"" << model << "\"";
-
-  if (!qnn_config.backend_lib.empty()) {
-    os << ", qnn_config=" << qnn_config.ToString() << ", ";
-  }
-
-  os << ")";
+  os << "model=\"" << model << "\"";
+
+  if (!qnn_config.backend_lib.empty()) {
+    os << ", qnn_config=" << qnn_config.ToString();
+  }
+
+  os << ")";

Also applies to: 31-33, 42-49

sherpa-onnx/kotlin-api/OfflineRecognizer.kt (1)

772-773: QNN presets for SenseVoice and Zipformer-CTC look consistent; double-check debug flags and consider dedup

  • IDs 9011–9021 for Zipformer-CTC QNN mirror the SenseVoice QNN presets: same directory naming scheme, libmodel.so + model.bin layout, and identical QnnConfig wiring (backendLib, systemLib, contextBinary).
  • Enabling debug = true only for the shorter-duration presets (9000–9002 and 9011–9013) is fine if you intentionally want extra logging there; otherwise you may want to align debug across all QNN presets.

If these blocks keep growing, it might be worth factoring out a small helper to construct these QNN OfflineModelConfig presets from modelDir to avoid further copy‑paste.

Also applies to: 789-790, 806-807, 938-1115

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dba8ab8 and 3f2722b.

📒 Files selected for processing (10)
  • .github/workflows/apk-qnn-vad-asr-simulated-streaming.yaml (3 hunks)
  • .gitignore (1 hunks)
  • android/SherpaOnnxSimulateStreamingAsr/app/src/main/java/com/k2fsa/sherpa/onnx/simulate/streaming/asr/SimulateStreamingAsr.kt (1 hunks)
  • scripts/apk/generate-qnn-vad-asr-apk-script.py (1 hunks)
  • sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (2 hunks)
  • sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (1 hunks)
  • sherpa-onnx/csrc/qnn/qnn-model.cc (1 hunks)
  • sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineZipformerCtcModelConfig.java (3 hunks)
  • sherpa-onnx/jni/offline-recognizer.cc (2 hunks)
  • sherpa-onnx/kotlin-api/OfflineRecognizer.kt (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sherpa-onnx/jni/offline-recognizer.cc
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-06T04:23:50.237Z
Learnt from: litongjava
Repo: k2-fsa/sherpa-onnx PR: 2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:23:50.237Z
Learning: The sherpa-onnx JNI library files are stored in Hugging Face repository at https://huggingface.co/csukuangfj/sherpa-onnx-libs under versioned directories like jni/1.12.7/, and the actual Windows JNI library filename is "sherpa-onnx-jni.dll" as defined in Core.java constants.

Applied to files:

  • .gitignore
📚 Learning: 2025-08-06T04:18:47.981Z
Learnt from: litongjava
Repo: k2-fsa/sherpa-onnx PR: 2440
File: sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/core/Core.java:4-6
Timestamp: 2025-08-06T04:18:47.981Z
Learning: In sherpa-onnx Java API, the native library names in Core.java (WIN_NATIVE_LIBRARY_NAME = "sherpa-onnx-jni.dll", UNIX_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.so", MACOS_NATIVE_LIBRARY_NAME = "libsherpa-onnx-jni.dylib") are copied directly from the compiled binary filenames and should not be changed to match other libraries' naming conventions.

Applied to files:

  • .gitignore
🧬 Code graph analysis (4)
sherpa-onnx/csrc/qnn/qnn-model.cc (1)
sherpa-onnx/csrc/qnn/qnn-backend.cc (3)
  • ret (185-188)
  • ret (190-194)
  • ret (196-200)
sherpa-onnx/csrc/offline-zipformer-ctc-model-config.cc (2)
sherpa-onnx/csrc/qnn-config.cc (4)
  • Register (15-32)
  • Register (15-15)
  • ToString (55-64)
  • ToString (55-55)
sherpa-onnx/csrc/offline-ctc-fst-decoder-config.cc (1)
  • p (27-27)
sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (3)
sherpa-onnx/csrc/qnn/offline-zipformer-ctc-model-qnn.h (2)
  • sherpa_onnx (12-37)
  • OfflineZipformerCtcModelQnn (14-35)
sherpa-onnx/csrc/offline-recognizer-ctc-impl.h (5)
  • sherpa_onnx (24-318)
  • Convert (26-316)
  • Init (93-184)
  • `` (186-192)
  • DecodeStreams (194-264)
sherpa-onnx/csrc/offline-recognizer-impl.h (1)
  • std (36-40)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineZipformerCtcModelConfig.java (2)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineSenseVoiceModelConfig.java (1)
  • Builder (38-67)
sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/QnnConfig.java (1)
  • QnnConfig (5-56)
🔇 Additional comments (8)
sherpa-onnx/csrc/qnn/qnn-model.cc (1)

137-137: LGTM! Explicit cast improves type clarity.

The explicit static_cast<int32_t>(ret) ensures the QNN error code is properly formatted in the log message. This is a minor improvement that enhances type safety without any functional impact.

scripts/apk/generate-qnn-vad-asr-apk-script.py (1)

214-228: Balanced pushd/popd in 9010 command block looks correct

The added popd in the cmd for model idx=9010 cleanly restores the directory stack and matches the pattern used for the other models. No further changes needed here.

.gitignore (1)

162-163: New ignore patterns for Zipformer-CTC QNN artifacts are consistent

Names and placement match existing QNN-related model directories; no issues.

.github/workflows/apk-qnn-vad-asr-simulated-streaming.yaml (1)

7-7: Workflow trigger and 10-way sharding look correct; confirm generator script assumptions

Switching the push branch to zipformer-ctc-qnn-2, expanding the matrix to 10 shards (total: "10", index: "0".."9"), and using a distinct -android-qnn ccache key are all consistent with the new QNN APK workload. Please just confirm that scripts/apk/generate-qnn-vad-asr-apk-script.py does not assume total == 5 or a smaller index range.

Also applies to: 27-28, 50-50

sherpa-onnx/kotlin-api/OfflineRecognizer.kt (1)

35-38: Zipformer-CTC Kotlin config gains QNN support in a consistent way

Adding qnnConfig: QnnConfig = QnnConfig() to OfflineZipformerCtcModelConfig mirrors OfflineSenseVoiceModelConfig and keeps the Kotlin config surface in sync with Java/C++.

sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineZipformerCtcModelConfig.java (1)

7-12: Java Zipformer-CTC config QNN wiring matches existing SenseVoice pattern

The new qnnConfig field, getter, and builder plumbing are consistent with OfflineSenseVoiceModelConfig and keep the Java API aligned with the Kotlin/C++ layers. No issues from a construction or immutability standpoint.

Also applies to: 22-24, 28-29, 39-42

sherpa-onnx/csrc/qnn/offline-recognizer-zipformer-ctc-qnn-impl.h (2)

25-29: LGTM: Forward declaration is correct.

The forward declaration correctly references the Convert function defined in offline-recognizer-ctc-impl.h, aligning with the external linkage pattern for this shared utility.


101-119: Confirm whether the QNN model requires feature normalization before inference.

The investigation reveals that the generic CTC implementation calls model_->NormalizeFeatures() to perform mean-variance normalization before inference. The QNN implementation skips this step entirely—QnnModel::SetInputTensorData() only performs quantization conversion, not feature normalization. The OfflineZipformerCtcModelQnn class has no NormalizeFeatures method at all.

Verify whether:

  1. The QNN model was trained without normalization and does not require it, or
  2. Feature normalization should be added in DecodeStream() before calling model_->Run() (similar to the generic CTC recognizer pattern)

Comment thread scripts/apk/generate-qnn-vad-asr-apk-script.py
@csukuangfj
csukuangfj merged commit d1c458b into k2-fsa:master Nov 24, 2025
28 of 43 checks passed
@csukuangfj
csukuangfj deleted the zipformer-ctc-qnn branch November 24, 2025 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants