解密大模型推理:从 Transformer 到 vLLM/TGI/LMDeploy 全解析

大模型推理机制与压测实战:从 Transformer 到 vLLM/TGI/LMDeploy 全解析

本文基于实际压测数据与框架源码分析,系统梳理了大模型推理的核心机制(Transformer、注意力、因果掩码、KV-cache、PagedAttention 等),并对比主流推理框架(vLLM、TGI、LMDeploy)的底层实现与性能表现,适合用于技术分享、面试准备与工程选型。


一、Transformer 结构回顾

在这里插入图片描述
Inputs=源句
Output(shiffed right) = 目标句
Output Probabilities = 可能的下一个词

1.1 数据处理

Transformer 的输入处理是:文本 → token-id → embedding → +位置编码 → 正式进入模型。
1.分词 → 映射为 token-id
组件:Tokenizer(AutoTokenizer)
2.查表 → 得到 token embedding
组件:nn.Embedding(权重就是模型文件里的 token_embedding.weight)

1.2 编码器(Encoder)图中左边部分

  • 一次性并行处理整个源句的嵌入向量,生成每个 token 的 K/V 表示。
  • 提供给 Decoder 用于交叉注意力,用完即弃。
  • 每个 token 的 K/V 包含整句信息,因此 BERT 等纯 Encoder 模型可见全文。

1.3 解码器(Decoder)图中右边部分

  • 自回归生成,每步生成一个新 token,一边随时回头查这些k/v向量,注意力权重动态决定“该看哪”。
  • Decoder 自己的自注意力 K/V 也是每步重新算的,为了记录以生产内容,保证自回归。
  • 使用因果掩码(Causal Mask)保证只能看左边。
  • 交叉注意力使用 Encoder 提供的 K/V,Q 来自 Decoder 自身。

注:交叉注意力即Decoder里的multi-head attention。而Encoder的k/v来自masked multi-head attention,masked multi-head attention除了计算k/v外还有因果掩码一个对角矩阵保证模型只能看到左边的句子。

二、训练与推理阶段流程

2.1 训练阶段

  • 用户给的是成对文本所以模型同时看到源句与目标句
  • 目标句整体输入,使用因果掩码并行预测每个位置的下一个 token。
  • 损失只计算非 pad 部分,反向传播优化。
  • 这个时候就利用masked multi-head attention的因果掩码来保证只看到左边,防止模型偷看。Encoder照常生成每个token包含整句意义 K/V的矩阵,如:K = [K_中, K_国, K_的, K_首, K_都, K_是, k_北]

2.2 推理阶段

  • 用户只提供源句,模型自回归生成目标句。
  • Encoder 生成 memory(每个token包含整句意义 K/V的矩阵),Decoder 逐步生成 token从起始符( <s>)开始
  • 每步生成一个新 token,直到遇到结束符(</s> )或达到长度上限。
  • 用户在整个预测过程里只提供了“源句”,目标句是模型逐 token 自动生成的。这时的masked multi-head attention只负责生成自己的k/v

三、QKV 矩阵计算与注意力机制

  1. 所有 token 通过 W_q、W_k、W_v 投影为 Q、K、V 矩阵。
  2. 计算注意力分数:scores = Q @ K^T / √d (一条矩阵乘就得到 全部 scores) 在scores中 第 i 行已经包含 token-i 与 0…seq-1 所有位置的相似度。
  3. 加掩码(因果或双向)→ Softmax → 与 V 相乘。attn = softmax(scores) -> Out = attn @ V (同样一次矩阵乘,整句所有位置的加权求和一起完成)
  4. 输出为加权求和后的表示,经过残差、LayerNorm、FeedForward 等处理 才得到 Encoder 该层的最终表示,再乘 W_k / W_v 才拿到 memory K/V。

四、因果、非因果模型总结

