评价此页

使用区域编译减少 AoT 冷启动编译时间#

作者: Sayak Paul, Charles Bensimon, Angela Yi

区域编译指南中,我们展示了如何在保持(几乎)全部编译收益的同时减少冷启动编译时间。该方法在即时(JIT)编译中进行了演示。

本指南展示了在提前(AoT)编译模型时如何应用类似的原则。如果您不熟悉 AOTInductor 和 torch.export,建议您查看此教程

先决条件#

  • PyTorch 2.6 或更高版本

  • 熟悉区域编译

  • 熟悉 AOTInductor 和 torch.export

设置#

在开始之前,如果尚未安装 torch,我们需要先进行安装。

pip install torch

步骤#

在本指南中,我们将遵循上述区域编译指南中的相同步骤

  1. 导入所有必要的库。

  2. 定义并初始化一个具有重复区域的神经网络。

  3. 测量完整模型与区域编译模型在 AoT 下的编译时间。

首先,让我们导入加载数据所需的库

import torch
torch.set_grad_enabled(False)

from time import perf_counter

定义神经网络#

我们将使用与区域编译指南相同的神经网络结构。

我们将使用一个由重复层组成的网络。这模拟了大型语言模型,通常由许多 Transformer 块组成。在本指南中,我们将使用 nn.Module 类创建一个 Layer 作为重复区域的代理。然后,我们将创建一个由该 Layer 类的 64 个实例组成的 Model

class Layer(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear1 = torch.nn.Linear(10, 10)
        self.relu1 = torch.nn.ReLU()
        self.linear2 = torch.nn.Linear(10, 10)
        self.relu2 = torch.nn.ReLU()

    def forward(self, x):
        a = self.linear1(x)
        a = self.relu1(a)
        a = torch.sigmoid(a)
        b = self.linear2(a)
        b = self.relu2(b)
        return b


class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(10, 10)
        self.layers = torch.nn.ModuleList([Layer() for _ in range(64)])

    def forward(self, x):
        # In regional compilation, the self.linear is outside of the scope of ``torch.compile``.
        x = self.linear(x)
        for layer in self.layers:
            x = layer(x)
        return x

提前编译模型#

由于我们要提前编译模型,我们需要准备在实际部署中模型预期的代表性输入示例。

让我们创建一个 Model 实例,并传入一些样本输入数据。

model = Model().cuda()
input = torch.randn(10, 10, device="cuda")
output = model(input)
print(f"{output.shape=}")
output.shape=torch.Size([10, 10])

现在,让我们提前编译模型。我们将使用上面创建的 input 传递给 torch.export。这将产生一个可以编译的 torch.export.ExportedProgram

/usr/lib/python3.10/copyreg.py:101: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
  return cls.__new__(cls, *args)
/var/lib/ci-user/.local/lib/python3.10/site-packages/torch/_inductor/compile_fx.py:321: UserWarning: TensorFloat32 tensor cores for float32 matrix multiplication available but not enabled. Consider setting `torch.set_float32_matmul_precision('high')` for better performance.
  warnings.warn(

我们可以从这个 path 加载并使用它来执行推理。

compiled_binary = torch._inductor.aoti_load_package(path)
output_compiled = compiled_binary(input)
print(f"{output_compiled.shape=}")
output_compiled.shape=torch.Size([10, 10])

提前编译模型的 _区域_#

另一方面,提前编译模型区域需要几个关键更改。

由于模型中所有重复的块(本例中为 Layer 实例)共享相同的计算模式,我们只需编译一个块并让 inductor 重用它即可。

model = Model().cuda()
path = torch._inductor.aoti_compile_and_package(
    torch.export.export(model.layers[0], args=(input,)),
    inductor_configs={
        # compile artifact w/o saving params in the artifact
        "aot_inductor.package_constants_in_so": False,
    }
)
/usr/lib/python3.10/copyreg.py:101: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
  return cls.__new__(cls, *args)

导出程序(torch.export.ExportedProgram)包含张量计算、包含所有提升参数和缓冲区的 state_dict,以及其他元数据。我们将 aot_inductor.package_constants_in_so 指定为 False,以便不在生成的产物中序列化模型参数。

现在,在加载编译后的二进制文件时,我们可以重用每个块的现有参数。这使我们能够利用上面获得的编译二进制文件。

for layer in model.layers:
    compiled_layer = torch._inductor.aoti_load_package(path)
    compiled_layer.load_constants(
        layer.state_dict(), check_full_update=True, user_managed=True
    )
    layer.forward = compiled_layer

output_regional_compiled = model(input)
print(f"{output_regional_compiled.shape=}")
output_regional_compiled.shape=torch.Size([10, 10])

就像 JIT 区域编译一样,提前编译模型内的区域可以显著减少冷启动时间。实际数值会因模型而异。

尽管完整模型编译提供了最全面的优化范围,但出于实际目的并根据模型类型,我们观察到区域编译(JiT 和 AoT)提供了相似的速度增益,同时大幅减少了冷启动时间。

测量编译时间#

接下来,让我们测量完整模型与区域编译的编译时间。

def measure_compile_time(input, regional=False):
    start = perf_counter()
    model = aot_compile_load_model(regional=regional)
    torch.cuda.synchronize()
    end = perf_counter()
    # make sure the model works.
    _ = model(input)
    return end - start

def aot_compile_load_model(regional=False) -> torch.nn.Module:
    input = torch.randn(10, 10, device="cuda")
    model = Model().cuda()

    inductor_configs = {}
    if regional:
        inductor_configs = {"aot_inductor.package_constants_in_so": False}

    # Reset the compiler caches to ensure no reuse between different runs
    torch.compiler.reset()
    with torch._inductor.utils.fresh_inductor_cache():
        path = torch._inductor.aoti_compile_and_package(
            torch.export.export(
                model.layers[0] if regional else model,
                args=(input,)
            ),
            inductor_configs=inductor_configs,
        )

        if regional:
            for layer in model.layers:
                compiled_layer = torch._inductor.aoti_load_package(path)
                compiled_layer.load_constants(
                    layer.state_dict(), check_full_update=True, user_managed=True
                )
                layer.forward = compiled_layer
        else:
            model = torch._inductor.aoti_load_package(path)
    return model

input = torch.randn(10, 10, device="cuda")
full_model_compilation_latency = measure_compile_time(input, regional=False)
print(f"Full model compilation time = {full_model_compilation_latency:.2f} seconds")

regional_compilation_latency = measure_compile_time(input, regional=True)
print(f"Regional compilation time = {regional_compilation_latency:.2f} seconds")

assert regional_compilation_latency < full_model_compilation_latency
/usr/lib/python3.10/copyreg.py:101: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
  return cls.__new__(cls, *args)
Full model compilation time = 11.87 seconds
/usr/lib/python3.10/copyreg.py:101: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
  return cls.__new__(cls, *args)
Regional compilation time = 5.52 seconds

模型中可能存在与编译不兼容的层。因此,完整编译会导致计算图碎片化,从而可能导致延迟增加。在这种情况下,区域编译会更有益。

结论#

本指南展示了在提前编译模型时如何控制冷启动时间。当您的模型具有重复块(通常见于大型生成模型)时,此方法非常有效。我们在各种模型上使用了此指南来加速实时性能。了解更多信息请点击此处

脚本总运行时间: (0 分 42.938 秒)