1
概述
語音識別技術(shù),是將語音信號轉(zhuǎn)換為文本內(nèi)容的技術(shù)。目前比較流行的語音識別技術(shù)主要有兩種。一種是基于Kaldi的傳統(tǒng)語音識別技術(shù),另一種是目前流行的基于深度學(xué)習(xí)模型的端到端語音識別技術(shù)。Kaldi是一種大而全的語音識別處理框架,集成了數(shù)據(jù)預(yù)處理、特征提取、聲學(xué)模型建模、語言模型建模、解碼等,識別效果上能夠滿足大多數(shù)的語音識別場景。但是Kaldi是自成一體的框架,沒有現(xiàn)在流行的pytorch、tensorflow框架的支持,需要開發(fā)者自行開發(fā)能應(yīng)用到生產(chǎn)環(huán)境中的服務(wù)。基于深度學(xué)習(xí)模型的端到端語音識別框架是指將語音信號直接輸入到深度學(xué)習(xí)模型中,通過端到端的方式進行語音識別,無需使用傳統(tǒng)的聲學(xué)模型和語言模型,常見的基于深度學(xué)習(xí)的端到端語音識別框架有很多,比如EspNet,WeNet等,這類語音識別框架有更通用的模型訓(xùn)練和部署框架支持,有著更好的識別性能和識別效果。
58自研語音識別引擎,最初是基于Kaldi框架進行開發(fā),在自研初期上線了架構(gòu)1.0版本,后續(xù)以降低機器資源、提升資源利用率、優(yōu)化性能為目標進行了升級重構(gòu),上線了架構(gòu)2.0版本。本文將介紹基于Kaldi的語音識別引擎的架構(gòu)設(shè)計,介紹從架構(gòu)1.0到2.0版本的優(yōu)化歷程。首先介紹業(yè)務(wù)背景,然后介紹Kaldi語音解碼的優(yōu)化,以及后端服務(wù)的各種優(yōu)化,最后是優(yōu)化取得的效果。
我們也在持續(xù)探索基于深度學(xué)習(xí)模型的端到端語音識別,嘗試了ESPNet,WeNet等流行的端到端框架。在2021年12月引入了端到端WeNet語音識別(由出門問問和西北工業(yè)大學(xué)于2021年1月開源),經(jīng)過持續(xù)的優(yōu)化,WeNet解碼服務(wù)在效果和性能上都超過了Kadli解碼,在2022年8月份,我們在線上全量替換了Kaldi語音解碼服務(wù)(WeNet端到端語音識別技術(shù)在58同城的大規(guī)模落地)。
2
背景
58同城是國內(nèi)領(lǐng)先的生活分類信息網(wǎng)站平臺,涉及業(yè)務(wù)有招聘、房產(chǎn)、車、本地生活服務(wù)(黃頁)等。語音是平臺上商家、用戶、銷售、客服之間溝通的主要媒介。
58平臺上的B端商家和C端用戶會使用電話、微聊進行語音溝通,同時58呼叫中心支撐著數(shù)千名銷售、客服人員工作,年通話時長數(shù)百萬小時。這些場景下產(chǎn)生了海量的語音數(shù)據(jù),這些語音數(shù)據(jù)經(jīng)過語音識別轉(zhuǎn)為文字之后,對于語音質(zhì)檢、信息治理和用戶畫像等任務(wù)有巨大的價值。此外,AI Lab團隊研發(fā)了可以提高人效的語音外呼機器人,典型應(yīng)用為銷售機器人“黃頁銷售智能外呼助手”和面試機器人“神奇面試間”。
3
架構(gòu)1.0
3.1 架構(gòu)1.0的背景
我們從2019年12月開始語音識別引擎的自研工作(3人半年打造語音識別引擎——58同城語音識別自研之路),業(yè)務(wù)方采購的是第三方的語音識別引擎,采購費用昂貴,采購合同即將在半年后到期。最終提前一個月上線切換到自研語音識別引擎。
語音識別系統(tǒng)通用處理流程是:客戶端發(fā)送音頻文件或者音頻流,服務(wù)端在接收后進行格式、采樣率等轉(zhuǎn)換,以及聲道分離、說話分離,轉(zhuǎn)換為多個人聲片段,再由解碼器對人聲片段進行解碼,輸出轉(zhuǎn)寫結(jié)果。一個語音識別系統(tǒng)的重點和關(guān)鍵點就是在盡量低資源(CPU/GPU)占用的情況下,能較大吞吐、較低延遲、較可靠的處理海量的音頻輸入,并保持較高的轉(zhuǎn)寫準確率。
架構(gòu)1.0系統(tǒng),基于語音識別系統(tǒng)的通用流程建立,服務(wù)主要包括網(wǎng)關(guān)接入服務(wù)、音頻解析服務(wù)、以及基于Kaldi的語音解碼內(nèi)核服務(wù)、靜音檢測和說話人服務(wù)、后處理服務(wù)等。各服務(wù)的主要功能:
網(wǎng)關(guān)接入服務(wù),負責(zé)業(yè)務(wù)接入分發(fā)、鑒權(quán)和檢測等功能。
音頻解析服務(wù),負責(zé)將音頻做轉(zhuǎn)換處理。語音解碼內(nèi)核服務(wù)負責(zé)將音頻解碼為文字。
靜音檢測和說話人服務(wù),負責(zé)將人聲片段分離出來,用于后續(xù)解碼。
后處理服務(wù),負責(zé)將轉(zhuǎn)寫后文字添加標點等處理任務(wù)。
語音解碼內(nèi)核服務(wù),負責(zé)將音頻片段轉(zhuǎn)寫為文本。
3.2 架構(gòu)1.0的不足
架構(gòu)1.0系統(tǒng)是在時間緊、任務(wù)重的情況下,滿足了快速上線的需要,但也存在以下不足:
占用機器資源太高
機器資源利用率不均衡
系統(tǒng)整體耗時高
可靠性和擴展性不足
重構(gòu)的目標主要是以下三個:
降低機器資源,節(jié)省成本
提高機器資源利用率
降低系統(tǒng)耗時、提升可靠性
4
架構(gòu)2.0
針對架構(gòu)1.0的不足,主要在以下兩個大方向上進行優(yōu)化:
1. 針對語音內(nèi)核解碼服務(wù)中,Kaldi并發(fā)解碼支持不足、性能差的問題,進行了服務(wù)性能優(yōu)化
2. 針對后端應(yīng)用服務(wù)中的不足,進行了服務(wù)拆分和一系列的性能優(yōu)化。
架構(gòu)2.0對1.0架構(gòu)中部分服務(wù)功能耦合的部分進行了拆分、對網(wǎng)關(guān)接入服務(wù)、音頻解析、解碼內(nèi)核服務(wù)做了重構(gòu)升級。
架構(gòu)2.0的服務(wù)包括網(wǎng)關(guān)接入服務(wù)、消息調(diào)度服務(wù)、數(shù)據(jù)上報服務(wù)、音頻解析服務(wù)、消息補償服務(wù)、靜音檢測服務(wù)、說話人分離服務(wù)、以及語音解碼內(nèi)核服務(wù)等。其中新增了消息調(diào)度服務(wù)、數(shù)據(jù)上報服務(wù)、消息補償服務(wù)。靜音檢測服務(wù)、說話人分離服務(wù),是從之前靜音檢測和說話人分離服務(wù)拆分而來。對這幾個服務(wù)的情況進行如下說明:
網(wǎng)關(guān)接入服務(wù),負責(zé)業(yè)務(wù)接入分發(fā)、鑒權(quán)和檢測等功能。將消息可靠性功能拆分為補償服務(wù),對服務(wù)的性能進行了優(yōu)化
消息調(diào)度服務(wù)、數(shù)據(jù)上報服務(wù),負責(zé)基于機器負載狀態(tài)進行消息分發(fā)。
消息補償服務(wù),將消息補償?shù)牟糠窒⒖煽啃员WC的功能,從之前的服務(wù)中拆分,負責(zé)對不同業(yè)務(wù)提供不同個性化補償策略。
靜音檢測服務(wù)、是從之前靜音檢測和說話人分離服務(wù)拆分而來,將之前同步的流程拆分,進行異步處理。
語音解碼內(nèi)核服務(wù),負責(zé)將音頻片段轉(zhuǎn)寫為文本。將語音解碼內(nèi)核服務(wù)優(yōu)化為可以進行并發(fā)解碼,處理并發(fā)請求。
4.1 Kaldi解碼優(yōu)化實踐
Kaldi主要功能由c++開發(fā)完成,共有26萬行代碼。解碼器是Kaldi中的核心組件,用于將聲學(xué)特征序列轉(zhuǎn)換為文本序列。Kaldi提供了一些解碼器的接口,以及shell離線腳本demo。但是未提供生產(chǎn)級的服務(wù)。Kaldi原生解碼的主要問題有:
4.1.1 無服務(wù)化支持
需要梳理調(diào)用關(guān)系,增加服務(wù)端、協(xié)議、客戶端調(diào)用支持。我們將模型、解碼器相關(guān)的接口抽象出來,封裝為gRPC服務(wù),服務(wù)接收音頻數(shù)據(jù)、解碼為文本轉(zhuǎn)寫結(jié)果。
4.1.2 無并發(fā)能力支持
原生的解碼器對并發(fā)請求的處理能力差。需要將服務(wù)的網(wǎng)絡(luò)請求模型和解碼器關(guān)聯(lián)起來,使服務(wù)獲取并發(fā)處理能力。我們的方案是服務(wù)啟動時初始化足夠的解碼器數(shù)目到同步隊列中,當(dāng)服務(wù)請求線程到來時,從隊列中取出解碼器。當(dāng)請求結(jié)束后,再放回隊列中。
那么服務(wù)啟動時初始化足夠數(shù)目的解碼器,這個數(shù)目是多少比較合適?服務(wù)初始的解碼器數(shù)目,就是可以支持的最大并行解碼的數(shù)目,這個數(shù)目越大,耗時越高、CPU/GPU的資源利用率越高。設(shè)置多少數(shù)目的解碼器,取決對實時率、尾包延遲的性能要求、也取決于服務(wù)器的硬件性能。比如在一臺CPU是Intel Xeon Silver 4210的物理機上,轉(zhuǎn)寫一個30s的音頻,要求在2s內(nèi)返回轉(zhuǎn)寫結(jié)果,系統(tǒng)最多能容忍32個解碼器并行處理,或者正在實時轉(zhuǎn)寫的數(shù)據(jù)流,尾包延遲要求在100ms內(nèi),系統(tǒng)最多能容忍16個解碼器并行處理。以定義好的性能數(shù)值為目標,從小到大的設(shè)置解碼器數(shù)目進行測試,滿足性能數(shù)值目標時,此時的數(shù)字就是服務(wù)需要初始化的解碼器數(shù)目。
4.1.3 CUDA GPU解碼支持不足
需要處理CUDA環(huán)境、模型、解碼器的關(guān)系,對于非Exclusive模式有OOM異常風(fēng)險。一個解碼服務(wù)進程只能有一個模型對象進行初始化,CUDA環(huán)境和模型對象是一一映射關(guān)系,單卡綁定一個CUDA環(huán)境、一個模型對象。而模型對象和解碼器之間是一對多的關(guān)系。
對于GPU解碼需要注意多個并發(fā)請求時轉(zhuǎn)寫結(jié)果偶爾會出現(xiàn)亂碼、錯字等情況,這是由于在Kaldi CUDA接口中的轉(zhuǎn)寫回調(diào)函數(shù)在一個進程環(huán)境下只有一個,這里需要在回調(diào)函數(shù)處理轉(zhuǎn)寫結(jié)果時加鎖、避免這些問題。
另外的一個問題是在GPU解碼獲取lattice回調(diào)結(jié)果時,有資源未清理的問題,會直接導(dǎo)致進程異常退出,這是由于在初始化時解碼進程綁定了唯一id和cuda channel的關(guān)系,但是在解碼結(jié)束時沒有解綁導(dǎo)致的,這個問題我們發(fā)現(xiàn)后提交了PR就行了修復(fù)。
最終,解碼服務(wù)的設(shè)計如下,基于Kaldi和CUDA環(huán)境,在離線環(huán)境中完成聲學(xué)模型、語言模型的訓(xùn)練、添加相關(guān)的配置。在解碼服務(wù)啟動時,加載服務(wù)配置,加載離線訓(xùn)練的模型,初始化解碼器同步隊列,當(dāng)有音頻請求到來時,根據(jù)協(xié)議判斷音頻請求的開始和結(jié)束狀態(tài),從隊列中加載解碼器,轉(zhuǎn)寫出結(jié)果后,返回給服務(wù)的調(diào)用方。
4.2 后端應(yīng)用服務(wù)的優(yōu)化
除了在語音解碼服務(wù)上的優(yōu)化,在后端服務(wù)上我們也進行了一系列的優(yōu)化,包含并發(fā)處理、多級緩存、I/O優(yōu)化、GC優(yōu)化、異步處理、分發(fā)效率優(yōu)化等方面,大大的優(yōu)化了系統(tǒng)的處理性能。具體的優(yōu)化如下:
4.2.1 并發(fā)處理和兩級緩存優(yōu)化
在音頻解析服務(wù)中,有很多音頻解析、轉(zhuǎn)換、分離、解碼、組合的處理模塊,處理鏈路長,而消息接收的效率、解析轉(zhuǎn)換的效率、解碼的效率是不同的。如果整個處理過程是單一的處理鏈條,由于模塊間處理效率上的不匹配,會出現(xiàn)下游模塊等待上游模塊的情況,那么整體的處理效率就會受到影響。為了盡可能降低模塊間的阻塞等待,可以將耦合度低的模塊拆分出來,增加緩存單獨并行處理,此時可以認為兩個緩存下的模塊是并行處理的鏈條,在處理效率上理論上大于等于單一鏈條的處理效率。在單一鏈條模塊有阻塞等待情況時,甚至要遠高于單一鏈條的處理效率。
將服務(wù)優(yōu)化為設(shè)立二級緩存來縮短處理鏈條,同時兩個緩存下的模塊獨立并行處理。二級緩存中的第一級在消息接收和解碼轉(zhuǎn)換之間,第二級在轉(zhuǎn)換和解碼之間,在兩個不同分級之間,使用多線程批量處理提高吞吐能力。優(yōu)化后相比優(yōu)化前,TP999耗時降低了91%。
4.2.2 I/O優(yōu)化
常說的I/O包含網(wǎng)絡(luò)I/O、磁盤I/O、設(shè)備I/O等,由于I/O時通常會涉及到數(shù)據(jù)交換、系統(tǒng)內(nèi)核態(tài)的切換,相應(yīng)的就會增加系統(tǒng)的開銷。我們本次I/O優(yōu)化利用緩存、批量處理等手段來降低I/O,提升系統(tǒng)的性能。在服務(wù)中,涉及到多次磁盤I/O和網(wǎng)絡(luò)I/O:
(1) 服務(wù)里包含對大量音頻文件的讀寫操作,會產(chǎn)生多次的磁盤I/O讀。通過使用緩存,以空間換時間的方式,將多次磁盤I/O讀降低為通過一次I/O緩存全部數(shù)據(jù),缺點是增加了內(nèi)存,由于服務(wù)是Java服務(wù),會相應(yīng)的增加GC回收頻率和停頓時長,那就還涉及到GC上的優(yōu)化。
(2)服務(wù)里包含請求和響應(yīng)相似的單獨請求,涉及到大量的網(wǎng)絡(luò)I/O。通過將這些相似請求進行合并,增加批量接口,進行批量請求,降低網(wǎng)絡(luò)I/O次數(shù)。
整體上,優(yōu)化后相比優(yōu)化前,TP999耗時降低了10倍。
4.2.3?GC優(yōu)化
系統(tǒng)中的上層處理服務(wù)是Java服務(wù),Java服務(wù)由于垃圾回收的關(guān)系,會在回收期間暫停應(yīng)用程序線程的執(zhí)行(Stop-The-World),直到垃圾回收操作完成,毫無疑問這會降低系統(tǒng)性能。之前服務(wù)使用G1垃圾回收器,也進行了參數(shù)調(diào)優(yōu),比如增加堆內(nèi)存、調(diào)整G1HeapRegionSize、MaxGCPauseMillis等,但是效果不是很理想。這是由于緩存音頻數(shù)據(jù)導(dǎo)致服務(wù)的內(nèi)存占用大,老年代對象較多,會頻繁的進行Mixed GC和Full GC。服務(wù)中G1的回收頻率5s左右,回收停頓的時間平均1.8s、最大停頓時間接近40s,拉低了服務(wù)整體處理性能。
ZGC在JDK11中首次發(fā)布,是一種低停頓時間、適合大堆內(nèi)存的垃圾回收器,能在幾毫秒到幾十毫秒內(nèi)完成垃圾回收。ZGC基于并發(fā)標記、并發(fā)轉(zhuǎn)移、以及讀屏障等技術(shù),而且回收時僅需要掃描GC Roots, 使得STW的延遲非常低。通過將JDK版本升級到JDK11,使用ZGC回收器替換G1回收器后,GC回收頻率控制在10~20s左右,回收停頓時間降低到10ms 以內(nèi)。
4.2.4 分發(fā)效率優(yōu)化
在之前的系統(tǒng)中,是基于不同業(yè)務(wù)場景的消息分發(fā),對不同的業(yè)務(wù)場景實現(xiàn)消息隔離、資源隔離,在流量不高的情況下,這種實現(xiàn)方式簡單、靈活。但在各業(yè)務(wù)流量增大,流量不均衡的情況下,會導(dǎo)致不同業(yè)務(wù)場景資源利用率不均衡、處理性能不均衡。
從基于業(yè)務(wù)場景的消息分發(fā),修改為基于資源負載數(shù)據(jù)的消息分發(fā)。針對消息不同處理階段,賦予不同的分發(fā)狀態(tài):接收狀態(tài)、分發(fā)狀態(tài)、處理狀態(tài)、完成狀態(tài)。根據(jù)這些狀態(tài)和機器自身的負載數(shù)據(jù),進行分發(fā),盡可能的將消息發(fā)送到低利用率的機器上,以達到機器負載水平整體均衡的狀態(tài)。優(yōu)化后的實現(xiàn)方式,實現(xiàn)難度上有所增加,系統(tǒng)上有一個中心化的調(diào)度服務(wù),根據(jù)收集到的數(shù)據(jù)分發(fā)調(diào)度。調(diào)度服務(wù)不但能實現(xiàn)基于負載的分發(fā),也可以定向分發(fā)、或者延遲分發(fā)。
定向分發(fā)是對于某些業(yè)務(wù)場景,有特殊處理情況,可以將流量定向到某臺機器、某個集群上去處理。延遲分發(fā),是對于某些業(yè)務(wù)場景流量不規(guī)律,短時間的流量尖刺會發(fā)送大量請求,延遲分發(fā)對流量進行平滑、延遲處理,緩解對下游服務(wù)的處理負擔(dān)。
4.2.5 異步化
如果在服務(wù)中存在一些耗時高的模塊,但是和上下鏈的模塊依賴度不高,和服務(wù)響應(yīng)的關(guān)聯(lián)度也不高,那么可以考慮將高耗時的模塊異步處理,而快速返回低耗時模塊的同步處理結(jié)果。
在網(wǎng)關(guān)接入服務(wù)中,就符合這些異步化處理的條件。存在一些高耗時模塊,比如時長計算、音頻下載分析等模塊,而服務(wù)返回結(jié)果和這些高耗時模塊也沒有關(guān)聯(lián)。其他功能模塊和這個高耗時模塊的依賴度也不高。如果服務(wù)采用同步處理,一方面服務(wù)的響應(yīng)耗時會很高,另一方面會出現(xiàn)線程阻塞、請求排隊的情況。采取的優(yōu)化方案是將高耗時模塊后置異步處理,而其它功能模塊則同步處理,快速返回結(jié)果。優(yōu)化上線后,將服務(wù)的TP999耗時從數(shù)百毫秒降低到了幾十毫秒。
4.3 數(shù)據(jù)效果
從架構(gòu)1.0升級到架構(gòu)2.0后,在資源利用率、系統(tǒng)性能、系統(tǒng)可靠性上都得到了提升。GPU卡的最高利用率從45%提升到75%左右;GPU卡資源占用節(jié)省了62%;線上平均耗時降低了88%,TP999耗時降低了98%。
5
總結(jié)
本文介紹了基于Kaldi的語音識別引擎的后端架構(gòu)設(shè)計,在前期人力少、排期緊、流量不大的情況下,快速了完成架構(gòu)1.0的上線,滿足了當(dāng)時的業(yè)務(wù)轉(zhuǎn)寫需求。隨著接入場景越來越多,流量越來越大,針對架構(gòu)1.0的不足進行了重構(gòu)和升級,重點針對基于Kaldi的內(nèi)核解碼服務(wù)的不足,進行了并發(fā)化改造優(yōu)化,針對其它后端應(yīng)用服務(wù)進行了拆分和性能優(yōu)化,提升了GPU的利用率、以更低的資源占用處理更多的音頻數(shù)據(jù),系統(tǒng)的整體性能也有了較大幅度的降低,系統(tǒng)可靠性得到了更好的保證。
【作者簡介】
王焱,58同城后端高級架構(gòu)師,58同城TEG-AI Lab語音架構(gòu)部負責(zé)人,主要負責(zé)語音識別、語音合成等語音技術(shù)的后端架構(gòu)設(shè)計和開發(fā)工作。
編輯:黃飛
?
評論
查看更多