模型类型代表模型掩码类型可见范围应用场景
Masked LMBERT双向全文可见理解类任务
Seq2SeqT5Encoder 双向,Decoder 因果源句全文,目标句左向翻译、摘要
Prefix LMT5、UniLM前缀双向,后缀因果前缀全文,后缀左向续写、填空、翻译
Causal LMGPT、LLaMA因果仅左边文本生成、对话
  1. Masked LM(纯 Encoder,BERT 系列)
    目标:学会“上下文填词”
    做法:
    输入:把一句子里 15% 的词换成 [MASK],然后让模型一次性输出这些被遮住的词。
    掩码:全向可见(没有三角),位置 i 能看到前后所有词。
    训练:并行算整句,一次性预测所有 [MASK]。
    推理:还是一次性前向,输出被遮住的词。
    用途:文本分类、信息抽取、相似度计算等“理解”任务。
  2. Seq2Seq(独立 Encoder + Decoder,原版 Transformer)
    目标:机器翻译、摘要等“一段变另一段”
    做法:
    Encoder:把整句原文读一遍,生成一组“上下文向量”。
    Decoder:自回归地生成译文,每一步都能回头去“查”Encoder 的向量(交叉注意力)。
    掩码:
    – Encoder:全向可见
    – Decoder:下三角(因果)
    训练: teacher-forcing,并行算整句目标语。
    推理:自回归,一步一生成。
  3. Prefix LM(T5、UniLM)——“把 Encoder 和 Decoder 合并到同一套参数里”
    目标:续写、填空、翻译都能干
    核心思想:
    输入拆成两段:
    – 前缀(Prefix):双向可见,像 BERT 一样随便看;
    – 后缀(Suffix):只能看左边,像 GPT 一样自回归生成。
    人为区分前缀、后缀作为训练数据区分用特殊token
    前缀(Prefix):输入里被给定的那一段
    后缀(Suffix):输入里需要模型生成的那一段
    同一堆 Transformer 层,前半段用“全向掩码”,后半段用“因果掩码”。

[Prefix | Suffix]
[p1 p2 p3 | s1 s2 s3]
掩码矩阵

    p1 p2 p3 s1 s2 s3
p1  1  1  1  0  0  0   ← p1 只看前缀
p2  1  1  1  0  0  0
p3  1  1  1  0  0  0
s1  1  1  1  1  0  0   ← s1 可看全部前缀+自己
s2  1  1  1  1  1  0
s3  1  1  1  1  1  1

前缀区域 → 全 1(双向,像 BERT)
后缀区域 → 下三角(因果,像 GPT)

训练:一次前向就能同时做“填空”+“续写”。
推理:给定前缀后,自回归生成后缀。

  1. Causal LM(纯 Decoder,GPT / Llama)
    目标:开放式文本生成
    做法:
    结构:只有 Decoder,没有 Encoder。
    掩码:严格的下三角,位置 i 永远看不到 i+1… 未来词。
    训练:Next Token Prediction,并行算整句,但用因果掩码模拟“单步生成”。
    推理:自回归,一步吐一个 token。
    注意:“让模型能看到多少上下文”决定掩码形状,掩码形状决定模型能干什么任务。

五、因果模型总结(Causal LM)

  • 只用 Transformer 的 Decoder 部分,并且用因果掩码(Causal Mask)强制“只能看左边”。
  • 用因果掩码(下三角矩阵)一次性并行算整句,但让位置 i 只能利用 0…i 的信息 → 模型学会“永远不看未来”。
  • 自回归一个一个吐 token,天然只能看已生成的左边;这正是训练时因果掩码所模拟的场景。→ 因果掩码是“能力”,自回归是“用法”;
  • 没有掩码,模型在训练时就能偷看未来,推理时即使单步生成也会失效。

六、自回归推理的两个阶段

6.1 Prefill 阶段(填充/初始化)

  • 一次性处理用户输入的所有 token。
  • 计算初始 logits,生成第一个 token。
  • 同时将所有 K/V 写入 KV-cache。

6.2 Decode 阶段(解码/生成)

  • 每步只输入一个新 token。
  • 取最新 logits → sample → 得到下一个 token。
  • 使用 KV-cache 中的历史 K/V 计算注意力。
  • 生成新 token 并更新 KV-cache,直到结束。

七、结合 QKV 机制的具体推理流程示例

1. 用户输入(Prefill 阶段)

用户 prompt:
中国的首都是

步骤操作说明
① 分词得到 token 序列[中, 国, 的, 首, 都, 是](共 6 个 token)
② 一次性前向6 个 token 全部送入模型每层 Transformer 计算 QKV,执行自注意力
③ 更新表示6 个 token 的 embedding 被刷新每个 token 获得整句加权后的新向量
④ 提取 KV将每层的 K/V 按 (layer, head, seq, dim) 保存写入 KV-cache
⑤ 取最后一位置仅用最末 token 向量lm_head → logits → softmax → 采样
⑥ 得到首个生成 token概率最高 = 序列延长为 7 个:[中, 国, 的, 首, 都, 是, 北]

2. 继续生成(Decode 阶段)

目标:生成下一个字。

子步骤动作细节
① 输入仅送 新生成的单 token "北"不再重复计算前面 6 个
② 计算 QKV现算 "北" 的 q, k, vQ 即时计算,K/V 待追加
③ 拼接历史K_cache = [K_中, ..., K_是, k_北] <br> V_cache 同理与 KV-cache 中前 6 个拼接
④ 自注意力可见 全部历史位置因 KV-cache 已存之前所有 K/V
⑤ 更新缓存"北" 的 k/v 追加写入缓存长度变为 7
⑥ 采样取 logits → softmax → 选最高概率得到 “京”
⑦ 序列更新现在序列 = [中, 国, 的, 首, 都, 是, 北, 京]8 个 token

3. 循环直到结束

  • 重复第 2 步
    • 永远只送 最新 1 个 token 进网络;
    • Q 现算,K/V 追加到缓存;
    • 注意力每次都能看见 全部历史(靠 KV-cache);
  • 终止条件:遇到 <EOS> 或达到长度上限。

八、lm_head 的作用与机制

  • 将 Transformer 最后一层隐藏向量映射为词表 logits。
  • 通常与 token embedding 共享权重,减少参数量。
  • 输出 logits 后通过 softmax 得到概率,采样生成 token。

很多模型(GPT、LLaMA)把 lm_head 权重(向量->分数->贪心->采样-> id) 与 token embedding(token embedding 矩阵就是“id → 向量”的查找表) 矩阵共享(输出层参数 = 输入层参数转置),减少参数量

多头注意力 → 残差+LayerNorm → FFN → 残差+LayerNorm → lm_head

多头注意力体现:
多头注意力内部把 hidden 切成 num_heads × head_dim,做完 Attention 再 concat 回 hidden;lm_head 拿到的已是 concat 后的完整向量,所以看不到“头”维度,只需一次矩阵乘即可


九、推理框架内部处理逻辑(以 vLLM 为例)

用户 prompt → 切成 token → 生成一个 Sequence(一句话)
每个 seq 有唯一 seq_id,状态:{WAITING, RUNNING, SWAPPED}。

伪结构

class Sequence:
    seq_id: int
    prompt_token_ids: List[int]   # 这句话的所有 token id
    generated_token_ids: List[int] # 已生成的 token id
    status: RUNNING | WAITING | SWAPPED

一共有两个队列 WAITING(等待), RUNNING(运行)。swapped 只是 running 里某些 seq 的“暂存态”即序列的 KV 页被换到 CPU,等 GPU 空位。

