Skip to content

Export nemotron-3.5-asr-streaming-0.6b to QNN - #3741

Merged
csukuangfj merged 3 commits into
k2-fsa:masterfrom
csukuangfj:nemotron-3.5-qnn
Jul 9, 2026
Merged

csukuangfj merged 3 commits into
k2-fsa:masterfrom
csukuangfj:nemotron-3.5-qnn

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Jul 9, 2026 •

Copy link
Copy Markdown
Collaborator

See #3671 #3664

cc @paoloantinori @JulianPscheid


See models at

In the following, we show how to run it on Xiaomi 17 Pro, which has Qualcomm SM8850


Usage

Download a model (with context binary files)

wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models-qnn-binary-3/sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms.tar.bz2
tar xvf sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms.tar.bz2
rm sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms.tar.bz2
ls -lh sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/
total 1271816
-rw-r--r--@  1 fangjun  staff    29M  9 Jul 13:55 decoder.bin
-rw-r--r--@  1 fangjun  staff   583M  9 Jul 13:55 encoder.bin
-rw-r--r--@  1 fangjun  staff    21B  9 Jul 13:55 info.txt
-rw-r--r--@  1 fangjun  staff   9.3M  9 Jul 13:55 joiner.bin
drwxr-xr-x@ 11 fangjun  staff   352B  9 Jul 13:55 test_wavs
-rw-r--r--@  1 fangjun  staff   128K  9 Jul 13:55 tokens.txt

Run it

Please follow https://k2-fsa.github.io/sherpa/onnx/qnn/run-executables-on-your-phone-binary.html


./sherpa-onnx \
   --model-type=nemo_transducer \
   --provider=qnn \
   --tokens=./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/tokens.txt \
   --transducer.qnn-backend-lib=./libQnnHtp.so \
   --transducer.qnn-system-lib=./libQnnSystem.so \
   --transducer.qnn-context-binary=./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/decoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/joiner.bin \
   ./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/test_wavs/de.wav

The logs are:

/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/parse-options.cc:Read:374 ./sherpa-onnx --model-type=nemo_transducer --provider=qnn --tokens=./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/tokens.txt --transducer.qnn-backend-lib=./libQnnHtp.so --transducer.qnn-system-lib=./libQnnSystem.so --transducer.qnn-context-binary=./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/decoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/joiner.bin ./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/test_wavs/en.wav

OnlineRecognizerConfig(feat_config=FeatureExtractorConfig(sampling_rate=16000, feature_dim=80, low_freq=20, high_freq=-400, dither=0, normalize_samples=True, snip_edges=False), model_config=OnlineModelConfig(transducer=OnlineTransducerModelConfig(encoder="", decoder="", joiner="", qnn_config=QnnConfig(backend_lib="./libQnnHtp.so", context_binary="./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/decoder.bin,./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/joiner.bin", system_lib="./libQnnSystem.so")), paraformer=OnlineParaformerModelConfig(encoder="", decoder=""), wenet_ctc=OnlineWenetCtcModelConfig(model="", chunk_size=16, num_left_chunks=4), zipformer2_ctc=OnlineZipformer2CtcModelConfig(model=""), nemo_ctc=OnlineNeMoCtcModelConfig(model=""), t_one_ctc=OnlineToneCtcModelConfig(model=""), provider_config=ProviderConfig(device=0, provider="qnn", cuda_config=CudaConfig(cudnn_conv_algo_search=1), trt_config=TensorrtConfig(trt_max_workspace_size=2147483647, trt_max_partition_iterations=10, trt_min_subgraph_size=5, trt_fp16_enable="True", trt_detailed_build_log="False", trt_engine_cache_enable="True", trt_engine_cache_path=".", trt_timing_cache_enable="True", trt_timing_cache_path=".",trt_dump_subgraphs="False" )), tokens="./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/tokens.txt", num_threads=1, warm_up=0, debug=False, model_type="nemo_transducer", modeling_unit="cjkchar", bpe_vocab=""), lm_config=OnlineLMConfig(model="", scale=0.5, lodr_scale=0.01, lodr_fst="", lodr_backoff_id=-1, shallow_fusion=True), endpoint_config=EndpointConfig(rule1=EndpointRule(must_contain_nonsilence=False, min_trailing_silence=2.4, min_utterance_length=0), rule2=EndpointRule(must_contain_nonsilence=True, min_trailing_silence=1.2, min_utterance_length=0), rule3=EndpointRule(must_contain_nonsilence=False, min_trailing_silence=0, min_utterance_length=20)), ctc_fst_decoder_config=OnlineCtcFstDecoderConfig(graph="", max_active=3000), enable_endpoint=True, max_active_paths=4, hotwords_score=1.5, hotwords_file="", decoding_method="greedy_search", blank_penalty=0, temperature_scale=2, rule_fsts="", rule_fars="", reset_encoder=False, hr=HomophoneReplacerConfig(lexicon="", rule_fsts=""))
Start to create recognizer
     0.0ms [WARN   ] QnnDsp <W> Initializing HtpProvider
/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/qnn/utils.cc:CopyGraphsInfo:548 version: 3
     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList
/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/qnn/utils.cc:CopyGraphsInfo:548 version: 3
     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList
/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/qnn/utils.cc:CopyGraphsInfo:548 version: 3
     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList
Recognizer created in 6.36714 s
/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/features.cc:AcceptWaveformImpl:111 Creating a resampler:
   in_sample_rate: 24000
   output_sample_rate: 16000

./sherpa-onnx-qnn-SM8850-binary-nemotron-3.5-asr-streaming-0.6b-1120ms/test_wavs/en.wav
Number of threads: 1, Elapsed seconds: 1.4, Audio duration (s): 3.8, Real time factor (RTF) = 1.4/3.8 = 0.37
Ask not what your country can do for you.  Ask what you can do for your country
{ "text": "Ask not what your country can do for you.  Ask what you can do for your country", "tokens": [" A", "sk", " not", " what", " your", " c", "ou", "n", "t", "ry", " can", " ", "do", " for", " you", ".", " ", " A", "sk", " what", " you", " can", " ", "do", " for", " your", " c", "ou", "n", "t", "ry"], "timestamps": [0.24, 0.48, 0.72, 0.96, 1.12, 1.20, 1.20, 1.20, 1.28, 1.28, 1.44, 1.60, 1.60, 1.76, 1.92, 2.24, 2.24, 2.40, 2.56, 2.64, 2.80, 2.88, 3.04, 3.04, 3.20, 3.36, 3.52, 3.52, 3.52, 3.60, 3.60], "ys_probs": [], "lm_probs": [], "context_scores": [], "segment": 0, "words": [], "start_time": 0.00, "is_final": false, "is_eof": false}

     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList
     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList
     0.0ms [WARN   ] QnnDsp <W> m_CFBCallbackInfoObj is not initialized, return emptyList

Summary by CodeRabbit

  • New Features

    • Added new export workflows for the Nemotron streaming ASR model, including per-chunk processing and QNN packaging.
    • Added tooling to generate, export, and validate ONNX/QNN model artifacts for streaming ASR.
    • Expanded QNN recognition support for language-specific prompts and streaming model handling.
  • Bug Fixes

    • Improved model export stability with stricter dependency versions and updated setup steps.
    • Updated result handling to better preserve timestamps and filter language-tag tokens in recognition output.

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 9, 2026
@csukuangfj
csukuangfj requested a review from Copilot July 9, 2026 09:03
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 816b8e61-def1-4700-b84b-d591d969a23f

📥 Commits

Reviewing files that changed from the base of the PR and between 8c83c46 and 3c02b87.

📒 Files selected for processing (8)
  • .github/scripts/export-qnn/Dockerfile-2.40
  • .github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py
  • .github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml
  • sherpa-onnx/csrc/online-stream.cc
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.h
  • sherpa-onnx/csrc/qnn/online-recognizer-nemo-transducer-qnn-impl.h
  • sherpa-onnx/csrc/qnn/online-recognizer-zipformer-transducer-qnn-impl.h
💤 Files with no reviewable changes (2)
  • sherpa-onnx/csrc/qnn/online-recognizer-zipformer-transducer-qnn-impl.h
  • .github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py
✅ Files skipped from review due to trivial changes (1)
  • sherpa-onnx/csrc/online-stream.cc
🚧 Files skipped from review as they are similar to previous changes (5)
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.h
  • .github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml
  • .github/scripts/export-qnn/Dockerfile-2.40
  • sherpa-onnx/csrc/qnn/online-recognizer-nemo-transducer-qnn-impl.h
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc

📝 Walkthrough

Walkthrough

This PR adds a QNN export pipeline for the nemotron-3.5-asr-streaming-0.6b model, including a new Dockerfile update, config generator script, and GitHub Actions workflows for ONNX/QNN export and conversion. It refactors the existing chunk-based ONNX export to a per-chunk-size matrix flow, adds wrapper/test scripts for encoder-decoder-joiner ONNX export and validation, and updates the QNN transducer runtime to support prompt-index-based multilingual prompting with language-tag token filtering in the recognizer.

Changes

Nemotron 3.5 ASR Streaming QNN Export & Runtime

