Skip to content

Add C++ runtime for Parakeet TDT models with QNN#3720

Merged
csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:cpp-qnn-nemo-tdt
Jul 4, 2026
Merged

Add C++ runtime for Parakeet TDT models with QNN#3720
csukuangfj merged 1 commit into
k2-fsa:masterfrom
csukuangfj:cpp-qnn-nemo-tdt

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

See also #3719

Usage

The following shows how to run parakeet-tdt-0.6b-v2 and parakeet-tdt-0.6b-v3 on my Xiaomi 17 Pro with Qualcomm NPU using sherpa-onnx

https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2

Please download a QNN model for it from https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models-qnn-binary-2

The following is an example.

wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models-qnn-binary-2/sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer.tar.bz2

tar xvf sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer.tar.bz2
rm sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer.tar.bz2
ls -lh sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer
total 1246384
-rw-r--r--@ 1 fangjun  staff    14M  3 Jul 18:41 decoder.bin
-rw-r--r--@ 1 fangjun  staff   580M  3 Jul 18:41 encoder.bin
-rw-r--r--@ 1 fangjun  staff    15B  3 Jul 18:41 info.txt
-rw-r--r--@ 1 fangjun  staff   1.7M  3 Jul 18:41 joiner.bin
drwxr-xr-x@ 5 fangjun  staff   160B  3 Jul 18:41 test_wavs
-rw-r--r--@ 1 fangjun  staff   9.2K  3 Jul 18:41 tokens.txt

Now copy them to your device with Qualcomm NPU and follow
https://k2-fsa.github.io/sherpa/onnx/qnn/run-executables-on-your-phone-binary.html

The following is an example:

./sherpa-onnx-offline \
   --provider=qnn \
   --tokens=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/tokens.txt \
   --model-type=nemo_transducer \
   --transducer.qnn-backend-lib=./libQnnHtp.so \
   --transducer.qnn-system-lib=./libQnnSystem.so \
   --transducer.qnn-context-binary=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/decoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/joiner.bin \
   ./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/test_wavs/2.wav

Output logs:

/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/parse-options.cc:Read:374 ./sherpa-onnx-offline --provider=qnn --tokens=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-
v2-8s-transducer/tokens.txt --model-type=nemo_transducer --transducer.qnn-backend-lib=./libQnnHtp.so --transducer.qnn-system-lib=./libQnnSystem.so --transducer.qnn-context-binary
=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/decoder.bin,./sherpa-onnx-qnn-S
M8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/joiner.bin ./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/test_wavs/2.wav

