add qwen3-asr
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased] - 2025-02-04
|
||||
|
||||
### Added
|
||||
- Support for Qwen3-ASR model
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
+9
-1
@@ -17,7 +17,9 @@ show_help() {
|
||||
echo " minicpm4-0.5b"
|
||||
echo " qwen2.5vl-3b"
|
||||
echo " qwen2.5vl-7b"
|
||||
echo " qwen3-0.6b"
|
||||
echo " qwen3-0.6b"
|
||||
echo " qwen3asr-0.6b"
|
||||
echo " qwen3asr-1.7b"
|
||||
echo " qwen3vl-2b"
|
||||
echo " qwen3vl-4b"
|
||||
echo " qwen3vl-8b"
|
||||
@@ -56,6 +58,12 @@ case $MODEL_ALIAS in
|
||||
"qwen3-0.6b")
|
||||
MODEL_ID="Qwen/Qwen3-0.6B"
|
||||
;;
|
||||
"qwen3asr-0.6b")
|
||||
MODEL_ID="Qwen/Qwen3-ASR-0.6B"
|
||||
;;
|
||||
"qwen3asr-1.7b")
|
||||
MODEL_ID="Qwen/Qwen3-ASR-1.7B"
|
||||
;;
|
||||
"qwen3vl-2b")
|
||||
MODEL_ID="Qwen/Qwen3-VL-2B-Instruct"
|
||||
;;
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod minicpm4;
|
||||
pub mod paddleocr_vl;
|
||||
pub mod qwen2_5vl;
|
||||
pub mod qwen3;
|
||||
pub mod qwen3_asr;
|
||||
pub mod qwen3vl;
|
||||
pub mod rmbg2_0;
|
||||
pub mod voxcpm;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! Fun-ASR-Nano-2512 exec implementation for CLI `run` subcommand
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::{Ok, Result};
|
||||
|
||||
use crate::exec::ExecModel;
|
||||
use crate::models::qwen3_asr::generate::Qwen3AsrGenerateModel;
|
||||
use crate::models::{GenerateModel};
|
||||
|
||||
pub struct Qwen3ASRExec;
|
||||
|
||||
impl ExecModel for Qwen3ASRExec {
|
||||
fn run(input: &[String], output: Option<&str>, weight_path: &str) -> Result<()> {
|
||||
|
||||
let i_start = Instant::now();
|
||||
let mut model = Qwen3AsrGenerateModel::init(weight_path, None, None)?;
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("Time elapsed in load model is: {:?}", i_duration);
|
||||
|
||||
// Create ChatCompletionParameters for ASR
|
||||
let url = &input[1];
|
||||
let input_url = if url.starts_with("http://")
|
||||
|| url.starts_with("https://")
|
||||
|| url.starts_with("file://")
|
||||
{
|
||||
url.clone()
|
||||
} else {
|
||||
format!("file://{}", url)
|
||||
};
|
||||
|
||||
let message = format!(
|
||||
r#"{{
|
||||
"model": "fun-asr-nano",
|
||||
"messages": [
|
||||
{{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{{
|
||||
"type": "audio",
|
||||
"audio_url": {{
|
||||
"url": "{}"
|
||||
}}
|
||||
}}
|
||||
]
|
||||
}}
|
||||
]
|
||||
}}"#,
|
||||
input_url
|
||||
);
|
||||
let mes = serde_json::from_str(&message)?;
|
||||
|
||||
let i_start = Instant::now();
|
||||
let res = model.generate(mes)?;
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("Time elapsed in generate is: {:?}", i_duration);
|
||||
|
||||
println!("Result: {:?}", res);
|
||||
|
||||
if let Some(out) = output {
|
||||
std::fs::write(out, format!("{:?}", res))?;
|
||||
println!("Output saved to: {}", out);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+17
-5
@@ -1,15 +1,15 @@
|
||||
use std::{net::IpAddr, str::FromStr, time::Duration};
|
||||
|
||||
use aha::{models::WhichModel, utils::{download_model, get_default_save_dir}};
|
||||
use std::{net::IpAddr, str::FromStr};
|
||||
|
||||
use aha::{
|
||||
models::WhichModel,
|
||||
utils::{download_model, get_default_save_dir},
|
||||
};
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use modelscope::ModelScope;
|
||||
use rocket::{
|
||||
Config,
|
||||
data::{ByteUnit, Limits},
|
||||
routes,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::api::init;
|
||||
mod api;
|
||||
@@ -149,6 +149,8 @@ fn get_model_id(model: WhichModel) -> &'static str {
|
||||
WhichModel::Qwen2_5vl3B => "Qwen/Qwen2.5-VL-3B-Instruct",
|
||||
WhichModel::Qwen2_5vl7B => "Qwen/Qwen2.5-VL-7B-Instruct",
|
||||
WhichModel::Qwen3_0_6B => "Qwen/Qwen3-0.6B",
|
||||
WhichModel::Qwen3ASR0_6B => "Qwen/Qwen3-ASR-0.6B",
|
||||
WhichModel::Qwen3ASR1_7B => "Qwen/Qwen3-ASR-1.7B",
|
||||
WhichModel::Qwen3vl2B => "Qwen/Qwen3-VL-2B-Instruct",
|
||||
WhichModel::Qwen3vl4B => "Qwen/Qwen3-VL-4B-Instruct",
|
||||
WhichModel::Qwen3vl8B => "Qwen/Qwen3-VL-8B-Instruct",
|
||||
@@ -171,6 +173,8 @@ fn run_list() -> anyhow::Result<()> {
|
||||
WhichModel::Qwen2_5vl3B,
|
||||
WhichModel::Qwen2_5vl7B,
|
||||
WhichModel::Qwen3_0_6B,
|
||||
WhichModel::Qwen3ASR0_6B,
|
||||
WhichModel::Qwen3ASR1_7B,
|
||||
WhichModel::Qwen3vl2B,
|
||||
WhichModel::Qwen3vl4B,
|
||||
WhichModel::Qwen3vl8B,
|
||||
@@ -289,6 +293,14 @@ fn run_run(args: RunArgs) -> anyhow::Result<()> {
|
||||
use aha::exec::qwen3::Qwen3Exec;
|
||||
Qwen3Exec::run(&input, output.as_deref(), &weight_path)?;
|
||||
}
|
||||
WhichModel::Qwen3ASR0_6B => {
|
||||
use aha::exec::qwen3_asr::Qwen3ASRExec;
|
||||
Qwen3ASRExec::run(&input, output.as_deref(), &weight_path)?;
|
||||
}
|
||||
WhichModel::Qwen3ASR1_7B => {
|
||||
use aha::exec::qwen3_asr::Qwen3ASRExec;
|
||||
Qwen3ASRExec::run(&input, output.as_deref(), &weight_path)?;
|
||||
}
|
||||
WhichModel::Qwen3vl2B => {
|
||||
use aha::exec::qwen3vl::Qwen3vlExec;
|
||||
Qwen3vlExec::run(&input, output.as_deref(), &weight_path)?;
|
||||
|
||||
@@ -7,13 +7,13 @@ use crate::utils::{
|
||||
};
|
||||
|
||||
pub struct WhisperFeatureExtractor {
|
||||
feature_size: usize,
|
||||
// feature_size: usize,
|
||||
hop_length: usize,
|
||||
chunk_length: usize,
|
||||
n_samples: usize,
|
||||
// chunk_length: usize,
|
||||
// n_samples: usize,
|
||||
n_fft: usize,
|
||||
dither: f64,
|
||||
padding_value: f32,
|
||||
// padding_value: f32,
|
||||
sampling_rate: usize,
|
||||
mel_filters: Tensor,
|
||||
window: Tensor,
|
||||
@@ -23,10 +23,10 @@ impl WhisperFeatureExtractor {
|
||||
pub fn new(
|
||||
feature_size: usize,
|
||||
hop_length: usize,
|
||||
chunk_length: usize,
|
||||
// chunk_length: usize,
|
||||
n_fft: usize,
|
||||
dither: f64,
|
||||
padding_value: f32,
|
||||
// padding_value: f32,
|
||||
sampling_rate: usize,
|
||||
device: &Device,
|
||||
) -> Result<Self> {
|
||||
@@ -44,15 +44,15 @@ impl WhisperFeatureExtractor {
|
||||
device,
|
||||
)?
|
||||
.t()?;
|
||||
let n_samples = chunk_length * sampling_rate;
|
||||
// let n_samples = chunk_length * sampling_rate;
|
||||
Ok(Self {
|
||||
feature_size,
|
||||
// feature_size,
|
||||
hop_length,
|
||||
chunk_length,
|
||||
n_samples,
|
||||
// chunk_length,
|
||||
// n_samples,
|
||||
n_fft,
|
||||
dither,
|
||||
padding_value,
|
||||
// padding_value,
|
||||
sampling_rate,
|
||||
mel_filters,
|
||||
window,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::f32;
|
||||
|
||||
use aha_openai_dive::v1::resources::chat::ChatCompletionParameters;
|
||||
use anyhow::Result;
|
||||
@@ -9,22 +8,16 @@ use crate::{
|
||||
feature_extractor::feature_extraction_whisper::WhisperFeatureExtractor,
|
||||
glm_asr_nano::config::GlmAsrNanoProcessorConfig,
|
||||
},
|
||||
utils::{
|
||||
audio_utils::{
|
||||
apply_stft, create_hann_window, extract_audios, extract_frames, mel_filter_bank,
|
||||
torch_stft,
|
||||
},
|
||||
tensor_utils::{log10, pad_reflect_last_dim, split_tensor},
|
||||
},
|
||||
utils::{audio_utils::extract_audios, tensor_utils::split_tensor},
|
||||
};
|
||||
|
||||
pub struct GlmAsrNanoProcessor {
|
||||
sampling_rate: usize,
|
||||
chunk_length: usize,
|
||||
n_samples: usize,
|
||||
n_fft: usize,
|
||||
window: Tensor,
|
||||
mel_filters: Tensor,
|
||||
// n_fft: usize,
|
||||
// window: Tensor,
|
||||
// mel_filters: Tensor,
|
||||
hop_length: usize,
|
||||
audio_token: String,
|
||||
// audio_token_id: u32,
|
||||
@@ -55,29 +48,29 @@ impl GlmAsrNanoProcessor {
|
||||
let sampling_rate = processor_cfg.feature_extractor.sampling_rate;
|
||||
let chunk_length = processor_cfg.feature_extractor.chunk_length;
|
||||
let n_samples = processor_cfg.feature_extractor.n_samples;
|
||||
let n_fft = processor_cfg.feature_extractor.n_fft;
|
||||
// let n_fft = processor_cfg.feature_extractor.n_fft;
|
||||
let hop_length = processor_cfg.feature_extractor.hop_length;
|
||||
let window = create_hann_window(n_fft, dtype, device)?;
|
||||
let window = window.unsqueeze(0)?.unsqueeze(0)?;
|
||||
let mel_filters = mel_filter_bank(
|
||||
1 + n_fft / 2,
|
||||
processor_cfg.feature_extractor.feature_size,
|
||||
0.0,
|
||||
8000.0,
|
||||
sampling_rate as f32,
|
||||
Some("slaney"),
|
||||
crate::utils::audio_utils::MelScale::Slaney,
|
||||
false,
|
||||
device,
|
||||
)?
|
||||
.t()?;
|
||||
// let window = create_hann_window(n_fft, dtype, device)?;
|
||||
// let window = window.unsqueeze(0)?.unsqueeze(0)?;
|
||||
// let mel_filters = mel_filter_bank(
|
||||
// 1 + n_fft / 2,
|
||||
// processor_cfg.feature_extractor.feature_size,
|
||||
// 0.0,
|
||||
// 8000.0,
|
||||
// sampling_rate as f32,
|
||||
// Some("slaney"),
|
||||
// crate::utils::audio_utils::MelScale::Slaney,
|
||||
// false,
|
||||
// device,
|
||||
// )?
|
||||
// .t()?;
|
||||
let whisper_feature_extrator = WhisperFeatureExtractor::new(
|
||||
processor_cfg.feature_extractor.feature_size,
|
||||
processor_cfg.feature_extractor.hop_length,
|
||||
processor_cfg.feature_extractor.chunk_length,
|
||||
// processor_cfg.feature_extractor.chunk_length,
|
||||
processor_cfg.feature_extractor.n_fft,
|
||||
processor_cfg.feature_extractor.dither,
|
||||
processor_cfg.feature_extractor.padding_value,
|
||||
// processor_cfg.feature_extractor.padding_value,
|
||||
processor_cfg.feature_extractor.sampling_rate,
|
||||
device,
|
||||
)?;
|
||||
@@ -85,9 +78,9 @@ impl GlmAsrNanoProcessor {
|
||||
sampling_rate,
|
||||
chunk_length,
|
||||
n_samples,
|
||||
n_fft,
|
||||
window,
|
||||
mel_filters,
|
||||
// n_fft,
|
||||
// window,
|
||||
// mel_filters,
|
||||
hop_length,
|
||||
audio_token,
|
||||
// audio_token_id,
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
utils::{
|
||||
audio_utils::{
|
||||
create_hann_window, extract_audio_url, get_waveform_and_window_properties, kaldi_fbank,
|
||||
kaldi_get_mel_banks, load_audio, mel_filter_bank, resample_simple, spectrogram,
|
||||
kaldi_get_mel_banks, load_audio, mel_filter_bank, resample_simple,
|
||||
torch_stft,
|
||||
},
|
||||
get_vb_model_path,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use candle_core::{D, IndexOp, Tensor};
|
||||
use candle_core::{D, Tensor};
|
||||
use candle_nn::{
|
||||
Conv1d, Embedding, Init, LayerNorm, Linear, Module, VarBuilder, embedding, linear,
|
||||
};
|
||||
|
||||
+20
-4
@@ -1,5 +1,5 @@
|
||||
pub mod common;
|
||||
pub mod campplus;
|
||||
pub mod common;
|
||||
pub mod deepseek_ocr;
|
||||
pub mod feature_extractor;
|
||||
pub mod fun_asr_nano;
|
||||
@@ -29,8 +29,9 @@ use crate::models::{
|
||||
glm_asr_nano::generate::GlmAsrNanoGenerateModel,
|
||||
hunyuan_ocr::generate::HunyuanOCRGenerateModel, minicpm4::generate::MiniCPMGenerateModel,
|
||||
paddleocr_vl::generate::PaddleOCRVLGenerateModel, qwen2_5vl::generate::Qwen2_5VLGenerateModel,
|
||||
qwen3::generate::Qwen3GenerateModel, qwen3vl::generate::Qwen3VLGenerateModel,
|
||||
rmbg2_0::generate::RMBG2_0Model, voxcpm::generate::VoxCPMGenerate,
|
||||
qwen3::generate::Qwen3GenerateModel, qwen3_asr::generate::Qwen3AsrGenerateModel,
|
||||
qwen3vl::generate::Qwen3VLGenerateModel, rmbg2_0::generate::RMBG2_0Model,
|
||||
voxcpm::generate::VoxCPMGenerate,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
|
||||
@@ -43,7 +44,11 @@ pub enum WhichModel {
|
||||
Qwen2_5vl7B,
|
||||
#[value(name = "qwen3-0.6b", hide = true)]
|
||||
Qwen3_0_6B,
|
||||
#[value(name = "qwen3vl-2b", hide = true)]
|
||||
#[value(name = "qwen3asr-0.6b", hide = true)]
|
||||
Qwen3ASR0_6B,
|
||||
#[value(name = "qwen3asr-1.7b", hide = true)]
|
||||
Qwen3ASR1_7B,
|
||||
#[value(name = "qwen3vl-4b", hide = true)]
|
||||
Qwen3vl2B,
|
||||
#[value(name = "qwen3vl-4b", hide = true)]
|
||||
Qwen3vl4B,
|
||||
@@ -88,6 +93,7 @@ pub enum ModelInstance<'a> {
|
||||
MiniCPM4(MiniCPMGenerateModel<'a>),
|
||||
Qwen2_5VL(Qwen2_5VLGenerateModel<'a>),
|
||||
Qwen3(Qwen3GenerateModel<'a>),
|
||||
Qwen3ASR(Qwen3AsrGenerateModel<'a>),
|
||||
Qwen3VL(Qwen3VLGenerateModel<'a>),
|
||||
DeepSeekOCR(DeepseekOCRGenerateModel),
|
||||
HunyuanOCR(HunyuanOCRGenerateModel<'a>),
|
||||
@@ -104,6 +110,7 @@ impl<'a> GenerateModel for ModelInstance<'a> {
|
||||
ModelInstance::MiniCPM4(model) => model.generate(mes),
|
||||
ModelInstance::Qwen2_5VL(model) => model.generate(mes),
|
||||
ModelInstance::Qwen3(model) => model.generate(mes),
|
||||
ModelInstance::Qwen3ASR(model) => model.generate(mes),
|
||||
ModelInstance::Qwen3VL(model) => model.generate(mes),
|
||||
ModelInstance::DeepSeekOCR(model) => model.generate(mes),
|
||||
ModelInstance::HunyuanOCR(model) => model.generate(mes),
|
||||
@@ -131,6 +138,7 @@ impl<'a> GenerateModel for ModelInstance<'a> {
|
||||
ModelInstance::Qwen2_5VL(model) => model.generate_stream(mes),
|
||||
ModelInstance::Qwen3(model) => model.generate_stream(mes),
|
||||
ModelInstance::Qwen3VL(model) => model.generate_stream(mes),
|
||||
ModelInstance::Qwen3ASR(model) => model.generate_stream(mes),
|
||||
ModelInstance::DeepSeekOCR(model) => model.generate_stream(mes),
|
||||
ModelInstance::HunyuanOCR(model) => model.generate_stream(mes),
|
||||
ModelInstance::PaddleOCRVL(model) => model.generate_stream(mes),
|
||||
@@ -160,6 +168,14 @@ pub fn load_model(model_type: WhichModel, path: &str) -> Result<ModelInstance<'_
|
||||
let model = Qwen3GenerateModel::init(path, None, None)?;
|
||||
ModelInstance::Qwen3(model)
|
||||
}
|
||||
WhichModel::Qwen3ASR0_6B => {
|
||||
let model = Qwen3AsrGenerateModel::init(path, None, None)?;
|
||||
ModelInstance::Qwen3ASR(model)
|
||||
}
|
||||
WhichModel::Qwen3ASR1_7B => {
|
||||
let model = Qwen3AsrGenerateModel::init(path, None, None)?;
|
||||
ModelInstance::Qwen3ASR(model)
|
||||
}
|
||||
WhichModel::Qwen3vl2B => {
|
||||
let model = Qwen3VLGenerateModel::init(path, None, None)?;
|
||||
ModelInstance::Qwen3VL(model)
|
||||
|
||||
+189
-73
@@ -1,86 +1,202 @@
|
||||
use serde::Deserialize;
|
||||
use candle_nn::Activation;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct FunASRNanoConfig {
|
||||
pub audio_encoder_conf: AudioEncoderConf,
|
||||
pub llm_conf: LlmConf,
|
||||
pub audio_adaptor_conf: AudioAdaptorConf,
|
||||
pub detach_ctc_decoder: bool,
|
||||
pub ctc_decoder_conf: CtcDecoderConf,
|
||||
pub ctc_weight: f64,
|
||||
pub ctc_conf: CtcConf,
|
||||
pub frontend_conf: FrontendConf,
|
||||
use crate::models::qwen3::config::Qwen3Config;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
pub struct Qwen3ASRConfig {
|
||||
pub model_type: String,
|
||||
pub support_languages: Vec<String>,
|
||||
pub thinker_config: ThinkerConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct AudioEncoderConf {
|
||||
pub output_size: usize,
|
||||
pub attention_heads: usize,
|
||||
pub linear_units: usize,
|
||||
pub num_blocks: usize,
|
||||
pub tp_blocks: usize,
|
||||
pub dropout_rate: f64,
|
||||
pub positional_dropout_rate: f64,
|
||||
pub attention_dropout_rate: f64,
|
||||
pub input_layer: String,
|
||||
pub pos_enc_class: String,
|
||||
pub normalize_before: bool,
|
||||
pub kernel_size: usize,
|
||||
pub sanm_shfit: usize,
|
||||
pub selfattention_layer_type: String,
|
||||
pub freeze: bool,
|
||||
pub freeze_layer_num: i32,
|
||||
pub feat_permute: bool,
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
pub struct ThinkerConfig {
|
||||
pub model_type: String,
|
||||
pub audio_config: Qwen3ASRAudioConfig,
|
||||
pub audio_end_token_id: u32,
|
||||
pub audio_start_token_id: u32,
|
||||
pub audio_token_id: u32,
|
||||
pub dtype: String,
|
||||
pub initializer_range: f64,
|
||||
pub text_config: Qwen3ASRTextConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct LlmConf {
|
||||
pub hub: String,
|
||||
pub freeze: bool,
|
||||
pub llm_dtype: String,
|
||||
pub init_param_path: String,
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
pub struct Qwen3ASRAudioConfig {
|
||||
pub activation_dropout: f32,
|
||||
pub activation_function: Activation,
|
||||
pub add_cross_attention: bool,
|
||||
pub attention_dropout: f32,
|
||||
pub bad_words_ids: Option<Vec<u32>>,
|
||||
pub begin_suppress_tokens: Option<Vec<u32>>,
|
||||
pub bos_token_id: Option<u32>,
|
||||
pub chunk_size_feed_forward: usize,
|
||||
pub conv_chunksize: usize,
|
||||
pub cross_attention_hidden_size: Option<usize>,
|
||||
pub d_model: usize,
|
||||
pub decoder_start_token_id: Option<u32>,
|
||||
pub diversity_penalty: f64,
|
||||
pub do_sample: bool,
|
||||
pub downsample_hidden_size: usize,
|
||||
pub dropout: f32,
|
||||
pub early_stopping: bool,
|
||||
pub encoder_attention_heads: usize,
|
||||
pub encoder_ffn_dim: usize,
|
||||
pub encoder_layers: usize,
|
||||
pub encoder_no_repeat_ngram_size: usize,
|
||||
pub eos_token_id: Option<u32>,
|
||||
pub exponential_decay_length_penalty: Option<Vec<f32>>,
|
||||
pub forced_bos_token_id: Option<u32>,
|
||||
pub forced_eos_token_id: Option<u32>,
|
||||
pub id2label: std::collections::HashMap<String, String>,
|
||||
pub initializer_range: f64,
|
||||
pub is_decoder: bool,
|
||||
pub is_encoder_decoder: bool,
|
||||
pub label2id: std::collections::HashMap<String, usize>,
|
||||
pub length_penalty: f64,
|
||||
pub max_length: usize,
|
||||
pub max_source_positions: usize,
|
||||
pub min_length: usize,
|
||||
pub model_type: String,
|
||||
pub n_window: usize,
|
||||
pub n_window_infer: usize,
|
||||
pub no_repeat_ngram_size: usize,
|
||||
pub num_beam_groups: usize,
|
||||
pub num_beams: usize,
|
||||
pub num_hidden_layers: usize,
|
||||
pub num_mel_bins: usize,
|
||||
pub num_return_sequences: usize,
|
||||
pub output_attentions: bool,
|
||||
pub output_dim: usize,
|
||||
pub output_hidden_states: bool,
|
||||
pub output_scores: bool,
|
||||
pub pad_token_id: Option<u32>,
|
||||
pub prefix: Option<String>,
|
||||
pub problem_type: Option<String>,
|
||||
pub pruned_heads: std::collections::HashMap<String, Vec<i32>>,
|
||||
pub remove_invalid_values: bool,
|
||||
pub repetition_penalty: f64,
|
||||
pub return_dict: bool,
|
||||
pub return_dict_in_generate: bool,
|
||||
pub scale_embedding: bool,
|
||||
pub sep_token_id: Option<u32>,
|
||||
pub suppress_tokens: Option<Vec<u32>>,
|
||||
pub task_specific_params: Option<std::collections::HashMap<String, serde_json::Value>>,
|
||||
pub temperature: f64,
|
||||
pub tf_legacy_loss: bool,
|
||||
pub tie_encoder_decoder: bool,
|
||||
pub tie_word_embeddings: bool,
|
||||
pub tokenizer_class: Option<String>,
|
||||
pub top_k: usize,
|
||||
pub top_p: f64,
|
||||
pub torchscript: bool,
|
||||
pub typical_p: f64,
|
||||
pub use_bfloat16: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct AudioAdaptorConf {
|
||||
pub downsample_rate: usize,
|
||||
pub use_low_frame_rate: bool,
|
||||
pub ffn_dim: usize,
|
||||
pub llm_dim: usize,
|
||||
pub encoder_dim: usize,
|
||||
pub n_layer: usize,
|
||||
pub freeze: bool,
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
pub struct Qwen3ASRTextConfig {
|
||||
pub add_cross_attention: bool,
|
||||
pub attention_bias: bool,
|
||||
pub attention_dropout: f64,
|
||||
pub bad_words_ids: Option<Vec<u32>>,
|
||||
pub begin_suppress_tokens: Option<Vec<u32>>,
|
||||
pub bos_token_id: Option<u32>,
|
||||
pub chunk_size_feed_forward: usize,
|
||||
pub cross_attention_hidden_size: Option<usize>,
|
||||
pub decoder_start_token_id: Option<u32>,
|
||||
pub diversity_penalty: f64,
|
||||
pub do_sample: bool,
|
||||
pub dtype: Option<String>,
|
||||
pub early_stopping: bool,
|
||||
pub encoder_no_repeat_ngram_size: usize,
|
||||
pub eos_token_id: Option<u32>,
|
||||
pub exponential_decay_length_penalty: Option<Vec<f32>>,
|
||||
pub forced_bos_token_id: Option<u32>,
|
||||
pub forced_eos_token_id: Option<u32>,
|
||||
pub head_dim: usize,
|
||||
pub hidden_act: Activation,
|
||||
pub hidden_size: usize,
|
||||
pub id2label: std::collections::HashMap<String, String>,
|
||||
pub initializer_range: f64,
|
||||
pub intermediate_size: usize,
|
||||
pub is_decoder: bool,
|
||||
pub is_encoder_decoder: bool,
|
||||
pub label2id: std::collections::HashMap<String, u32>,
|
||||
pub length_penalty: f64,
|
||||
pub max_length: usize,
|
||||
pub max_position_embeddings: usize,
|
||||
pub min_length: usize,
|
||||
pub model_type: String,
|
||||
pub no_repeat_ngram_size: usize,
|
||||
pub num_attention_heads: usize,
|
||||
pub num_beam_groups: usize,
|
||||
pub num_beams: usize,
|
||||
pub num_hidden_layers: usize,
|
||||
pub num_key_value_heads: usize,
|
||||
pub num_return_sequences: usize,
|
||||
pub output_attentions: bool,
|
||||
pub output_hidden_states: bool,
|
||||
pub output_scores: bool,
|
||||
pub pad_token_id: Option<u32>,
|
||||
pub prefix: Option<String>,
|
||||
pub problem_type: Option<String>,
|
||||
pub remove_invalid_values: bool,
|
||||
pub repetition_penalty: f64,
|
||||
pub return_dict: bool,
|
||||
pub return_dict_in_generate: bool,
|
||||
pub rms_norm_eps: f64,
|
||||
pub rope_scaling: Qwen3ASRRopeScaling,
|
||||
pub rope_theta: f32,
|
||||
pub sep_token_id: Option<u32>,
|
||||
pub suppress_tokens: Option<Vec<u32>>,
|
||||
pub temperature: f64,
|
||||
pub tf_legacy_loss: bool,
|
||||
pub tie_encoder_decoder: bool,
|
||||
pub tie_word_embeddings: bool,
|
||||
pub tokenizer_class: Option<String>,
|
||||
pub top_k: usize,
|
||||
pub top_p: f64,
|
||||
pub torchscript: bool,
|
||||
pub typical_p: f64,
|
||||
pub use_bfloat16: bool,
|
||||
pub use_cache: bool,
|
||||
pub vocab_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct CtcDecoderConf {
|
||||
pub downsample_rate: u32,
|
||||
pub ffn_dim: u32,
|
||||
pub llm_dim: u32,
|
||||
pub encoder_dim: u32,
|
||||
pub n_layer: u32,
|
||||
pub freeze: bool,
|
||||
pub fn qwen3asr_text_config2qwen3_config(cfg: &Qwen3ASRTextConfig) -> Qwen3Config {
|
||||
Qwen3Config {
|
||||
attention_bias: cfg.attention_bias,
|
||||
attention_dropout: cfg.attention_dropout as f64,
|
||||
bos_token_id: cfg.bos_token_id.unwrap_or(151643) as u32,
|
||||
eos_token_id: cfg.eos_token_id.unwrap_or(151645) as u32,
|
||||
head_dim: cfg.head_dim,
|
||||
hidden_act: cfg.hidden_act,
|
||||
hidden_size: cfg.hidden_size,
|
||||
initializer_range: cfg.initializer_range as f64,
|
||||
intermediate_size: cfg.intermediate_size,
|
||||
max_position_embeddings: cfg.max_position_embeddings,
|
||||
max_window_layers: 0,
|
||||
num_attention_heads: cfg.num_attention_heads,
|
||||
num_hidden_layers: cfg.num_hidden_layers,
|
||||
num_key_value_heads: cfg.num_key_value_heads,
|
||||
rms_norm_eps: cfg.rms_norm_eps,
|
||||
rope_theta: cfg.rope_theta,
|
||||
tie_word_embeddings: true,
|
||||
torch_dtype: "bfloat16".to_string(),
|
||||
use_cache: cfg.use_cache,
|
||||
use_sliding_window: false,
|
||||
vocab_size: cfg.vocab_size,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct CtcConf {
|
||||
pub dropout_rate: f64,
|
||||
pub ctc_type: String,
|
||||
pub reduce: bool,
|
||||
pub ignore_nan_grad: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
pub struct FrontendConf {
|
||||
pub fs: usize,
|
||||
pub window: String,
|
||||
pub n_mels: usize,
|
||||
pub frame_length: f32,
|
||||
pub frame_shift: f32,
|
||||
pub lfr_m: usize,
|
||||
pub lfr_n: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cmvn_file: Option<serde_yaml::Value>,
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
pub struct Qwen3ASRRopeScaling {
|
||||
pub interleaved: bool,
|
||||
pub mrope_interleaved: bool,
|
||||
pub mrope_section: Vec<usize>,
|
||||
pub rope_type: String,
|
||||
pub r#type: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
|
||||
@@ -89,4 +205,4 @@ pub struct Qwen3ASRGenerationConfig {
|
||||
pub eos_token_id: Vec<usize>,
|
||||
pub pad_token_id: usize,
|
||||
pub temperature: f32,
|
||||
}
|
||||
}
|
||||
|
||||
+136
-155
@@ -1,34 +1,38 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use aha_openai_dive::v1::resources::chat::{
|
||||
ChatCompletionChunkResponse, ChatCompletionParameters, ChatCompletionResponse,
|
||||
};
|
||||
use anyhow::{Result, anyhow};
|
||||
use candle_core::{DType, Device, Tensor, pickle::read_all_with_key};
|
||||
use candle_core::{DType, Device, Tensor};
|
||||
use candle_nn::VarBuilder;
|
||||
use rocket::async_stream::stream;
|
||||
use rocket::futures::Stream;
|
||||
|
||||
use crate::{
|
||||
chat_template::ChatTemplate, models::{
|
||||
chat_template::ChatTemplate,
|
||||
models::{
|
||||
GenerateModel,
|
||||
fun_asr_nano::{
|
||||
config::FunASRNanoConfig, model::FunAsrNanoModel,
|
||||
feature_extractor::config::FeatureExtractor,
|
||||
qwen3_asr::{
|
||||
config::{Qwen3ASRConfig, Qwen3ASRGenerationConfig},
|
||||
model::Qwen3ASRModel,
|
||||
processor::Qwen3AsrProcessor,
|
||||
},
|
||||
qwen3::config::{Qwen3Config, Qwen3GenerationConfig}, qwen3_asr::{config::Qwen3ASRGenerationConfig, processor::Qwen3AsrProcessor},
|
||||
}, tokenizer::TokenizerModel, utils::{
|
||||
},
|
||||
tokenizer::TokenizerModel,
|
||||
utils::{
|
||||
build_completion_chunk_response, build_completion_response, find_type_files, get_device,
|
||||
get_dtype, get_logit_processor,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Qwen3AsrGenerateModel<'a> {
|
||||
chat_template: ChatTemplate<'a>,
|
||||
// tokenizer: TokenizerModel,
|
||||
tokenizer: TokenizerModel,
|
||||
processor: Qwen3AsrProcessor,
|
||||
// fun_asr_nano: FunAsrNanoModel,
|
||||
qwen3_asr: Qwen3ASRModel,
|
||||
device: Device,
|
||||
// dtype: DType,
|
||||
dtype: DType,
|
||||
eos_token_id1: u32,
|
||||
eos_token_id2: u32,
|
||||
generation_config: Qwen3ASRGenerationConfig,
|
||||
@@ -38,28 +42,40 @@ pub struct Qwen3AsrGenerateModel<'a> {
|
||||
impl<'a> Qwen3AsrGenerateModel<'a> {
|
||||
pub fn init(path: &str, device: Option<&Device>, dtype: Option<DType>) -> Result<Self> {
|
||||
let chat_template = ChatTemplate::init(path)?;
|
||||
let tokenizer = TokenizerModel::init(path)?;
|
||||
let generation_config_path = path.to_string() + "/generation_config.json";
|
||||
let generation_config: Qwen3ASRGenerationConfig =
|
||||
serde_json::from_slice(&std::fs::read(generation_config_path)?)?;
|
||||
let device = get_device(device);
|
||||
let processor = Qwen3AsrProcessor::new(&device)?;
|
||||
let preprocess_config_path = path.to_string() + "/preprocessor_config.json";
|
||||
let preprocess_config: FeatureExtractor =
|
||||
serde_json::from_slice(&std::fs::read(preprocess_config_path)?)?;
|
||||
let processor = Qwen3AsrProcessor::new(&device, &preprocess_config)?;
|
||||
let config_path = path.to_string() + "/config.json";
|
||||
let cfg: Qwen3ASRConfig = serde_json::from_slice(&std::fs::read(config_path)?)?;
|
||||
let cfg_dtype = cfg.thinker_config.dtype.as_str();
|
||||
let dtype = get_dtype(dtype, cfg_dtype);
|
||||
let model_list = find_type_files(path, "safetensors")?;
|
||||
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&model_list, dtype, &device)? };
|
||||
let qwen3_asr = Qwen3ASRModel::new(vb, &cfg)?;
|
||||
|
||||
|
||||
Ok(Self {
|
||||
chat_template,
|
||||
// tokenizer,
|
||||
tokenizer,
|
||||
processor,
|
||||
// fun_asr_nano,
|
||||
qwen3_asr,
|
||||
device,
|
||||
// dtype,
|
||||
dtype,
|
||||
eos_token_id1: generation_config.eos_token_id[0] as u32,
|
||||
eos_token_id2: generation_config.eos_token_id[1] as u32,
|
||||
generation_config,
|
||||
model_name: "qwen3-asr".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&mut self, mes: ChatCompletionParameters) -> Result<()> {
|
||||
impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
||||
fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
||||
let temperature = match mes.temperature {
|
||||
None => self.generation_config.temperature,
|
||||
Some(tem) => tem,
|
||||
@@ -68,145 +84,110 @@ impl<'a> Qwen3AsrGenerateModel<'a> {
|
||||
None => 34562u64,
|
||||
Some(s) => s as u64,
|
||||
};
|
||||
let mut logit_processor =
|
||||
get_logit_processor(Some(temperature), mes.top_p, None, seed);
|
||||
let mut logit_processor = get_logit_processor(Some(temperature), mes.top_p, None, seed);
|
||||
let render_text = self.chat_template.apply_chat_template(&mes)?;
|
||||
let audio_data =
|
||||
self.processor.process_info(&mes, &render_text)?;
|
||||
// for audio in audio_data {
|
||||
// let text =
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
let audio_datas = self
|
||||
.processor
|
||||
.process_info(&mes, &render_text, &self.tokenizer)?;
|
||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||
let mut generate = Vec::new();
|
||||
for data in audio_datas.iter() {
|
||||
let mut input_ids = data.input_ids.clone();
|
||||
let mut input_features = Some(data.input_features.clone().to_dtype(self.dtype)?);
|
||||
let mut seq_len = input_ids.dim(1)?;
|
||||
let mut seqlen_offset = 0;
|
||||
for _ in 0..sample_len {
|
||||
let logits =
|
||||
self.qwen3_asr
|
||||
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
||||
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||
let next_token = logit_processor.sample(&logits)?;
|
||||
generate.push(next_token);
|
||||
if next_token == self.eos_token_id1 || next_token == self.eos_token_id2 {
|
||||
break;
|
||||
}
|
||||
seqlen_offset += seq_len;
|
||||
seq_len = 1;
|
||||
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
input_features = None;
|
||||
}
|
||||
self.qwen3_asr.clear_kv_cache();
|
||||
}
|
||||
let num_token = generate.len() as u32;
|
||||
let res = self.tokenizer.token_decode(generate)?;
|
||||
let response = build_completion_response(res, &self.model_name, Some(num_token));
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn generate_stream(
|
||||
&mut self,
|
||||
mes: ChatCompletionParameters,
|
||||
) -> Result<
|
||||
Box<
|
||||
dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
||||
+ Send
|
||||
+ Unpin
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
let temperature = match mes.temperature {
|
||||
None => self.generation_config.temperature,
|
||||
Some(tem) => tem,
|
||||
};
|
||||
let seed = match mes.seed {
|
||||
None => 34562u64,
|
||||
Some(s) => s as u64,
|
||||
};
|
||||
let mut logit_processor = get_logit_processor(Some(temperature), mes.top_p, None, seed);
|
||||
let render_text = self.chat_template.apply_chat_template(&mes)?;
|
||||
let audio_datas = self
|
||||
.processor
|
||||
.process_info(&mes, &render_text, &self.tokenizer)?;
|
||||
let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||
let stream = stream! {
|
||||
let mut error_tokens = Vec::new();
|
||||
for data in audio_datas.iter() {
|
||||
let mut input_ids = data.input_ids.clone();
|
||||
let mut input_features = Some(data.input_features.clone().to_dtype(self.dtype)?);
|
||||
let mut seq_len = input_ids.dim(1)?;
|
||||
let mut seqlen_offset = 0;
|
||||
for _ in 0..sample_len {
|
||||
let logits =
|
||||
self.qwen3_asr
|
||||
.forward(&input_ids, seqlen_offset, input_features.as_ref())?;
|
||||
let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||
let next_token = logit_processor.sample(&logits)?;
|
||||
let mut decode_ids = Vec::new();
|
||||
if !error_tokens.is_empty() {
|
||||
decode_ids.extend_from_slice(&error_tokens);
|
||||
}
|
||||
decode_ids.push(next_token);
|
||||
let decoded_token = self.tokenizer.token_decode(decode_ids).map_err(|e| anyhow!(format!("stream decode error{e}")))?;
|
||||
if decoded_token.contains("�") {
|
||||
error_tokens.push(next_token);
|
||||
if error_tokens.len() > 3 {
|
||||
error_tokens.clear();
|
||||
}
|
||||
seqlen_offset += seq_len;
|
||||
seq_len = 1;
|
||||
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
input_features = None;
|
||||
continue;
|
||||
}
|
||||
error_tokens.clear();
|
||||
let chunk = build_completion_chunk_response(decoded_token, &self.model_name, None, None);
|
||||
yield Ok(chunk);
|
||||
if next_token == self.eos_token_id1 || next_token == self.eos_token_id2 {
|
||||
break;
|
||||
}
|
||||
seqlen_offset += seq_len;
|
||||
seq_len = 1;
|
||||
input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
input_features = None;
|
||||
}
|
||||
self.qwen3_asr.clear_kv_cache();
|
||||
}
|
||||
};
|
||||
Ok(Box::new(Box::pin(stream)))
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> GenerateModel for Qwen3AsrGenerateModel<'a> {
|
||||
// fn generate(&mut self, mes: ChatCompletionParameters) -> Result<ChatCompletionResponse> {
|
||||
// let temperature = match mes.temperature {
|
||||
// None => self.generation_config.temperature,
|
||||
// Some(tem) => tem,
|
||||
// };
|
||||
// let seed = match mes.seed {
|
||||
// None => 34562u64,
|
||||
// Some(s) => s as u64,
|
||||
// };
|
||||
// let mut logit_processor =
|
||||
// get_logit_processor(Some(temperature), mes.top_p, None, seed);
|
||||
// let audio_data =
|
||||
// self.processor.process_info(&mes)?;
|
||||
// for audio in audio_data {
|
||||
// let text =
|
||||
// }
|
||||
// let mut speech = Some(speech.to_dtype(self.dtype)?);
|
||||
// let mut fbank_mask = Some(&fbank_mask);
|
||||
// let mut seq_len = input_ids.dim(1)?;
|
||||
// let mut seqlen_offset = 0;
|
||||
// let mut generate = Vec::new();
|
||||
// let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||
// for _ in 0..sample_len {
|
||||
// let logits = self.fun_asr_nano.forward(
|
||||
// &input_ids,
|
||||
// speech.as_ref(),
|
||||
// fbank_mask,
|
||||
// seqlen_offset,
|
||||
// )?;
|
||||
// let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||
// let next_token = logit_processor.sample(&logits)?;
|
||||
// generate.push(next_token);
|
||||
// if next_token == self.eos_token_id1 || next_token == self.eos_token_id2 {
|
||||
// break;
|
||||
// }
|
||||
// seqlen_offset += seq_len;
|
||||
// seq_len = 1;
|
||||
// input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
// speech = None;
|
||||
// fbank_mask = None;
|
||||
// }
|
||||
// let num_token = generate.len() as u32;
|
||||
// let res = self.tokenizer.token_decode(generate)?;
|
||||
// self.fun_asr_nano.clear_kv_cache();
|
||||
// let response = build_completion_response(res, &self.model_name, Some(num_token));
|
||||
// Ok(response)
|
||||
// }
|
||||
|
||||
// fn generate_stream(
|
||||
// &mut self,
|
||||
// mes: ChatCompletionParameters,
|
||||
// ) -> Result<
|
||||
// Box<
|
||||
// dyn Stream<Item = Result<ChatCompletionChunkResponse, anyhow::Error>>
|
||||
// + Send
|
||||
// + Unpin
|
||||
// + '_,
|
||||
// >,
|
||||
// > {
|
||||
// let temperature = match mes.temperature {
|
||||
// None => self.generation_config.temperature,
|
||||
// Some(tem) => tem,
|
||||
// };
|
||||
// let top_p = match mes.top_p {
|
||||
// None => self.generation_config.top_p,
|
||||
// Some(top_p) => top_p,
|
||||
// };
|
||||
// let top_k = self.generation_config.top_k;
|
||||
// let seed = match mes.seed {
|
||||
// None => 34562u64,
|
||||
// Some(s) => s as u64,
|
||||
// };
|
||||
// let mut logit_processor =
|
||||
// get_logit_processor(Some(temperature), Some(top_p), Some(top_k), seed);
|
||||
// let (speech, fbank_mask, input_ids) = self.processor.process_info(&mes, &self.tokenizer)?;
|
||||
// let mut seq_len = input_ids.dim(1)?;
|
||||
// let mut seqlen_offset = 0;
|
||||
// let sample_len = mes.max_tokens.unwrap_or(1024);
|
||||
// let stream = stream! {
|
||||
// let mut error_tokens = Vec::new();
|
||||
// let mut speech = Some(speech.to_dtype(self.dtype)?);
|
||||
// let mut fbank_mask = Some(&fbank_mask);
|
||||
// let mut input_ids = input_ids;
|
||||
// for _ in 0..sample_len {
|
||||
// let logits = self.fun_asr_nano.forward(
|
||||
// &input_ids,
|
||||
// speech.as_ref(),
|
||||
// fbank_mask,
|
||||
// seqlen_offset,
|
||||
// )?;
|
||||
// let logits = logits.squeeze(0)?.squeeze(0)?.to_dtype(DType::F32)?;
|
||||
// let next_token = logit_processor.sample(&logits)?;
|
||||
// let mut decode_ids = Vec::new();
|
||||
// if !error_tokens.is_empty() {
|
||||
// decode_ids.extend_from_slice(&error_tokens);
|
||||
// }
|
||||
// decode_ids.push(next_token);
|
||||
// let decoded_token = self.tokenizer.token_decode(decode_ids).map_err(|e| anyhow!(format!("stream decode error{e}")))?;
|
||||
// if decoded_token.contains("�") {
|
||||
// error_tokens.push(next_token);
|
||||
// if error_tokens.len() > 3 {
|
||||
// error_tokens.clear();
|
||||
// }
|
||||
// seqlen_offset += seq_len;
|
||||
// seq_len = 1;
|
||||
// input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
// speech = None;
|
||||
// fbank_mask = None;
|
||||
// continue;
|
||||
// }
|
||||
// error_tokens.clear();
|
||||
// let chunk = build_completion_chunk_response(decoded_token, &self.model_name, None, None);
|
||||
// yield Ok(chunk);
|
||||
// if next_token == self.eos_token_id1 || next_token == self.eos_token_id2 {
|
||||
// break;
|
||||
// }
|
||||
// seqlen_offset += seq_len;
|
||||
// seq_len = 1;
|
||||
// input_ids = Tensor::from_vec(vec![next_token], (1, 1), &self.device)?;
|
||||
// speech = None;
|
||||
// fbank_mask = None;
|
||||
// }
|
||||
// self.fun_asr_nano.clear_kv_cache();
|
||||
// };
|
||||
// Ok(Box::new(Box::pin(stream)))
|
||||
// }
|
||||
// }
|
||||
|
||||
+334
-592
@@ -1,646 +1,388 @@
|
||||
use anyhow::Result;
|
||||
use candle_core::{D, IndexOp, Tensor};
|
||||
use candle_nn::{Conv1d, LayerNorm, Linear, Module, VarBuilder, linear, ops::softmax_last_dim};
|
||||
use candle_core::Tensor;
|
||||
use candle_nn::{
|
||||
Activation, Conv2d, Embedding, LayerNorm, Linear, Module, RmsNorm, VarBuilder, embedding,
|
||||
linear, linear_no_bias, rms_norm,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
models::{
|
||||
common::{
|
||||
NaiveAttention, TwoLinearMLP, eager_attention_forward, get_conv1d, get_layer_norm,
|
||||
common::{NaiveAttention, get_conv2d, get_layer_norm},
|
||||
qwen3::model::Qwen3DecoderLayer,
|
||||
qwen3_asr::{
|
||||
config::{
|
||||
Qwen3ASRAudioConfig, Qwen3ASRConfig, Qwen3ASRTextConfig, ThinkerConfig,
|
||||
qwen3asr_text_config2qwen3_config,
|
||||
},
|
||||
processor::get_feat_extract_output_lengths,
|
||||
},
|
||||
fun_asr_nano::config::FunASRNanoConfig,
|
||||
qwen3::{config::Qwen3Config, model::Qwen3Model},
|
||||
},
|
||||
position_embed::sinusoidal_pe::SinusoidalPositionEncoderCat,
|
||||
utils::tensor_utils::{get_equal_mask, attn_masked_fill, masked_scatter_dim0},
|
||||
position_embed::{
|
||||
rope::Qwen3VLTextRotaryEmbedding, sinusoidal_pe::SinusoidalPositionEncoderCat,
|
||||
},
|
||||
utils::tensor_utils::{
|
||||
get_equal_mask, masked_scatter_dim0, prepare_causal_attention_mask, split_tensor,
|
||||
split_tensor_with_size,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct MultiHeadedAttentionSANM {
|
||||
head_dim: usize,
|
||||
n_head: usize,
|
||||
linear_out: Linear,
|
||||
linear_q_k_v: Linear,
|
||||
fsmn_block: Conv1d,
|
||||
left_padding: usize,
|
||||
right_padding: usize,
|
||||
scaling: f64,
|
||||
}
|
||||
|
||||
impl MultiHeadedAttentionSANM {
|
||||
pub fn new(
|
||||
vb: VarBuilder,
|
||||
n_head: usize,
|
||||
in_dim: usize,
|
||||
hidden_dim: usize,
|
||||
kernel_size: usize,
|
||||
sanm_shfit: usize,
|
||||
) -> Result<Self> {
|
||||
let head_dim = hidden_dim / n_head;
|
||||
let linear_out = linear(hidden_dim, hidden_dim, vb.pp("linear_out"))?;
|
||||
let linear_q_k_v = linear(in_dim, hidden_dim * 3, vb.pp("linear_q_k_v"))?;
|
||||
let fsmn_block = get_conv1d(
|
||||
vb.pp("fsmn_block"),
|
||||
hidden_dim,
|
||||
hidden_dim,
|
||||
kernel_size,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
hidden_dim,
|
||||
false,
|
||||
)?;
|
||||
let mut left_padding = (kernel_size - 1) / 2;
|
||||
if sanm_shfit > 0 {
|
||||
left_padding += sanm_shfit;
|
||||
}
|
||||
let right_padding = kernel_size - 1 - left_padding;
|
||||
let scaling = (head_dim as f64).powf(-0.5);
|
||||
Ok(Self {
|
||||
head_dim,
|
||||
n_head,
|
||||
linear_out,
|
||||
linear_q_k_v,
|
||||
fsmn_block,
|
||||
left_padding,
|
||||
right_padding,
|
||||
scaling,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward_fsmn(
|
||||
&self,
|
||||
inputs: &Tensor,
|
||||
mask: Option<&Tensor>,
|
||||
mask_shfit_chunk: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let mut inputs = inputs.clone();
|
||||
let mask = if let Some(mask) = mask {
|
||||
let mut mask = mask.unsqueeze(D::Minus1)?.unsqueeze(0)?;
|
||||
if let Some(mask_shfit_chunk) = mask_shfit_chunk {
|
||||
mask = mask.broadcast_mul(mask_shfit_chunk)?;
|
||||
}
|
||||
inputs = inputs.broadcast_mul(&mask)?;
|
||||
Some(mask)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let xs = inputs.transpose(1, 2)?;
|
||||
let xs = xs.pad_with_zeros(D::Minus1, self.left_padding, self.right_padding)?;
|
||||
let xs = self.fsmn_block.forward(&xs)?;
|
||||
let xs = xs.transpose(1, 2)?;
|
||||
let mut xs = xs.add(&inputs)?;
|
||||
if let Some(mask) = mask {
|
||||
xs = xs.broadcast_mul(&mask)?;
|
||||
}
|
||||
Ok(xs)
|
||||
}
|
||||
pub fn forward_qkv(&self, xs: &Tensor) -> Result<(Tensor, Tensor, Tensor, Tensor)> {
|
||||
let (b, t, _) = xs.dims3()?;
|
||||
let q_k_v = self
|
||||
.linear_q_k_v
|
||||
.forward(xs)?
|
||||
.reshape((b, t, 3, self.n_head, ()))?
|
||||
.permute((2, 0, 3, 1, 4))?
|
||||
.contiguous()?;
|
||||
let q_h = q_k_v.i(0)?.contiguous()?;
|
||||
let k_h = q_k_v.i(1)?.contiguous()?;
|
||||
let v_h = q_k_v.i(2)?.contiguous()?;
|
||||
let v = v_h.transpose(1, 2)?.reshape((b, t, ()))?;
|
||||
Ok((q_h, k_h, v_h, v))
|
||||
}
|
||||
|
||||
pub fn forward_attention(
|
||||
&self,
|
||||
values: &Tensor,
|
||||
scores: &Tensor,
|
||||
mask: Option<&Tensor>,
|
||||
mask_att_chunk_encoder: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let bs = scores.dim(0)?;
|
||||
let attn = if let Some(mask) = mask {
|
||||
let mask = if let Some(mask_att_chunk_encoder) = mask_att_chunk_encoder {
|
||||
mask.mul(mask_att_chunk_encoder)?
|
||||
} else {
|
||||
mask.clone()
|
||||
};
|
||||
// mask: rank = 2
|
||||
let mask = get_equal_mask(&mask, 0)?;
|
||||
let scores = attn_masked_fill(scores, &mask, f32::NEG_INFINITY)?;
|
||||
let attn = softmax_last_dim(&scores)?;
|
||||
attn_masked_fill(&attn, &mask, 0.0)?
|
||||
} else {
|
||||
softmax_last_dim(scores)?
|
||||
};
|
||||
let xs = attn.matmul(values)?;
|
||||
let xs =
|
||||
xs.transpose(1, 2)?
|
||||
.contiguous()?
|
||||
.reshape((bs, (), self.n_head * self.head_dim))?;
|
||||
let xs = self.linear_out.forward(&xs)?;
|
||||
Ok(xs)
|
||||
}
|
||||
|
||||
pub fn forward_simple(&self, xs: &Tensor) -> Result<Tensor> {
|
||||
let (b, t, _) = xs.dims3()?;
|
||||
let q_k_v = self.linear_q_k_v.forward(xs)?;
|
||||
let dim = self.head_dim * self.n_head;
|
||||
let q_h = q_k_v
|
||||
.narrow(D::Minus1, 0, dim)?
|
||||
.reshape((b, t, self.n_head, ()))?
|
||||
.permute((0, 2, 1, 3))?;
|
||||
let k_h = q_k_v
|
||||
.narrow(D::Minus1, dim, dim)?
|
||||
.reshape((b, t, self.n_head, ()))?
|
||||
.permute((0, 2, 1, 3))?;
|
||||
let v = q_k_v.narrow(D::Minus1, dim * 2, dim)?;
|
||||
let v_h = v.reshape((b, t, self.n_head, ()))?.permute((0, 2, 1, 3))?;
|
||||
let fsmn_memory = v.transpose(1, 2)?;
|
||||
let fsmn_memory = fsmn_memory
|
||||
.pad_with_zeros(D::Minus1, self.left_padding, self.right_padding)?
|
||||
.contiguous()?;
|
||||
let fsmn_memory = self.fsmn_block.forward(&fsmn_memory)?;
|
||||
// let fsmn_memory = conv1d_group_parallel(&fsmn_memory, &self.fsmn_block)?;
|
||||
|
||||
let fsmn_memory = fsmn_memory.transpose(1, 2)?;
|
||||
let fsmn_memory = fsmn_memory.add(&v)?;
|
||||
let att_outs = eager_attention_forward(&q_h, &k_h, &v_h, None, None, self.scaling)?;
|
||||
let att_outs = att_outs.reshape((b, t, ()))?;
|
||||
let att_outs = self.linear_out.forward(&att_outs)?;
|
||||
let att_outs = att_outs.add(&fsmn_memory)?;
|
||||
Ok(att_outs)
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&self,
|
||||
xs: &Tensor,
|
||||
mask: Option<&Tensor>,
|
||||
mask_shfit_chunk: Option<&Tensor>,
|
||||
mask_att_chunk_encoder: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let (q_h, k_h, v_h, v) = self.forward_qkv(xs)?;
|
||||
let fsmn_memory = self.forward_fsmn(&v, mask, mask_shfit_chunk)?;
|
||||
let q_h = q_h.affine(self.scaling, 0.0)?;
|
||||
let scores = q_h.matmul(&k_h.transpose(D::Minus2, D::Minus1)?)?;
|
||||
let attn_outs = self.forward_attention(&v_h, &scores, mask, mask_att_chunk_encoder)?;
|
||||
let att_outs = attn_outs.add(&fsmn_memory)?;
|
||||
Ok(att_outs)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncoderLayerSANM {
|
||||
self_attn: MultiHeadedAttentionSANM,
|
||||
feed_forward: TwoLinearMLP,
|
||||
norm1: LayerNorm,
|
||||
norm2: LayerNorm,
|
||||
concat_linear: Option<Linear>,
|
||||
normalize_before: bool,
|
||||
in_dim: usize,
|
||||
hidden_dim: usize,
|
||||
}
|
||||
|
||||
impl EncoderLayerSANM {
|
||||
pub fn new(
|
||||
vb: VarBuilder,
|
||||
in_dim: usize,
|
||||
hidden_dim: usize,
|
||||
n_head: usize,
|
||||
kernel_size: usize,
|
||||
sanm_shfit: usize,
|
||||
hidden_units: usize,
|
||||
normalize_before: bool,
|
||||
concat_after: bool,
|
||||
) -> Result<Self> {
|
||||
let self_attn = MultiHeadedAttentionSANM::new(
|
||||
vb.pp("self_attn"),
|
||||
n_head,
|
||||
in_dim,
|
||||
hidden_dim,
|
||||
kernel_size,
|
||||
sanm_shfit,
|
||||
)?;
|
||||
let feed_forward = TwoLinearMLP::new(
|
||||
vb.pp("feed_forward"),
|
||||
hidden_dim,
|
||||
hidden_units,
|
||||
hidden_dim,
|
||||
candle_nn::Activation::Relu,
|
||||
true,
|
||||
"w_1",
|
||||
"w_2",
|
||||
)?;
|
||||
let norm1 = get_layer_norm(vb.pp("norm1"), 1e-5, in_dim)?;
|
||||
let norm2 = get_layer_norm(vb.pp("norm2"), 1e-5, hidden_dim)?;
|
||||
let concat_linear = if concat_after {
|
||||
let lin = linear(hidden_dim * 2, hidden_dim, vb.pp("concat_linear"))?;
|
||||
Some(lin)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Self {
|
||||
self_attn,
|
||||
feed_forward,
|
||||
norm1,
|
||||
norm2,
|
||||
concat_linear,
|
||||
normalize_before,
|
||||
in_dim,
|
||||
hidden_dim,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&self,
|
||||
xs: &Tensor,
|
||||
mask: Option<&Tensor>,
|
||||
mask_shfit_chunk: Option<&Tensor>,
|
||||
mask_att_chunk_encoder: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let stoch_layer_coeff = 1.0f64;
|
||||
let residual = xs.clone();
|
||||
let mut xs = if self.normalize_before {
|
||||
self.norm1.forward(xs)?
|
||||
} else {
|
||||
xs.clone()
|
||||
};
|
||||
if self.concat_linear.is_some() {
|
||||
let attn =
|
||||
self.self_attn
|
||||
.forward(&xs, mask, mask_shfit_chunk, mask_att_chunk_encoder)?;
|
||||
let x_concat = Tensor::cat(&[&xs, &attn], D::Minus1)?;
|
||||
if self.in_dim == self.hidden_dim {
|
||||
let x_concat = self
|
||||
.concat_linear
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.forward(&x_concat)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&x_concat)?;
|
||||
} else {
|
||||
xs = self
|
||||
.concat_linear
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.forward(&x_concat)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
}
|
||||
} else if self.in_dim == self.hidden_dim {
|
||||
let attn = self
|
||||
.self_attn
|
||||
.forward(&xs, mask, mask_shfit_chunk, mask_att_chunk_encoder)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&attn)?;
|
||||
} else {
|
||||
xs = self
|
||||
.self_attn
|
||||
.forward(&xs, mask, mask_shfit_chunk, mask_att_chunk_encoder)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
}
|
||||
|
||||
if !self.normalize_before {
|
||||
xs = self.norm1.forward(&xs)?;
|
||||
}
|
||||
let residual = xs.clone();
|
||||
if self.normalize_before {
|
||||
xs = self.norm2.forward(&xs)?;
|
||||
}
|
||||
xs = self
|
||||
.feed_forward
|
||||
.forward(&xs)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&xs)?;
|
||||
if !self.normalize_before {
|
||||
xs = self.norm2.forward(&xs)?;
|
||||
}
|
||||
Ok(xs)
|
||||
}
|
||||
|
||||
pub fn forward_simple(&self, xs: &Tensor) -> Result<Tensor> {
|
||||
let residual = xs.clone();
|
||||
let mut xs = self.norm1.forward(xs)?;
|
||||
if self.in_dim == self.hidden_dim {
|
||||
let attn = self.self_attn.forward_simple(&xs)?;
|
||||
xs = residual.add(&attn)?;
|
||||
} else {
|
||||
xs = self.self_attn.forward_simple(&xs)?;
|
||||
}
|
||||
|
||||
let residual = xs.clone();
|
||||
let xs = self.norm2.forward(&xs)?;
|
||||
|
||||
let xs = self.feed_forward.forward(&xs)?;
|
||||
let xs = residual.add(&xs)?;
|
||||
Ok(xs)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SenseVoiceEncoderSmall {
|
||||
embed: SinusoidalPositionEncoderCat,
|
||||
encoders0: EncoderLayerSANM,
|
||||
encoders: Vec<EncoderLayerSANM>,
|
||||
tp_encoders: Vec<EncoderLayerSANM>,
|
||||
after_norm: LayerNorm,
|
||||
tp_norm: LayerNorm,
|
||||
scaling: f64,
|
||||
}
|
||||
|
||||
impl SenseVoiceEncoderSmall {
|
||||
pub fn new(
|
||||
vb: VarBuilder,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
attention_heads: usize,
|
||||
linear_units: usize,
|
||||
num_blocks: usize,
|
||||
tp_blocks: usize,
|
||||
normalize_before: bool,
|
||||
kernel_size: usize,
|
||||
sanm_shfit: usize,
|
||||
) -> Result<Self> {
|
||||
let embed = SinusoidalPositionEncoderCat::new(Some(input_size), true, vb.device())?;
|
||||
|
||||
let encoders0 = EncoderLayerSANM::new(
|
||||
vb.pp("encoders0.0"),
|
||||
input_size,
|
||||
output_size,
|
||||
attention_heads,
|
||||
kernel_size,
|
||||
sanm_shfit,
|
||||
linear_units,
|
||||
normalize_before,
|
||||
false,
|
||||
)?;
|
||||
let mut encoders = vec![];
|
||||
let vb_encoders = vb.pp("encoders");
|
||||
for i in 0..(num_blocks - 1) {
|
||||
let encoder_i = EncoderLayerSANM::new(
|
||||
vb_encoders.pp(i),
|
||||
output_size,
|
||||
output_size,
|
||||
attention_heads,
|
||||
kernel_size,
|
||||
sanm_shfit,
|
||||
linear_units,
|
||||
normalize_before,
|
||||
false,
|
||||
)?;
|
||||
encoders.push(encoder_i);
|
||||
}
|
||||
let vb_tp_encoders = vb.pp("tp_encoders");
|
||||
let mut tp_encoders = vec![];
|
||||
for i in 0..tp_blocks {
|
||||
let tp_blocks_i = EncoderLayerSANM::new(
|
||||
vb_tp_encoders.pp(i),
|
||||
output_size,
|
||||
output_size,
|
||||
attention_heads,
|
||||
kernel_size,
|
||||
sanm_shfit,
|
||||
linear_units,
|
||||
normalize_before,
|
||||
false,
|
||||
)?;
|
||||
tp_encoders.push(tp_blocks_i);
|
||||
}
|
||||
let after_norm = get_layer_norm(vb.pp("after_norm"), 1e-5, output_size)?;
|
||||
let tp_norm = get_layer_norm(vb.pp("tp_norm"), 1e-5, output_size)?;
|
||||
let scaling = (output_size as f64).powf(0.5);
|
||||
Ok(Self {
|
||||
embed,
|
||||
encoders0,
|
||||
encoders,
|
||||
tp_encoders,
|
||||
after_norm,
|
||||
tp_norm,
|
||||
scaling,
|
||||
})
|
||||
}
|
||||
pub fn forward(&self, xs: &Tensor) -> Result<Tensor> {
|
||||
let xs = xs.affine(self.scaling, 0.0)?;
|
||||
let xs = self.embed.forward(&xs, 0)?;
|
||||
let mut xs = self.encoders0.forward_simple(&xs)?;
|
||||
for encoder_layer in &self.encoders {
|
||||
xs = encoder_layer.forward_simple(&xs)?;
|
||||
}
|
||||
xs = self.after_norm.forward(&xs)?;
|
||||
for tp_layer in &self.tp_encoders {
|
||||
xs = tp_layer.forward_simple(&xs)?;
|
||||
}
|
||||
xs = self.tp_norm.forward(&xs)?;
|
||||
Ok(xs)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdaptorEncoderLayer {
|
||||
pub struct Qwen3ASRAudioEncoderLayer {
|
||||
self_attn: NaiveAttention,
|
||||
feed_forward: TwoLinearMLP,
|
||||
norm1: LayerNorm,
|
||||
norm2: LayerNorm,
|
||||
concat_linear: Option<Linear>,
|
||||
normalize_before: bool,
|
||||
self_attn_layer_norm: LayerNorm,
|
||||
activation_fn: Activation,
|
||||
fc1: Linear,
|
||||
fc2: Linear,
|
||||
final_layer_norm: LayerNorm,
|
||||
}
|
||||
|
||||
impl AdaptorEncoderLayer {
|
||||
pub fn new(
|
||||
vb: VarBuilder,
|
||||
llm_dim: usize,
|
||||
n_head: usize,
|
||||
normalize_before: bool,
|
||||
concat_after: bool,
|
||||
) -> Result<Self> {
|
||||
impl Qwen3ASRAudioEncoderLayer {
|
||||
pub fn new(vb: VarBuilder, config: &Qwen3ASRAudioConfig) -> Result<Self> {
|
||||
let self_attn = NaiveAttention::new(
|
||||
vb.pp("self_attn"),
|
||||
llm_dim,
|
||||
n_head,
|
||||
n_head,
|
||||
config.d_model,
|
||||
config.encoder_attention_heads,
|
||||
config.encoder_attention_heads,
|
||||
None,
|
||||
true,
|
||||
Some("linear_q"),
|
||||
Some("linear_k"),
|
||||
Some("linear_v"),
|
||||
Some("linear_out"),
|
||||
Some("q_proj"),
|
||||
Some("k_proj"),
|
||||
Some("v_proj"),
|
||||
Some("out_proj"),
|
||||
)?;
|
||||
let feed_forward = TwoLinearMLP::new(
|
||||
vb.pp("feed_forward"),
|
||||
llm_dim,
|
||||
llm_dim / 4,
|
||||
llm_dim,
|
||||
candle_nn::Activation::Relu,
|
||||
true,
|
||||
"w_1",
|
||||
"w_2",
|
||||
)?;
|
||||
let norm1 = get_layer_norm(vb.pp("norm1"), 1e-5, llm_dim)?;
|
||||
let norm2 = get_layer_norm(vb.pp("norm2"), 1e-5, llm_dim)?;
|
||||
let concat_linear = if concat_after {
|
||||
let lin = linear(llm_dim * 2, llm_dim, vb.pp("concat_linear"))?;
|
||||
Some(lin)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let self_attn_layer_norm =
|
||||
get_layer_norm(vb.pp("self_attn_layer_norm"), 1e-5, config.d_model)?;
|
||||
let activation_fn = config.activation_function;
|
||||
let fc1 = linear(config.d_model, config.encoder_ffn_dim, vb.pp("fc1"))?;
|
||||
let fc2 = linear(config.encoder_ffn_dim, config.d_model, vb.pp("fc2"))?;
|
||||
let final_layer_norm = get_layer_norm(vb.pp("final_layer_norm"), 1e-5, config.d_model)?;
|
||||
Ok(Self {
|
||||
self_attn,
|
||||
feed_forward,
|
||||
norm1,
|
||||
norm2,
|
||||
concat_linear,
|
||||
normalize_before,
|
||||
self_attn_layer_norm,
|
||||
activation_fn,
|
||||
fc1,
|
||||
fc2,
|
||||
final_layer_norm,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward(&self, xs: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
|
||||
let stoch_layer_coeff = 1.0f64;
|
||||
let residual = xs.clone();
|
||||
let mut xs = if self.normalize_before {
|
||||
self.norm1.forward(xs)?
|
||||
} else {
|
||||
xs.clone()
|
||||
};
|
||||
if self.concat_linear.is_some() {
|
||||
let attn = self.self_attn.forward(&xs, None, None, mask, false)?;
|
||||
let x_concat = Tensor::cat(&[&xs, &attn], D::Minus1)?;
|
||||
let x_concat = self
|
||||
.concat_linear
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.forward(&x_concat)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&x_concat)?;
|
||||
} else {
|
||||
let attn = self
|
||||
.self_attn
|
||||
.forward(&xs, None, None, mask, false)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&attn)?;
|
||||
}
|
||||
if !self.normalize_before {
|
||||
xs = self.norm1.forward(&xs)?;
|
||||
}
|
||||
let residual = xs.clone();
|
||||
if self.normalize_before {
|
||||
xs = self.norm2.forward(&xs)?;
|
||||
}
|
||||
xs = self
|
||||
.feed_forward
|
||||
.forward(&xs)?
|
||||
.affine(stoch_layer_coeff, 0.0)?;
|
||||
xs = residual.add(&xs)?;
|
||||
if !self.normalize_before {
|
||||
xs = self.norm2.forward(&xs)?;
|
||||
}
|
||||
let xs = self.self_attn_layer_norm.forward(xs)?;
|
||||
let xs = self.self_attn.forward(&xs, None, None, mask, false)?;
|
||||
let residual = xs.add(&residual)?;
|
||||
let xs = self.final_layer_norm.forward(&residual)?;
|
||||
let xs = self.fc1.forward(&xs)?.apply(&self.activation_fn)?;
|
||||
let xs = self.fc2.forward(&xs)?;
|
||||
let xs = xs.add(&residual)?;
|
||||
Ok(xs)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioAdaptor {
|
||||
k: usize,
|
||||
linear1: Linear,
|
||||
linear2: Linear,
|
||||
blocks: Vec<AdaptorEncoderLayer>,
|
||||
pub struct Qwen3ASRAudioEncoder {
|
||||
n_window: usize,
|
||||
positional_embedding: SinusoidalPositionEncoderCat,
|
||||
layers: Vec<Qwen3ASRAudioEncoderLayer>,
|
||||
ln_post: LayerNorm,
|
||||
conv2d1: Conv2d,
|
||||
conv2d2: Conv2d,
|
||||
conv2d3: Conv2d,
|
||||
conv_out: Linear,
|
||||
proj1: Linear,
|
||||
act: Activation,
|
||||
proj2: Linear,
|
||||
// n_window_infer: usize,
|
||||
conv_chunksize: usize,
|
||||
}
|
||||
|
||||
impl AudioAdaptor {
|
||||
pub fn new(
|
||||
vb: VarBuilder,
|
||||
downsample_rate: usize,
|
||||
encoder_dim: usize,
|
||||
llm_dim: usize,
|
||||
ffn_dim: usize,
|
||||
n_layer: usize,
|
||||
attention_heads: usize,
|
||||
) -> Result<Self> {
|
||||
let linear1 = linear(encoder_dim * downsample_rate, ffn_dim, vb.pp("linear1"))?;
|
||||
let linear2 = linear(ffn_dim, llm_dim, vb.pp("linear2"))?;
|
||||
let mut blocks = vec![];
|
||||
let vb_blocks = vb.pp("blocks");
|
||||
for i in 0..n_layer {
|
||||
let layer =
|
||||
AdaptorEncoderLayer::new(vb_blocks.pp(i), llm_dim, attention_heads, true, false)?;
|
||||
blocks.push(layer);
|
||||
impl Qwen3ASRAudioEncoder {
|
||||
pub fn new(vb: VarBuilder, config: &Qwen3ASRAudioConfig) -> Result<Self> {
|
||||
let n_window = config.n_window;
|
||||
let positional_embedding =
|
||||
SinusoidalPositionEncoderCat::new(Some(config.d_model), true, vb.device())?;
|
||||
let mut layers = vec![];
|
||||
let vb_layers = vb.pp("layers");
|
||||
for i in 0..config.encoder_layers {
|
||||
let layer = Qwen3ASRAudioEncoderLayer::new(vb_layers.pp(i), config)?;
|
||||
layers.push(layer);
|
||||
}
|
||||
let ln_post = get_layer_norm(vb.pp("ln_post"), 1e-5, config.d_model)?;
|
||||
let conv2d1 = get_conv2d(
|
||||
vb.pp("conv2d1"),
|
||||
1,
|
||||
config.downsample_hidden_size,
|
||||
3,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
)?;
|
||||
let conv2d2 = get_conv2d(
|
||||
vb.pp("conv2d2"),
|
||||
config.downsample_hidden_size,
|
||||
config.downsample_hidden_size,
|
||||
3,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
)?;
|
||||
let conv2d3 = get_conv2d(
|
||||
vb.pp("conv2d3"),
|
||||
config.downsample_hidden_size,
|
||||
config.downsample_hidden_size,
|
||||
3,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
true,
|
||||
)?;
|
||||
let in_dim =
|
||||
config.downsample_hidden_size * ((((config.num_mel_bins + 1) / 2 + 1) / 2 + 1) / 2);
|
||||
let conv_out = linear_no_bias(in_dim, config.d_model, vb.pp("conv_out"))?;
|
||||
let proj1 = linear(config.d_model, config.d_model, vb.pp("proj1"))?;
|
||||
let act = config.activation_function;
|
||||
let proj2 = linear(config.d_model, config.output_dim, vb.pp("proj2"))?;
|
||||
// let n_window_infer = config.n_window_infer;
|
||||
let conv_chunksize = config.conv_chunksize;
|
||||
Ok(Self {
|
||||
k: downsample_rate,
|
||||
linear1,
|
||||
linear2,
|
||||
blocks,
|
||||
n_window,
|
||||
positional_embedding,
|
||||
layers,
|
||||
ln_post,
|
||||
conv2d1,
|
||||
conv2d2,
|
||||
conv2d3,
|
||||
conv_out,
|
||||
proj1,
|
||||
act,
|
||||
proj2,
|
||||
// n_window_infer,
|
||||
conv_chunksize,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward(&self, xs: &Tensor) -> Result<Tensor> {
|
||||
let (bs, seq_len, dim) = xs.dims3()?;
|
||||
let chunk_num = (seq_len - 1) / self.k + 1;
|
||||
let pad_num = chunk_num * self.k - seq_len;
|
||||
let xs = xs.pad_with_zeros(1, 0, pad_num)?;
|
||||
let xs = xs.contiguous()?.reshape((bs, chunk_num, dim * self.k))?;
|
||||
let xs = self.linear1.forward(&xs)?.relu()?;
|
||||
let mut xs = self.linear2.forward(&xs)?;
|
||||
for block in &self.blocks {
|
||||
xs = block.forward(&xs, None)?;
|
||||
// xs: (feature_dim, feature_len)
|
||||
let feature_lens = xs.dim(1)?;
|
||||
// let aftercnn_lens = get_feat_extract_output_lengths(feature_lens);
|
||||
let chunk_num = feature_lens / (self.n_window * 2);
|
||||
let mut chunk_lengths = vec![self.n_window * 2; chunk_num];
|
||||
let last = feature_lens % (self.n_window * 2);
|
||||
if last > 0 {
|
||||
chunk_lengths.push(last);
|
||||
}
|
||||
Ok(xs)
|
||||
let mut chunk_list = split_tensor(&xs.t()?, &chunk_lengths, 0)?;
|
||||
if last > 0 {
|
||||
let chunk_last = chunk_list
|
||||
.pop()
|
||||
.ok_or(anyhow::anyhow!(format!("chunk_list is empty")))?;
|
||||
let pad_size = self.n_window * 2 - last;
|
||||
let chunk_last = chunk_last.pad_with_zeros(0, 0, pad_size)?;
|
||||
chunk_list.push(chunk_last);
|
||||
}
|
||||
let padded_feature = Tensor::stack(&chunk_list, 0)?.transpose(1, 2)?;
|
||||
let feature_lens_after_cnn: Vec<usize> = chunk_lengths
|
||||
.iter()
|
||||
.map(|&i| get_feat_extract_output_lengths(i))
|
||||
.collect();
|
||||
let feature_len_after_cnn = feature_lens_after_cnn.iter().sum();
|
||||
let padded_feature = padded_feature.unsqueeze(1)?;
|
||||
let mut padded_embeds = vec![];
|
||||
let feature_splits = split_tensor_with_size(&padded_feature, self.conv_chunksize, 0)?;
|
||||
for chunk in feature_splits.iter() {
|
||||
let padded_embed = self.conv2d1.forward(chunk)?.gelu()?;
|
||||
let padded_embed = self.conv2d2.forward(&padded_embed)?.gelu()?;
|
||||
let padded_embed = self.conv2d3.forward(&padded_embed)?.gelu()?;
|
||||
padded_embeds.push(padded_embed);
|
||||
}
|
||||
let padded_embed = Tensor::cat(&padded_embeds, 0)?;
|
||||
let (b, c, f, t) = padded_embed.dims4()?;
|
||||
let padded_embed =
|
||||
padded_embed
|
||||
.permute((0, 3, 1, 2))?
|
||||
.contiguous()?
|
||||
.reshape((b, t, c * f))?;
|
||||
let padded_embed = self.conv_out.forward(&padded_embed)?;
|
||||
let padded_embed = self.positional_embedding.forward(&padded_embed, 0)?;
|
||||
let padded_embed = padded_embed.flatten(0, 1)?;
|
||||
let mut hidden_states = padded_embed
|
||||
.narrow(0, 0, feature_len_after_cnn)?
|
||||
.unsqueeze(0)?;
|
||||
for layer in &self.layers {
|
||||
hidden_states = layer.forward(&hidden_states, None)?;
|
||||
}
|
||||
let hidden_states = hidden_states.squeeze(0)?;
|
||||
let hidden_states = self.ln_post.forward(&hidden_states)?;
|
||||
let hidden_states = self.proj1.forward(&hidden_states)?.apply(&self.act)?;
|
||||
let hidden_states = self.proj2.forward(&hidden_states)?;
|
||||
Ok(hidden_states)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FunAsrNanoModel {
|
||||
audio_encoder: SenseVoiceEncoderSmall,
|
||||
audio_adaptor: AudioAdaptor,
|
||||
llm: Qwen3Model,
|
||||
pub struct Qwen3ASRThinkerTextModel {
|
||||
embed_tokens: Embedding,
|
||||
layers: Vec<Qwen3DecoderLayer>,
|
||||
norm: RmsNorm,
|
||||
rotary_emb: Qwen3VLTextRotaryEmbedding,
|
||||
mrope_section: Vec<usize>,
|
||||
}
|
||||
impl FunAsrNanoModel {
|
||||
pub fn new(vb: VarBuilder, config: &FunASRNanoConfig, llm_cfg: &Qwen3Config) -> Result<Self> {
|
||||
let input_size = config.frontend_conf.lfr_m * config.frontend_conf.n_mels;
|
||||
let audio_encoder = SenseVoiceEncoderSmall::new(
|
||||
vb.pp("audio_encoder"),
|
||||
input_size,
|
||||
config.audio_encoder_conf.output_size,
|
||||
config.audio_encoder_conf.attention_heads,
|
||||
config.audio_encoder_conf.linear_units,
|
||||
config.audio_encoder_conf.num_blocks,
|
||||
config.audio_encoder_conf.tp_blocks,
|
||||
config.audio_encoder_conf.normalize_before,
|
||||
config.audio_encoder_conf.kernel_size,
|
||||
config.audio_encoder_conf.sanm_shfit,
|
||||
)?;
|
||||
let audio_adaptor = AudioAdaptor::new(
|
||||
vb.pp("audio_adaptor"),
|
||||
config.audio_adaptor_conf.downsample_rate,
|
||||
config.audio_adaptor_conf.encoder_dim,
|
||||
config.audio_adaptor_conf.llm_dim,
|
||||
config.audio_adaptor_conf.ffn_dim,
|
||||
config.audio_adaptor_conf.n_layer,
|
||||
8,
|
||||
)?;
|
||||
let llm = Qwen3Model::new(llm_cfg, vb.pp("llm"))?;
|
||||
|
||||
impl Qwen3ASRThinkerTextModel {
|
||||
pub fn new(vb: VarBuilder, cfg: &Qwen3ASRTextConfig) -> Result<Self> {
|
||||
let embed_tokens = embedding(cfg.vocab_size, cfg.hidden_size, vb.pp("embed_tokens"))?;
|
||||
let mut layers = vec![];
|
||||
let vb_layers = vb.pp("layers");
|
||||
let qwen3cfg = qwen3asr_text_config2qwen3_config(cfg);
|
||||
for i in 0..cfg.num_hidden_layers {
|
||||
let layer = Qwen3DecoderLayer::new(&qwen3cfg, vb_layers.pp(i))?;
|
||||
layers.push(layer);
|
||||
}
|
||||
let norm = rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("norm"))?;
|
||||
let rotary_emb = Qwen3VLTextRotaryEmbedding::new(cfg.head_dim, cfg.rope_theta);
|
||||
Ok(Self {
|
||||
audio_encoder,
|
||||
audio_adaptor,
|
||||
llm,
|
||||
embed_tokens,
|
||||
layers,
|
||||
norm,
|
||||
rotary_emb,
|
||||
mrope_section: cfg.rope_scaling.mrope_section.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&mut self,
|
||||
input_embeds: &Tensor,
|
||||
seqlen_offset: usize,
|
||||
position_ids: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let (b_size, seq_len, _) = input_embeds.dims3()?;
|
||||
let position_ids = match position_ids {
|
||||
Some(ids) => ids.clone(),
|
||||
None => Tensor::arange(
|
||||
seqlen_offset as u32,
|
||||
(seq_len + seqlen_offset) as u32,
|
||||
input_embeds.device(),
|
||||
)?
|
||||
.unsqueeze(0)?
|
||||
.unsqueeze(0)?
|
||||
.broadcast_as((3, b_size, seq_len))?,
|
||||
};
|
||||
let (cos, sin) = self.rotary_emb.forward_asr(
|
||||
&position_ids,
|
||||
input_embeds.dtype(),
|
||||
self.mrope_section.clone(),
|
||||
)?;
|
||||
let mut xs = input_embeds.clone();
|
||||
let attention_mask: Option<Tensor> = {
|
||||
if seq_len <= 1 {
|
||||
None
|
||||
} else {
|
||||
Some(prepare_causal_attention_mask(
|
||||
b_size,
|
||||
seq_len,
|
||||
0,
|
||||
input_embeds.device(),
|
||||
)?)
|
||||
}
|
||||
};
|
||||
for layer in self.layers.iter_mut() {
|
||||
xs = layer.forward(&xs, &cos, &sin, attention_mask.as_ref())?;
|
||||
}
|
||||
let xs = self.norm.forward(&xs)?;
|
||||
Ok(xs)
|
||||
}
|
||||
|
||||
pub fn clear_kv_cache(&mut self) {
|
||||
for layer in self.layers.iter_mut() {
|
||||
layer.clear_kv_cache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Qwen3ASRThinker {
|
||||
audio_tower: Qwen3ASRAudioEncoder,
|
||||
model: Qwen3ASRThinkerTextModel,
|
||||
audio_token_id: u32,
|
||||
lm_head: Linear,
|
||||
}
|
||||
|
||||
impl Qwen3ASRThinker {
|
||||
pub fn new(vb: VarBuilder, config: &ThinkerConfig) -> Result<Self> {
|
||||
let audio_tower = Qwen3ASRAudioEncoder::new(vb.pp("audio_tower"), &config.audio_config)?;
|
||||
let model = Qwen3ASRThinkerTextModel::new(vb.pp("model"), &config.text_config)?;
|
||||
let lm_head = if config.text_config.tie_word_embeddings {
|
||||
Linear::new(model.embed_tokens.embeddings().clone(), None)
|
||||
} else {
|
||||
linear_no_bias(
|
||||
config.text_config.hidden_size,
|
||||
config.text_config.vocab_size,
|
||||
vb.pp("lm_head"),
|
||||
)?
|
||||
};
|
||||
Ok(Self {
|
||||
audio_tower,
|
||||
model,
|
||||
audio_token_id: config.audio_token_id,
|
||||
lm_head,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&mut self,
|
||||
input_ids: &Tensor,
|
||||
speech: Option<&Tensor>,
|
||||
fbank_mask: Option<&Tensor>,
|
||||
seqlen_offset: usize,
|
||||
input_features: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let mut inputs_embeds = self.llm.embedding_token_id(input_ids)?;
|
||||
if let Some(speech) = speech
|
||||
&& let Some(fbank_mask) = fbank_mask
|
||||
{
|
||||
let speech = self.audio_encoder.forward(speech)?;
|
||||
let encoder_out = self.audio_adaptor.forward(&speech)?;
|
||||
let speech_token_len = fbank_mask.sum_all()?.to_scalar::<u32>()?;
|
||||
let audio_embed = encoder_out
|
||||
.squeeze(0)?
|
||||
.narrow(0, 0, speech_token_len as usize)?;
|
||||
inputs_embeds = masked_scatter_dim0(&inputs_embeds, &audio_embed, fbank_mask)?;
|
||||
let mut input_embeds = self.model.embed_tokens.forward(input_ids)?;
|
||||
if let Some(input_features) = input_features {
|
||||
let audio_feature = self.audio_tower.forward(&input_features)?;
|
||||
// println!("audio_feature: {}", audio_feature);
|
||||
let audio_mask = get_equal_mask(input_ids, self.audio_token_id)?;
|
||||
let n_audio_tokens = audio_mask.sum_all()?.to_scalar::<u32>()?;
|
||||
if n_audio_tokens as usize != audio_feature.dim(0)? {
|
||||
return Err(anyhow::anyhow!(format!(
|
||||
"n_audio_tokens num: {} not equal to audio_feature len: {}",
|
||||
n_audio_tokens,
|
||||
audio_feature.dim(0)?
|
||||
)));
|
||||
}
|
||||
input_embeds = masked_scatter_dim0(&input_embeds, &audio_feature, &audio_mask)?;
|
||||
}
|
||||
let logits = self
|
||||
.llm
|
||||
.forward(None, Some(&inputs_embeds), seqlen_offset)?;
|
||||
let outputs = self.model.forward(&input_embeds, seqlen_offset, None)?;
|
||||
let seq_len = outputs.dim(1)?;
|
||||
let hidden_state = outputs.narrow(1, seq_len - 1, 1)?;
|
||||
let logits = self.lm_head.forward(&hidden_state)?;
|
||||
Ok(logits)
|
||||
}
|
||||
|
||||
pub fn clear_kv_cache(&mut self) {
|
||||
self.llm.clear_kv_cache();
|
||||
self.model.clear_kv_cache();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Qwen3ASRModel {
|
||||
thinker: Qwen3ASRThinker,
|
||||
}
|
||||
|
||||
impl Qwen3ASRModel {
|
||||
pub fn new(vb: VarBuilder, config: &Qwen3ASRConfig) -> Result<Self> {
|
||||
let thinker = Qwen3ASRThinker::new(vb.pp("thinker"), &config.thinker_config)?;
|
||||
Ok(Self { thinker })
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&mut self,
|
||||
input_ids: &Tensor,
|
||||
seqlen_offset: usize,
|
||||
input_features: Option<&Tensor>,
|
||||
) -> Result<Tensor> {
|
||||
let logits = self
|
||||
.thinker
|
||||
.forward(input_ids, seqlen_offset, input_features)?;
|
||||
Ok(logits)
|
||||
}
|
||||
pub fn clear_kv_cache(&mut self) {
|
||||
self.thinker.clear_kv_cache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use aha_openai_dive::v1::resources::chat::ChatCompletionParameters;
|
||||
use anyhow::Result;
|
||||
use candle_core::{Device, Tensor};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::utils::{
|
||||
audio_utils::{extract_audios, split_audio_into_chunks},
|
||||
capitalize_first_letter, extract_user_text_vec,
|
||||
tensor_utils::float_range_normalize,
|
||||
use crate::{
|
||||
models::feature_extractor::{
|
||||
config::FeatureExtractor, feature_extraction_whisper::WhisperFeatureExtractor,
|
||||
},
|
||||
tokenizer::TokenizerModel,
|
||||
utils::{
|
||||
audio_utils::{extract_audios, split_audio_into_chunks},
|
||||
capitalize_first_letter,
|
||||
tensor_utils::float_range_normalize,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Qwen3AsrProcessor {
|
||||
@@ -14,10 +19,12 @@ pub struct Qwen3AsrProcessor {
|
||||
sample_rate: usize,
|
||||
support_language: Vec<String>,
|
||||
max_asr_input_seconds: f32,
|
||||
whisper_feature_extracor: WhisperFeatureExtractor,
|
||||
audio_token: String,
|
||||
}
|
||||
|
||||
impl Qwen3AsrProcessor {
|
||||
pub fn new(device: &Device) -> Result<Self> {
|
||||
pub fn new(device: &Device, config: &FeatureExtractor) -> Result<Self> {
|
||||
let support_language: Vec<String> = vec![
|
||||
"Chinese",
|
||||
"English",
|
||||
@@ -53,11 +60,23 @@ impl Qwen3AsrProcessor {
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let whisper_feature_extracor = WhisperFeatureExtractor::new(
|
||||
config.feature_size,
|
||||
config.hop_length,
|
||||
// config.chunk_length,
|
||||
config.n_fft,
|
||||
config.dither,
|
||||
// config.padding_value,
|
||||
config.sampling_rate,
|
||||
device,
|
||||
)?;
|
||||
Ok(Self {
|
||||
device: device.clone(),
|
||||
sample_rate: 16000,
|
||||
support_language,
|
||||
max_asr_input_seconds: 1200.0,
|
||||
whisper_feature_extracor,
|
||||
audio_token: "<|audio_pad|>".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,7 +92,19 @@ impl Qwen3AsrProcessor {
|
||||
self.support_language.contains(lang)
|
||||
}
|
||||
|
||||
pub fn process_info(&self, mes: &ChatCompletionParameters, render: &str) -> Result<()> {
|
||||
fn replace_special_tokens(&self, text: &str, token_len: usize) -> String {
|
||||
let replace = "<|audio_placeholder|>".repeat(token_len as usize);
|
||||
let text = text.replacen(&self.audio_token, &replace, 1);
|
||||
let text = text.replace("<|audio_placeholder|>", &self.audio_token);
|
||||
text
|
||||
}
|
||||
|
||||
pub fn process_info(
|
||||
&self,
|
||||
mes: &ChatCompletionParameters,
|
||||
render: &str,
|
||||
tokenizer: &TokenizerModel,
|
||||
) -> Result<Vec<AudioData>> {
|
||||
let audio_count = render
|
||||
.matches("<|audio_start|><|audio_pad|><|audio_end|>")
|
||||
.count();
|
||||
@@ -104,24 +135,38 @@ impl Qwen3AsrProcessor {
|
||||
let wavs = split_audio_into_chunks(wav, self.sample_rate, self.max_asr_input_seconds)?;
|
||||
split_wavs.extend_from_slice(&wavs);
|
||||
}
|
||||
|
||||
|
||||
// let mut audio_datas = vec![];
|
||||
// for (i, wav) in audio_tensors.iter().enumerate() {
|
||||
// let wavs = split_audio_into_chunks(wav, self.sample_rate, self.max_asr_input_seconds)?;
|
||||
// for i_w in wavs {
|
||||
// let audio_data = AudioData {
|
||||
// wav: i_w,
|
||||
// language: langs[i].clone(),
|
||||
// };
|
||||
// audio_datas.push(audio_data);
|
||||
// }
|
||||
// }
|
||||
Ok(())
|
||||
let mut audio_datas = vec![];
|
||||
for wav in split_wavs.iter() {
|
||||
let (input_features, _) =
|
||||
self.whisper_feature_extracor
|
||||
.call(wav, self.sample_rate, false)?;
|
||||
let audio_len = input_features.dim(2)?;
|
||||
let output_len = get_feat_extract_output_lengths(audio_len);
|
||||
let text = self.replace_special_tokens(&render, output_len);
|
||||
let input_ids = tokenizer.text_encode(text, &self.device)?;
|
||||
let input_features = input_features.squeeze(0)?;
|
||||
let audio = AudioData {
|
||||
input_features,
|
||||
input_ids,
|
||||
};
|
||||
audio_datas.push(audio);
|
||||
}
|
||||
Ok(audio_datas)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioData {
|
||||
pub wav: Tensor,
|
||||
pub language: Option<String>,
|
||||
pub input_features: Tensor,
|
||||
pub input_ids: Tensor,
|
||||
}
|
||||
|
||||
pub fn get_feat_extract_output_lengths(audio_len: usize) -> usize {
|
||||
let input_len_leave = audio_len % 100;
|
||||
let output_len = if input_len_leave > 0 {
|
||||
let feat_lengths = (input_len_leave - 1) / 2 + 1;
|
||||
((feat_lengths - 1) / 2 + 1 - 1) / 2 + 1 + (audio_len / 100) * 13
|
||||
} else {
|
||||
(audio_len / 100) * 13
|
||||
};
|
||||
output_len
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use candle_nn::{
|
||||
use crate::{
|
||||
models::{
|
||||
common::{
|
||||
GLU, NaiveAttention, TwoLinearMLP, eager_attention_forward, get_conv1d, get_layer_norm,
|
||||
GLU, TwoLinearMLP, eager_attention_forward, get_conv1d, get_layer_norm,
|
||||
},
|
||||
w2v_bert_2_0::config::W2VBert2_0Config,
|
||||
},
|
||||
|
||||
@@ -340,6 +340,70 @@ impl Qwen3VLTextRotaryEmbedding {
|
||||
}
|
||||
Ok(freqs_t)
|
||||
}
|
||||
|
||||
pub fn apply_interleaved_mrope_asr(
|
||||
&self,
|
||||
freqs: &Tensor,
|
||||
mrope_section: Vec<usize>,
|
||||
) -> Result<Tensor> {
|
||||
let mut freqs_t = freqs.i(0)?.contiguous()?; //(3, bs, seq_len, head_dim //2) -> (bs, seq_len, head_dim //2)
|
||||
|
||||
// for dim in 1..3 {
|
||||
for (dim, offset) in (1..3).enumerate() {
|
||||
let dim = dim +1;
|
||||
let length = mrope_section[dim];
|
||||
let idx = Tensor::arange_step(offset as u32, length as u32, 3, freqs.device())?;
|
||||
let src = freqs.i(dim)?.contiguous()?; // (bs, seq_len, head_dim //2)
|
||||
let src = src.index_select(&idx, D::Minus1)?.contiguous()?;
|
||||
let idx = idx
|
||||
.unsqueeze(0)?
|
||||
.unsqueeze(0)?
|
||||
.broadcast_as(src.shape())?
|
||||
.contiguous()?;
|
||||
freqs_t = freqs_t.scatter(&idx, &src, D::Minus1)?;
|
||||
}
|
||||
Ok(freqs_t)
|
||||
}
|
||||
|
||||
pub fn forward_asr(
|
||||
&self,
|
||||
position_ids: &Tensor,
|
||||
dtype: DType,
|
||||
mrope_section: Vec<usize>,
|
||||
) -> Result<(Tensor, Tensor)> {
|
||||
// position_ids shape: (3, bs, position) -> (3, bs, 1, position)
|
||||
let position_ids = if position_ids.rank() == 2 {
|
||||
let (bs, len) = position_ids.dims2()?;
|
||||
position_ids.unsqueeze(0)?.expand((3, bs, len))?
|
||||
} else {
|
||||
position_ids.clone()
|
||||
};
|
||||
let position_ids_expanded = position_ids
|
||||
.unsqueeze(D::Minus2)?
|
||||
.to_dtype(DType::F32)?
|
||||
.contiguous()?;
|
||||
// inv_freq Vec<f32> -> Tensor(1, 1, head_dim / 2, 1) -> (3, bs, head_dim / 2, 1)
|
||||
let inv_freq_expanded = Tensor::from_vec(
|
||||
self.inv_freq.clone(),
|
||||
(1, 1, self.inv_freq.len(), 1),
|
||||
position_ids.device(),
|
||||
)?
|
||||
.broadcast_as((3, position_ids.dim(1)?, self.inv_freq.len(), 1))?
|
||||
.to_dtype(DType::F32)?
|
||||
.contiguous()?;
|
||||
|
||||
// (3, bs, head_dim / 2, 1) matmul (3, bs, 1, position)
|
||||
// -> (3, bs, head_dim / 2, seq_len) -> (3, bs, seq_len, head_dim / 2)
|
||||
let freqs = inv_freq_expanded
|
||||
.matmul(&position_ids_expanded)?
|
||||
.transpose(2, 3)?;
|
||||
let freqs = self.apply_interleaved_mrope_asr(&freqs, mrope_section)?;
|
||||
let emb = Tensor::cat(&[&freqs, &freqs], D::Minus1)?.contiguous()?;
|
||||
let cos = emb.cos()?;
|
||||
let sin = emb.sin()?;
|
||||
Ok((cos.to_dtype(dtype)?, sin.to_dtype(dtype)?))
|
||||
}
|
||||
|
||||
pub fn forward(
|
||||
&self,
|
||||
position_ids: &Tensor,
|
||||
|
||||
+69
-7
@@ -1,6 +1,10 @@
|
||||
use anyhow::{Ok, Result, anyhow};
|
||||
use candle_core::{Device, Tensor};
|
||||
use tokenizers::Tokenizer;
|
||||
use serde_json::Value;
|
||||
use tokenizers::{
|
||||
AddedToken, Tokenizer, decoders::byte_level::ByteLevel as ByteLevelDecoder, models::bpe::BPE,
|
||||
pre_tokenizers::byte_level::ByteLevel,
|
||||
};
|
||||
|
||||
pub struct TokenizerModel {
|
||||
pub tokenizer: Tokenizer,
|
||||
@@ -14,12 +18,70 @@ impl TokenizerModel {
|
||||
"model path file not exists"
|
||||
);
|
||||
let tokenizer_file = path.clone() + "/tokenizer.json";
|
||||
assert!(
|
||||
std::path::Path::new(&tokenizer_file).exists(),
|
||||
"tokenizer.json not exists in model path"
|
||||
);
|
||||
let tokenizer = Tokenizer::from_file(tokenizer_file)
|
||||
.map_err(|e| anyhow!(format!("tokenizer from file error{}", e)))?;
|
||||
let tokenizer = if std::path::Path::new(&tokenizer_file).exists() {
|
||||
Tokenizer::from_file(tokenizer_file)
|
||||
.map_err(|e| anyhow!(format!("tokenizer from file error{}", e)))?
|
||||
} else {
|
||||
// 如果不存在 tokenizer.json,尝试使用 vocab.json 和 merges.txt
|
||||
let vocab_file = path.clone() + "/vocab.json";
|
||||
let merges_file = path.clone() + "/merges.txt";
|
||||
let config_file = path.clone() + "/tokenizer_config.json";
|
||||
|
||||
if !std::path::Path::new(&vocab_file).exists() {
|
||||
return Err(anyhow!(
|
||||
"Neither tokenizer.json nor vocab.json found in model path"
|
||||
));
|
||||
}
|
||||
|
||||
if !std::path::Path::new(&merges_file).exists() {
|
||||
return Err(anyhow!(
|
||||
"Neither tokenizer.json nor merges.txt found in model path"
|
||||
));
|
||||
}
|
||||
// 创建 BPE 模型
|
||||
let bpe = BPE::from_file(&vocab_file, &merges_file)
|
||||
.build()
|
||||
.map_err(|e| anyhow!(format!("failed to build BPE tokenizer: {}", e)))?;
|
||||
|
||||
// 创建分词器
|
||||
let mut tokenizer = Tokenizer::new(bpe);
|
||||
// 添加字节级预分词器,这会处理换行符等特殊字符
|
||||
let byte_level_pre_tokenizer = ByteLevel::new(false, true, false);
|
||||
tokenizer.with_pre_tokenizer(Some(byte_level_pre_tokenizer));
|
||||
tokenizer.with_decoder(Some(ByteLevelDecoder::default()));
|
||||
if std::path::Path::new(&config_file).exists() {
|
||||
let config_content = std::fs::read_to_string(&config_file)?;
|
||||
let config: Value = serde_json::from_str(&config_content)?;
|
||||
if let Some(added_tokens_decoder) = config.get("added_tokens_decoder") {
|
||||
let mut special_tokens = Vec::new();
|
||||
|
||||
if let Value::Object(tokens_map) = added_tokens_decoder {
|
||||
for (_, token_info) in tokens_map {
|
||||
if let Value::Object(token_obj) = token_info {
|
||||
if let Some(content_val) = token_obj.get("content") {
|
||||
if let Some(content) = content_val.as_str() {
|
||||
let special = token_obj
|
||||
.get("special")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let added_token =
|
||||
AddedToken::from(content.to_string(), special);
|
||||
special_tokens.push(added_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加所有特殊标记
|
||||
if !special_tokens.is_empty() {
|
||||
tokenizer.add_special_tokens(&special_tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
tokenizer
|
||||
};
|
||||
Ok(Self { tokenizer })
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ use anyhow::{Result, anyhow};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use candle_core::{
|
||||
Context, DType, Device, Shape, Tensor,
|
||||
pickle::{Object, PthTensors, Stack, TensorInfo, read_all_with_key},
|
||||
pickle::{Object, Stack, TensorInfo, read_all_with_key},
|
||||
};
|
||||
use candle_nn::VarBuilder;
|
||||
use candle_transformers::generation::{LogitsProcessor, Sampling};
|
||||
|
||||
@@ -105,13 +105,14 @@ pub fn split_tensor_with_size<D: Dim>(
|
||||
let dim = dim.to_index(t.shape(), "split")?;
|
||||
let mut split_res = Vec::new();
|
||||
let dim_size = t.dim(dim)?;
|
||||
assert_eq!(
|
||||
dim_size % splits_size,
|
||||
0,
|
||||
"input tensor dim size % splits_size must be equal to 0"
|
||||
);
|
||||
for split in (0..dim_size).step_by(splits_size) {
|
||||
split_res.push(t.narrow(dim, split, splits_size)?);
|
||||
// assert_eq!(
|
||||
// dim_size % splits_size,
|
||||
// 0,
|
||||
// "input tensor dim size % splits_size must be equal to 0"
|
||||
// );
|
||||
for (i, split) in (0..dim_size).step_by(splits_size).enumerate() {
|
||||
let size = splits_size.min(dim_size - i*splits_size);
|
||||
split_res.push(t.narrow(dim, split, size)?);
|
||||
}
|
||||
Ok(split_res)
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use aha::utils::{audio_utils::create_hann_window, tensor_utils::interpolate_nearest_1d};
|
||||
use aha::utils::{tensor_utils::interpolate_nearest_1d};
|
||||
use anyhow::Result;
|
||||
use candle_core::{DType, Tensor};
|
||||
use candle_core::{Tensor};
|
||||
// use symphonia::core::io::MediaSourceStream;
|
||||
|
||||
#[test]
|
||||
|
||||
+52
-56
@@ -1,12 +1,57 @@
|
||||
use std::{pin::pin, time::Instant};
|
||||
|
||||
use aha::models::{GenerateModel, fun_asr_nano::generate::FunAsrNanoGenerateModel, qwen3_asr::generate::Qwen3AsrGenerateModel};
|
||||
use aha::models::{GenerateModel, qwen3_asr::generate::Qwen3AsrGenerateModel};
|
||||
use aha_openai_dive::v1::resources::chat::ChatCompletionParameters;
|
||||
use anyhow::Result;
|
||||
use rocket::futures::StreamExt;
|
||||
#[test]
|
||||
fn qwen3_asr_generate() -> Result<()> {
|
||||
// RUST_BACKTRACE=1 cargo test -F cuda qwen3_asr_generate -r -- --nocapture
|
||||
let save_dir =
|
||||
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||
let model_path = format!("{}/Qwen/Qwen3-ASR-0.6B/", save_dir); //Qwen/Qwen3-ASR-1.7B
|
||||
let message = r#"
|
||||
{
|
||||
"model": "qwen3-asr",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "audio",
|
||||
"audio_url":
|
||||
{
|
||||
"url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
// "metadata": {"language": "Chinese"}
|
||||
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
|
||||
let i_start = Instant::now();
|
||||
let mut model = Qwen3AsrGenerateModel::init(&model_path, None, None)?;
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("Time elapsed in load model is: {:?}", i_duration);
|
||||
let i_start = Instant::now();
|
||||
let res = model.generate(mes)?;
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("generate: \n {:?}", res);
|
||||
if res.usage.is_some() {
|
||||
let num_token = res.usage.as_ref().unwrap().total_tokens;
|
||||
let duration_secs = i_duration.as_secs_f64();
|
||||
let tps = num_token as f64 / duration_secs;
|
||||
println!("Tokens per second (TPS): {:.2}", tps);
|
||||
}
|
||||
println!("Time elapsed in generate is: {:?}", i_duration);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn qwen3_asr_stream() -> Result<()> {
|
||||
// RUST_BACKTRACE=1 cargo test -F cuda qwen3_asr_stream -r -- --nocapture
|
||||
let save_dir =
|
||||
aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||
let model_path = format!("{}/Qwen/Qwen3-ASR-0.6B/", save_dir);
|
||||
@@ -26,69 +71,20 @@ fn qwen3_asr_generate() -> Result<()> {
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {"language": "Chinese"}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
let mes: ChatCompletionParameters = serde_json::from_str(message)?;
|
||||
let i_start = Instant::now();
|
||||
let mut model = Qwen3AsrGenerateModel::init(&model_path, None, None)?;
|
||||
let mut fun_asr_model = Qwen3AsrGenerateModel::init(&model_path, None, None)?;
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("Time elapsed in load model is: {:?}", i_duration);
|
||||
let i_start = Instant::now();
|
||||
let res = model.generate(mes)?;
|
||||
let mut stream = pin!(fun_asr_model.generate_stream(mes)?);
|
||||
while let Some(item) = stream.next().await {
|
||||
println!("generate: \n {:?}", item);
|
||||
}
|
||||
let i_duration = i_start.elapsed();
|
||||
println!("generate: \n {:?}", res);
|
||||
// if res.usage.is_some() {
|
||||
// let num_token = res.usage.as_ref().unwrap().total_tokens;
|
||||
// let duration_secs = i_duration.as_secs_f64();
|
||||
// let tps = num_token as f64 / duration_secs;
|
||||
// println!("Tokens per second (TPS): {:.2}", tps);
|
||||
// }
|
||||
println!("Time elapsed in generate is: {:?}", i_duration);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn qwen3_asr_stream() -> Result<()> {
|
||||
// // RUST_BACKTRACE=1 cargo test -F cuda fun_asr_nano_stream -r -- --nocapture
|
||||
// let save_dir =
|
||||
// aha::utils::get_default_save_dir().ok_or(anyhow::anyhow!("Failed to get save dir"))?;
|
||||
// let model_path = format!("{}/Qwen/Qwen3-ASR-0.6B/", save_dir);
|
||||
// let message = r#"
|
||||
// {
|
||||
// "model": "qwen3-asr",
|
||||
// "messages": [
|
||||
// {
|
||||
// "role": "user",
|
||||
// "content": [
|
||||
// {
|
||||
// "type": "audio",
|
||||
// "audio_url":
|
||||
// {
|
||||
// "url": "https://package-release.coderbox.cn/aiway/test/other/%E5%93%AA%E5%90%92.wav"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "type": "text",
|
||||
// "text": "语音转写:"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// "#;
|
||||
// let mes: ChatCompletionParameters = serde_json::from_str(message)?;
|
||||
// let i_start = Instant::now();
|
||||
// let mut fun_asr_model = FunAsrNanoGenerateModel::init(&model_path, None, None)?;
|
||||
// let i_duration = i_start.elapsed();
|
||||
// println!("Time elapsed in load model is: {:?}", i_duration);
|
||||
// let i_start = Instant::now();
|
||||
// let mut stream = pin!(fun_asr_model.generate_stream(mes)?);
|
||||
// while let Some(item) = stream.next().await {
|
||||
// println!("generate: \n {:?}", item);
|
||||
// }
|
||||
// let i_duration = i_start.elapsed();
|
||||
// println!("Time elapsed in generate is: {:?}", i_duration);
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user