LLM 推理性能优化实验场 · 用可运行、可验证、可复现的代码拆解推理加速四大支柱:KV Cache、Attention 内核、权重量化、Triton 融合算子。所有基准数据均为本机实测,附复现命令。
与其背"FlashAttention 是 IO 感知算法",不如亲手写出 KV Cache、看着它把解码吞吐提升 3.3 倍、把量化误差压到 0.8%。这个仓库里的每个优化都有正确性测试(结果必须与朴素实现一致)和性能基准(数据必须可复现)。
双设备实测:RTX 3090 24GB(CUDA · torch 2.11+cu128) 与 Apple M-series(MPS · torch 2.14),原始数据在 benchmarks/results/(按设备分目录),全部可用仓库内脚本一键复现。
| prompt 长度 | 无 cache | KV Cache | 加速比 |
|---|---|---|---|
| 512 | 153.6 tok/s | 138.7 tok/s | 0.90x |
| 1024 | 74.7 tok/s | 88.7 tok/s | 1.19x |
| 2048 | 22.6 tok/s | 77.0 tok/s | 3.41x |
| 4096 | 0.2 tok/s | 41.2 tok/s | 179.4x 🚀 |
这张表是本仓库最有讲头的实测:4096 长度下无 cache 重计算要 557 秒,KV cache 只要 3.1 秒——O(T²) 与 O(T) 的差距在长上下文上被 GPU 彻底放大;而 512 长度时 cache 反而慢 10%,因为每步重算前缀的计算量还小,Python 循环 + kernel launch 开销主导。crossover 点的存在本身就是"为什么要有 prefill/decode 分离"的实证。(MPS 上同款 crossover 见
benchmarks/results/kv_cache.md,1024 时 3.26x)
| 序列长度 | naive(O(T²) 显存) | chunked(分块降峰值) | SDPA(融合内核) | SDPA 加速 |
|---|---|---|---|---|
| 512 | 0.207 ms | 0.758 ms | 0.057 ms | 3.6x |
| 1024 | 0.321 ms | 1.954 ms | 0.101 ms | 3.2x |
| 2048 | 1.112 ms | 3.470 ms | 0.205 ms | 5.4x |
| 4096 | 4.452 ms | 6.527 ms | 0.514 ms | 8.7x |
| 8192 | —(跳过) | 22.769 ms | 1.553 ms | 14.7x |
随长度增长 SDPA 优势从 3.6x 拉到 14.7x——融合内核省下的显存读写量与 T 成正比,这正是 FlashAttention "IO-aware" 论文的实测注脚。三种实现数值一致性由测试锁定(rtol 1e-4)。
| 方案 | 权重体积 | 相对误差 | 耗时 |
|---|---|---|---|
| fp16 基线 | 16 MiB | 0 | 0.064 ms |
| INT8(逐通道对称) | 4 MiB(4.0x 压缩) | 0.83% | 0.100 ms* |
| NF4(QLoRA 同款 16 电平) | 4.25 MiB(3.76x vs fp32) | 9.20% | 0.229 ms* |
*按"反量化再计算"教学路径实现;生产引擎将反量化融合进 GEMM。压缩比与数值契约是重点。
从零实现的 draft-verify 循环(perf_lab/speculative.py):小模型草拟 γ 个 token → 大模型一次前向并行验证 → KV cache 回退到接受前缀。金标准测试锁定输出与 target 逐 token 一致。
自研实现(draft = target 的浅层截断,6L×256d target / 2L×256d draft,接受率 100%):
| γ | 每 target 前向产出 | 耗时 | 加速比 |
|---|---|---|---|
| 2 | 3.01 tok | 1.51 s | 1.41x |
| 4 | 5.02 tok | 1.43 s | 1.49x |
| 8 | 8.83 tok | 1.16 s | 1.83x |
γ 越大每次验证摊薄越多,加速比单调上升——算法定性正确。同一个循环在 MPS 上 0.98x(kernel launch 开销主导的小模型上投机解码无从加速),平台决定上限。
真实模型对照(transformers assisted generation,Qwen2.5 家族,数据在 results/):
| 配置 | 结果 | 根因分析 |
|---|---|---|
| 1.5B target + 0.5B draft | 0.86x | draft/target 参数比仅 3:1,draft 成本占比过高 |
| 7B target + 0.5B draft | 0.55x | 7B 基线仅 13 tok/s——瓶颈在 generate 编排开销而非 GPU 算力(3090 正常应 30+),此基线下 draft 往返永远亏 |
诚实的负结果:HF transformers 的 assisted decoding 在未优化推理栈上不产生收益;投机解码的落地前提是 vLLM 级别的高效 base forward。自研实现证明算法正确性,真实模型实验定位了工程边界——两层结论合起来才是完整认知。(复现:
examples/speculative_qwen_gpu.py --target Qwen/Qwen2.5-7B-Instruct)
| 内核 | PyTorch eager | 本仓 Triton | 结果 |
|---|---|---|---|
| RMSNorm | 0.136 ms | 0.180 ms | 0.75x |
| Softmax | 0.186 ms | 0.219 ms | 0.85x |
诚实结论:数值验证通过(rtol/atol 1e-2),但没打过 PyTorch eager。两个原因:triton-windows 是社区移植版(编译质量低于 Linux 官方版),且本内核是未调优的教学实现(num_warps/block size 未扫参);PyTorch eager 底层本来就是高度优化的 CUDA 库。Linux + 官方 Triton + 调优才是这类内核的正确打开方式——正确性已由测试锁定,性能留作 Roadmap。
perf_lab/
├── kv_cache.py # 预分配 KV Cache:append/commit 两段式,O(1) 附加新 token
├── model.py # 极简 decoder-only Transformer(RMSNorm+GELU MLP),支持增量解码
├── attention.py # naive / chunked / SDPA 三实现 + 等价性保证
├── quant.py # INT8 逐通道量化、NF4 块量化、QuantizedLinear 即插即用
└── bench.py # 计时框架(warmup+中位数,CUDA/MPS 同步),JSON+Markdown 报告
triton_kernels/ # Triton 融合 RMSNorm / Softmax(CUDA 环境运行,见 benchmarks/bench_triton.py)
benchmarks/ # 五个基准脚本 + results/ 实测数据
tests/ # 22 个测试:数值等价、误差上界、贪心输出一致、内存线性
uv sync # 或 pip install torch numpy pytest
uv run pytest # 22 tests passed
uv run python benchmarks/bench_kv_cache.py --prompts 512 1024 2048 4096 --d-model 512 --layers 8 --heads 16 # 复现 3090 表
uv run python benchmarks/bench_attention.py --seq 512 1024 2048 4096 8192 # 复现 attention 表
uv run python benchmarks/bench_quant.py # 复现量化数据
uv run python benchmarks/bench_triton.py # CUDA GPU + tritonCPU / MPS / CUDA 均可运行核心测试与前三项基准(设备自动选择);Triton 内核仅在 CUDA 上运行,无 GPU 时优雅跳过。
- KV Cache:
test_cached_and_uncached_logits_identical要求逐 token 喂入与整段前向的 logits 完全一致——cache 不是"大约对",是位级对齐的数学等价。 - INT8:误差上界有闭式证明
max_err ≤ absmax/254(逐通道对称量化),测试直接验证该界。 - NF4:块内 absmax 缩放 + 16 电平最近邻,实测相对误差 9.2%——与论文报告的 4-bit 量级一致。
- Chunked attention:任意 chunk 大小与 naive 完全一致(rtol 1e-4),证明分块只是重排计算、不改变语义。
- Paged KV Cache(vLLM 式分页,支持 prefix sharing)
- Speculative decoding(草稿模型 + 验证接受)
- CUDA C++ GEMM tiling(配合 nsight 计算屋顶线分析)
Q1:KV Cache 179.4x 的加速比是怎么测出来的?会不会是挑好看的数字?
同一模型(8L×512d、29.5M 参数)在同一张 RTX 3090 上做两组逐 token 解码:无 cache 时每步重算整个前缀(总计算量 O(T²)),有 cache 时只算新 token(O(T))。prompt=4096 + 生成 128 token 时实测无 cache 557.7 秒、有 cache 3.1 秒,比值即 179.4x,原始数据在 benchmarks/results/3090/kv_cache.{json,md},复现命令见快速开始。同一张表里 prompt=512 时 cache 只有 0.90x(反而慢 10%,kernel launch 开销主导)——crossover 的存在本身才是这条实验的重点,快慢两端都如实报告。
Q2:自研投机解码 1.83x,换真实模型(HF assisted generation)为什么反而 0.55x?
两个实验回答两个问题。自研实现隔离测量"一次前向并行验证 γ 个草稿 token"的机制收益,金标准测试锁定输出与 target 逐 token 一致;真实模型对照里,Qwen2.5-7B-Instruct 在 3090 上的朴素 generate 基线只有约 13 tok/s(瓶颈在编排开销而非 GPU 算力,正常推理栈应 30+),draft 往返成本叠加在这样的基线上必然为负(0.55x,数据在 results/speculative_qwen_7b_3090.json)。合起来的结论是:投机解码的落地前提是 vLLM 级别的高效 base forward。
Q3:自研投机解码"接受率 100%"是不是自欺欺人?
是实验设计而非巧合。draft 直接取 target 的浅层截断(共享 embedding + 前 2 层,6L×256d target / 2L×256d draft),且两者都在确定性的数数序列上预训练过,浅层网络足以跟上 target 的贪心输出(见 benchmarks/bench_speculative.py)。这样设计是为了把"接受率"固定为 100%,单独测量验证摊薄这一个变量;真实 draft 分布失配下的收益边界,由 HF 真实模型对照实验补充定位。
Q4:我没有 RTX 3090,能在 CPU / 其他设备上复现吗?
核心测试与 KV Cache / Attention / 量化三个基准在 CPU / MPS / CUDA 上自动选设备(perf_lab/bench.py),快速开始里的命令直接可跑;Triton 基准只在 CUDA + triton 环境运行,无 GPU 时优雅跳过。绝对数值会随设备、驱动与 torch 版本浮动,但与设备无关的结论——crossover 形态、SDPA 加速比随序列长度增长、INT8 误差闭式上界 max_err ≤ absmax/254、NF4 的 4-bit 误差量级——由 22 个测试锁定,在任何设备上都应成立。
Q5:INT8 误差 0.83%、NF4 误差 9.2%,这么大的误差还能用?
看用途。INT8 逐通道对称有闭式误差上界 absmax/254,测试直接验证该界,可直接替换 fp16 前向;NF4 用 16 电平换 3.76x(vs fp32)的权重体积压缩,9.2% 相对误差与 QLoRA 论文报告的 4-bit 量级一致——QLoRA 的用法是把 NF4 权重冻结作基底、靠高精度 LoRA 增量吸收误差,而不是拿裸反量化精度硬扛。本仓锁定的是量化/反量化的数值契约(压缩比 + 误差上界);表中带 * 的耗时按"反量化再计算"教学路径计,生产引擎会把反量化融合进 GEMM。
- 平台覆盖有限:全部实测来自两台设备——RTX 3090 24GB(Windows · torch 2.11+cu128 · triton-windows 社区移植版)与 Apple M-series(MPS · torch 2.14)。未覆盖 A100/H100、Linux 官方 Triton 工具链、多卡与批处理服务化场景(attention 基准固定 B=1),绝对数字不能外推到其他架构或 batch 配置。
- 主力实验对象是 ≤30M 参数的教学模型:KV Cache / Attention / 投机解码的吞吐数字来自自定义 TinyGPT(默认 vocab 4096),用于放大机制信号;生产级模型只做了 Qwen2.5 家族的 HF assisted generation 对照,未在 vLLM / TensorRT-LLM 等生产推理栈上复测。
- Triton 结果是下界不是上限:0.75x / 0.85x 的"没打过 eager"结论来自未调优内核(num_warps / block size 未扫参)加 triton-windows 社区工具链,不能据此评价 Triton 本身或 Linux 官方工具链的性能上限。
- 量化基准是教学路径:按"反量化再计算"计时,不含反量化融合进 GEMM 的生产做法;且只量化权重,未覆盖 activation / KV cache 量化。压缩比与误差契约成立,端到端推理加速需另行验证。
- 投机解码负结果的适用范围:0.55x / 0.86x 针对的是"transformers generate 编排在 3090 上未优化"这一具体场景(7B 基线仅 13 tok/s),该结论随 transformers 版本、硬件与推理栈而变化,不能据此断言投机解码在高效推理栈上无收益。
- 快照式基准,非 CI 自动化:
results/与benchmarks/results/是一次性人工实测快照;基准脚本与模型配置强耦合(如 KV 表对应 8L×512d + 默认 vocab 4096),升级 torch / transformers 版本或改动模型结构后需重跑,数字以脚本实测为准。
MIT