返回 文章 学习 CMS 文章

超越单一构建路径:NVIDIA TensorRT 全局性能调优器如何扩展性能优化

用类比与数学框架说明:深度学习编译没有唯一最优调度,TensorRT 通过全局性能调优器在多种构建路径间自动搜索更优引擎。

TensorRT性能优化深度学习编译器调度策略
成长分 / 100 65 综合收获、行动、留存与影响

超越单一构建路径:NVIDIA TensorRT 全局性能调优器如何扩展性能优化
为什么值得读理解深度学习编译器为何是“有多种合法答案的编译问题”,调度决策如何影响端到端推理性能。

认识启发式优化在模型与硬件多样化下出现的三类扩展性问题:启发式膨胀、性能与精度纠缠、性能定制化。

关键洞察
  1. TensorRT 构建引擎时会对图 IR 变换、融合、实现选择、内存布局和量化做出相互依赖的决策,这一过程称为调度。
  2. 不同调度选择可保持模型语义不变,却产生差异很大的推理性能,因此调度是深度学习编译器优化的核心。
  3. 没有一种调度策略对所有模型和部署环境都最优;单独更快的 kernel 可能因融合、张量布局或内存访问而在完整图中表现不佳。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:18817

部署训练好的神经网络,不只是把每个算子翻译成 GPU kernel。它是一个编译问题,存在许多合理的答案。例如,当 NVIDIA TensorRT 构建 engine 时,它会就图 IR 变换、融合、实现选择、内存放置和量化做出相互依赖的决策。这一过程称为调度(scheduling)。不同的调度选择可以保持模型语义不变,却产生截然不同的推理性能,因此调度是深度学习编译器优化的核心。

没有哪一种调度策略对所有模型和部署环境都是最优的。加速某个网络的变换可能会拖慢另一个网络。单独看更快的 kernel,可能与图中其他位置的融合、张量布局或内存访问产生不良交互。激进的调度可能提升性能,却违反应用的精度要求。这些相互耦合、依赖模型的影响往往只有在完整 engine 构建并测量之后才会显现,使得正确的策略越来越难以预测。

本文是两部分系列文章的第 1 部分,旨在建立一种更清晰的性能优化推理方式。文章探讨了以下问题:是什么让一个编译后的 engine 优于另一个?为什么单独看很有希望的决定可能在端到端上失败?精度应如何约束性能优化?文章介绍了 Global Performance Tuner——TensorRT 11.1 中发布的自动性能调优功能,并通过一个直观示例和数学框架来探讨这些问题。

Global Performance Tuner 功能解决了什么挑战?

下面的类比说明了在 TensorRT 中构建 Global Performance Tuner 功能的动机。

设想一位厨师,有一道出色的菜,多年来一直让三位常客满意。一位喜欢辣,另一位喜欢酸甜口味,第三位喜欢多放盐。厨师把辣椒、糖浆、柠檬和盐组合成一份标准配方,满足了这三位顾客。有一天,第四位新顾客来了,他不喜欢辣。保留辣椒会让新顾客失望;去掉辣椒又可能失去第一位顾客。

厨师困境的根本原因在于,添加调料的方式无法扩展:相互影响的例外数量会随顾客群体增长,而针对一位顾客的改动可能让另一位顾客的用餐体验变差。

有一位厨师为四位不同的顾客供应同一道菜。第一位喜欢辣,第二位喜欢酸甜,第三位喜欢咸,第四位讨厌辣。厨师可以通过在菜中加入辣椒、糖浆、柠檬和盐来服务好前三位顾客,但不知道如何服务第四位顾客。
图 1. 一份固定配方无法扩展到相互冲突的顾客偏好

一种可扩展的做法是保留基础菜品,但让调料可配置。厨师为每位常客记录合适的组合。对于偏好未知的新顾客,厨师提供不同组合的免费样品,排除不合适的变体,并保留顾客最喜欢的那一种。

厨师将调料分开,分别用于四位不同的顾客。给第一位顾客的菜会加入辣椒。给第二位顾客的菜会加入糖浆和柠檬。给第三位顾客的菜会加入盐。给第四位顾客的菜会加入其他调料。
图 2. 为每位顾客定制口味

