Skip to content

Add C++ runtime for Whisper with Ascend NPU - #3009

Merged
csukuangfj merged 13 commits into
k2-fsa:masterfrom
csukuangfj:cpp-whisper-ascend
Jan 9, 2026
Merged

csukuangfj merged 13 commits into
k2-fsa:masterfrom
csukuangfj:cpp-whisper-ascend

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Jan 8, 2026 •

Copy link
Copy Markdown
Collaborator

Screenshots

Screenshot 2026-01-09 at 15 37 09
./build/bin/sherpa-onnx-offline \
  --whisper-encoder=sherpa-onnx-ascend-910B-cann-7.0-whisper-tiny/tiny-encoder.om \
  --whisper-decoder=sherpa-onnx-ascend-910B-cann-7.0-whisper-tiny/tiny-decoder.om \
  --tokens=sherpa-onnx-ascend-910B-cann-7.0-whisper-tiny/tiny-tokens.txt \
  --provider=ascend \
  --debug=0 \
  ./en-16k.wav \
  ./1.wav

Summary by CodeRabbit

  • New Features

    • Ascend NPU backend for offline Whisper speech recognition and a templated Whisper recognizer.
  • Improvements

    • Multilingual model types, language-token utilities, and language-detection support.
    • Centralized feature normalization and improved device memory/resource handling.
    • Support for Whisper models in existing recognizer creation flows on Ascend.
  • Bug Fixes

    • Explicit initialization when resizing feature buffers to avoid uninitialized values.

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

@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jan 8, 2026
@coderabbitai

coderabbitai Bot commented Jan 8, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds Ascend NPU Whisper support: new Ascend-specific OfflineWhisperModel implementation and header, Whisper model/config utilities and token mappings, feature-normalization helpers, a templated Whisper recognizer, Ascend utility fixes (move/Release, MB calc), CMake registration, and a minor Zipformer zero-init change.

Changes

Cohort / File(s) Summary
Build
\sherpa-onnx/csrc/CMakeLists.txt``
Registers .../ascend/offline-whisper-model-ascend.cc in ASCEND_NPU sources.
Ascend Whisper Model
\sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h`, `sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc``
New OfflineWhisperModelAscend (PIMPL) with constructors/dtor, Run and FeatureDim; implements device/context init, encoder/decoder setup, device memory allocation, encoder/decoder execution, language handling, and decoding loop.
Recognizer Template & Integration
\sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h`, `sherpa-onnx/csrc/offline-recognizer-impl.cc``
Adds OfflineRecognizerWhisperTplImpl<WhisperModel> template with Create/DecodeStreams/Convert logic; factory updated to instantiate it for Ascend Whisper models.
Whisper Model Config & Tokens
\sherpa-onnx/csrc/offline-whisper-model-config.h`, `sherpa-onnx/csrc/offline-whisper-model-config.cc``
Adds WhisperModelType enum, parse/ToString, multilingual checks, language token id/code mappings, lists of supported languages, and OfflineWhisperDecoderResult struct.
Public API / Decoder header
\sherpa-onnx/csrc/offline-whisper-decoder.h``
Removed OfflineWhisperDecoderResult from this header; note: some callers/return types still reference this type elsewhere (possible include/ABI inconsistency).
Feature Normalization
\sherpa-onnx/csrc/math.h`, `sherpa-onnx/csrc/math.cc`, `sherpa-onnx/csrc/offline-whisper-model.cc``
Adds NormalizeWhisperFeatures and MaxElementIndex; offline-whisper-model now delegates normalization to the new helper.
Ascend Utilities
\sherpa-onnx/csrc/ascend/utils.h`, `sherpa-onnx/csrc/ascend/utils.cc``
Add AclDevicePtr::Get<T>(); define move ctor/assign for AclDataBuffer, introduce Release() and use it in destructor; fix MB calculation to use floating-point division.
Ascend Zipformer minor
\sherpa-onnx/csrc/ascend/offline-zipformer-ctc-model-ascend.cc``
Explicitly zero-initializes newly allocated elements when resizing feature vector.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Recognizer as OfflineRecognizerWhisperTplImpl
    participant Model as OfflineWhisperModelAscend
    participant Ascend as Ascend NPU Runtime

    App->>Recognizer: CreateStream()
    Recognizer->>App: stream (includes feature_dim)
    App->>Recognizer: Push features / Request decode
    Recognizer->>Recognizer: NormalizeWhisperFeatures(features)
    Recognizer->>Model: Run(normalized_features)
    Model->>Ascend: Upload inputs / Execute encoder
    Ascend-->>Model: encoder outputs
    Model->>Ascend: Prepare decoder inputs / Execute decoder steps (KV cache updates)
    Ascend-->>Model: logits / KV states
    Model-->>Recognizer: OfflineWhisperDecoderResult (tokens, lang)
    Recognizer-->>App: OfflineRecognitionResult (text, tokens, lang)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped in code with careful hops,
Wired tokens, caches, Ascend props,
Features cleaned and languages found,
Whisper wakes without a sound,
Tests await — let's munch those ops!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately describes the main change: adding C++ runtime support for Whisper models on Ascend NPU hardware.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 extends the sherpa-onnx project by adding experimental C++ runtime support for Whisper models on Ascend NPU. It introduces dedicated classes for handling Whisper inference on Ascend hardware, refactors common utility functions for feature processing and memory management, and integrates these new components into the existing offline recognizer framework. The changes aim to leverage Ascend's capabilities for accelerated Whisper model execution.

Highlights

  • Ascend NPU Support for Whisper: Introduced a C++ runtime for Whisper models specifically optimized for Ascend NPU, enabling efficient inference on this hardware.
  • New Whisper Model Implementation: Added OfflineWhisperModelAscend and OfflineRecognizerWhisperTplImpl classes to manage Whisper model loading, execution, and feature processing on Ascend.
  • Feature Normalization Refactoring: Extracted Whisper-specific feature normalization logic into a reusable NormalizeWhisperFeatures function, improving code modularity.
  • ACL Data Buffer Enhancements: Improved AclDataBuffer utility class with move semantics and an explicit Release() mechanism for better resource management.
  • Build System and Recognizer Integration: Updated CMakeLists to include new Whisper Ascend sources and integrated the new Whisper recognizer into the OfflineRecognizerImpl factory.
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 a C++ runtime for Whisper models on Ascend NPU, which is a significant feature enhancement. The overall structure, including the use of a PIMPL pattern and a templated recognizer implementation, is well-designed for extensibility. However, as a work-in-progress, there are several critical issues and areas for improvement. I've identified a compilation bug in a move assignment operator, some unimplemented constructors and methods that would lead to runtime crashes, and incomplete logic in the decoding process. Additionally, I've suggested some refactoring to reduce code duplication and improve readability. Addressing these points will be crucial for finalizing this feature.

