Skip to main content
Version: Next

推理与部署:让模型高效服役

训练是算力的消耗战,推理是延迟、吞吐、成本的三体问题。本篇讲推理链路的核心优化:KV Cache、采样策略、量化、连续批处理与生产部署。

一、自回归推理为什么贵​

生成第 t 个 token 需要一次完整 forward,而注意力要访问之前所有 token 的 K、V。朴素实现每步重算全部历史 → O(T²) 计算。

KV Cache:空间换时间​

把每步算出的 K、V 缓存起来,新 token 只需算自己的 Q/K/V,再与缓存做注意力:

无缓存:每步 O(T²),总 O(T³)
有缓存:每步 O(T), 总 O(T²)
# 带 KV Cache 的注意力(推理模式)
def forward_infer(self, x, kv_cache):
q, k, v = self.qkv(x).chunk(3, -1)
if kv_cache is not None: # 拼接历史
k = torch.cat([kv_cache["k"], k], dim=2)
v = torch.cat([kv_cache["v"], v], dim=2)
kv_cache = {"k": k, "v": v}
out = F.scaled_dot_product_attention(q, k, v) # 无需 causal mask(q 只有新 token)
...

显存账:KV Cache 大小 = 2( K和V ) × n_layer × n_kv_head × d_head × T × 2字节。

7B 模型(32 层、GQA 8 kv 头、d_head=128)、32k 上下文:2×32×8×128×32768×2B ≈ 4.3GB——每个并发请求一份,这就是高并发下显存的真正瓶颈,也是 GQA/MQA 和 PagedAttention 存在的理由。

二、采样策略:控制生成​

logits 是分布,怎么从分布取词决定了输出风格。

温度(Temperature)​

pᵢ = softmax(zᵢ / T)
T < 1:分布变尖锐 → 保守、确定 T > 1:变平坦 → 多样、发散

代码/事实任务用 T=00.3;创意写作用 0.71.0。

Top-k / Top-p(核采样)​

  • Top-k:只从概率最高的 k 个 token 里采样,其余概率归零。
  • Top-p:按概率从大到小累加,截断在累计 ≥ p 处(动态 k)。主流默认 top_p=0.9。
def sample(logits, temperature=0.8, top_p=0.9):
logits = logits / temperature
sorted_logits, idx = torch.sort(logits, descending=True)
cum = torch.cumsum(F.softmax(sorted_logits, -1), -1)
sorted_logits[cum > top_p] = float("-inf")
probs = F.softmax(sorted_logits, -1)
return idx.gather(-1, torch.multinomial(probs, 1))

重复惩罚与确定性​

  • repetition_penalty(如 1.1):对出现过的 token 的 logit 除以惩罚系数,治复读机。
  • Greedy / Beam Search:翻译等确定性任务可用,开放对话下 beam 反而更无聊,已少用。
  • 投机采样(Speculative Decoding):小模型先草拟 k 个 token,大模型一次前向并行验证,接受前缀——无损加速 2-3 倍,是当前最重要的推理加速技术之一。

三、量化:更小的显存与带宽​

推理瓶颈往往是显存带宽(每步要读全部权重),量化直接减少字节数:

精度显存(7B)说明
FP16/BF16~14GB训练与高精度推理标准
INT8 (W8A16)~7GB权重 8bit,激活 16bit,几乎无损
INT4 (GPTQ/AWQ)~4GB分组量化,质量损失 1-2% PPL,单卡 24GB 可跑 70B 的 Q4
FP8~7GBH100 硬件原生,训练推理两用,DeepSeek 系采用
  • GPTQ:训练后逐层二阶信息补偿量化误差;
  • AWQ:保护"重要权重"(激活幅度大的 1%)不量化;
  • GGUF/llama.cpp:CPU/边缘侧推理的事实标准。

四、服务化:vLLM 与连续批处理​

单请求推理 GPU 利用率极低。生产系统靠两个技术把吞吐拉高一个数量级:

  1. 连续批处理(Continuous Batching):不等整批结束,某条请求一完成立刻插入新请求,GPU 永不空转。
  2. PagedAttention:KV Cache 像操作系统分页一样按块分配,消除显存碎片,支持前缀共享(同一 system prompt 只存一份)。
# 用 vLLM 起一个 OpenAI 兼容服务
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-7B-Instruct \
--gpu-memory-utilization 0.9 \
--max-model-len 32768
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
print(client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": "用一句话解释注意力机制"}]
).choices[0].message.content)

五、容量规划速查​

显存需求 ≈ 模型权重 + Σ(每并发 KV Cache) + 激活与碎片余量(~10%)

例:7B-BF16、32k 上下文、目标 32 并发: 权重 14GB + 32 × 4.3GB ≈ 152GB → 需要 2×A100-80G,或改用 FP8/GQA 更激进的配置。

延迟指标盯两个:TTFT(首 token 时间,受 prefill 算力限制)与 TPOT(每 token 间隔,受带宽限制)。长 prompt 场景优化 TTFT(前缀缓存、分块 prefill),长生成场景优化 TPOT(量化、投机采样)。

系列回顾​

至此你走过了完整链路:

数学 → 分词器 → Transformer → 手写 mini-LLM
→ 数据集工程 → 预训练工程 → SFT/DPO 对齐 → 推理部署

接下来推荐:把 mini-LLM 的数据换成自己领域语料重训一遍,再用 vLLM 把开源 7B 模型跑起来做一次 SFT + DPO 全流程——动手一遍,胜过十遍阅读。