Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

llm-perf-lab

LLM 推理性能优化实验场 · 用可运行、可验证、可复现的代码拆解推理加速四大支柱:KV Cache、Attention 内核、权重量化、Triton 融合算子。所有基准数据均为本机实测,附复现命令。

tests Python License: MIT

与其背"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/(按设备分目录),全部可用仓库内脚本一键复现。

KV Cache:从 launch 开销主导到 O(T²) 主导的完整 crossover(RTX 3090 · 8L×512d · 29.5M 参数)

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)

Attention 后端(RTX 3090 · fp16 · B=1 · H=8 · hd=64)

序列长度 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)。

权重量化(RTX 3090 · 2048×2048 Linear · batch 64)

方案 权重体积 相对误差 耗时
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。压缩比与数值契约是重点。

投机解码(RTX 3090)

从零实现的 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)

Triton 融合算子(3090 + triton-windows · 4096×4096 fp16)

内核 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 + triton

CPU / 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),证明分块只是重排计算、不改变语义。

🛣️ Roadmap

  • Paged KV Cache(vLLM 式分页,支持 prefix sharing)
  • Speculative decoding(草稿模型 + 验证接受)
  • CUDA C++ GEMM tiling(配合 nsight 计算屋顶线分析)

常见问题(FAQ)

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。

⚠️ 局限与已知问题(Limitations)

  • 平台覆盖有限:全部实测来自两台设备——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 版本或改动模型结构后需重跑,数字以脚本实测为准。

License

MIT

About

LLM 推理性能优化实验场:KV Cache、attention 内核、INT8/NF4 量化、Triton 融合算子,全部基准本机实测可复现

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages