微信扫码
添加专属顾问
我要投稿
AI数字人导购对话延迟从5.64秒优化到1.32秒的实战经验分享,大幅提升交互体验。 核心内容: 1. 分析AI数字人对话链路中的延迟瓶颈 2. 采用Qwen Omni一体化模型和流式传输方案 3. 客户端音频窗口缓冲机制实现嘴型同步
问题背景与现状
我们经过多组测试,得到平均的基线数据如下(人设提示词2万字+,每组10个会话):
对于用户而言,最关心的是问完问题之后,多久能够得到数字人的回复。因此,我们核心需要优化ASR结束到最终数字人回答的耗时。在本文中,我们完整的总结了将AI数字人语音对话端到端链路延迟从5.64秒优化到1.32秒的完整方案与实战经验。
最终对话效果如下:
解决方案调研
在优化之前,我们先回顾一下目前 AI 数字人对话链路,它通常采用 ASR → LLM → TTS & A2BS 的三段式串行结构:
这种架构的痛点在于:每个环节都必须等待上一个环节完全结束(或至少积累一定 buffer)才能开始,导致延迟逐级叠加。针对这个"5 s 以上首句延迟"的痛点,我们在多个方向和不同阶段做过可行性评估:
不同方案对比:
原始的导购对话流程,基线数据的链路是"ASR → LLM → TTS & A2BS"的三段式结构:ASR 识别结束后发起文本请求,获取请求之后,当达到一定的标点符号截断,才会调用 TTS和面部动画生成,回传结果之后,再播放音频和面部动画。采用Qwen Omni,可以Audio到Audio,减少中间环节。
而因为字幕原因,最终我们使用的是 Qwen Omni的Text→Audio/Text模式,让 LLM 直接流式返回音频和文字,端上再负责补建嘴型、同步播放功能。所以新的链路结构为"ASR->LLM->Text/Audio->A2BS"。
性能监控系统设计
在实施优化前,我们首先完善了性能监控体系,确保每一步改动都有数据支撑。这是本次优化的关键基础设施。
enum class EventType { ASR_START, // VAD 检测到语音开始 ASR_END, // ASR 识别完成 LLM_REQUEST_START, // LLM 请求发起 LLM_FIRST_TOKEN, // LLM 首个 token 返回 LLM_COMPLETE, // LLM 流式完成 TTS_REQUEST_START, // TTS 请求开始(每个 chunk) TTS_RESPONSE, // TTS 响应返回 AUDIO_PLAY_START, // 音频开始播放 ANIMATION_START, // 表情动画开始Omni_STREAM_START, // Omni 流式开始Omni_FIRST_AUDIO_CHUNK, // 首个音频 chunk 到达Omni_AUDIO_CHUNK, // 音频 chunk(每个)Omni_STREAM_COMPLETE, // 流式完成Omni_STREAM_ERROR, // 流式错误// A2BS 窗口化相关Omni_AUDIO_WINDOW_SEALED, // 音频窗口封存Omni_A2BS_FACIAL_START, // A2BS 请求开始Omni_A2BS_FACIAL_RESPONSE,// A2BS 响应返回Omni_A2BS_FACIAL_PLAY_START, // 首次嘴型播放Omni_AUDIO_WINDOW_PLAY, // 窗口音频播放Omni_FACIAL_START, // 面部数据生成开始Omni_FACIAL_COMPLETE, // 面部数据生成完成Omni_FACIAL_ERROR // 面部数据生成失败...};
// 单次请求的性能数据struct RequestPerformance {int64_t requestId;std::string inputType; // "voice" or "text"std::vector<std::pair<EventType, int64_t>> timeline;std::map<EventType, std::string> metadata;// 计算后的指标int64_t asr_duration_ms;int64_t llm_ttft_ms;int64_t llm_total_ms;int64_t end_to_end_ms;int64_t end_to_end_bs_ms;int64_t Omni_first_audio_ms;int64_t tts_avg_ms;int32_t tts_count;};// 全局统计数据struct PerformanceStatistics {int32_t total_requests;// 均值/最大/最小double avg_asr_ms, max_asr_ms, min_asr_ms;double avg_llm_ttft_ms, max_llm_ttft_ms, min_llm_ttft_ms;double avg_end_to_end_ms, max_end_to_end_ms, min_end_to_end_ms;// P95/P99,反映稳定性double p95_end_to_end_ms, p99_end_to_end_ms;double p95_llm_ttft_ms, p99_llm_ttft_ms;double p95_Omni_first_audio_ms, p99_Omni_first_audio_ms;double p95_end_to_end_bs_ms, p99_end_to_end_bs_ms;// Omni 统计double avg_Omni_first_audio_ms;double min_Omni_first_audio_ms;double max_Omni_first_audio_ms;double avg_end_to_end_bs_ms;double min_end_to_end_bs_ms;double max_end_to_end_bs_ms;};
使用
class PerformanceMonitor {private:std::mutex m_Mutex;std::map<int64_t, RequestPerformance> m_Requests;public:void RecordEvent(int64_t requestId, EventType type,const std::string& metadata = "") {std::lock_guard<std::mutex> lock(m_Mutex);// ... 记录逻辑}};
在关键事件到达时自动触发指标计算:
AUDIO_PLAY_START: 计算 End-to-EndOmni_A2BS_FACIAL_PLAY_START: 计算 End-to-End-BS实时日志格式:
========== Performance Report for Request 1 ==========Input Type: VoiceTimeline:[269806374 ms] ASR_START[269809542 ms] ASR_END - {"text":"给我介绍一下伯希和"}[269810133 ms] LLM_FIRST_TOKEN - 伯[269810374 ms] Omni_FIRST_AUDIO_CHUNK[269810385 ms] AUDIO_PLAY_STARTPerformance Metrics:ASR Duration: 3168 msLLM TTFT: 590 msEnd-to-End: 843 msEnd-to-End-BS: 1856 msOmni First Audio: 241 msExportAllToJSON)格式:{ "session": "20251113_150748", "total_requests": 31, "requests": [ { "requestId": 1, "durations": { "asr_duration_ms": 3168, "llm_ttft_ms": 590, "end_to_end_ms": 843, "end_to_end_bs_ms": 1856, "Omni_first_audio_ms": 241 }, "timeline": [...] } ], "statistics": { "total_requests": 31, "asr": {"avg_ms": 2449.7, "max_ms": 3868, "min_ms": 1284}, "llm": { "avg_ttft_ms": 968.5, "p95_ttft_ms": 1683.0, "p99_ttft_ms": 1920.0 }, "Omni_first_audio": { "avg_ms": 1250.0, "p95_ms": 1450.0, "p99_ms": 1500.0 }, "end_to_end_bs": { "avg_ms": 1856.6, "p95_ms": 2100.0, "p99_ms": 2300.0 } }}统计报表 ( LogStatisticsReport )格式:
==================== Performance Statistics Report ====================Total Requests: 31ASR Performance: avg=2449.7ms, min=1284ms, max=3868msLLM TTFT: avg=968.5ms, min=640ms, max=1920ms, P95=1683ms, P99=1920msOmni First Audio: avg=1250.0ms, min=890ms, max=2314ms, P95=1450ms, P99=1500msEnd-to-End Latency: avg=1264.5ms, min=890ms, max=2314ms, P95=1683ms, P99=2314msEnd-to-End-BS Latency: avg=1856.6ms, min=1500ms, max=2300ms, P95=2100ms, P99=2300msTTS Performance: avg=N/A (Omni mode)======================================================================
创建 PerformanceConfig.h 统一控制编译开关:
#ifndef PERFORMANCE_MONITORING_ENABLED#define PERFORMANCE_MONITORING_ENABLED 1#endif
需要打点的文件统一引入。
// 1. 记录简单事件PerformanceMonitor::GetInstance()->RecordEvent( requestId, EventType::ASR_START);// 2. 记录带元数据的事件nlohmann::json metadata;metadata["text"] = "用户问题";PerformanceMonitor::GetInstance()->RecordEvent( requestId, EventType::ASR_END, metadata.dump());// 3. 记录窗口化 A2BSmetadata["windowIndex"] = windowIdx;metadata["audioDuration"] = duration;PerformanceMonitor::GetInstance()->RecordEvent( requestId, EventType::Omni_A2BS_FACIAL_START, metadata.dump());// 4. 触发报告(在 LLM_COMPLETE 时)PerformanceMonitor::GetInstance()->LogSummary(requestId);// 5. 导出全局统计PerformanceMonitor::GetInstance()->LogStatisticsReport();PerformanceMonitor::GetInstance()->ExportAllToJSON("/path/to/output");技术方案详细设计
基于完善的监控体系,我们开始实施 Omni 链路改造。
JSON 中新增 character_bs(嘴型角色)以及 Omni_audio.enabled (后续可能配置更多与Omni相关内容) 相关设置:
{ "model_name": "qwen3-omni-flash", "voice": "Cherry", "character_bs": "yuhe", "Omni_audio": { "enabled": true }}对于Omni的语音返回,我们需要调用服务端A2BS接口,生成对应的表情。Omni返回的每个chunkSzie 在 20480字节,如果每次返回都进行请求,那么表情生成的会非常不自然,会有明显的不连续。在原始的链路中,是根据文本断句到标点符号进行请求,对应语音的返回,通常用户说话1~2秒即为完整的句子。
所以我们设计为按采样率累积 1~2 秒音频窗口,达到目标窗口时长再触发A2BS。此外,算法也会针对性进行平滑。
其关键结构如下:
// `OmniA2BSHelper`struct OmniA2BSConfig { float targetDurationSeconds = 1.0f; // 目标窗口时长 float minDurationSeconds = 0.01f; // 最小窗口时长 int32_t sampleRate = 24000;};struct AudioWindow { int32_t windowIndex = 0; // 每个窗口记录 `windowIndex`,便于打点与回放排序 std::vector<std::string> base64Chunks; // Base64 音频块 std::vector<int16_t> pcmSamples; // PCM 音频样本 std::string mergedBase64; // 合并后的 Base64 int32_t sampleRate = 24000; bool base64Finalized = false; void Append(const std::string &base64Chunk, const int16_t *samples, size_t sampleCount); float GetDurationSeconds() const; void FinalizeBase64(); std::string ConsumeMergedBase64();};struct AudioWindowQueue { explicit AudioWindowQueue(const OmniA2BSConfig &cfg); std::shared_ptr<AudioWindow> SealReadyWindow(); std::shared_ptr<AudioWindow> SealRemainingWindow(); float CurrentDurationSeconds() const; bool HasCurrentWindow() const;private: void EnsureWindowLocked(); OmniA2BSConfig config; mutable std::mutex mutex; std::shared_ptr<AudioWindow> currentWindow; int32_t nextWindowIndex = 0;};基于伯希和导购业务对话场景,对于一组数据会询问以后问题,每次三组数据:
采用不同的接口或者策略迭代演进多次,得到以下数据对比:
其中,第5组数据的考虑是基于,因为用户说话需要字幕,ASR是必须的,所以想到Omni也可以从输入音频,到切换为输入文本,同样保留输出为音频和文本。测试Omni输入文本,相较于输入音频处理速度更快。
总体来看,之前性能的瓶颈,一个是大模型返回的速度更慢,另外一个是每次TTS占用的时间更长,虽然A2BS累积也会导致一定的延迟,但不是主要的延迟来源。
对于整体解决方案,完整的性能监控打点机制是优化的基础,具体实施时,一方面,我们用更合适的模型,端测针对新模型增加输入输出链路,另一方面,端测针对A2BS进行窗口累积和语音表情同步的处理,保障链路的正确性。
关键指标数据:
P95/P99 稳定性分析:
结论:不仅平均延迟大幅降低,P95/P99 也控制在合理范围,用户体验稳定性显著提升。
基线链路:
Omni 链路:
在之后的优化中,也设想了一些方向:
通过本次优化,我们不仅将端到端延迟从 5.64s 降至 1.32s(76.6% 提升),更重要的是建立了一套完整的性能监控与优化体系。不仅适用于当前的 Omni 链路,也为后续的自部署、端上推理、双模型加速等探索奠定了基础。性能监控体系的建立,让我们能够快速定位问题、量化优化效果,真正做到"用数据说话"。
值得一提的是,使用 Qwen Omni 模型也有局限,只支持官方音色输出,而不像我们通过TTS可以自由生成任意音色。对于通用场景,我们需要使用特定人的声音,那么就需要有音色复刻的能力,同时有流式输出的能力。25年11月中旬,百炼发布的的Qwen-TTS-Realtime模型,可以实时复刻声音并且流式输出,可以同时满足上诉两个需求。在后续自定义音色的场景下,我们可以采用任意LLM模型加TTS-Realtime的模式,模拟Omni的流程,LLM流式输出的内容通过WebSocket与TTS-Realtime交互,流式的生成自定义音色的TTS。时间上会增加些许的网络通信延迟。所以在不同的场景业务下,我们可以采用不同的解决方案。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-02-13
“思考”更深,生成更准|Seedream 5.0 Lite 发布
2026-02-12
Seedance 2.0上线火山方舟体验中心,API即将开放
2026-02-12
Seedance 2.0 正式发布
2026-02-11
Qwen-Image-2.0发布:中文生图彻底不拧巴了
2026-02-10
对话离哲:企业AI告别「对话玩具」,多模态记忆是分水岭
2026-02-10
Qwen-Image-2.0: 字字清晰,张张细腻
2026-02-03
多模态文档智能解析最新开源进展:GLM-OCR方法概述
2026-02-02
月之暗面Kimi正式发布官方编程工具:Kimi Code
2025-12-15
2026-01-10
2025-12-06
2025-12-17
2025-12-07
2025-12-11
2026-01-05
2025-12-14
2026-01-27
2025-12-17
2025-12-31
2025-08-04
2025-05-26
2025-05-13
2025-04-08
2025-04-05
2025-03-30
2025-03-26