Skip to content

Commit 4ea060a

Browse files
authored
Add funASR-Nano with LLM support (#2936)
1 parent 1f12460 commit 4ea060a

29 files changed

Lines changed: 4022 additions & 3 deletions

cxx-api-examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ target_link_libraries(dolphin-ctc-cxx-api sherpa-onnx-cxx-api)
145145
add_executable(vad-cxx-api ./vad-cxx-api.cc)
146146
target_link_libraries(vad-cxx-api sherpa-onnx-cxx-api)
147147

148+
add_executable(funasr-nano-cxx-api ./funasr-nano-cxx-api.cc)
149+
target_link_libraries(funasr-nano-cxx-api sherpa-onnx-cxx-api)
150+
148151
if(SHERPA_ONNX_ENABLE_TTS)
149152
add_executable(matcha-tts-zh-cxx-api ./matcha-tts-zh-cxx-api.cc)
150153
target_link_libraries(matcha-tts-zh-cxx-api sherpa-onnx-cxx-api)
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// cxx-api-examples/funasr-nano-cxx-api.cc
2+
//
3+
// Copyright (c) 2025 zengyw
4+
//
5+
// This file demonstrates how to use FunASR-nano with sherpa-onnx's C++ API.
6+
//
7+
// clang-format off
8+
//
9+
// Example usage:
10+
// ./bin/funasr-nano-cxx-api \
11+
// --funasr-nano-encoder-adaptor=/path/to/encoder_adaptor.onnx \
12+
// --funasr-nano-llm-prefill=/path/to/llm_prefill.onnx \
13+
// --funasr-nano-llm-decode=/path/to/llm_decode.onnx \
14+
// --funasr-nano-embedding=/path/to/embedding.onnx \
15+
// --funasr-nano-tokenizer=/path/to/Qwen3-0.6B \
16+
// /path/to/audio.wav
17+
//
18+
// clang-format on
19+
20+
#include <chrono>
21+
#include <cstring>
22+
#include <iostream>
23+
#include <string>
24+
25+
#include "sherpa-onnx/c-api/cxx-api.h"
26+
27+
int32_t main(int32_t argc, char *argv[]) {
28+
using namespace sherpa_onnx::cxx;
29+
30+
const char *kUsageMessage = R"usage(
31+
FunASR-nano speech recognition example using sherpa-onnx C++ API.
32+
33+
Usage:
34+
./bin/funasr-nano-cxx-api \
35+
--funasr-nano-encoder-adaptor=/path/to/encoder_adaptor.onnx \
36+
--funasr-nano-llm-prefill=/path/to/llm_prefill.onnx \
37+
--funasr-nano-llm-decode=/path/to/llm_decode.onnx \
38+
--funasr-nano-tokenizer=/path/to/Qwen3-0.6B \
39+
--funasr-nano-embedding=/path/to/embedding.onnx \
40+
[--funasr-nano-user-prompt="语音转写:"] \
41+
[--funasr-nano-max-new-tokens=512] \
42+
[--funasr-nano-temperature=0.3] \
43+
[--funasr-nano-top-p=0.8] \
44+
/path/to/audio.wav
45+
46+
Required arguments:
47+
--funasr-nano-encoder-adaptor: Path to encoder_adaptor.onnx
48+
--funasr-nano-llm-prefill: Path to llm_prefill.onnx
49+
--funasr-nano-llm-decode: Path to llm_decode.onnx
50+
--funasr-nano-tokenizer: Path to tokenizer directory (e.g., Qwen3-0.6B)
51+
--funasr-nano-embedding: Path to embedding.onnx
52+
53+
Optional arguments:
54+
--funasr-nano-user-prompt: User prompt template (default: "语音转写:")
55+
--funasr-nano-max-new-tokens: Maximum tokens to generate (default: 512)
56+
--funasr-nano-temperature: Sampling temperature (default: 0.3)
57+
--funasr-nano-top-p: Top-p sampling threshold (default: 0.8)
58+
--num-threads: Number of threads (default: 2)
59+
--provider: cpu (default) or cuda
60+
61+
Example:
62+
./bin/funasr-nano-cxx-api \
63+
--funasr-nano-encoder-adaptor=./models/encoder_adaptor.onnx \
64+
--funasr-nano-llm-prefill=./models/llm_prefill.onnx \
65+
--funasr-nano-llm-decode=./models/llm_decode.onnx \
66+
--funasr-nano-tokenizer=./models/Qwen3-0.6B \
67+
--funasr-nano-embedding=./models/embedding.onnx \
68+
./test.wav
69+
)usage";
70+
71+
if (argc < 6) {
72+
std::cerr << kUsageMessage << "\n";
73+
return -1;
74+
}
75+
76+
OfflineRecognizerConfig config;
77+
config.model_config.num_threads = 2;
78+
config.model_config.debug = false;
79+
config.model_config.provider = "cpu";
80+
81+
// Parse command line arguments
82+
const char kEncoderAdaptor[] = "--funasr-nano-encoder-adaptor=";
83+
const char kLlmPrefill[] = "--funasr-nano-llm-prefill=";
84+
const char kLlmDecode[] = "--funasr-nano-llm-decode=";
85+
const char kEmbedding[] = "--funasr-nano-embedding=";
86+
const char kTokenizer[] = "--funasr-nano-tokenizer=";
87+
const char kUserPrompt[] = "--funasr-nano-user-prompt=";
88+
const char kMaxNewTokens[] = "--funasr-nano-max-new-tokens=";
89+
const char kTemperature[] = "--funasr-nano-temperature=";
90+
const char kTopP[] = "--funasr-nano-top-p=";
91+
const char kNumThreads[] = "--num-threads=";
92+
const char kProvider[] = "--provider=";
93+
94+
for (int32_t i = 1; i < argc; ++i) {
95+
std::string arg = argv[i];
96+
if (arg.find(kEncoderAdaptor) == 0) {
97+
config.model_config.funasr_nano.encoder_adaptor =
98+
arg.substr(sizeof(kEncoderAdaptor) - 1);
99+
} else if (arg.find(kLlmPrefill) == 0) {
100+
config.model_config.funasr_nano.llm_prefill =
101+
arg.substr(sizeof(kLlmPrefill) - 1);
102+
} else if (arg.find(kLlmDecode) == 0) {
103+
config.model_config.funasr_nano.llm_decode =
104+
arg.substr(sizeof(kLlmDecode) - 1);
105+
} else if (arg.find(kEmbedding) == 0) {
106+
config.model_config.funasr_nano.embedding =
107+
arg.substr(sizeof(kEmbedding) - 1);
108+
} else if (arg.find(kTokenizer) == 0) {
109+
config.model_config.funasr_nano.tokenizer =
110+
arg.substr(sizeof(kTokenizer) - 1);
111+
} else if (arg.find(kUserPrompt) == 0) {
112+
config.model_config.funasr_nano.user_prompt =
113+
arg.substr(sizeof(kUserPrompt) - 1);
114+
} else if (arg.find(kMaxNewTokens) == 0) {
115+
config.model_config.funasr_nano.max_new_tokens =
116+
std::stoi(arg.substr(sizeof(kMaxNewTokens) - 1));
117+
} else if (arg.find(kTemperature) == 0) {
118+
config.model_config.funasr_nano.temperature =
119+
std::stof(arg.substr(sizeof(kTemperature) - 1));
120+
} else if (arg.find(kTopP) == 0) {
121+
config.model_config.funasr_nano.top_p =
122+
std::stof(arg.substr(sizeof(kTopP) - 1));
123+
} else if (arg.find(kNumThreads) == 0) {
124+
config.model_config.num_threads =
125+
std::stoi(arg.substr(sizeof(kNumThreads) - 1));
126+
} else if (arg.find(kProvider) == 0) {
127+
config.model_config.provider = arg.substr(sizeof(kProvider) - 1);
128+
} else if (arg[0] != '-') {
129+
// This should be the audio file
130+
std::string wave_filename = arg;
131+
132+
std::cout << "Loading model...\n";
133+
std::cout << " encoder_adaptor: "
134+
<< config.model_config.funasr_nano.encoder_adaptor << "\n";
135+
std::cout << " llm_prefill: "
136+
<< config.model_config.funasr_nano.llm_prefill << "\n";
137+
std::cout << " llm_decode: "
138+
<< config.model_config.funasr_nano.llm_decode << "\n";
139+
std::cout << " tokenizer: " << config.model_config.funasr_nano.tokenizer
140+
<< "\n";
141+
std::cout << " embedding: "
142+
<< config.model_config.funasr_nano.embedding << "\n";
143+
144+
const auto begin_init = std::chrono::steady_clock::now();
145+
146+
OfflineRecognizer recognizer = OfflineRecognizer::Create(config);
147+
if (!recognizer.Get()) {
148+
std::cerr << "Failed to create recognizer. Please check your config.\n";
149+
return -1;
150+
}
151+
152+
const auto end_init = std::chrono::steady_clock::now();
153+
float elapsed_seconds_init =
154+
std::chrono::duration_cast<std::chrono::milliseconds>(end_init -
155+
begin_init)
156+
.count() /
157+
1000.;
158+
std::cout << "Model loaded in " << elapsed_seconds_init << " seconds\n";
159+
160+
Wave wave = ReadWave(wave_filename);
161+
if (wave.samples.empty()) {
162+
std::cerr << "Failed to read: '" << wave_filename << "'\n";
163+
return -1;
164+
}
165+
166+
std::cout << "Audio file: " << wave_filename << "\n";
167+
std::cout << "Sample rate: " << wave.sample_rate << " Hz\n";
168+
std::cout << "Duration: "
169+
<< wave.samples.size() / static_cast<float>(wave.sample_rate)
170+
<< " seconds\n";
171+
172+
std::cout << "\nStart recognition...\n";
173+
const auto begin = std::chrono::steady_clock::now();
174+
175+
OfflineStream stream = recognizer.CreateStream();
176+
stream.AcceptWaveform(wave.sample_rate, wave.samples.data(),
177+
wave.samples.size());
178+
179+
recognizer.Decode(&stream);
180+
181+
OfflineRecognizerResult result = recognizer.GetResult(&stream);
182+
183+
const auto end = std::chrono::steady_clock::now();
184+
const float elapsed_seconds =
185+
std::chrono::duration_cast<std::chrono::milliseconds>(end - begin)
186+
.count() /
187+
1000.;
188+
float duration =
189+
wave.samples.size() / static_cast<float>(wave.sample_rate);
190+
float rtf = elapsed_seconds / duration;
191+
192+
std::cout << "Text: " << result.text << "\n";
193+
std::cout << "Audio duration: " << duration << "s\n";
194+
std::cout << "Processing time: " << elapsed_seconds << "s\n";
195+
std::cout << "Real-time factor (RTF): " << rtf << "\n";
196+
197+
return 0;
198+
}
199+
}
200+
201+
std::cerr << "Error: Please provide an audio file.\n";
202+
std::cerr << kUsageMessage << "\n";
203+
return -1;
204+
}
205+

0 commit comments

Comments
 (0)