阿牛字幕 0.7.1:CPU 核心用的越多,反而越慢,坑填了,聊聊为什么

萝卜2026-09-13

把推理线程从 10 改成 4,M4 上转写快了 25%。核不是用得越多越好。

一个朋友跟我反馈:他用阿牛字幕,i9-12900HX,跑 Fun-ASR-Nano,只有 1.8 倍速。

我愣了一下。因为我那台 2017 年的 iMac(i5-7500),跑同样的东西是 1.5 倍速。

一颗 2022 年的旗舰移动 CPU——8 个性能核加 8 个能效核——只比一颗九年前的入门四核快 20%?这不对劲。硬件差距一定是被什么东西吃掉了。

0.7.0 就是这个"什么东西"。查完的结论是:问题出在"用几个核"上,而且不是越多越好。

我们从 0.6.0 开始,讲讲这几个版本踩的性能的坑。

一、先把几台机器摆在一起

CPU 上市 核心构成 逻辑线程 0.6.x 固定线程 0.7.0 逻辑核 0.7.1 性能核
Intel i5-7500 2017 4 个 P 核(无超线程) 4 4 4 4
Apple M1 2020 4 P + 4 E 8 4 8 4
Intel i9-12900HX 2022 8 P(16 线程)+ 8 E 24 4 24 8
Apple M4 2024 4 P + 6 E 10 4 10 4

问题一眼就出来了:i9-12900HX 的 24 个线程里,0.6.x 只用了 4 个——8 个性能核只用了一半,8 个能效核全闲着。

而 i5-7500 一共就 4 个核,用 4 个,正好用满。

于是在软件眼里,这两台机器的算力是一样的(都是 4 线程),差距只剩单核性能。这就是"i9 只快两成"的全部原因,跟 CPU 本身一点关系都没有。

二、原因:FunASR 里写死的那个 4

翻 FunASR 源码,找到这么一行:

# funasr/auto/auto_model.py
def _resolve_ncpu(config, fallback=4):
    value = config.get("ncpu", fallback)   # ← 不传就是 4

AutoModel 初始化时会执行 torch.set_num_threads(_resolve_ncpu(kwargs, 4))。也就是说:不显式传 ncpu,推理线程就被定死成 4,跟你机器上有几个核毫无关系。

这不算 FunASR 的错——作为通用推理框架,它得假设最差情况,用一个保守数字兜底是合理的。

三、0.7.0 修改就把自己坑了

0.7.0 修法非常直接:检测本机核数,喂给 ncpu。

ncpu = os.cpu_count()   # 4 核机器 → 4,24 线程机器 → 24

很自然对吧?核多线程多,跑得快。我甚至把它写进了 0.7.0 的发布说明:「推理线程数自适应,不再固定 4 核,改为按本机 CPU 核数设置。核多的机器,转写更快。」

这句话说对了一半,也埋了一个坑。

写完之后读文档时,被 AI 提醒了一句:用所有核心,可能不是个好策略。

当时没有在意,直到遇到尊贵的 i9-12900HX 和我 9 年前的 i5 性能差不多,才意识到,这个提醒,可能比想象的严重。

于是,重新回到核心问题,测试。

四、实测:线程数超过物理核,反而更慢

拿本机那台 4 核 i5-7500,同一段 8 秒音频、同一个模型,只改推理线程数,每档跑 5 次取中位数:

线程数 墙钟耗时 相对 1 线程
1 1.073s 1.00x
2 0.658s 1.63x
3 0.505s 2.12x
4 0.443s 2.42x

两个信号:

  1. 4 个核只换来 2.42 倍。 每加一个核,收益都在衰减——第 4 个核只贡献了 +14%;
  2. 累计 CPU 时间从单线程的 1.02 秒涨到 1.48 秒,+45%。多线程本身是有成本的。

接着把线程数开到物理核数以上(4 核开 5/6/8 线程,相当于往每个核里塞一个半到两个线程):

线程数 超订倍数 墙钟耗时 相对 4 线程
4 1.0x 0.429s —
5 1.25x 0.573s -25%
6 1.5x 0.546s -22%
8 2.0x 0.511s -16%

只多开一个线程,就慢了 25%。 一台 4 核机器上就已经这么敏感了,24 线程的 i9 上会怎样,可想而知。

为什么会这样

推理框架里的算子(矩阵乘、归一化、激活……)是分片并行 + barrier 同步的:一个算子按线程数切成 N 份,每个线程算一份,算完在 barrier 上等齐,才能进下一个算子。

于是有这么一条:

一个算子要多久,不取决于大家平均多快,取决于最慢的那一片有多慢。

线程数超过真实算力之后,多出来的线程拿不到执行单元,却还是被分了一份活。它算得慢,所有人就得等它。并行度越高,这个"拖后腿"的效应越明显。

超线程和 E 核,是同一个问题的两个版本

  • 超线程:两个逻辑线程共享同一套物理执行单元(尤其是浮点/向量单元)。矩阵乘这类密集计算根本拿不到 2 倍,通常只有 0 ~ 15%,甚至负收益。
  • E 核(能效核):它是货真价实的核,但单核吞吐只有 P 核的 1/3 ~ 1/2。放进同一个 barrier 里,它就成了那个"最慢的分片"。

所以在大小核机器上,"用满逻辑核"是双重踩坑:先被超线程稀释一遍,再被 E 核拖一遍。

M4 上的验证:10 线程 → 4 线程,快 25%

最后在一台 M4(4 个 P 核 + 6 个 E 核,10 个逻辑核)上做了严格 A/B:

  • A:用满 10 线程(4 个 P 核 + 6 个 E 核)
  • B:只用 4 个 P 核

同一台机器、同一个视频、同一个模型,4 线程比 10 线程快 25%。

这也说明了 0.7.0 在大小核机器上是倒退的:它把线程数从 4 改成了 10(M4)、24(i9-12900HX),方向完全反了。

五、0.7.1 的做法:找"性能核",而不是"核"

结论很清楚:要的是性能核的数量,不是核的数量。

0.7.1 按平台分别探测:

平台 取法 例子
macOS(Apple Silicon) sysctl 读 hw.perflevel0.logicalcpu(性能核) M4 → 4
macOS(Intel) sysctl 读 hw.physicalcpu(没有大小核之分;physicalcpu 是物理核数,已排除超线程) i5-7500 → 4
Linux 按 cpufreq/cpuinfo_max_freq 只数最高频那一档(P 核),并用 core_cpus_list 排掉超线程 8P + 8E → 8
Windows 调 GetLogicalProcessorInformationEx,按 EfficiencyClass 只数性能核 i9-12900HX → 8

三条原则:

  • 不设上限。 机器上有多少性能核就用多少——M4 给 4,M2 Pro 及以上给 6、8、10……不人为截断。
  • 探测失败就退回 4。 和 FunASR 原本的兜底一致。宁可保守,不赌。
  • 加一道合理性校验。 性能核数不可能超过逻辑核数,超过就判定为解析异常、直接走兜底——避免识别出错时把线程数设成离谱的值。

六、哪些机器会变快,哪些没变化

先把话说明白,免得升级之后觉得"没感觉":

机器 变化
i9-12900HX 这类 8 个 P 核的 4 → 8 线程,8 个性能核全部用上
M2 Pro / M4 Pro 这类性能核多于 4 个的 4 → 全部性能核(6 个、8 个、10 个……)
M1 / M4 / i5-7500 这类性能核正好 4 个的 和 0.7.0 一样是 4,感受不到区别
没有超线程、没有 E 核的老机器 无变化

换句话说:0.7.1 修的是 0.7.0 在大小核机器上踩的坑,普通四核机器不受影响。

具体能快多少,取决于你的 CPU 和视频内容。我们只在 M4 上做了严格的 A/B 对比(+25%);i9-12900HX 那台机器是把"只用一半 P 核"变成"全部用上",方向明确,但数值得等更多机器反馈。

七、顺带:逐字时间戳不是元凶

0.7.0 刚加了逐字时间戳(SenseVoice 走 CTC 强制对齐),我一度怀疑是它拖慢了转写。顺手量了一下:整片只多 5% ~ 7%,其中最大的一笔是 FunASR 内部对同一份 encoder 输出重复算了一次大矩阵投影(每段约 30ms)。

它够不上主要矛盾,所以 0.7.1 没动它。真正吃掉性能的,从头到尾都是线程数。

升级说明

  • 建议直接下载覆盖安装。FunASR、Python 运行时依旧全部随包内置,无需手动安装依赖
  • 本次只改了推理线程数的取法,转写结果结构没有任何变化,缓存全部保留,旧文件不需要重新转写
  • 如果你的 CPU 性能核多于 4 个(i9 / i7 的 HX、H 系列,M2 Pro 及以上),升级后转写会明显更快;四核机器表现不变

写在最后

这个坑让我记住一件事:"把资源用满"不是优化,是本能。 真正的优化得先弄清楚资源到底是怎么被用掉的——超线程不是免费的加倍,"核心数"这个词在大小核时代也已经不准确了。

那颗 i9-12900HX 的 1.8 倍速,最后不是硬件不够,是软件没让它上场。

免费使用,本地运行,下载即用。

👉 官网下载体验:https://thinking.vip/a-niu-subtitle

阿牛字幕 生成字幕,粗剪视频

2026-09-14 09:57:5948 次阅读