• 文档 >
  • CPU 线程和 TorchScript 推理
快捷方式

CPU 线程和 TorchScript 推理

PyTorch 允许在 TorchScript 模型推理期间使用多个 CPU 线程。下图显示了在典型应用程序中可以找到的不同级别的并行性

../_images/cpu_threading_torchscript_inference.svg

一个或多个推理线程对给定输入执行模型的前向传递。每个推理线程都会调用一个 JIT 解释器,该解释器会逐个内联执行模型的操作。模型可以使用 fork TorchScript 原语来启动异步任务。一次 fork 多个操作会导致并行执行任务。fork 算子返回一个 Future 对象,该对象可用于稍后同步,例如

@torch.jit.script
def compute_z(x):
    return torch.mm(x, self.w_z)

@torch.jit.script
def forward(x):
    # launch compute_z asynchronously:
    fut = torch.jit._fork(compute_z, x)
    # execute the next operation in parallel to compute_z:
    y = torch.mm(x, self.w_y)
    # wait for the result of compute_z:
    z = torch.jit._wait(fut)
    return y + z

PyTorch 为操作间并行使用单个线程池,该线程池由应用程序进程中 fork 的所有推理任务共享。

除了操作间并行性之外,PyTorch 还可以在操作中利用多个线程(操作内并行性)。这在许多情况下都很有用,包括对大型张量的逐元素操作、卷积、GEMM、嵌入查找等。

构建选项

PyTorch 使用内部 ATen 库来实现操作。除此之外,PyTorch 还可以构建为支持外部库,例如 MKLMKL-DNN,以加快 CPU 上的计算速度。

ATen、MKL 和 MKL-DNN 支持操作内并行性,并依赖于以下并行化库来实现它

  • OpenMP - 一种标准(和库,通常随编译器一起提供),广泛用于外部库中;

  • TBB - 一种较新的并行化库,针对基于任务的并行性和并发环境进行了优化。

OpenMP 在历史上已被大量库使用。它以相对易用和对基于循环的并行性和其他原语的支持而闻名。

TBB 在外部库中的使用程度较低,但同时针对并发环境进行了优化。PyTorch 的 TBB 后端保证应用程序中运行的所有操作都使用一个单独的、唯一的、每个进程的操作内线程池。

根据用例的不同,人们可能会发现一种或另一种并行化库在其应用程序中是更好的选择。

PyTorch 允许使用以下构建选项在构建时选择 ATen 和其他库使用的并行化后端

构建选项

备注

ATen

ATEN_THREADING

OMP(默认),TBB

MKL

MKL_THREADING

(同上)

要启用 MKL,请使用 BLAS=MKL

MKL-DNN

MKLDNN_CPU_RUNTIME

(同上)

要启用 MKL-DNN,请使用 USE_MKLDNN=1

建议不要在一个构建中混合使用 OpenMP 和 TBB。

上面的任何 TBB 值都需要 USE_TBB=1 构建设置(默认值:OFF)。OpenMP 并行性需要单独的设置 USE_OPENMP=1(默认值:ON)。

运行时 API

以下 API 用于控制线程设置

并行性类型

设置

备注

操作间并行性

at::set_num_interop_threadsat::get_num_interop_threads(C++)

set_num_interop_threadsget_num_interop_threads(Python,torch 模块)

默认线程数:CPU 核心数。

操作内并行性

at::set_num_threadsat::get_num_threads(C++) set_num_threadsget_num_threads(Python,torch 模块)

环境变量:OMP_NUM_THREADSMKL_NUM_THREADS

对于操作内并行性设置,at::set_num_threadstorch.set_num_threads 始终优先于环境变量,MKL_NUM_THREADS 变量优先于 OMP_NUM_THREADS

调整线程数

以下简单脚本显示了矩阵乘法的运行时间如何随线程数的变化而变化

import timeit
runtimes = []
threads = [1] + [t for t in range(2, 49, 2)]
for t in threads:
    torch.set_num_threads(t)
    r = timeit.timeit(setup = "import torch; x = torch.randn(1024, 1024); y = torch.randn(1024, 1024)", stmt="torch.mm(x, y)", number=100)
    runtimes.append(r)
# ... plotting (threads, runtimes) ...

在具有 24 个物理 CPU 内核的系统(Xeon E5-2680,基于 MKL 和 OpenMP 构建)上运行脚本会导致以下运行时间

../_images/cpu_threading_runtimes.svg

在调整运算内和运算间线程数时,应考虑以下因素

  • 选择线程数时,需要避免“超额订阅”(使用过多线程,会导致性能下降)。例如,在使用大型应用程序线程池或严重依赖运算间并行的应用程序中,可能会发现禁用运算内并行是一个可行的选择(即通过调用 set_num_threads(1));

  • 在典型的应用程序中,可能会遇到“延迟”(处理推理请求所花费的时间)和“吞吐量”(每单位时间完成的工作量)之间的权衡。调整线程数是调整这种权衡的一种有用工具。例如,在延迟关键型应用程序中,可能希望增加运算内线程数以尽快处理每个请求。同时,运算的并行实现可能会增加额外的开销,从而增加每个请求完成的工作量,从而降低整体吞吐量。

警告

OpenMP 不保证在应用程序中将使用单个进程内运算内线程池。相反,两个不同的应用程序或运算间线程可能对运算内工作使用不同的 OpenMP 线程池。这可能会导致应用程序使用大量线程。在 OpenMP 情况下,需要格外小心地调整线程数,以避免多线程应用程序中的超额订阅。

注意

预构建的 PyTorch 版本使用 OpenMP 支持进行编译。

注意

parallel_info 实用程序打印有关线程设置的信息,可用于调试。在 Python 中,可以使用 torch.__config__.parallel_info() 调用获取类似的输出。

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

获取针对初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并获得问题的解答

查看资源