跳转至

03 - 推理服务部署

⚠️ 时效性说明(2026-03-27):本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。本轮已收紧 Prompt Caching 计费表述并更新 Anthropic 示例型号。

📌 定位说明:本章侧重部署架构与推理框架深度对比。 - 📖 应用开发者快速部署指南请参考 LLM 应用/11-大模型部署 - 📖 MLOps 全链路工程化部署请参考 MLOps 与 AI 工程化/02-模型部署与服务化

学习目标:掌握大模型推理服务的部署技术,包括 vLLM 、 TGI 、量化部署和 API 服务封装。


目录

  1. 推理部署概述
  2. vLLM 部署实战
  3. Text Generation Inference (TGI)
  4. SGLang 部署实战
  5. TensorRT-LLM 部署实战
  6. 量化部署
  7. API 服务封装
  8. 性能优化与监控
  9. KV Cache 缓存体系与缓存命中率优化

推理部署概述

1.1 推理 vs 训练

Text Only
训练阶段 vs 推理阶段

训练阶段:
├── 目标:更新模型参数
├── 计算:前向 + 反向传播
├── 内存:存储参数、梯度、优化器状态
├── 批处理:大batch,追求吞吐
└── 精度:FP32/BF16/FP16

推理阶段:
├── 目标:生成预测结果
├── 计算:仅前向传播
├── 内存:只需模型参数 + KV Cache
├── 批处理:动态batch,追求延迟
└── 精度:可量化到INT8/INT4

推理优化的核心目标:
├── 低延迟(Latency):快速响应
├── 高吞吐(Throughput):处理更多请求
├── 低成本(Cost):减少资源消耗
└── 高可用(Availability):稳定服务

1.2 推理架构选择

Text Only
┌─────────────────────────────────────────────────────────────────┐
│                     推理部署架构选择                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 本地部署(Local Deployment)                                  │
│  ├── 适用:开发测试、个人使用                                     │
│  ├── 工具:Transformers、llama.cpp                               │
│  └── 特点:简单易用,资源有限                                     │
│                                                                  │
│  2. 服务器部署(Server Deployment)                               │
│  ├── 适用:生产环境、API服务                                      │
│  ├── 工具:vLLM、TGI、TensorRT-LLM                               │
│  └── 特点:高性能,支持并发                                       │
│                                                                  │
│  3. 云端部署(Cloud Deployment)                                  │
│  ├── 适用:弹性伸缩、大规模服务                                   │
│  ├── 平台:AWS SageMaker、Azure ML、GCP Vertex AI                │
│  └── 特点:托管服务,自动扩缩容                                   │
│                                                                  │
│  4. 边缘部署(Edge Deployment)                                   │
│  ├── 适用:移动设备、嵌入式系统                                   │
│  ├── 工具:MLC-LLM、Qualcomm AI Stack                            │
│  └── 特点:极致量化,低功耗                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

vLLM 部署实战

2.1 vLLM 核心特性

Text Only
vLLM核心优势:

1. PagedAttention
   ├── 将KV Cache分页管理
   ├── 减少内存碎片
   └── 提高内存利用率

2. 连续批处理(Continuous Batching)
   ├── 动态添加新请求
   ├── 请求完成后立即释放资源
   └── 提高吞吐率

3. 量化支持
   ├── GPTQ、AWQ、SqueezeLLM
   └── 降低内存占用

4. 张量并行
   ├── 多卡推理
   └── 支持大模型

2.2 vLLM 安装与基础使用

Bash
# 安装vLLM
pip install vllm

# 对于CUDA 11.8
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu118

# 对于CUDA 12.1
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu121
Python
# 基础推理示例
from vllm import LLM, SamplingParams

# 加载模型
llm = LLM(
    model="meta-llama/Llama-2-7b-hf",
    tensor_parallel_size=1,  # 单卡
    gpu_memory_utilization=0.9,  # GPU内存使用率
)

# 设置采样参数
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=256,
)

# 生成
prompts = [
    "The future of AI is",
    "In the beginning,",
    "Once upon a time,",
]

outputs = llm.generate(prompts, sampling_params)

# 打印结果
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}")
    print(f"Generated: {generated_text!r}")
    print("-" * 50)

2.3 vLLM 高级配置

Python
from vllm import LLM, SamplingParams

class VLLMDeployment:
    """
    vLLM高级部署配置
    """

    def __init__(self, model_path, config=None):
        self.config = config or {}

        # 初始化LLM
        self.llm = LLM(
            model=model_path,

            # 并行配置
            tensor_parallel_size=self.config.get('tensor_parallel_size', 1),
            pipeline_parallel_size=self.config.get('pipeline_parallel_size', 1),

            # 内存配置
            gpu_memory_utilization=self.config.get('gpu_memory_utilization', 0.9),
            max_num_seqs=self.config.get('max_num_seqs', 256),
            max_model_len=self.config.get('max_model_len', 4096),

            # 量化配置
            quantization=self.config.get('quantization', None),
            # 可选: 'awq', 'gptq', 'squeezellm'

            # 其他配置
            dtype=self.config.get('dtype', 'auto'),
            # 可选: 'float16', 'bfloat16', 'float32'

            trust_remote_code=self.config.get('trust_remote_code', True),
        )

    def generate(self, prompts, **sampling_kwargs):
        """
        批量生成
        """
        sampling_params = SamplingParams(
            temperature=sampling_kwargs.get('temperature', 0.7),
            top_p=sampling_kwargs.get('top_p', 0.9),
            top_k=sampling_kwargs.get('top_k', -1),
            max_tokens=sampling_kwargs.get('max_tokens', 256),
            presence_penalty=sampling_kwargs.get('presence_penalty', 0.0),
            frequency_penalty=sampling_kwargs.get('frequency_penalty', 0.0),
            stop=sampling_kwargs.get('stop', None),
        )

        outputs = self.llm.generate(prompts, sampling_params)

        # 格式化输出
        results = []
        for output in outputs:
            results.append({
                'prompt': output.prompt,
                'text': output.outputs[0].text,
                'tokens': len(output.outputs[0].token_ids),
            })

        return results

    def chat(self, messages, **kwargs):  # **kwargs收集关键字参数
        """
        对话模式
        """
        # 构建prompt
        prompt = self._build_chat_prompt(messages)

        # 生成
        result = self.generate([prompt], **kwargs)

        return result[0]['text']

    def _build_chat_prompt(self, messages):
        """
        构建对话prompt
        """
        # 使用模型特定的chat template
        if hasattr(self.llm.get_tokenizer(), 'apply_chat_template'):  # hasattr检查对象是否有某属性
            prompt = self.llm.get_tokenizer().apply_chat_template(
                messages,
                tokenize=False,
                add_generation_prompt=True
            )
        else:
            # 简单的对话格式
            prompt = ""
            for msg in messages:
                role = msg['role']
                content = msg['content']
                prompt += f"{role}: {content}\n"
            prompt += "assistant:"

        return prompt

# 配置示例
VLLM_CONFIG = {
    'tensor_parallel_size': 2,  # 2卡并行
    'gpu_memory_utilization': 0.85,
    'max_num_seqs': 128,
    'max_model_len': 8192,
    'quantization': None,
    'dtype': 'bfloat16',
}

# 使用示例
deployment = VLLMDeployment("meta-llama/Llama-2-7b-hf", VLLM_CONFIG)

# 批量生成
results = deployment.generate(
    ["Hello, how are you?", "What is machine learning?"],
    temperature=0.7,
    max_tokens=100
)

# 对话模式
response = deployment.chat([
    {'role': 'user', 'content': 'Explain quantum computing'}
])

2.4 vLLM 服务部署

Python
# 启动vLLM OpenAI兼容API服务
# 命令行方式(vLLM 0.6+推荐使用 vllm serve)
"""
vllm serve meta-llama/Llama-2-7b-hf \
    --tensor-parallel-size 2 \
    --gpu-memory-utilization 0.85 \
    --max-num-seqs 256 \
    --port 8000

# 旧版本也可使用:
# python -m vllm.entrypoints.openai.api_server \
#     --model meta-llama/Llama-2-7b-hf ...
"""

# 使用Python自定义服务
import uvicorn
from fastapi import FastAPI
from vllm import LLM, SamplingParams

app = FastAPI()

# 全局模型实例
llm_engine = None

@app.on_event("startup")
async def startup_event():  # async def定义协程函数
    global llm_engine
    llm_engine = LLM(
        model="meta-llama/Llama-2-7b-hf",
        tensor_parallel_size=2,
    )

@app.post("/v1/completions")
async def create_completion(request: CompletionRequest):
    """
    OpenAI兼容的completions API
    """
    sampling_params = SamplingParams(
        temperature=request.temperature,
        max_tokens=request.max_tokens,
        top_p=request.top_p,
    )

    outputs = llm_engine.generate(request.prompt, sampling_params)

    return {
        "id": "cmpl-" + str(uuid.uuid4()),
        "object": "text_completion",
        "created": int(time.time()),
        "model": request.model,
        "choices": [
            {
                "text": output.outputs[0].text,
                "index": i,
                "logprobs": None,
                "finish_reason": "stop"
            }
            for i, output in enumerate(outputs)  # enumerate同时获取索引和元素
        ]
    }

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest):
    """
    OpenAI兼容的chat completions API
    """
    # 构建prompt
    prompt = build_chat_prompt(request.messages)

    sampling_params = SamplingParams(
        temperature=request.temperature,
        max_tokens=request.max_tokens,
    )

    outputs = llm_engine.generate(prompt, sampling_params)

    return {
        "id": "chatcmpl-" + str(uuid.uuid4()),
        "object": "chat.completion",
        "created": int(time.time()),
        "model": request.model,
        "choices": [
            {
                "index": i,
                "message": {
                    "role": "assistant",
                    "content": output.outputs[0].text
                },
                "finish_reason": "stop"
            }
            for i, output in enumerate(outputs)
        ]
    }

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Text Generation Inference (TGI)

3.1 TGI 简介

Text Only
TGI (Text Generation Inference)
├── 开发者:Hugging Face
├── 特点:
│   ├── 生产级推理服务器
│   ├── 支持连续批处理
│   ├── 支持流式生成
│   ├── 支持Safetensors格式
│   └── 支持量化(GPTQ、AWQ)
└── 适用:生产环境部署

3.2 TGI 安装与使用

Bash
# Docker方式安装(推荐)
docker run --gpus all --shm-size 1g -p 8080:80 \
    -v $(pwd)/data:/data \
    ghcr.io/huggingface/text-generation-inference:1.4 \
    --model-id meta-llama/Llama-2-7b-hf \
    --num-shard 2 \
    --quantize bitsandbytes

# 本地安装
pip install text-generation
Python
# Python客户端
from text_generation import Client

client = Client("http://localhost:8080")

# 文本生成
text = client.generate(
    "The future of AI is",
    max_new_tokens=100,
    temperature=0.7,
    top_p=0.9,
).generated_text

print(text)

# 流式生成
for response in client.generate_stream(
    "Explain machine learning:",
    max_new_tokens=100
):
    print(response.token.text, end="", flush=True)

3.3 TGI 高级配置

Bash
# 启动参数说明
docker run --gpus all --shm-size 1g -p 8080:80 \
    -v $(pwd)/data:/data \
    ghcr.io/huggingface/text-generation-inference:latest \
    --model-id meta-llama/Llama-2-7b-hf \
    --revision main \
    --sharded true \
    --num-shard 2 \
    --quantize bitsandbytes-nf4 \
    --max-input-length 4096 \
    --max-total-tokens 8192 \
    --max-batch-prefill-tokens 16384 \
    --max-batch-total-tokens 32768

# 参数说明:
# --model-id: 模型ID或本地路径
# --sharded: 是否使用模型分片
# --num-shard: 分片数量(GPU数量)
# --quantize: 量化方式 (bitsandbytes, bitsandbytes-nf4, bitsandbytes-fp4, gptq, awq, eetq)
# --max-input-length: 最大输入长度
# --max-total-tokens: 最大总token数(输入+输出)
# --max-batch-prefill-tokens: 预填充阶段最大batch token数
# --max-batch-total-tokens: 生成阶段最大batch token数

3.4 TGI 与 vLLM 对比

特性 TGI vLLM
PagedAttention
连续批处理
量化支持 GPTQ, AWQ, BnB GPTQ, AWQ, SqueezeLLM
张量并行
流水线并行
流式输出
OpenAI API 部分兼容 ✅ 完整兼容
生产就绪 ✅ 更成熟 ✅ 高性能
易用性 中等

SGLang 部署实战

4.1 SGLang 简介

Text Only
SGLang(Structured Generation Language)
├── 开发者:UC Berkeley LMSYS 团队
├── 定位:高性能 LLM 推理框架
├── 核心创新:
│   ├── RadixAttention:前缀 KV Cache 自动共享
│   ├── xgrammar:结构化输出加速(JSON 速度快 10 倍)
│   ├── 零开销批处理调度器(v0.4)
│   └── 缓存感知负载均衡器(v0.4)
├── 性能优势:
│   ├── DeepSeek MLA 优化(7 倍加速)
│   ├── Torch.compile 深度集成(1.5 倍加速)
│   └── 共享前缀场景吞吐量提升 2-5 倍
└── 适用:Agent 多轮对话、结构化输出、高并发部署

4.2 SGLang 安装与基础使用

Bash
# 方法1:pip 安装(推荐)
pip install --upgrade pip
pip install "sglang[all]"
pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/

# 方法2:从源码安装
git clone -b v0.4.0 https://github.com/sgl-project/sglang.git
cd sglang
pip install -e "python[all]"

# 方法3:Docker 部署
docker run --gpus all -p 30000:30000 -v /models:/models lmsysorg/sglang
Python
# 基础推理示例
import sglang as sgl

# 方式1:离线批量推理
llm = sgl.Engine(model_path="Qwen/Qwen2.5-7B-Instruct")

prompts = [
    "解释什么是大模型推理优化",
    "介绍 KV Cache 的作用"
]
outputs = llm.generate(
    prompts,
    sampling_params={"temperature": 0.7, "max_new_tokens": 256}
)
for out in outputs:
    print(out["text"])

llm.shutdown()
Python
# 方式2:启动 OpenAI 兼容服务器
# 终端执行:
# python -m sglang.launch_server --model Qwen/Qwen2.5-7B-Instruct --port 8000

# 客户端调用
import openai
client = openai.OpenAI(base_url="http://localhost:8000/v1", api_key="none")

response = client.chat.completions.create(
    model="Qwen/Qwen2.5-7B-Instruct",
    messages=[{"role": "user", "content": "用JSON格式输出三个中国城市的信息"}],
    response_format={"type": "json_object"},
)
print(response.choices[0].message.content)

4.3 SGLang 高级配置与优化

Python
from sglang import Engine

class SGLangDeployment:
    """SGLang 高级部署配置"""

    def __init__(self, model_path, config=None):
        self.config = config or {}

        # 初始化 Engine
        self.llm = Engine(
            model_path=model_path,

            # 并行配置
            tensor_parallel_size=self.config.get('tensor_parallel_size', 1),
            pipeline_parallel_size=self.config.get('pipeline_parallel_size', 1),

            # 内存配置
            mem_fraction_static=self.config.get('mem_fraction_static', 0.9),
            max_running_requests=self.config.get('max_running_requests', 256),
            max_total_num_tokens=self.config.get('max_total_num_tokens', 4096),

            # 优化配置
            enable_torch_compile=self.config.get('enable_torch_compile', True),
            enable_mla=self.config.get('enable_mla', False),  # DeepSeek MLA 优化

            # 量化配置
            quantization=self.config.get('quantization', None),
            # 可选: 'fp8', 'fp8_kv_cache', 'awq', 'gptq'

            # 其他配置
            dtype=self.config.get('dtype', 'auto'),
            trust_remote_code=True,
        )

    def generate(self, prompts, **sampling_kwargs):
        """批量生成"""
        outputs = self.llm.generate(prompts, sampling_params=sampling_kwargs)
        return [out["text"] for out in outputs]

    def shutdown(self):
        """关闭引擎"""
        self.llm.shutdown()


# 使用示例
deployment = SGLangDeployment(
    model_path="meta-llama/Llama-3-8B-Instruct",
    config={
        "tensor_parallel_size": 2,
        "enable_torch_compile": True,
        "quantization": "fp8",
    }
)

results = deployment.generate(
    prompts=["Hello, how are you?"],
    temperature=0.7,
    max_new_tokens=256
)
print(results[0])

4.4 SGLang vs vLLM vs TGI 对比

特性 SGLang vLLM TGI
核心创新 RadixAttention + xgrammar PagedAttention Hugging Face 优化
KV Cache 管理 Radix Tree 前缀共享 分页内存管理 分页内存管理
结构化输出 原生 xgrammar(快 10 倍) Outlines 集成 基础支持
DeepSeek 优化 MLA 7 倍加速 一般 一般
编译优化 Torch.compile(1.5 倍) 有限 有限
多模态支持 LLaVA-OneVision 可用 可用
量化支持 FP8/FP4/INT4/AWQ/GPTQ FP8/AWQ/INT4 GPTQ/AWQ/BnB
社区生态 快速增长(15K+ Stars) 最成熟 Hugging Face 生态
适用场景 Agent、结构化输出 通用部署 Hugging Face 模型

选型建议

Text Only
选择 SGLang:
  ✅ Agent 多轮对话(大量共享 System Prompt)
  ✅ JSON/结构化输出约束(xgrammar 加速)
  ✅ DeepSeek 部署(MLA 7 倍加速)
  ✅ 追求极致吞吐量

选择 vLLM:
  ✅ 通用生产部署(生态最成熟)
  ✅ 需要广泛的模型兼容性
  ✅ 团队已有 vLLM 经验

选择 TGI:
  ✅ Hugging Face 模型部署
  ✅ 需要官方优化支持
  ✅ 快速原型验证

TensorRT-LLM 部署实战

5.1 TensorRT-LLM 核心特性与架构

Text Only
TensorRT-LLM v0.10.0(2026年3月发布)核心架构:

┌─────────────────────────────────────────────────────────────────────┐
│                        TensorRT-LLM 架构                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐             │
│  │  Python API │    │  C++ Runtime│    │  Triton     │             │
│  │  (高层封装)  │    │  (高性能)   │    │  Backend    │             │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘             │
│         │                  │                  │                     │
│         └──────────────────┼──────────────────┘                     │
│                            ▼                                        │
│              ┌─────────────────────────┐                            │
│              │    TensorRT Engine       │                            │
│              │  ┌───────────────────┐  │                            │
│              │  │  Attention Kernel │  │                            │
│              │  │  (Flash Attention)│  │                            │
│              │  ├───────────────────┤  │                            │
│              │  │  Fused MLP Layer │  │                            │
│              │  ├───────────────────┤  │                            │
│              │  │  Tensor Parallel │  │                            │
│              │  ├───────────────────┤  │                            │
│              │  │  Paged KV Cache  │  │                            │
│              │  └───────────────────┘  │                            │
│              └─────────────────────────┘                            │
│                            │                                        │
│                            ▼                                        │
│              ┌─────────────────────────┐                            │
│              │   NVIDIA GPU (CUDA)     │                            │
│              │   A100 / H100 / H200   │                            │
│              └─────────────────────────┘                            │
└─────────────────────────────────────────────────────────────────────┘

核心特性:
1. 自定义 Attention 内核(Flash Attention 2.0)
2. 实时批处理(Dynamic Batch Scheduling)
3. 分页 KV Cache(Paged KV Cache Management)
4. 张量并行(Tensor Parallelism up to 16 GPUs)
5. 流水线并行(Pipeline Parallelism)
6. Weight Streaming(v0.10.0 新特性)
7. Weight Stripping(v0.10.0 新特性)
8. Executor API 支持(v0.10.0 新特性)
9. Fused MLP 支持(v0.10.0 新特性)

5.2 TensorRT-LLM v0.10.0 新特性详解

Text Only
v0.10.0 主要更新(2026年3月19日发布):

1. Python 高层 API 增强
   ├── embedding parallel(嵌入并行)
   ├── embedding sharing(嵌入共享)
   └── fused MLP support(融合MLP层)

2. Executor API
   ├── 简化的推理执行接口
   ├── 自动批处理优化
   └── 内存管理自动化

3. Weight Stripping(权重剥离)
   ├── trtllm-refit 命令行工具
   ├── 减少引擎体积
   └── 优化加载速度
   参考:examples/sample_weight_stripping/README.md

4. Weight Streaming(权重流式加载)
   ├── 大模型内存优化
   ├── 按需加载权重
   └── 支持超大规模模型
   参考:docs/source/advanced/weight-streaming.md

5. 性能优化
   ├── Hopper 架构深度优化
   ├── Blackwell 架构支持
   └── INT8/FP8 量化增强

5.3 TensorRT-LLM 安装与环境配置

Bash
# 方法1:使用 Docker 镜像(推荐)
# 拉取官方预构建镜像
docker pull nvcr.io/nvidia/tensorrtllm:24.03-py3

# 运行容器
docker run --gpus all --shm-size 1g -p 8000:8000 \
    --runtime nvidia --name trtllm \
    -v ${PWD}:/workspace \
    -it nvcr.io/nvidia/tensorrtllm:24.03-py3

# 方法2:从源码编译
git clone https://github.com/NVIDIA/TensorRT-LLM.git
cd TensorRT-LLM
git checkout tags/v0.10.0 -b release/0.10.0
git submodule update --init --recursive
git lfs install
git lfs pull

# 编译 Docker 镜像
make -C docker release_build
make release_run

# 方法3:pip 安装(Python 接口)
pip install tensorrtllm

# 验证安装
python -c "import tensorrt_llm; print(tensorrt_llm.__version__)"

5.4 TensorRT-LLM Python API 实战

Python
# TensorRT-LLM Python 高层 API 示例
from tensorrt_llm import LLM, BatchTokenizer
from tensorrt_llm.runtime import ModelConfig, SamplingConfig

# 方式1:使用高层 API(推荐)
llm = LLM(
    model_dir="/models/llama-7b",
    model_type="llama",
    tensor_parallel_size=2,  # 多卡并行
    dtype="float16",
)

# 批量推理
tokenizer = BatchTokenizer(model_dir="/models/llama-7b")

prompts = [
    "TensorRT-LLM is",
    "The future of AI is",
    "Large language models",
]

# 编码输入
input_ids = tokenizer.encode_batch(prompts)

# 执行推理
outputs = llm.generate(
    input_ids,
    max_new_tokens=100,
    temperature=0.7,
    top_p=0.9,
)

# 解码输出
for output in outputs:
    print(tokenizer.decode(output))
Python
# 方式2:使用底层 API 进行自定义优化
from tensorrt_llm.builder import Builder
from tensorrt_llm.network import net
from tensorrt_llm.layers import AttentionLayer, MlpLayer

# 定义模型配置
model_config = ModelConfig(
    vocab_size=32000,
    hidden_size=4096,
    num_layers=32,
    num_heads=32,
    max_seq_len=4096,
    tensor_parallel_size=2,
)