Layer / File(s) Summary
QNN SDK Docker image and build trigger
.github/scripts/export-qnn/Dockerfile-2.40, .github/workflows/qnn-sdk-docker-build.yaml
Adds OCI metadata labels, reorders ARGs, adjusts apt/pip dependency install steps (pinning numpy<2, dropping pyyaml), and changes the docker build trigger branch to docker-qnn.
Build matrix generator script
.github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py
New script builds a Config dataclass per SoC/chunk-size/model-name combination and prints a JSON build matrix.
New QNN export workflow
.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml
New workflow exports ONNX models, generates the build matrix, and runs QNN conversion/packaging/upload jobs across encoder/decoder/joiner graphs and target architectures.
Per-chunk-size ONNX export workflow refactor
.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml
Converts the export workflow to a chunk_size_ms matrix job, updating export, test, collection, packaging, and HuggingFace publish steps to run per chunk size.
Chunk-size-driven ONNX export script
scripts/nemo/nemotron-3.5-asr-streaming-0.6b/export_onnx.py
Adds a --chunk-size-ms CLI argument and reworks main() to export/quantize a single chunk size instead of looping over all sizes.
QNN model wrapper/export script
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/run.sh, .../wrapper.py
Adds dependency setup and a wrapper script that monkey-patches NeMo's ConformerEncoder and exports encoder/decoder/joiner ONNX graphs with prompt-index support.
ONNX pipeline test script
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py, scripts/nemo/qnn/nemotron-speech-streaming-en-0.6b/test_onnx.py
Adds an end-to-end streaming ASR ONNX test script with feature extraction and greedy decoding, and renames a joiner output variable to logits in an existing test script.
QNN model prompt-index and language support
sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.{cc,h}, sherpa-onnx/csrc/online-stream.cc
Adds a language-to-prompt-ID dictionary, extends RunEncoder to accept and set prompt_index, adds GetLanguagePromptId, and reworks joiner/encoder shape validation with transpose detection.
Recognizer language-tag filtering
sherpa-onnx/csrc/qnn/online-recognizer-nemo-transducer-qnn-impl.h, .../online-recognizer-zipformer-transducer-qnn-impl.h
Wires prompt_index into DecodeStreams via the language stream option, removes blank-token insertion into result tokens, adds helpers to detect/filter language-tag tokens, and removes ys_probs propagation in the zipformer QNN conversion path.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as GitHub Actions Workflow
  participant Wrapper as wrapper.py
  participant ONNX as encoder/decoder/joiner.onnx
  participant Model as OnlineNemoTransducerModelQnn
  participant Recognizer as OnlineRecognizerNemoTransducerQnnImpl

  Workflow->>Wrapper: run export (chunk_size_ms, model_id)
  Wrapper->>ONNX: torch.onnx.export encoder/decoder/joiner
  Workflow->>Model: qnn-onnx-converter + QNN SDK tools
  Note over Model: loads context binaries, checks prompt_index input

  Recognizer->>Recognizer: derive prompt_index from language option
  Recognizer->>Model: RunEncoder(features, num_frames, states, prompt_index)
  Model->>Model: set "prompt_index" QNN input if supported
  Model-->>Recognizer: encoder_out (transposed if needed)
  Recognizer->>Recognizer: GetResult -> ContainsLanguageTag?
  alt language tag present
    Recognizer->>Recognizer: FilterLanguageTags(decoder_result)
  end
  Recognizer->>Recognizer: ConvertResult -> final text
Loading

Possibly related PRs

  • k2-fsa/sherpa-onnx#3671: Implements the same multilingual runtime plumbing for NeMo streaming, adding prompt_index/language-prompt handling in RunEncoder/GetLanguagePromptId and language-tag token filtering.
  • k2-fsa/sherpa-onnx#3728: Introduced the QNN Nemo transducer runtime components (online-nemo-transducer-model-qnn.* and recognizer headers) that this PR directly extends with prompt-index support.
  • k2-fsa/sherpa-onnx#3732: Modifies the same NeMo ONNX exporter (export_onnx.py) for chunk-size variants and encoder attention context sizing.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: exporting nemotron-3.5-asr-streaming-0.6b to QNN.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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 introduces support for exporting and running the Nemotron 3.5 ASR streaming model using Qualcomm QNN. The changes include Dockerfile updates, Python scripts for ONNX model export and testing, and C++ updates to handle language prompt IDs, model-specific transpositions, and language tag filtering. The review feedback highlights three key issues: a required cast of prompt_index to torch.int64 in wrapper.py to prevent a PyTorch ONNX export tracing error, the need to declare ANDROID_NDK_VERSION earlier in the Dockerfile before it is referenced in a LABEL instruction, and a missing defensive check in online-nemo-transducer-model-qnn.cc to avoid a potential division-by-zero crash.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +313 to +317
prompt.scatter_(
2,
prompt_index.view(batch_size, 1, 1).expand(-1, time_steps, -1),
1.0,
)

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

In PyTorch, the scatter_ operation requires the index tensor to be of type torch.int64 (or torch.long). Since prompt_index is passed as torch.int32 (declared on line 373), calling prompt.scatter_ directly will raise a RuntimeError: index elements must be long during ONNX export tracing. Casting prompt_index to torch.int64 before scattering resolves this issue.

Suggested change
prompt.scatter_(
2,
prompt_index.view(batch_size, 1, 1).expand(-1, time_steps, -1),
1.0,
)
prompt.scatter_(
2,
prompt_index.to(torch.int64).view(batch_size, 1, 1).expand(-1, time_steps, -1),
1.0,
)

Comment on lines 4 to 6
ARG QNN_SDK_VERSION=2.40.0.251030
ARG QNN_TOOLKIT_URL=https://huggingface.co/csukuangfj/qnn-toolkit/resolve/main/v${QNN_SDK_VERSION}.zip

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 ANDROID_NDK_VERSION argument is referenced in the LABEL instruction on line 10, but it is not declared until line 46. In Dockerfiles, ARG variables must be declared before they are used, otherwise they evaluate to empty strings. Declaring ARG ANDROID_NDK_VERSION earlier resolves this issue.

ARG QNN_SDK_VERSION=2.40.0.251030
ARG QNN_TOOLKIT_URL=https://huggingface.co/csukuangfj/qnn-toolkit/resolve/main/v${QNN_SDK_VERSION}.zip
ARG ANDROID_NDK_VERSION=29.0.14206865

