要理解 kernel,需要先理解 GPU 是怎么工作的。
假设你要在一个城市里送 1000 个快递。
方式 A: 你雇 1000 个快递员,每人负责一个包裹,每次送完一个就集合开会分配下一个。开会本身花掉大量时间,真正送货的时间反而很少。
方式 B: 你把 1000 个包裹按区域打包成 50 批,每批 20 个,一个快递员一次拿一批,规划好路线连续送完。开会的次数从 1000 次降到 50 次。
GPU 上的 kernel 就是这里的“一批送货任务”——它是一次提交给 GPU 执行的、完整的并行计算单元。而“开会”就是 kernel launch(内核启动)的开销。
在 GPU 编程里,kernel 是一个在 GPU 上并行执行的函数。你用 CUDA 或类似语言写一个函数,标记它为 kernel,然后指定用多少个线程来执行它。GPU 会把这个函数同时跑在成千上万个线程上,每个线程处理数据的一部分。
举个例子,做矩阵乘法 C = A × B:
不用 kernel 的思路(在 CPU 上):写三重循环,逐元素算 C[i][j]。
用 kernel 的思路:写一个函数 matmul_kernel,每个线程负责计算 C 中的一个元素。启动时指定 100 万个线程,GPU 的几千个核心同时开工,每个核心处理一批线程。
这个 matmul_kernel 就是一个 kernel。它是 GPU 上最小的可调度执行单位——GPU 的调度器一次只能启动一个 kernel,等它跑完(或部分跑完)才能启动下一个。
把几个概念串起来:
| 概念 | 层次 | 说明 |
|---|---|---|
| 算子(operator) | 框架层 | PyTorch 里的 torch.matmul、torch.softmax,是逻辑上的计算单元 |
| kernel | 硬件执行层 | GPU 上实际跑的函数,一次启动完成一批并行计算 |
| 推理引擎 | 系统层 | 负责把算子图优化、融合,生成或调用高效的 kernel |
一个算子可以对应多个 kernel(比如大矩阵乘会拆成多个 kernel 分块计算),一个 kernel 也可以融合多个算子。推理引擎的价值,很大程度上就体现在它能不能为你的模型和硬件生成高质量的融合 kernel。
在 PyTorch eager 模式下,模型的一层可能被拆成很多细碎的算子。比如一个 Transformer 的注意力层,可能包含:
矩阵乘 → 除以缩放因子 → 加 mask → softmax → 矩阵乘 → dropout → 残差加 → LayerNorm
每个箭头都是一次独立的 kernel launch。GPU 执行每个 kernel 的时间可能只有几微秒,但启动 kernel 的开销(CPU 端准备参数、驱动层提交、GPU 调度)往往也是几微秒。结果就是大量时间花在“开会”上,而不是“送货”。
推理引擎做的事,就是把这一长串小算子融合成一个大 kernel。比如把“矩阵乘 + 缩放 + mask + softmax”写成一个专门的 fused attention kernel,一次启动就完成全部计算。这样:
这就是 FlashAttention 的核心思想之一——它不是算法变了,而是把注意力的整个计算流程融合成一个高度优化的 kernel,避免了中间矩阵的反复读写。
推理引擎的存在,本质上是因为训练框架和部署需求之间存在一道很深的鸿沟。PyTorch、TensorFlow 这些训练框架的设计目标是灵活、可调试、支持反向传播,而线上推理的目标是低延迟、高吞吐、省显存、跨硬件。这两套目标几乎是对立的,所以需要一层专门的系统来桥接。
用 PyTorch 直接做推理,问题不在“能不能跑”,而在“跑得有多浪费”。
计算图是动态的。 PyTorch 默认 eager 模式,每行代码立即执行,算子逐个启动。推理时这意味着一堆 Python 解释器开销和 kernel launch 开销,GPU 利用率可能只有 30%—50%。推理引擎会把模型先编译成静态图,做算子融合、常量折叠、内存复用,把几百个小算子合并成几十个大 kernel。
没有针对推理的优化。 训练框架的算子是为通用性写的,不会专门为某个形状、某种精度做极致调优。推理引擎会针对具体硬件写定制 kernel,比如 NVIDIA 的 TensorRT 会为每种层类型生成高度优化的实现,FlashAttention、PagedAttention 这类技术也都是在推理引擎层面落地的。
显存管理粗放。 训练时显存分配可以容忍碎片和冗余,推理时每一 MB 都直接影响能跑多长的上下文、多大的 batch。推理引擎会做显存池化、KV Cache 分页管理、权重常驻显存等精细控制。
不支持量化推理。 训练框架原生跑 INT4/INT8 权重效率很低,推理引擎才真正把量化 kernel 做到可用——GPTQ、AWQ 的加速效果,很大一部分来自推理引擎对低精度矩阵乘的专门实现。
可以把它理解为一个“翻译层 + 优化器 + 调度器”的组合。
图优化层。 把训练框架导出的模型(ONNX、TorchScript 等)转成内部表示,然后做算子融合(如 Conv+BN+ReLU 合成一个)、死代码消除、布局转换。这一步通常能带来 20%—40% 的纯计算加速。
算子与 kernel 层。 针对目标硬件提供高度优化的算子库。同一套模型在 TensorRT、vLLM、llama.cpp 上的速度差异,很大程度来自这一层的实现质量。比如 vLLM 的 PagedAttention 把 KV Cache 按页管理,显存浪费从 60%—80% 降到 4% 以下。
调度与批处理层。 这是推理引擎和“单纯算子优化”最不一样的地方。线上请求是动态到达的,引擎需要做 continuous batching——不等一个 batch 跑完就插入新请求,让 GPU 始终满载。还有请求优先级、超时控制、多模型共享 GPU 等调度逻辑。
因为没有一款引擎能同时满足所有场景,不同引擎在不同维度上做了取舍。
| 引擎 | 主战场 | 核心优势 | 典型取舍 |
|---|---|---|---|
| TensorRT | NVIDIA GPU 生产部署 | 极致 kernel 优化、低延迟 | 闭源、绑定 NVIDIA、编译慢 |
| vLLM | 云端 LLM 服务 | PagedAttention、高吞吐 | 偏重 LLM,通用模型支持弱 |
| llama.cpp / GGUF | 本地、CPU、边缘 | 跨平台、CPU 友好、单文件 | GPU 吞吐不如专用引擎 |
| ONNX Runtime | 通用模型跨平台 | 生态广、支持多后端 | LLM 优化不如 vLLM |
| Triton Inference Server | 多模型、多框架服务 | 调度、模型编排 | 本身不是 kernel 优化引擎 |
| ExLlama | GPTQ/AWQ 桌面推理 | 消费级 GPU 上快 | 生态窄,主要围绕量化 LLM |
这些引擎的差异,本质上是优化目标不同:TensorRT 追求单请求最低延迟,vLLM 追求整体吞吐,llama.cpp 追求“什么机器都能跑”,ONNX Runtime 追求“什么模型都能跑”。
同一个 Llama 3 8B 模型:
模型没变,变的是推理引擎对计算、显存、调度的组织方式。
评论专区
评论加载中...登录后即可发表评论