# 构建 TensorRT 网络
with net(network_name="llama-7b"):
    # 嵌入层
    embedding = EmbeddingLayer(vocab_size=32000, hidden_size=4096)

    # Transformer 层
    for layer_idx in range(32):
        # 自注意力层
        attention = AttentionLayer(
            hidden_size=4096,
            num_heads=32,
            q_scaling=1.0,
        )

        # FFN 层(融合 MLP)
        ffn = MlpLayer(
            hidden_size=4096,
            ffn_hidden_size=11008,
            dtype="float16",
        )

    # 输出层
    lm_head = LinearLayer(vocab_size=32000, dtype="float16")

# 编译 TensorRT Engine
builder = Builder()
engine = builder.build_engine(net, model_config)
engine.save("llama-7b.engine")

5.5 TensorRT-LLM C++ API 实战

C++
// TensorRT-LLM C++ 运行时示例
#include <tensorrt_llm/runtime.h>
#include <tensorrt_llm/models/llama.h>

using namespace tensorrt_llm::runtime;

// 初始化 C++ 运行时
void init_llm() {
    // 加载 TensorRT Engine
    auto engine = Engine::load("llama-7b.engine");

    // 创建 Session
    auto session = engine->createSession();

    // 配置采样参数
    SamplingParams params;
    params.temperature = 0.7f;
    params.top_p = 0.9f;
    params.max_new_tokens = 100;
    params.beam_width = 1;

    // 准备输入
    std::vector<int32_t> input_ids = {1, 2, 3, 4, 5};  // tokenized prompt

    // 执行推理
    auto output = session->forward(input_ids, params);

    // 获取结果
    std::vector<int32_t> generated = output->output_ids;
}

// 多卡并行推理
void init_multigpu() {
    // 张量并行配置
    TensorParallelConfig tp_config;
    tp_config.tp_size = 4;  // 4 卡并行
    tp_config.tp_rank = 0;

    // 初始化 NCCL 通信
    NcclConfig nccl;
    nccl.tp_size = 4;
    nccl.tp_rank = 0;

    // 加载模型
    auto model = LlamaModel::load("llama-7b", tp_config);
    auto engine = model->compile();
}
C++
// TensorRT-LLM C++ 高级用法:自定义 Attention Kernel
#include <tensorrt_llm/kernels/attention.h>

// 注册自定义 Attention 实现
class CustomAttention : public IAttention {
public:
    void forward(
        const Tensor& query,
        const Tensor& key,
        const Tensor& value,
        const Tensor& mask,
        Tensor* output,
        const AttentionParams& params) override {

        // 调用 Flash Attention 2.0 实现
        tensorrt_llm::kernels::flash_attention::invoke(
            query.data_ptr<float>(),
            key.data_ptr<float>(),
            value.data_ptr<float>(),
            output->data_ptr<float>(),
            params.batch_size,
            params.num_heads,
            params.head_dim,
            params.seq_len,
            params.causal,
            params.scale
        );
    }
};

// 注册自定义 kernel
auto registry = KernelRegistry::get_instance();
registry->register_attention("custom_flash", std::make_shared<CustomAttention>());
C++
// TensorRT-LLM C++ 推理服务器示例
#include <tensorrt_llm/runtime.h>
#include <triton_server.h>

class TritonLLMServer {
private:
    std::unique_ptr<tensorrt_llm::runtime::Engine> engine_;
    std::unique_ptr<tensorrt_llm::runtime::Session> session_;

public:
    void initialize(const std::string& engine_path) {
        engine_ = tensorrt_llm::runtime::Engine::load(engine_path);
        session_ = engine_->createSession();
    }

    std::vector<int32_t> generate(
        const std::vector<int32_t>& input_ids,
        const SamplingParams& params) {

        auto output = session_->forward(input_ids, params);
        return output->output_ids;
    }
};

5.6 TensorRT-LLM 模型构建与优化

Python
# TensorRT-LLM 模型构建完整流程
from tensorrt_llm.builder import Builder
from tensorrt_llm.models import LLaMAModel
from tensorrt_llm.network import net

# 1. 下载并转换模型
# 使用 HuggingFace 模型
huggingface_model = "meta-llama/Llama-2-7b-hf"

# 转换模型格式
# python tensorrt_llm/models/convert.py \
#     --model_dir ${hf_model_dir} \
#     --output_dir ./llama-7b-trt \
#     --dtype float16 \
#     --tp_size 2

# 2. 构建 TensorRT Engine
from tensorrt_llm.builder import Builder

builder = Builder()

# 创建网络配置
network_config = {
    'model_name': 'llama',
    'vocab_size': 32000,
    'hidden_size': 4096,
    'num_layers': 32,
    'num_heads': 32,
    'num_kv_heads': 32,  # GQA 支持
    'hidden_act': 'silu',
    'max_seq_len': 4096,
    'dtype': 'float16',
}

# 构建引擎
engine = builder.build(
    network=net,
    config=network_config,
    optimization_level=3,  # 最高优化级别
)

# 保存引擎
engine.save('llama-7b-fp16.engine')

# 3. 量化引擎构建(INT8/FP8)
from tensorrt_llm.quantization import QuantMode

quant_config = {
    'quant_mode': QuantMode.INT8_WEIGHT_ONLY,  # INT8 权重量化
    'kv_cache_quant_mode': QuantMode.INT8,
}

quantized_engine = builder.build(
    network=net,
    config=quant_config,
    optimization_level=3,
)

quantized_engine.save('llama-7b-int8.engine')

5.7 TensorRT-LLM vs vLLM 性能对比

Text Only
性能对比基准测试(基于 A100 80GB):

┌────────────────────────────────────────────────────────────────────────┐
│                     TensorRT-LLM vs vLLM 性能对比                       │
├──────────────────┬──────────────────────┬──────────────────────┤
│      指标         │    TensorRT-LLM      │       vLLM           │
├──────────────────┼──────────────────────┼──────────────────────┤
│ 首次生成延迟      │      更低            │        中等          │
│ 吞吐量            │      最高            │        高            │
│ 内存效率          │      高              │        高            │
│ 多卡并行          │      优秀            │        良好          │
│ 易用性            │      中等            │        优秀          │
│ 动态序列长度      │      支持            │        支持          │
│ PagedAttention   │      ✅              │        ✅            │
│ Continuous Batch │      ✅              │        ✅            │
│ INT8/INT4 量化   │      ✅              │        ✅            │
│ 生产成熟度        │      高              │        高            │
└──────────────────┴──────────────────────┴──────────────────────┘

场景选择建议:

选择 TensorRT-LLM:
  ✅ NVIDIA H100/A100 部署
  ✅ 追求极致性能
  ✅ 大规模生产环境
  ✅ 需要 C++ 集成
  ✅ 超大规模模型(70B+)

选择 vLLM:
  ✅ 快速原型验证
  ✅ 多硬件支持(AMD/Intel)
  ✅ Python 优先
  ✅ 动态序列长度场景
  ✅ 共享前缀场景

5.8 Triton Inference Server 集成部署

Bash
# 使用 Triton 部署 TensorRT-LLM 模型

# 1. 准备模型仓库
mkdir -p /model_repo/tensorrt-llm
cp llama-7b.engine /model_repo/tensorrt-llm/1/

# 2. 配置 config.pbtxt
cat > /model_repo/tensorrt-llm/config.pbtxt << 'EOF'
name: "tensorrt-llm"
platform: "tensorrtllm"
max_batch_size: 8

instance_group [
    {
        kind: KIND_GPU
        count: 2
    }
]

parameters {
    key: "precision"
    value: { string_value: "float16" }
}

parameters {
    key: "max_beam_width"
    value: { string_value: "1" }
}
EOF

# 3. 启动 Triton 服务器
docker run --gpus all --shm-size 1g -p 8000:8000 \
    -v /model_repo:/model_repo \
    nvcr.io/nvidia/tritonserver:24.03-trtllm-py3 \
    tritonserver --model-repository=/model_repo

# 4. 客户端调用
python << 'EOF'
import tritonclient.http as client
import numpy as np

# 创建客户端
triton_client = client.InferenceServerClient(
    url="localhost:8000",
    verbose=False
)

# 准备输入
inputs = [
    client.InferInput("input_ids", [1, 10], "int32"),
]
inputs[0].data = np.array([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]])

# 设置输出
outputs = [
    client.InferRequestedOutput("output_ids"),
]

# 执行推理
results = triton_client.infer(
    model_name="tensorrt-llm",
    inputs=inputs,
    outputs=outputs,
)

print(results.as_numpy("output_ids"))
EOF

5.9 常见问题与优化技巧

Text Only
常见问题:

Q1: TensorRT-LLM 编译时间过长?
   解决方案:
   - 使用预编译引擎(官方提供的模型)
   - 减少 optimization_level(0-3)
   - 使用 weight streaming 减少内存占用

Q2: 多卡并行失败(NCCL 错误)?
   解决方案:
   - 检查 GPU NVLink 连接
   - 验证 NCCL 版本兼容性
   - 使用单机多卡而非多机

Q3: INT8 量化精度下降?
   解决方案:
   - 使用 INT8 + FP16 KV Cache
   - 尝试 FP8 量化(Hopper 架构)
   - 校准数据集覆盖主要场景

Q4: 动态 Batch 性能不佳?
   解决方案:
   - 调整 max_batch_size
   - 使用连续批处理(Continuous Batching)
   - 优化预填充/解码比例

优化技巧:

1. 内存优化
   - 启用 weight streaming(大模型)
   - 使用 paged KV cache
   - 调整 max_seq_len 避免浪费

2. 性能优化
   - 启用 Flash Attention 2.0
   - 使用 Tensor Parallelism
   - 选择合适的 dtype(FP16/BF16/FP8)

3. 吞吐量优化
   - 启用连续批处理
   - 调整 batch size 参数
   - 使用 KV Cache 复用

5.10 TensorRT-LLM 生产部署检查清单

Text Only
生产部署检查清单:

□ 环境准备
  □ NVIDIA driver >= 535
  □ CUDA >= 12.1
  □ TensorRT >= 9.0
  □ cuBLAS, cuDNN, NCCL 已安装

□ 模型准备
  □ 模型格式转换完成
  □ 引擎编译完成
  □ 量化验证精度损失可接受

□ 性能验证
  □ 延迟满足 SLA 要求
  □ 吞吐量满足 QPS 要求
  □ 内存使用在预期范围内

□ 监控告警
  □ GPU 利用率监控
  □ 推理延迟监控
  □ 错误率监控
  □ 资源使用告警

□ 高可用配置
  □ 多副本部署
  □ 负载均衡配置
  □ 故障自动恢复
  □ 滚动更新策略

量化部署

6.1 量化方法对比

Text Only
量化方法对比
═══════════════════════════════════════════════════════════════════

方法              精度    压缩比    性能损失    适用场景
─────────────────────────────────────────────────────────────────
FP16             16bit   2x        无          通用
BF16             16bit   2x        无          Ampere+ GPU
INT8             8bit    4x        <1%         通用
INT4 (GPTQ)      4bit    8x        1-3%        本地部署
INT4 (AWQ)       4bit    8x        <1%         高质量需求
NF4 (QLoRA)      4bit    8x        1-2%        微调+推理
FP4              4bit    8x        2-4%        实验性

═══════════════════════════════════════════════════════════════════

6.2 GPTQ 量化部署

Python
# 使用AutoGPTQ进行量化
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

# 量化配置
quantize_config = BaseQuantizeConfig(
    bits=4,  # 4-bit量化
    group_size=128,  # 分组大小
    desc_act=False,  # 是否降序激活
)

