HySpark头像
信创环境下的流式语音系统优化封面图

信创环境下的流式语音系统优化

一、问题从会议现场开始

在政府机关和党政办公场景中,会议记录有一个比较明显的特点:不能等会议结束后再慢慢处理。

例如,党组会议、专题研究会、部门协调会等场景,往往需要在会议进行过程中同步查看发言内容。对于离线会议系统来说,实时转写不仅是一个功能展示问题,也直接影响后续的会议纪要生成和内容检索。

项目适配信创平台时,原有 Paraformer 流式识别方案出现了一个问题:音频能够正常采集,模型也能够返回文字,但文字输出明显滞后。

最开始尝试调整音频缓存和推理线程,效果并不稳定。进一步分析后发现,不能将问题简单归结为 CPU 性能不足,还需要重新检查模型推理方式、分块策略和流式状态管理。

因此,本次技术调整的重点放在 Zipformer 流式识别架构上。

二、先把实时延迟拆开

很多语音项目在做性能优化时,容易只记录模型推理时间。

但从用户实际体验来看,会议现场说出一句话,到屏幕上显示对应文字,中间涉及多个环节。

可以将延迟拆成:

其中:

环节具体内容
T1音频采集
T2缓存等待
T3特征提取
T4模型推理
T5解码和前端展示

举个例子,假设音频采集和传输没有明显问题,但系统需要积累较长时间的音频才开始执行推理,那么即使模型本身运行速度较快,最终显示文字仍然会慢。

因此,优化之前需要分别记录各个环节的耗时,而不是直接通过增加线程数解决所有问题。

三、Zipformer 流式识别的实现思路

Zipformer 可以结合 Transducer 架构实现流式语音识别。

其核心并不是简单地把完整音频切成多个小文件,而是在持续输入音频的过程中,利用模型的状态信息完成连续解码。

1. 模型结构

典型 Zipformer Transducer 包含三个主要部分:

Encoder:

负责提取音频特征,将输入的声学信息转换为高层表示。

Prediction Network:

根据之前已经输出的文本 Token,预测后续可能出现的内容。

Joiner:

将 Encoder 输出和 Prediction Network 输出进行融合,计算下一个 Token 的概率。

整体处理关系如下:

与需要等待较长上下文的处理方式相比,流式 Transducer 可以利用历史状态持续处理新输入的音频。

但需要注意,实际延迟还受到模型右侧上下文、特征帧移、分块配置和解码策略影响。

2. 为什么需要缓存?

流式模型每次收到新的音频数据,并不意味着都要从头处理之前的所有内容。

合理的状态缓存能够减少重复计算。

例如:

第一批音频:
Audio_1 → Encoder → Cache_1 → Result_1

第二批音频:
Audio_2 + Cache_1 → Encoder → Cache_2 → Result_2

第三批音频:
Audio_3 + Cache_2 → Encoder → Cache_3 → Result_3

这里的 Cache 不只是普通的音频缓存,还可能包含模型内部的历史状态。

具体缓存结构由模型实现决定,不能在不同模型之间直接复用。

如果每次都重新计算完整历史音频,不仅会增加计算量,还会让处理时间随着会议持续时间增长。

四、实现方案:分块采集与持续解码

本次优化建议采用以下结构:

这里将音频采集和模型推理解耦,主要是为了避免模型推理耗时反过来阻塞音频采集。

1. 音频分块处理

建议使用固定长度的 PCM 音频块作为采集单位。

例如:

import numpy as np
import queue

audio_queue = queue.Queue(maxsize=100)

SAMPLE_RATE = 16000
CHUNK_MS = 100
CHUNK_SIZE = SAMPLE_RATE * CHUNK_MS // 1000

def audio_callback(pcm_data):
    """
    pcm_data: 采集线程获取的PCM数据
    """
    samples = np.frombuffer(
        pcm_data,
        dtype=np.int16
    ).astype(np.float32) / 32768.0

    if not audio_queue.full():
        audio_queue.put(samples)
    else:
        # 实际项目中应记录丢帧并触发告警
        print("audio queue is full")

这里使用 100ms 作为采集块示例,并不是说模型必须每 100ms 完成一次解码。

采集块大小、模型内部帧移和解码触发条件应分别配置。

另外,实际生产环境中不建议简单丢弃满队列后的音频数据。应结合缓冲区水位、处理能力和会议录音完整性制定背压策略。

2. 持续输入模型

以 sherpa-onnx 的在线识别接口为例,可以将音频持续送入同一个 Stream:

import sherpa_onnx

