CPU 线程和 TorchScript 推理¶
PyTorch 允许在 TorchScript 模型推理期间使用多个 CPU 线程。下图显示了在典型应用程序中会发现的不同并行化级别
一个或多个推理线程在给定输入上执行模型的前向传递。每个推理线程都会调用一个 JIT 解释器,该解释器会逐个执行模型的操作。模型可以使用 fork
TorchScript 原语来启动异步任务。一次性分叉多个操作会导致一个并行执行的任务。 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 使用单个线程池进行操作间并行,此线程池由应用程序进程中分叉的所有推理任务共享。
除了操作间并行之外,PyTorch 还可以在操作内部使用多个线程(操作内并行)。这在许多情况下非常有用,包括对大型张量的逐元素操作、卷积、GEMM、嵌入查找等。
构建选项¶
PyTorch 使用内部 ATen 库来实现操作。此外,PyTorch 还可以构建为支持外部库,例如 MKL 和 MKL-DNN,以加快 CPU 上的计算速度。
ATen、MKL 和 MKL-DNN 支持操作内并行,并依赖以下并行化库来实现它
历史上,OpenMP 被大量的库使用过。它以相对易于使用而闻名,并且支持基于循环的并行性和其他原语。
TBB 在外部库中的使用较少,但同时针对并发环境进行了优化。PyTorch 的 TBB 后端保证应用程序中运行的所有操作都使用一个单独的、每个进程一个的、操作内线程池。
根据使用情况,人们可能会发现在一个应用程序中,一个或另一个并行化库是更好的选择。
PyTorch 允许在构建时使用以下构建选项选择 ATen 和其他库使用的并行化后端
库 |
构建选项 |
值 |
注释 |
---|---|---|---|
ATen |
|
|
|
MKL |
|
(相同) |
要启用 MKL,请使用 |
MKL-DNN |
|
(相同) |
要启用 MKL-DNN,请使用 |
建议不要在一个构建中混合使用 OpenMP 和 TBB。
上面任何 TBB
值都需要 USE_TBB=1
构建设置(默认:OFF)。OpenMP 并行需要单独的设置 USE_OPENMP=1
(默认:ON)。
运行时 API¶
以下 API 用于控制线程设置
并行化类型 |
设置 |
注释 |
---|---|---|
操作间并行 |
|
默认线程数:CPU 内核数。 |
操作内并行 |
环境变量: |
对于操作内并行设置,at::set_num_threads
,torch.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 的构建)会导致以下运行时间
调整操作间和操作内线程数时,应考虑以下因素
选择线程数时,应避免 过度订阅(使用过多的线程会导致性能下降)。例如,在使用大型应用程序线程池或严重依赖操作间并行的应用程序中,人们可能会发现禁用操作内并行是一种可能的选项(例如,通过调用
set_num_threads(1)
);在典型的应用程序中,可能会遇到 延迟(处理推理请求所花费的时间)和 吞吐量(单位时间内完成的工作量)之间的权衡。调整线程数量可以作为一种有效的工具,以某种方式调整这种权衡。例如,在延迟关键的应用程序中,人们可能希望增加线程数以尽快处理每个请求。 同时,操作的并行实现可能会增加额外的开销,从而增加单个请求完成的工作量,并因此降低整体吞吐量。
警告
OpenMP 不保证应用程序将使用单个每个进程的内部操作线程池。相反,两个不同的应用程序或跨操作线程可能使用不同的 OpenMP 线程池来进行内部操作工作。 这可能会导致应用程序使用大量线程。 在 OpenMP 的情况下,需要格外注意调整线程数量,以避免在多线程应用程序中出现过度订阅。
注意
预构建的 PyTorch 版本已编译并支持 OpenMP。
注意
parallel_info
实用程序打印有关线程设置的信息,可用于调试。 类似的输出也可以在 Python 中使用 torch.__config__.parallel_info()
调用获取。