最近在做一个基于ws通信协议的实时语音识别项目,在测试并发性能的时候显存、内存、带宽这些资源都没有达到瓶颈,但是实时语音识别的响应时间已经很大了,下面讲一下我的处理方式。
问题:
测试并发性能的时候显存、内存、带宽这些资源都没有达到瓶颈,但是并发的路数却提不上去。
问题原因:
问题出在我的服务是跑在单个线程上的,虽然用了异步的处理方式,但由于python的GIL的限制,并不能做到真正的并行处理,导致并发路数提不上去。
解决方法:
一般有两种方式,多线程和多进程,打破GIL的限制,这里我使用的是多进程的方式。
import json
import torch
import websockets
import asyncio
import ssl
import time
import socket
from multiprocessing import Process, Manager
from infer_server_self_utils import *
from transcribe_fn import transcribe as zhuanlu
import warnings
import pdb
import resource
resource.setrlimit(resource.RLIMIT_NOFILE, (65535, 65535)) # 设置进程可以打开的最大文件数限制的
warnings.filterwarnings("ignore", category=FutureWarning)
async def hello(websocket, shared_pipeline):
try:
# 将共享的pipeline转移到cuda上
shared_pipeline.device = torch.device("cuda") # 进程之间共享模型必须是加载的cpu的模型,进程加载之后再放在cuda上
shared_pipeline.model.to(torch.device("cuda")
while True:
audio_bytes = await websocket.recv() # 接收数据
if audio_bytes == 'null': # 判断是否结束
res_results = {"result": '', "status": 'true'}
await websocket.send(str(res_results))
else:
text = shared_pipeline(audio_bytes) # 转录接收的数据
res_results = {"result": text['text'], "status": 'true'}
await websocket.send(str(res_results))
except websockets.exceptions.ConnectionClosedError as e:
print(f"Connection closed unexpectedly: {e}")
finally:
# 连接关闭时,将模型移回CPU并释放显存
shared_pipeline.model.to(torch.device("cpu"))
torch.cuda.empty_cache()
def start_websocket_worker(shared_pipeline):
# 创建一个套接字并设置SO_REUSEPORT选项,实现多进程
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(('0.0.0.0', 30582))
sock.listen(5)
asyncio.set_event_loop(asyncio.new_event_loop())
loop = asyncio.get_event_loop()
async def accept_connection(websocket, path):
await hello(websocket, shared_pipeline)
start_server = websockets.serve(accept_connection, sock=sock)
loop.run_until_complete(start_server)
loop.run_forever()
if __name__ == '__main__':
# 在多进程中使用torch
torch.multiprocessing.set_start_method('spawn')
# 构建共享模型管理器
manager = Manager()
pipeline, generate_kwargs = zhuanlu() # zhuanlu()是我用来读取语音识别pipeline的一个函数
shared_pipeline = manager.Namespace()
shared_pipeline.pipeline= pipeline
num_workers = 4 # 工作进程数,可以根据需求调整
# 启动工作进程
processes = []
for _ in range(num_workers):
p = Process(target=start_websocket_worker, args=(shared_pipeline.pipeline,))
p.start()
processes.append(p)
# 等待所有进程结束
for p in processes:
p.join()
可以看到这里我是每个进程加载一次模型,然后每个进程里每路请求都是共享一个模型,这里可以节省非常多的显存。
首次尝试多进程的时候没有共享模型,导致每增加一路的并发就要加载一次模型,这样显存很快就爆了,增加了共享模型之后,每增加一个进程加载一次模型,显存的使用大大降低,这样最终限制并发路数的瓶颈就是GPU的使用率。