• Tutorials >
  • 在 CPU 上使用最高自动调优编译以提高性能
Shortcuts

在 CPU 上使用最高自动调优编译以提高性能

Created On: Oct 10, 2024 | Last Updated: Oct 10, 2024 | Last Verified: Oct 10, 2024

作者: Jiong GongLeslie FangChunyuan Wu

在本教程中,您将学习如何通过利用 Inductor CPU 后端的最高自动调优模式来提高 PyTorch 模型在 CPU 上的性能。探索激活过程,了解与传统方法的区别,并将最大自动调优集成到您的代码中以增强计算效率。深入了解使用高级 GEMM 模板进行更快的处理和更高的运行时性能。

简介

Inductor CPU 后端的 max-autotune 模式在 torch.compile (RFC 链接) 中会在编译时对操作的多个实现进行分析,并选择性能最佳的一个,牺牲较长的编译时间以提高运行时性能。这种增强尤其有利于 GEMM 相关的操作。我们在 Inductor CPU 后端引入了基于 C++ 模板的 GEMM 实现,作为依赖 oneDNN 和 MKL 库的基于 ATen 方法的替代方案。这与 CUDA 上的 max-autotune 模式类似,其中考虑了来自 ATen、Triton 和 CUTLASS 的实现。

我们涵盖了大多数流行的数据类型,包括 FP32、BF16、FP16 和 INT8,带有 x86 CPU 的后处理融合。

尽管开发仍在进行中,我们已经在通过三个基准套件和 LLM 的推理测得的纯基于 ATen 的 GEMM 上看到了令人鼓舞的加速效果。

激活 max-autotune 模式

要在 PyTorch 中激活 max-autotune 模式,使用 torch.compile 编译模型时将 mode 参数设置为 max-autotune。如果您更喜欢跳过调优过程并始终使用 C++ 模板实现,则可以通过环境变量进行配置:export TORCHINDUCTOR_MAX_AUTOTUNE_GEMM_BACKENDS=CPP

示例

下面的代码示例展示了在一个简单的神经网络上使用 max-autotune 模式,其中包括一个线性层跟随一个 ReLU 激活。

在基于 C++ 模板的 GEMM 实现中,我们会预先打包权重以实现良好的缓存使用。在推理的情况下(这是 CPU AI 工作负载的主要场景),模型权重是恒定的,我们在编译期间预先打包它们,以便数据访问是在缓存块内连续的。因此,我们仅支持使用 torch.no_grad 或推理模式的冻结模型。您需要设置环境变量 export TORCHINDUCTOR_FREEZING=1 并确保编译和推理步骤都在 torch.no_grad 的上下文中执行。

import torch
from torch._inductor import config
config.trace.log_autotuning_results = True # enable the log of autotuning results

class M(torch.nn.Module):
    def __init__(
        self,
        in_features,
        out_features,
        bias,
        **kwargs,
    ):
        super().__init__()
        self.linear = torch.nn.Linear(
            in_features,
            out_features,
            bias,
            **kwargs,
        )
        self.relu = torch.nn.ReLU()

    def forward(self, x):
        x = self.linear(x)
        x = self.relu(x)
        return x

amp_enabled = True
batch_size = 64
in_features = 16
out_features = 32
bias = True

x = torch.randn(batch_size, in_features)
model = M(in_features, out_features, bias)

with torch.no_grad(), torch.cpu.amp.autocast(enabled=amp_enabled):
    compiled = torch.compile(model, mode="max-autotune") # turn on "max-autotune" mode
    y = compiled(x)

运行上述代码片段时,您将看到自动调优结果(性能数字仅为演示用途)。在此示例中,C++ 模板优于 ATen 内核,因此将被选择。

AUTOTUNE linear_unary(64x16, 32x16, 32)
cpp_packed_gemm_0 0.2142 ms 100.0%
_linear_pointwise 0.2441 ms 87.7%

我们可以通过设置 export TORCH_LOGS="+output_code" 检查生成的输出代码。当选择 C++ 模板时,生成的代码中不再有 ``torch.ops.mkldnn._linear_pointwise.default``(用于 bfloat16)或 ``torch.ops.mkl._mkl_linear.default``(用于 float32),而是基于 C++ GEMM 模板的内核 ``cpp_fused__to_copy_relu_1``(为了简化,仅展示部分代码)并在 C++ GEMM 模板内核中将 bias 和 relu 后处理融合。