Comment on lines +73 to +76
Impl(Manager *mgr, const OfflineModelConfig &config) : config_(config) {
SHERPA_ONNX_LOGE("Not implemented");
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.

critical

The constructor for loading models via an asset manager is not implemented and will cause the program to exit if called. This is a critical omission if this functionality is expected to be supported, as it is in other backends within the project. Please implement this constructor to handle model loading from a manager.

Comment thread sherpa-onnx/csrc/ascend/utils.h Outdated
other.p_ = nullptr;
}
AclDataBuffer &operator=(AclDataBuffer &&other) {
if (other == *this) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The self-assignment check if (other == *this) is incorrect because operator== is not defined for AclDataBuffer. This will cause a compilation error. The check should compare the addresses of the objects.

Suggested change
if (other == *this) {
if (this == &other) {


NormalizeWhisperFeatures(f.data(), num_frames, feat_dim);

model_->Run(std::move(f));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The DecodeStream function is incomplete. It calls model_->Run(), but the returned token IDs are discarded, and no recognition result is set on the stream. The implementation should process the tokens, convert them to text, and set the result on the stream s using s->SetResult(). Without this, the recognizer will not produce any output.

"small chunks.");
}

features.resize(num_frames_ * feat_dim_);

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

For clarity and to avoid any ambiguity with default initialization, it's better to explicitly specify the padding value when resizing the features vector. This is consistent with changes made in other parts of the codebase.

    features.resize(num_frames_ * feat_dim_, 0.0f);

Comment on lines +166 to +187
cross_kv_ptr_.reserve(n_text_layer_ * 2);
for (int32_t i = 0; i < n_text_layer_ * 2; ++i) {
auto p = std::make_unique<AclDevicePtr>(num_out_frames_ * n_text_state_ *
sizeof(float));
cross_kv_ptr_.push_back(std::move(p));
total += num_out_frames_ * n_text_state_ * sizeof(float);
}

self_kv_ptr_.reserve(n_text_layer_ * 2);
for (int32_t i = 0; i < n_text_layer_ * 2; ++i) {
auto p = std::make_unique<AclDevicePtr>(n_text_ctx_ * n_text_state_ *
sizeof(float));
self_kv_ptr_.push_back(std::move(p));
total += n_text_ctx_ * n_text_state_ * sizeof(float);
}

delta_kv_ptr_.reserve(n_text_layer_ * 2);
for (int32_t i = 0; i < n_text_layer_ * 2; ++i) {
auto p = std::make_unique<AclDevicePtr>(n_text_state_ * sizeof(float));
delta_kv_ptr_.push_back(std::move(p));
total += n_text_state_ * sizeof(float);
}

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 loops for allocating cross_kv_ptr_, self_kv_ptr_, and delta_kv_ptr_ are very similar. This code can be refactored into a helper function to reduce duplication and improve maintainability.

For example, you could introduce a helper function like this:

void AllocateDevicePtrs(std::vector<std::unique_ptr<AclDevicePtr>>& vec, int32_t count, size_t size, int32_t& total) {
    vec.reserve(count);
    for (int32_t i = 0; i < count; ++i) {
        auto p = std::make_unique<AclDevicePtr>(size);
        vec.push_back(std::move(p));
        total += size;
    }
}

const std::vector<std::vector<int64_t>> &input_shapes =
decoder_model_->GetInputShapes();
// tokens, self_kv, cross_kv, offset, mask
int32_t expected_num_inputs = 1 + 2 * n_text_layer_ + 2 * n_text_layer_ + 2;

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 expression for expected_num_inputs can be simplified for better readability.

    int32_t expected_num_inputs = 3 + 4 * n_text_layer_;

Comment on lines +76 to +77
auto memory_info =
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault);

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 memory_info variable is initialized but never used. It should be removed.

@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: 4

🤖 Fix all issues with AI agents
In @sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc:
- Around line 72-76: The template constructor Impl(Manager *mgr, const
OfflineModelConfig &config) currently aborts at runtime (SHERPA_ONNX_LOGE /
SHERPA_ONNX_EXIT) but is explicitly instantiated elsewhere, causing crashes on
Android/OHOS; fix by either implementing the Manager-based path (e.g., delegate
to the existing non-template Impl(const OfflineModelConfig&) initializer or
perform proper Manager-specific initialization and set config_ without exiting)
or, if Manager support is not intended, remove the explicit template
instantiations and the template constructor declaration (or replace with a
compile-time guard/static_assert that clearly indicates WIP). Ensure changes
reference the Impl(Manager *mgr, const OfflineModelConfig &config) template and
the explicit instantiations so the build no longer hits the runtime exit.
- Around line 85-97: The code always calls features.resize(num_frames_ *
feat_dim_) even when input is shorter, causing unintended padding; change the
logic to compute an explicit frames_to_use = std::min(num_frames, num_frames_)
and only call features.resize(num_frames_ * feat_dim_) when num_frames >
num_frames_ (i.e., truncation), leaving smaller inputs unpadded unless padding
is intentional; update downstream uses (e.g., the transpose call and any buffer
lengths) to use frames_to_use (or num_frames_ only when you actually truncated)
so the transpose uses the correct frame count and the warning remains for
truncation cases.

In @sherpa-onnx/csrc/ascend/utils.h:
- Around line 178-188: The move assignment operator
AclDataBuffer::operator=(AclDataBuffer &&other) incorrectly attempts to compare
objects with `if (other == *this)`; replace this with an address comparison `if
(this == &other)` to detect self-assignment, leaving the rest of the method
(calling Release(), moving p_, and nulling other.p_) unchanged.

In @sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h:
- Around line 75-86: DecodeStream currently creates an unused memory_info, drops
model_->Run() output, and never writes recognition results to the OfflineStream;
remove the dead memory_info, capture the returned std::vector<int32_t> tokens =
model_->Run(std::move(f)), convert those token IDs to text using symbol_table_
(e.g., look up each id and join into a string, handling any special tokens), and
store the resulting text back on the stream (call the stream's result setter
such as OfflineStream::SetResult or similar). Keep
NormalizeWhisperFeatures(f.data(), num_frames, feat_dim) and ensure
FeatureDim(), GetFrames(), symbol_table_, and model_->Run are used as described.
🧹 Nitpick comments (5)
sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h (1)

65-66: Consider making debug log conditional.

Using SHERPA_ONNX_LOGE for non-error messages like "use greedy_search" may clutter logs in production. Consider guarding this with a debug flag similar to other parts of the codebase (e.g., if (config_.model_config.debug)).

sherpa-onnx/csrc/math.cc (1)

106-117: Disabled Eigen implementation has incorrect syntax.

The #if 0 block has a syntax error at line 110:

Map<ArrayXXf, Eigen::RowMajor> feats(features, num_frames, feat_dim);

Eigen::RowMajor is not a valid alignment parameter for Map. For row-major mapping, you need:

using RowMajorArrayXXf = Eigen::Array<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
Eigen::Map<RowMajorArrayXXf> feats(features, num_frames, feat_dim);

If you plan to enable this optimization later, the current code would not compile.

♻️ Suggested fix for the Eigen block
 #if 0
-  using Eigen::ArrayXXf;
   using Eigen::Map;
+  using RowMajorArrayXXf = Eigen::Array<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

-  Map<ArrayXXf, Eigen::RowMajor> feats(features, num_frames, feat_dim);
+  Map<RowMajorArrayXXf> feats(features, num_frames, feat_dim);

   feats = feats.max(1e-10f).log10();
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h (1)

24-28: Consider clarifying the features parameter documentation.

The comment says features shape is (1, feat_dim, num_frames) but the parameter is a flat std::vector<float>. Consider clarifying whether the vector should be in row-major or column-major order, and what the expected size is (feat_dim * num_frames).

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (2)

34-39: Unused function: CreateCausalMask.

This function is defined but never called anywhere in this file. Consider removing dead code or adding a TODO if it's needed for the decoder implementation.


102-107: Debug logging in production path and incomplete decoder.

  1. Lines 102-105: SHERPA_ONNX_LOGE calls for "features size" and "run encoder done" should be guarded with config_.debug to avoid log noise in production.

  2. Line 107: Run always returns an empty vector since the decoder is not implemented. This is WIP, but callers will receive no tokens.

♻️ Guard debug logs
-    SHERPA_ONNX_LOGE("features size: %d. %dx%d", (int)features.size(),
-                     (int)features.size() / feat_dim_, feat_dim_);
+    if (config_.debug) {
+      SHERPA_ONNX_LOGE("features size: %d. %dx%d", (int)features.size(),
+                       (int)features.size() / feat_dim_, feat_dim_);
+    }
     RunEncoder(std::move(features));
-    SHERPA_ONNX_LOGE("run encoder done!");
+    if (config_.debug) {
+      SHERPA_ONNX_LOGE("run encoder done!");
+    }

+    // TODO(fangjun): Implement decoder to produce token IDs
     return {};
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e061d42 and a645de9.

📒 Files selected for processing (13)
  • sherpa-onnx/csrc/CMakeLists.txt
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h
  • sherpa-onnx/csrc/ascend/offline-zipformer-ctc-model-ascend.cc
  • sherpa-onnx/csrc/ascend/utils.cc
  • sherpa-onnx/csrc/ascend/utils.h
  • sherpa-onnx/csrc/math.cc
  • sherpa-onnx/csrc/math.h
  • sherpa-onnx/csrc/offline-recognizer-impl.cc
  • sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h
  • sherpa-onnx/csrc/offline-whisper-model-config.cc
  • sherpa-onnx/csrc/offline-whisper-model-config.h
  • sherpa-onnx/csrc/offline-whisper-model.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:

  • sherpa-onnx/csrc/offline-whisper-model.cc