Comment on lines +757 to 764
subsampling_factor_ = window_shift_ / num_encoder_frames_;
if (subsampling_factor_ <= 0) {
SHERPA_ONNX_LOGE(
"The joiner input '%s' should be [1, %d, 1]. Actual: [%d, %d, %d]",
joiner_encoder_input_name_.c_str(), encoder_out_dim_,
encoder_in_shape[0], encoder_in_shape[1], encoder_in_shape[2]);
"Invalid subsampling factor: %d (window_shift=%d, "
"num_encoder_frames=%d)",
subsampling_factor_, window_shift_, num_encoder_frames_);
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

If num_encoder_frames_ is calculated as 0 (e.g., due to an unexpected or malformed model output shape), the division window_shift_ / num_encoder_frames_ will cause a division-by-zero crash. Adding a defensive check for num_encoder_frames_ <= 0 before the division prevents this crash.

    if (num_encoder_frames_ <= 0) {
      SHERPA_ONNX_LOGE(
          "Invalid num_encoder_frames: %d. It must be greater than 0.",
          num_encoder_frames_);
      SHERPA_ONNX_EXIT(-1);
    }

    subsampling_factor_ = window_shift_ / num_encoder_frames_;
    if (subsampling_factor_ <= 0) {
      SHERPA_ONNX_LOGE(
          "Invalid subsampling factor: %d (window_shift=%d, "
          "num_encoder_frames=%d)",
          subsampling_factor_, window_shift_, num_encoder_frames_);
      SHERPA_ONNX_EXIT(-1);
    }

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 adds a QNN export/packaging pipeline for nemotron-3.5-asr-streaming-0.6b and updates the QNN NeMo transducer runtime to support multilingual prompting (prompt_index) and filter language-tag tokens from recognition results.

Changes:

  • Extend the QNN NeMo transducer path to pass per-stream language via prompt_index, and filter <xx> / <xx-YY> language-tag tokens from output.
  • Update Online/Offline stream option getters to support a fallback default value.
  • Add/adjust GitHub Actions workflows and helper scripts for exporting ONNX + producing QNN artifacts; update the QNN SDK Dockerfile metadata.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
sherpa-onnx/csrc/qnn/online-recognizer-nemo-transducer-qnn-impl.h Pass language prompt id into QNN encoder; filter language-tag tokens; adjust blank bootstrap behavior.
sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.h Extend QNN encoder API to accept prompt_index; add language→prompt-id helper declaration.
sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc Implement prompt_index input plumbing; derive encoder output layout; hardcode prompt dictionary.
sherpa-onnx/csrc/online-stream.h Change option getter to return default when missing (now returns std::string).
sherpa-onnx/csrc/online-stream.cc Implement updated GetOption(key, default) behavior.
sherpa-onnx/csrc/offline-stream.h Change option getter to return default when missing (now returns std::string).
sherpa-onnx/csrc/offline-stream.cc Implement updated GetOption(key, default) behavior.
scripts/nemo/qnn/nemotron-speech-streaming-en-0.6b/test_onnx.py Rename joiner output variable from log_probs to logits.
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/wrapper.py New exporter wrapper to produce QNN-friendly ONNX for Nemotron 3.5 streaming ASR.
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py New ONNX test driver for the exported Nemotron 3.5 streaming model.
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/run.sh New helper script to install deps and run the export wrapper.
scripts/nemo/nemotron-3.5-asr-streaming-0.6b/export_onnx.py Make export parameterized by chunk size and export one chunk size per run.
.github/workflows/qnn-sdk-docker-build.yaml Update the branch trigger name for the QNN SDK docker build workflow.
.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml Matrix-based chunk-size export; simplify collection/testing per chunk size.
.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml New workflow to export ONNX artifacts and convert them to QNN binaries/libs per SoC.
.github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py New build-matrix generator for the QNN workflow.
.github/scripts/export-qnn/Dockerfile-2.40 Improve QNN SDK container labels and dependency setup; pin numpy<2.

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

Comment thread sherpa-onnx/csrc/online-stream.h Outdated
Comment on lines +126 to +130
// Returns the value for the given key, or default_value if the key
// does not exist. No exception is thrown for missing keys.
const std::string &GetOption(const std::string &key) const;
std::string GetOption(const std::string &key,
const std::string &default_value = "") const;

Comment thread sherpa-onnx/csrc/offline-stream.h Outdated
Comment on lines +122 to +126
// Returns the value for the given key, or default_value if the key
// does not exist. No exception is thrown for missing keys.
const std::string &GetOption(const std::string &key) const;
std::string GetOption(const std::string &key,
const std::string &default_value = "") const;

Comment on lines 240 to 244
const auto &last = s->GetQnnResult();
if (!last.tokens.empty() && last.tokens.back() != blank_id_ &&
static_cast<int32_t>(last.tokens.size()) > 1) {
s->GetCurrentSegment() += 1;
}
Comment on lines 8 to 16
#include <algorithm>
#include <cmath>
#include <memory>
#include <numeric>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
Comment on lines +359 to +363
OnlineTransducerDecoderResultNoOrt ans;
ans.frame_offset = src.frame_offset;
ans.num_trailing_blanks = src.num_trailing_blanks;
ans.decoder_out = src.decoder_out;
ans.states = src.states;
Comment on lines +748 to +756
auto enc_out_shape = encoder_->TensorShape("encoder_out");
int64_t total = enc_out_shape[1] * enc_out_shape[2];
num_encoder_frames_ = static_cast<int32_t>(total / encoder_out_dim_);

