大模型推理基础:Transformer架构、自回归生成、KV Cache、预填充与解码阶段

要搭建企业级vLLM推理服务,首先必须深入理解大语言模型(LLM)在推理时的底层运行机制。vLLM的诸多核心优化——如PagedAttention、连续批处理(Continuous Batching)、张量并行等——都是针对Transformer解码器的计算特征与显存瓶颈设计的。如果对模型推理的基本原理缺乏清晰认知,后续的部署调优、性能诊断和容量规划都将无从下手。本章将系统拆解Transformer架构的推理视角、自回归生成机制、KV Cache的作用原理,以及预填充(Prefill)与解码(Decode)两个关键阶段的计算特征。

LLM推理核心机制总览 输入序列 "请介绍一下vLLM" 预填充阶段 (Prefill) 并行处理全部输入Token 计算并缓存KV Cache 解码阶段 (Decode) 逐Token自回归生成 读取KV Cache + 计算新Token 输出 Token流 KV Cache 显存管理 逻辑视图 连续显存块 碎片化严重 vLLM PagedAttention 分页物理块 按需分配、零碎片 自回归生成循环 Step 1: 输入全部Token Step 2: 预测下一个Token Step 3: 拼接新Token到输入 Step 4: 重复直到EOS 计算特征对比 预填充 (Prefill) 计算密集 (Compute-Bound) 高并行度,GPU利用率高 解码 (Decode) 访存密集 (Memory-Bound) 低并行度,带宽瓶颈 vLLM 核心优化 Continuous Batching PagedAttention + 抢占调度

Transformer架构的推理视角

Transformer架构自2017年提出以来,已成为大语言模型的事实标准。从推理服务的角度看,我们不需要关注训练阶段的细节,而应聚焦于其推理时的数据流与计算模式。一个标准的Decoder-only Transformer(如GPT系列、LLaMA系列)由多个相同的解码器层堆叠而成,每一层包含两个核心子模块:自注意力机制(Self-Attention)和前馈神经网络(FFN)。

在推理过程中,输入Token序列首先经过Embedding层映射为向量,然后逐层通过解码器。每一层的计算可以概括为:

  1. 自注意力计算:对输入向量分别乘以三个权重矩阵(WQ、WK、WV),得到Query、Key、Value向量。然后计算Query与所有Key的点积,经过Softmax归一化后加权求和Value,得到注意力输出。
  2. 残差连接与层归一化:注意力输出与输入相加(残差连接),再经过LayerNorm。
  3. 前馈网络:通常包含两个线性变换和一个激活函数(如GELU或SwiGLU),对每个Token位置独立计算。
  4. 再次残差连接与层归一化:FFN输出与输入相加,再经过LayerNorm。

从计算量的角度看,自注意力层的计算复杂度为O(n²·d),其中n是序列长度,d是隐藏维度;而FFN层的计算复杂度为O(n·d²)。当序列较短时(如几十个Token),FFN占主导;当序列较长时(如数千Token),自注意力的平方复杂度成为瓶颈。这直接影响我们在部署vLLM时对最大序列长度的设置——过长的序列不仅消耗大量显存,还会显著增加单次推理延迟。

关键理解:推理时的Transformer与训练时有一个本质区别——训练时所有Token的Query、Key、Value可以一次性并行计算;而推理时由于自回归特性,新Token的生成依赖于之前所有Token的Key和Value。如果每次重新计算所有历史Token的K和V,计算量将随生成长度呈平方级增长,这是完全不可接受的。KV Cache正是为了解决这一问题而引入的。

自回归生成机制

大语言模型的文本生成本质上是自回归(Autoregressive)过程:模型在每一步只预测下一个Token的概率分布,然后从分布中采样(或取最大概率)得到新Token,将其拼接到输入序列末尾,再继续预测下一个Token,直到生成结束符(EOS)或达到最大长度限制。

以输入"请介绍一下vLLM"为例,完整的生成过程如下:

步骤 输入序列 模型输出(概率分布) 采样结果
1 请介绍一下vLLM P(的)=0.35, P(是)=0.20, P(中)=0.15... 的
2 请介绍一下vLLM的 P(核心)=0.28, P(架构)=0.22, P(原理)=0.18... 核心
3 请介绍一下vLLM的核心 P(特性)=0.30, P(概念)=0.25, P(技术)=0.20... 特性
... ... ... ...
N 请介绍一下vLLM的核心特性... P(<EOS>)=0.85, P(。)=0.10... <EOS>(停止)

