- 设计规格:../../2026-07-31-llm-applications-design.md —>
LLM 推理与性能:从推理引擎到吞吐延迟优化
0. 元信息
- 主题路径:
docs/topics/llm-applications/subtopics/llm-inference-and-perf/ - 父主题:
llm-applications - 适合对象:已能调用模型服务、希望理解部署与性能的开发者
- 建议周期:1~2 周
- 前置知识:无额外前置(继承父主题依赖)
- 最终目标:能在固定模型、硬件、上下文和并发条件下比较推理引擎,并用数据优化 KV cache、量化、吞吐和延迟
1. 学习路线
推理请求与基准方法
→ Ollama 本地服务
→ TGI 服务化推理
→ vLLM continuous batching
→ KV cache 与显存
→ 量化
→ 吞吐/延迟压测与调优
2. 阶段周数分配(1~2 周,每天 1.5~2 小时)
| 阶段 | 1 周方案 | 2 周方案 | 备注 |
|---|---|---|---|
| 1. 引擎对比 | 0.25 周 | 0.5 周 | vLLM / TGI / Ollama 定位与部署 |
| 2. 量化 | 0.25 周 | 0.5 周 | FP16 / INT8 / INT4 与质量权衡 |
| 3. KV cache 与显存 | 0.25 周 | 0.5 周 | 显存预算、prefix cache、连续批处理 |
| 4. 性能调优 | 0.25 周 | 0.5 周 | 压测、调参、报告 |
分阶段概述:
- 第 1 周(1 周方案):完成三引擎对比 + 量化基础 + 一次同模型压测;输出第一版
report.md。 - 第 1~2 周(2 周方案):补 KV cache / 连续批处理实验 + 多档位压测 + 调优记录;末尾做一次”误归因”复盘,避免单一平均值下结论。
1 周方案每天 2 小时;2 周方案每天 1.5 小时,多留 2 天做量化与 KV cache 实验。基准条件不一致会让比较失效——所有结论必须记录硬件、模型、量化、上下文和并发。
3. 阶段表
| 阶段 | 核心知识 | 实践产出 | 可观察学会标准 |
|---|---|---|---|
| 1. 引擎对比 | vLLM、TGI、Ollama 的定位、API、部署边界 | 三引擎对比表 | 能在相同模型/请求下说明选择依据 |
| 2. 量化 | FP16/BF16、GPTQ/AWQ、bitsandbytes、精度权衡 | 量化前后基准 | 能同时报告质量、显存、延迟和吞吐变化 |
| 3. 性能调优 | batching、并发、max tokens、prefix cache、P95 | 压测报告与调参记录 | 能定位瓶颈,避免用单一平均值下结论 |
4. 第一周(每天 1.5~2 小时)
环境约定:本子主题统一使用 Python 3.11+ 与
python -m venv .venv;压测脚本用纯 Python(参考 §9.7.1);硬件记录写在notes/perf/hardware.md(GPU 型号 / 显存 / CUDA / 驱动)。API key 通过export ANTHROPIC_API_KEY=...共享 .env 模板:父主题attachments/.env.example含 Anthropic / OpenAI / Voyage / Qdrant / Ollama / OTel / Langfuse 全套变量;本地cp ../../attachments/.env.example .env后填值,.env加入.gitignore,加载用python-dotenv。禁止把真实 key 提交到仓库或写入 trace / 日志。 注入;本地推理用ollama serve起端点(默认http://localhost:11434)。
| 日 | 任务 | 当天交付 | 自检 |
|---|---|---|---|
| Day 1 | 装 ollama 与 curl,拉取 qwen2.5:7b 并起 OpenAI-compatible 端点;写最小对话脚本 | notes/perf/day1.md + curl 命令 | curl http://localhost:11434/v1/models 返回 qwen2.5:7b;对话脚本拿到一次回复 |
| Day 2 | 写压测脚本:测 10 条短 prompt,记录 TTFT(首 token 延迟)/ TPOT(每 token 延迟)/ 总耗时 / 完成 token 数 | notes/perf/day2.md + perf/bench.py | 10 条样本跑完;TTFT / TPOT / 总耗时三项均有统计;输出含 model / precision / context |
| Day 3 | 同模型对比 Ollama(CPU / GPU)、vLLM(如果 GPU 可用):写 notes/perf/engines.md 三引擎对比表 | notes/perf/engines.md | 三引擎在同一 prompt / 同一 max_tokens 下可比;显存 / 吞吐 / 延迟差异可解释 |
| Day 4 | 量化档位对比:用本地 7B 模型跑 FP16 与 INT4(ollama show --modelfile 看量化),记录显存与延迟;用同一组中文评测 prompt 验证质量不掉档 | notes/perf/quant.md | FP16 与 INT4 的显存差 ≥ 30%;中文 prompt 输出无明显退化 |
| Day 5 | 加并发:用 asyncio 或 concurrent.futures 跑 4 / 8 并发,记录 P50 / P95 / P99 / 错误率 | notes/perf/concurrency.md | P95 相比单请求没有数量级飙升;错误率 < 1% |
| Day 6 | 调 max_tokens / temperature / top_p:观察对首 token 与总延迟的影响;保存调参记录 | notes/perf/tuning.md | 至少 3 组参数对比;TTFT 与 TPOT 变化幅度有解释 |
| Day 7 | 综合压测报告:把 6 天数据汇总到 report.md,含硬件 / 模型 / 量化 / 上下文 / 并发 / TTFT / TPOT / P95 / 吞吐;做一次”误归因”复盘(说明哪些结论其实是配置差异而非引擎差异) | notes/perf/report.md | 报告按模板填齐;每条结论附原始日志;至少指出 1 条可能误归因的判断 |
第一周复盘要求
每日把硬件、模型、量化、上下文、并发、TTFT / TPOT / P95 原始数据写到 notes/perf/ 对应日期的 md;Week 1 结束前用 30 分钟复盘:哪些参数被误以为是引擎差异?哪些瓶颈其实在 prefill 而非 decode?
5. 阶段通用验收
- 不看答案独立重写压测脚本与显存预算估算;
- 用自己的话解释 prefill / decode / KV cache / continuous batching 的瓶颈差异;
- 画”请求 → prefill → KV cache → decode → batch → 指标”的数据流图;
- 测试冷启动、长短请求混合、高并发、超长 context、显存不足 5 类边界;
- 准备至少 3 组模型 / 量化 / 并发组合并贴出实际数据;
- 记录 TTFT、TPOT、总延迟、P50/P95/P99、tokens/s、显存占用;
- 能修改已有基准(换模型 / 换量化 / 改并发)并复现压测报告。
交付存放:第 3 项的图、第 5 项的实际数据、第 6 项的指标统一存到
notes/perf/或 README 对应章节,便于复盘与综合项目引用。
6. 最终验收(学完 1~2 周后)
- 完成 ≥ 3 引擎对比(Ollama / TGI / vLLM 或其中可用的子集),同模型同 prompt 同 max_tokens 下可比;
- 完成 ≥ 2 档量化对比(FP16 / INT8 或 FP16 / INT4),附中文评测 prompt 的质量观察;
- 完成 ≥ 3 组并发压测(1 / 4 / 8 并发),报告 P50 / P95 / P99 / 错误率 / 吞吐;
- 完成 ≥ 1 份综合报告
report.md,记录硬件 / 模型 / 量化 / 上下文 / 并发 / TTFT / TPOT / P95 / 吞吐; - 至少识别 2 条”误归因”风险(如:把延迟差异归因到引擎,但实际是 max_tokens 或上下文长度不同);
- 完成 1 个综合项目(见 §7),含 README、压测报告与调优记录;
- 能用 15 分钟讲清楚 prefill / decode、KV cache、量化、连续批处理、TTFT / TPOT / P95 之间的依赖与权衡。
API key 配置与回退方案
- 首选:本地
ollama serve+qwen2.5:7b起 OpenAI-compatible 端点(默认http://localhost:11434);如需云端对照,加export ANTHROPIC_API_KEY=...跑claude-haiku-4-5做参照。 - 回退 1(无 GPU):CPU 上跑
qwen2.5:1.5b或更小模型;TTFT / TPOT 数字与 GPU 不可比,报告里必须注明”CPU baseline”。 - 回退 2(纯离线):把
bench.py改成对一段固定 prompt + 固定 max_tokens 反复调用,只统计耗时与 tokens,不依赖模型回复质量;用于验证压测脚本本身。 - 回退 3(无 Ollama):用
transformers+accelerate+bitsandbytes直接跑模型(参考 §9.7.2 显存预算),但要意识到transformers不带 continuous batching,单请求延迟可比,吞吐显著低于 vLLM / TGI。 - 记录:每次实验保存
{model, version, quantization, gpu, cuda, driver, context, max_tokens, concurrency, ttft_p50, ttft_p95, tpot_p50, tpot_p95, tokens_per_s, vram_gb}到notes/runs/<date>.json,便于复现。
7. 综合项目
首选:推理引擎选型与压测报告(必做:三引擎对比 + 多档量化 + 多并发压测 + report.md + 至少 2 条”误归因”识别)。
备选:本地 RAG 推理优化(必做:固定 RAG pipeline + 量化 + prefix cache + 压测 + 报告)。
任何综合项目都必须包含:
- 需求说明:要解决的问题、用户故事、输入输出约定(含冷启动、长短请求混合、超长 context);
- 数据流与引擎选择理由:为什么选这套引擎 / 量化 / 并发;记录权衡;
- 算法与策略说明:显存预算、KV cache 复用、continuous batching、prefix cache 触发条件;
- 模块化源码:
bench.py/vram.py/tuning.py等职责单一; - 边界与安全测试:冷启动、超长 context、显存不足、并发打满、量化档位误选;
- 运行说明:
make bench或清晰python -m ...命令,注明环境变量与依赖; - README:项目介绍、运行步骤、目录结构、复盘(踩过的坑、可改进点);
- 评测与复盘记录:
notes/retrospective.md(用时、难点、收获、下一步)。
notes/ 与 README 存放规范
所有”画图(数据流 / 压测拓扑)“和”贴原始数据”类交付物统一存放在项目根目录的 notes/ 子目录或 README 的对应章节;提交时一并带上,避免散落在聊天或临时文件里。综合项目的 notes/ 至少包含:
notes/design.md:硬件、模型、量化、并发配置;引擎 / 量化选型理由;notes/test.md:每组测试数据的输入(prompt / max_tokens / 并发)、期望指标、实际指标与”误归因”识别;notes/retrospective.md:复盘(用时、难点、收获、下一步)。
本主题贡献(Loop 6-D · llm-applications / llm-inference-and-perf)
本主题把”显存预算、量化档位、KV cache 复用、continuous batching、prefix cache”五条独立工程线拧成一条验收闭环,输出可被父主题与下游子主题直接复用的推理压测包。
3 项核心职责
- 显存预算 + 量化:按 GPU 显存(如 A100 80G / H100 80G / L40S 48G)选量化档位(FP16 / BF16 / INT8 / INT4 AWQ / GPTQ),压测给出”模型大小 × 量化 × max_model_len × 并发”的显存表,避免 OOM 误归因。
- KV cache + 连续批处理:用 vLLM / TGI / TensorRT-LLM 的 continuous batching + PagedAttention 复用 KV cache;用 prefix cache(自动前缀命中)减少 system prompt / few-shot 的重复计算。
- 指标采集 + misattribution:压测时同时打 TTFT(首 token 时延)、TPOT(每 token 时延)、端到端 latency、tokens/s、显存峰值,识别”指标好但体感差”的误归因(如 TPOT 稳但首 token 慢)。
4 项交付物
- 显存预算表 + 量化决策树:
vram.py(按模型 + 量化 + 并发算峰值显存)+ 决策树(FP16 → INT8 AWQ → INT4 GPTQ 三档选型),附 A100 / H100 / L40S 三卡实测。 - 压测脚本 + 报告:
bench.py(输入 prompt 长度 / max_tokens / 并发 / 量化 4 维扫描),输出 TTFT / TPOT / latency / tokens/s / 显存峰值 5 项指标表。 - 引擎 + 部署配置:vLLM / TGI / TensorRT-LLM 三选一的 deployment.yaml(含 continuous batching + prefix cache + max_num_seqs),附引擎选型理由(吞吐 / 延迟 / 易用性)。
- 边界测试 + 必读交付:冷启动 / 超长 context / 显存不足 / 并发打满 / 量化档位误选 5 类各 1 例,配
notes/design.md(硬件 + 模型 + 量化 + 并发)+notes/retrospective.md。
3 个验收指标
- 压测覆盖:5 项指标(TTFT / TPOT / latency / tokens/s / 显存峰值)× 4 维扫描(prompt 长度 / max_tokens / 并发 / 量化)≥ 20 组数据。
- OOM / 量化误选识别:5 类边界(冷启动 / 超长 context / 显存不足 / 并发打满 / 量化档位误选)必须命中 ≥ 3 类,误归因排查记录 ≥ 2 条。
- 引擎选型:continuous batching + prefix cache 必须启用,prefix cache 命中率 ≥ 30%(system prompt / few-shot 场景),TTFT P95 ≤ 300ms(中等 prompt + 中等并发)。
8. 推荐开源资料(按角色分工,避免堆链接)
| 阶段 | 角色 | 资料 | 链接 | 用法 |
|---|---|---|---|---|
| 1 | 本地推理 | Ollama 文档 | https://ollama.com/docs | 本地 OpenAI-compatible 端点;学习 / 验证用 |
| 1 | 服务化推理 | vLLM 文档 | https://docs.vllm.ai/ | continuous batching、PagedAttention;高吞吐首选 |
| 1 | 服务化推理 | TGI 文档 | https://huggingface.co/docs/text-generation-inference | Hugging Face 系服务化;Rust 实现,部署与 vLLM 各有取舍 |
| 2 | 量化 | bitsandbytes | https://github.com/bitsandbytes-foundation/bitsandbytes | INT8 / INT4 量化的工程基础;MIT 许可证 |
| 2 | 量化 | AutoGPTQ | https://github.com/AutoGPTQ/AutoGPTQ | GPTQ 量化参考;使用前确认 LICENSE |
| 2 | 量化 | AutoAWQ | https://github.com/casper-hansen/AutoAWQ | AWQ 量化参考;使用前确认 LICENSE |
| 3 | KV cache | vLLM PagedAttention 论文 | https://arxiv.org/abs/2309.06180 | 理解 KV cache 分页与显存节省原理 |
| 3 | 推理原理 | 《LLM Inference Unveiled》 | https://medium.com/@yangyou_berkeley/llm-inference-unveiled-3d-scheduling-101-9bba5bd96f3e | prefill / decode 调度入门;博客参考,引用即可 |
| 4 | 压测工具 | vLLM Benchmark Script | https://github.com/vllm-project/vllm/tree/main/benchmarks | vLLM 自带 benchmark;对照自有脚本 |
| 4 | 压测工具 | OpenAI Evals(性能部分) | https://github.com/openai/evals | 评测 + 压测结合;不要直接当生产评测 |
| 通识 | 模型查阅 | Hugging Face Hub | https://huggingface.co/docs | 查模型卡、tokenizer、推理参数 |
| 通识 | API 查阅 | Anthropic API 文档 | https://docs.anthropic.com/en/api/overview | 查参数、token、限制与价格;用于云端对照 |
许可证提示:复制或参考 vLLM / TGI / bitsandbytes / AutoGPTQ / AutoAWQ 等开源仓库代码前,先打开 LICENSE 确认:vLLM(Apache-2.0)、TGI(Apache-2.0)、bitsandbytes(MIT)、AutoGPTQ(MIT)、AutoAWQ(MIT)。默认做法是读思路后自己重写,而不是复制粘贴;GPL 类代码用于商业 / 闭源项目前请逐条阅读。
硬件记录:每条压测结论必须附
{gpu, vram_gb, cuda, driver, model, quantization, context, max_tokens, concurrency},否则视为不可复现;评测集要兼顾中文与英文,避免”英文 MMLU 高分 → 中文指令微调模型其实崩盘”的归因偏差。
默认使用顺序:先用 Ollama 起本地端点 + qwen2.5:7b 跑最小对话 → 写 bench.py 测 TTFT / TPOT → 引入 vLLM / TGI 做三引擎对比 → 加 INT8 / INT4 量化对照 → 加 4 / 8 并发与 P95 → 调 max_tokens / temperature / top_p → 汇总 report.md 并做”误归因”复盘 → 复盘到 notes/retrospective.md。
9. 学习资料汇聚(v0.3 自包含)
9.1 背景与动机
模型能力只是服务的一部分;推理引擎决定如何调度请求、复用 KV cache 和利用显存。性能优化必须建立可复现基准,否则吞吐提升可能以质量、尾延迟或成本恶化为代价。
9.2 概念地图
flowchart LR
Request[请求] --> Prefill[Prefill]
Prefill --> KV[KV cache]
KV --> Decode[Decode]
Decode --> Batch[批处理/调度]
Model[模型权重] --> Quant[量化]
Quant --> Memory[显存]
Memory --> KV
Batch --> Metrics[吞吐/首 token/总延迟/P95]
9.3 基础知识讲解
主推 vLLM、TGI 和 Ollama 官方文档;备查量化工具和模型仓库说明。先用 Ollama 理解本地服务,再用 TGI/vLLM 做同模型基准;所有结论记录硬件、模型、量化、上下文和并发。
9.4 经典问题与经典案例
| 问题 | 最简答案 |
|---|---|
| 显存不够 | 减小模型/上下文、量化或分片,并重新测质量 |
| 首 token 慢 | 优化 prefill、上下文长度、prefix cache 和批处理 |
| 解码吞吐低 | 检查 batch、并发、GPU 利用率和 memory bandwidth |
| P95 抖动 | 分离冷启动、长短请求,检查排队和 max tokens |
| 量化后质量降 | 用固定评测集比较,不只看主观样例 |
| 本地快、线上慢 | 固定硬件、驱动、引擎版本和请求分布后再归因 |
9.5 学习难点
- 概念难点:prefill 与 decode 的瓶颈不同;分别测首 token 和生成速度。
- 思维难点:吞吐和延迟是服务目标的权衡,不存在脱离负载的“最快”。
- 工程难点:基准条件不一致会让比较失效,必须保存配置和原始结果。
9.6 技术标准与接口
Entity
OpenAI-compatible endpoint、prefill/decode、KV cache、continuous batching、quantization、tokens/s、TTFT、TPOT、P95。
Scope
推理引擎负责模型服务和调度,不保证应用质量、事实性或业务安全;性能指标只在给定负载与硬件下成立。
Structure
必须掌握模型权重精度、显存占用、上下文长度、max new tokens、并发、batch、TTFT、TPOT、总延迟和吞吐。
Ecosystem
Ollama 偏本地易用;TGI 偏 Hugging Face 服务化;vLLM 偏高吞吐调度与兼容 API。版本、GPU、CUDA 和量化格式会改变结论。
Depth Tiers
L0 知道引擎存在;L1 看懂启动参数;L2 能部署并压测;L3 能定位显存/排队/批处理瓶颈;L4 能设计多模型路由和容量规划。本子主题要求 L3。
Source
vLLM、TGI、Ollama 官方文档及对应量化项目文档;版本快照日期:2026-07-28。
9.7 关键代码
9.7.1 TTFT / TPOT / tokens/s 压测采集
# 9.7.1 OpenAI-compatible endpoint 压测:TTFT、TPOT、tokens/s
import time
import urllib.request
import json
ENDPOINT = "http://localhost:11434/v1/chat/completions" # Ollama 示例
PROMPT = "用一句话解释 KV cache。"
CONCURRENCY = 4
N_REQ = 8
def one_request() -> tuple[float, float, int]:
body = json.dumps({
"model": "qwen2.5:7b",
"messages": [{"role": "user", "content": PROMPT}],
"stream": False,
}).encode()
req = urllib.request.Request(ENDPOINT, data=body, headers={"Content-Type": "application/json"})
t0 = time.perf_counter()
with urllib.request.urlopen(req, timeout=60) as r:
first = time.perf_counter()
data = json.loads(r.read())
t1 = time.perf_counter()
out = data.get("choices", [{}])[0].get("message", {}).get("content", "")
completion_tokens = max(1, len(out))
return (first - t0) * 1000, (t1 - first) * 1000 / completion_tokens, completion_tokens
if __name__ == "__main__":
samples = [one_request() for _ in range(N_REQ)]
ttft = [s[0] for s in samples]
tpot = [s[1] for s in samples]
print(f"TTFT mean={sum(ttft)/len(ttft):.1f}ms max={max(ttft):.1f}ms")
print(f"TPOT mean={sum(tpot)/len(tpot):.1f}ms")
9.7.2 显存预算估算 + 量化档位对比
# 9.7.2 显存预算估算(FP16 / INT8 / INT4)
def vram_gb(params_b: float, precision_bytes: float, overhead: float = 1.2) -> float:
"""params_b: 十亿参数;precision_bytes: 2=FP16, 1=INT8, 0.5=INT4;overhead 含 KV/激活。"""
weights = params_b * precision_bytes
return weights * overhead
if __name__ == "__main__":
p = 7.0 # 7B 模型
for name, b in [("FP16", 2), ("INT8", 1), ("INT4", 0.5)]:
print(f"{name}: {vram_gb(p, b):.1f} GB")
9.7.3 真实可运行:Anthropic SDK + 简单的并发吞吐测试
# 9.7.3 用 Anthropic SDK 测一次非流式 + 流式的首 token / 总延迟
# 运行:export ANTHROPIC_API_KEY=...; python perf_anthropic.py
import os
import time
import anthropic
client = anthropic.Anthropic()
PROMPT = "用一句话回答:1+1=?"
t0 = time.perf_counter()
resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=64,
messages=[{"role": "user", "content": PROMPT}],
)
t1 = time.perf_counter()
text = resp.content[0].text
in_tok = resp.usage.input_tokens
out_tok = resp.usage.output_tokens
print(f"非流式: {(t1-t0)*1000:.0f}ms, in={in_tok} out={out_tok}, text={text!r}")
# 流式测首 token
t0 = time.perf_counter()
first = None
with client.messages.stream(
model="claude-haiku-4-5",
max_tokens=64,
messages=[{"role": "user", "content": PROMPT}],
) as stream:
for event in stream:
if getattr(event, "type", "") == "content_block_start":
first = time.perf_counter()
break
stream.until_done()
t1 = time.perf_counter()
print(f"流式首 token: {((first-t0)*1000 if first else 0):.0f}ms 总耗时 {(t1-t0)*1000:.0f}ms")
10. 常见误区
- 用”单请求延迟”做选型:单请求可能 < 1s,但 P95 / 尾延迟才决定线上体感;
- 换引擎不锁模型/版本:HF 升级或量化算法变了,“快 30%” 可能只是换精度换来的;
- 量化只看显存:INT4 显存减半但质量/P95 抖动未必可控,必须跑固定评测集;
- max_tokens 不限制:单请求可能拉满到几秒,调度器排满,尾延迟飙升;
- 不区分 prefill 与 decode:把首 token 慢归到 GPU 不行,先看 prefill 阶段 profiling;
- 冷启动当常态测:刚启动的 KV cache 空,TTFT 偏长,测前要预热;
- Ollama 拿生产用:单进程,无连续批处理;高并发要先上 vLLM / TGI;
- batch_size 拍脑袋:和 max_tokens、上下文长度强相关,必须在同负载下扫一遍;
- 量化档位对中文指令微调模型特别敏感:必须用中文评测集而不是英文 MMLU;
- 压测只发短 prompt:线上 4k / 8k 上下文会拖慢 prefill,分布必须按真实流量配。
11. 所有知识点分类(统一规则)
- 编程语言
- 数据结构与算法
- 计算机基础
- 工程技术
- Web 与后端
- 前端与客户端
- 数据与人工智能
- 项目与职业能力
本计划归属:数据与人工智能 主 + 工程技术 辅。