端到端语音对话(Qwen2.5-Omni)真打不过级联ASR+LLM+TTS?RTX 4090 单卡实测全记录

📅 2026/6/30 12:50:31
端到端语音对话(Qwen2.5-Omni)真打不过级联ASR+LLM+TTS?RTX 4090 单卡实测全记录
关键词:Qwen2.5-Omni、端到端语音对话、speech-to-speech、SenseVoice、CosyVoice2、级联语音、RTX 4090、本地部署、显存 OOM、RTF一句话结论:在非流式口径下,Qwen2.5-Omni-7B 端到端 speech-in→speech-out 完整回复要11.8 秒,而 SenseVoice LLM CosyVoice2 级联只要5.66 秒——端到端反而更慢。但端到端真的听懂了你的情绪,而且 24G 单卡跑它几乎没有余量。本文把两套方案在同一块 RTX 4090 上从环境、下载、代码到踩坑全部复现一遍,所有数字均为脚本实测(计时 nvidia-smi采显存),非估算。0. 为什么要做这个实测端到端语音大模型(speech-in → speech-out,一个模型直接听音频、直接吐语音)这两年很火:Qwen2.5-Omni、GLM-4-Voice、Moshi、Step-Audio……宣传里都是低延迟“全双工”“听得懂情绪”。但做陪伴类产品的人最关心的其实是两个朴素问题:它到底比我现在的ASR LLM TTS三段式快多少?一块 24G 的 4090 到底跑不跑得动?能不能上生产?网上纸面对比一大堆,真机数字很少。于是我在一块 RTX 4090 24G 上,把Qwen2.5-Omni-7B 端到端和SenseVoice(ASR) LLM CosyVoice2(TTS) 级联喂同一段中文语音问题,逐段计时、采显存,得到一组挺反直觉的数据。测试硬件:RTX 4090 24G / 128G RAM,模型权重放在机械盘/mnt/sda。1. 先看结论(TL;DR)同一段 10 秒中文语音问题(“用一句话介绍你自己 我今天心情低落,做点什么开心起来”),喂给两套方案:维度Qwen2.5-Omni-7B(端到端)级联 ASRLLMTTS听输入的方式直接听音频(含语气/情绪/语速)先 ASR 转成文字(丢掉语气情绪)文字回复时延1.4s(warm;cold 首次 4.5s)ASR 0.62s LLM 1.4s ≈ 2.0s完整语音回复时延11.8s(非流式,等整段音频)ASR 0.62 LLM 1.4 TTS 3.64 5.66s输出音频时长16.1s16.5s生成 RTF0.73(Talkercode2wav)TTS 段 0.22(CosyVoice2)显存峰值21.3–22.0GB(近满 24G,OOM 边缘)三段各 4GB模型数1 个,架构统一3 个,模块化可换加载耗时15–79s(7B 音频塔,~21GB)各段秒级中文质量✅ 自然口语,带语气词嗯…✅ ASR 转写零错、TTS 克隆自然三条最值得记住的发现:反直觉:非流式下级联更快(5.66s vs 11.8s)。因为 Qwen2.5-Omni 的 Talker code2wav 声码器一次性吐整段音频,比 CosyVoice2 慢(Omni RTF 0.73 vs CosyVoice2 RTF 0.22)。端到端的真正价值不在批量出整段更快,而在懂输入语气情绪 架构统一 可流式低延迟首响。端到端真的听懂了。Qwen2.5-Omni 直接听音频,回复里自带你今天心情低落啊,那你可以看看喜剧电影、散散步、听听音乐——情绪 语义都接住了,这是级联(先转文字)丢不掉也补不回的体验。24G 是硬瓶颈。Qwen2.5-Omni-7B(bf16)光权重就吃21.3GB,出语音时 Talker 再要 ~300MB 就直接 OOM,本机必须先腾出 ComfyUI 占的 560MB 才跑通。生产部署在 24G 单卡上几乎没有并发余量。级联三段每段都 4GB,轻得多、好扩容。下面是完整复现步骤,CSDN 老规矩——你照着抄就能跑起来。2. 环境准备(复用 vllm conda 环境,省一套 env)这是第一个省事的点:不用新建环境。本机已有的vllmconda 环境里,transformers已经是5.12.1,自带Qwen2_5OmniForConditionalGeneration,所以只补几个音频依赖即可:conda activate vllm# 只补音频处理 Omni 工具包,transformers 已经够新不动它pipinstallsoundfile librosa qwen-omni-utils accelerate环境关键版本:torch 2.11 cu130(推理正常;flash-attn 未装,走sdpaattention,够用)transformers 5.12.1(已含Qwen2_5OmniForConditionalGeneration/Qwen2_5OmniProcessor)提示:如果你的transformers版本低于 5.x、没有Qwen2_5OmniForConditionalGeneration这个类,升级一下即可:pip install -U transformers4.52(Omni 类在较新版本才合入)。级联那边用的是另一套audio-bench环境(FunASR CosyVoice2),第 5 节再说。3. 模型下载(hf-mirror huggingface-cli,约 21GB)国内直连 HuggingFace 基本下不动,走 hf-mirror.com 镜像。注意:下载时如果你的环境里挂了 socks 代理,反而会把镜像搞崩(后面踩坑 §6.2 详述),所以下载前先把代理变量 unset 掉,只认镜像:# 清掉可能干扰的代理,只走镜像unsethttp_proxy https_proxy all_proxyexportHF_ENDPOINThttps://hf-mirror.comexportHF_HOME/mnt/sda/hf-cache# 权重放机械盘,21GB 别塞系统盘# 下载 Qwen2.5-Omni-7B(bf16 全量,约 21GB)huggingface-cli download Qwen/Qwen2.5-Omni-7B --local-dir-use-symlinks False下完后权重落在HF_HOME指定的缓存目录。21GB,机械盘首次加载会慢(后面 §6 会看到冷启动 ~79s),有条件放 SSD 更舒服。4. Qwen2.5-Omni-7B 端到端:加载与推理代码端到端的核心 pipeline:一段音频进 → 模型直接听 → thinker 出文字 Talker/code2wav 出语音。完整可跑脚本如下(bench_qwen_omni.py):#!/usr/bin/env python# -*- coding: utf-8 -*-Qwen2.5-Omni-7B 端到端语音对话实测(RTX 4090 24G) speech-in - textspeech-out,量两个时延:①只出文字 ②出文字语音importos,time,json,gc os.environ.setdefault(HF_HOME,/mnt/sda/hf-cache)os.environ.setdefault(HF_ENDPOINT,https://hf-mirror.com)importtorchimportsoundfileassffromtransformersimportQwen2_5OmniForConditionalGeneration,Qwen2_5OmniProcessorfromqwen_omni_utilsimportprocess_mm_info MODELQwen/Qwen2.5-Omni-7BQWAVdata/user_question_zh.wav# 你的中文语音问题OUTresults/qwen_omniSPEAKERChelsie# 内置发音人:Chelsie(女) / Ethan(男)# ☆ 出语音必须带这段指定 system prompt,否则 Talker 不正常工作(踩坑 §6.3)SYS(You are Qwen, a virtual human developed by the Qwen Team, Alibaba Group, capable of perceiving auditory and visual inputs, as well as generating text and speech. 请用简体中文、温暖自然的口语回答。)os.makedirs(OUT,exist_okTrue)defvram():returntorch.cuda.max_memory_allocated()/1024/1024defbuild_inputs(processor,conv):textprocessor.apply_chat_template(conv,add_generation_promptTrue,tokenizeFalse)# process_mm_info 负责把对话里的 audio/image/video 路径解析成张量audios,images,videosprocess_mm_info(conv,use_audio_in_videoFalse)inputsprocessor(texttext,audioaudios,imagesimages,videosvideos,return_tensorspt,paddingTrue,use_audio_in_videoFalse)returninputs.to(model.device).to(model.dtype)print( loading Qwen2.5-Omni-7B )torch.cuda.reset_peak_memory_stats();t0time.time()modelQwen2_5OmniForConditionalGeneration.from_pretrained(MODEL,torch_dtypetorch.bfloat16,device_mapcuda,attn_implementationsdpa)processorQwen2_5OmniProcessor.from_pretrained(MODEL)print(f loaded in{time.time()-t0:.1f}s, vram{vram():.0f}MB)# 一段音频进,system user(audio)conv[{role:system,content:[{type:text,text:SYS}]},{role:user,content:[{type:audio,audio:QWAV}]},]inputsbuild_inputs(processor,conv)# ---- 模式 A:只出文字(thinker 路径,最快)----torch.cuda.reset_peak_memory_stats();torch.cuda.synchronize();ttime.time()withtorch.no_grad():text_idsmodel.generate(**inputs,return_audioFalse,thinker_max_new_tokens256,use_audio_in_videoFalse)torch.cuda.synchronize();text_only_stime.time()-t replyprocessor.batch_decode(text_ids,skip_special_tokensTrue)[0].split(assistant)[-1].strip()print(f[A] text-only{text_only_s:.1f}s | reply:{reply[:120]})# ---- 模式 B:出文字 语音(thinker talker code2wav)----deltext_ids;gc.collect();torch.cuda.empty_cache()torch.cuda.reset_peak_memory_stats();torch.cuda.synchronize();ttime.time()withtorch.no_grad():text_ids2,audiomodel.generate(**inputs,return_audioTrue,speakerSPEAKER,thinker_max_new_tokens256,talker_max_new_tokens2048,use_audio_in_videoFalse)torch.cuda.synchronize();full_stime.time()-t wavaudio.reshape(-1).detach().cpu().numpy();sr24000sf.write(os.path.join(OUT,reply.wav),wav,sampleratesr)durlen(wav)/srprint(f[B] textspeech{full_s:.1f}s | out audio{dur:.1f}s | rtf{full_s/dur:.2f})几个关键参数解释:return_audioTrue—— 真正触发出语音(Talker code2wav 声码器);False只走 thinker 出文字,快很多。speakerChelsie—— 内置发音人,女声 Chelsie / 男声 Ethan,固定音色。SYS这段 system prompt 是出语音的硬性前提,少了它 Talker 不正常工作(踩坑 §6.3)。process_mm_info(...)——qwen-omni-utils提供的多模态解析函数,负责把对话里的audio路径读成模型能吃的张量。输出采样率固定24000。实测结果(warm):[A] text-only 1.4s [B] textspeech 11.8s | out audio 16.1s | rtf 0.73 显存峰值 21.3–22.0GB 回复文字:你今天心情低落啊,那你可以看看喜剧电影、散散步、听听音乐……(带口语语气词)文字 1.4 秒就出,但完整语音要 11.8 秒——慢就慢在 Talker code2wav 一次性吐整段音频(RTF 0.73,即生成 16 秒音频要花约 11.8 秒)。5. 级联基线 ASR LLM TTS:三段代码级联思路就是隐界(以及大多数陪伴产品)现在的形态:SenseVoice 把语音转文字 → LLM 想回复 → CosyVoice2 把回复合成语音。三段串行,各自可换、各自可控。环境用另一套audio-bench(FunASR CosyVoice2)。核心脚本bench_cascade.py:5.1 第一段 ASR:SenseVoice(0.62s,转写零错)importtime,refromfunasrimportAutoModel QWAVdata/user_question_zh.wavdefrun_asr():mAutoModel(modeliic/SenseVoiceSmall,trust_remote_codeFalse,disable_updateTrue)ttime.time()rm.generate(inputQWAV,languageauto,use_itnTrue)dttime.time()-t# SenseVoice 输出带 |zh||EMO| 等标签,正则清掉txtre.sub(r\|[^|]*\|,,r[0][text]).strip()returndt,txt asr_s,transcriptrun_asr()print(fASR{asr_s:.2f}s ::{transcript})# - ASR 0.62s,转写零错SenseVoiceSmall 实测0.62 秒/句、转写零错,而且它本身就能输出情绪标签(|HAPPY|/|SAD|等)——这点很关键,它是少数能在 ASR 阶段就把情绪带出来的模型,后面给隐界的落点会用到。5.2 第二段 LLM:思考(本测用同机 7B thinker 的 1.4s 作参照)这一段是想出回复文字。本测为了和端到端可比,直接复用 Qwen2.5-Omni thinker 的纯文字时延 1.4s 作为本地 LLM参照值(通过--llm_s传入);真实产品里这一段是远程 API(隐界用的是远程 Claude,约 2–4s TTFT),另算。# LLM 阶段不在本脚本内实测,作为参数传入(秒)# 本地 7B 参照 1.4s;远程 Claude 真实 ≈ 2–4s TTFTllm_s1.4reply今天心情低落的话,可以先给自己泡一杯热茶,听几首喜欢的歌,再出门散散步晒晒太阳,慢慢就会好起来的。5.3 第三段 TTS:CosyVoice2 zero-shot(3.64s,RTF 0.22)importsys,os,time sys.path.insert(0,/mnt/sda/audio-bench/CosyVoice)sys.path.insert(0,/mnt/sda/audio-bench/CosyVoice/third_party/Matcha-TTS)importtorch,torchaudiofromcosyvoice.cli.cosyvoiceimportCosyVoice2 OUTresults/cascadePROMPT_WAV/mnt/sda/audio-bench/CosyVoice/asset/zero_shot_prompt.wav# ☆ 收路径非 tensorPROMPT_TXT希望你以后能够做的比我还好呦。defrun_tts(text):cvCosyVoice2(/mnt/sda/audio-bench/hf-cache/cosyvoice2,load_jitFalse,load_trtFalse,fp16False)outos.path.join(OUT,reply_tts.wav)ttime.time()# ☆ 短中文用 zero_shot,别用 cross_lingual(会幻觉),见踩坑 §6.4outslist(cv.inference_zero_shot(text,PROMPT_TXT,PROMPT_WAV,streamFalse))wavtorch.cat([o[tts_speech]foroinouts],dim1)torchaudio.save(out,wav,cv.sample_rate)returntime.time()-t,out,wav.shape[1]/cv.sample_rate tts_s,tts_wav,tts_durrun_tts(reply)print(fTTS{tts_s:.2f}s -{tts_wav}({tts_dur:.1f}s))# - TTS 3.64s,RTF 0.225.4 级联总时延totalasr_sllm_stts_sprint(f级联总时延 ≈ ASR{asr_s:.2f} LLM{llm_s:.2f} TTS{tts_s:.2f}{total:.2f}s)# - 级联总时延 ≈ ASR 0.62 LLM 1.40 TTS 3.64 5.66s三段串行累加 5.66 秒,显存每段都 4GB,4090 上还能并发、还能同时干别的。6. 完整踩坑记录(照着复现请逐条看)这部分是本文最值钱的地方。每一条都是真机踩出来的,不看大概率会卡住。6.1 Qwen2.5-Omni 出语音直接 OOM(21.3GB Talker 撑爆 24G)现象:只出文字没事,一旦return_audioTrue出语音就CUDA out of memory。原因:7B bf16 权重本身就占21.3GB,出语音时 Talker code2wav 还要再要 ~300MB,而此时显卡只剩 ~120MB free,直接爆。修复(两步叠加才跑通):# ① 减少显存碎片,让分配器能弹性扩展段exportPYTORCH_CUDA_ALLOC_CONFexpandable_segments:True# ② 腾出其他进程占的显存(本机 ComfyUI 常驻占 560MB),先停掉再跑 Omni# 跑完语音实测再把 ComfyUI 拉起来# 用 nvidia-smi 确认显存真的释放了:nvidia-smi --query-gpumemory.used,memory.free--formatcsv这条本身就是结论:24G 单卡跑 Qwen2.5-Omni 出语音没有部署余量。bf16 不量化的话,基本是一个模型独占整张卡,别说并发了,连后台挂个 ComfyUI 都嫌挤。要上生产,要么量化(INT4/AWQ,本轮未做),要么换更大显存卡 / 多卡。6.2 transformers 走 socks 代理崩 必须离线加载现象:权重明明全量下到本地缓存了,加载时还是报OSError: Unknown scheme for proxy URL。原因:transformers加载时仍会尝试联网做版本校验,撞上环境里的socks://代理——而httpx不认这个 scheme,直接崩。修复:unset 所有代理 强制离线加载,让它只读本地缓存:unsethttp_proxy https_proxy all_proxyexportHF_HUB_OFFLINE1exportTRANSFORMERS_OFFLINE1注意这和 §3 下载阶段是矛盾的:下载时要镜像(联网)、推理时要离线。下完模型再开离线开关即可。6.3 出语音必须带指定 system prompt现象:不带 system prompt,或 system prompt 内容随便写,Talker 出来的语音不正常 / 不出声。原因:Qwen2.5-Omni 的 Talker 路径依赖那段固定的官方 system 提示(You are Qwen ... capable of ... generating text and speech)才能正常工作。修复:把官方那段 system prompt 固化进对话(见 §4 代码里的SYS),后面可以再追加你自己的中文风格要求(“请用简体中文、温暖自然的口语回答”)。只出文字时这条不强制,出语音时必须有。6.4 造输入音频:CosyVoice2 zero-shot 收路径非 tensor 短中文别用 cross_lingual为了有一个干净、可控、音色独立的中文问题(避免模型自说自话),我用 CosyVoice2 zero-shot 先合成一段 10 秒中文问题。两个坑:inference_zero_shot的 prompt 参数收文件路径,不收加载好的 tensor。传 tensor 会报Invalid file: tensor(...)。直接传.wav路径即可(见 §5.3 的PROMPT_WAV)。短中文务必用inference_zero_shot,别用inference_cross_lingual——会幻觉(吐出莫名其妙的内容)。zero-shot 合成干净,用 SenseVoice 回读校验零错。6.5 用 vllm 环境跑 Omni,省一套环境如 §2 所说,本机vllmconda 环境的transformers已经 5.12.1 自带 Omni 类,只补soundfile / librosa / qwen-omni-utils / accelerate就能跑,不必新建独立环境。torch 2.11cu130、走 sdpa(没装 flash-attn)推理也正常。能省则省。7. 延迟拆解:谁慢在哪把两套方案按阶段拆开看,问题就很清楚了:阶段端到端 Omni级联听懂输入 想出文字1.4s(thinker 一步到位)ASR 0.62s → LLM 1.4s合成语音~10.4s(Talker code2wav,慢)TTS 3.64s(CosyVoice2,快)合计(非流式)11.8s5.66s端到端在理解上很优雅——一步听懂、连情绪一起接住;但在非流式吐整段语音上被自带声码器拖慢。级联靠 CosyVoice2 更快的 TTS(RTF 0.22 vs Omni 0.73)在总延迟上反超。要真正发挥端到端的优势,得上流式:边想边说、首字音频 1s 就开口,而不是等整段 16 秒音频全生成完。Qwen2.5-Omni 本身支持流式分块出音频,真实时首字延迟应远低于 11.8s——但本轮为了和级联的等完整音频口径可比,测的是批量整段,流式未测(诚实标注)。8. 结论与选型建议Qwen2.5-Omni-7B(端到端):✅ 直接听音频,连我心情低落的语义 情绪一起接住,回复贴心自然带口语语气词——这是级联丢不掉也补不回的体验。✅ 架构统一,一个模型 multimodal(文/图/音/视频)进、文字 语音出,省去拼三个模型的工程。✅ 文字回复极快(warm 1.4s),适合先出字幕、再补语音的体验。❌ 显存 21.3GB 近满 24G,出语音 OOM 边缘,单卡无并发余量。❌ 非流式整段语音慢(11.8s / RTF 0.73);低延迟得靠流式(本轮未测)。❌ 首次冷加载 ~79s(7B 音频塔从机械盘读),warm 15s。级联 ASR LLM TTS:✅ 模块化,ASR/LLM/TTS 各自可换可控;LLM 可接现有远程 provider,不必本地扛 21GB。✅ 轻,SenseVoice CosyVoice2 都 4GB,4090 上能并发、能同时干别的。✅ 快(本测 5.66s 总),ASR 0.62s 转写零错、TTS RTF 0.22。✅ 中间就是文字,对日志/审核/记忆天然友好。❌ 丢输入语气情绪(ASR 只给文字)。❌ 误差累积 串行延迟(ASR 错→LLM 跟着错;三段时延相加)。❌ 不天然支持全双工(边听边说/被打断),要额外做 VAD 打断逻辑。一句话选型:现在就要上生产、要可控要省显存→ 级联(SenseVoice LLM CosyVoice2),并给 ASR 选一个能带情绪标签的(SenseVoice 就行),把丢情绪这个最大短板补回来。要做能听出你情绪的实时语音通话、愿意上流式、有更大显存→ Qwen2.5-Omni 端到端,赌的是流式首响 情绪理解,不是赌总延迟,而且先得解决 24G 没余量的问题(量化 / 大卡 / 多卡 / 只在高级功能里开)。对陪伴类产品最实际的路线:短期保持LLM TTS两段式,先补一个能听情绪的 ASR(SenseVoice 0.62s、零错、4GB)把语音输入接进现有文本链路;端到端作为流式语音通话的中期差异化实验,而不是现在就拿非流式端到端替换整条链路——既慢(11.8s 整段)、又挤(21GB 近满 24G)、还更难审核/记忆(没有中间文字)。9. 诚实边界(未测项,别被本文误导)只实测了 Qwen2.5-Omni-7B 一个端到端模型;GLM-4-Voice-9B / Moshi(全双工)/ Step-Audio 等未真机测(大模型另需下载 复杂环境),它们的纸面生态对比另文分析。未测流式:本轮为可比性测的是等完整音频的批量口径;Qwen2.5-Omni 支持流式分块出音频,真实时首字音频延迟应远低于 11.8s,待补流式实测。未测全双工 / 打断:Moshi 这类 full-duplex 能力本轮没碰。级联 LLM 阶段为参照值:用同机 7B thinker 的 1.4s 代表本地 LLM;真实远程 API(如 Claude)约 2–4s TTFT,故真实级联总延迟约 7–9s,仍与端到端同量级,但工程可控性 显存成本明显更优。输入是合成语音非真人录音:用 CosyVoice2 造的干净问题,真人带口音/噪声/口语会更难,鲁棒性差异待真人样本验证。显存口径:21.3GB 为 bf16 不量化;INT4/AWQ 量化可压显存,但本轮未做。如果这篇对你复现有帮助,欢迎收藏点赞。所有数字都是同一块 4090 上脚本实测出来的,可复现。下一篇打算补Qwen2.5-Omni 流式出音频的首字延迟实测——那才是端到端真正该比的赛道。