# 加载并量化模型
model = AutoGPTQForCausalLM.from_pretrained(
    "meta-llama/Llama-2-7b-hf",
    quantize_config,
)

# 准备校准数据
calib_data = [
    "auto-gptq is an easy-to-use model quantization library",
    "with user-friendly apis",
    # ... 更多校准数据
]

# 执行量化
model.quantize(calib_data)

# 保存量化模型
model.save_quantized("Llama-2-7b-4bit-gptq")

# 加载量化模型进行推理
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

model = AutoGPTQForCausalLM.from_quantized(
    "Llama-2-7b-4bit-gptq",
    device="cuda:0",
)

# 生成
inputs = tokenizer("Hello, how are you?", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=50)
print(tokenizer.decode(outputs[0]))

6.3 AWQ 量化部署

Python
# AWQ量化(通常比GPTQ质量更好)
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

# 加载模型
model_path = "meta-llama/Llama-2-7b-hf"
quant_path = "Llama-2-7b-awq"

# 量化配置
quant_config = {
    "zero_point": True,
    "q_group_size": 128,
    "w_bit": 4,
    "version": "GEMM"
}

# 加载模型
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

# 准备校准数据
examples = [
    tokenizer("auto-gptq is an easy-to-use model quantization library")
    for _ in range(8)
]

# 量化
model.quantize(
    tokenizer,
    quant_config=quant_config,
    calib_data=examples,
)

# 保存
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

# 加载量化模型
model = AutoAWQForCausalLM.from_quantized(
    quant_path,
    fuse_layers=True,  # 融合层以加速
)

6.4 vLLM 中的量化部署

Python
# vLLM支持多种量化方式

# 1. AWQ量化
llm = LLM(
    model="TheBloke/Llama-2-7B-AWQ",
    quantization="awq",
    tensor_parallel_size=1,
)

# 2. GPTQ量化
llm = LLM(
    model="TheBloke/Llama-2-7B-GPTQ",
    quantization="gptq",
    tensor_parallel_size=1,
)

# 3. SqueezeLLM量化
llm = LLM(
    model="path/to/squeezellm/model",
    quantization="squeezellm",
)

API 服务封装

7.1 FastAPI 封装

Python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn
from vllm import LLM, SamplingParams

app = FastAPI(title="LLM Inference API")

# 全局模型
llm = None

class CompletionRequest(BaseModel):  # Pydantic BaseModel:自动数据验证和序列化
    model: str
    prompt: str
    max_tokens: Optional[int] = 256  # Optional表示值可以为None
    temperature: Optional[float] = 0.7
    top_p: Optional[float] = 0.9
    top_k: Optional[int] = -1
    stop: Optional[List[str]] = None
    stream: Optional[bool] = False

class ChatCompletionRequest(BaseModel):
    model: str
    messages: List[dict]
    max_tokens: Optional[int] = 256
    temperature: Optional[float] = 0.7
    top_p: Optional[float] = 0.9
    stream: Optional[bool] = False

@app.on_event("startup")
async def load_model():
    global llm
    llm = LLM(
        model="meta-llama/Llama-2-7b-hf",
        tensor_parallel_size=1,
        gpu_memory_utilization=0.9,
    )
    print("Model loaded successfully")

@app.post("/v1/completions")
async def create_completion(request: CompletionRequest):
    try:  # try/except捕获异常,防止程序崩溃
        sampling_params = SamplingParams(
            temperature=request.temperature,
            top_p=request.top_p,
            top_k=request.top_k,
            max_tokens=request.max_tokens,
            stop=request.stop,
        )

        outputs = llm.generate(request.prompt, sampling_params)

        return {
            "id": f"cmpl-{uuid.uuid4()}",
            "object": "text_completion",
            "created": int(time.time()),
            "model": request.model,
            "choices": [
                {
                    "text": output.outputs[0].text,
                    "index": i,
                    "logprobs": None,
                    "finish_reason": "stop"
                }
                for i, output in enumerate(outputs)
            ],
            "usage": {
                "prompt_tokens": len(outputs[0].prompt_token_ids),
                "completion_tokens": len(outputs[0].outputs[0].token_ids),
                "total_tokens": len(outputs[0].prompt_token_ids) + len(outputs[0].outputs[0].token_ids)
            }
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest):
    try:
        # 构建chat prompt
        prompt = build_chat_prompt(request.messages)

        sampling_params = SamplingParams(
            temperature=request.temperature,
            top_p=request.top_p,
            max_tokens=request.max_tokens,
        )

        outputs = llm.generate(prompt, sampling_params)

        return {
            "id": f"chatcmpl-{uuid.uuid4()}",
            "object": "chat.completion",
            "created": int(time.time()),
            "model": request.model,
            "choices": [
                {
                    "index": i,
                    "message": {
                        "role": "assistant",
                        "content": output.outputs[0].text
                    },
                    "finish_reason": "stop"
                }
                for i, output in enumerate(outputs)
            ],
            "usage": {
                "prompt_tokens": len(outputs[0].prompt_token_ids),
                "completion_tokens": len(outputs[0].outputs[0].token_ids),
                "total_tokens": len(outputs[0].prompt_token_ids) + len(outputs[0].outputs[0].token_ids)
            }
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

def build_chat_prompt(messages):
    """构建chat prompt"""
    prompt = ""
    for msg in messages:
        role = msg.get('role', '')
        content = msg.get('content', '')
        if role == 'system':
            prompt += f"[INST] <<SYS>>\n{content}\n<</SYS>>\n\n"
        elif role == 'user':
            prompt += f"{content} [/INST]"
        elif role == 'assistant':
            prompt += f" {content} </s><s>[INST] "
    return prompt

@app.get("/health")
async def health_check():
    return {"status": "healthy", "model_loaded": llm is not None}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

7.2 客户端调用示例

Python
import requests
import json

class LLMClient:
    """
    LLM API客户端
    """
    def __init__(self, base_url="http://localhost:8000"):
        self.base_url = base_url

    def complete(self, prompt, **kwargs):
        """
        文本补全
        """
        response = requests.post(
            f"{self.base_url}/v1/completions",
            json={
                "model": "llama-2-7b",
                "prompt": prompt,
                **kwargs
            }
        )
        return response.json()

    def chat(self, messages, **kwargs):
        """
        对话
        """
        response = requests.post(
            f"{self.base_url}/v1/chat/completions",
            json={
                "model": "llama-2-7b",
                "messages": messages,
                **kwargs
            }
        )
        return response.json()

# 使用示例
client = LLMClient()

# 文本补全
result = client.complete(
    "The future of AI is",
    max_tokens=100,
    temperature=0.7
)
print(result['choices'][0]['text'])

# 对话
result = client.chat([
    {"role": "user", "content": "Explain quantum computing"}
])
print(result['choices'][0]['message']['content'])

性能优化与监控

8.1 性能指标

Python
class PerformanceMonitor:
    """
    性能监控器
    """

    def __init__(self):
        self.metrics = {
            'latency': [],  # 延迟(秒)
            'throughput': [],  # 吞吐量(tokens/秒)
            'queue_size': [],  # 队列大小
        }

    def measure_latency(self, func, *args, **kwargs):  # func为传入的函数,*args/**kwargs透传任意参数,实现通用性能测量
        """
        测量延迟
        """
        import time

        start = time.time()
        result = func(*args, **kwargs)  # 将收集的参数解包后原样传给被测量的函数
        end = time.time()

        latency = end - start
        self.metrics['latency'].append(latency)

        return result, latency

    def calculate_throughput(self, num_tokens, latency):
        """
        计算吞吐量
        """
        throughput = num_tokens / latency
        self.metrics['throughput'].append(throughput)
        return throughput

    def get_statistics(self):
        """
        获取统计信息
        """
        import numpy as np

        stats = {}
        for key, values in self.metrics.items():
            if values:
                stats[key] = {
                    'mean': np.mean(values),
                    'median': np.median(values),
                    'p95': np.percentile(values, 95),
                    'p99': np.percentile(values, 99),
                    'min': np.min(values),
                    'max': np.max(values),
                }

        return stats

# 基准测试
def benchmark_inference(model, prompts, max_tokens=100):
    """
    推理基准测试
    """
    monitor = PerformanceMonitor()

    for prompt in prompts:
        # 测量延迟
        outputs, latency = monitor.measure_latency(
            model.generate,
            prompt,
            max_tokens=max_tokens
        )

        # 计算吞吐量
        num_tokens = len(outputs[0].outputs[0].token_ids)
        throughput = monitor.calculate_throughput(num_tokens, latency)

        print(f"Prompt: {prompt[:50]}...")
        print(f"Latency: {latency:.3f}s")
        print(f"Throughput: {throughput:.1f} tokens/s")
        print("-" * 50)

    # 打印统计
    stats = monitor.get_statistics()
    print("\n=== Benchmark Results ===")
    for metric, values in stats.items():
        print(f"\n{metric.upper()}:")
        for stat, value in values.items():
            print(f"  {stat}: {value:.3f}")

8.2 优化建议

Text Only
推理优化建议
═══════════════════════════════════════════════════════════════════

1. 批处理优化
├── 使用动态批处理(continuous batching)
├── 设置合适的max_num_seqs
└── 调整max_batch_total_tokens

2. 内存优化
├── 使用量化(INT8/INT4)
├── 启用KV Cache分页(vLLM)
├── 调整gpu_memory_utilization
└── 使用梯度检查点(如果同时训练)

3. 并行优化
├── 张量并行(多卡)
├── 流水线并行(超大模型)
└── 数据并行(多实例)

4. 编译优化
├── 使用Torch.compile(PyTorch 2.0+)
├── 使用TensorRT-LLM
└── 使用ONNX Runtime

5. 网络优化
├── 使用gRPC代替HTTP
├── 启用压缩
└── 使用CDN加速模型下载

═══════════════════════════════════════════════════════════════════

KV Cache 缓存体系与缓存命中率优化

📌 为什么单独成章: KV Cache 和缓存命中率直接决定推理成本与延迟,是 2025-2026 年 LLM 推理工程的核心优化战场。本节从原理到工程实践全面覆盖。

9.1 KV Cache 工作原理精解

Text Only
自回归生成的重复计算问题
═══════════════════════════════════════════════════════════════════

【无 KV Cache】
生成第 t 个 token 时:
  对前 t-1 个 token 重新计算 Q、K、V → 注意力 → 输出
  总计算量:O(L²) 其中 L 为序列长度

【有 KV Cache】
                  ┌───────────────────────────────────┐
  Prefill 阶段    │  输入全部 Prompt → 一次性计算      │
  (首次填充)    │  所有 token 的 K、V → 存入显存     │
                  └─────────────────────┬─────────────┘
                                        │ KV Cache
                  ┌─────────────────────▼─────────────┐
  Decode 阶段     │  每步只计算新 token 的 Q            │
  (逐 token 生成)│  用缓存的 K、V 做注意力计算        │
                  │  新 K、V append 到 Cache 末尾       │
                  └───────────────────────────────────┘

计算量对比(以 seq_len=2048 为例):
  无缓存:2048 步 × O(2048) = O(2048²) ≈ 400 万次矩阵乘
  有缓存:Prefill O(2048²) + Decode 2048 × O(1) ≈ 减少 99% Decode 计算

显存代价(Llama-3-70B, FP16):
  每个 token:2(K+V) × 80层 × 8头 × 128维 × 2B = 327 KB
  4096 tokens:327KB × 4096 ≈ 1.3 GB
  batch=64:1.3GB × 64 ≈ 83 GB  ← 显存管理成为核心工程挑战
═══════════════════════════════════════════════════════════════════

9.2 Prefix Caching (前缀缓存)

9.2.1 原理: KV Cache 的跨请求复用

Text Only
传统 KV Cache:仅在单次请求内复用
  请求 A:[System Prompt × 1000 tokens][用户问题 A]
  请求 B:[System Prompt × 1000 tokens][用户问题 B]  ← 重复计算 System Prompt!

Prefix Caching:跨请求复用相同前缀的 KV Cache
  请求 A:[System Prompt KV Cache(命中)][用户问题 A 新算]
  请求 B:[System Prompt KV Cache(命中)][用户问题 B 新算]
            ───────────────────────────
                     ↑ 直接从缓存读取,无需重算

命中条件:
  Token 序列完全一致(基于 Token ID 做 Hash 匹配)
  常见场景:
  ├── 多用户共享同一 System Prompt(RAG/Agent 系统)
  ├── 多轮对话(前几轮历史相同)
  ├── Few-shot 示例复用
  └── 批量处理同类文档(文档前缀相同)

理论收益:
  System Prompt = 2000 tokens,用户输入 = 100 tokens
  无 Prefix Caching:每次 Prefill = 2100 tokens
  有 Prefix Caching(命中):每次 Prefill = 100 tokens
  节省:2000/2100 ≈ 95% 的 Prefill 计算!
  TTFT 降低比例 ≈ 1 - (100/2100) = 95%

9.2.2 实现机制:基于 Block Hash 的前缀树

Text Only
PagedAttention + Prefix Caching 结合(vLLM 的实现)

核心数据结构:Radix Tree(基数树)

  每个 Block = 16 个连续 token 的 KV Cache
  Block 的唯一标识 = SHA256(token_ids in block)

  示例(Block Size = 4):
  请求 1: [T1 T2 T3 T4 | T5 T6 T7 T8 | T9 T10 T11 T12 | ...用户输入...]
           ├── Block-A (hash:aa11)        命中 ✓
               ├── Block-B (hash:bb22)   命中 ✓
                   ├── Block-C (hash:cc33) 命中 ✓
                       └── Block-D (新建) ← 用户独有部分

  请求 2: [T1 T2 T3 T4 | T5 T6 T7 T8 | T9 T10 T11 T12 | ...不同用户输入...]
           ├── Block-A (hash:aa11) 命中 ✓
               ├── Block-B (hash:bb22) 命中 ✓
                   ├── Block-C (hash:cc33) 命中 ✓
                       └── Block-E (新建) ← 不同用户输入

缓存淘汰策略:LRU(Least Recently Used)
  当显存不足时,优先淘汰最久未使用的 Block
  保留热门 System Prompt 的 Block(高命中率保证)

粒度限制:前缀必须对齐 Block 边界
  Block 大小 = 16 tokens,前缀长度 = 1537 tokens
  实际命中 = floor(1537/16) × 16 = 1536 tokens(最后一个残缺 Block 不命中)

9.2.3 vLLM 启用 Automatic Prefix Caching

Python
from vllm import LLM, SamplingParams

# 启用 Automatic Prefix Caching(APC)
llm = LLM(
    model="meta-llama/Llama-3-70B-Instruct",
    enable_prefix_caching=True,          # 关键参数
    gpu_memory_utilization=0.90,
    tensor_parallel_size=4,
    max_num_seqs=256,
    # 缓存预热:启动时预计算固定 System Prompt
    # preemption_mode="recompute",       # 显存不足时重算(默认)
    # preemption_mode="swap",            # 显存不足时换出到 CPU
)

# ---- 场景 1:多用户共享 System Prompt ----
SYSTEM_PROMPT = """
You are an expert AI assistant with deep knowledge in...
[此处填充 2000 tokens 的详细系统提示]
"""

def build_messages(user_question: str):
    return [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user",   "content": user_question},
    ]

# 第一批请求:System Prompt 被计算并缓存
prompts_batch1 = [
    llm.get_tokenizer().apply_chat_template(
        build_messages(q), tokenize=False, add_generation_prompt=True
    )
    for q in ["What is RAG?", "Explain Attention", "How does RLHF work?"]
]

sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
# 首次生成:System Prompt 进行 Prefill 并缓存
outputs1 = llm.generate(prompts_batch1, sampling_params)

# 第二批请求:System Prompt KV Cache 命中,TTFT 大幅下降
prompts_batch2 = [
    llm.get_tokenizer().apply_chat_template(
        build_messages(q), tokenize=False, add_generation_prompt=True
    )
    for q in ["What is MoE?", "Explain vLLM", "How does LoRA work?"]
]
# 再次生成:System Prompt 命中缓存,只 Prefill 用户输入部分
outputs2 = llm.generate(prompts_batch2, sampling_params)


# ---- 场景 2:多轮对话的前缀复用 ----
class CachedMultiTurnChat:
    """多轮对话:历史轮次命中 Prefix Cache"""

    def __init__(self, llm_engine, system_prompt: str):
        self.llm = llm_engine
        self.system_prompt = system_prompt
        self.history = []

    def chat(self, user_input: str) -> str:
        self.history.append({"role": "user", "content": user_input})

        messages = [{"role": "system", "content": self.system_prompt}] + self.history
        prompt = self.llm.get_tokenizer().apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )

        # 每次请求,历史对话前缀命中缓存,只需计算新增 token
        output = self.llm.generate(
            [prompt],
            SamplingParams(temperature=0.7, max_tokens=256)
        )
        response = output[0].outputs[0].text
        self.history.append({"role": "assistant", "content": response})
        return response

