Together AI 如何构建全球最快语音转文本技术栈
How Together AI built the world’s fastest speech-to-text stack
Together 优化了 ASR(自动语音识别)服务的端到端延迟,针对 NVIDIA Parakeet-TDT 0.6B v3 和 OpenAI Whisper Large v3 模型。关键方法包括:使用 TensorRT 多配置文件引擎适配真实音频形状,将编码器内存从 6 GB 降至 5 GB;通过条件 CUDA graph 将解码器分支移至 GPU,速度提升 2-3 倍;采用共享内存和持久化 Unix 域套接字实现零拷贝数据路径;使用 epoll 事件驱动 I/O 替代每连接单线程,降低流式场景尾延迟;在启动预分配后调用 gc.freeze() 消除 Python GC 导致的 p95 峰值。优化后,Parakeet-TDT 0.6B v3 可在 10 秒内转录约 20 小时语音。

Artificial Analysis 报告的速度因子(每秒转录的输入音频秒数)——越高越好
模态很重要
一个 100 万 token 的文本 prompt 可以装下整部《哈利·波特》系列,体积却只有大约 5 MB。这个规模听起来很大,但输入本身很紧凑。文本也几乎可以直接用于推理:分词、批量处理,然后送入模型即可。
音频则改变了问题的形态。同一部《哈利·波特》的有声书版本体积为 5 到 10 GB,大约是文本版本的三到四个数量级。在数据到达 GPU 之前,服务器必须先解码容器、重采样、过滤噪声、运行 VAD(语音活动检测)、分割语音并计算音频特征。
模型侧的情况也完全不同。如今的 LLM 拥有数千亿甚至数万亿参数,因此服务负载自然集中在 GPU 内部:量化、KV cache、attention 内核、批处理和并行化。语音转文本模型则小得多,通常只有数亿到数十亿参数,因此周围的数据路径就显得更为关键。
这使得 ASR 服务成为一个贯穿 GPU 执行、CPU 预处理、内存移动、传输、连接调度和运行时行为的全路径系统问题。同一套技术栈还必须服务于两种不同的场景:离线转录(吞吐量最重要)和流式转录(延迟和抖动占主导)。
Together 的 ASR 技术栈服务于 Artificial Analysis 排名中延迟最低的两个语音转文本模型:NVIDIA 的 Parakeet-TDT 0.6B v3 和 OpenAI 的 Whisper Large v3。其中较快的 NVIDIA Parakeet-TDT 0.6B v3 可以在不到 10 秒内转录大约 20 小时的语音,这大致相当于《哈利·波特》电影系列的总时长。
本文的其余部分将分解实现这一结果背后的生产级改动:针对真实音频形状的 TensorRT 配置文件、GPU 侧的解码器控制流、低拷贝的 CPU 路径、事件驱动的流式 I/O,以及运行时 GC 控制。
为真实音频形状编译编码器
Parakeet 采用编码器-解码器架构,其大约 95% 的权重位于编码器中。编码器接收可变长度的语音片段,并为解码器生成声学帧,因此它成为优化的首要目标。
音频输入的长度范围很广,从 200 毫秒的流式数据包到 30 秒的不间断语音。针对一种输入形状调整过的内核计划在另一种形状下可能会慢得多,因此引擎需要在编译时知道它将看到的形状分布。
在使用 TensorRT 之前,我们已经在使用一个经过优化的 PyTorch 路径,结合了 torch.compile 和 CUDA graphs,并针对相同的形状配置文件进行了调优。这为我们提供了一个强大的基线:在不离开 PyTorch 技术栈的情况下实现配置文件感知的执行。
TensorRT 为我们提供了更快的生产级编码器路径。它会提前构建一个优化的执行计划,在可能的情况下融合内核,调整内存布局,并针对我们预期服务的形状范围对内核变体进行基准测试。
关键的细节在于配置文件调优。一个仅针对最大输入形状调优的单一引擎会强制较短的音频片段走填充路径,这对于流式数据块和短语音片段来说尤其昂贵。一个多配置文件的 TensorRT 引擎允许我们在内存中保留一份编码器权重,同时为每个请求选择正确的优化配置文件。
内存节省是适度的,大约从 6 GB 降到 5 GB。更大的收益在于避免了不良的形状匹配,并将调优后的配置文件从优化的 PyTorch 迁移到 TensorRT。在小输入场景下,配置文件感知的 TensorRT 比通过大型填充配置文件发送这些请求要快几倍。
随着编码器得到优化,解码器循环成为了下一个瓶颈。
从解码器循环中移除 CPU
Parakeet 的解码器迭代处理编码器的声学帧,并为那些不推进转录的帧输出一个 token 或一个 BLANK。代码本质上是:
state = init()
for frame in encoder_output:
token = predict(frame, state)
if token != BLANK:
emit(token)
state = update(state, token)
在性能分析时,我们发现 predict 和 update 都很快。每次迭代的 GPU 工作以微秒计。
开销大的地方在于分支:
if token != BLANK:
该分支要求 CPU 从 GPU 内存中读回 token 以决定走哪条路径。这个主机同步操作阻止了解码循环被捕获为单个 CUDA graph,并强制每次迭代都通过 Python 进行一次往返。GPU 做几微秒的工作,等待 CPU,启动下一个内核,然后每个请求重复数千次这种模式。
条件 CUDA graph 节点将该分支移到了 GPU 上。一个小的设备端内核评估条件,并告诉 CUDA 运行时是否进入 token 发射和状态更新子图。分支在 GPU 内部解析,无需离开 GPU,因此整个解码循环(计数器、条件、发射和状态更新)可以被捕获并作为一个 CUDA graph 启动。
CPU 离开了解码器的内部循环,结果是解码器速度提升了 2 到 3 倍。