📚 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/offline-whisper-model.cc
🧬 Code graph analysis (9)
sherpa-onnx/csrc/math.h (1)
sherpa-onnx/csrc/math.cc (2)
  • NormalizeWhisperFeatures (101-143)
  • NormalizeWhisperFeatures (101-102)
sherpa-onnx/csrc/ascend/utils.cc (1)
sherpa-onnx/csrc/ascend/utils.h (1)
  • AclDataBuffer (166-197)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h (1)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (16)
  • OfflineWhisperModelAscend (303-305)
  • OfflineWhisperModelAscend (308-310)
  • OfflineWhisperModelAscend (312-312)
  • OfflineWhisperModelAscend (324-325)
  • OfflineWhisperModelAscend (329-330)
  • Run (314-317)
  • Run (314-315)
  • features (78-108)
  • features (78-78)
  • features (113-138)
  • features (113-113)
  • FeatureDim (319-321)
  • FeatureDim (319-319)
  • Impl (63-70)
  • Impl (63-63)
  • Impl (73-76)
sherpa-onnx/csrc/offline-whisper-model-config.h (1)
sherpa-onnx/csrc/offline-whisper-model-config.cc (6)
  • ToString (80-91)
  • ToString (80-80)
  • ToString (93-115)
  • ToString (93-93)
  • ParseWhisperModelType (117-133)
  • ParseWhisperModelType (117-117)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (2)
sherpa-onnx/csrc/offline-whisper-model-config.cc (6)
  • ParseWhisperModelType (117-133)
  • ParseWhisperModelType (117-117)
  • ToString (80-91)
  • ToString (80-80)
  • ToString (93-115)
  • ToString (93-93)
sherpa-onnx/csrc/math.cc (2)
  • Transpose (62-76)
  • Transpose (62-62)
sherpa-onnx/csrc/math.cc (2)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (4)
  • features (78-108)
  • features (78-78)
  • features (113-138)
  • features (113-113)
sherpa-onnx/csrc/offline-stream.cc (1)
  • features (198-198)
sherpa-onnx/csrc/ascend/utils.h (1)
sherpa-onnx/csrc/ascend/utils.cc (4)
  • AclDataBuffer (344-351)
  • AclDataBuffer (353-353)
  • Release (355-361)
  • Release (355-355)
sherpa-onnx/csrc/offline-whisper-model.cc (1)
sherpa-onnx/csrc/math.cc (2)
  • NormalizeWhisperFeatures (101-143)
  • NormalizeWhisperFeatures (101-102)
sherpa-onnx/csrc/ascend/offline-zipformer-ctc-model-ascend.cc (1)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (5)
  • features (78-108)
  • features (78-78)
  • features (113-138)
  • features (113-113)
  • feat_dim_ (110-110)
🔇 Additional comments (12)
sherpa-onnx/csrc/ascend/utils.h (2)

174-177: LGTM! Move constructor correctly transfers ownership.

The move constructor properly transfers ownership of the underlying aclDataBuffer* and nullifies the source pointer.


190-190: Good addition of the Release() method.

The explicit Release() method provides clear separation between resource cleanup and object destruction, which is essential for proper move semantics.

sherpa-onnx/csrc/ascend/utils.cc (1)

353-361: LGTM! Destructor and Release() implementation are correct.

The destructor properly delegates to Release(), and the Release() implementation:

  • Guards against null pointers
  • Performs proper error checking
  • Ensures idempotency by setting p_ to nullptr

This pattern enables safe move semantics and allows Release() to be called multiple times without issues.

sherpa-onnx/csrc/math.h (1)

164-165: LGTM! Clean utility function declaration.

The new NormalizeWhisperFeatures function provides a centralized implementation for Whisper feature normalization, promoting code reuse across the codebase.

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

67-67: Good clarity improvement with explicit zero-initialization.

Making the zero-initialization explicit in the resize call improves code readability, even though std::vector<float> already defaults new elements to 0.0.

sherpa-onnx/csrc/offline-whisper-model-config.cc (2)

93-115: LGTM! Comprehensive enum-to-string conversion.

The ToString function covers all Whisper model types with a defensive fallback for unexpected values.


117-133: LGTM! Appropriate error handling for unknown model types.

The function correctly exits on unknown model types, and the unreachable return statement (line 132) is a common pattern to satisfy compiler requirements after a no-return call.

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

232-232: LGTM! Proper addition of Ascend Whisper model source.

The new source file is correctly added to the build configuration within the Ascend NPU conditional block, consistent with other Ascend backend sources.

sherpa-onnx/csrc/offline-whisper-model.cc (1)

27-27: Excellent refactoring to centralize feature normalization!

Delegating to the NormalizeWhisperFeatures utility function eliminates code duplication and improves maintainability while preserving the public API.

Also applies to: 452-455

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

58-73: LGTM!

The new WhisperModelType enum and utility functions are cleanly added. The enum class provides type safety, and the comment clarifies the intended use across multiple backends (ascend/rknn/qnn/axera).

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

159-165: LGTM!

The Whisper model integration for Ascend NPU follows the established pattern used for SenseVoice, Paraformer, and Zipformer CTC models. The error message is appropriately updated to reflect the new capability.


496-503: LGTM!

The Manager-based Create overload is consistently updated to match the non-Manager overload, maintaining symmetry across both API paths.

Comment thread sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
Comment on lines +85 to +97
int32_t num_frames = features.size() / feat_dim_;
if (num_frames > num_frames_) {
SHERPA_ONNX_LOGE(
"Number of input frames %d is too large. Truncate it to %d frames.",
num_frames, num_frames_);

SHERPA_ONNX_LOGE(
"Recognition result may be truncated/incomplete. Please select a "
"model accepting longer audios or use VAD to cut your audio into "
"small chunks.");
}

features.resize(num_frames_ * feat_dim_);

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 | 🟡 Minor

Frame truncation logic has issues.

  1. The warning is logged when num_frames > num_frames_ but the code at line 97 resizes regardless of the condition, potentially padding smaller inputs with zeros unnecessarily.

  2. The resize should only happen for truncation, not padding:

🔧 Suggested fix
     int32_t num_frames = features.size() / feat_dim_;
     if (num_frames > num_frames_) {
       SHERPA_ONNX_LOGE(
           "Number of input frames %d is too large. Truncate it to %d frames.",
           num_frames, num_frames_);

       SHERPA_ONNX_LOGE(
           "Recognition result may be truncated/incomplete. Please select a "
           "model accepting longer audios or use VAD to cut your audio into "
           "small chunks.");
+      features.resize(num_frames_ * feat_dim_);
+      num_frames = num_frames_;
     }

-    features.resize(num_frames_ * feat_dim_);
-
     // (num_frames_, feat_dim_) -> (feat_dim_, num_frames_)
-    features = Transpose(features.data(), num_frames_, feat_dim_);
+    features = Transpose(features.data(), num_frames, feat_dim_);

Note: If padding to num_frames_ is intentional for the model, the transpose call should still use num_frames_ not the original num_frames.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc around lines 85 -
97, The code always calls features.resize(num_frames_ * feat_dim_) even when
input is shorter, causing unintended padding; change the logic to compute an
explicit frames_to_use = std::min(num_frames, num_frames_) and only call
features.resize(num_frames_ * feat_dim_) when num_frames > num_frames_ (i.e.,
truncation), leaving smaller inputs unpadded unless padding is intentional;
update downstream uses (e.g., the transpose call and any buffer lengths) to use
frames_to_use (or num_frames_ only when you actually truncated) so the transpose
uses the correct frame count and the warning remains for truncation cases.

Comment thread sherpa-onnx/csrc/ascend/utils.h
Comment thread sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Jan 9, 2026

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sherpa-onnx/csrc/ascend/utils.cc (1)

254-313: Make output “size in MB” consistent with input (avoid integer truncation).

Inputs now print MB as floating-point (Line 269), but outputs still use integer division (Line 294), which truncates and may confuse debugging.