// If encoder_out is [1, encoder_out_dim, num_frames], we need to
// transpose it to [1, num_frames, encoder_out_dim] before returning.
// If it is [1, num_frames, encoder_out_dim], no transpose needed.
encoder_out_needs_transpose_ = (enc_out_shape[1] == encoder_out_dim_);

Comment on lines +54 to +56
// Get the prompt ID for a given language (e.g., "en-US", "zh-CN").
// Returns 'auto' if the language is not found and logs available languages.
int32_t GetLanguagePromptId(const std::string &language) const;
Comment on lines +4 to +8
import json

from device_info import soc_info_dict
from dataclasses import asdict, dataclass
import itertools
Comment on lines +7 to +12
# See https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#labelling-container-images

LABEL org.opencontainers.image.title="QNN SDK ${QNN_SDK_VERSION}"
LABEL org.opencontainers.image.description="Pre-built development environment for Qualcomm AI Engine Direct (QNN) SDK ${QNN_SDK_VERSION}, used by sherpa-onnx to convert and run ASR/TTS models on Qualcomm Snapdragon chipsets (e.g. SM8850). Includes: Android NDK r29 (${ANDROID_NDK_VERSION}) for cross-compiling to aarch64-android; Python 3.10 virtualenv with PyTorch, ONNX, onnxruntime, and QNN Python dependencies; QNN CLI tools (qnn-onnx-converter, qnn-model-lib-generator, qnn-context-binary-generator, qnn-net-run) for quantizing ONNX models to QNN context binaries targeting HTP/DSP backends. Typical workflow: mount model files, source envsetup.sh, run qnn-onnx-converter to quantize, then qnn-context-binary-generator to produce .bin files deployable on-device."
LABEL org.opencontainers.image.source="https://github.com/k2-fsa/sherpa-onnx"
LABEL org.opencontainers.image.licenses="Apache-2.0"

@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

🧹 Nitpick comments (4)
.github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py (1)

27-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting why SM8350 is skipped.

The continue for SM8350 is undocumented. A brief comment would help future maintainers understand whether this is a permanent exclusion (e.g., unsupported by this model) or temporary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py around
lines 27 - 29, The SM8350 branch in the loop over soc_info_dict is an
undocumented exclusion, so add a brief comment near the existing continue in
generate_nemotron_35_asr_streaming.py explaining whether SM8350 is intentionally
unsupported or only skipped for now. Keep the note close to the model check so
future maintainers can understand the intent when reviewing the soc.model.name
comparison.
.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml (1)

1-420: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add an explicit permissions block to follow least-privilege and ensure release steps have required scopes.

The workflow has no top-level or job-level permissions block, so all jobs use the repository default token permissions. The release steps on lines 403–419 that omit repo_token rely on GITHUB_TOKEN and need contents: write to upload release assets. Without an explicit grant, these steps will fail if the repository defaults to read-only. Adding a top-level permissions: contents: read and job-specific overrides also addresses the excessive-permissions concern.

Proposed additions
 permissions:
   contents: read

 jobs:
   onnx:
     permissions:
       actions: write
   # generate_build_matrix inherits top-level (contents: read)
   qnn:
     permissions:
       contents: write
       actions: read
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml around
lines 1 - 420, The workflow is missing an explicit permissions policy, so the
default GITHUB_TOKEN scope may be too broad and the release upload steps in the
qnn job can fail without contents: write. Add a top-level permissions block to
set least-privilege defaults, then override permissions at the job level for qnn
so the Release steps can write release assets while the other jobs stay
read-only. Use the existing qnn, onnx, and generate_build_matrix job definitions
to place the permissions in the right scope.

Source: Linters/SAST tools

.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml (1)

52-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test step lacks output validation for recognition results.

The test step runs sherpa-onnx but does not assert or grep the output, so it only verifies the models load without crashing—not that recognition produces correct text. Consider adding a minimal assertion (e.g., checking for expected keywords in stdout) to catch silent regressions in exported models.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml around lines
52 - 95, The `Test ${{ matrix.chunk_size_ms }}ms onnx models` step only checks
that `sherpa-onnx` runs, not that it returns a valid transcription. Update this
workflow step to capture the CLI output from the `sherpa-onnx` invocations and
add a minimal assertion against stdout (for example, grep for an expected
keyword or non-empty recognized text) so the `chunk`/`src` model checks in the
test step verify recognition behavior, not just process startup.
scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py (1)

303-343: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace if True: with a debug flag.

The if True: block unconditionally dumps encoder/decoder/joiner .raw debug files and .txt manifests to disk. This is clearly a debug artifact that should be gated behind a --debug command-line argument to avoid cluttering the working directory during normal test runs.

♻️ Proposed fix
 def get_args():
     parser = argparse.ArgumentParser(
         formatter_class=argparse.ArgumentDefaultsHelpFormatter
     )

     parser.add_argument(
         "--wav",
         type=str,
         required=True,
     )
+    parser.add_argument(
+        "--debug",
+        action="store_true",
+        help="Dump intermediate tensors to .raw files",
+    )
     return parser.parse_args()
-    if True:
+    if args.debug:
         with open(f"{name}-encoder.txt", "w") as f:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py` around lines
303 - 343, The unconditional `if True:` block in `test_onnx.py` is a debug
artifact that always writes encoder/decoder/joiner `.raw` files and manifest
`.txt` files. Replace it with a proper debug gate, ideally driven by an existing
or new `--debug` CLI flag in the `main`/argument-parsing path, and wrap the
file-dumping logic in that condition so normal test runs do not clutter the
working directory. Use the existing `encoder_input_list`, `decoder_input_list`,
and `joiner_input_list` dumping section as the guarded block.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml:
- Line 75: The fallback after the `ls -lh *.raw` command is incorrect because
`head` may read from stdin and is not a safe no-op in this workflow. Update the
affected steps in the GitHub Actions workflow to use a harmless fallback such as
`|| true` instead of `|| head`, so the logging in this shell snippet remains
explicit and robust when no `.raw` files exist.
- Line 223: The workflow is referencing matrix.model_type even though the
generated build matrix from Config in generate_nemotron_35_asr_streaming.py does
not define that field, so the step name and artifact name resolve incorrectly.
Either add a meaningful model_type property to the Config dataclass and ensure
it is populated in the matrix generation, or remove matrix.model_type from the
“Run ${{ matrix.model_type }}” step and the artifact naming logic so both use
only defined matrix symbols.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml:
- Line 24: The checkout step currently relies on the default credential
persistence, which is unnecessary for this workflow. Update the
actions/checkout@v4 step to disable persisted GitHub credentials by setting
persist-credentials to false, since the workflow only reads the repo and does
not need to reuse the token later.

In `@sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc`:
- Around line 739-764: In online-nemo-transducer-model-qnn.cc, the shape-derived
calculations around encoder output handling in the encoder init logic are
missing validation for `encoder_out_dim_`, which can make the `total /
encoder_out_dim_` and `window_shift_ / num_encoder_frames_` divisions unsafe.
Add an early check after reading `encoder_in_shape[1]` in the initialization
path that verifies the dimension is positive before computing
`num_encoder_frames_`, `encoder_out_needs_transpose_`, and
`subsampling_factor_`, and fail fast with the existing error/exit pattern if the
shape is invalid.

---

Nitpick comments:
In @.github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py:
- Around line 27-29: The SM8350 branch in the loop over soc_info_dict is an
undocumented exclusion, so add a brief comment near the existing continue in
generate_nemotron_35_asr_streaming.py explaining whether SM8350 is intentionally
unsupported or only skipped for now. Keep the note close to the model check so
future maintainers can understand the intent when reviewing the soc.model.name
comparison.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml:
- Around line 1-420: The workflow is missing an explicit permissions policy, so
the default GITHUB_TOKEN scope may be too broad and the release upload steps in
the qnn job can fail without contents: write. Add a top-level permissions block
to set least-privilege defaults, then override permissions at the job level for
qnn so the Release steps can write release assets while the other jobs stay
read-only. Use the existing qnn, onnx, and generate_build_matrix job definitions
to place the permissions in the right scope.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml:
- Around line 52-95: The `Test ${{ matrix.chunk_size_ms }}ms onnx models` step
only checks that `sherpa-onnx` runs, not that it returns a valid transcription.
Update this workflow step to capture the CLI output from the `sherpa-onnx`
invocations and add a minimal assertion against stdout (for example, grep for an
expected keyword or non-empty recognized text) so the `chunk`/`src` model checks
in the test step verify recognition behavior, not just process startup.

In `@scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py`:
- Around line 303-343: The unconditional `if True:` block in `test_onnx.py` is a
debug artifact that always writes encoder/decoder/joiner `.raw` files and
manifest `.txt` files. Replace it with a proper debug gate, ideally driven by an
existing or new `--debug` CLI flag in the `main`/argument-parsing path, and wrap
the file-dumping logic in that condition so normal test runs do not clutter the
working directory. Use the existing `encoder_input_list`, `decoder_input_list`,
and `joiner_input_list` dumping section as the guarded block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf35c0e1-ee4e-4c21-ac5b-c5a98b218b65

📥 Commits

Reviewing files that changed from the base of the PR and between f8ffa43 and 8c83c46.

📒 Files selected for processing (17)
  • .github/scripts/export-qnn/Dockerfile-2.40
  • .github/scripts/export-qnn/generate_nemotron_35_asr_streaming.py
  • .github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml
  • .github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml
  • .github/workflows/qnn-sdk-docker-build.yaml
  • scripts/nemo/nemotron-3.5-asr-streaming-0.6b/export_onnx.py
  • scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/run.sh
  • scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/test_onnx.py
  • scripts/nemo/qnn/nemotron-3.5-asr-streaming-0.6b/wrapper.py
  • scripts/nemo/qnn/nemotron-speech-streaming-en-0.6b/test_onnx.py
  • sherpa-onnx/csrc/offline-stream.cc
  • sherpa-onnx/csrc/offline-stream.h
  • sherpa-onnx/csrc/online-stream.cc
  • sherpa-onnx/csrc/online-stream.h
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc
  • sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.h
  • sherpa-onnx/csrc/qnn/online-recognizer-nemo-transducer-qnn-impl.h


ls -lh *.onnx*
ls -lh *.txt
ls -lh *.raw || head

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

|| head is not a meaningful fallback for ls and may hang on stdin.

When no .raw files exist, ls fails and head is executed without arguments, reading from stdin. In GitHub Actions stdin is typically empty so this returns immediately, but the pattern is fragile and confusing. Use || true instead.

Proposed fix
-          ls -lh *.raw || head
+          ls -lh *.raw || true

Also applies to: 252-252

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml at line
75, The fallback after the `ls -lh *.raw` command is incorrect because `head`
may read from stdin and is not a safe no-op in this workflow. Update the
affected steps in the GitHub Actions workflow to use a harmless fallback such as
`|| true` instead of `|| head`, so the logging in this shell snippet remains
explicit and robust when no `.raw` files exist.

run: |
df -h

- name: Run ${{ matrix.model_type }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

matrix.model_type is not defined in the generated build matrix.

The Config dataclass in generate_nemotron_35_asr_streaming.py (lines 11–17) defines soc, soc_id, arch, chunk_size_ms, and model_name — but not model_type. As a result, ${{ matrix.model_type }} expands to an empty string, producing a step name of "Run " on line 223 and an artifact name with a double dash (e.g., nemotron-3.5-asr-streaming-0.6b-SM8850-80--json) on line 378.

Either add a model_type field to the Config dataclass if it carries meaningful information, or remove the reference from both lines.

Also applies to: 378-378

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b-qnn.yaml at line
223, The workflow is referencing matrix.model_type even though the generated
build matrix from Config in generate_nemotron_35_asr_streaming.py does not
define that field, so the step name and artifact name resolve incorrectly.
Either add a meaningful model_type property to the Config dataclass and ensure
it is populated in the matrix generation, or remove matrix.model_type from the
“Run ${{ matrix.model_type }}” step and the artifact naming logic so both use
only defined matrix symbols.

chunk_size_ms: [80, 160, 320, 560, 1120]

steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Set persist-credentials: false on checkout to avoid credential persistence.

The actions/checkout@v4 action persists the GitHub token in .git/config by default. This workflow does not push back to the origin repository (HuggingFace publish clones a separate repo; release upload uses its own token), so persisting credentials is unnecessary and increases the attack surface if any step inadvertently exposes the .git directory.

🔒 Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 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
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 24-24: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/export-nemotron-3.5-asr-streaming-0.6b.yaml at line 24,
The checkout step currently relies on the default credential persistence, which
is unnecessary for this workflow. Update the actions/checkout@v4 step to disable
persisted GitHub credentials by setting persist-credentials to false, since the
workflow only reads the repo and does not need to reuse the token later.

Source: Linters/SAST tools

Comment on lines +739 to 764
encoder_out_dim_ = encoder_in_shape[1];

// Compute num_encoder_frames from the encoder output shape and
// encoder_out_dim (read from the joiner input above).
// encoder_out shape differs between models:
// nemotron-speech-streaming-en-0.6b: [1, num_frames, encoder_out_dim]
// nemotron-3.5-asr-streaming-0.6b: [1, encoder_out_dim, num_frames]
// We use encoder_out_dim to determine which dimension is num_frames
// and whether the encoder output needs to be transposed.
auto enc_out_shape = encoder_->TensorShape("encoder_out");
int64_t total = enc_out_shape[1] * enc_out_shape[2];
num_encoder_frames_ = static_cast<int32_t>(total / encoder_out_dim_);

// If encoder_out is [1, encoder_out_dim, num_frames], we need to
// transpose it to [1, num_frames, encoder_out_dim] before returning.
// If it is [1, num_frames, encoder_out_dim], no transpose needed.
encoder_out_needs_transpose_ = (enc_out_shape[1] == encoder_out_dim_);

subsampling_factor_ = window_shift_ / num_encoder_frames_;
if (subsampling_factor_ <= 0) {
SHERPA_ONNX_LOGE(
"The joiner input '%s' should be [1, %d, 1]. Actual: [%d, %d, %d]",
joiner_encoder_input_name_.c_str(), encoder_out_dim_,
encoder_in_shape[0], encoder_in_shape[1], encoder_in_shape[2]);
"Invalid subsampling factor: %d (window_shift=%d, "
"num_encoder_frames=%d)",
subsampling_factor_, window_shift_, num_encoder_frames_);
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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc"

# Show the surrounding function with line numbers.
sed -n '700,790p' "$file" | cat -n

# Search for related validation and tensor-shape handling in this file.
rg -n "encoder_out_dim_|num_encoder_frames_|window_shift_|TensorShape\\(\"encoder_out\"\\)|Invalid subsampling factor" "$file"

Repository: k2-fsa/sherpa-onnx

Length of output: 5874


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc"

# Inspect the encoder shape handling and earlier validation around window_shift_.
sed -n '540,590p' "$file" | cat -n

# Inspect the code that consumes encoder_out_dim_ and num_encoder_frames_.
sed -n '780,810p' "$file" | cat -n

# Search for any explicit guards on encoder_out_dim_ or num_encoder_frames_.
rg -n "encoder_out_dim_ <=|num_encoder_frames_ <=|encoder_out_dim_|num_encoder_frames_" "$file"

Repository: k2-fsa/sherpa-onnx

Length of output: 4883


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the TensorShape implementation and any documentation about returned dims.
rg -n "TensorShape\\(" -g '!**/build/**' -g '!**/dist/**' sherpa-onnx k2-fsa 2>/dev/null || true

# Try to locate the model wrapper that defines TensorShape.
fd -a ".*(model|tensor|qnn).*\\.(cc|h|hpp|cxx|hh)$" sherpa-onnx | head -n 50

# Search for handling of unknown / dynamic dimensions.
rg -n "dynamic|unknown|dim.*-1|shape.*-1|TensorShape.*int64_t|std::vector<int64_t>" sherpa-onnx/csrc 2>/dev/null || true

Repository: k2-fsa/sherpa-onnx

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the QNN shape API implementation and how shapes are represented.
sed -n '250,340p' sherpa-onnx/csrc/qnn/qnn-model.cc | cat -n
sed -n '1,120p' sherpa-onnx/csrc/qnn/qnn-model.h | cat -n

# Inspect the exact validation block in the Nemo QNN model.
sed -n '724,770p' sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc | cat -n

# Look for any explicit checks against zero / negative dims in nearby QNN model code.
rg -n "<= 0|< 0|dynamic|unknown|replace dynamic dims|TensorShape\\(\"encoder_out\"\\)|GetShape\\(" sherpa-onnx/csrc/qnn/*.cc sherpa-onnx/csrc/qnn/*.h

Repository: k2-fsa/sherpa-onnx

Length of output: 11108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect a similar QNN transducer model that already validates these derived sizes.
sed -n '260,340p' sherpa-onnx/csrc/qnn/offline-zipformer-transducer-model-qnn.cc | cat -n

# Inspect the same kind of logic in another QNN model for comparison.
sed -n '300,350p' sherpa-onnx/csrc/qnn/offline-parakeet-tdt-model-qnn.cc | cat -n

Repository: k2-fsa/sherpa-onnx

Length of output: 5883


Guard the derived shape divisions. encoder_in_shape[1] is still unchecked, so a zero/negative encoder_out_dim_ can make total / encoder_out_dim_ and then window_shift_ / num_encoder_frames_ unsafe. Add a positive-dimension check before those divisions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sherpa-onnx/csrc/qnn/online-nemo-transducer-model-qnn.cc` around lines 739 -
764, In online-nemo-transducer-model-qnn.cc, the shape-derived calculations
around encoder output handling in the encoder init logic are missing validation
for `encoder_out_dim_`, which can make the `total / encoder_out_dim_` and
`window_shift_ / num_encoder_frames_` divisions unsafe. Add an early check after
reading `encoder_in_shape[1]` in the initialization path that verifies the
dimension is positive before computing `num_encoder_frames_`,
`encoder_out_needs_transpose_`, and `subsampling_factor_`, and fail fast with
the existing error/exit pattern if the shape is invalid.

