ASR 服务的并发与批处理:请求该怎么排队
ASR 服务的容量瓶颈通常不在”每秒能接多少请求”,而在”同一时刻能挂住多少路连接”。流式识别是长连接,一路对话从接通到挂断始终占着资源,照搬 Web 服务按每秒请求数做的容量规划,会把需求估得离谱。
这是很多团队上线语音能力时踩的第一个坑:压测数据漂亮,真实流量一来就崩。问题不在模型,在容量算错了单位。
一路对话和一个网页请求,占资源的方式完全不同
一个普通的 Web 请求,进来、算完、把资源还回去,周转往往在毫秒量级。正因为占用时间远小于统计窗口,“每秒请求数”这个指标才成立——它隐含的假设是资源会飞快地被归还再借出。
流式 ASR 恰好相反。连接一旦建立,音频就持续流入,模型的中间状态、音频缓冲、解码上下文都得一直留着,直到用户挂断。这段时间可能是几分钟。
于是同一个数字会给出完全相反的判断。一小时内只来了几百通对话,摊到每秒看几乎等于没有负载;但如果这几百通高度重叠在同一段时间里,那一刻真正挂着的路数才是压力来源。容量要按并发路数算,不按每秒请求数算。
压测也得跟着改。不是”每秒打多少请求”,而是”同时挂多少路、每路持续多久、按什么分布开始”。用短请求模型去压流式服务,测到的只是连接建立和释放,数字是假的。
实时倍率(RTF)同样不能直接换算成并发上限,它只是理论上限,详见RTF 是什么:语音识别快不快该怎么量。
容量规划该问自己的问题
真要估机器数量,先把下面这些问题答出来。答不出的部分就是你的风险敞口。
- 峰值时刻同时在线的路数是多少? 不是把日总量平摊出来的那个数。
- 单路平均持续多久?分布长什么样、长尾有多长? 少数超长通话会显著抬高常驻占用。
- 到达是均匀的还是尖峰的? 客服早高峰、整点开课,这类业务天生是尖峰。
- 实时流式和离线批量各占多少?它们是否共用同一批机器?
- 每增加一路连接,额外占多少内存或显存? 模型权重是共享的,真正随路数线性增长的是每路的增量部分。
- 允许的排队时长上限是多少?超过之后是继续等,还是直接拒?
- 打满时优先拒绝谁? 新会话,还是低优先级的批量任务?
- 一个节点挂掉,它上面的连接怎么办? 流式的中间状态能不能在别处重建?
- 客户端断网但连接没关闭,多久回收?
这份清单的作用不是给答案,而是逼你把”要几台机器”拆成一组可测量的量。
两类请求的资源特征对照表
实时流式和离线批量转写,虽然跑的是同一个模型,工程性质却几乎是两个物种。
| 维度 | 实时流式 | 离线批量 |
|---|---|---|
| 连接形态 | 长连接,全程占用 | 提交任务,算完即释放 |
| 占用曲线 | 平缓而漫长 | 陡峭而集中 |
| 延迟敏感度 | 极高,首字延迟直接决定体验 | 低,总时长可接受即可 |
| 攒批空间 | 很小,攒久了就卡顿 | 很大,可以放开攒 |
| 能不能排队 | 几乎不能,排队等于通话卡住 | 可以排很久 |
| 失败重试 | 难,音频流过去就没了 | 容易,源文件还在 |
| 扩容响应 | 慢,新机器只能接新会话,救不了已建立的连接 | 快,队列自然被消化 |
| 适合的调度 | 预留容量、优先通行 | 填谷,用空闲资源 |
最后一行是重点:批量任务天生适合填谷,实时通道则必须留预留量。把两者按同一套规则调度,等于让两种负载互相伤害。
实时优先:否则一批长音频会把实时通道堵死
这是相当常见的一类生产事故。运营上传一批会议长录音做转写,而批量任务和线上实时通道跑在同一批机器上。批量任务一旦占住资源就不撒手,实时请求排在它们后面,用户那边的表现是迟迟不出字,严重时干脆接不进来。
长音频本身就意味着长时间占用,即便做了切分也只是把一段拆成多段,总量不变,相关取舍见长音频为什么必须切开。
规则很简单:实时流式必须优先于离线批量。落地做法从松到严是分队列加权、批量限流、物理隔离两套资源池。最稳妥的是隔离,批量只在实时低谷时借用资源。
还有一点常被忽略:批量任务应当可中断、可续跑。实时高峰来临时能把它挤走,等空闲了再排回去,而不是硬挺着占完为止。
攒批:拿延迟换吞吐,两边的答案不一样
攒批(batching)是提高吞吐的标准手段——把多个请求凑成一批一起算,单位算力吐出的结果更多。代价同样明确:每个请求都要多等凑批的那段时间,单请求延迟因此增加。
所以这事没有统一答案,取决于谁在等:
- 实时流式:攒批窗口必须极短,甚至不攒。首字延迟是体验的命门,用户对”说完半天不出字”的容忍度极低。
- 离线批量:可以放开攒。没人盯着屏幕,吞吐才是唯一目标。
这条分界线也直接影响硬件选型——能不能攒批,是 GPU 与 CPU 各自划算的关键条件之一,见ASR 该用 GPU 还是 CPU。
背压:明确拒绝,好过让所有人一起慢
并发打满时只有两条路:都接进来一起变慢,或者拒绝一部分、保住已接入的会话。
第一种明显更糟,而且糟得不对称。
语音是实时交互,慢到一定程度就等于不可用——半句话卡住几秒,对话已经断了。这时候”大家都能连上”没有意义,因为没有一个人拿到可用的结果。更麻烦的是它不可恢复:请求继续涌入,队列越积越长,等待进一步拉长,形成正反馈,直到整个服务雪崩。
第二种至少保住了已经接入的会话,它们能正常完成对话。被拒绝的那部分虽然体验差,但结果是明确的,客户端可以提示、可以退避重试、可以降级到别的路线。
关键在于拒绝必须是显式的。返回明确的”容量已满,请稍后重试”,和不响应让客户端干等到超时,是两件完全不同的事。后者最糟:用户不知道发生了什么,客户端往往还会立刻重试,反而加重雪崩。
三个容易漏掉的工程细节
连接数与内存、显存的关系。 模型权重是共享的,但每一路连接都有自己的增量占用——音频缓冲、编码器的历史状态、解码时保留的候选。并发上限往往先撞在这块增量上,而不是算力上,所以它必须实测。
超时与僵尸连接清理。 客户端断网、进程被杀、手机切后台,连接没有正常关闭,服务端却以为对话还在进行,资源一直被占着。这类泄漏非常隐蔽:负载看起来没涨,可用容量却一天天变少,重启一次就恢复。必须配空闲超时和心跳检测,长时间收不到音频就主动断开并回收状态。
排队时长该不该告诉用户。 该告诉。人对不确定的等待,容忍度远低于对确定的等待。给出排位和预计时长,比一个转圈图标强得多,也能让用户自主选择继续等还是稍后再来。前提是这个估计不能明显失真,否则不如不给。
常见问题
问:能不能用 RTF 直接算出一台机器能挂多少路?
不能,那只是理论上限。真实并发还受每路的内存增量、批处理策略、请求到达分布制约,而且流式场景里机器多数时间在等音频到来。RTF 适合横向比较不同方案的计算效率,不适合当容量公式用。
问:实时和批量混在一起跑,是不是更省钱?
理论上能提高利用率,但前提是批量必须可被抢占、有独立的优先级和限流,否则省下的钱会以线上事故的形式还回去。没有精细调度能力时,物理隔离更划算。
问:流式 ASR 该怎么压测?
按并发路数设计,而不是按每秒请求数。用真实时长分布的音频回放,模拟连接持续占用的过程,同时看首字延迟和尾包延迟的分布,而不只看平均值。另外一定要压到打满,验证背压是否按预期触发——这才是压测最该测出来的东西。
相关阅读
本文讲的是语音服务部署中的通用工程原则,不绑定某一个具体实现。 文中不给出任何并发数、吞吐、延迟或资源占用的具体数值,这类数字高度依赖模型、硬件与业务形态,请以你自己的实测为准。 我们没有对任何方案做过性能实测,因此不做性能比较。