Proposed patch
-    os << " size out MB:    " << size_out_bytes / 1024 / 1024 << "\n";
+    os << " size out MB:    " << size_out_bytes / 1024. / 1024 << "\n";
🤖 Fix all issues with AI agents
In @sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc:
- Around line 238-241: In DetectLanguage, the return value from aclrtMemcpy
(assigned to ret) is not being checked; update the code after the aclrtMemcpy
call that copies from logits_ptr_ to logits_cpu_.data() to validate ret using
SHERPA_ONNX_ASCEND_CHECK(ret) (consistent with other memcpy calls in this file),
ensuring the memcpy arguments remain the same and the check triggers the
existing error handling on failure.

In @sherpa-onnx/csrc/ascend/utils.h:
- Around line 180-197: The move-assignment currently tests self-move by
comparing the wrapped pointer (other == *this) which checks pointer equality,
not object identity; change the self-move guard in
AclDataBuffer::operator=(AclDataBuffer&&) to check object identity (if (&other
== this) return *this), keep Release() and pointer transfer logic, and mark both
the move constructor AclDataBuffer(AclDataBuffer&&) and move-assignment
AclDataBuffer& operator=(AclDataBuffer&&) as noexcept to reflect that they
cannot throw.

In @sherpa-onnx/csrc/math.cc:
- Around line 145-153: The function MaxElementIndex currently calls Eigen::Map
and vec.maxCoeff without handling n==0; add a precondition check or assert to
prevent undefined behavior: in MaxElementIndex(const float *v, int32_t n)
validate that n > 0 (e.g., assert(n > 0) or throw/return an error) before
mapping and calling vec.maxCoeff, and/or update the function comment to document
the requirement that n must be > 0; also consider adding a defensive check at
the call site (offline-whisper-model-ascend.cc:230) where logits_cpu_.size() is
passed to ensure the size is non-zero before calling MaxElementIndex.

In @sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h:
- Around line 75-89: In DecodeStream, remove the unused local memory_info and
add validation before computing num_frames: ensure feat_dim (from
s->FeatureDim()) is > 0 and that f.size() is divisible by feat_dim (e.g.,
f.size() % feat_dim == 0); if the checks fail, handle the error early (return or
set an error result on the OfflineStream) to avoid division by zero or silent
truncation, then proceed to call NormalizeWhisperFeatures(f.data(), num_frames,
feat_dim), model_->Run(std::move(f)), Convert(...), and s->SetResult(...) as
before.
- Around line 41-52: The Whisper implementation must override CreateStream(const
std::string &hotwords) to avoid invoking the base-class exit path; add a method
with signature std::unique_ptr<OfflineStream> CreateStream(const std::string
&hotwords) const override in OfflineRecognizerWhisperTplImpl that simply
constructs a WhisperTag with tag.dim = model_->FeatureDim() and returns
std::make_unique<OfflineStream>(tag), ignoring the hotwords string (no logging
or exit). This mirrors the existing CreateStream() implementation and prevents
process termination when users call the hotwords overload.
🧹 Nitpick comments (12)
sherpa-onnx/csrc/math.h (1)

164-167: Consider adding documentation for the new functions.

The declarations are clear, but adding brief documentation comments would help users understand:

  • What NormalizeWhisperFeatures does (Whisper-specific log-mel normalization)
  • What MaxElementIndex returns (index of max element)
  • Expected constraints (e.g., non-empty inputs)
sherpa-onnx/csrc/math.cc (1)

106-117: Clarify the intent of the disabled Eigen implementation.

The #if 0 block preserves an alternative Eigen-based implementation. If this is intentional (e.g., for future optimization or reference), consider adding a comment explaining why both versions exist. If it's no longer needed, removing it would reduce maintenance burden.

sherpa-onnx/csrc/ascend/utils.cc (1)

353-361: Destructor calls SHERPA_ONNX_ASCEND_CHECK via Release(): confirm it can’t throw.

Centralizing cleanup in Release() is good, but destructors must not throw; if SHERPA_ONNX_ASCEND_CHECK can throw, this becomes UB during stack unwinding. If it always exit()s, it’s “safe” but still abrupt—worth confirming/documenting.

sherpa-onnx/csrc/ascend/utils.h (1)

63-69: AclDevicePtr::Get<T>() is OK, but consider naming to reduce ambiguity.

Having both void *Get() const and template <typename T> T *Get() const works, but GetAs<T>() (or similar) tends to be clearer at call sites and avoids accidental void* usage + cast patterns.

sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h (1)

61-73: Init() log message looks like an error path.

SHERPA_ONNX_LOGE("use greedy_search"); on the supported path is confusing/noisy; consider INFO/DEBUG or remove.

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

95-115: IsMultilingual() looks fine; consider making “unsupported” unreachable at compile time if enum expands.

Right now, a new WhisperModelType value added later will fall through to the runtime error. If you want compile-time pressure, consider adding a default: with a static_assert pattern (or ensure exhaustive handling is enforced by warnings in your toolchain).


141-157: ParseWhisperModelType() exits; consider returning an error instead for library usage.

SHERPA_ONNX_EXIT(-1) is consistent with some CLI-style code, but if this is used in library contexts, returning std::optional<WhisperModelType> (or a status) may be friendlier.


159-253: Avoid 4 separate “sources of truth” for language mappings (drift risk).

You now have:

  • kLangToToken
  • kTokenToLang
  • kLanguageTokenIds
  • kLanguageCodes

Any mismatch will be painful to debug. Prefer generating the reverse map + the two vectors from a single static table (e.g., std::array<pair<string_view,int32_t>>), or at least add a one-time runtime sanity check (sizes match; every entry round-trips).

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (3)

297-307: Hardcoded device ID may limit multi-device scenarios.

The device ID is hardcoded to 0. Consider making this configurable via OfflineModelConfig if multi-NPU deployments are anticipated.


442-466: Pointer aliasing and offset calculation relies on sizeof(float) == sizeof(int32_t).

The code uses a single offset counter for both float* and int32_t* pointer arithmetic. This works because both types are 4 bytes on most platforms, but it's fragile and unclear. Consider using separate offset trackers or byte-based addressing for clarity and safety.

// Current: offset is used for both float* and int32_t* increments
float *start = ptr_->Get<float>();
int32_t *start_int32 = ptr_->Get<int32_t>();  // same memory location
int32_t offset = 0;

features_ptr_ = start + offset;       // float* + offset
offset += feat_dim_ * num_frames_;    // offset now in "float units"

token_ptr_ = start_int32 + offset;    // int32_t* + offset (reinterprets as int32 units)
♻️ Suggested approach

Use byte-based offset calculation to make the intent explicit:

char *base = reinterpret_cast<char*>(ptr_->Get<void>());
size_t byte_offset = 0;

features_ptr_ = reinterpret_cast<float*>(base + byte_offset);
byte_offset += feat_dim_ * num_frames_ * sizeof(float);

token_ptr_ = reinterpret_cast<int32_t*>(base + byte_offset);
byte_offset += sizeof(int32_t);
// ... etc

284-295: Consider adding bounds check for defensive coding.

While the caller currently ensures offset < n_text_ctx_ / 2, a bounds check here would prevent potential out-of-bounds writes if the calling logic changes in the future.

