部署经过训练的神经网络不仅仅是将每个运算符转换为 GPU 内核。这是一个编译问题,有许多合理的答案。例如,NVIDIA TensorRT 构建引擎时,它会针对图形 – IR 转换、融合、实现选择、内存放置和量化做出相互依赖的决策。此过程称为调度。不同的调度选择可以保留模型语义,但产生截然不同的推理性能,从而使调度成为深度学习编译器优化的核心。
没有哪种调度策略适合每种模型和部署环境。加速一个网络的转换可能会减慢另一个网络的速度。独立运行速度更快的内核可能会与图中其他位置的融合、张量布局或内存流量交互不佳。主动调度可能会在违反应用准确性要求的同时提高性能。这些与模型相关的组合效应通常在构建和测量整个引擎后才会出现,这使得正确的策略越来越难以预测。
本文是分为两部分的系列文章的第一部分,旨在为性能优化提供更清晰的推理方法。它探讨了以下问题:是什么让一个编译引擎比另一个更好?为什么单个有前景的决策会失败端到端?准确性如何限制性能优化?它引入了全局性能调优器 ( TensorRT 11.1 中发布的自动性能调优功能) ,并通过直观的示例和数学框架来探讨这些问题。
全局性能调优器功能解决了哪些挑战?
以下类比说明了在 TensorRT 中构建全局性能调优器功能的动机。
想象一下,一位拥有一道多年来一直令三位老顾客满意的美味佳肴的主厨。一种是香料,另一种是甜酸味,还有第三种是额外的盐。他将红薯、糖汁、柠檬和盐融合到一个标准食谱中,满足这三种需求。有一天,第四个不喜欢香料的新客户来了。保留这种做法会令新手感到失望;如果将其去掉,就有可能失去第一批顾客。
造成主厨困境的根本原因是调料添加方法没有横向扩展:交互异常的数量会随着客户群的增加而增加,而一位顾客的变化会导致另一位顾客的食物质量下降。

可扩展的方法可以保留基础菜肴,但可以配置调味料。主厨会为每位老顾客记录一个合适的组合。对于偏好未知的新客户,主厨提供不同组合的免费样品,拒绝不合适的变化,并保留客户最喜欢的变化。

通过将这种类比与深度学习编译进行类比,TensorRT 是主菜,其共享工作流是基础配方,可调整编译器配置调料,编译引擎是成品菜肴。每个 ONNX 模型都是客户。性能调优提供样本,端到端性能反映客户满意度。
过去,TensorRT 性能优化始于内部 KPI 模型。工程师研究这些工作负载,识别可优化的模式,并在 TensorRT 默认调度策略中将其结果编码为启发式算法。每个启发式算法都会捕捉专业知识,并允许用户自动从代表性模型上发现的优化中受益。
随着模型和部署环境的多样化,这种方法会导致类似于类比中描述的扩展问题。受内部 KPI 模型启发的启发式方法加入了共享策略,这可能有助于熟悉的模式,同时导致隐藏的外部模型回归。添加另一个检查或异常可以保护该模型,但重复此过程会使调度逻辑越来越难以扩展。
问题以三种相关形式出现:
- 启发式蔓延:每个新的模型特定的启发式方法都引入了另一个规则,必须理解和维护其与现有规则的交互。随着这些规则的累积,代码将变得难以维护,而针对一个模型系列的改进可能会无意中影响另一个模型系列。
- 性能准确性纠结:降低精度的内核和其他积极的优化可以提高性能,同时引入超出应用程序可接受容限的数值差异。选择更高精度可能会降低这种风险,但会牺牲性能。因此,我们无法始终独立优化性能和数字可接受性。
- 性能自定义:适合某个模型或 GPU 的调度选择可能不适用于另一个模型或 GPU。将每个成功的选择编码到一个通用调度策略中,使得 TensorRT 难以高效地专门用于不断扩展的外部模型、硬件平台和应用程序需求。
调优为启发式优化提供了可扩展的补充。
深度学习编译器性能的数学理论
本节将开发深度学习编译器性能的抽象数学模型。目的是区分两个基本问题:
- 编译器可以为模型构建哪些可执行文件
- 编译器如何从这些可能性中选择一个以获得最佳性能
概念:模型、模型空间
让 \(m\) 表示模型,\(M\) 表示正在考虑的所有模型的集合,称为模型空间:
\(m \in M\)
在这里,模型是对要编译的计算的抽象描述。其具体表示形式可以是 ONNX 图形、通过 TensorRT API 创建的网络、PyTorch 模型,或完全指定模型计算的另一个前端表示形式。
概念:解决方案、解决方案空间
将模型 \(m\) 作为输入,深度学习编译器会为目标硬件构建可执行输出,并将其定义为解决方案。相同的模型一般可以在不同的调度策略下编译成不同的解决方案。让 \(s\) 表示一个这样的解,让 \(S\) 表示可能的解的集合,称为解空间。
解决方案是对目标硬件上的可执行计算进行抽象。具体来说,解决方案可能是 TensorRT 引擎或 GPU 核函数序列化的立方体。更抽象地说,它也可能是编译器的中间表示 (IR) ,前提是该表示在剩余编译过程中唯一地确定最终可执行文件。
每个解决方案还定义了从模型输入域到其输出域的函数:
\(s : X \rightarrow Y\)
\(s(x) = y\)
在这里,\((x,y)\) 是用于实现神经网络的 GPU 内核序列的输入 – 输出对。在这种抽象层面,模型描述了预期计算,而解决方案描述了在目标硬件上实现该计算的一种特定方式。
确定性原理
声明:当确定性程序及其完整执行环境都保持不变时,程序具有固定的底层运行时间。
该原则可用作性能分析的建模假设。在严格的解释下,环境包括输入数据、目标设备、软件堆栈、GPU 频率、温度、电源状态、系统负载以及其他可能影响运行时间的执行条件。在实践中,并非每个环境变量都能得到完美控制,因此重复的运行时测量会显示噪声。如果将这种干扰建模为独立高斯噪声,则重复测量的平均值是底层平均运行时间的最大概率估计值。
执行环境固定在抽象所需的级别后,每个解决方案都可以与一个特征运行时相关联。这种关联使运行时能够表示为数学函数。
概念:运行时函数
运行时函数 \(T\) 将每个解决方案映射到固定执行环境下指定目标设备上的运行时:
\(T(s_0) = t_0\)
该环境被视为固定环境,因此会从标记中省略。
概念:构建器算法
构建器算法 (也称为性能调度器) 是从模型空间到解空间集的映射。当给定特定模型作为输入时,构建器算法将输出具有给定精度要求的解空间。
在上述准确性条件下,输入 – 输出对 \((x, y)\) 被视为参考值,\(ε\) 代表精度容限值。参考数据、比较指标和容差被视为固定上下文,因此无法使用 \(B\) 的参数。
理想情况下,构建器算法应生成足够丰富的解决方案集,以包含 \(s^*\),即最佳解决方案:建模解决方案空间中最快的可行解决方案。
概念:搜索算法
搜索算法 (也称为解空间求解器) 是从解空间的集到解空间的映射。当给定一组候选解作为输入时,搜索算法将从中选取一个解作为输出。
\(A(S) = argmin_{s_i \in \underline{S}} T(s_i), \underline{S} \subseteq S\)
请注意,在现实世界中,搜索算法可能会从解空间中选择次优解。在 TensorRT 中,这种次优性至少有两个来源:
- 搜索时使用的启发式方法:在将候选项作为完整引擎的一部分进行评估之前,启发式方法可能会删除这些候选项。例如,构建器可以根据输入模型某层中的通道维度值来过滤某些卷积策略。如果过滤后的策略会参与到最快的端到端解决方案中,那么搜索算法就无法再找到最佳解决方案。
- 本地分析和端到端执行之间的不匹配:在引擎构建期间,可能会在策略选择期间单独分析候选内核,但在推理时,性能是在整个引擎中衡量的。独立运行的内核和作为大型图形一部分运行的相同内核不会共享相同的执行环境 (例如,NVIDIA Blackwell GPU 中使用的自适应网格调整技术) 。因此,单独测量的内核运行时的总和不一定等于端到端运行时。
在理想情况下,构建器算法会生成包含 \(s^*\) 的候选集,而搜索算法会可靠地对其进行选择:
因此,深度学习编译器可以用一个算法元组 \((A, B)\) 来表示,其中 \(B\) 会生成候选解决方案,而 \(A\) 会在其中进行选择。然而,在实际实现中,候选项生成、剪枝、分析和选择可能会紧密合。因此,\(A\) 和 \(B\) 之间的边界在编译器架构中可能不明确。
\(B^*\) 表示构建器算法系列中的最优构建器算法,\(A^*\) 表示搜索算法系列中的最优搜索算法。理想的编译器将实现元组 \((A^*,B^*)\)。对于任何输入模型,此元组都会生成一个包含全局最优解的候选集,然后可靠地加以发现:
在数学抽象层面,\(A^*\) 的一种可能实现是强力算法 – 评估候选集中的每个解,并选择运行时最低的解:
\(A^*(S) = argmin_{s_i \in S} T(s_i)\)
虽然在构建时间预算有限的情况下,应用图像搜索算法并不总是可以接受的,但构建时间和推理性能之间可能需要权衡取舍。\(A^*\) 用于表示搜索算法,在给定任何输入模型的情况下,这些算法肯定会返回最优解。
构建 \(B^*\) 是更基本的挑战。即使是理想的搜索算法也无法选择构建器永远无法提供的解决方案。与其期望在单个步骤中构建 \(B^*\),不如通过扩展和改进可供编译器使用的构建路由来逐步实现近似值。
全局性能调优器建模
本节将对编译序列进行建模,然后展示可定制的构建路由如何为准确性感知型性能调优奠定基础。
深度学习编译器建模
与传统编译器一样,深度学习编译器可被视为一个通道序列,每个通道都会将一个 IR 转换为另一个 IR。如前所述,IR 具有与最终可执行二进制文件的一对一映射,因此 IR 可视为一种解决方案。对于此抽象,让 \(f\) 表示从解空间到解空间的 IR 自映射:
\(f : S \rightarrow S\)
\(f(s) = s’\)
深度学习编译器的构建器算法可建模为将一个解转换为另一个解的一系列通道:
构建器算法 \(B\) 是一种函数复合,看起来像是模型穿过并转换为解空间的路径,因此 \(B\) 也可称为构建路由。
请注意以下事项:
- 严格来说,至少有一个编译器通道 (如最终代码生成转换) 会将模型空间 \(M\) 映射到可执行解空间 \(S\)。但是,编译器通道并不总是完全解,IR 可被视为唯一标识由剩余的固定工作流生成的引擎。因此,将通道建模为广义解 – 状态空间上的转换可提供有用的简化。
- 某些编译器通道在功能上不可或缺,但不参与所研究的性能选择。这种与性能无关的通道可以被视为身份映射,也可以从路线的可调整部分中省略,因为它们无法区分一条路线与另一条路线。
- 乍一看,上面的合成图似乎意味着一个构建路线只生成一个解决方案 (即构建引擎) 。实际上,在生成最终引擎之前,TensorRT 编译管线可能会考虑并过滤许多潜在的策略。由于 \(A\) 和 \(B\) 算法是紧密合的,因此候选内容生成过程中的大部分内容仍然是隐含在合成中的。该公式描述的是选定的解决方案,而不是在此过程中被考虑或丢弃的所有候选解决方案。
可定制的构建路线
性能分析和优化方面的经验表明,有效的调度依赖于模型。不同的模型可以从各个编译器组件的不同选择中受益,即使它们在同一目标 GPU 上运行也是如此。因此,适用于所有模型的最佳解决方案不一定源自统一的构建路线。
因此,构建路线应该是可定制的,允许根据模型及其部署环境做出调度决策。这种可调整的构建算法可称为可定制的构建路线。
例如,考虑两种模型:\(m\) 和 \(m’\),以及一系列可用的构建路由:
\(\{B_1, B_2, \ldots, B_n\}\)
这两个模型无需共享相同的最优路线。模型 \(m\) 可以通过 \(B_3\) 获得最佳结果,而模型 \(m’\) 可以通过 \(B_1\) 获得最佳结果:
\(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_{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 文档 并阅读本系列的第 2 部分,其中介绍了 Global Performance Tuner 的算法和架构设计以及使用它的最佳实践。