9.3 缓存命中率:度量、监控与优化目标

9.3.1 核心指标定义

Text Only
缓存命中率(Cache Hit Rate)
═══════════════════════════════════════════════════════════════════

① Token 级命中率(最常用)
   Hit Rate = 命中缓存的 Token 数 / 总 Prefill Token 数

   例:System Prompt = 1000 tokens,用户输入 = 100 tokens,全部命中
   Hit Rate = 1000 / 1100 = 90.9%

② 请求级命中率
   Hit Rate = 发生至少一次命中的请求数 / 总请求数

③ 成本节省率(API 计费场景)
   Cost Saving = 命中 Token 节省的费用 / 总原始 Prefill 费用

目标基准(取决于业务场景):
┌────────────────────────────────────┬─────────────────────────────┐
│ 场景                               │ 合理目标命中率               │
├────────────────────────────────────┼─────────────────────────────┤
│ 固定 System Prompt + 单次 Q&A      │ 80-95%                       │
│ 多轮对话(10轮以上)               │ 70-90%(随轮数增长)         │
│ RAG(每次检索结果不同)            │ 30-60%(仅 System Prompt)   │
│ 代码补全(逐字符变化)             │ 5-30%(文件前缀复用)        │
│ 批量文档处理(相同模板)           │ 90%+                         │
└────────────────────────────────────┴─────────────────────────────┘
═══════════════════════════════════════════════════════════════════

9.3.2 vLLM 缓存命中率监控

Python
import time
import threading
from collections import defaultdict
from typing import Dict, List, Optional

class PrefixCacheMonitor:
    """
    生产级 Prefix Cache 命中率监控
    支持实时统计、滑动窗口、告警
    """

    def __init__(self, window_size: int = 1000):
        self.window_size = window_size
        self._lock = threading.Lock()

        # 总体统计
        self.total_prefill_tokens = 0
        self.cache_hit_tokens = 0
        self.total_requests = 0
        self.hit_requests = 0

        # 滑动窗口(最近 N 次请求)
        self.window: List[Dict] = []

        # 按场景分类统计
        self.scene_stats: Dict[str, Dict] = defaultdict(lambda: {
            "total_tokens": 0,
            "hit_tokens": 0,
            "requests": 0,
        })

    def record(self,
               prompt_tokens: int,
               cached_tokens: int,
               scene: str = "default"):
        """
        记录一次请求的缓存情况

        Args:
            prompt_tokens: 本次 Prefill 的总 token 数
            cached_tokens: 其中命中缓存的 token 数(无需重算)
            scene: 业务场景标记(用于分场景分析)
        """
        with self._lock:
            self.total_prefill_tokens += prompt_tokens
            self.cache_hit_tokens += cached_tokens
            self.total_requests += 1
            if cached_tokens > 0:
                self.hit_requests += 1

            # 场景统计
            self.scene_stats[scene]["total_tokens"] += prompt_tokens
            self.scene_stats[scene]["hit_tokens"] += cached_tokens
            self.scene_stats[scene]["requests"] += 1

            # 滑动窗口
            entry = {
                "timestamp": time.time(),
                "prompt_tokens": prompt_tokens,
                "cached_tokens": cached_tokens,
                "hit_rate": cached_tokens / max(prompt_tokens, 1),
                "scene": scene,
            }
            self.window.append(entry)
            if len(self.window) > self.window_size:
                self.window.pop(0)

    @property
    def overall_token_hit_rate(self) -> float:
        """整体 Token 级命中率"""
        if self.total_prefill_tokens == 0:
            return 0.0
        return self.cache_hit_tokens / self.total_prefill_tokens

    @property
    def overall_request_hit_rate(self) -> float:
        """整体请求级命中率"""
        if self.total_requests == 0:
            return 0.0
        return self.hit_requests / self.total_requests

    @property
    def recent_token_hit_rate(self) -> float:
        """最近滑动窗口内的 Token 命中率"""
        if not self.window:
            return 0.0
        total = sum(e["prompt_tokens"] for e in self.window)
        hit = sum(e["cached_tokens"] for e in self.window)
        return hit / max(total, 1)

    def compute_cost_saving(self,
                            price_per_1k_tokens: float = 0.003) -> Dict:
        """
        计算节省的 API 费用(适用于 OpenAI / Anthropic 等按 Token 计费场景)

        Args:
            price_per_1k_tokens: 每千输入 token 的价格(美元)
        """
        saved_tokens = self.cache_hit_tokens
        original_cost = self.total_prefill_tokens / 1000 * price_per_1k_tokens
        saved_cost = saved_tokens / 1000 * price_per_1k_tokens
        return {
            "total_prefill_tokens": self.total_prefill_tokens,
            "saved_tokens": saved_tokens,
            "original_cost_usd": round(original_cost, 4),
            "saved_cost_usd": round(saved_cost, 4),
            "saving_rate": round(saved_cost / max(original_cost, 1e-9), 4),
        }

    def get_report(self) -> str:
        """输出可读性报告"""
        lines = [
            "=" * 60,
            "  Prefix Cache 命中率报告",
            "=" * 60,
            f"  总请求数        : {self.total_requests:,}",
            f"  总 Prefill Token: {self.total_prefill_tokens:,}",
            f"  命中 Token 数   : {self.cache_hit_tokens:,}",
            f"  Token 命中率    : {self.overall_token_hit_rate:.1%}",
            f"  请求命中率      : {self.overall_request_hit_rate:.1%}",
            f"  近期命中率(滑窗): {self.recent_token_hit_rate:.1%}",
            "-" * 60,
            "  分场景统计:",
        ]
        for scene, stat in self.scene_stats.items():
            scene_rate = stat["hit_tokens"] / max(stat["total_tokens"], 1)
            lines.append(
                f"    [{scene}] 命中率={scene_rate:.1%}  "
                f"请求={stat['requests']}  "
                f"hit_tokens={stat['hit_tokens']:,}"
            )
        lines.append("=" * 60)
        return "\n".join(lines)


# ---- 与 vLLM 集成的封装示例 ----
monitor = PrefixCacheMonitor(window_size=2000)

from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3-8B-Instruct",
    enable_prefix_caching=True,
    gpu_memory_utilization=0.90,
)