@csukuangfj
csukuangfj merged commit 9b33742 into k2-fsa:master Jul 9, 2026
3 of 27 checks passed
@csukuangfj
csukuangfj deleted the nemotron-3.5-qnn branch July 9, 2026 10:01
@paoloantinori

Copy link
Copy Markdown
Contributor

Hi Fangjun, thanks so much for the tag, and for all the Nemotron streaming work. We build Anti-Vocale, an on-device Italian voice-message transcriber for Android (sherpa-onnx under the hood); the multilingual Nemotron path is one of our streaming backends, and the per-stream language conditioning from #3671 was the real unlock for our multilingual users.

The QNN path is exciting to see. One thing that may be useful for planning: our primary test device is a Realme GT 6T, i.e. SM7675 / Snapdragon 7+ Gen 3, so the SM8850 binary won't run on it directly. We'd be very keen to try QNN once 7-Gen-3-class SoCs are in the build matrix, and happy to test an SM7675 build if that's ever on the roadmap.

Separately, thanks for the att_context_size fix in #3732; we're measuring its effect on Italian quality right now, and are glad to share WER numbers on real Italian voice messages if a multilingual data point is ever useful to you.

Thanks again, really appreciated.

@JulianPscheid

Copy link
Copy Markdown
Contributor

Glad to see NPU work landing on the Nemotron streaming path. On-device acceleration for the multilingual model on Android is something we'd love to use in Hedy.

The blocker for us is the per-SoC prebuilt context binary model. We ship to a really fragmented Android base: a long tail of Qualcomm SoCs, plus a big chunk of non-Qualcomm devices (MediaTek, Exynos, Tensor). Shipping and maintaining a separate prebuilt binary per SoC just isn't realistic for us, and since QNN wouldn't help the non-Qualcomm devices at all, we'd still be carrying the CPU ONNX path as the universal fallback anyway. So for us QNN ends up as a bonus for a slice of flagship Qualcomm users rather than something we could actually standardize on.

The thing that would flip that for us is a runtime-prepared path: one portable artifact that does the HTP graph prepare on-device at first run, instead of prebuilt per-SoC binaries. A one-time compile on first launch is fine. A per-SoC distribution matrix isn't.

Sharing this mainly in case it's useful for prioritization. For a broadly distributed consumer app, on-device prepare is what would make NPU acceleration realistic to adopt. For now we're staying on CPU ONNX. Thanks for all the Nemotron streaming work, the per-stream language conditioning has been a real unlock for our multilingual users.

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.

4 participants