把这个类比映射到深度学习编译:TensorRT 是厨师,其共享流水线是基础配方,可调的编译器配置是调料,编译后的 engine 是成品菜。每个 ONNX 模型是一位顾客。性能调优提供样品,端到端性能反映顾客满意度。

过去,TensorRT 性能优化从内部 KPI 模型开始。工程师研究这些工作负载,识别可优化的模式,并将发现编码为 TensorRT 默认调度策略中的启发式规则。每条启发式规则都凝聚了专家知识,使用户能够自动受益于在代表性模型上发现的优化。

随着模型和部署环境日益多样化,这种做法导致了类似上述类比中的扩展问题。受某个内部 KPI 模型启发的启发式规则加入共享策略后,可能有助于熟悉的模式,却对未见过的外部模型造成性能回退。增加另一项检查或例外可能保护该模型,但重复这一过程会使调度逻辑越来越难以扩展。

该问题以三种相关形式出现:

  • H启发式规则泛滥:每新增一条针对特定模型的启发式规则,都会引入另一条规则,而这条规则与现有规则的相互作用必须被理解和维护。随着这些规则不断累积,代码将变得难以维护,并且针对某一类模型的改进可能会无意中影响另一类模型。
  • 性能与精度的纠缠:降低精度的 kernel 和其他激进优化可以提升性能,同时引入超出应用可接受容差的数值差异。更高精度的选择可能降低这种风险,但会牺牲性能。因此,性能和数值可接受性并不总能独立优化。
  • 性能定制:对某个模型或 GPU 有效的调度选择,对另一个模型或 GPU 可能并不有效。把每一个成功的选择都编码进一个通用调度策略,会使 TensorRT 难以针对不断扩展的外部模型、硬件平台和应用需求进行高效特化。

调优为启发式优化提供了一种可扩展的补充。

深度学习编译器性能的数学理论

本节建立深度学习编译器性能的抽象数学模型。其目的是区分两个基本问题:

  • 编译器能为模型构建哪些可执行文件
  • 编译器如何从这些可能性中选择一个以获得最优性能

概念:模型、模型空间

令 mm 表示一个模型,MM 表示所考虑的全部模型的集合,称为模型空间:

m∈Mm \in M

这里,模型是对待编译计算的抽象描述。其具体表示可以是 ONNX 图、通过 TensorRT API 创建的网络、PyTorch 模型,或其他完整指定模型计算的前端表示。

概念:解、解空间

给定一个模型 mm 作为输入,深度学习编译器为目标硬件构建一个可执行输出,定义为解。同一个模型通常可以在不同的调度策略下编译成不同的解。令 ss 表示这样一个解,SS 表示可能的解的集合,称为解空间。

s∈S={s0,s1,s2,…,sn}s \in S = \{s_0, s_1, s_2, …, s_n\}

解是对目标硬件上可执行计算的抽象。具体而言,解可以是 TensorRT 引擎,或序列化 GPU 内核序列的 cubin。更抽象地说,它也可以是编译器的中间表示(IR),前提是该表示在剩余编译过程中唯一确定最终可执行文件。

每个解还定义了从模型输入域到其输出陪域的函数:

s:X→Ys : X \rightarrow Y

s(x)=ys(x) = y

这里,(x,y)(x,y) 是实现神经网络的 GPU 内核序列的输入-输出对。在此抽象层次上,模型描述预期的计算,而解描述在目标硬件上实现该计算的一种特定方式。

确定性原理

陈述:当确定性程序及其完整执行环境都保持固定时,该程序具有固定的底层运行时间。

该原理作为性能分析的建模假设。在严格的解释下,环境包括输入数据、目标设备、软件栈、GPU 频率、温度、电源状态、系统负载以及其他可能影响运行时间的执行条件。在实践中,并非每个环境变量都能被完美控制,因此重复的运行时间测量会表现出噪声。如果将此扰动建模为独立高斯噪声,则重复测量的平均值是底层平均运行时间的最大似然估计。

一旦执行环境在抽象所要求的层面上被固定,每个解就可以与一个特征运行时间相关联。这种关联使运行时间能够表示为数学函数。

概念:运行时间函数

运行时间函数 TT 将每个解映射到其在固定执行环境下于指定目标设备上的运行时间:

T:S→R+T : S \rightarrow \mathbb{R}^+

T(s0)=t0T(s_0) = t_0

环境被视为固定,因此从记号中省略。

概念:构建器算法

