资讯详情

从4秒到850ms:端侧语音助手ASR-LLM-TTS全链路实战

📅 2026/11/5 13:08:15 | 华诺云谱 👁 阅读
从4秒到850ms:端侧语音助手ASR-LLM-TTS全链路实战
端侧语音助手这个方向我从两年前就开始折腾中间推翻重来过三次架构。最早那版是把云端那套东西硬塞进一块开发板里结果唤醒后要等四秒才出声体验还不如一个十几块的语音模块。后来把全链路拆开一段一段抠延迟、抠内存才慢慢做到今天这个能用的状态——唤醒到首字出声 800ms 出头整句回答在一秒半内起播断网状态下也能完整跑完 ASR、LLM、TTS 三段。这篇东西讲的就是这条 ASR-LLM-TTS 全链路怎么从零搭起来。它解决的核心问题很具体在没有网络、没有云端算力、只有一块巴掌大的板子和几百兆内存的前提下让设备听清你说的话、想明白你要什么、再用自然的声音答出来。适合三类人看——正在做智能硬件原型的开发者、想把大模型能力下沉到设备端的算法同学以及单纯好奇这东西到底是怎么跑起来的的爱好者。我会把选型逻辑、参数怎么算、坑在哪全部摊开讲。1. 先把账算明白端侧语音助手的四道坎动手写第一行代码之前我建议你先花半天时间把预算算清楚。端侧项目和云端项目最大的区别是云端你只需要关心效果好不好端侧你必须同时关心效果、延迟、内存、功耗这四个互相打架的东西。我见过太多人上来就下载一个几 G 的模型往板子里拷拷到一半发现存储不够换个量化版本跑起来发现一句话要等八秒最后项目烂尾。1.1 一条语音指令要跑完的五段路从你对着设备说话到设备回你一句话中间其实有五个环节很多人只盯着中间三个模型忽略了前后两段结果体验卡在莫名其妙的地方。第一段是音频前端。麦克风采集、降噪、回声消除、增益控制这一段决定了后面模型拿到的信号干不干净。第二段是唤醒与端点检测判断你是不是在叫它以及你是不是说完了。第三段是ASR把音频转成文本。第四段是LLM理解意图并生成回复。第五段是TTS把文字变成声音播出来。关键认知是这五段的延迟是累加的但优化的空间不是均分的。我实测下来端点检测判断你说完没有那 300 到 600 毫秒的等待往往是整条链路里最容易被忽略、又最影响体感的部分。你说话停顿一下设备就以为你说完了开始识别识别出来半句话这个体验比慢两秒还糟糕。所以我在架构上做的第一件事不是选模型而是把这条链路切成可以并行和必须串行两块。唤醒之后的音频缓冲、ASR 的流式输出、LLM 的首 token 生成这三件事完全可以流水线重叠只有 TTS 必须等 LLM 吐出足够长的文本片段才能开始。这个思路后面会反复用到。1.2 端侧和云端的差距不在模型在预算很多人的第一反应是端侧就是用小模型这话对了一半。真正的差距在于云端你可以用几十路并发摊薄成本端侧你只有一份资源而且是硬约束。我给自己定的资源红线是这样的内存占用峰值不超过 1.2GB模型文件总体积不超过 800MB待机功耗控制在 1.5W 以内满载不超过 6W。这几个数字不是拍脑袋来的是根据市面上常见的边缘计算模组4GB 内存、16GB 存储、无风扇被动散热反推出来的。风扇一加产品形态就废了内存一超系统就开始杀进程跑一晚上必崩。注意一定要在真实板子上测内存峰值不要在开发机上测。很多推理引擎在 x86 上的内存行为和 ARM 上完全不同我吃过这个亏开发机上显示占 700MB上板直接 OOM。还有一个容易被忽略的点是存储的读写寿命。端侧设备如果是长期运行日志、缓存、临时音频文件会持续写 flash一年下来能把便宜片子写坏。我的做法是把所有临时音频放在内存盘里日志按大小滚动单个文件不超过 2MB总量不超过 20MB。1.3 三个必须钉死的指标我给这个项目定义了三个验收指标后面所有的优化都围着它们转。指标定义我的目标值实测值唤醒响应延迟说完唤醒词到设备给出提示音≤ 300ms220ms首字出声延迟用户说完最后一个字到扬声器出第一个音≤ 1200ms850ms语音识别字错率安静环境下 5 米内普通话短句≤ 8%6.4%注意第一个指标和第二指标的区别。唤醒是独立的小模型跑得很快首字出声则是整条链路的合力它才是用户真正能感知到的这玩意儿灵不灵。我见过一些项目演示视频里看起来很流畅实际是剪过的真实延迟两秒以上用户说一句话要等一下才回应那种感觉非常别扭。第三个指标容易被低估。字错率 8% 听起来还行但如果错的是关键词比如打开客厅灯识别成打开客厅等整个意图就废了。所以我在 ASR 后面加了一层热词纠错把设备支持的指令词表作为先验注入实际把关键指令的准确率拉到了 95% 以上。1.4 我的选型结论和取舍绕了一圈最后的架构是这样的唤醒用超轻量的关键词检测模型参数量在 10 万级别常驻内存不到 3MBASR 用流式端到端模型量化后 60MB 左右LLM 用 1.5B 级别参数量的中文模型4bit 量化后约 900MBTTS 用轻量端到端方案声学模型加声码器合计 40MB。这个组合的核心权衡是把参数预算的大头给 LLMASR 和 TTS 尽量精简。理由很实在——ASR 和 TTS 是转换任务模型再大边际收益也有限而 LLM 是理解加生成任务参数量直接决定它能不能听懂人话。我试过把 LLM 降到 0.5B模型体积是省了 600MB但回答质量断崖式下跌经常答非所问用户问三句它懵两句那还不如不做。另一个取舍是放弃全离线的高精度声纹识别和多轮复杂对话。不是做不到是资源不够分。与其每样都做得很勉强不如把单轮问答做到极致。这个决定在当时被质疑过但实际用下来用户对一句话就能办好一件事的满意度远高于能聊十轮但每轮都卡。2. 硬件与软件底座怎么搭把预算花在刀刃上选型阶段最忌讳的是先买板子再想做什么。我第二次重构时就是因为手里有一块现成的板子硬往上凑结果发现 NPU 的算子支持列表里没有我需要的那个卷积变体整个 ASR 模型跑在 CPU 上延迟翻了三倍。这一节讲的是怎么反过来从需求推硬件。2.1 算力平台的真实选型标准看芯片参数表的时候别看 TOPS 那个数字那个数字含水量极高。我判断一块板子能不能用只看四件事。第一NPU 或 GPU 的算子覆盖度。去翻它的算子支持文档重点看三类卷积的各种变体、注意力机制相关的矩阵运算、以及量化后的定点算子。我遇到的坑是某个平台支持 INT8 卷积但不支持 INT8 的 LayerNorm结果模型里一小部分算子回退到 CPU整体速度掉了 40%。第二内存带宽。这一点被严重低估。端侧推理的瓶颈很多时候不是算力不够是数据搬运跟不上。1.5B 参数的模型每生成一个 token 都要把权重过一遍如果你的模型是 4bit 量化约 900MB那每个 token 至少要读 900MB 的数据。内存带宽 10GB/s 的话理论下限就是 90ms 一个 token这就是为什么有些板子算力标得很高但生成速度上不去。第三CPU 单核性能。别小看这个音频前端处理、VAD、重采样、日志、进程调度全压在 CPU 上。我用过一款大小核设计的芯片小核性能太弱音频线程被调度到小核上结果出现周期性的爆音查了三天才定位到是调度问题。解决办法是给音频线程绑核并提高优先级。第四散热和供电的稳定性。这条最土但最要命。满载跑十分钟后如果芯片降频你的延迟就会从 800ms 漂移到 1500ms而且这种漂移是渐进的测试的时候不容易发现。我的做法是强制跑三十分钟压力测试同时记录每轮推理的耗时曲线看有没有抬头趋势。2.2 麦克风与音频前端的门道这一块是纯硬件活但软件同学必须懂否则后面调模型会怀疑人生。我用的是双麦克风方案间距 6 厘米。为什么不上四麦或六麦因为四麦以上的阵列需要更复杂的波束成形算法那套算法在端侧跑起来要吃掉一个核而且标定工艺复杂小批量试产根本做不好一致性。双麦做简单的差分降噪加固定波束效果在小房间场景里够用成本还低。具体的信号链是这样的两个麦克风采集 16kHz、16bit 单声道数据先做直流偏置消除再做双麦差分抑制远场噪声然后过一层轻量的谱减降噪最后做自动增益控制把音量拉到统一的动态范围。这里有个细节自动增益一定要放在降噪之后顺序反了会把噪声也一起放大后面怎么降都降不干净。提示回声消除AEC是必须做的哪怕你的设备不怎么放音乐。因为 TTS 播报的时候扬声器的声音会被麦克风收进去如果没有 AEC设备会把自己的声音当成用户说话出现自己跟自己对话的诡异现象。我第一次遇到的时候还以为是模型出 bug 了。AEC 的实现有两种路子用芯片自带的硬件 AEC或者软件做。硬件 AEC 省 CPU 但参数不好调软件 AEC 灵活但要吃掉 5% 到 10% 的一个核。我最后选了软件方案因为硬件 AEC 的收敛速度在我的结构上扬声器和麦克风距离只有 3 厘米表现很差残留回声明显。2.3 软件栈分层与进程隔离端侧项目最容易烂的地方不是算法是软件结构。我第一版把所有东西塞在一个进程里结果 ASR 模型加载失败直接把整个服务带崩了连唤醒都失效设备变成砖。第二版改成多进程稳定性质变。我的分层是这样的最底层是音频服务进程负责采集、播放、AEC、降噪这个进程优先级最高任何情况下不能被杀中间是推理服务进程按需加载 ASR、LLM、TTS 三个模型用完可以释放最上层是业务逻辑进程处理意图、管理状态机、对接外部接口。进程之间用共享内存传音频数据用本地 socket 传控制指令。为什么用共享内存因为音频数据量大16kHz 单声道每秒 32KB如果走 socket 拷贝CPU 开销不小而且会有额外延迟。共享内存加环形缓冲区生产者写、消费者读零拷贝。这里有个经验环形缓冲区的大小要算清楚。我设的是 2 秒容量也就是 64KB。太小了推理进程稍微卡一下音频就丢了太大了端到端延迟会被缓冲区撑起来。2 秒是个平衡点因为最长的单次推理间隔不会超过 1.5 秒。2.4 存储与内存预算的计算过程这部分我给一个具体的算法你可以直接套。模型体积方面参数量 N、量化位宽 B体积约等于 N × B / 8 字节再加上 10% 的元数据和算子表开销。比如 1.5B 参数、4bit 量化就是 1.5e9 × 0.5 750MB加 10% 约 825MB。ASR 模型 20M 参数、8bit 量化约 20MB 加开销 22MB。TTS 40M 参数、8bit约 44MB。合计不到 900MB16GB 存储完全够。运行时内存要复杂一些。除了模型权重常驻还要算三块KV Cache、音频缓冲、运行时开销。KV Cache 的算法是2 × 层数 × 注意力头数 × 每头维度 × 序列长度 × 批次 × 每元素字节数。以 1.5B 模型为例28 层、12 头、每头 128 维、上下文 1024 token、batch 1、FP16 存储2 × 28 × 12 × 128 × 1024 × 2 字节 ≈ 176MB。如果 KV Cache 也量化到 INT8能压到 88MB 左右代价是生成质量轻微下降实测在短对话场景几乎无感。音频缓冲按 2 秒共享内存加 1 秒播放缓冲算约 100KB可以忽略。运行时开销推理引擎、算子 workspace、Python 解释器如果用了的话我预留 200MB比较宽裕。加起来825 22 44 176 200 ≈ 1267MB。超了我定的 1.2GB 红线。所以我把 LLM 的上下文从 1024 砍到 768KV Cache 降到 132MB同时把 ASR 和 TTS 改成用时加载、用完释放释放后内存归还给系统峰值降到 1.1GB 左右。这个细节很关键——不是所有模型都要常驻只有唤醒模型必须常驻其他都可以按需加载代价是首次加载有 200 到 500 毫秒的延迟可以用唤醒后立即预加载 ASR这个动作把它藏起来。3. 唤醒与 ASR把第一公里走稳唤醒和识别是用户接触设备的第一公里这一段做不好后面 LLM 再强也没用。我的原则是唤醒宁可漏一点也不能误识别宁可慢一点也不能断。3.1 两级唤醒架构与误唤醒率控制一级唤醒用的是超轻量的关键词检测模型输入是 1 秒的音频窗口每 100 毫秒滑动推理一次输出是是否包含唤醒词的概率。模型很小10 万参数级别量化后 100KB 出头跑一次不到 3 毫秒可以常驻内存持续跑。二级唤醒是复核。一级给出高置信度后把前后各 0.5 秒的音频截出来送进一个稍大的模型约 50 万参数做二次确认同时做简单的说话人验证判断是不是注册用户的声音。两级都通过才真正唤醒。为什么要两级因为单级模型要么误唤醒高、要么漏唤醒高调不到理想状态。我实测过单级方案在电视开着的情况下一天误唤醒十几次用户会疯。两级之后误唤醒降到两三天一次而唤醒率保持在 95% 以上。误唤醒的另一个来源是背景音里的相似音节。我做过统计中文里小X小X这种结构的词在电视剧里出现频率很高。解决办法是在训练数据里塞进大量相似音节的负样本同时给关键词的音素序列做加权让模型更关注韵母部分。注意唤醒词不要选太短的两个字的唤醒词误唤醒率是四个字的三倍以上。四字词听起来啰嗦但工程上省事太多。3.2 VAD 与端点检测比识别模型更影响体验唤醒之后进入监听状态这时候 VAD语音活动检测就上场了。它的任务是判断用户是不是在说话和用户是不是说完了。我用的是混合方案能量 VAD 做粗筛只在能量超过阈值时才启动小的神经网络 VAD 做精判。这样平均 CPU 占用很低安静时几乎不耗资源。神经网络 VAD 用的是几百 KB 的小模型每 10 毫秒一帧输出语音概率。端点检测的判定逻辑比 VAD 本身更讲究。我的规则是连续 300 毫秒检测到静音且前面至少有 200 毫秒的语音才判定为说完了。这个 300 毫秒不是随便定的太短了用户换气就会被切断太长了响应就慢。我实测下来250 到 350 毫秒是甜区具体值可以根据目标用户的语速微调。还有个技巧是动态调整。如果用户前面已经说了很长一句超过 5 秒说明他在讲复杂的事情这时候把静音阈值放宽到 450 毫秒给他更多停顿空间如果只说了两个字阈值收紧到 250 毫秒快速响应。这个看人下菜碟的逻辑让用户投诉率降了一大截。另外一个坑是环境噪声突变。比如突然有人关门能量猛地上去VAD 会以为用户开始说话了然后录进去一段噪声送给 ASR模型瞎识别一通。我的处理是加一个语音起始确认能量突增后先观察 100 毫秒如果这段时间里频谱的平坦度很高噪声的典型特征就是频谱平坦就判定为噪声不触发。3.3 流式ASR的模型裁剪与量化ASR 我选的是流式端到端架构基于 Conformer 类的编码器加 CTC 解码。选它的理由是流式能力好每收到 320 毫秒的音频块就能吐出增量结果不用等整句话说完。模型参数从原始的 70M 裁剪到 20M裁剪策略是把编码器的层数从 12 层减到 8 层注意力头从 8 头减到 4 头隐藏维度从 512 降到 320解码器保留。裁剪之后字错率从 5.1% 涨到 6.8%这个损失可以接受。裁剪的时候一定要先裁剪再重训几轮直接裁剪不重训字错率会跌到 15% 以上完全没法用。量化方面编码器用 INT8 的对称量化解码器保持 FP16。为什么解码器不量化因为解码器的输出直接决定最终文字量化误差会被放大成错字。我实测过解码器也量化到 INT8字错率从 6.8% 涨到 9.2%不划算。编码器量化基本无损速度还能提升 1.8 倍。量化校准集的构造很讲究。不能随便拿几千条音频就完事要覆盖各种场景安静室内、有背景音乐、远场、不同性别年龄。我用了 500 条覆盖 6 种场景的音频做校准比用 2000 条单一场景的音频效果好得多。3.4 识别结果后处理与热词注入ASR 输出的是原始文本直接用会有很多小问题。我在后面加了三层后处理。第一层是标点恢复。流式 ASR 通常不带标点但 LLM 需要标点来理解断句。我用一个极小的标点模型2M 参数在识别结果后面补标点不影响速度。第二层是热词纠错。把设备支持的所有指令词、设备名、房间名做成一个词表用编辑距离加拼音相似度做匹配。比如客厅被识别成客停拼音完全一致直接纠正。这层的规则要保守编辑距离阈值设小一点宁可漏纠不能错纠。第三层是数字和单位规范化。一百二十三要转成123三点五要转成3.5因为后面的 LLM 和业务逻辑对纯数字处理更稳。这里说个踩过的坑热词表不能太大。我一开始把几百个设备名全塞进去结果纠错时出现了张冠李戴把打开卧室灯纠成了打开卧室灯带。后来按场景分组只在当前场景的组内做匹配问题解决。4. 端侧LLM能不能跑起来跑起来好不好用这一段是整个项目里最难的部分也是我推倒重来次数最多的部分。核心矛盾很直白模型小了不聪明模型大了跑不动。4.1 模型尺寸的确定从内存反推参数量先说结论在 4GB 内存的板子上做中文单轮问答1.5B 参数是当前比较舒服的点。推导过程是这样的。前面算过除去系统和音频服务能给模型的运行时内存大约是 1.2GB。模型权重加上 KV Cache 加上运行时开销必须塞进这 1.2GB。假设上下文 768 tokenKV Cache 约 130MB运行时开销 200MB那留给权重的就是 870MB。按 4bit 量化算参数量上限是 870MB / 0.5 字节 ≈ 1.7B。取整到 1.5B留点余量。这个推导的意义在于它告诉你参数量的天花板是被内存带宽和容量共同决定的跟你主观上想要多聪明没关系。如果你非要上 3B 模型要么加内存要么把上下文砍到 256 token那样多轮对话就没法做了要么接受 OOM 风险。我还试过混合方案常用指令走规则匹配复杂问题才调 LLM。这个方案在小场景下很香因为规则匹配几乎零延迟能覆盖 60% 的高频指令。但对通用助手的定位来说不够优雅用户问一句今天适合穿什么规则库就傻眼了。最后我保留了规则作为快速通道但主路径还是 LLM。4.2 量化方案对比与实测量化是端侧 LLM 的必修课但不是越激进越好。我做了四组对比实验都是在同一块板子上、同样的输入、测生成速度和回答质量。量化方案模型体积生成速度(词/秒)主观质量适用场景FP163.0GB2.1基准跑不动仅作对照INT81.5GB5.8几乎无损内存充足时首选4bit 分组量化(组大小128)900MB11.4轻微下降我的最终选择4bit 分组量化(组大小64)950MB9.6基本无损质量优先关键发现是分组大小的影响比位宽还大。组大小 128 的时候量化误差被限制在一个小范围内效果明显好于整层统一量化。组大小降到 64质量进一步回升接近 INT8但体积和速度都变差了性价比不高。另一个经验是不要量化 embedding 层和输出层。这两层参数量占比不大1.5B 模型里大约占 15%但对语义影响很大。我把它们保持 INT8其余层 4bit体积从 900MB 涨到 980MB质量提升明显尤其是回答的用词准确度。注意量化后的模型一定要做回归测试。我准备了一套 200 条的问答集每次换量化方案都跑一遍看有没有出现答非所问或者复读的情况。4bit 量化最容易出现的退化是重复生成模型卡在某个词上出不来这个在长回答里特别明显。4.3 推理引擎与KV Cache管理推理引擎的选择上我倾向支持 GGUF 或 ONNX 这两种格式的轻量方案因为转换链路成熟、算子覆盖全。选引擎时看三个点支不支持你板子的加速后端、支不支持流式输出、支不支持 KV Cache 复用。KV Cache 复用这一条特别重要。单轮对话里系统提示词是固定的每次对话都要重新计算一遍系统提示词的 KV那是白烧算力。我的做法是在服务启动时把系统提示词的 KV Cache 算好并固化下来每次新对话直接复用能省掉 100 到 200 毫秒的首 token 时间。1.5B 模型、300 token 的系统提示词这个优化非常值。内存管理上KV Cache 我预分配而不是动态增长。预分配 768 token 的空间用满了就触发滑动窗口把最早的一批对话裁掉。滑动窗口的边界要落在完整的句子处不然模型会看到半句话输出质量下降。缓存淘汰策略我试过两种FIFO 和保留系统提示 最近 N 轮。后者明显更好因为多轮对话里最早的几轮往往已经不重要了但系统提示词永远重要。4.4 提示词与上下文窗口的工程化处理端侧 LLM 的提示词要短、要硬、要给例子。我最初写的提示词是自然语言描述你是一个智能助手请根据用户的话……效果很一般模型经常自由发挥。后来改成结构化的三段式[角色] 你是设备语音助手只回答与设备控制和生活建议相关的问题。 [约束] 回答不超过40字不确定时说这个我不太清楚禁止编造设备不存在的能力。 [示例] 用户把客厅的灯调暗一点 回答好的客厅灯已调到30%亮度。 用户明天会下雨吗 回答抱歉我没有联网能力查不了天气。这个格式的好处是约束明确、输出长度可控。40 字的限制很重要因为 TTS 合成 40 字大约 6 秒再长用户就等不及了而且端侧生成长文本的速度撑不住。上下文窗口我最终定在 768 token分配是系统提示 250、历史对话 350、当前输入 100、预留输出 68。历史对话只保留最近两轮更早的直接丢。为什么只留两轮因为单轮问答占绝大多数多轮对话里超过两轮的信息价值很低还挤占输入空间。还有一个工程细节是输出的 JSON 化。业务逻辑需要知道模型想干什么是控制设备还是纯聊天所以我让模型输出固定格式比如先输出一个意图标签再输出正文。解析的时候要容错模型有时候会多输出几个字用正则兜底解析失败就降级成纯聊天。4.5 首token延迟优化用户感知最强的不是生成速度是首 token 延迟——从说完话到屏幕上出现第一个字或者扬声器出第一个音。这个指标我优化到了 380 毫秒做了四件事。第一ASR 流式输出。不等整句识别完识别到 80% 置信度的稳定片段就先送给 LLM 做前缀填充。这个做法有风险如果后面识别改了前缀就错了。我的处理是只对置信度极高的片段做预填充其余等最终结果。第二prompt 预热。前面说的系统提示 KV 固化省 150 毫秒。第三投机采样。用一个极小的草稿模型先猜几个 token主模型验证。端侧资源紧我用的草稿模型只有 30M 参数接受率大约 65%整体能快 1.4 倍。但这个方案对内存有额外要求内存吃紧的时候要能动态关掉。第四限制输出长度并强制提前结束。让模型学会说短句一句好的已打开比好的我已经为您打开了客厅的灯请问还有其他需要吗的体验好得多而且省了 2 秒的生成和合成时间。5. TTS让回答听得下去TTS 是最容易被敷衍的一环。很多人觉得能出声就行但机械音会瞬间拉低整个产品的档次。我在这块花的功夫不比 LLM 少。5.1 两段式与端到端的选择主流方案有两种两段式声学模型把文本转成声学特征声码器把特征转成波形和端到端直接文本到波形。端到端方案音质好、韵律自然但推理慢而且流式支持差。两段式灵活声学模型可以流式出特征声码器按帧合成能做到边生成边播放。我最终选的是两段式理由是流式能力对我的场景是刚需。用户等 1.2 秒才听到第一个音已经够难受了如果还要等整句合成完可能要 3 秒才开始播那体验就废了。声学模型的参数量我控制在 20M用了非自回归结构一次前向出一整段梅尔频谱速度快。声码器用基于 GAN 的轻量方案20M 参数左右每次合成 20 毫秒的音频块延迟很低。5.2 声码器与流式合成流式合成的关键在分块策略。文本按标点切句每句再按字切块每块 5 到 8 个字送入声学模型输出对应的梅尔频谱段然后声码器按 20 毫秒一帧合成波形推进播放缓冲区。这里有个细节叫重叠拼接。块与块之间如果不做处理会有明显的咔哒声。我的做法是每块前后各多合成 10 毫秒拼接时做交叉淡化重叠部分线性混合。这个 10 毫秒是试出来的5 毫秒不够消除边界效应20 毫秒又会吃掉太多时间。声码器的量化我用 INT8音质损失几乎听不出来。但要注意声码器对量化的敏感度高于声学模型量化校准的时候要用带各种音素的音频不能只用单调的测试音否则会出现某些韵母发虚的情况。5.3 拼接播放与打断处理播放这一块看着简单实际坑很多。首先是缓冲区管理。播放缓冲区要够大能扛住推理的抖动。我设的是 500 毫秒容量也就是 16KB。推理进程偶尔卡顿 200 毫秒播放不会断。缓冲区太小会卡顿太大则打断响应慢。然后是打断处理。用户在 TTS 播报过程中说停或者直接说新指令要能立刻停止播放并转入监听。这个逻辑要做得干脆收到打断信号立刻清空播放缓冲区、停止声码器推理、切换状态机到监听。我第一版没清缓冲用户说完停设备还啰嗦了两秒才停非常尴尬。还有个隐蔽的问题是AEC 的收敛延迟。TTS 刚起播的时候AEC 还没收敛残留回声会被 VAD 当成用户说话导致自打断。解决办法是在起播后 200 毫秒内屏蔽 VAD 的触发等 AEC 收敛稳定。这个 200 毫秒是实测出来的不同结构的设备可能不一样要自己测。5.4 音色与韵律的调优经验音色这块端侧做声音克隆不现实但做有限音色选择是可以的。我准备了三个音色一个偏中性用于日常回答一个偏低沉用于播报信息一个偏轻快用于确认指令。切换音色通过声学模型的说话人嵌入实现不用换模型只换一个 256 维的向量几乎零成本。韵律调优的经验是语速比音色更重要。端侧合成的语音如果语速太快听起来像机器人念经太慢又显得迟钝。我调到每分钟 260 字左右比正常人类说话略慢一点听感最舒服。另外句末的降调要明显一点中文里句末上扬容易听成疑问句会造成理解偏差。标点符号对韵律的影响很大。逗号处要加 150 毫秒停顿句号加 300 毫秒问号要在句末做升调处理。这些规则写在文本前端里比让模型自己学更可控。6. 全链路联调把延迟从4秒压到1.2秒单模块都调通了之后真正的挑战才开始。我第一版联调跑出来的首字出声延迟是 4.2 秒用户说完话要愣四秒那感觉就像设备死机了。6.1 流水线并行与分段触发优化的核心思路是把串行变成并行。原始链路是录完整句 → 识别完整句 → 生成完整回答 → 合成完整语音 → 播放。这是彻底串行的每一段都要等前一段完成。改造成流水线之后录音的同时做流式识别识别出稳定片段就送给 LLM 做预填充LLM 生成到第一个完整短句比如 8 个字就切出来送 TTSTTS 合成出前 200 毫秒音频就开始播放。三段重叠起来总时间从求和变成取最大值加首段延迟。效果是4.2 秒降到 1.4 秒后面又抠了一些细节降到 850 毫秒。这里的关键决策是切句时机。切太早模型还在组织语言切出来的半句可能语义不完整TTS 念出来很怪切太晚就失去了并行的意义。我的规则是遇到句号、问号、感叹号就切或者累计到 12 个字且遇到逗号也切。后者是为了应对模型输出长句的情况。6.2 各环节耗时实测与瓶颈定位我给每一段都埋了点跑了一千次真实交互统计出这样的分布单位毫秒取 P50 和 P95环节P50P95优化手段端点检测300450动态阈值ASR 识别180320INT8 量化 流式LLM 首 token380620KV 复用 投机采样TTS 首包160280流式合成 预加载音频通路60120共享内存 绑核看这张表能发现LLM 的首 token 是最大头。所以后续优化的重心都放在那里把系统提示词从 400 token 压到 250 token首 token 从 380 降到 300把输出长度限制写死在提示词里让模型尽快出第一个句号。还有一个隐藏成本是模型切换。ASR 用完要释放内存给 LLMLLM 生成完要释放给 TTS。这个切换如果每次都做会有 200 到 400 毫秒的加载开销。我的做法是唤醒时预加载 ASR识别完成后不释放 ASR它只有 22MB而是直接加载 LLMLLM 加载的同时 tts 模型也在后台预热。三个模型其实可以共存只要总内存不超就行。这一改动省掉了大部分切换开销。6.3 音频通路的踩坑音频通路是最不起眼但最容易出问题的地方。我踩过的坑列几个。采样率不一致。麦克风采集是 16kHz但某个环节的库默认重采样到 44.1kHz来回转换引入了延迟和失真。排查方法是在每个环节打印采样率一路查下去。最后的做法是全链路统一 16kHz只在最终播放前重采样一次到 48kHz。缓冲区欠载导致的爆音。播放时如果数据供不上会出现刺耳的爆音。原因是声码器合成速度偶尔跟不上播放速度。解决办法是加大预缓冲起播前先攒够 300 毫秒的数据同时给声码器线程提高优先级。时钟漂移。长时间播放后采集时钟和播放时钟有微小偏差累积下来会导致缓冲区慢慢被填满或掏空。我的处理是定期做一次微调检测缓冲区水位超过 80% 就丢一小段低于 20% 就补一段静音。丢和补都要控制在 5 毫秒以内听不出来。6.4 稳定性与内存泄漏端侧设备要连续跑几个月稳定性比性能还重要。内存泄漏是最常见的问题。我的做法是每小时打印一次内存快照连续观察 24 小时看峰值有没有缓慢抬升。有个隐蔽的泄漏是推理引擎的临时张量没释放每次推理漏几十 KB跑一天就漏几百 MB。定位方法是用内存分析工具抓堆栈看哪个分配点没配对释放。另一个问题是长时间运行后的性能衰减。跑 8 小时后延迟从 850 毫秒涨到 1300 毫秒重启就好。查下来是日志文件太大导致写盘变慢以及某些缓存没清理。解决办法是日志严格限大小缓存加 TTL每小时做一次轻量的资源回收。提示加一个看门狗进程监控推理服务的响应时间。连续三次超过阈值就重启推理进程。这个简单的机制救了我好几次尤其是在长时间压测的时候。7. 常见问题速查与避坑清单这一节是我这两年里攒下来的问题库按现象归类遇到问题可以先在这里查一遍。7.1 问题排查速查表现象可能原因排查方法解决方式唤醒后无反应状态机卡住 / ASR 加载失败看进程日志和状态标志加状态超时回退5 秒无响应自动复位识别总是漏掉开头几个字端点检测起始判定太晚打印 VAD 触发时间戳前向保留 300ms 音频环形缓冲回答一半就断输出长度限制或 KV 窗口溢出检查生成长度和窗口位置调整滑动窗口边界到句末TTS 有咔哒声分块拼接无重叠听波形找断点加 10ms 交叉淡化设备自问自答AEC 未收敛关闭 TTS 看是否复现起播后 200ms 屏蔽 VAD远场识别率骤降增益控制把噪声放大对比近场和远场频谱AGC 移到降噪之后长时间运行变卡内存碎片 / 日志膨胀内存快照对比定时回收 日志限容误唤醒频繁唤醒词太短 / 负样本不足统计误唤醒录音换四字词 补相似音节负样本用这张表的时候有个技巧先怀疑音频再怀疑模型。我遇到过的模型 bug里有七成其实是音频通路的问题——采样率错了、声道反了、增益爆了、缓冲区串了。所以排查顺序永远是先抓原始音频听一遍确认信号是干净的再看模型输入输出。7.2 几条用时间和返工换来的经验第一条流式比精度更重要。我早期花了两个月优化 ASR 的字错率从 8% 降到 6%用户毫无感知后来花两周做流式输出把首字延迟从 1.8 秒降到 0.9 秒用户反馈立刻变好。端侧场景里体感延迟的权重远高于绝对精度。第二条所有的等待都要给反馈。用户说完话如果 500 毫秒内没有任何动静会以为设备没听见。我的做法是收到语音结束就立刻播一个极短的叮声同时在指示灯上给出状态。这个 30 毫秒的音效能把主观等待感降低一半。第三条别在端侧追求通用。云端助手可以跟你聊哲学端侧助手最好只做它擅长的那几十件事。我第一版试图让它什么都能聊结果什么都聊不好还经常生成一堆无用长文拖慢 TTS。收敛到设备控制 生活提醒 简单问答三个域之后准确率和速度都上去了。第四条测试集要用真实的脏数据。我最初用录音棚里录的干净音频做测试指标漂亮得不行上线后投诉一片。后来改成用真实家庭环境录的音频——有电视声、有炒菜声、有小孩哭、有方言口音——重新调了一遍模型和阈值才真正能用。这份脏数据集的制作花了整整一周但它是整个项目里性价比最高的投入。第五条给用户一个慢一点但听清了的兜底。当 ASR 置信度低的时候不要硬猜直接回一句没听清你再说一遍好吗。这比自作聪明地执行一个错误指令要好得多尤其是控制类指令执行错了可能很麻烦。我个人在实际操作中的体会是端侧这条链路最难的地方不在任何一个单点技术而在资源约束下的全局平衡。你每给一个模块多一点内存就有另一个模块要挨饿你每为一个环节多争取 100 毫秒另一个环节就要让步。真正让项目跑起来的是那些看起来很土的决定砍掉不必要的能力、接受可控的精度损失、用规则兜底、把等待藏进流水线。如果让我再做一个类似的设备我会从第一天就把脏数据测试集和全链路埋点这两件事做在前面。前面省下的这几天后面要花几周去补。还有一个小的技巧送给你在设备上留一个隐藏的调试入口能实时打印每个环节的耗时和内存用手机连上看一眼就知道问题在哪。这个入口在我调优的后期比任何工具都管用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