OfflineRecognizerConfig(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=OfflineModelConfig(transducer=OfflineTransducerModelConfig(encoder_filename="", decoder_filename="", joiner_filename="", qnn_config=QnnConfig(backend_lib="./libQnnHt
p.so", context_binary="./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/decoder.b
in,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/joiner.bin", system_lib="./libQnnSystem.so")), paraformer=OfflineParaformerModelConfig(model=""), nemo_ctc=O
fflineNemoEncDecCtcModelConfig(model=""), whisper=OfflineWhisperModelConfig(encoder="", decoder="", language="", task="transcribe", tail_paddings=-1, enable_token_timestamps=Fals
e, enable_segment_timestamps=False, ), fire_red_asr=OfflineFireRedAsrModelConfig(encoder="", decoder=""), tdnn=OfflineTdnnModelConfig(model=""), zipformer_ctc=OfflineZipformerCtc
ModelConfig(model=""), wenet_ctc=OfflineWenetCtcModelConfig(model=""), sense_voice=OfflineSenseVoiceModelConfig(model="", language="auto", use_itn=False), moonshine=OfflineMoonsh
ineModelConfig(preprocessor="", encoder="", uncached_decoder="", cached_decoder="", merged_decoder=""), dolphin=OfflineDolphinModelConfig(model=""), canary=OfflineCanaryModelConf
ig(encoder="", decoder="", src_lang="", tgt_lang="", use_pnc=True), cohere_transcribe=OfflineCohereTranscribeModelConfig(encoder="", decoder="", language="", use_punct=True, use_
itn=True), omnilingual=OfflineOmnilingualAsrCtcModelConfig(model=""), funasr_nano=OfflineFunASRNanoModelConfig(encoder_adaptor="", llm="", embedding="", tokenizer="", system_prom
pt="You are a helpful assistant.", user_prompt="语音转写:", max_new_tokens=512, temperature=1e-06, top_p=0.8, seed=42, language="", itn=True, hotwords=""), medasr=OfflineMedAsrC
tcModelConfig(model=""), fire_red_asr_ctc=OfflineFireRedAsrCtcModelConfig(model=""), qwen3_asr=OfflineQwen3ASRModelConfig(conv_frontend="", encoder="", decoder="", tokenizer="",
hotwords="", max_total_len=512, max_new_tokens=128, temperature=1e-06, top_p=0.8, seed=42), telespeech_ctc="", tokens="./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-tra
nsducer/tokens.txt", num_threads=2, debug=False, provider="qnn", model_type="nemo_transducer", modeling_unit="cjkchar", bpe_vocab=""), lm_config=OfflineLMConfig(model="", scale=0
.5, lodr_scale=0.01, lodr_fst="", lodr_backoff_id=-1), ctc_fst_decoder_config=OfflineCtcFstDecoderConfig(graph="", max_active=3000), decoding_method="greedy_search", max_active_p
aths=4, hotwords_file="", hotwords_score=1.5, blank_penalty=0, rule_fsts="", rule_fars="", hr=HomophoneReplacerConfig(lexicon="", rule_fsts=""))
Creating 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.104 s
Started
Done!

./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v2-8s-transducer/test_wavs/2.wav
{"lang": "", "emotion": "", "event": "", "text": "Well, I don't wish to see it any more, observed Pebe, turning away her eyes. It is certainly very like the old portrait.", "timestamps": [0.40, 0.64, 0.72, 0.80, 0.96, 1.04, 1.04, 1.12, 1.28, 1.44, 1.60, 1.76, 1.92, 2.00, 2.24, 2.32, 2.40, 2.48, 2.56, 2.72, 2.88, 3.12, 3.36, 3.44, 3.52, 3.68, 3.76, 3.92, 4.16, 4.24, 4.40, 4.64, 4.96, 5.12, 5.28, 5.36, 5.52, 5.60, 5.76, 6.00, 6.24, 6.40, 6.48, 6.64, 6.72, 6.80, 6.96, 7.04], "durations": [0.24, 0.08, 0.08, 0.16, 0.08, 0.00, 0.08, 0.16, 0.16, 0.16, 0.16, 0.16, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.16, 0.00, 0.16, 0.24, 0.08, 0.08, 0.16, 0.08, 0.16, 0.24, 0.08, 0.16, 0.24, 0.16, 0.16, 0.16, 0.08, 0.16, 0.08, 0.16, 0.24, 0.24, 0.16, 0.08, 0.16, 0.08, 0.08, 0.16, 0.08, 0.24], "tokens":[" Well", ",", " I", " don", "'", "t", " w", "ish", " to", " see", " it", " any", " more", ",", " ob", "s", "er", "ved", " P", "e", "be", ",", " t", "ur", "ning", " a", "way", " her", " e", "y", "es", ".", " It", " is", " c", "ert", "ain", "ly", " very", " like", " the", " o", "ld", " p", "ort", "ra", "it", "."], "ys_log_probs": [-0.014221, -0.184570, -0.000099, -0.001823, -0.000221, 0.000000, -0.000099, 0.000000, -0.000107, -0.000038, -0.000069, -0.000198, -0.061008, -0.537109, -0.000023, -0.000008, 0.000000, -0.000259, -0.001572, -0.763535, -0.018227, -0.038132, -0.000038, -0.000107, 0.000000, -0.000145, -0.000053, -0.000046, -0.000046, -0.000008, -0.000130, -0.264198, -0.001572, -0.000175, -0.000542, 0.000000, 0.000000, -0.000023, -0.001030, -0.000656, -0.000221, -0.002243, 0.000000, -0.000839, -0.000061, 0.000000, 0.000000, -0.811222], "words": []}
----
num threads: 2
decoding method: greedy_search
Elapsed seconds: 0.439 s
Real time factor (RTF): 0.439 / 7.435 = 0.059
     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

https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3

Please download a QNN model for it from https://github.com/k2-fsa/sherpa-onnx/releases/tag/asr-models-qnn-binary-2

The following is an example.

wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models-qnn-binary-2/sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer.tar.bz2

tar xvf sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer.tar.bz2
rm sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer.tar.bz2
ls -lh sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer
total 1273424
-rw-r--r--@ 1 fangjun  staff    23M  3 Jul 19:27 decoder.bin
-rw-r--r--@ 1 fangjun  staff   580M  3 Jul 19:27 encoder.bin
-rw-r--r--@ 1 fangjun  staff    15B  3 Jul 19:27 info.txt
-rw-r--r--@ 1 fangjun  staff   6.2M  3 Jul 19:27 joiner.bin
drwxr-xr-x@ 5 fangjun  staff   160B  3 Jul 19:27 test_wavs
-rw-r--r--@ 1 fangjun  staff    92K  3 Jul 19:27 tokens.txt

Now copy them to your device with Qualcomm NPU and follow
https://k2-fsa.github.io/sherpa/onnx/qnn/run-executables-on-your-phone-binary.html

The following is an example:

./sherpa-onnx-offline \
   --provider=qnn \
   --tokens=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/tokens.txt \
   --model-type=nemo_transducer \
   --transducer.qnn-backend-lib=./libQnnHtp.so \
   --transducer.qnn-system-lib=./libQnnSystem.so \
   --transducer.qnn-context-binary=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/decoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/joiner.bin \
   ./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/test_wavs/2.wav

Output logs:

/Users/fangjun/open-source/sherpa-onnx/sherpa-onnx/csrc/parse-options.cc:Read:374 ./sherpa-onnx-offline --provider=qnn --tokens=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-
v3-8s-transducer/tokens.txt --model-type=nemo_transducer --transducer.qnn-backend-lib=./libQnnHtp.so --transducer.qnn-system-lib=./libQnnSystem.so --transducer.qnn-context-binary
=./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/decoder.bin,./sherpa-onnx-qnn-S
M8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/joiner.bin ./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/test_wavs/2.wav

OfflineRecognizerConfig(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=OfflineModelConfig(transducer=OfflineTransducerModelConfig(encoder_filename="", decoder_filename="", joiner_filename="", qnn_config=QnnConfig(backend_lib="./libQnnHt
p.so", context_binary="./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/encoder.bin,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/decoder.b
in,./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/joiner.bin", system_lib="./libQnnSystem.so")), paraformer=OfflineParaformerModelConfig(model=""), nemo_ctc=O
fflineNemoEncDecCtcModelConfig(model=""), whisper=OfflineWhisperModelConfig(encoder="", decoder="", language="", task="transcribe", tail_paddings=-1, enable_token_timestamps=Fals
e, enable_segment_timestamps=False, ), fire_red_asr=OfflineFireRedAsrModelConfig(encoder="", decoder=""), tdnn=OfflineTdnnModelConfig(model=""), zipformer_ctc=OfflineZipformerCtc
ModelConfig(model=""), wenet_ctc=OfflineWenetCtcModelConfig(model=""), sense_voice=OfflineSenseVoiceModelConfig(model="", language="auto", use_itn=False), moonshine=OfflineMoonsh
ineModelConfig(preprocessor="", encoder="", uncached_decoder="", cached_decoder="", merged_decoder=""), dolphin=OfflineDolphinModelConfig(model=""), canary=OfflineCanaryModelConf
ig(encoder="", decoder="", src_lang="", tgt_lang="", use_pnc=True), cohere_transcribe=OfflineCohereTranscribeModelConfig(encoder="", decoder="", language="", use_punct=True, use_
itn=True), omnilingual=OfflineOmnilingualAsrCtcModelConfig(model=""), funasr_nano=OfflineFunASRNanoModelConfig(encoder_adaptor="", llm="", embedding="", tokenizer="", system_prom
pt="You are a helpful assistant.", user_prompt="语音转写:", max_new_tokens=512, temperature=1e-06, top_p=0.8, seed=42, language="", itn=True, hotwords=""), medasr=OfflineMedAsrC
tcModelConfig(model=""), fire_red_asr_ctc=OfflineFireRedAsrCtcModelConfig(model=""), qwen3_asr=OfflineQwen3ASRModelConfig(conv_frontend="", encoder="", decoder="", tokenizer="",
hotwords="", max_total_len=512, max_new_tokens=128, temperature=1e-06, top_p=0.8, seed=42), telespeech_ctc="", tokens="./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/tokens.txt", num_threads=2, debug=False, provider="qnn", model_type="nemo_transducer", modeling_unit="cjkchar", bpe_vocab=""), lm_config=OfflineLMConfig(model="", scale=0.5, lodr_scale=0.01, lodr_fst="", lodr_backoff_id=-1), ctc_fst_decoder_config=OfflineCtcFstDecoderConfig(graph="", max_active=3000), decoding_method="greedy_search", max_active_paths=4, hotwords_file="", hotwords_score=1.5, blank_penalty=0, rule_fsts="", rule_fars="", hr=HomophoneReplacerConfig(lexicon="", rule_fsts=""))
Creating 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 5.019 s
Started
Done!

./sherpa-onnx-qnn-SM8850-binary-parakeet-tdt-0.6b-v3-8s-transducer/test_wavs/2.wav
{"lang": "", "emotion": "", "event": "", "text": "Well, I don't wish to see it any more, observed Phoebe, turning away her eyes. It is certainly very like the old portrait.", "timestamps": [0.32, 0.48, 0.56, 0.64, 0.80, 0.88, 0.96, 1.04, 1.12, 1.28, 1.36, 1.52, 1.68, 1.84, 2.00, 2.16, 2.40, 2.56, 2.64, 2.80, 2.96, 3.12, 3.28, 3.52, 3.68, 3.84, 4.00, 4.16, 4.32, 4.48, 4.64, 4.88, 5.12, 5.28, 5.44, 5.60, 5.76, 6.00, 6.16, 6.32, 6.40, 6.56, 6.80, 7.04], "durations": [0.16, 0.08, 0.08, 0.16, 0.08, 0.08, 0.08, 0.08, 0.16, 0.08, 0.16, 0.16, 0.08, 0.08, 0.16, 0.24, 0.16, 0.08, 0.16, 0.16, 0.16, 0.16, 0.24, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.24, 0.24, 0.16, 0.16, 0.16, 0.16, 0.24, 0.16, 0.16, 0.08, 0.16, 0.24, 0.24, 0.16], "tokens":[" W", "ell", ",", " I", " don", "'", "t", " w", "ish", " to", " see", " it", " any", " more", ",", " obser", "ved", " P", "ho", "eb", "e", ",", " tur", "ning", " a", "way", " her", " e", "y", "es", ".", " It", " is", " cer", "tain", "ly", " very", " like", " the", " ol", "d", " port", "rait", "."], "ys_log_probs": [-0.002341, -0.000556, -0.278612, -0.000648, -0.008892, -0.000010, -0.000004, -0.000451, -0.000259, -0.000718, -0.000634, -0.000882, -0.248227, -0.015185, -0.150477, -0.001028, -0.009532, -0.004053, -0.009192, -0.001387, -0.001984, -0.023924, -0.000038, -0.000028, -0.000731, -0.000018, -0.000318, -0.000094, -0.000341, -0.000010, -0.070848, -0.026106, -0.001763, -0.000675, -0.000000, -0.000006, -0.000806, -0.015650, -0.002750, -0.020044, -0.000553, -0.008260, -0.000844, -0.481244], "words": []}
----
num threads: 2
decoding method: greedy_search
Elapsed seconds: 0.417 s
Real time factor (RTF): 0.417 / 7.435 = 0.056
     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 support for Parakeet TDT QNN-based offline recognition, including encoder, decoder, and joiner processing.
    • Expanded QNN tensor handling to support float16 inputs and outputs.
  • Bug Fixes
    • Improved recognition of certain metadata values so model settings are handled more consistently.
    • Updated QNN model selection so the correct Parakeet TDT path is used for supported artifacts.

@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds Parakeet TDT model support for QNN backends, including a new offline model implementation with encoder/decoder/joiner pipelines, a corresponding recognizer with greedy-search decoding, float16 tensor I/O support in the QNN runtime, and factory wiring. Minor unrelated cleanups touch the NeMo CTC model file.

Changes

Parakeet TDT QNN Implementation

Layer / File(s) Summary
QNN utils float16 conversion helpers
sherpa-onnx/csrc/qnn/utils.h, sherpa-onnx/csrc/qnn/utils.cc
Adds Float32ToFloat16/Float16ToFloat32 bit-conversion routines and FillDataFloat16/GetDataFloat16 functions plus header declarations.
QNN model tensor I/O float16 support
sherpa-onnx/csrc/qnn/qnn-model.cc
Extends SetInputTensorData and GetOutputTensorData to handle QNN_DATATYPE_FLOAT_16, and switches context-binary reading to ReadFile plus a byte vector.
OfflineParakeetTdtModelQnn implementation
sherpa-onnx/csrc/qnn/offline-parakeet-tdt-model-qnn.h, sherpa-onnx/csrc/qnn/offline-parakeet-tdt-model-qnn.cc
Adds encoder/decoder/joiner QNN pipelines, model-library/context-binary initialization, tensor shape validation, and public inference/query methods with Android/OHOS constructor instantiations.
OfflineRecognizerParakeetTdtQnnImpl and factory wiring
sherpa-onnx/csrc/qnn/offline-recognizer-parakeet-tdt-qnn-impl.h, sherpa-onnx/csrc/offline-recognizer-impl.cc, sherpa-onnx/csrc/CMakeLists.txt
Adds a greedy-search TDT recognizer with result conversion, wires the QNN factory to return this implementation for Nemo transducer artifacts, updates supported-model error messages, and adds the new source file to the build.

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

NeMo CTC Model Minor Fixes

Layer / File(s) Summary
Include order, formatting, and normalize_type_ normalization
sherpa-onnx/csrc/offline-nemo-enc-dec-ctc-model.cc
Reorders the ort-env.h include, reformats an Ort::Session construction call, and normalizes the "NA" sentinel value of normalize_type_ to an empty string.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant OfflineRecognizerImpl
  participant OfflineRecognizerParakeetTdtQnnImpl
  participant OfflineParakeetTdtModelQnn

  Client->>OfflineRecognizerImpl: Create(config)
  OfflineRecognizerImpl->>OfflineRecognizerImpl: IsQnnTransducerArtifact + model_type check
  OfflineRecognizerImpl-->>Client: OfflineRecognizerParakeetTdtQnnImpl

  Client->>OfflineRecognizerParakeetTdtQnnImpl: DecodeStreams(streams)
  OfflineRecognizerParakeetTdtQnnImpl->>OfflineParakeetTdtModelQnn: RunEncoder(frames)
  OfflineRecognizerParakeetTdtQnnImpl->>OfflineRecognizerParakeetTdtQnnImpl: GreedySearch(encoder_out)
  loop per decoded token
    OfflineRecognizerParakeetTdtQnnImpl->>OfflineParakeetTdtModelQnn: RunDecoder / RunJoiner
    OfflineParakeetTdtModelQnn-->>OfflineRecognizerParakeetTdtQnnImpl: token, timestamp, duration
  end
  OfflineRecognizerParakeetTdtQnnImpl-->>Client: OfflineRecognitionResult
Loading

Possibly related PRs

  • k2-fsa/sherpa-onnx#2766: Extends the same QNN layer with float16 tensor input/output support in qnn-model.cc and qnn/utils.{h,cc} that this PR builds upon.
  • k2-fsa/sherpa-onnx#3655: Modifies the same offline-recognizer-impl.cc QNN factory selection logic based on IsQnnTransducerArtifact(...), routing a different transducer type.
  • k2-fsa/sherpa-onnx#3699: Touches the same offline-recognizer-impl.cc QNN provider factory and supported-models messaging plus CMakeLists.txt for a different QNN model implementation.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding C++ runtime support for Parakeet TDT models using QNN.
✨ 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 the Parakeet TDT model in QNN, including the implementation of the offline model, its corresponding recognizer, and integration into the build system and recognizer factory. Additionally, it adds float16 tensor data conversion utilities to the QNN backend. The review feedback highlights several robustness improvements, specifically recommending validation checks to prevent out-of-bounds reads when accessing joiner output logits, ensuring model filenames are provided when context binaries are absent, and verifying that the decoder states vector contains the expected number of elements before access.

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 +207 to +210
float *token_logits = logits.data();
int32_t output_size = static_cast<int32_t>(logits.size());
int32_t num_durations = output_size - vocab_size;
const float *duration_logits = logits.data() + vocab_size;

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

To prevent potential out-of-bounds reads and crashes when a mismatched model or tokens.txt is provided, we should validate that the joiner output size is at least equal to vocab_size before accessing duration_logits.

      float *token_logits = logits.data();
      int32_t output_size = static_cast<int32_t>(logits.size());
      if (output_size < vocab_size) {
        SHERPA_ONNX_LOGE(
            "Joiner output size %d is less than vocab_size %d. "
            "Please check your model and tokens.txt",
            output_size, vocab_size);
        SHERPA_ONNX_EXIT(-1);
      }
      int32_t num_durations = output_size - vocab_size;
      const float *duration_logits = logits.data() + vocab_size;

Comment on lines +180 to +187
if (!context_binaries_.empty() && context_binaries_.size() != 3) {
SHERPA_ONNX_LOGE(
"There should be 3 files for offline parakeet TDT context binary. "
"Actual: %d. '%s'",
static_cast<int32_t>(context_binaries_.size()),
config_.transducer.qnn_config.context_binary.c_str());
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 context_binaries_ is empty, we must ensure that encoder_filename, decoder_filename, and joiner_filename are not empty. Otherwise, the model will fail to load with a generic error. Adding a validation check here improves usability and robustness.

    if (!context_binaries_.empty() && context_binaries_.size() != 3) {
      SHERPA_ONNX_LOGE(
          "There should be 3 files for offline parakeet TDT context binary. "
          "Actual: %d. '%s'",
          static_cast<int32_t>(context_binaries_.size()),
          config_.transducer.qnn_config.context_binary.c_str());
      SHERPA_ONNX_EXIT(-1);
    }

    if (context_binaries_.empty()) {
      if (config_.transducer.encoder_filename.empty() ||
          config_.transducer.decoder_filename.empty() ||
          config_.transducer.joiner_filename.empty()) {
        SHERPA_ONNX_LOGE(
            "Please provide encoder_filename, decoder_filename, and "
            "joiner_filename when context_binary is not provided.");
        SHERPA_ONNX_EXIT(-1);
      }
    }

Comment on lines +109 to +114
std::lock_guard<std::mutex> lock(mutex_);
decoder_->SetInputTensorData(decoder_input_y_name_, &token, 1);
decoder_->SetInputTensorData(decoder_input_h_name_, states[0].data(),
static_cast<int32_t>(states[0].size()));
decoder_->SetInputTensorData(decoder_input_c_name_, states[1].data(),
static_cast<int32_t>(states[1].size()));

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

To prevent potential out-of-bounds access or crashes, we should validate that states contains at least 2 elements (representing the hidden state h and cell state c) before accessing states[0] and states[1].

    if (states.size() < 2) {
      SHERPA_ONNX_LOGE("states should contain at least 2 elements (h and c).");
      SHERPA_ONNX_EXIT(-1);
    }
    std::lock_guard<std::mutex> lock(mutex_);
    decoder_->SetInputTensorData(decoder_input_y_name_, &token, 1);
    decoder_->SetInputTensorData(decoder_input_h_name_, states[0].data(),
                                 static_cast<int32_t>(states[0].size()));
    decoder_->SetInputTensorData(decoder_input_c_name_, states[1].data(),
                                 static_cast<int32_t>(states[1].size()));

@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.

🧹 Nitpick comments (1)
sherpa-onnx/csrc/qnn/qnn-model.cc (1)

121-122: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid the full duplicate copy of the context binary.

ReadFile already returns a std::vector<char>, and buffer is only consumed as static_cast<void *>(buffer.data()) + buffer.size() (Lines 135, 189). Copying it into a std::vector<uint8_t> doubles peak memory while the binary is held — noticeable for large Parakeet TDT context binaries on constrained NPU devices. Use the char buffer directly.

♻️ Proposed change
-    auto char_buffer = ReadFile(binary_context_file);
-    std::vector<uint8_t> buffer(char_buffer.begin(), char_buffer.end());
+    auto buffer = ReadFile(binary_context_file);

buffer.data() (now char *) still casts to void * at Lines 135 and 189 without change.

🤖 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/qnn-model.cc` around lines 121 - 122, The context binary
is being duplicated unnecessarily in qnn-model.cc; use the existing ReadFile
result directly instead of copying into a std::vector<uint8_t>. Update the code
around the binary_context_file read path in qnn-model.cc so the existing char
buffer is passed through to the later buffer.data()/buffer.size() consumers, and
keep the handling consistent in the code paths that use the buffer for QNN
setup.
🤖 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.

Nitpick comments:
In `@sherpa-onnx/csrc/qnn/qnn-model.cc`:
- Around line 121-122: The context binary is being duplicated unnecessarily in
qnn-model.cc; use the existing ReadFile result directly instead of copying into
a std::vector<uint8_t>. Update the code around the binary_context_file read path
in qnn-model.cc so the existing char buffer is passed through to the later
buffer.data()/buffer.size() consumers, and keep the handling consistent in the
code paths that use the buffer for QNN setup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ee3fb1c-6b1e-45e0-9e04-0d3973b051dc

📥 Commits

Reviewing files that changed from the base of the PR and between d250d04 and daea2ae.

📒 Files selected for processing (9)
  • sherpa-onnx/csrc/CMakeLists.txt
  • sherpa-onnx/csrc/offline-nemo-enc-dec-ctc-model.cc
  • sherpa-onnx/csrc/offline-recognizer-impl.cc
  • sherpa-onnx/csrc/qnn/offline-parakeet-tdt-model-qnn.cc
  • sherpa-onnx/csrc/qnn/offline-parakeet-tdt-model-qnn.h
  • sherpa-onnx/csrc/qnn/offline-recognizer-parakeet-tdt-qnn-impl.h
  • sherpa-onnx/csrc/qnn/qnn-model.cc
  • sherpa-onnx/csrc/qnn/utils.cc
  • sherpa-onnx/csrc/qnn/utils.h

@csukuangfj csukuangfj merged commit 5f1153b into k2-fsa:master Jul 4, 2026
26 of 27 checks passed
@csukuangfj csukuangfj deleted the cpp-qnn-nemo-tdt branch July 4, 2026 07:44
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.

1 participant