推理优化 2026:投机解码、前缀缓存与分离式部署

推理优化 2026:投机解码、前缀缓存与分离式部署

同样一张卡,为什么别人的服务吞吐是你的 5 倍?差距不在模型,在推理引擎和优化手段。这篇讲清 2026 年主流的五种优化手段,并给 vLLM 实战部署配置。

在这里插入图片描述


一、先搞清楚:推理为什么慢

大模型推理是自回归的——一个 token 一个 token 往外蹦。每个 token 都要:

  1. 从显存加载模型权重(显存带宽瓶颈);
  2. 做矩阵运算;
  3. 读写 KV Cache。

关键认知:推理的瓶颈通常不是算力(FLOPS),而是显存带宽。你 GPU 利用率看着 100%,其实大部分时间在等数据搬运。

所以优化思路就两条:

  • 减少重复计算(前缀缓存、投机解码);
  • 提高硬件利用率(批处理、PD 分离、量化)。

二、五种优化手段

在这里插入图片描述

1. 投机解码(Speculative Decoding)

原理:

  1. 用一个小模型(草稿模型)快速生成 N 个候选 token;
  2. 大模型一次性并行验证这 N 个 token;
  3. 对的直接采纳,错的从第一个错的位置重新生成。

为什么快:大模型验证 N 个 token 的耗时 ≈ 生成 1 个 token 的耗时(并行),但小模型生成 N 个很便宜。

收益:通常 1.5-3x 加速,且输出质量完全不变(因为最终由大模型确认)。

关键前提:草稿模型要和大模型同分布(比如 Qwen2.5-0.5B 给 Qwen2.5-7B 做草稿),否则命中率低反而更慢。

2. 前缀缓存(Prefix Caching)

原理:把已计算的 KV Cache 缓存起来,下次遇到相同前缀直接复用,不做重复计算。

典型场景:

  • System Prompt 很长(几 k token),每次请求都重复计算 → 缓存后省掉;
  • 多轮对话:历史部分可复用;
  • RAG:相同的文档片段被多次检索时。

收益:首 token 延迟(TTFT)大幅下降;命中率高时吞吐提升明显。

3. 连续批处理(Continuous Batching)

传统批处理:一批请求一起进来,必须等最慢的那个完成才能放新请求进来(队头阻塞)。

连续批处理:请求逐个完成、逐个补充,谁好了谁走,新请求随时插入。

收益:吞吐 2-5x,这是 vLLM 等现代框架的核心能力。

4. 分离式部署(PD 分离)

推理分两个阶段,特性完全不同:

阶段 特点 瓶颈
Prefill(预填充) 处理输入 prompt,可并行 算力密集
Decode(解码) 逐 token 生成 显存带宽密集

混在一起跑 = 两种负载互相干扰。分离式部署把两个阶段放到不同的卡/机器上,各自用最合适的硬件和并行策略。

收益:整体成本降 30%+,是大模型服务规模化的关键架构。

5. 量化

精度 显存 速度 精度损失
FP16 基准 基准 无
FP8 减半 快 极小
INT8 减半 快 小
INT4 (GPTQ/AWQ) 1/4 快 可接受

建议:7B 模型 16G 显存跑 FP16 没问题;要跑 70B 就得量化(AWQ/GPTQ INT4)。


三、vLLM 实战部署(2026 主流)

vLLM 是目前最常用的推理引擎,天然支持连续批处理 + PagedAttention + 前缀缓存。

3.1 安装

pip install vllm

3.2 基础启动(OpenAI 兼容 API)

python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-7B-Instruct \
  --served-model-name qwen2.5 \
  --host 0.0.0.0 \
  --port 8000 \
  --gpu-memory-utilization 0.9 \
  --max-model-len 8192 \
  --enable-prefix-caching

3.3 开启投机解码

python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-7B-Instruct \
  --speculative-model Qwen/Qwen2.5-0.5B-Instruct \
  --num-speculative-tokens 5 \
  --enable-prefix-caching

--num-speculative-tokens 是每次草稿的 token 数,通常 3-5。

3.4 多卡并行

# 张量并行(单模型拆到多卡)
python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-32B-Instruct \
  --tensor-parallel-size 4

3.5 量化启动

# AWQ 量化模型
python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-7B-Instruct-AWQ \
  --quantization awq

四、调用测试

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""调用 vLLM 的 OpenAI 兼容接口"""
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed",          # vLLM 不需要 key
)

resp = client.chat.completions.create(
    model="qwen2.5",
    messages=[
        {"role": "system", "content": "你是一个助手。"},
        {"role": "user", "content": "用一句话解释什么是投机解码。"},
    ],
    temperature=0.1,
    max_tokens=200,
)
print(resp.choices[0].message.content)
print(f"\n[用量] prompt={resp.usage.prompt_tokens}, "
      f"completion={resp.usage.completion_tokens}")

输出:

投机解码是用小模型快速生成候选 token、再由大模型并行验证的加速技术,
能在不损失输出质量的前提下显著提升生成速度。

[用量] prompt=24, completion=48

五、性能压测方法

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简单压测:并发请求,测吞吐(tokens/s)与延迟
依赖:pip install openai
"""
import time
import threading
from openai import OpenAI
from concurrent.futures import ThreadPoolExecutor

client = OpenAI(base_url="http://localhost:8000/v1", api_key="x")
CONCURRENCY = 16
N_REQUESTS  = 64


def one(i):
    t0 = time.time()
    r = client.chat.completions.create(
        model="qwen2.5",
        messages=[{"role": "user",
                   "content": f"请简短介绍一下人工智能(第{i}次)。"}],
        max_tokens=150, temperature=0.7,
    )
    dt = time.time() - t0
    return dt, r.usage.completion_tokens


def main():
    t0 = time.time()
    with ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
        results = list(pool.map(one, range(N_REQUESTS)))
    total = time.time() - t0

    total_tokens = sum(t for _, t in results)
    latencies = [d for d, _ in results]
    latencies.sort()

    print(f"总请求:{N_REQUESTS},并发:{CONCURRENCY}")
    print(f"总耗时:{total:.2f}s")
    print(f"吞吐  :{total_tokens/total:.1f} tokens/s")
    print(f"QPS   :{N_REQUESTS/total:.2f}")
    print(f"平均延迟:{sum(latencies)/len(latencies):.2f}s")
    print(f"P95延迟:{latencies[int(len(latencies)*0.95)]:.2f}s")


if __name__ == "__main__":
    main()

典型输出(单卡 4090,7B):

总请求:64,并发:16
总耗时:18.42s
吞吐  :892.3 tokens/s
QPS   :3.47
平均延迟:4.51s
P95延迟:6.12s

优化前后对比(同一张卡):

配置 吞吐(tokens/s) 提升
基础(HF transformers) ~120 基准
vLLM(连续批处理) ~610 5.1x
vLLM + 前缀缓存 ~780 6.5x
vLLM + 投机解码 ~890 7.4x

六、落地建议

  1. 优先上 vLLM(或 SGLang),光是连续批处理就有数倍提升,零成本;
  2. System Prompt 固定时必开前缀缓存——收益巨大且无副作用;
  3. 投机解码要看命中率:草稿模型不合适反而会变慢,先小流量验证;
  4. 量化按需:显存够就别量化(精度最好),显存不够优先 AWQ;
  5. PD 分离是规模化方案:日活请求量大时才需要,小规模别过度设计;
  6. 监控三指标:TTFT(首 token 延迟)、TPOT(每 token 时间)、吞吐。

七、选型速查

场景 推荐方案
单机 demo / 开发 Ollama(最简单)
生产 API 服务 vLLM(连续批处理 + 前缀缓存)
极致低延迟 vLLM + 投机解码
超大规模 vLLM + PD 分离 + 多机并行
CPU / 边缘 llama.cpp + GGUF 量化

小结

  • 推理瓶颈通常是显存带宽,不是算力;
  • 五大手段:投机解码、前缀缓存、连续批处理、PD 分离、量化;
  • vLLM 是生产首选,开箱即有连续批处理(5x+);
  • 前缀缓存零成本收益大,固定 System Prompt 场景必开;
  • 压测看三指标:TTFT、TPOT、吞吐。

环境:vLLM 0.6+ / Qwen2.5-7B / NVIDIA 4090 24G

posted @ 2026-09-10 20:42  橘和柠  阅读(22)  评论(0)    收藏  举报