外观
TensorRT 与 GPU 推理
一、概念定义:把"通用图"打磨成"GPU 上的极速图"
TensorRT 是 NVIDIA 自 2015 年起推出的 GPU 推理优化引擎(inference accelerator)。它的输入是 PyTorch / TensorFlow / MXNet 训练出来的"通用计算图",输出是一份绑定特定 GPU 架构、经过层层优化的"Plan 文件"——你可以把它理解为"为这张卡、这个 batch、这个精度编译出来的本地可执行文件"。
PyTorch 模型 (.pt)
│ tracing / scripting
▼
ONNX / TorchScript 中间表示
│ TensorRT Builder(图优化 + kernel 自动调优 + 量化)
▼
Plan 文件 (.plan/.engine) ← 绑定 GPU 架构 + 精度 + shape 范围
│ TensorRT Runtime
▼
GPU 上跑推理TensorRT 与 PyTorch 的关键差异在于"编译 vs 解释":PyTorch 默认是 eager 模式(一行行解释执行),TensorRT 是 AOT(ahead-of-time)编译,构建期把整张图看一遍、合并算子、挑最快 kernel、写死最优执行计划。在 ResNet-50 / BERT 这类经典模型上,相对 PyTorch eager 通常能拿到 2–5× 的延迟降低;在 LLM 上结合 TensorRT-LLM(见 TensorRT-LLM)能进一步突破。
要理解 TensorRT 的位置,把它放到推理栈里看(详见 推理路径全景 与 GPU 优化原理):上层是训练框架,下层是 CUDA Driver,TensorRT 在中间做"框架无关的 GPU 推理编译器"。
二、核心机制:Builder → Engine → Runtime
TensorRT 的工作流分三个阶段,理解这三个阶段就理解了 TensorRT 的全部心智模型。
1. Builder:编译期优化的"作坊"
Builder 接收网络定义,做四件事:
| 优化手段 | 做什么 | 典型收益 |
|---|---|---|
| 图优化 | 算子融合(Conv+BN+ReLU、Linear+ReLU)、常量折叠、死代码消除 | 减少访存、减少 kernel launch 开销 |
| Kernel auto-tuning | 对每个算子从 kernel 库中挑若干候选,在目标 GPU 上实测选最优 | 同一算子不同 batch/shape 选不同实现 |
| 精度校准 | FP32 → FP16 / INT8 / FP8,配 calibration 或量化感知 | 2–4× 吞吐 |
| Workspace 规划 | 给每个算子分配临时显存 workspace | 拿空间换速度 |
原始图: MatMul → Bias → ReLU → MatMul → Bias → ReLU
优化后: [FusedLinearRelu] → [FusedLinearRelu] (两次 kernel launch)2. Engine:编译产物
Builder 输出的二进制叫 Engine(序列化后是 .plan / .engine 文件)。Engine 与 GPU 架构绑定——在 A100 上 build 的 engine 不能直接拿到 H100 上跑(除非用 --useUnifiedCapture 或重新 build)。这一点是初学者最常踩的坑:CI 构建机和部署机不同型号时,必须在部署机重新 build,或者用容器统一镜像。
Engine 不可跨架构迁移
不要把本地 RTX 4090 上 build 的 plan 直接推到 A100 服务器跑。常见做法:
- 在与生产同型号的卡上 build,把 plan 文件随镜像发布;
- 用
trtexec --saveEngine在部署机上 build; - 容器内 build 后随镜像分发(推荐,但要求镜像里的 driver/CUDA 版本对齐)。
3. Runtime:执行期
Runtime 加载 Engine,创建 ExecutionContext,绑定输入输出 buffer,调用 enqueueV2 / execute_v3 执行推理。Context 是有状态的对象——同一 engine 可以创建多个 context 并行跑(受 GPU SM 资源约束),这是 Triton 中"concurrent model execution"的底层基础(见 Triton 推理服务)。
python
import tensorrt as trt
import pycuda.driver as cuda # 或 cuda-python
runtime = trt.Runtime(trt.Logger())
with open("resnet50.engine", "rb") as f:
engine = runtime.deserialize_cuda_engine(f.read())
context = engine.create_execution_context()
# 分配输入输出 device buffer、绑定时需要 name → buffer 地址三、INT8 量化与 Calibration
INT8 是 TensorRT 的招牌能力之一。详见 量化 与 权重-激活混合精度,这里聚焦 TensorRT 的实现路径。
PTQ 路径:Entropy Calibration
TensorRT 用 IInt8EntropyCalibrator2 做 PTQ(post-training quantization):
- 准备几百张代表输入(calibration dataset);
- Builder 用 FP32 跑这些输入,收集每一层激活的分布;
- 对每层选一组
(scale, zero_point),使得量化前后 KL 散度最小(hence "entropy"); - 把 scale 写入 engine,推理时按 INT8 计算。
python
import tensorrt as trt
# 自定义 calibrator
class MyCalibrator(trt.IInt8EntropyCalibrator2):
def __init__(self, data_loader, cache_file="int8.cache"):
super().__init__()
self.data_loader = data_loader
self.cache_file = cache_file
self.batch_iter = iter(data_loader)
self.device_input = cuda.mem_alloc(...) # 按 batch 大小分配
def get_batch(self, names):
try:
batch = next(self.batch_iter)
except StopIteration:
return None
cuda.memcpy_htod(self.device_input, batch.numpy())
return [int(self.device_input)]
def read_calibration_cache(self):
if os.path.exists(self.cache_file):
with open(self.cache_file, "rb") as f:
return f.read()
def write_calibration_cache(self, cache):
with open(self.cache_file, "wb") as f:
f.write(cache)
builder.int8_calibrator = MyCalibrator(...)
builder.config.set_flag(trt.BuilderFlag.INT8)QAT 路径:从 PyTorch 量化感知训练导入
如果 PTQ 掉点严重(<1% 也算严重),就走 QAT:在 PyTorch / pytorch-quantization toolkit 里训练带伪量化算子的模型,导出 ONNX 时带 QuantizeLinear/DequantizeLinear 节点,TensorRT 8.x+ 会识别并直接生成 INT8 engine。QAT 通常精度比 PTQ 高 0.3–1 个点,但训练成本高。
精度阶梯
| 精度 | 显存 | 吞吐 | 精度损失 | 何时用 |
|---|---|---|---|---|
| FP32 | 1× | 1× | 0 | 验证基线 |
| FP16 | 0.5× | ~2× | <0.1% | 几乎总是开 |
| INT8 (PTQ) | 0.25× | 3–4× | 0.3–1% | 检测/分类主流 |
| INT8 (QAT) | 0.25× | 3–4× | <0.3% | 精度敏感场景 |
| FP8 (H100+) | 0.25× | 4–6× | <0.5% | 大模型新宠 |
四、Plugin 接口:自定义算子的逃生通道
模型里有 TensorRT 不认识的算子怎么办?两条路:
- 改写模型:用等价的、TensorRT 支持的算子组合替代(推荐,可维护性好)。
- 写 Plugin:实现
IPluginV2/IPluginV2DynamicExt/IPluginV3接口,注册到注册表,TensorRT build 时按名字解析。
cpp
// 简化的 Plugin 接口
class CustomLayerPlugin : public nvinfer1::IPluginV2DynamicExt {
const char* getPluginType() const noexcept override { return "CustomLayer"; }
int enqueue(...) noexcept override {
// 调用自定义 CUDA kernel
custom_layer_cuda_forward(...);
return 0;
}
// ... clone / serialize / getOutputDimensions 等
};
REGISTER_TENSORRT_PLUGIN(CustomLayerPlugin);Plugin 的存在让 TensorRT 既能"优化标准算子",又能"逃出去做不标准的事"——这是它兼容性兜底的最后一道防线。代价是 Plugin 算子不参与 graph optimization(无法与相邻算子融合),性能通常低于原生算子。
Plugin 的取舍
能用标准算子组合表达就别写 Plugin。维护成本高、build 链路复杂、跨版本 API 不稳定(V2 → V2DynamicExt → V3 经历过若干 breaking change)。但有些自定义 attention / 投影层确实只能靠 Plugin,掌握它仍然是工程必备。
五、与 PyTorch 的关系:torch_tensorrt
直接走 ONNX → TensorRT 的链路繁琐:导出 ONNX、检查 opset 支持度、调 plugin。torch_tensorrt 是 NVIDIA 推出的"直连"方案——直接吃 torch.nn.Module 或 torch.export 图,内部用 Dynamo/TorchScript 编译到 TensorRT,未覆盖的算子回退到 PyTorch eager(hybrid graph)。
python
import torch_tensorrt
model = model.eval().cuda()
inputs = [torch.randn(1, 3, 224, 224).cuda()]
trt_model = torch_tensorrt.compile(
model,
ir="torch_compile", # 新链路,基于 Dynamo
inputs=inputs,
enabled_precisions={torch.float, torch.half}, # 开 FP16
workspace_size=1 << 30,
min_block_size=5, # 子图最小节点数,小于这个回退 eager
)
# 之后 trt_model 跟原模型一样调用
out = trt_model(inputs[0])torch_tensorrt 1.x 起以 ir="torch_compile" 为推荐路径,与 PyTorch 2.x torch.compile 生态对齐——这是 2024 年后 PyTorch 推理加速的事实标准。详见 图优化原理。
六、性能数据:基线参考
下面给一组基线数字,仅作"量级感"参考,实测请用你自己的模型 + 卡(基准方法论见 基准测试):
| 模型 | 硬件 | 精度 | 延迟(batch=1) | 相对 PyTorch eager |
|---|---|---|---|---|
| ResNet-50 | A100 80G | FP16 | ~0.4 ms | 3–4× |
| BERT-base | A100 80G | FP16 | ~1.0 ms | 3× |
| ResNet-50 | T4 | INT8 | ~1.0 ms | 5–6× |
| YOLOv8-m | A100 | FP16 | ~2.5 ms | 3× |
| ViT-B/16 | A100 | FP16 | ~2.0 ms | 3–4× |
基线数字的陷阱
任何"TensorRT 比 PyTorch 快 X 倍"的数字都受以下因素影响:
- PyTorch 基线是否开 AMP /
torch.compile:开torch.compile后差距通常缩到 1.3–1.8×; - batch size:大 batch 下差距收窄(瓶颈转向显存带宽);
- shape 是否动态:动态 shape 下 TensorRT 优化空间被压缩;
- 是否预热:第一次推理含 CUDA 上下文初始化,必须排除。
七、局限与边界
TensorRT 不是万能的。它的"边界"来自设计取舍:
- GPU 架构绑定:Engine 不可跨架构迁移,CI/CD 流水线必须按目标卡 build;FPGA、AMD GPU、Apple Silicon 完全不支持——这种场景考虑 ONNX Runtime 或 OpenVINO。
- 动态 shape 麻烦:支持但要预声明
OptProfile(min/opt/max),超出范围会报错或性能骤降。LLM 这种 seq_len 高度动态的场景,TensorRT-LLM 用 "Paged KV Cache + In-Flight Batching" 解决(见 TensorRT-LLM)。 - 算子覆盖不全:模型带新算子或非标准结构(如某些 attention 变体)时容易踩"unsupported op",要么写 Plugin、要么改模型。
- 构建时间长:Builder 的 kernel auto-tuning 很贵,大模型 build 一次几小时到几十小时。CI 必须用 plan 缓存。
- 闭源:开源替代(TVM、MLC、TensorRT 的开源部分
ONNX Parser)成熟度仍有差距。 - 学习曲线高:API 在 7.x/8.x/8.6/10.x 间多次 break,文档与版本对应关系复杂。
八、与同类方案的关系
| 方案 | 定位 | 关系 |
|---|---|---|
| ONNX Runtime | 跨平台通用引擎(见 ONNX Runtime) | ORT 有 TensorRT Execution Provider——ORT 是上层,TensorRT 是 backend 之一 |
| OpenVINO | Intel CPU/iGPU 优化(见 OpenVINO) | 同样是"图优化 + 量化"思路,但目标硬件不同 |
| torch.compile / inductor | PyTorch 原生 AOT 编译 | 与 TensorRT 形成"原生 vs 旗舰"两条路,torch_tensorrt 把两者打通 |
| TensorRT-LLM | LLM 专用扩展 | 在 TensorRT 之上做 PagedAttention、In-Flight Batching、continuous batching 等 LLM 优化(见 TensorRT-LLM) |
| vLLM / SGLang | LLM 推理引擎(见 vLLM) | 互补而非替代——TensorRT-LLM 在 NVIDIA 栈里对位 vLLM |
九、可继续追踪
- 概念页:图优化原理、算子融合、量化、权重-激活混合精度、GPU 优化原理、延迟与吞吐
- 案例页:ONNX Runtime、OpenVINO、TensorRT-LLM、Triton 推理服务、分布式推理
- 实践页:引擎对比、调优实践、基准测试、避坑指南
- 论文与硬件:核心论文、硬件入门
参考资料
- NVIDIA. TensorRT Developer Guide — 官方文档(按版本对齐)
- NVIDIA. TensorRT Sample Plugins — 开源仓库(含 parser、plugin 示例)
- NVIDIA. torch_tensorrt — PyTorch 官方集成
- NVIDIA. trtexec — 命令行 build/bench 工具
- Migacz. 8-bit Inference with TensorRT(GTC 2017) — INT8 calibrator 经典讲义
- NVIDIA. FP8 Quantization with TensorRT(2023) — H100 上 FP8 实践