def monitored_generate(prompts: List[str],
                       scene: str = "default",
                       **kwargs) -> List:
    """
    带监控的推理封装:自动记录缓存命中情况
    """
    params = SamplingParams(**kwargs)
    outputs = llm.generate(prompts, params)

    for output in outputs:
        prompt_tokens = len(output.prompt_token_ids)
        # vLLM RequestOutput 包含 num_cached_tokens 字段(v0.5+)
        cached_tokens = getattr(output, "num_cached_tokens", 0)
        monitor.record(prompt_tokens, cached_tokens, scene=scene)

    return outputs


# 使用示例
results = monitored_generate(
    ["What is RAG?", "Explain Attention Mechanism"],
    scene="qa",
    temperature=0.7,
    max_tokens=256,
)
print(monitor.get_report())

9.4 主流框架缓存支持对比

Text Only
主流框架 Prefix Caching 支持矩阵(2026 Q1)
═══════════════════════════════════════════════════════════════════

框架           | 自动前缀缓存 | 跨请求复用 | 多租户共享 | 缓存预热 | 命中率指标
───────────────┼────────────┼──────────┼──────────┼────────┼──────────
vLLM           |  ✅ APC    |  ✅      |  ✅      |  ✅    |  ✅
SGLang         |  ✅ RadixAttn| ✅      |  ✅      |  ✅    |  ✅(最详细)
TGI            |  ✅(v2.0+)|  ✅     |  部分    |  ❌    |  有限
TensorRT-LLM   |  ✅(KV Reuse)| ✅   |  部分    |  ✅    |  ✅
llama.cpp      |  ✅(--cache-prompt)| ✅ | ❌   |  ❌    |  ❌
Ollama         |  ✅(自动) |  单会话  |  ❌      |  ❌    |  ❌

推荐选型:
  高并发多租户场景:SGLang(缓存控制最细粒度)
  通用生产部署:vLLM(APC + 生态最成熟)
  轻量本地:Ollama / llama.cpp(零配置,但无多租户共享)
═══════════════════════════════════════════════════════════════════

9.4.1 SGLang RadixAttention 示例

Python
# SGLang 以激进的 KV Cache 复用著称
# pip install sglang[all]

import sglang as sgl

@sgl.function
def multi_turn_qa(s, system_prompt, questions):
    """
    SGLang 原生多轮对话:自动识别可复用前缀
    """
    s += sgl.system(system_prompt)
    answers = []
    for q in questions:
        s += sgl.user(q)
        s += sgl.assistant(sgl.gen("answer", max_new_tokens=256))
        answers.append(s["answer"])
    return answers

# 初始化运行时(启用 Radix Cache)
runtime = sgl.Runtime(
    model_path="meta-llama/Llama-3-8B-Instruct",
    tp_size=1,
    # RadixAttention 默认开启,无需额外参数
)
sgl.set_default_backend(runtime)

# 批处理:相同 System Prompt → 自动共享前缀 KV Cache
SYSTEM = "You are a helpful coding assistant specializing in Python."
queries = [
    ["What is a decorator?", "How does yield work?"],
    ["Explain asyncio", "What is GIL?"],
]

for q_list in queries:
    results = multi_turn_qa.run(
        system_prompt=SYSTEM,
        questions=q_list,
    )
    print(results["answer"])

runtime.shutdown()

9.5 API 侧 Prompt Caching (节省成本的工程实践)

Text Only
主流 API 的 Prompt Caching 支持(2026 Q1,需以各家官方定价页/文档实时复核)
═══════════════════════════════════════════════════════════════════

供应商        | 功能名称           | 触发方式              | 费用/TTL 说明
─────────────┼───────────────────┼───────────────────────┼────────────────────────
Anthropic    | Prompt Caching    | 显式 cache_control    | 读/写缓存分别计费,TTL 以官方文档为准
OpenAI       | Prompt Caching    | 共享前缀自动命中      | cached input 单独计费,触发阈值/TTL 以官方页为准
Google Gemini| Context Caching   | 显式 cachedContent    | 区分存储与读取成本,TTL 受接口限制
其他平台      | 平台自定义         | 自动或手动            | 名称、命中规则与统计字段差异很大

关键洞察:
  - 长 System Prompt / 大段共享上下文 + 高频调用 = 更容易看出收益
  - 不同厂商会把“缓存创建”“缓存读取”“普通输入”拆成不同计费项
  - 缓存有 TTL(生存时间)或命中前缀要求,部署前必须做真实压测
═══════════════════════════════════════════════════════════════════
Python
import anthropic
import time
from typing import List, Dict

client = anthropic.Anthropic()

# =====================================================================
# Anthropic Prompt Caching 实战
# =====================================================================

LARGE_SYSTEM_PROMPT = """
You are an expert AI assistant with the following detailed knowledge base:
[此处插入 2000+ tokens 的长系统提示、RAG 结果、Few-shot 示例等]
"""

def call_with_cache(user_message: str) -> Dict:
    """
    使用 Prompt Caching 的 Anthropic API 调用
    """
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=1024,
        system=[
            {
                "type": "text",
                "text": LARGE_SYSTEM_PROMPT,
                "cache_control": {"type": "ephemeral"},  # 显式标记缓存点
            }
        ],
        messages=[{"role": "user", "content": user_message}],
    )

    usage = response.usage
    return {
        "content": response.content[0].text,
        "input_tokens": usage.input_tokens,
        "cache_creation_tokens": getattr(usage, "cache_creation_input_tokens", 0),
        "cache_read_tokens": getattr(usage, "cache_read_input_tokens", 0),
    }

def demo_cache_savings():
    """演示缓存带来的成本节约"""
    questions = [
        "Summarize the key points",
        "What are the main challenges?",
        "Suggest three improvements",
        "Compare with alternative approaches",
    ]

    total_input = 0
    total_cache_read = 0
    total_cache_create = 0

    for i, q in enumerate(questions):
        result = call_with_cache(q)
        total_input += result["input_tokens"]
        total_cache_read += result["cache_read_tokens"]
        total_cache_create += result["cache_creation_tokens"]

        status = "CACHE HIT" if result["cache_read_tokens"] > 0 else "CACHE MISS"
        print(f"请求 {i+1}: [{status}] "
              f"input={result['input_tokens']} "
              f"cache_read={result['cache_read_tokens']} "
              f"cache_create={result['cache_creation_tokens']}")

    # 成本计算示例
    # 注意:Anthropic 会区分普通输入、缓存创建、缓存读取;
    # 具体费率应从官方 pricing 页面实时读取或由配置中心注入
    PRICE_INPUT = 15.0 / 1_000_000
    PRICE_CACHE_CREATE = 18.75 / 1_000_000
    PRICE_CACHE_READ = 1.5 / 1_000_000

    actual_cost = (
        total_input * PRICE_INPUT
        + total_cache_create * PRICE_CACHE_CREATE
        + total_cache_read * PRICE_CACHE_READ
    )
    no_cache_cost = (total_input + total_cache_read + total_cache_create) * PRICE_INPUT

    print(f"\n总计:")
    print(f"  实际成本(有缓存): ${actual_cost:.6f}")
    print(f"  理论成本(无缓存): ${no_cache_cost:.6f}")
    print(f"  节省比例         : {1 - actual_cost/no_cache_cost:.1%}")

demo_cache_savings()


# =====================================================================
# OpenAI Prompt Caching(自动触发,无需显式标记)
# =====================================================================
from openai import OpenAI

oai_client = OpenAI()

def call_openai_with_cache(system_prompt: str, user_message: str) -> Dict:
    """
    OpenAI 自动 Prefix Caching:
    - Prompt 的前缀(1024 token 对齐)超过 1024 tokens 自动触发
    - 无需任何 API 参数修改
    """
    response = oai_client.chat.completions.create(
        model="gpt-5.4",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user",   "content": user_message},
        ],
    )

    usage = response.usage
    prompt_details = getattr(usage, "prompt_tokens_details", None)
    cached_tokens = getattr(prompt_details, "cached_tokens", 0) if prompt_details else 0

    token_hit_rate = cached_tokens / max(usage.prompt_tokens, 1)
    return {
        "content": response.choices[0].message.content,
        "prompt_tokens": usage.prompt_tokens,
        "cached_tokens": cached_tokens,
        "token_hit_rate": token_hit_rate,
    }

9.6 语义缓存( Semantic Cache )

Text Only
语义缓存 vs Prefix Caching
═══════════════════════════════════════════════════════════════════

Prefix Caching(Token 精确匹配):
  命中条件:Token ID 完全一致
  适用:固定模板、System Prompt 复用
  局限:"What is AI?" 和 "What's AI?" ≠ 命中

语义缓存(Embedding 相似度匹配):
  命中条件:语义相似度 > 阈值(通常 0.92-0.97)
  适用:FAQ、重复性问答、知识库查询
  收益:可缓存完整的 LLM 响应,命中时完全跳过推理
  代价:需要额外 Embedding 推理 + 向量检索延迟

何时用语义缓存:
  ✅ 高频重复性问答(客服、FAQ)
  ✅ 知识相对固定(不需要实时信息)
  ✅ 推理成本高(大模型 / 长输出)
  ❌ 需要实时/个性化响应
  ❌ 安全敏感场景(缓存攻击风险)
═══════════════════════════════════════════════════════════════════
Python
import hashlib
import json
import time
from typing import Optional, Tuple
import numpy as np

# 依赖:pip install redis sentence-transformers numpy

class SemanticCache:
    """
    生产级语义缓存:
    - 精确哈希缓存(L1)+ 语义相似度缓存(L2)
    - L1 命中:无需 Embedding,亚毫秒响应
    - L2 命中:Embedding 相似度匹配,跳过 LLM 推理
    - 未命中:调用 LLM,结果写入双层缓存
    """

    def __init__(
        self,
        redis_url: str = "redis://localhost:6379",
        similarity_threshold: float = 0.93,
        ttl_seconds: int = 3600,
        embedding_model: str = "all-MiniLM-L6-v2",
        max_cache_size: int = 10_000,
    ):
        import redis
        from sentence_transformers import SentenceTransformer

        self.redis = redis.from_url(redis_url, decode_responses=True)
        self.threshold = similarity_threshold
        self.ttl = ttl_seconds
        self.max_size = max_cache_size

        self.embedder = SentenceTransformer(embedding_model)

        # 内存向量索引(小规模)
        # 生产建议换 Faiss / Milvus / Qdrant
        self.vectors: list = []   # [(embedding, cache_key)]

        # 监控计数器
        self.stats = {"l1_hits": 0, "l2_hits": 0, "misses": 0}

    def _exact_key(self, query: str) -> str:
        """L1 精确哈希键"""
        return "sem_cache:exact:" + hashlib.sha256(query.encode()).hexdigest()

    def _semantic_key(self, cache_key: str) -> str:
        """L2 语义缓存的 Redis 键"""
        return "sem_cache:semantic:" + cache_key

    def _embed(self, text: str) -> np.ndarray:
        """计算归一化 Embedding"""
        vec = self.embedder.encode(text, normalize_embeddings=True)
        return vec

    def _find_similar(self, query_vec: np.ndarray) -> Optional[Tuple[float, str]]:
        """
        在内存向量库中找最相似的缓存项
        返回 (similarity, cache_key) 或 None
        """
        if not self.vectors:
            return None

        vecs = np.array([v for v, _ in self.vectors])  # (N, dim)
        keys = [k for _, k in self.vectors]

        # 余弦相似度(已归一化 → 直接点积)
        sims = vecs @ query_vec
        best_idx = int(np.argmax(sims))
        best_sim = float(sims[best_idx])

        if best_sim >= self.threshold:
            return best_sim, keys[best_idx]
        return None

    def get(self, query: str) -> Optional[Dict]:
        """
        查询缓存
        返回 {"response": str, "cache_level": "L1"/"L2", "similarity": float}
        或 None(未命中)
        """
        # L1:精确哈希命中
        exact_key = self._exact_key(query)
        cached = self.redis.get(exact_key)
        if cached:
            self.stats["l1_hits"] += 1
            return {"response": cached, "cache_level": "L1", "similarity": 1.0}

        # L2:语义相似度命中
        query_vec = self._embed(query)
        result = self._find_similar(query_vec)
        if result:
            sim, sem_key = result
            response = self.redis.get(self._semantic_key(sem_key))
            if response:
                self.stats["l2_hits"] += 1
                return {"response": response, "cache_level": "L2", "similarity": sim}

        self.stats["misses"] += 1
        return None

    def set(self, query: str, response: str):
        """写入缓存(同时写 L1 和 L2)"""
        # L1 精确缓存
        exact_key = self._exact_key(query)
        self.redis.setex(exact_key, self.ttl, response)

        # L2 语义缓存
        query_vec = self._embed(query)
        cache_key = hashlib.sha256(query.encode()).hexdigest()[:16]
        self.redis.setex(self._semantic_key(cache_key), self.ttl, response)

        # 更新向量索引(LRU 管理大小)
        if len(self.vectors) >= self.max_size:
            self.vectors.pop(0)  # 简单 FIFO,生产用 LRU
        self.vectors.append((query_vec, cache_key))

    @property
    def hit_rate(self) -> float:
        total = sum(self.stats.values())
        if total == 0:
            return 0.0
        return (self.stats["l1_hits"] + self.stats["l2_hits"]) / total

    def get_stats(self) -> Dict:
        total = sum(self.stats.values())
        return {
            "l1_hits": self.stats["l1_hits"],
            "l2_hits": self.stats["l2_hits"],
            "misses": self.stats["misses"],
            "total_requests": total,
            "hit_rate": f"{self.hit_rate:.1%}",
            "l1_hit_rate": f"{self.stats['l1_hits']/max(total,1):.1%}",
            "l2_hit_rate": f"{self.stats['l2_hits']/max(total,1):.1%}",
        }