构建器算法(也称为性能调度器)是从模型空间到解空间幂集的映射。当给定一个特定模型作为输入时,构建器算法将输出一个满足给定精度要求的解空间。

B:M→2SB: M \rightarrow 2^S

B(m)={s1,s2,…,sn∣max⁡x∈X\norms(x)–y≤ϵ}=SB(m) = \{s_1, s_2, …, s_n \mid \max_{x \in X} \norm{ s(x) – y } \leq \epsilon \} = S

在上述精度条件中,输入-输出对 (x,y)(x, y) 被视为参考值,εε 表示精度容差阈值。参考数据、比较指标和容差被视为固定上下文,因此从 BB 的参数中省略。

理想情况下,构建器算法应生成足够丰富的解集,以包含 s∗s^*,即最优解:建模解空间中最快的可行解。

s∗∈B(m)s^* \in B(m)

概念:搜索算法

搜索算法(也称为解空间求解器)是从解空间幂集到解空间的映射。当给定一组候选解作为输入时,搜索算法将从中挑选一个解作为输出。

A:2S→SA: 2^S \rightarrow S

A(S)=argminsi∈S‾T(si),S‾⊆SA(S) = argmin_{s_i \in \underline{S}} T(s_i), \underline{S} \subseteq S

注意,在现实世界中,搜索算法可能从解空间中挑选一个次优解。在 TensorRT 中,这种次优性至少可能来自两个来源:

  • 搜索过程中使用的启发式方法:启发式方法可能在候选解被作为完整引擎的一部分进行评估之前就将其移除。例如,构建器可能根据输入模型某一层中的通道维度阈值过滤某些卷积策略。如果被过滤的策略本会参与最快的端到端解,搜索算法就无法再发现该最优解。
  • 局部性能分析与端到端执行之间的不匹配:在引擎构建期间,内核候选可能在策略选择期间被单独进行性能分析,然而,推理时性能是在整个引擎范围内测量的。单独运行的内核与作为更大图的一部分运行的同一内核并不共享完全相同的执行环境(例如,NVIDIA Blackwell GPU 中使用的自适应网格尺寸技术)。因此,单独测量的内核运行时间之和不一定等于端到端运行时间。

在理想情况下,构建器算法生成包含 s∗s^* 的候选集,搜索算法可靠地选择它:

A(S)=s∗A(S) = s^*

因此,深度学习编译器可以由算法元组 (A,B)(A, B) 表示,其中 BB 生成候选解,AA 在它们之间进行选择。然而,在实际实现中,候选生成、剪枝、性能分析和选择可能紧密耦合。因此,AA 与 BB 之间的边界在编译器架构中可能并不明确。

令 B∗B^* 表示构建器算法族中的最优构建器算法,A∗A^* 表示搜索算法族中的最优搜索算法。理想的编译器将实现元组 (A∗,B∗)(A^*,B^*)。对于任何输入模型,该元组将生成包含全局最优解的候选集,然后可靠地发现它:

∀A,B,∀m∈M,T(A∗(B∗(m)))≤T(A(B(m)))\forall A, B, \forall m \in M, T(A^*(B^*(m))) \leq T(A(B(m)))

在数学抽象的层面上,A∗A^* 的一种可能实现是暴力算法——评估候选集中的每个解,并选择运行时间最短的那个:

A∗(S)=argminsi∈ST(si)A^*(S) = argmin_{s_i \in S} T(s_i)

尽管在构建时间预算有限时,应用暴力搜索算法并不总是可接受的,但构建时间与推理性能之间可能存在权衡。A∗A^* 用于表示在给定任何输入模型时都必定返回最优解的搜索算法。

构建 B∗B^* 是更根本的挑战。即使是理想的搜索算法,也无法选择构建器从未使其可达的解。与其期望一步构建出 B∗B^*,不如通过扩展和改进编译器可用的构建路径来逐步逼近它。

Global Performance Tuner 建模

本节将编译建模为一系列编译器 pass,然后展示可定制构建路径如何为精度感知的性能调优提供基础。

深度学习编译器建模

与传统编译器一样,深度学习编译器可以看作一系列 pass,每个 pass 将一个 IR 转换为另一个 IR。如前所述,IR 与最终可执行二进制文件具有一对一映射;因此 IR 可以被视为一个解。对于这种抽象,令 ff 表示从解空间到解空间的 IR 自映射:

f:S→Sf : S \rightarrow S

f(s)=s’f(s) = s’

深度学习编译器的构建器算法可以建模为一系列 pass,将一个解转换为另一个解:

B(m)=fk−1(fk−2(…(f1(f0(m)))))=fk−1∘fk−2∘…∘f0(m)B(m) = f_{k-1}(f_{k-2}(…(f_1(f_0(m))))) = f_{k-1} \circ f_{k-2} \circ … \circ f_0 (m)

B=fk−1∘fk−2∘…∘f1∘f0B = f_{k-1} \circ f_{k-2} \circ … \circ f_1 \circ f_0

构建器算法 BB 是一个函数复合,看起来像模型经过并被转换到解空间的一条路径,因此 BB 也可以称为构建路径。

请注意以下几点:

  • 严格来说,至少有一个编译器 pass(例如最终的代码生成转换)从模型空间 MM 映射到可执行解空间 SS。然而,编译器 pass 并不总是完全解耦的,并且 IR 可以被视为唯一标识由其余固定流水线生成的引擎。因此,将 pass 建模为广义解状态空间上的变换提供了一种有用的简化。
  • 有些编译器 pass 在功能上不可或缺,但不参与所研究的性能选择。此类与性能无关的 pass 可以被视为恒等映射,或从路径的可调部分中省略,因为它们不能区分不同路径。
  • 乍看之下,上述复合可能意味着一条构建路径只生成一个解(即构建出的引擎)。在实践中,TensorRT 编译流水线在生成最终引擎之前可能会考虑并过滤许多潜在策略。由于算法 AA 和 BB 紧密耦合,这一候选生成过程的很大一部分在复合中仍然是隐式的。该公式描述的是被选中的解,而不是沿途考虑过或丢弃的每个候选。

可定制构建路径

性能分析和优化的经验表明,有效的调度取决于模型。不同模型可以从各个编译器组件的不同选择中受益,即使它们运行在同一目标 GPU 上。因此,所有模型的最优解不一定来自同一条通用构建路径。

因此,构建路径应当是可定制的,允许调度决策适应模型及其部署环境。这种可调的构建器算法可以称为可定制构建路径。

例如,考虑两个模型 mm 和 m’m’,以及一组可用的构建路径:

{B1,B2,…,Bn}\{B_1, B_2, \ldots, B_n\}

这两个模型不必共享相同的最优路径。模型 mm 可能通过 B3B_3 取得最佳结果,而模型 m’m’ 可能通过 B1B_1 取得最佳结果:

A∗(B(m))=arg⁡min⁡Bi(m){T(B1(m)),T(B2(m)),…,T(Bn(m))}=B3(m)A^*(B(m)) = \arg\min_{B_i(m)} \{T(B_1(m)), T(B_2(m)), \ldots, T(B_n(m))\} = B_3(m)

A∗(B(m’))=arg⁡min⁡Bi(m’){T(B1(m’)),T(B2(m’)),…,T(Bn(m’))}=B1(m’)A^*(B(m’)) = \arg\min_{B_i(m’)} \{T(B_1(m’)), T(B_2(m’)), \ldots, T(B_n(m’))\} = B_1(m’)

Global Performance Tuner 从可定制构建路径的理念出发。对于每个候选解,它根据指定的精度标准验证输出,并测量端到端推理性能。在所有满足精度要求的解中,搜索算法从扩展后的解集中选择运行时间最短的可行解。

以下三项原则定义了该框架:

  • 精度感知调优:使用端到端精度作为约束来指导性能调优。
  • 可定制构建路径:使构建路径可定制,以便调度能够适应每个模型和部署环境。
  • 大解空间:构建丰富的可行解集,然后选择数值差异保持在指定容差内的最快候选。

Global Performance Tuner 的数学表示可以写为:

B={B1,B2,…,Bn}\mathcal{B} = \{B_1, B_2, …, B_n\}

s∗=A(B1(m)∪B2(m)∪⋯∪Bn(m))s^* = A(B_1(m) \cup B_2(m) \cup \dots \cup B_n(m))

了解更多

要了解更多信息,请查看 Global Performance Tuner 文档,并阅读本系列的 第 2 部分,其中介绍了 Global Performance Tuner 的算法和架构设计以及使用它的最佳实践。