生成的代码因 CPU 架构不同而有所不同,并且是实现特定的,可能会发生变化。

cpp_fused__to_copy_relu_1 = async_compile.cpp_pybinding(['const bfloat16*', 'const bfloat16*', 'const bfloat16*', 'bfloat16*'], '''

...

template <bool accum>
inline void kernel_micro_gemm_amx_kernel_32_2(
    AMXState& amx_state,
    const bfloat16* __restrict__ A,
    const bfloat16* __restrict__ B,
    float* __restrict__ C,
    int64_t K,
    int64_t lda,
    int64_t ldb,
    int64_t ldc,
    uint8_t tilecfg_rows
) {
    ...
}

...

template <bool accum>
inline void kernel_micro_gemm(
    AMXState& amx_state,
    const bfloat16* __restrict__ A,
    const bfloat16* __restrict__ B,
    float* __restrict__ C,
    int64_t M,
    int64_t N,
    int64_t K,
    int64_t lda,
    int64_t ldb,
    int64_t ldc
) {
    ...
}

extern "C"
void kernel(const bfloat16* X, const bfloat16* W, const bfloat16* inp, bfloat16* Y)
{
    constexpr int64_t num_threads = 40;
    constexpr int64_t N = 32;
    constexpr int64_t K = 16;
    constexpr int64_t M = static_cast<int64_t>(64L);
    ...
    #pragma omp parallel num_threads(40)
    {
        const int tid = omp_get_thread_num();
        ...
        for (int64_t mc_block_id = 0; mc_block_id < num_Mc_blocks_per_thread; mc_block_id++) {
            ...
            for (int64_t nc = n_block_start; nc < n_block_end; nc += Nc_blocks) {
                ...
                for (int64_t kc = k_block_start; kc < k_block_end; kc += Kc_blocks) {
                    ...
                    for (int64_t nci = nc; nci < nc_block_end; nci++) {
                        if (kc == k_block_start) {
                            kernel_micro_gemm<static_cast<bool>(false)>(
                                ...
                            );

                        } else {
                            kernel_micro_gemm<static_cast<bool>(true)>(
                                ...
                            );

                        }
                    }
                }
                {
                    {
                        // Epilogue fusion here for bias and relu
                        #pragma GCC ivdep
                        for(int64_t x0=static_cast<int64_t>(0L); x0<static_cast<int64_t>(m_end + ((-1L)*m_start)); x0+=static_cast<int64_t>(1L))
                        {
                            for(int64_t x1=static_cast<int64_t>(0L); x1<static_cast<int64_t>(16L*(c10::div_floor_integer(static_cast<int64_t>((n_end + ((-1L)*n_start))), static_cast<int64_t>(16L)))); x1+=static_cast<int64_t>(16L))
                            {
                                auto tmp0 = at::vec::Vectorized<bfloat16>::loadu(inp + static_cast<int64_t>(n_start + x1), static_cast<int64_t>(16));
                                auto tmp2 = at::vec::Vectorized<float>::loadu(local_acc_buf + static_cast<int64_t>(x1 + (Nc_blocks*Nr*x0)), static_cast<int64_t>(16));
                                auto tmp1 = at::vec::convert<float>(tmp0);
                                auto tmp3 = tmp1 + tmp2;
                                auto tmp4 = at::vec::convert<bfloat16>(tmp3);
                                auto tmp5 = static_cast<float>(0.0);
                                auto tmp6 = at::vec::Vectorized<float>(tmp5);
                                auto tmp7 = at::vec::maximum(tmp3, tmp6);
                                auto tmp8 = at::vec::convert<bfloat16>(tmp7);
                                tmp8.store(Y + static_cast<int64_t>(n_start + x1 + (32L*m_start) + (32L*x0)), static_cast<int64_t>(16));
                            }

                            ...

                        }
                    }

                }
            }
        }
        ...
    }
}
''')

总结

在本教程中,我们介绍了 CPU 上的 GEMM 模板的 max-autotune支持。我们解释了激活此功能的 API,并演示了 GEMM 模板生成的代码。

此功能处于原型阶段。如果您有任何功能请求或遇到任何问题,请在 GitHub issues 提交错误报告。

文档

访问 PyTorch 的详细开发者文档

查看文档

教程

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

查看教程

资源

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

查看资源