# ---- 集成 LLM 调用的完整示例 ----
from openai import OpenAI

sem_cache = SemanticCache(similarity_threshold=0.93, ttl_seconds=7200)
oai = OpenAI()

def cached_llm_call(question: str, system_prompt: str = "") -> Dict:
    """
    带语义缓存的 LLM 调用
    - 命中:直接返回缓存,延迟 < 10ms
    - 未命中:调用 LLM,写入缓存
    """
    t0 = time.time()
    cache_result = sem_cache.get(question)

    if cache_result:
        return {
            "answer": cache_result["response"],
            "source": f"cache({cache_result['cache_level']})",
            "similarity": cache_result["similarity"],
            "latency_ms": (time.time() - t0) * 1000,
        }

    # 未命中 → 调用 LLM
    response = oai.chat.completions.create(
        model="gpt-5.4",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user",   "content": question},
        ],
    )
    answer = response.choices[0].message.content

    # 写入缓存
    sem_cache.set(question, answer)

    return {
        "answer": answer,
        "source": "llm",
        "similarity": None,
        "latency_ms": (time.time() - t0) * 1000,
    }


# 测试
questions = [
    "What is machine learning?",
    "What is ML?",                   # 语义相似 → L2 命中
    "Explain machine learning",      # 语义相似 → L2 命中
    "What is machine learning?",     # 精确匹配 → L1 命中
    "How does deep learning work?",  # 新问题 → 未命中
]

for q in questions:
    result = cached_llm_call(q)
    print(f"[{result['source']:12}] {q[:40]:40} | {result['latency_ms']:.1f}ms")

print("\n缓存统计:", json.dumps(sem_cache.get_stats(), indent=2, ensure_ascii=False))

9.7 缓存命中率优化最佳实践

Text Only
缓存命中率优化策略全景
═══════════════════════════════════════════════════════════════════

① 结构设计:固定前缀前置
   ✅ 推荐:[System Prompt(固定)][Few-shot(固定)][用户输入(变化)]
   ❌ 不推荐:[时间戳][用户输入][System Prompt]  ← 前缀每次变化

   原则:将不变部分放在上下文最前面
   效果:命中率 0% → 80%+(视 System Prompt 占比)

② 内容规范化
   - 统一空白符、标点、大小写(避免同义字符串不命中)
   - 日期/时间等动态内容放在用户输入侧,不放 System Prompt
   - 避免在 System Prompt 中插入请求 ID / trace_id 等唯一标识

③ 缓存预热(Cache Warm-up)
   服务启动时主动发送一条含完整 System Prompt 的虚拟请求
   → 第一批真实用户请求直接命中缓存,避免冷启动延迟

   # vLLM 缓存预热示例
   def warm_up_cache(llm, system_prompt: str):
       warmup_msg = [
           {"role": "system", "content": system_prompt},
           {"role": "user",   "content": "Hello"},
       ]
       prompt = llm.get_tokenizer().apply_chat_template(
           warmup_msg, tokenize=False, add_generation_prompt=True
       )
       llm.generate([prompt], SamplingParams(max_tokens=1))
       print("[Cache] Warm-up complete")

④ 多租户 System Prompt 共享
   - 同一业务场景下所有用户共享完全相同的 System Prompt
   - 避免在 System Prompt 中插入用户 ID / 个性化内容
   - 个性化内容放在用户输入(第一轮消息)中

⑤ Few-shot 示例的缓存友好写法
   - Few-shot 示例放在 System Prompt 末尾(固定部分)
   - 示例内容不依赖运行时数据(静态知识 OK,动态检索结果不 OK)

⑥ RAG 场景的 Prefix Caching 策略
   挑战:每次检索结果不同 → 整个 Prompt 唯一 → 命中率极低

   优化方案 1:两段式 Prompt
     [固定 System Prompt(缓存)][Query: ...][\n检索结果:\n{docs}]
     → System Prompt 部分仍可命中

   优化方案 2:文档 Prefix Caching
     针对同类文档批量处理,将文档内容放在前缀
     [System Prompt][文档内容(同一文档多个问题)][问题]
     → 同文档不同问题时命中率接近 100%

⑦ 长对话的缓存管理
   - 保持对话历史结构不变(追加而非重排)
   - 超出上下文窗口时:使用摘要压缩,但保留原始历史作为前缀
   - 避免在历史中修改已有消息(会使后续 Block Hash 失效)

⑧ 监控告警阈值
   场景                    | 告警阈值(Token 命中率)
   固定 System Prompt 场景 | < 70%  → 排查前缀结构
   多轮对话场景            | < 50%  → 排查历史清空策略
   批量处理场景            | < 85%  → 排查模板一致性
═══════════════════════════════════════════════════════════════════

9.8 端到端性能对比实验

Python
import time
import statistics
from typing import List
from vllm import LLM, SamplingParams

def benchmark_prefix_caching(
    model_name: str,
    system_prompt_tokens: int,
    user_input: str,
    num_requests: int = 50,
) -> None:
    """
    对比启用/禁用 Prefix Caching 下的 TTFT 和吞吐量
    """
    # 构造足够长的 System Prompt
    system_prompt = "You are a helpful assistant. " * (system_prompt_tokens // 6)

    results = {}
    for cache_enabled in [False, True]:
        llm = LLM(
            model=model_name,
            enable_prefix_caching=cache_enabled,
            gpu_memory_utilization=0.85,
        )

        tokenizer = llm.get_tokenizer()
        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user",   "content": user_input},
        ]
        prompt = tokenizer.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )
        params = SamplingParams(max_tokens=128, temperature=0.0)

        # 预热(仅在启用缓存时,首次填充缓存)
        if cache_enabled:
            llm.generate([prompt], params)

        latencies = []
        t_total_start = time.time()
        for _ in range(num_requests):
            t0 = time.time()
            llm.generate([prompt], params)
            latencies.append(time.time() - t0)
        total_time = time.time() - t_total_start

        label = "Prefix Cache ON " if cache_enabled else "Prefix Cache OFF"
        results[label] = {
            "mean_latency_ms": statistics.mean(latencies) * 1000,
            "p50_latency_ms":  statistics.median(latencies) * 1000,
            "p95_latency_ms":  sorted(latencies)[int(num_requests * 0.95)] * 1000,
            "throughput_rps":   num_requests / total_time,
        }
        del llm  # 释放显存

    # 输出对比
    print(f"\n{'='*65}")
    print(f"  Benchmark: System Prompt ≈ {system_prompt_tokens} tokens")
    print(f"  模型: {model_name}  |  请求数: {num_requests}")
    print(f"{'='*65}")
    print(f"{'指标':<20} {'无缓存':>18} {'有缓存':>18} {'提升':>12}")
    print(f"{'-'*65}")

    no_cache = results["Prefix Cache OFF"]
    has_cache = results["Prefix Cache ON "]
    for key, label in [
        ("mean_latency_ms", "平均延迟 (ms)"),
        ("p50_latency_ms",  "P50 延迟 (ms)"),
        ("p95_latency_ms",  "P95 延迟 (ms)"),
        ("throughput_rps",  "吞吐量 (req/s)"),
    ]:
        v_no  = no_cache[key]
        v_yes = has_cache[key]
        if key == "throughput_rps":
            improvement = f"+{(v_yes/v_no - 1)*100:.1f}%"
        else:
            improvement = f"-{(1 - v_yes/v_no)*100:.1f}%"
        print(f"  {label:<18} {v_no:>15.1f}   {v_yes:>15.1f}   {improvement:>10}")
    print(f"{'='*65}\n")


# 运行基准测试(需要 GPU 环境)
# benchmark_prefix_caching(
#     model_name="meta-llama/Llama-3-8B-Instruct",
#     system_prompt_tokens=1000,
#     user_input="What is the capital of France?",
#     num_requests=50,
# )

总结

部署方案选择指南

场景 推荐方案 理由
本地开发 Transformers / llama.cpp 简单易用
生产 API vLLM / TGI 高性能,稳定
超大模型 vLLM + 张量并行 支持多卡
边缘设备 llama.cpp / MLC-LLM 极致量化
快速原型 HuggingFace Inference API 无需部署
高频重复 Q&A 语义缓存 + vLLM APC 成本最低

部署检查清单

  • 模型格式正确( Safetensors 推荐)
  • 量化配置适当
  • 批处理参数调优
  • 内存使用监控
  • 错误处理机制
  • 日志记录完善
  • 健康检查接口
  • 自动扩缩容策略

缓存体系检查清单

  • enable_prefix_caching=True( vLLM )或对应框架缓存参数已开启
  • System Prompt 固定不变、位于 Prompt 最前端
  • 服务启动后执行缓存预热请求
  • 缓存命中率监控接入 Prometheus / Grafana
  • Token 命中率告警阈值已配置(建议 > 70%)
  • API 侧 Prompt Caching ( Anthropic cache_control / OpenAI 自动)已验证生效
  • 高频 FAQ 场景已接入语义缓存( Semantic Cache )
  • 多轮对话历史仅追加,不修改历史消息

附录: LLM 推理两阶段详解与异步架构

📌 来源参考:本节内容综合自 Datawhale《LLM部署》教程。

A.1 推理的两阶段:Prefill 与 Decode

Text Only
LLM 推理的两个阶段:

阶段1:预填充(Prefill)
├── 处理输入 prompt 的所有 tokens
├── 并行计算所有输入 tokens 的 attention
├── 生成并缓存 KV Cache
├── 通常耗时较长,但只需执行一次
└── 决定 TTFT(首 Token 延迟)