一次batch本轮一起 forward 的 token 总数,可以跨多个句子、跨预填充/解码阶段:
batch组成,running 里有 5 条 seq,每条再生成 1 个 token → batch = 5
同时 waiting 里拉来 1 条长 60 token 的 prompt,拆成 chunk=30 → batch += 30取决于(可用预算 = max_num_batched_tokens − 当前 running 已占 token 数)
35 个 token 来自 6 条不同句子,但一次 CUDA kernel 算完,这就叫 一个 batch。

runing的5个token的用于decoder预测下一个token的
waiting是拿新的seq做chunked-prefill(可拆块-预填充)输出 KV-block,如果所有块都处理完进入 runing。
显存不够 → 把 running 队尾 seq 的 KV 页换出到 CPU,等有空位再换回来
token 是最小调度单位 ,GPU 永不空转。


十、PagedAttention 的优势

  • 内存块动态分配,无需连续地址,避免碎片。
  • 支持乱序映射,块可以不连续。
  • 同前缀序列可共享 KV 块,节省显存 60~80%。

十一、两阶段分离(可选)(Splitting Prefill/Decode)

  • 痛点:Prefill 算整句,计算量大;Decode 只算 1 token,延迟要求高。
  • 老做法:同一实例混跑 → Prefill 一占 SM,Decode 就排队 → 抖动大
    SM = Streaming Multiprocessor(流式多处理器)≈ “GPU 核心计算单元”,一张 A100 有 108 个 SM。

vLLM可选方案:
 Prefill 实例(大算力 GPU )
   – 只负责 chunked-prefill → 输出 KV-block
   – 算完通过 NCCL/P2P 异步把块直传到 Decode 实例
 Decode 实例(低延迟 GPU—Decode 每步都要“重看”整条历史,带宽随长度线性涨;用户却坐在屏幕前等下一个字,所以延迟要求极高。)
   – 永远只跑单 token decode → 每步延迟稳定
   – 块已提前到位 → 无等待


十二、对比的来看TGI、LMDeploy

如何拆分预算粒度

框架拆分策略粒度混排方式感受
vLLMtoken 级拼图token任意长度实时拆,块地址乱序也能算公交
TGIrequest 级接力request长句才拆 chunk,先吃 chunk 再统一 decode地铁
LMDeploymicro-batch 级砖块micro-batch切成固定小块,再跟 decode 单字拼进同一次 kernel乐高

Prefill vs Decode 到底怎么混

1. vLLM:token-level 随时混排(“拼图”)

┌─本轮 CUDA kernel 输入(total_tokens=35)
│ ① decode-0  1tok
│ ② decode-1  1tok
│ ③ decode-2  1tok
│ ④ decode-3  1tok
│ ⑤ decode-4  1tok
│ ⑥ chunk-prefill  30tok(新长句前 30 token)
└──────────────────────────
→ 一次 forward 算完 35 个 token
  • 无阶段概念:只要预算有空位,长句拆任意 chunk 与 decode 单字拼在同一张矩阵。
  • 矩阵形状 (35, hidden),FlashAttention 统一 kernel 内部分头处理。

2. TGI:request-level 阶段接力(“双队列”)

Phase-1 Prefill(占预算上限 50%)

┌─ CUDA kernel-1(64 tok)──────
│chunk-0  64tok(新长句前 64 token)│
└────────────────────────────────

Phase-2 Decode(剩余预算)

┌─CUDA kernel-2(8 tok)
│decode-0  1tok
│ decode-1  1tok
│ … 8 条
└──────────────────
  • 先跑完所有 Prefill chunk,再跑所有 Decode;界限清晰。
  • 好处:Prefill 不挤 Decode 的 SM,单字延迟抖动小;代价:多一次 kernel 发射。

3. LMDeploy:micro-batch 砖块内交错(“同 kernel 内混砌”)

  • 长句先切成 固定 64-token 砖块(micro-batch),再与 decode 单字拼成同一张矩阵。
  • 线程块 ID 分区:
    • 线程块 0-3 负责 4 个 micro-batch
    • 线程块 4-11 负责 8 个 decode
  • 物理上同 kernel,逻辑分区域,无额外 kernel 发射,SM 利用率最高。

