Skip to content

性能调优实战:从理论到实践

性能调优是 LLM 应用落地的关键环节。本文系统讲解从理论到实战的优化方法。

性能指标定义

核心指标

指标描述目标
吞吐量 (Throughput)每秒处理的 token 数或请求数越高越好
延迟 (Latency)单请求响应时间(P50, P95, P99)P99 < 2s(交互场景)
内存占用GPU/CPU 显存使用模型能装下 + 批处理余量
成本每千 token 成本或每请求成本在质量前提下尽可能低
准确率任务完成质量(如 pass@k, F1)与基线持平或更高

一、模型层面优化

1. 量化(Quantization)

原理:降低权值精度,减少内存和计算。

方法对比

方法精度速度提升精度损失工具
FP16 → BF1616位1.0×<0.5%原生支持
INT8 量化8位1.5-2×~1-2%ONNX, TensorRT
GPTQ (4-bit)4位2-3×~3-5%AutoGPTQ
AWQ (4-bit)4位2-3×~2-3%AutoAWQ
NF4 (4-bit)4位浮点2-3×<1%bitsandbytes

实战:使用 GPTQ 量化 LLaMA 2 7B

bash
# 安装 AutoGPTQ
pip install auto-gptq optimum

# 量化(需要少量校准数据,500-1000 样本)
python -m optimum.cli.export \
  --model meta-llama/Llama-2-7b-chat-hf \
  --task text-generation \
  --weight-format gptq \
  --bits 4 \
  --group-size 128 \
  --output ./llama-2-7b-gptq-4bit
python
# 加载量化模型推理
from transformers import AutoModelForCausalLM, AutoTokenizer