recognizer = sherpa_onnx.OnlineRecognizer.from_transducer(
    encoder="encoder.onnx",
    decoder="decoder.onnx",
    joiner="joiner.onnx",
    tokens="tokens.txt",
    num_threads=2,
    sample_rate=16000,
    feature_dim=80,
    decoding_method="greedy_search",
    provider="cpu"
)

stream = recognizer.create_stream()

def process_audio_chunk(chunk):
    recognizer.accept_waveform(
        stream,
        16000,
        chunk
    )

    while recognizer.is_ready(stream):
        recognizer.decode_stream(stream)

    return recognizer.get_result(stream)

上述为接口逻辑示意,具体方法签名应根据安装版本调整。

生产环境还需要补充音频结束通知、结果读取、异常恢复和流对象生命周期管理。

尤其要注意,不能每收到一个音频块就重新创建 Stream,否则模型历史状态无法连续保留。

3. 临时结果和最终结果分离

在会议现场,用户看到的识别文本可能会不断变化。

例如:

第一次:

今天我们召开一个专题会议

第二次:

今天我们召开一个专题会议,主要研究

第三次:

今天我们召开一个专题会议,主要研究下一阶段的工作安排。

如果每次都把返回结果直接追加到数据库,就容易出现文字重复。

因此建议维护两类文本:

class MeetingText:
    def __init__(self):
        self.partial_text = ""
        self.final_text = []

    def update_partial(self, text):
        self.partial_text = text

    def append_final(self, text):
        if text:
            self.final_text.append(text)
            self.partial_text = ""

实际系统还需要根据解码器返回的句子边界、稳定性标记等信息更新状态,不能仅依靠字符串拼接判断句子是否完成。

五、针对信创平台的性能优化

模型完成接入后,还需要结合实际硬件进行性能验证。

1. 控制线程数量

线程并不是越多越好。

在信创 CPU 平台上,如果推理线程数量过多,可能出现线程竞争、上下文切换增加和内存带宽争用。

建议分别测试:

  • 单线程;

  • 双线程;

  • 四线程;

  • 多路并发情况下的线程分配。

同时记录 CPU 使用率和 P95 推理延迟。

2. 检查推理后端

如果使用 ONNX 模型,需要确认目标平台的 ONNX Runtime 版本及算子支持情况。

重点检查:

  • 模型是否能够完整加载;

  • 是否存在不支持的算子;

  • 是否出现 CPU 与其他执行后端之间的意外切换;

  • 是否发生重复内存拷贝;

  • 模型加载后内存占用是否稳定。

如果目标平台具备专用推理加速能力,还需要使用相应厂商提供的运行库和适配方案进行验证。

3. 优化音频缓存

缓存长度直接影响用户体验。

缓存过长会增加等待时间,缓存过短可能造成处理开销增加。

因此建议通过测试确定合适的采集块、解码块及队列容量,而不是简单地将所有缓存设置成最小值。

4. 建立实时率监控

可以使用以下方法记录处理性能:

import time

def benchmark(process_func, audio_chunk):
    start = time.perf_counter()

    process_func(audio_chunk)

    cost = time.perf_counter() - start

    print("processing cost:", round(cost, 4), "seconds")

正式测试时,应分别统计:

  • 首字延迟;

  • 分块处理耗时;

  • 端到端延迟;

  • P50、P95和P99延迟;

  • 实时率RTF;

  • 长时间运行内存变化。

不能只测一次,也不能只使用提前准备好的短录音。

六、政府机关离线会议场景的工程要求

模型切换完成后,系统还需要满足政务内网的运行要求。

第一,模型文件和推理依赖应提前准备,部署时不依赖外部网络。

第二,会议录音、转写文本和会议纪要应按照业务权限进行管理。

第三,系统应支持异常恢复,不能因为单次模型解码失败导致整场会议录音丢失。

第四,语音识别结果应保留必要的时间信息,方便后续核对原始录音。

第五,后续接入声纹识别、会议纪要及知识库时,应尽量通过统一的文本数据接口衔接,避免各模块重复处理原始音频。

对于类似熙瑾会悟的离线会议产品,这种分层设计也有助于后续适配不同国产化操作系统和硬件平台。

七、总结

这次流式识别优化,核心并不是简单地把 Paraformer 换成 Zipformer,而是重新考虑模型架构、音频缓存、在线解码和运行环境之间的关系。

Zipformer 流式 Transducer 为持续音频输入提供了另一种技术实现路线,但最终能否降低信创平台上的实时延迟,仍需要通过目标设备上的实际测试确认。

对于政府机关和党政办公场景,真正可用的智能语音系统,不仅要识别准确,还需要做到持续运行、及时反馈、数据可控和结果可追溯。

只有将模型优化与工程部署结合起来,才能让离线语音识别真正融入日常办公流程。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/HySpark/article/details/170144022

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--