停止复制音频字节
在编码器和解码器运行良好之后,剩余的延迟来自模型周围的 CPU 路径。这正是我们审计过的大多数 ASR 代码消耗延迟预算的地方:冗余拷贝、热路径上不必要的进程跳转,以及本可以从更高并行度中受益的单线程函数。
第一个杠杆是合并不必要的进程边界。
音频预处理,无论是文件解码、重采样、语音活动检测(VAD)、特征提取还是数据块处理,大多是 I/O 或原生 C/C++ 工作,会释放 Python 全局解释器锁(GIL)。典型的微服务架构将预处理分散到三到四个独立的进程中,为工作负载并不需要的隔离性付出了代价。将大部分工作合并到更少的进程中,可以消除内核拷贝和序列化/反序列化传递,这些操作在处理大文件时可能耗费数百毫秒。
当确实需要进程间通信时,像 ZeroMQ 这样的常见选项也会带来可观的额外开销。在我们的工作负载中,一个基于持久化 Unix 域套接字、携带原始音频字节的简单自定义协议在高并发下表现最佳,因为它保持了最小的帧结构,并避免了重复的连接建立。
对于大文件,套接字仍然会引入两次拷贝:发送方用户空间到内核缓冲区,然后内核缓冲区到接收方用户空间。为了避免这条路径,我们使用了共享内存。通过共享内存,两个进程映射相同的物理区域,因此生产者写入的数据对消费者可见,无需经过内核往返。这为我们提供了一条零拷贝的数据路径。
复杂性的代价是真实存在的,因此只有当数据量证明其价值时,才值得使用共享内存。
对流式传输使用事件驱动 I/O
流式 ASR 增加了另一个问题:连接生命周期。
我们的第一个流式实现使用每个连接一个线程。当数百个流同时发送数据块时,数百个线程同时唤醒,GIL 争用激增,尾延迟飙升。
我们转而使用一个线程阻塞在 epoll 上。
epoll 允许一个线程注册数千个连接,并通过一次系统调用询问内核:“当这些连接中的任何一个有数据时,唤醒我。”当消息到达时,内核返回完整的就绪集合,该线程处理活跃的套接字,然后重新进入休眠状态。
相同的工作负载,调度器压力小得多。对于流式 ASR,这种可预测性至关重要,因为延迟的部分转录可能会让语音系统感觉迟钝,即使平均延迟看起来不错。

冻结启动状态以消除 GC 尾延迟
我们差点错过了这一点。
在流式工作负载的负载下,p50 和 p90 延迟看起来健康,但 p95 会定期飙升约 200 毫秒。日志显示队列深度小,GPU 时间正常,但通常运行时间低于 5 毫秒的 CPU 函数突然花费了超过 100 毫秒。
后台有东西在窃取请求循环的时间。
性能分析指向了 Python 的垃圾回收器(GC)。Python 使用引用计数进行大部分内存管理,并配有一个循环检测收集器来捕获引用循环。该收集器按代运行。最老的一代包含长生命周期对象,完整的收集可能会遍历一个庞大的对象图。
我们在启动时预分配了一大池缓冲区、模型状态和查找表,专门为了避免稳态下的分配延迟。这些长生命周期对象落入了最老的一代,因此完整的 GC 遍历会检查数十万个引用。这就是那 200 毫秒的停顿。
修复方法是在启动预分配之后添加一行代码:
gc.freeze()
gc.freeze() 告诉 Python 将预分配的状态排除在未来的 GC 扫描之外,这样正常的请求作用域对象仍然会被收集,而巨大的初始状态则被保留不动。
p95 峰值消失了,p50 也得到改善,因为系统能够维持更平滑的流量模式。
教训是:性能分析不能止步于模型。GPU 时间、队列深度和模型执行看起来都正常;延迟峰值存在于 Python 运行时中。
语音延迟是一个端到端的系统问题
语音代理通常以级联方式运行:ASR 生成转录文本,LLM 生成响应,TTS 生成音频。ASR 是该路径的第一阶段,因此其延迟和抖动设定了用户可见响应时间的最早界限。
上述优化针对该路径的不同部分。TensorRT 多配置文件引擎针对真实音频形状调整编码器执行。条件 CUDA graph 从解码器循环中移除了 CPU 往返。持久化 Unix 域套接字、共享内存和 epoll 减少了 CPU 路径开销。gc.freeze() 消除了运行时级别的 p95 故障模式。
同样的约束适用于技术栈的其余部分:每个阶段都必须控制中位延迟和尾延迟,涵盖模型执行、预处理、传输、调度和运行时行为。
NVIDIA Parakeet-TDT 0.6B v3 和 OpenAI Whisper Large v3 已在 Together 上可用。如果您在生产中扩展语音 AI,欢迎联系我们。
Parakeet v3 是 v2 的继任者,v2 是一个仅支持英语的模型,曾在 Hugging Face Open ASR Leaderboard 的单语言吞吐量上领跑。v3 在此基础上进行了显著扩展,将语言支持从英语扩展到 25 种欧洲语言,增加了自动语言检测功能(无需语言 prompt),并在 170 万小时的音频数据上进行了训练——包括 NVIDIA 的 Granary 多语言语料库。