model = AutoModelForCausalLM.from_pretrained(
    "./llama-2-7b-gptq-4bit",
    device_map="auto",
    torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("./llama-2-7b-gptq-4bit")

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

Benchmark 对比

配置显存占用生成速度 (tokens/s)延迟 P99
FP1614 GB451.2s
INT87 GB750.7s
GPTQ 4-bit4 GB950.5s

选择建议

  • 追求精度:INT8
  • 追求速度与压缩:GPTQ/AWQ 4-bit
  • CPU 推理:GGUF(llama.cpp)

2. 模型架构优化

使用 vLLM 提升吞吐

vLLM 使用 PagedAttention 技术,显著提升吞吐:

bash
# 启动 vLLM 服务器(OpenAI API 兼容)
python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-2-7b-chat-hf \
  --quantization gptq \
  --dtype half \
  --max-model-len 4096 \
  --tensor-parallel-size 1

测试对比(批量大小=32,序列长度=512):

框架吞吐量 (req/s)内存使用首令牌延迟
HuggingFace Transformers128 GB120 ms
vLLM (PagedAttention)958 GB80 ms

PagedAttention 原理:将 KV Cache 分块存储,消除内部碎片,内存利用率提升 3-4 倍。

TensorRT-LLM 极致优化

NVIDIA 专有优化,性能最佳:

bash
# 构建 TensorRT engine
trtllm-build \
  --model_dir ./llama-2-7b \
  --output_dir ./trt_engine \
  --max_batch_size 32 \
  --max_input_len 2048 \
  --max_output_len 512 \
  --precision fp16

启动服务:

bash
trtllm-serve \
  --model_dir ./trt_engine \
  --port 8000

性能:比 vLLM 再提升 20-30%。


二、推理优化策略

1. Batch 处理优化

动态批处理(Dynamic Batching)

将短时间内到达的请求合并成一个 batch:

python
from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")
sampling_params = SamplingParams(temperature=0.7, max_tokens=256)

# 提交多个请求(vLLM 自动批处理)
outputs = llm.generate(
    ["你好,请问", "解释一下", "如何学习"],  # 多个独立请求
    sampling_params
)

效果:吞吐提升 5-10 倍(GPU 利用率越高越明显)。

连续批处理(Continuous Batching)

vLLM 的核心:当一个请求完成时,立即加入新请求,保持 GPU 始终满载。

对比传统静态批处理

Batch 策略平均吞吐尾延迟(P99)
静态 batch (size=16)45 req/s2.1s
连续 batch (vLLM)95 req/s1.3s

2. 采样策略优化

选择合适的采样方法

  • Greedy(贪心):速度快,确定性,适合需要稳定输出的场景(如代码生成)
  • Beam Search:更优但慢,适合翻译等追求最优解的任务
  • Top-k / Top-p (nucleus):多样性好,适合对话生成
python
sampling_params = SamplingParams(
    temperature=0.7,  # 控制随机性,0=确定性
    top_p=0.9,        # nucleus sampling
    top_k=50,         # 限制候选词数量
    max_tokens=256
)

性能影响:Beam Search 比 Greedy 慢 2-5 倍,但质量略高。

3. KV Cache 优化

问题:自回归生成中,KV Cache 存储所有 past key-value,内存占用随序列长度线性增长。

优化

  • FlashAttention:减少内存访问,提升速度 2×,降低内存占用
  • KV Cache 量化:将 FP16 KV Cache 转为 INT8,节省 50%
python
# vLLM 自动使用 FlashAttention 和 KV cache 优化
llm = LLM(model="...", enable_chunked_prefill=True)  # 支持长序列

三、应用层优化

1. 缓存(Caching)

Prompt 缓存

相同 prompt 的结果缓存(Redis, Memcached):

python
import redis, hashlib, json

r = redis.Redis(host='localhost', port=6379)

def get_completion(prompt, use_cache=True):
    # 生成缓存 key
    key = "llm:" + hashlib.md5(prompt.encode()).hexdigest()
    
    if use_cache:
        cached = r.get(key)
        if cached:
            return json.loads(cached)
    
    # 调用 LLM
    response = llm(prompt)
    
    # 缓存(TTL 24 小时)
    r.setex(key, 86400, json.dumps(response))
    return response

适用场景:常见问题、静态文档问答。 命中率目标:>30%

向量缓存

RAG 中,相同查询的检索结果缓存:

python
from functools import lru_cache

@lru_cache(maxsize=1000)
def retrieve(query: str):
    return retriever.retrieve(query)

2. 请求合并(Deduplication)

短时间内相同用户查询合并,返回相同结果:

python
from typing import Dict
from datetime import datetime, timedelta

class RequestDeduplicator:
    def __init__(self, ttl_seconds=5):
        self.cache: Dict[str, (str, datetime)] = {}
        self.ttl = timedelta(seconds=ttl_seconds)
    
    def get_or_set(self, key, func):
        now = datetime.now()
        if key in self.cache:
            result, timestamp = self.cache[key]
            if now - timestamp < self.ttl:
                return result
        
        result = func()
        self.cache[key] = (result, now)
        return result

效果:突发流量下减少 30-50% 的 LLM 调用。

3. 异步与非阻塞

FastAPI + 异步:

python
from fastapi import FastAPI
from langchain_core.runnables import RunnablePassthrough

app = FastAPI()

# LCEL 链是异步可调用的
rag_chain_async = rag_chain.with_config(configurable={"async": True})

@app.post("/chat")
async def chat(request: ChatRequest):
    response = await rag_chain_async.ainvoke({
        "question": request.question,
        "chat_history": request.history
    })
    return {"answer": response}

并发能力:单个服务器实例可支持数百并发(相比同步版提升 5-10 倍)。


四、系统架构优化

1. 负载均衡与水平扩展

用户 → Nginx (负载均衡) → vLLM 实例集群 (3 台 A100) → 向量数据库

Nginx 配置

nginx
upstream llm_backend {
    least_conn;  # 最少连接
    server 10.0.1.1:8000 max_fails=3 fail_timeout=30s;
    server 10.0.1.2:8000 max_fails=3 fail_timeout=30s;
    server 10.0.1.3:8000 max_fails=3 fail_timeout=30s;
}

server {
    location /v1/ {
        proxy_pass http://llm_backend;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_next_upstream error timeout http_500;
    }
}

会话粘性(Session Stickiness):如果需要保持上下文,使用 ip_hash 或一致性哈希。

2. 向量数据库性能

索引参数调优(Milvus HNSW)

python
from pymilvus import Collection, FieldSchema, CollectionSchema, DataType, connections

connections.connect(host="localhost", port=19530)

collection = Collection("docs")

# 重建索引(优化参数)
index_params = {
    "metric_type": "COSINE",
    "index_type": "HNSW",
    "params": {
        "M": 32,           # 每层节点最大出度,越大准确但慢
        "efConstruction": 400,  # 构建时搜索深度,越大构建慢但质量高
        "ef": 100         # 查询时搜索深度,越大准确但慢
    }
}
collection.create_index("embedding", index_params)

权衡

  • 高吞吐:增大 ef 牺牲一点精度,换取速度(PUBG 等实时应用)
  • 高精度:M=16-32, ef=200-400
  • 小数据集:可以不用 HNSW,用 IVF_FLAT

预过滤与查询优化

Milvus 支持查询时过滤(metadata 过滤):

python
# 检索 + 过滤(只查技术类文档)
results = collection.search(
    data=[query_embedding],
    anns_field="embedding",
    param={"metric_type": "COSINE", "params": {"ef": 100}},
    limit=10,
    expr="category == '技术' and source == 'internal'"
)

效果:减少扫描向量数量,加速 30-50%。

3. 缓存层

在向量数据库前加 Redis 缓存:

查询 → Redis(缓存) → Miss → 向量数据库 → 结果缓存
python
import redis, pickle
from sentence_transformers import SentenceTransformer

r = redis.Redis()
model = SentenceTransformer("all-MiniLM-L6-v2")

def cached_search(query, top_k=5):
    cache_key = f"search:{hashlib.md5(query.encode()).hexdigest()}"
    
    cached = r.get(cache_key)
    if cached:
        return pickle.loads(cached)
    
    # 向量化并搜索
    q_vec = model.encode(query).tolist()
    results = collection.search([q_vec], limit=top_k)
    
    # 缓存 1 小时
    r.setex(cache_key, 3600, pickle.dumps(results))
    return results

五、监控与诊断

1. 性能监控

Prometheus 指标

python
from prometheus_client import Counter, Histogram

llm_requests = Counter("llm_requests_total", "Total LLM requests")
llm_duration = Histogram("llm_duration_seconds", "LLM response time")

@app.post("/chat")
async def chat(request):
    llm_requests.inc()
    with llm_duration.time():
        response = await rag_chain.ainvoke(request.question)
    return response

Grafana 面板

  • 请求 QPS
  • P50/P95/P99 延迟
  • 错误率
  • GPU 利用率

2. 瓶颈诊断

使用 PyTorch Profiler:

python
import torch
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("...").cuda()

with torch.profiler.profile(
    activities=[torch.profiler.ProfilerActivity.CUDA],
    schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=1),
    on_trace_ready=torch.profiler.tensorboard_trace_handler('./log'),
    record_shapes=True,
    profile_memory=True
) as prof:
    for step in range(5):
        inputs = tokenizer([f"step {step}"] * 32, return_tensors="pt").to("cuda")
        outputs = model(**inputs)
        prof.step()