阶段2:生成(Decode)
├── 自回归逐 token 生成
├── 使用已缓存的 KV Cache
├── 每步只需计算最新 token 的 attention
├── 每个 token 耗时短,但需要串行执行
└── 决定 TPOT(每 Token 延迟)

总体延迟 = TTFT + (TPOT × 生成 token 数量)

A.2 关键性能指标

指标 全称 含义 影响因素
TTFT Time to First Token 从请求到首个 token 的时间 prompt 长度、模型大小、网络
TPOT Time per Output Token 每个 token 的平均生成时间 模型大小、批处理策略
QPS Queries Per Second 每秒处理请求数 并发能力、批处理效率
TPS Tokens Per Second 每秒输出 token 数 总生成时间、并发请求数

A.3 vLLM 异步架构演进

Text Only
vLLM 架构演进:

早期架构(单进程):
├── API Server + vLLM Engine 在同一 Python 进程
├── 问题1:CPU 资源竞争
├── 问题2:Python GIL 限制多线程
└── 问题3:推理任务阻塞新请求

新架构(PR#6883,进程分离):
├── API Server 独立进程(FastAPI)
├── vLLM Engine 独立进程
├── 通过 ZMQ(ZeroMQ)异步通信
├── 优势:
│   ├── 进程隔离,互不干扰
│   ├── 异步通信,非阻塞
│   └── 充分利用多核 CPU
└── 高并发场景性能显著提升

异步输出处理:
├── GPU 完成前向计算后不等待 CPU 处理输出
├── 立即开始下一步推理
├── CPU 并行处理上一步输出
└── TPOT 提升 ~8.7%(Llama 70B + 4xH100)

A.4 分布式推理实践

Python
# vLLM 张量并行 + Ray 数据并行
import ray
from vllm import LLM, SamplingParams

@ray.remote(num_gpus=0)
def infer(model_path, prompts):
    """使用 vLLM 进行张量并行推理"""
    llm = LLM(
        model=model_path,
        tensor_parallel_size=4,       # 4 卡张量并行
        dtype="float16",
        gpu_memory_utilization=0.95,
    )
    sampling_params = SamplingParams(
        top_p=1.0, temperature=0.5, max_tokens=2048
    )
    outputs = llm.generate(prompts=prompts, sampling_params=sampling_params)
    return [output.outputs[0].text for output in outputs]

def main(model_path, prompts, num_processes=2):
    """Ray 数据并行:多个 vLLM 实例分摊请求"""
    ray.init()
    per_process = len(prompts) // num_processes
    futures = []
    for idx in range(num_processes):
        start = idx * per_process
        end = start + per_process if idx < num_processes - 1 else len(prompts)
        futures.append(infer.remote(model_path, prompts[start:end]))
    all_outputs = ray.get(futures)
    ray.shutdown()
    return all_outputs

6. 练习题

🤔 思考题

  1. KV Cache 原理:为什么 KV Cache 能显著提升推理速度?在什么场景下 KV Cache 的收益最大?Prefix Caching 又是如何进一步优化的?
  2. 框架选型:vLLM、TGI、SGLang、TensorRT-LLM 各自的核心优势是什么?在什么场景下你会选择哪个框架?
  3. 量化权衡:GPTQ 和 AWQ 量化方法的核心区别是什么?4-bit 量化对模型精度的影响有多大?如何评估量化后的质量损失?
  4. 批处理策略:Continuous Batching 与 Static Batching 的本质区别是什么?为什么 Continuous Batching 能显著提高吞吐量?
  5. 生产部署:在部署 LLM 推理服务时,你会如何设计监控指标体系?哪些指标对发现性能瓶颈最关键?

💻 代码实践

  1. 入门:使用 vLLM 加载一个开源模型(如 Qwen2.5-1.5B),实现 OpenAI 兼容的 API 服务,并用 curl 发送请求
  2. 进阶:对比 vLLM 和 SGLang 在相同模型上的推理吞吐量和延迟,分析 Continuous Batching 的效果
  3. 高级:搭建一个完整的推理服务监控系统:Prometheus + Grafana,追踪首 Token 延迟(TTFT)、生成速度(Tokens/s)、KV Cache 命中率等核心指标
💡 参考答案 #### 思考题参考答案 **1. KV Cache 原理** KV Cache 的核心思想是缓存 Transformer 解码过程中每一层的 Key 和 Value 矩阵。在自回归生成中,每生成一个新 Token 都需要对前面所有 Token 做注意力计算。如果不缓存,第 t 步需要 O(t) 的重复计算。KV Cache 将已计算的 K、V 保存下来,每步只需计算新 Token 的 Q、K、V 并追加到缓存中,将每步复杂度从 O(t²) 降到 O(t)。 收益最大的场景: - **多轮对话**:前文不变,只需缓存一次 - **长文本生成**:序列越长,避免的重复计算越多 - **Batch 推理**:多个请求共享公共前缀 Prefix Caching 进一步优化:识别不同请求中的公共前缀(如系统提示词),将前缀部分的 KV Cache 跨请求复用,避免重复计算。vLLM 的 `enable_prefix_caching=True` 即可实现。 **2. 框架选型** | 框架 | 核心优势 | 适用场景 | |------|---------|---------| | **vLLM** | PagedAttention 高效内存管理,生态成熟,社区活跃 | 通用推理服务,快速部署 | | **TGI** | HuggingFace 原生支持,Flash Attention + Continuous Batching,部署简单 | HuggingFace 模型快速上线 | | **SGLang** | RadixAttention 前缀缓存,编程模型灵活,多轮对话优化 | 多轮对话密集型应用 | | **TensorRT-LLM** | NVIDIA 深度优化,kernel 融合,极致性能 | 追求极致吞吐量的生产环境 | 选型建议:快速验证用 vLLM/TGI;追求极致性能用 TensorRT-LLM;多轮对话场景考虑 SGLang。 **3. 量化权衡** - **GPTQ**:基于近似二阶信息(Hessian 矩阵)逐层量化,需要校准数据集。量化精度较高但速度较慢 - **AWQ**:基于激活值感知的权重量化,保护"重要"权重通道(激活值大的通道),不需要反向传播。量化速度更快 4-bit 量化的精度影响: - 通常在通用任务上精度损失 < 1-2%(困惑度略微上升) - 在数学推理、代码生成等精密任务上损失可能更大(2-5%) - 评估方法:困惑度(Perplexity)、基准测试(MMLU、HumanEval)、人工评估 **4. 批处理策略** - **Static Batching**:凑齐一批请求后统一推理,必须等最长的序列生成完毕才能释放所有请求。短序列被长序列"拖累",GPU 利用率低 - **Continuous Batching**(迭代级调度):每个解码步都检查是否有请求完成,完成后立即替换为新请求。GPU 始终保持满载,吞吐量可提升 2-4 倍 本质区别:调度的粒度不同——Static Batching 是请求级,Continuous Batching 是迭代(Token)级。 **5. 监控指标体系** 核心指标分四层: | 层级 | 指标 | 意义 | |------|------|------| | **延迟** | TTFT(首 Token 延迟) | Prefill 阶段性能 | | | E2E Latency(端到端延迟) | 用户体验 | | | Time-per-output-token | Decode 阶段速度 | | **吞吐** | Requests/s | 服务并发能力 | | | Output Tokens/s | GPU 生成效率 | | **资源** | GPU 利用率 | 硬件使用效率 | | | KV Cache 利用率 | 内存瓶颈检测 | | | 显存使用量 | 容量规划 | | **质量** | 请求成功率 | 服务稳定性 | | | 队列等待时间 | 负载是否过载 | 发现性能瓶颈最关键的指标:**TTFT**(定位 Prefill 瓶颈)、**KV Cache 利用率**(定位内存瓶颈)、**队列等待时间**(定位过载)。 #### 代码实践参考答案 **实践 1:vLLM API 服务**
Bash
# 安装 vLLM
pip install vllm

# 启动 OpenAI 兼容 API 服务
python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-1.5B-Instruct \
    --host 0.0.0.0 \
    --port 8000 \
    --gpu-memory-utilization 0.9

# 用 curl 发送请求
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-1.5B-Instruct",
    "messages": [
      {"role": "system", "content": "你是一个有帮助的AI助手。"},
      {"role": "user", "content": "解释一下什么是 KV Cache"}
    ],
    "max_tokens": 512,
    "temperature": 0.7
  }'
**实践 2:vLLM vs SGLang 性能对比**
Python
import time
import asyncio
from openai import AsyncOpenAI

async def benchmark(client, prompts, model_name):
    """测量吞吐量和延迟"""
    start = time.time()
    tasks = [
        client.chat.completions.create(
            model=model_name,
            messages=[{"role": "user", "content": p}],
            max_tokens=128
        )
        for p in prompts
    ]
    responses = await asyncio.gather(*tasks)
    elapsed = time.time() - start

    total_tokens = sum(r.usage.completion_tokens for r in responses)
    print(f"[{model_name}]")
    print(f"  总耗时: {elapsed:.2f}s")
    print(f"  吞吐量: {total_tokens / elapsed:.1f} tokens/s")
    print(f"  平均延迟: {elapsed / len(prompts):.2f}s/请求")

async def main():
    prompts = [f"请用50字解释概念:{topic}" for topic in
               ["机器学习", "深度学习", "自然语言处理", "计算机视觉", "强化学习"] * 20]

    # vLLM 基准测试
    vllm_client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="empty")
    await benchmark(vllm_client, prompts, "Qwen/Qwen2.5-1.5B-Instruct")

    # SGLang 基准测试(需另启 SGLang 服务)
    sglang_client = AsyncOpenAI(base_url="http://localhost:8001/v1", api_key="empty")
    await benchmark(sglang_client, prompts, "Qwen/Qwen2.5-1.5B-Instruct")

asyncio.run(main())
**实践 3:推理监控仪表盘**
Python
# prometheus_llm_metrics.py — 自定义 Prometheus 指标
from prometheus_client import Histogram, Gauge, Counter, start_http_server
import time

# 定义核心指标
TTFT = Histogram(
    'llm_time_to_first_token_seconds',
    '首 Token 延迟',
    buckets=[0.1, 0.25, 0.5, 1.0, 2.0, 5.0]
)
TOKENS_PER_SECOND = Gauge(
    'llm_output_tokens_per_second',
    '每秒生成 Token 数'
)
KV_CACHE_USAGE = Gauge(
    'llm_kv_cache_utilization_ratio',
    'KV Cache 使用率'
)
REQUEST_QUEUE_TIME = Histogram(
    'llm_request_queue_seconds',
    '请求排队等待时间',
    buckets=[0.05, 0.1, 0.25, 0.5, 1.0, 2.0]
)
FAILED_REQUESTS = Counter(
    'llm_failed_requests_total',
    '失败请求总数'
)

def monitor_inference(func):
    """装饰器:自动记录推理指标"""
    def wrapper(*args, **kwargs):
        queue_start = time.time()
        result = func(*args, **kwargs)
        ttft = result.metrics.get('time_to_first_token', 0)
        TTFT.observe(ttft)
        REQUEST_QUEUE_TIME.observe(time.time() - queue_start - ttft)
        return result
    return wrapper

# Grafana 仪表盘 JSON 配置要点:
# - TTFT P50/P95/P99 折线图
# - Tokens/s 实时曲线
# - KV Cache 利用率仪表盘
# - 队列等待时间热力图
# - 失败请求率告警规则

if __name__ == "__main__":
    start_http_server(9090)
    print("Prometheus 指标服务启动在 :9090/metrics")
    import signal
    signal.pause()

最后更新日期: 2026-04-21 适用版本: LLM 学习教程 v2026

下一步:学习04-对齐技术,掌握 RLHF 和 DPO 等对齐技术!