这种逐Token生成的方式带来了两个重要推论:

  • 推理延迟与生成长度成正比:生成100个Token所需的时间大约是生成50个Token的两倍(假设每步耗时相同)。这要求我们在SLA(服务等级协议)设计时,必须明确最大生成长度。
  • 无法并行生成:第N个Token的生成必须等待前N-1个Token全部生成完毕。这限制了单请求的吞吐量上限,也是vLLM引入连续批处理来提升整体吞吐的根本原因。
工程启示:在实际部署中,我们通常通过设置max_tokens参数来限制最大生成长度。过大的max_tokens会占用更多显存(因为需要预留KV Cache空间),过小则可能导致输出被截断。合理的做法是根据业务场景统计历史请求的Token长度分布,设置P95或P99分位值。

KV Cache:原理与显存占用

KV Cache是Transformer推理中最关键的优化技术。其核心思想是:在自回归生成过程中,对于已经计算过的Token,将其Key和Value向量缓存下来,避免重复计算。具体来说,在第t步生成时,我们只需要计算新Token的Query、Key、Value,然后用新Query与所有历史Key(包括缓存的)做注意力计算,再与所有Value加权求和。

KV Cache的显存占用可以用以下公式估算:

显存占用(字节) = 2(K和V) × 层数 × 隐藏维度 × 序列长度 × 精度字节数

以LLaMA-7B模型为例(32层,隐藏维度4096,FP16精度),每Token的KV Cache占用为:

2 × 32 × 4096 × 2字节 = 524,288字节 ≈ 0.5MB/Token

这意味着,如果并发处理100个请求,每个请求生成1024个Token,仅KV Cache就需要约50GB显存——这已经超过了单张A100(80GB)的一半。更严重的是,KV Cache的大小是动态变化的(随生成进度增长),传统深度学习框架会预先分配最大可能长度的连续显存块,导致大量碎片和浪费。

显存瓶颈警示:KV Cache是推理服务显存占用的主要部分,往往超过模型权重本身。vLLM的核心创新——PagedAttention——正是借鉴操作系统虚拟内存的分页思想,将KV Cache划分为固定大小的物理块,按需分配,消除了内部碎片和外部碎片,将显存利用率提升到接近100%。这是vLLM能够实现高吞吐的关键。

预填充与解码阶段

一次完整的推理请求被vLLM明确划分为两个阶段:预填充(Prefill)和解码(Decode)。这两个阶段的计算特征截然不同,对GPU资源的利用方式也完全不同。

预填充阶段(Prefill)

预填充阶段处理用户输入的Prompt部分。此时,模型需要并行计算所有输入Token的Key和Value,并生成第一个输出Token。由于输入Token数量通常较多(几十到几千),这一阶段的计算具有高并行度的特点,GPU的计算单元(CUDA Cores/Tensor Cores)能够被充分利用。从计算性质上看,预填充是计算密集(Compute-Bound)的。

预填充的耗时主要取决于输入Token数量和模型大小。例如,一个2048 Token的Prompt在A100上可能需要数百毫秒。对于交互式应用(如聊天机器人),用户感知的"首Token延迟"(TTFT, Time To First Token)主要由预填充阶段决定。

解码阶段(Decode)

解码阶段逐Token生成输出。每一步只处理一个Token(或一个批次中的多个Token),并行度极低。此时,GPU的计算单元大量空闲,而主要瓶颈在于从显存中读取KV Cache。每一步需要读取所有历史Token的K和V,计算注意力分数。因此,解码阶段是访存密集(Memory-Bound)的。

解码阶段的性能指标通常用吞吐量(每秒生成的Token数)来衡量。在vLLM中,通过连续批处理(Continuous Batching),将多个请求的解码步骤合并为一个批次执行,显著提升了GPU利用率。

对比维度 预填充(Prefill) 解码(Decode)
处理对象 整个Prompt序列 单个新Token
并行度 高(序列内并行) 低(仅批次内并行)
计算特征 计算密集(Compute-Bound) 访存密集(Memory-Bound)
关键指标 TTFT(首Token延迟) TPOT(每Token输出时间)/吞吐量
GPU利用率 高 低(无批处理时)
vLLM优化 Chunked Prefill(分块预填充) Continuous Batching(连续批处理)

实战场景:理解阶段特征对性能调优的指导意义

假设我们在一台配备4张A100(80GB)的GPU服务器上部署vLLM,服务一个企业级知识库问答应用。业务特征如下:

  • 平均Prompt长度:800 Token(用户问题 + 检索到的上下文文档)
  • 平均输出长度:300 Token
  • 并发请求数:峰值约50个
  • SLA要求:TTFT < 1.5秒,TPOT < 50ms(即每秒至少生成20个Token)

初始现象与问题:使用vLLM默认配置部署后,TTFT在低并发时表现良好(约300ms),但当并发达到30以上时,TTFT急剧恶化至3秒以上,且TPOT不稳定,部分请求出现明显卡顿。

分析判断过程:

  1. 首先检查GPU利用率:使用nvidia-smi观察发现,GPU利用率在50%左右波动,但显存占用接近90%。这说明瓶颈不在计算能力,而在显存容量和KV Cache管理。
  2. 计算KV Cache需求:50并发 × 1100 Token(800+300)× 0.5MB/Token ≈ 27.5GB。加上模型权重(约14GB)和激活值,单张A100的80GB显存理论上足够,但vLLM默认的KV Cache预留比例(gpu_memory_utilization=0.9)可能导致预留空间不足。
  3. 进一步分析:使用vLLM的--verbose日志发现,存在大量KV Cache的"驱逐"(Eviction)和重新计算(Recompute)操作。这是因为默认的调度策略在显存压力大时,会抢占某些请求的KV Cache空间,导致这些请求需要重新执行预填充。

实际操作步骤:

# 调整vLLM启动参数
python -m vllm.entrypoints.openai.api_server \
    --model /data/models/llama-7b \
    --tensor-parallel-size 4 \
    --gpu-memory-utilization 0.92 \
    --max-num-seqs 64 \
    --max-model-len 2048 \
    --enable-chunked-prefill \
    --chunked-prefill-size 512

关键调整说明:

  • --enable-chunked-prefill:将长Prompt拆分为512 Token的块,避免单个预填充请求独占GPU计算资源,阻塞其他请求的解码。
  • --max-num-seqs 64:限制最大并发序列数,防止过度订阅导致显存溢出。
  • --gpu-memory-utilization 0.92:略微提高显存利用率,为KV Cache预留更多空间。

结果对比:调整后,TTFT在50并发下稳定在800ms-1.2秒,TPOT稳定在35-45ms,满足SLA要求。GPU利用率提升至75%左右,吞吐量从原来的约800 Token/s提升至1500 Token/s。

风险与代价:Chunked Prefill会增加预填充的总计算时间(因为分块后注意力计算有重叠),但换取了更平滑的调度。对于TTFT极端敏感的场景(如实时语音交互),可能需要关闭Chunked Prefill并增加GPU数量。

复盘经验:理解预填充与解码的计算特征差异,是进行vLLM参数调优的前提。当出现TTFT恶化时,优先检查KV Cache是否充足;当TPOT不稳定时,优先检查是否存在请求抢占和重新计算。这些诊断思路都源于对本章基础概念的深刻理解。

常见误区与总结复盘

误区一:认为KV Cache是"缓存"所以不重要。实际上,KV Cache是推理过程中最大的显存消耗者,其管理效率直接决定了服务的并发能力和吞吐量。vLLM的PagedAttention本质上就是一种KV Cache的显存管理方案。
误区二:混淆TTFT和TPOT的优化目标。预填充阶段优化目标是降低TTFT,解码阶段优化目标是提高吞吐量。两者往往存在权衡——增加批次大小可以提升吞吐,但会延长TTFT。需要根据业务SLA明确优先级。
误区三:忽视序列长度对KV Cache的影响。在容量规划时,必须同时考虑Prompt长度和Max Tokens设置。一个常见的错误是只按平均长度规划,导致长尾请求触发显存溢出或频繁驱逐。
总结:本章的核心知识框架可以概括为"一个架构、一个机制、一个缓存、两个阶段"。Transformer架构决定了计算模式,自回归机制决定了生成方式,KV Cache解决了重复计算问题但引入了显存瓶颈,预填充与解码阶段的不同特征则指导着vLLM的调度与优化策略。掌握这些基础概念,是后续理解vLLM的PagedAttention、Continuous Batching、动态调度等高级特性的基石。