3、TensorRT-LLM初探:架构解析、核心组件(Builder、Runtime、Plugin)

好,咱们正式开始聊 TensorRT-LLM。说实话,我第一次接触这个框架的时候,第一反应是「这玩意儿跟 TensorRT 到底啥关系?」后来用了一段时间才摸清楚门道。今天我就带你把它拆开看看,到底里面装了些什么。

3.1 整体架构:一张图看懂

先别急着看代码,咱们先搞清楚 TensorRT-LLM 的「骨架」。我个人习惯把它的架构分成三层:

  • 前端层:负责模型定义、权重加载、图构建。说白了就是把你手里的 PyTorch 模型「翻译」成 TensorRT 能理解的东西。
  • 中间层:核心引擎,包括 Builder(构建器)和 Runtime(运行时)。Builder 负责优化和编译,Runtime 负责跑起来。
  • 后端层:Plugin(插件)系统,用来处理那些标准算子搞不定的「特殊操作」,比如 FlashAttention、PageAttention 这些。

下面这张图是我自己画的,你可以对照着看:

前端层:模型定义 & 图构建 PyTorch 模型 → 权重加载 → 图优化 → TRT-LLM 中间表示 中间层:核心引擎 Builder(构建/优化/编译) Runtime(加载/执行/推理) 后端层:Plugin 插件系统 FlashAttention · PageAttention · 自定义算子

嗯,这里要注意:这三层不是严格串行的。实际开发中,你经常需要在前端和后端之间来回调整,比如发现某个算子性能不好,就得写个 Plugin 来替换它。

3.2 Builder:模型的「锻造师」

Builder 是 TensorRT-LLM 里我最喜欢的组件。为什么?因为它把一堆「脏活累活」全包了。你想想看,一个 70B 的模型,几十亿参数,要在 GPU 上跑得又快又省显存,这中间有多少优化要做?

Builder 主要干这几件事:

  • 图优化:合并算子、消除冗余、常量折叠。我记得有一次,一个简单的 LayerNorm 被 Builder 优化后,速度提升了 30%。
  • 精度校准
  • 内核自动调优:Builder 会尝试不同的 CUDA kernel 实现,选最快的那个。这个过程叫「tactic selection」,说白了就是「哪个快用哪个」。

核心概念:Engine

Builder 的输出是一个 .engine 文件。这个文件包含了优化后的模型结构、权重、以及 CUDA kernel 的二进制代码。说白了,它就是 TensorRT-LLM 的「可执行文件」。

下面是一个典型的 Builder 配置示例:

from tensorrt_llm import Builder
from tensorrt_llm.models import LLaMAForCausalLM

# 加载模型
model = LLaMAForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")

# 创建 Builder
builder = Builder()
builder_config = builder.create_builder_config(
    precision="float16",          # 精度
    max_batch_size=8,             # 最大 batch
    max_input_len=2048,           # 最大输入长度
    max_output_len=512,           # 最大输出长度
)

# 构建引擎
engine = builder.build_engine(model, builder_config)
engine.save("llama2_7b_fp16.engine")

避坑指南

我曾经在构建引擎时把 max_batch_size 设得太大(比如 64),结果显存直接爆了。建议你先用小 batch 测试,确认没问题再往上加。

3.3 Runtime:模型的「驾驶员」

Builder 造好了引擎,Runtime 负责让它跑起来。Runtime 的核心职责就两个:加载引擎、执行推理。

Runtime 的工作流程其实很简单:

  1. 加载 .engine 文件到显存
  2. 创建推理上下文(Context)
  3. 绑定输入输出张量
  4. 执行推理
  5. 获取结果

代码示例:

from tensorrt_llm import Runtime

# 加载引擎
runtime = Runtime()
engine = runtime.deserialize_engine("llama2_7b_fp16.engine")

# 创建上下文
context = engine.create_execution_context()

# 准备输入
input_ids = ...  # shape: [1, seq_len]
input_lengths = ...  # shape: [1]

# 绑定张量
context.set_input_shape("input_ids", input_ids.shape)
context.set_tensor_address("input_ids", input_ids.data_ptr())

# 执行推理
context.execute_async()

# 获取输出
output_ids = context.get_tensor("output_ids")

注意

Runtime 是线程安全的,但 Context 不是。如果你要用多线程推理,记得每个线程创建自己的 Context。

3.4 Plugin:模型的「特种兵」

Plugin 是 TensorRT-LLM 最灵活的部分。标准算子搞不定的操作,比如 FlashAttention、PageAttention、GQA(Grouped Query Attention),都需要 Plugin 来实现。

我刚开始用 TensorRT-LLM 时,遇到一个自定义的激活函数,标准算子不支持。当时我硬着头皮写了一个 Plugin,结果发现其实没那么难。Plugin 的核心就三个接口:

接口 作用 说明
getPluginType() 返回插件类型 用于唯一标识这个 Plugin
enqueue() 执行计算 这里是 CUDA kernel 的入口
getOutputDimensions() 计算输出维度 告诉 Builder 输出张量长什么样

举个例子,FlashAttention 的 Plugin 大概长这样:

class FlashAttentionPlugin : public nvinfer1::IPluginV2DynamicExt {
public:
    const char* getPluginType() const noexcept override {
        return "FlashAttention";
    }

    int32_t enqueue(
        const nvinfer1::PluginTensorDesc* inputDesc,
        const nvinfer1::PluginTensorDesc* outputDesc,
        const void* const* inputs, void* const* outputs,
        void* workspace, cudaStream_t stream) noexcept override {
        
        // 调用 FlashAttention CUDA kernel
        flash_attn_kernel(
            inputs[0],  // Q
            inputs[1],  // K
            inputs[2],  // V
            outputs[0], // O
            ...,
            stream
        );
        return 0;
    }
};

我的经验

写 Plugin 时,最容易踩的坑是维度计算。我曾经因为 getOutputDimensions() 返回的维度不对,导致 Builder 报错「shape mismatch」。建议你在写 Plugin 之前,先用 PyTorch 把计算逻辑跑通,确认输入输出维度没问题。

3.5 三者如何协作?

Builder、Runtime、Plugin 三者之间的关系,我用一个实际场景来说明:

假设你要部署一个 LLaMA 模型,用了 GQA(Grouped Query Attention)。

  1. Builder 阶段:Builder 读取模型,发现 GQA 这个操作标准算子不支持。于是它去 Plugin 注册表里找,找到了 GQA 的 Plugin。
  2. 优化阶段:Builder 把 GQA Plugin 嵌入到计算图中,然后进行全局优化。比如把连续的矩阵乘法和 GQA 融合成一个 kernel。
  3. Runtime 阶段:推理时,Runtime 遇到 GQA 操作,直接调用 Plugin 的 enqueue() 方法执行。

说白了,Builder 是「设计师」,Runtime 是「执行者」,Plugin 是「特种工具」。三者缺一不可。

小技巧

如果你不确定某个操作有没有现成的 Plugin,可以查看 TensorRT-LLM 的 tensorrt_llm/plugins/ 目录。里面列出了所有官方支持的 Plugin。

好了,这一章的内容就到这里。TensorRT-LLM 的架构其实不复杂,关键是理解 Builder、Runtime、Plugin 各自扮演的角色。下一章我们会深入 Builder 的配置细节,到时候再聊。


专注资料整理

专注资料整理