♻️ Optional defensive check
 void UpdateSelfKvCache() {
   int32_t offset = token_offset_mask_cpu_[1];
+  if (offset >= n_text_ctx_) {
+    SHERPA_ONNX_LOGE("UpdateSelfKvCache: offset %d >= n_text_ctx_ %d", offset, n_text_ctx_);
+    return;
+  }
   for (int32_t i = 0; i < n_text_layer_ * 2; ++i) {
sherpa-onnx/csrc/offline-whisper-model-config.h (1)

87-99: Token constant structs are declared but not used.

These structs define token constants that match the hardcoded values in InitSotSequence() (lines 369-399 in the .cc file). Consider using these structs in InitSotSequence() to centralize the token definitions and avoid duplication.

♻️ Example usage in InitSotSequence
void InitSotSequence() {
  if (IsMultilingual(model_type_)) {
    WhisperModelMultilingualTokens tokens;
    sot_sequence_ = {tokens.sot, 50259, tokens.transcribe, tokens.no_timestamps};
    eot_ = tokens.eot;
    translate_ = tokens.translate;
  } else {
    WhisperModelEnglishTokens tokens;
    sot_sequence_ = {tokens.sot, tokens.no_timestamps};
    eot_ = tokens.eot;
  }
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a645de9 and e2ba852.

📒 Files selected for processing (10)
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h
  • sherpa-onnx/csrc/ascend/utils.cc
  • sherpa-onnx/csrc/ascend/utils.h
  • sherpa-onnx/csrc/math.cc
  • sherpa-onnx/csrc/math.h
  • sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h
  • sherpa-onnx/csrc/offline-whisper-decoder.h
  • sherpa-onnx/csrc/offline-whisper-model-config.cc
  • sherpa-onnx/csrc/offline-whisper-model-config.h
💤 Files with no reviewable changes (1)
  • sherpa-onnx/csrc/offline-whisper-decoder.h
🧰 Additional context used
🧬 Code graph analysis (6)
sherpa-onnx/csrc/ascend/utils.cc (1)
sherpa-onnx/csrc/ascend/utils.h (1)
  • AclDataBuffer (172-203)
sherpa-onnx/csrc/ascend/utils.h (3)
sherpa-onnx/csrc/ascend/utils.cc (6)
  • Get (128-128)
  • Get (128-128)
  • AclDataBuffer (344-351)
  • AclDataBuffer (353-353)
  • Release (355-361)
  • Release (355-355)
sherpa-onnx/csrc/axcl/utils.h (1)
  • Get (45-45)
sherpa-onnx/csrc/axcl/utils.cc (2)
  • Release (24-37)
  • Release (24-24)
sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h (4)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h (1)
  • sherpa_onnx (13-36)
sherpa-onnx/csrc/offline-whisper-model.cc (2)
  • tokens (106-128)
  • tokens (108-110)
sherpa-onnx/csrc/offline-recognizer-impl.h (1)
  • std (36-40)
sherpa-onnx/csrc/math.cc (2)
  • NormalizeWhisperFeatures (101-143)
  • NormalizeWhisperFeatures (101-102)
sherpa-onnx/csrc/offline-whisper-model-config.h (1)
sherpa-onnx/csrc/offline-whisper-model-config.cc (16)
  • ToString (82-93)
  • ToString (82-82)
  • ToString (117-139)
  • ToString (117-117)
  • IsMultilingual (95-115)
  • IsMultilingual (95-95)
  • ParseWhisperModelType (141-157)
  • ParseWhisperModelType (141-141)
  • GetWhisperLanguageTokenId (159-190)
  • GetWhisperLanguageTokenId (159-159)
  • GetWhisperLanguageCode (192-222)
  • GetWhisperLanguageCode (192-192)
  • GetAllWhisperLanguageTokenIds (224-238)
  • GetAllWhisperLanguageTokenIds (224-224)
  • GetAllWhisperLanguageCodes (240-253)
  • GetAllWhisperLanguageCodes (240-240)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (2)
sherpa-onnx/csrc/offline-whisper-model-config.cc (14)
  • ParseWhisperModelType (141-157)
  • ParseWhisperModelType (141-141)
  • IsMultilingual (95-115)
  • IsMultilingual (95-95)
  • GetWhisperLanguageTokenId (159-190)
  • GetWhisperLanguageTokenId (159-159)
  • GetWhisperLanguageCode (192-222)
  • GetWhisperLanguageCode (192-192)
  • GetAllWhisperLanguageTokenIds (224-238)
  • GetAllWhisperLanguageTokenIds (224-224)
  • ToString (82-93)
  • ToString (82-82)
  • ToString (117-139)
  • ToString (117-117)
sherpa-onnx/csrc/math.cc (4)
  • Transpose (62-76)
  • Transpose (62-62)
  • MaxElementIndex (145-153)
  • MaxElementIndex (145-145)
sherpa-onnx/csrc/math.h (1)
sherpa-onnx/csrc/math.cc (4)
  • NormalizeWhisperFeatures (101-143)
  • NormalizeWhisperFeatures (101-102)
  • MaxElementIndex (145-153)
  • MaxElementIndex (145-145)
⏰ 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). (17)
  • GitHub Check: Release shared-OFF tts-OFF
  • GitHub Check: Release shared-ON tts-ON
  • GitHub Check: Release shared-ON tts-OFF
  • GitHub Check: Debug shared-OFF tts-ON
  • GitHub Check: Debug shared-OFF tts-OFF
  • GitHub Check: Release shared-OFF tts-ON
  • GitHub Check: Debug shared-ON tts-OFF
  • GitHub Check: Debug shared-ON tts-ON
  • GitHub Check: swift (macos-15-intel)
  • GitHub Check: rknn shared ON
  • GitHub Check: rknn shared OFF
  • GitHub Check: ubuntu-24.04 3.13
  • GitHub Check: ubuntu-24.04 3.11
  • GitHub Check: ubuntu-24.04 3.10
  • GitHub Check: ubuntu-24.04 3.12
  • GitHub Check: ubuntu-24.04 3.9
  • GitHub Check: ubuntu-24.04 3.8
🔇 Additional comments (10)
sherpa-onnx/csrc/math.cc (1)

119-142: LGTM: The scalar implementation is correct.

The normalization logic properly implements the Whisper feature preprocessing:

  1. Clamps values to prevent log of zero/negative
  2. Computes log10 transformation
  3. Applies max-relative threshold (-8 dB)
  4. Scales to [0, 1] range approximately

The initialization of max_v = -1e20 is appropriate as a sentinel value for max computation.

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.h (1)

15-34: The template constructor is properly defined and explicitly instantiated — no issue here.

The template constructor is defined in the .cc file (lines 265–268) with explicit instantiations for AAssetManager and NativeResourceManager (lines 282–289). This avoids the ODR risk you mentioned.

OfflineWhisperDecoderResult is available through the include chain: the header includes offline-model-config.h, which includes offline-whisper-model-config.h where the result type is defined.

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (5)

32-37: LGTM!

The UpdateCausalMask helper function correctly fills the mask array with 0s up to offset and 1s from offset to capacity, which aligns with the masking semantics (masked=1, unmasked=0).


39-57: LGTM!

The parsing logic correctly validates the input format (expects {model_type}-mel) and delegates to ParseWhisperModelType for the actual model type parsing. Error handling is consistent with the project's patterns.


170-191: LGTM!

The decoding loop correctly implements autoregressive token generation: sets the current token, runs the decoder to predict the next token, updates the causal mask, and increments the offset. The termination conditions (offset limit and EOT token) are properly handled.


333-366: LGTM!

The decoder buffer initialization correctly sets up input and output buffers matching the expected model input/output structure documented at line 539.


660-690: LGTM!

The public API implementation correctly forwards to the Impl class following the PIMPL pattern, with appropriate template instantiations for Android and OHOS platforms.

sherpa-onnx/csrc/offline-whisper-model-config.h (3)

8-8: LGTM!

Added <vector> include is required for the new std::vector return types in the declared functions.


59-63: LGTM!

The OfflineWhisperDecoderResult struct provides a clean data structure for decoder output containing token IDs and detected language.


65-85: LGTM!

The WhisperModelType enum covers all official Whisper model variants, and the utility function declarations properly expose model type conversion, language token lookup, and multilingual detection capabilities.

Comment thread sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
Comment on lines +180 to 197
AclDataBuffer(AclDataBuffer &&other) {
p_ = other.p_;
other.p_ = nullptr;
}
AclDataBuffer &operator=(AclDataBuffer &&other) {
if (other == *this) {
return *this;
}

Release();

p_ = other.p_;
other.p_ = nullptr;
return *this;
}

void Release();

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 | 🟠 Major

Fix self-move guard in AclDataBuffer::operator=, and consider noexcept for move ops.

if (other == *this) compares underlying aclDataBuffer* via implicit conversion, not object identity; it can incorrectly treat two distinct wrappers as “self” if they (erroneously) hold the same pointer.

Proposed patch
-  AclDataBuffer(AclDataBuffer &&other) {
+  AclDataBuffer(AclDataBuffer &&other) noexcept {
     p_ = other.p_;
     other.p_ = nullptr;
   }
-  AclDataBuffer &operator=(AclDataBuffer &&other) {
-    if (other == *this) {
+  AclDataBuffer &operator=(AclDataBuffer &&other) noexcept {
+    if (this == &other) {
       return *this;
     }
🤖 Prompt for AI Agents
In @sherpa-onnx/csrc/ascend/utils.h around lines 180 - 197, The move-assignment
currently tests self-move by comparing the wrapped pointer (other == *this)
which checks pointer equality, not object identity; change the self-move guard
in AclDataBuffer::operator=(AclDataBuffer&&) to check object identity (if
(&other == this) return *this), keep Release() and pointer transfer logic, and
mark both the move constructor AclDataBuffer(AclDataBuffer&&) and
move-assignment AclDataBuffer& operator=(AclDataBuffer&&) as noexcept to reflect
that they cannot throw.

Comment thread sherpa-onnx/csrc/math.cc
Comment on lines +145 to +153
int32_t MaxElementIndex(const float *v, int32_t n) {
// Map raw pointer to an Eigen vector (no copy)
Eigen::Map<const Eigen::VectorXf> vec(v, n);

Eigen::Index maxIndex;
vec.maxCoeff(&maxIndex);

return static_cast<int32_t>(maxIndex);
}

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n -B3 -A1 'MaxElementIndex\s*\(' --type=cpp

Repository: k2-fsa/sherpa-onnx

Length of output: 1074


🏁 Script executed:

# Get more context around the MaxElementIndex call
sed -n '210,240p' sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc

# Also search for logits_cpu_ initialization
rg -n 'logits_cpu_' sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc -A 2 -B 2

# Search more broadly for any other calls we might have missed
rg -n 'MaxElementIndex' --type=cpp --type=h

Repository: k2-fsa/sherpa-onnx

Length of output: 2422


Add a defensive check or document the precondition for n > 0.

Calling maxCoeff on an empty Eigen vector (when n = 0) results in undefined behavior. The sole call site at offline-whisper-model-ascend.cc:230 passes logits_cpu_.size() directly without validating that it's non-empty. While the Whisper vocabulary size should always be positive in practice, add either a defensive check in MaxElementIndex (e.g., assert(n > 0)) or explicitly document that n must be greater than zero.

🤖 Prompt for AI Agents
In @sherpa-onnx/csrc/math.cc around lines 145 - 153, The function
MaxElementIndex currently calls Eigen::Map and vec.maxCoeff without handling
n==0; add a precondition check or assert to prevent undefined behavior: in
MaxElementIndex(const float *v, int32_t n) validate that n > 0 (e.g., assert(n >
0) or throw/return an error) before mapping and calling vec.maxCoeff, and/or
update the function comment to document the requirement that n must be > 0; also
consider adding a defensive check at the call site
(offline-whisper-model-ascend.cc:230) where logits_cpu_.size() is passed to
ensure the size is non-zero before calling MaxElementIndex.

Comment on lines +41 to +52
std::unique_ptr<OfflineStream> CreateStream() const override {
WhisperTag tag;
tag.dim = model_->FeatureDim();
return std::make_unique<OfflineStream>(tag);
}

void DecodeStreams(OfflineStream **ss, int32_t n) const override {
// batch decoding is not implemented yet
for (int32_t i = 0; i != n; ++i) {
DecodeStream(ss[i]);
}
}

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

🧩 Analysis chain

🏁 Script executed:

# Find the OfflineRecognizerImpl class definition
find . -type f -name "*.h" | xargs grep -l "class OfflineRecognizerImpl"

Repository: k2-fsa/sherpa-onnx

Length of output: 146


🏁 Script executed:

# Also search for CreateStream method overloads
rg "CreateStream\(" --type h -A 2 | head -100

Repository: k2-fsa/sherpa-onnx

Length of output: 7040


🏁 Script executed:

# Look at the Whisper implementation file more carefully
cat -n sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h | head -60

Repository: k2-fsa/sherpa-onnx

Length of output: 2389


🏁 Script executed:

# View the OfflineRecognizerImpl class definition with the CreateStream methods
cat -n sherpa-onnx/csrc/offline-recognizer-impl.h | grep -A 5 "CreateStream"

Repository: k2-fsa/sherpa-onnx

Length of output: 544


🏁 Script executed:

# Get more context around the CreateStream(hotwords) method
sed -n '1,100p' sherpa-onnx/csrc/offline-recognizer-impl.h | tail -50

Repository: k2-fsa/sherpa-onnx

Length of output: 1609


🏁 Script executed:

# Search for calls to CreateStream with a string parameter
rg "CreateStream\s*\(\s*['\"]" --type h --type cpp

Repository: k2-fsa/sherpa-onnx

Length of output: 234


🏁 Script executed:

# Check the public OfflineRecognizer API wrapper to see if it exposes CreateStream(hotwords)
cat -n sherpa-onnx/csrc/offline-recognizer.h | head -80

Repository: k2-fsa/sherpa-onnx

Length of output: 3117


🏁 Script executed:

# Get more of the OfflineRecognizer public API
cat -n sherpa-onnx/csrc/offline-recognizer.h | sed -n '79,150p'

Repository: k2-fsa/sherpa-onnx

Length of output: 2195


Override CreateStream(const std::string &hotwords) in Whisper implementation to prevent process termination.

The public API OfflineRecognizer::CreateStream(const std::string &hotwords) is exposed to users. Without an override in OfflineRecognizerWhisperTplImpl, calls to this method will invoke the base class implementation, which logs "Only transducer models support contextual biasing." and calls exit(-1), crashing the entire process. Even empty hotwords strings will trigger this.

🤖 Prompt for AI Agents
In @sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h around lines 41 - 52,
The Whisper implementation must override CreateStream(const std::string
&hotwords) to avoid invoking the base-class exit path; add a method with
signature std::unique_ptr<OfflineStream> CreateStream(const std::string
&hotwords) const override in OfflineRecognizerWhisperTplImpl that simply
constructs a WhisperTag with tag.dim = model_->FeatureDim() and returns
std::make_unique<OfflineStream>(tag), ignoring the hotwords string (no logging
or exit). This mirrors the existing CreateStream() implementation and prevents
process termination when users call the hotwords overload.

Comment on lines +75 to +89
void DecodeStream(OfflineStream *s) const {
auto memory_info =
Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault);

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

NormalizeWhisperFeatures(f.data(), num_frames, feat_dim);

auto r = model_->Run(std::move(f));
auto res = Convert(r, symbol_table_);

s->SetResult(res);
}

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 | 🟠 Major

Remove dead code and guard feature dimensions before division.

  • memory_info is unused.
  • num_frames = f.size() / feat_dim should guard feat_dim > 0 (and ideally f.size() % feat_dim == 0) to avoid UB / silent truncation.
Proposed patch
   void DecodeStream(OfflineStream *s) const {
-    auto memory_info =
-        Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault);
-
     int32_t feat_dim = s->FeatureDim();
+    if (feat_dim <= 0) {
+      SHERPA_ONNX_LOGE("Invalid feature dim: %d", feat_dim);
+      SHERPA_ONNX_EXIT(-1);
+    }
     std::vector<float> f = s->GetFrames();
+    if (f.size() % feat_dim != 0) {
+      SHERPA_ONNX_LOGE("Invalid feature vector size: %zu for dim %d", f.size(),
+                       feat_dim);
+      SHERPA_ONNX_EXIT(-1);
+    }
     int32_t num_frames = f.size() / feat_dim;
🤖 Prompt for AI Agents
In @sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h around lines 75 - 89,
In DecodeStream, remove the unused local memory_info and add validation before
computing num_frames: ensure feat_dim (from s->FeatureDim()) is > 0 and that
f.size() is divisible by feat_dim (e.g., f.size() % feat_dim == 0); if the
checks fail, handle the error early (return or set an error result on the
OfflineStream) to avoid division by zero or silent truncation, then proceed to
call NormalizeWhisperFeatures(f.data(), num_frames, feat_dim),
model_->Run(std::move(f)), Convert(...), and s->SetResult(...) as before.

@csukuangfj csukuangfj changed the title WIP: Add C++ runtime for Whisper with Ascend NPU Add C++ runtime for Whisper with Ascend NPU Jan 9, 2026
@csukuangfj
csukuangfj requested a review from Copilot January 9, 2026 07:47

@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

🤖 Fix all issues with AI agents
In @sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc:
- Around line 241-243: The aclrtMemcpy call that copies device logits to
logits_cpu_ assigns the result to ret but omits the standard error check; add
the same error handling used elsewhere by passing ret into
SHERPA_ONNX_ASCEND_CHECK(...) immediately after the aclrtMemcpy that uses
logits_cpu_.data(), logits_cpu_.size() * sizeof(float), logits_ptr_, and
ACL_MEMCPY_DEVICE_TO_HOST so failures are caught consistently with other memcpy
calls.
🧹 Nitpick comments (4)
sherpa-onnx/csrc/ascend/utils.h (1)

180-194: Move semantics are correct; consider adding noexcept for STL optimization.

The move constructor and move assignment properly transfer ownership and null the source. However, adding noexcept would enable move optimizations in STL containers (e.g., std::vector reallocation).

Optional: Add noexcept specifiers
-  AclDataBuffer(AclDataBuffer &&other) {
+  AclDataBuffer(AclDataBuffer &&other) noexcept {
     p_ = other.p_;
     other.p_ = nullptr;
   }
-  AclDataBuffer &operator=(AclDataBuffer &&other) {
+  AclDataBuffer &operator=(AclDataBuffer &&other) noexcept {
     if (this == &other) {
       return *this;
     }

     Release();

     p_ = other.p_;
     other.p_ = nullptr;
     return *this;
   }
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (3)

174-180: Signed/unsigned comparison warning.

Comparing int32_t i with sot_sequence.size() (which returns size_t) may trigger compiler warnings. Consider using size_t for the loop variable or casting.

Optional: Use size_t for loop index
-    for (int32_t i = 0; i < sot_sequence.size(); ++i) {
+    for (size_t i = 0; i < sot_sequence.size(); ++i) {

300-310: Device ID is hardcoded to 0.

Consider making the device ID configurable via OfflineModelConfig to support multi-device deployments in the future.


445-469: Mixed pointer arithmetic relies on sizeof(float) == sizeof(int32_t).

The code reinterprets the same memory block as both float* and int32_t*, using a single offset variable for pointer arithmetic on both types. This works because both types are 4 bytes, but it's fragile and potentially confusing.

Consider using byte offsets with explicit casting, or adding a static assertion to document and enforce the assumption:

Optional: Add static assertion to document assumption
static_assert(sizeof(float) == sizeof(int32_t),
              "Preallocate() offset arithmetic assumes float and int32_t have equal size");
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2ba852 and 36edb38.

📒 Files selected for processing (3)
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
  • sherpa-onnx/csrc/ascend/utils.h
  • sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • sherpa-onnx/csrc/offline-recognizer-whisper-tpl-impl.h
🧰 Additional context used
🧬 Code graph analysis (2)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (2)
sherpa-onnx/csrc/offline-whisper-model-config.cc (14)
  • ParseWhisperModelType (141-157)
  • ParseWhisperModelType (141-141)
  • IsMultilingual (95-115)
  • IsMultilingual (95-95)
  • GetWhisperLanguageTokenId (159-190)
  • GetWhisperLanguageTokenId (159-159)
  • GetWhisperLanguageCode (192-222)
  • GetWhisperLanguageCode (192-192)
  • GetAllWhisperLanguageTokenIds (224-238)
  • GetAllWhisperLanguageTokenIds (224-224)
  • ToString (82-93)
  • ToString (82-82)
  • ToString (117-139)
  • ToString (117-117)
sherpa-onnx/csrc/math.cc (4)
  • Transpose (62-76)
  • Transpose (62-62)
  • MaxElementIndex (145-153)
  • MaxElementIndex (145-145)
sherpa-onnx/csrc/ascend/utils.h (3)
sherpa-onnx/csrc/ascend/utils.cc (6)
  • Get (128-128)
  • Get (128-128)
  • AclDataBuffer (344-351)
  • AclDataBuffer (353-353)
  • Release (355-361)
  • Release (355-355)
sherpa-onnx/csrc/axcl/utils.h (1)
  • Get (45-45)
sherpa-onnx/csrc/axcl/utils.cc (2)
  • Release (24-37)
  • Release (24-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). (17)
  • GitHub Check: Release shared-OFF tts-OFF
  • GitHub Check: Debug shared-OFF tts-ON
  • GitHub Check: Debug shared-ON tts-ON
  • GitHub Check: Release shared-ON tts-ON
  • GitHub Check: Debug shared-OFF tts-OFF
  • GitHub Check: Release shared-OFF tts-ON
  • GitHub Check: Release shared-ON tts-OFF
  • GitHub Check: ubuntu-24.04 3.9
  • GitHub Check: ubuntu-24.04 3.8
  • GitHub Check: ubuntu-24.04 3.13
  • GitHub Check: ubuntu-24.04 3.11
  • GitHub Check: ubuntu-24.04 3.12
  • GitHub Check: ubuntu-24.04 3.10
  • GitHub Check: rknn shared ON
  • GitHub Check: rknn shared OFF
  • GitHub Check: swift (macos-latest)
  • GitHub Check: swift (macos-15-intel)
🔇 Additional comments (8)
sherpa-onnx/csrc/ascend/utils.h (2)

65-68: LGTM! Type-safe accessor provides convenient typed access.

The templated Get<T>() complements the existing void* Get() and will be useful for avoiding repeated casts at call sites.


196-196: LGTM!

The Release() declaration aligns with the implementation in utils.cc which properly destroys the buffer and nullifies the pointer.

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (6)

32-37: LGTM!

The mask update logic correctly sets unmasked (0) for positions before offset and masked (1) for positions from offset onwards.


39-57: LGTM!

The parsing logic correctly validates the expected format and delegates to ParseWhisperModelType for the model type extraction.


501-536: LGTM!

The encoder post-initialization properly extracts dimensions from model shapes and validates the batch size constraint.


538-578: LGTM!

The decoder post-initialization correctly validates input count and dimension consistency with the encoder.


614-661: LGTM!

Member variables are properly initialized with sensible defaults. The raw pointer members (lines 639-647) are correctly documented as pointing into preallocated device memory, clarifying that they don't need separate cleanup.


663-691: LGTM!

Clean PIMPL pattern implementation with proper forwarding. The explicit template instantiations for Android and OHOS platforms are correctly guarded.

Comment thread sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
@csukuangfj
csukuangfj merged commit 151dd9c into k2-fsa:master Jan 9, 2026
24 of 27 checks passed
@csukuangfj
csukuangfj deleted the cpp-whisper-ascend branch January 9, 2026 07:56

@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: 0

🧹 Nitpick comments (3)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (3)

87-197: LGTM: Main inference logic is well-structured.

The Run method correctly handles Whisper's encoder-decoder inference flow, including multilingual models, language detection, and SOT sequence processing. Thread safety is ensured via mutex.

Optional: Document frame rate assumption.

Line 112 assumes a frame rate of 100 Hz (10ms frames) when estimating token count. Consider adding a comment to clarify this assumption:

// Assume at most 6 tokens per second; frame rate is 100 Hz (10ms frames)
int32_t num_possible_tokens = num_frames / 100.0 * 6;

418-501: Memory layout assumes sizeof(float) == sizeof(int32_t).

The Preallocate method uses a single offset counter for both float* and int32_t* pointer arithmetic (lines 447-467). This implicitly assumes sizeof(float) == sizeof(int32_t), which is true on all modern platforms but not guaranteed by the C++ standard.

Consider adding a static assertion to make this assumption explicit:

static_assert(sizeof(float) == sizeof(int32_t), 
              "Preallocate assumes float and int32_t have the same size");

Alternatively, use separate offset counters or byte-based offset calculations for better clarity.


202-287: LGTM: Encoder/decoder execution is correctly implemented.

The execution paths properly handle ACL model invocation, memory transfers, and KV cache updates for Whisper's autoregressive decoding.

Optional: Add runtime validation of model outputs.

Consider adding runtime checks to verify that encoder/decoder outputs match expected shapes, especially for cross_kv_ptr_, delta_kv_ptr_, and logits_ptr_. This would catch model mismatches early:

// In RunEncoder, after aclmdlExecute:
if (output_dataset.GetSize() != cross_kv_ptr_.size()) {
  SHERPA_ONNX_LOGE("Encoder output count mismatch");
  SHERPA_ONNX_EXIT(-1);
}

While the shapes are validated during initialization, runtime checks add an extra safety layer.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36edb38 and 7a197e8.

📒 Files selected for processing (2)
  • sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc
  • sherpa-onnx/csrc/math.cc
🧰 Additional context used
🧬 Code graph analysis (2)
sherpa-onnx/csrc/math.cc (2)
sherpa-onnx/csrc/offline-whisper-model.cc (2)
  • features (98-104)
  • features (98-98)
sherpa-onnx/csrc/offline-dolphin-model.cc (4)
  • features (52-61)
  • features (52-53)
  • features (67-78)
  • features (67-68)
sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (2)
sherpa-onnx/csrc/offline-whisper-model-config.cc (14)
  • ParseWhisperModelType (141-157)
  • ParseWhisperModelType (141-141)
  • IsMultilingual (95-115)
  • IsMultilingual (95-95)
  • GetWhisperLanguageTokenId (159-190)
  • GetWhisperLanguageTokenId (159-159)
  • GetWhisperLanguageCode (192-222)
  • GetWhisperLanguageCode (192-192)
  • GetAllWhisperLanguageTokenIds (224-238)
  • GetAllWhisperLanguageTokenIds (224-224)
  • ToString (82-93)
  • ToString (82-82)
  • ToString (117-139)
  • ToString (117-117)
sherpa-onnx/csrc/math.cc (4)
  • Transpose (62-76)
  • Transpose (62-62)
  • MaxElementIndex (120-128)
  • MaxElementIndex (120-120)
🔇 Additional comments (5)
sherpa-onnx/csrc/math.cc (2)

101-118: LGTM! Correct Whisper feature normalization.

The implementation correctly follows the Whisper log-mel spectrogram normalization as documented in the PyTorch reference comments. The Eigen-based approach is efficient and the in-place modification is appropriate for this use case.


120-128: LGTM! Simple and correct implementation.

The function correctly wraps Eigen's maxCoeff to find the maximum element index. The cast from Eigen::Index to int32_t is safe in practice since the input parameter n is already int32_t, ensuring the returned index will fit.

sherpa-onnx/csrc/ascend/offline-whisper-model-ascend.cc (3)

34-57: LGTM: Helper functions are well-implemented.

Both UpdateCausalMask and ParseWhisperModelFromString are straightforward, with appropriate error handling and validation.


302-614: LGTM: Initialization logic is thorough and well-validated.

The initialization sequence properly:

  • Sets up ACL device and context
  • Loads encoder/decoder models from files or memory buffers
  • Validates model shapes and dimensions
  • Allocates and configures buffers
  • Sets up SOT sequences based on model type

Debug logging throughout aids troubleshooting.


665-694: LGTM: Public interface cleanly implements the PIMPL pattern.

The forwarding to Impl is straightforward, and the templated constructor with explicit instantiations for Android and OHOS platforms enables platform-specific asset loading.

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 pull request adds C++ runtime support for Whisper speech recognition models on Ascend NPU hardware, enabling efficient offline transcription with language detection capabilities.

Key Changes:

  • Templated Whisper recognizer implementation supporting different backend hardware
  • Centralized Whisper feature normalization utilities
  • Multilingual support with language token mappings and automatic language detection

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
offline-whisper-model.cc Refactors feature normalization to use centralized utility function
offline-whisper-model-config.h Adds shared types (OfflineWhisperDecoderResult, WhisperModelType enum), language utilities, and token definitions
offline-whisper-model-config.cc Implements model type parsing, language token mappings, and multilingual support utilities
offline-whisper-decoder.h Removes OfflineWhisperDecoderResult struct (moved to config header)
math.h / math.cc Adds NormalizeWhisperFeatures and MaxElementIndex utility functions
offline-recognizer-whisper-tpl-impl.h New templated recognizer implementation for hardware-agnostic Whisper support
offline-recognizer-impl.cc Integrates Ascend Whisper support into recognizer factory
ascend/offline-whisper-model-ascend.h / .cc Complete Ascend NPU implementation with encoder/decoder execution and language detection
ascend/utils.h / .cc Adds move semantics to AclDataBuffer and fixes floating-point division for size display
ascend/offline-zipformer-ctc-model-ascend.cc Fixes uninitialized buffer by explicitly initializing resized vector elements to 0
CMakeLists.txt Adds offline-whisper-model-ascend.cc to build when Ascend NPU is enabled
Comments suppressed due to low confidence (1)

sherpa-onnx/csrc/math.cc:143

  • Dead code wrapped in preprocessor directive. The Eigen-based implementation is disabled with #if 0 and a manual loop implementation is used instead. If the Eigen version is not needed for future reference, this dead code should be removed. If it's being kept for comparison or debugging purposes, add a comment explaining why.

  using Eigen::ArrayXXf;
  using Eigen::Map;

  Map<ArrayXXf, Eigen::RowMajor> feats(features, num_frames, feat_dim);

  feats = feats.max(1e-10f).log10();

  float max_v = feats.maxCoeff() - 8.0f;

  feats = feats.max(max_v);
  feats = (feats + 4.0f) / 4.0f;
}

int32_t MaxElementIndex(const float *v, int32_t n) {
  // Map raw pointer to an Eigen vector (no copy)
  Eigen::Map<const Eigen::VectorXf> vec(v, n);

  Eigen::Index maxIndex;
  vec.maxCoeff(&maxIndex);

  return static_cast<int32_t>(maxIndex);
}

}  // namespace sherpa_onnx


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

return r;
}

private:

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

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

Duplicate private access specifier. There is already a private section starting at line 63, so this second private declaration at line 116 is redundant and should be removed.

Suggested change
private:

Copilot uses AI. Check for mistakes.
symbol_table_.ApplyBase64Decode();

if (config_.decoding_method == "greedy_search") {
SHERPA_ONNX_LOGE("use greedy_search");

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

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

Using LOGE (error log level) for informational message. When greedy_search is correctly configured, the message "use greedy_search" is logged as an error. This should use a debug or info log level instead, or be removed entirely if it's not needed.

Suggested change
SHERPA_ONNX_LOGE("use greedy_search");
SHERPA_ONNX_LOGI("use greedy_search");

Copilot uses AI. Check for mistakes.
}

// assume at most 6 tokens per second
int32_t num_possible_tokens = num_frames / 100.0 * 6;

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

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

Mixed integer and floating-point division may cause confusion. The expression num_frames / 100.0 * 6 divides an int32_t by a double, then multiplies by an integer, before assigning to an int32_t. This should be clarified, either by using num_frames * 6 / 100 or by making the intent explicit with a cast and comment.

Suggested change
int32_t num_possible_tokens = num_frames / 100.0 * 6;
int32_t num_possible_tokens = num_frames * 6 / 100;

Copilot uses AI. Check for mistakes.

RunEncoder(std::move(features));

// Note(fangjun): No need to intialize the self kv cache to 0

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

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

Spelling error in comment. "intialize" should be "initialize".

Suggested change
// Note(fangjun): No need to intialize the self kv cache to 0
// Note(fangjun): No need to initialize the self kv cache to 0

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +100
struct WhisperModelMultilingualTokens {
int32_t sot = 50258;
int32_t eot = 50257;
int32_t transcribe = 50359;
int32_t translate = 50358;
int32_t no_timestamps = 50363;
};

struct WhisperModelEnglishTokens {
int32_t sot = 50257;
int32_t eot = 50256;
int32_t no_timestamps = 50362;
};

Copilot AI Jan 9, 2026

Copy link

Choose a reason for hiding this comment

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

Unused struct definitions. The WhisperModelMultilingualTokens and WhisperModelEnglishTokens structs are defined but not used anywhere in this PR. The comment indicates they are for ascend/rknn/qnn/axera, but the Ascend implementation in this PR doesn't use them. Consider removing these if they're not needed yet, or document their intended future use more clearly.

Suggested change
struct WhisperModelMultilingualTokens {
int32_t sot = 50258;
int32_t eot = 50257;
int32_t transcribe = 50359;
int32_t translate = 50358;
int32_t no_timestamps = 50363;
};
struct WhisperModelEnglishTokens {
int32_t sot = 50257;
int32_t eot = 50256;
int32_t no_timestamps = 50362;
};

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants