在 CPU 上扩展 BERT 推理 (第 1 部分)
Hugging Face 详细介绍了一种在现代 CPU 上扩展类 BERT 模型推理的方法,证明了通过部署绑定到特定物理 CPU 核心的多个独立模型实例可以实现最大吞吐量。这种方法避免了同步多线程 (SMT) 和跨插槽通信的开销,从而实现了吞吐量近乎线性的扩展性。
开箱即用的框架性能
在 AWS c5.metal 实例 (Intel Xeon Platinum 8275 CPU, 48 cores/96 threads) 上进行的初步基准测试表明,在各种配置下,PyTorch (1.8.1) 在 BERT 推理的开箱即用性能方面通常优于 Google TensorFlow (2.4.1)。这种性能差异归因于底层的执行技术:PyTorch 利用 OpenMP 和 Intel MKL (oneDNN),而 TensorFlow 依赖于 Eigen 及其自身的线程实现。
CPU 架构与线程亲和性
为了优化像 BERT 推理这样主要由通用矩阵乘法 (GEMMs) 组成的 CPU 密集型任务,需要考虑特定的硬件因素:
同步多线程 (SMT)
SMT (或 Hyper-Threading) 允许两个软件线程共享单个物理核心。然而,由于 BERT 推理是 CPU 密集型的,逻辑核心会竞争相同的执行资源,这意味着 SMT 不提供性能收益,应避免使用 SMT 而仅使用物理核心。
NUMA 与插槽管理
现代多插槽服务器使用非一致性内存访问 (NUMA),其中每个 CPU 插槽管理其自身的内存子集。为了防止跨插槽通信开销导致的性能下降,必须使用 numactl 等工具将进程绑定到特定的核心和内存池。
关键配置策略:
- 线程亲和性: 将进程绑定到一组特定的物理核心 (例如,
numactl -C 0-47)。 - 内存分配: 确保内存分配在距离执行计算的核心最近的插槽上 (例如,
numactl -m 0,1)。
核心数扩展 vs. 多流推理
扩展推理资源有两种主要方式,每种方式对延迟和吞吐量的影响各不相同。
核心数扩展 (强扩展性)
这涉及增加分配给单个任务的核心数量以降低延迟。这种方法的有效性取决于问题规模:
- 中小规模问题: 使用单个插槽通常会产生最佳性能。
- 大规模问题: 跨插槽通信的开销会被计算成本抵消,使得使用跨两个插槽的所有可用核心变得有利。
多流推理 (实例并行性)
这种方法不是为单个实例分配更多核心,而是分配多个独立的模型实例,每个实例绑定到不重叠的物理核心子集。
例如,运行四个绑定到 12 个核心的独立实例,与使用 24 个核心的单个实例相比,单个实例的延迟会略有增加,但总吞吐量会增加 4 倍。这允许进行“智能调度”,即将请求路由到针对不同序列长度优化的特定实例 (例如,8 个核心用于短序列,24 个核心用于长序列)。
Batch Size 扩展与吞吐量
Batch Size 扩展涉及将全局 Batch $B$ 分配到 $N$ 个实例中,其中每个实例在 $C/N$ 个核心上处理 $B/N$ 的子批次。
延迟与吞吐量的权衡
- 延迟: 一个批次的整体延迟由池中最慢的实例决定。为了最小化延迟,实例的最佳数量取决于问题规模;对于 Batch 为 8 且序列长度为 128 的情况,4 个实例 (每个实例 Batch 为 2 且拥有 12 个核心) 提供了最佳结果。
- 吞吐量: 只要每个实例的工作负载按比例减少,吞吐量就会随着实例数量的增加而几乎线性地扩展。这表明了最佳的硬件利用率。
硬件优化影响总结
通过正确调整线程亲和性与实例分配,组织可以显著降低基础设施成本。Hugging Face 指出,对于某些工作负载,如果问题规模不需要额外的核心来实现最佳延迟,那么从 48 核机器 ($4.848/h) 迁移到 8 核实例 ($0.808/h) 可以实现 6 倍的成本降低。