Published on

FunASR部署避坑指南:多进程下CPU线程爆炸导致内存溢出卡死的排查与解决

推理服务实践已归档系统工程
Authors

1. 问题背景:豪横的 180 核服务器反而跑不起来?

最近在部署 FunASR 语音识别服务时,遇到了一件非常"反直觉"的事情。我使用了一台配置极高的服务器,拥有 180 个 CPU 核心。本以为这样的硬件能带来极致的推理速度,结果服务启动并在高并发压测下,直接崩溃了。


故障现象

具体的故障现象如下:

报错信息:

控制台疯狂输出 OpenMP 错误:

libgomp: Thread creation failed: Resource temporarily unavailable

系统指标:

  • CPU 使用率:显示为 14000%(正常应该是 100% * 核心数,但这里显示异常高)
  • 内存:快速增长直至 OOM(Out Of Memory) killer 触发
  • 服务状态:进程卡死,无法响应请求

问题定位过程

2.1. 初步排查

最初怀疑是内存泄漏,但通过内存分析工具(如 valgrind、gdb)检查,并未发现明显的泄漏点。

2.2. 深入分析

通过 htop 命令观察,发现单个进程创建了上百个线程,而系统中同时运行着多个这样的进程。在 180 核的服务器上,理论上可以支持大量线程,但问题在于:

  • 线程爆炸:每个 FunASR 进程默认会根据 CPU 核心数创建大量线程
  • 上下文切换开销:当线程数远超物理核心数时,大量的上下文切换导致 CPU 时间浪费在调度上
  • 内存压力:每个线程都需要分配栈空间,大量线程导致内存快速耗尽

2.3. 根本原因

FunASR 基于 PyTorch,而 PyTorch 默认使用 OpenMP 进行并行计算。OpenMP 会尝试创建与 CPU 核心数相等的线程数,但在多进程环境下,如果每个进程都这样做,总线程数会呈指数级增长。


解决方案

3.1. 限制线程数

通过设置环境变量或代码配置,限制每个进程的线程数:

import torch
import os

# 方法1:设置全局线程数
torch.set_num_threads(4)  # 限制为4个线程

# 方法2:通过环境变量
os.environ["OMP_NUM_THREADS"] = "4"
os.environ["MKL_NUM_THREADS"] = "4"

3.2. 配置优化

根据服务器实际核心数和进程数,合理分配线程数:

# 假设有180核,部署10个进程
# 每个进程分配 180/10 = 18个线程,但实际可以更保守
torch.set_num_threads(8)  # 每个进程使用8个线程

3.3. 效果验证

配置后重新部署:

  • CPU 使用率:恢复到正常水平,不再出现虚高的 14000%
  • 内存占用:稳定在合理范围
  • QPS 提升:由于减少了昂贵的上下文切换开销,整体服务的吞吐量(QPS)反而提升了,延迟也更加稳定

避坑总结

在 180核 这种怪兽级服务器上跑深度学习推理,千万不要迷信默认配置。

  • 现象:CPU 几千/几万%,报 libgomp 错误,服务卡死。
  • 本质:线程爆炸 (Thread Explosion)。
  • 对策torch.set_num_threads() 是必须配置的参数。