分析:

  • 注意力层耗时? → 考虑 FlashAttention
  • 内存带宽瓶颈? →KV Cache 量化
  • GPU利用率低 (<30%)? → 增大 batch size 或动态批处理

六、调优实战案例

案例:客服问答系统

初始架构

  • GPT-3.5 Turbo API
  • 单用户请求,等待完整生成
  • 无缓存

指标

  • P99 延迟:4.2s
  • 成本:$0.002 / request(1000 tokens)
  • QPS:15

优化步骤

  1. 量化 + vLLM 自托管:LLaMA 2 13B + GPTQ 4-bit

    • 延迟:P99 1.8s(↓57%)
    • 成本:$0.0003 / request(↓85%)
  2. Redis 缓存:常见问题缓存

    • QPS 提升:15 → 45(3×)
  3. 连续批处理:vLLM 天然支持

    • 吞吐:45 → 120 req/s(2.7×)
  4. 向量缓存:RAG 检索结果缓存 5min

    • 检索耗时:从 150ms → 5ms(99% 命中)

最终结果

  • P99 延迟:1.2s(↓71%)
  • 成本:$0.00015 / request(↓92.5%)
  • QPS:200+

七、常用工具与脚本

1. 性能测试脚本

python
import time, statistics
from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="token")

def benchmark(num_requests=100, max_tokens=256):
    latencies = []
    for i in range(num_requests):
        start = time.time()
        resp = client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": "Hello"}],
            max_tokens=max_tokens
        )
        latencies.append(time.time() - start)
    
    print(f"P50: {statistics.median(latencies):.3f}s")
    print(f"P95: {sorted(latencies)[int(num_requests*0.95)]:.3f}s")
    print(f"P99: {sorted(latencies)[int(num_requests*0.99)]:.3f}s")
    print(f"Throughput: {num_requests/sum(latencies):.1f} req/s")

benchmark(100, 256)

2. 自动调参工具(vLLM)

bash
# vLLM Benchmark
python -m vllm.benchmarks.benchmark_throughput \
  --model meta-llama/Llama-2-7b-chat-hf \
  --input-len 512 \
  --output-len 256 \
  --batch-size 32

总结

性能优化是系统工程:

优先级

  1. 先优化瓶颈最大处(通常是模型推理,用量化、vLLM)
  2. 再优化流水线(缓存、动态批处理)
  3. 最后优化细节(并行、内核级优化)

黄金法则

  • 测量,不要猜:用 profiler 找瓶颈
  • A/B 测试:每项优化都对比前后指标
  • 质量不妥协:优化后必须验证 accuracy 是否达标

这是一个持续迭代的过程,没有银弹。