显存怎么切块、怎么映射、怎么搬

特性vLLMTGILMDeploy
块粒度4 KB ≈ 128 tok4 KB ≈ 128 tok固定 32/64/128 tok
映射方式虚拟→物理 乱序虚拟→物理 乱序物理连续 slab
换页时机同步(调度线程阻塞)同步(保守,尽量不换)异步后台线程
是否阻塞 decode✅ 会阻塞✅ 会阻塞不阻塞(异步)
量化块外部插件外部插件原生 INT4/INT8 块
地址连续性要求❌ 不要求❌ 不要求要求连续 slab

Tensor Core & FlashAttention 速写

  • Tensor Core:NVIDIA GPU 专用矩阵乘法硬件单元。
  • FlashAttention = 分块 + 重计算,把 Attention 内存访问降一个量级。
    • 标准三步:S=QK^T/√dP=softmax(S)O=PVN×N 中间矩阵,三次 HBM (显存)读写,带宽成为瓶颈.。
    • FlashAttention 沿 seq 维切 B×B 小块,在 SRAM 里在线 softmax,只算不存。

FlashAttention

  • 将 Attention 计算拆分为小块,避免存储中间矩阵。
  • 使用 GPU 片上 SRAM 完成计算,减少 HBM 访问。
  • 实现在线 Softmax,保留局部统计信息,避免重复读写。

FlashAttention-2 三级内存瀑布

内存级别大小带宽角色
HBM40 GB1.6 TB/s存 Q/K/V/O
SRAM192 KB19 TB/sB×B 子矩阵
寄存器256 B>100 TB/s累加局部 max/sum

手工调度数据搬运:计算只在 SRAM/HBM 各读一次,带宽瓶颈消失。


十三、显存管理机制对比

特性vLLMTGILMDeploy
块粒度4KB ≈ 128tok4KB ≈ 128tok固定 32/64/128tok
映射方式虚拟→物理乱序虚拟→物理乱序物理连续 slab
换页时机同步阻塞同步(保守)异步后台线程
是否阻塞 decode❌(不阻塞)
量化块支持插件支持插件支持原生 INT4/INT8
地址连续性要求✅(连续 slab)

十四、压测环境与准备

组件版本/配置说明
GPUCH20 140GB ×1
模型Llama-2-7B-chat
框架版本vLLM 0.5.1/ LMDeploy 0.4.0
压测脚本profile_throughput.py
测试接口OpenAI-compatible API
测试参数prompt=512 tokens, output=128 tokens, concurrency=10/50/100

十五、框架启动命令

vLLM

python -m vllm.entrypoints.openai.api_server \
  --model ./Llama-2-7b-chat-ms \
  --dtype float16 \
  --port 8801 \
  --max-model-len 2048 \
  --max-num-batched-tokens 16384 \
  --max-num-seqs 256

默认配置
python -m vllm.entrypoints.openai.api_server --model ./Llama-2-7b-chat-ms --dtype float16 --port 8801 --max-model-len 2048 --served-model-name Llama-2-7b-chat-ms

连续批处理(continuous batching)配置
python -m vllm.entrypoints.openai.api_server --model ./Llama-2-7b-chat-ms --dtype float16 --port 8801 --max-model-len 2048 --served-model-name Llama-2-7b-chat-ms --max-num-batched-tokens 16384 --max-num-seqs 256

参数作用你设的值
–max-num-batched-tokens一个 forward 里最多同时处理多少 token(prefill+decode 一起算)16384
–max-num-seqs同一个时刻最多允许多少条序列挤在 batch 里256
查看id   curl http://localhost:8801/v1/models
请求测试
curl http://localhost:8801/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Llama-2-7b-chat-ms",
    "messages": [{"role": "user", "content": "Hello"}],
    "max_tokens": 20
  }'

LMDeploy

lmdeploy serve api_server ./Llama-2-7b-chat-ms \
  --server-port 8803 \
  --tp 1 \
  --cache-max-entry-count 0.8

并发

lmdeploy serve api_server ./Llama-2-7b-chat-ms \ --server-name 0.0.0.0 \ --server-port 8801 \ --model-name Llama-2-7b-chat-ms \ --cache-max-entry-count 0.6 \ --max-batch-size 256 \ --tp 4
参数说明
--server-port 23333RESTful API 监听端口
--cache-max-entry-count 0.4KV-cache 占用显存比例,范围 0~1
--session-len 4096最大上下文长度
--tp 2张量并行卡数(多 GPU)
--quant-policy 1开启 KV-cache 量化

十六、压测命令

脚本:https://github.com/Wuzheyuan456/llama2-7b-alpaca-zh/tree/main/stress-testing

python profile_throughput.py \
  --backend openai-chat \
  --host localhost --port 8801 \
  --tokenizer ./Llama-2-7b-chat-ms \
  --num-prompts 1000 \
  --prompt-tokens 512 \
  --output-tokens 128 \
  --concurrency 50 \
  --csv llama7b_vllm_c50.csv

十七、压测结果汇总(Llama-2-7B, A100 80G, FP16)

框架并发成功请求总耗时 (s)总生成 tokensThroughput (tok/s)TTFT 平均 (ms)TTFT 95th (ms)Request 平均延迟 (ms)特殊配置
vLLM101000/100062.83771741228.21623.111049.14623.11默认
vLLM501000/100021.34778343647.511033.281743.261033.28默认
vLLM1001000/100018.16764354209.961723.672986.941723.67默认
vLLM101000/1000191.6476110397.161900.784113.531900.78–max-num-batched-tokens 16384 --max-num-seqs 256
vLLM501000/100059.93795831327.882867.706055.142867.70–max-num-batched-tokens 16384 --max-num-seqs 256
vLLM1001000/100036.12757012096.023386.096545.833386.09–max-num-batched-tokens 16384 --max-num-seqs 256
vLLM101000/1000198.8875698380.621972.334313.341972.33–max-num-batched-tokens 8192 --max-num-seqs 128
vLLM501000/100055.36787191422.042664.294975.632664.29–max-num-batched-tokens 8192 --max-num-seqs 128
vLLM1001000/100028.30778512750.772524.295684.572524.29–max-num-batched-tokens 8192 --max-num-seqs 128
vLLM101000/1000230.6876701332.502276.715077.382276.71–max-num-batched-tokens 4096 --max-num-seqs 64
vLLM501000/100063.89772541209.203076.166216.203076.16–max-num-batched-tokens 4096 --max-num-seqs 64
vLLM1001000/100046.53769941654.784247.456824.874247.45–max-num-batched-tokens 4096 --max-num-seqs 64
LMDeploy101000/1000339.76116774343.703388.693990.703388.69默认
LMDeploy501000/1000193.73116482601.259614.0211513.879614.02默认
LMDeploy1001000/1000178.32116409652.8117645.1223737.3117645.12默认
LMDeploy101000/1000235.25115396490.532341.883469.032341.88未注明(非默认优化)
LMDeploy501000/1000123.80116125938.036096.047719.606096.04未注明(非默认优化)
LMDeploy1001000/1000104.741163601110.9010216.0913031.2510216.09未注明(非默认优化)

vLLM 主打低延迟,LMDeploy 主打高吞吐;并发越高,两者差距越极端。


十八、结论与选型建议

场景需求推荐框架理由说明
高吞吐、离线批量推理LMDeployCUDA 优化极致,吞吐最高
低延迟、交互式对话vLLMTTFT 最小,适合实时响应
企业部署、功能丰富TGI支持量化、投机解码、日志完善

📎 附录

  • 所有测试基于公开脚本与模型,结果可复现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值