ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LLM Infra 论文研读指南:从计算优化到显存管理的工程落地实践

LLM Infra 论文研读指南:从计算优化到显存管理的工程落地实践

1. LLM Infra 论文研读的切入逻辑

1.1 为什么“读论文”在 LLM Infra 领域格外重要

LLM Infra 这个方向有个很尴尬的特点:工程实践跑得比论文快,但论文里埋着的坑,工程上往往要花几个月才能踩明白。我刚开始接触大模型推理优化的时候,第一反应是直接上 vLLM 跑一遍 benchmark,结果发现吞吐量死活上不去,后来翻了一圈论文才知道问题出在 KV Cache 的显存碎片上——PagedAttention 那篇论文其实早就把这个问题讲透了,只是我当时没看。

这就是 LLM Infra 论文的价值所在。它不像纯算法论文那样追求 SOTA 指标,而是解决“怎么让模型跑得更快、更省、更稳”这类工程问题。你读一篇 FlashAttention 的论文,收获的不是某个新模型结构,而是一种把注意力计算从 O(N²) 显存降到 O(N) 的思路,这个思路可以直接迁移到自己的推理框架里。

适合读这类论文的人其实很明确:做推理部署的工程师、搞训练框架的开发者、需要做模型压缩和量化的同学,以及想理解“为什么我的 GPU 利用率只有 30%”的运维人员。你不需要有很深的数学背景,但需要对 GPU 架构、内存层级、并行计算有基本概念。

1.2 LLM Infra 论文的四大分类框架

我把这个领域的论文分成四类,这个分类不是学术界的标准分法,而是从工程落地角度出发的实用分类:

第一类是计算优化类,核心解决“算得快不快”的问题。代表工作包括 FlashAttention 系列、各种 Attention 变体、算子融合方案。这类论文的关键指标是 MFU(Model FLOPs Utilization)和实际吞吐量。

第二类是显存管理类,解决“装不装得下”的问题。PagedAttention、GQA/MQA、KV Cache 量化、ZeRO 系列都属于这一类。这类论文要看的是显存占用曲线和最大 batch size。

第三类是并行策略类,解决“怎么把模型摊到多卡上”的问题。Tensor Parallel、Pipeline Parallel、Sequence Parallel、Expert Parallel 的各种组合方案。关键指标是通信开销占比和扩展效率。

第四类是服务调度类,解决“多个请求怎么排队”的问题。Continuous Batching、Chunked Prefill、Prefill-Decode 分离、投机解码。这类论文看的是 TTFT(Time To First Token)和 TPOT(Time Per Output Token)。

这个分类的好处是,你遇到具体问题时可以直接定位到对应类别去找论文,而不是漫无目的地刷 arXiv。

1.3 读论文的正确姿势:从问题出发而非从论文出发

我见过太多人读论文的方式是:打开 arXiv,按时间排序,从最新的一篇开始读。这种方式效率极低,因为 LLM Infra 的论文之间关联性很强,你不理解 PagedAttention 就看不懂 Chunked Prefill 的设计动机。

我的建议是反过来的:先明确你当前遇到的工程问题,然后带着问题去搜论文。比如你发现推理时 GPU 显存利用率只有 60%,那就去搜 KV Cache 管理相关的论文;你发现多卡扩展效率在 4 卡以上急剧下降,那就去看并行策略和通信优化的工作。

读的时候重点看三个部分:问题定义(作者认为的瓶颈是什么)、核心洞察(作者发现了什么别人没发现的东西)、权衡分析(这个方案牺牲了什么换来了什么)。实验部分可以快速扫过,因为不同硬件环境下的数字差异很大,但权衡分析是通用的。

2. 计算优化类论文的核心脉络

2.1 FlashAttention:从 IO 感知到算法重排

FlashAttention 是 LLM Infra 领域被引用最多的论文之一,但很多人只记住了“它很快”,没理解它为什么快。核心洞察其实就一句话:注意力计算的瓶颈不在 FLOPs,而在 GPU 显存层级之间的数据搬运。

GPU 的显存层级大致是:SRAM(片上缓存,约 20MB)→ HBM(高带宽显存,40-80GB)→ 主机内存。SRAM 的带宽是 HBM 的 10 倍以上,但容量极小。标准注意力计算需要把完整的注意力矩阵(N×N)写到 HBM 再读回来,这个读写量是 O(N²) 的。FlashAttention 的做法是把注意力计算分块(tiling),每块在 SRAM 内完成计算,只把最终结果写回 HBM,把 HBM 访问量降到 O(N²/M),其中 M 是 SRAM 大小。

具体实现上,FlashAttention 用了两个关键技巧:在线 softmax(online softmax)和重计算(recomputation)。在线 softmax 解决的是分块计算时 softmax 归一化因子需要全局信息的问题,通过维护运行时的最大值和指数和,可以在不看到完整行的情况下逐步计算 softmax。重计算则是在反向传播时不存储中间的注意力矩阵,而是重新计算一遍,用计算换显存。

注意:FlashAttention 的加速效果在长序列(N > 512)时才明显,短序列下因为分块开销反而可能略慢。我实测下来,N=128 时 FlashAttention 比标准注意力慢约 5%,N=2048 时快 2-3 倍。

2.2 FlashAttention-2 与 FlashAttention-3 的改进方向

FlashAttention-2 的改进主要集中在并行度和工作划分上。FA1 在序列维度上的并行度不够,导致 GPU 的 SM(Streaming Multiprocessor)利用率不高。FA2 把注意力计算在序列维度上切分得更细,让每个 thread block 处理更小的块,提高了 occupancy。另外 FA2 减少了非矩阵乘法的 FLOPs,把 softmax 中的一些操作合并到了矩阵乘法里。

FlashAttention-3 则是针对 Hopper 架构(H100)的特性做了深度优化,核心是利用了TMA(Tensor Memory Accelerator)和WGMMA(Warpgroup Matrix Multiply-Accumulate)指令。TMA 可以异步地在 HBM 和 SRAM 之间搬运数据,WGMMA 则允许一个 warpgroup(128 个线程)协同完成矩阵乘法。FA3 还引入了生产者-消费者模式,一部分 warp 负责数据搬运,另一部分负责计算,进一步隐藏了内存延迟。

从工程角度看,FA3 的代码复杂度比 FA1 高了一个数量级,如果你不是在做极致的推理优化,FA2 的性价比更高。FA3 在 H100 上的加速比 FA2 大约高 1.5-2 倍,但在 A100 上几乎没有提升。

2.3 算子融合与 CUDA Graph 的工程实践

除了注意力本身,LLM 推理中还有大量小算子(LayerNorm、激活函数、残差连接等),这些算子的计算量不大,但 kernel launch 的开销不可忽略。算子融合的思路是把多个小算子合并成一个 kernel,减少 launch 次数和中间结果的读写。

CUDA Graph 是另一个层面的优化:把整个推理过程(或一个 decode step)的所有 kernel launch 录制成一个图,后续执行时直接回放,消除了 CPU 端的 launch 开销。这个优化在 decode 阶段特别有效,因为 decode 阶段每个 step 的计算量很小,CPU launch 开销占比很高。

我实测过一个 7B 模型的 decode 场景,不开启 CUDA Graph 时 CPU launch 开销占总时间的 15-20%,开启后降到 2% 以下。但 CUDA Graph 有个坑:它要求输入输出的内存地址固定,所以需要预分配固定大小的输入输出 buffer,对于变长序列需要做 padding 或使用专门的变长 kernel。

3. 显存管理类论文的工程落地

3.1 PagedAttention 与 KV Cache 的碎片化问题

PagedAttention 的核心洞察来自操作系统的虚拟内存管理:KV Cache 的显存碎片化会导致大量浪费。在标准实现中,每个请求的 KV Cache 需要预分配一个连续的显存块,长度按最大可能序列长度算。但实际上大部分请求的序列长度远小于最大值,导致预分配的空间大量闲置。更糟糕的是,不同请求的生命周期不同,频繁的分配和释放会产生外部碎片。

PagedAttention 把 KV Cache 分成固定大小的 block(比如 16 个 token 一个 block),每个请求的 KV Cache 由多个不连续的 block 组成,通过一个 block table 来索引。这样显存分配的最小单位从“整个序列”变成了“一个 block”,碎片率大幅降低。论文中的数据显示,PagedAttention 可以把显存浪费从 60-80% 降到 4% 以下。

工程上落地 PagedAttention 需要注意几点:block size 的选择会影响性能,太小则 block table 开销大,太大则碎片率上升,通常 16 或 32 是比较平衡的选择;block table 的查找需要额外的间接寻址,在注意力计算时需要通过 block table 把逻辑位置映射到物理位置,这个映射的开销在长序列下不可忽略。

3.2 GQA 与 MQA:用少量质量损失换显存

Grouped Query Attention(GQA)和 Multi Query Attention(MQA)解决的是 KV Cache 随序列长度线性增长的问题。标准 Multi-Head Attention(MHA)中,每个注意力头都有独立的 Key 和 Value 投影,KV Cache 的大小是 2 × num_heads × head_dim × seq_len × batch_size × dtype_size。

MQA 的做法是所有注意力头共享一组 Key 和 Value,KV Cache 直接缩小到 1/num_heads。GQA 则是折中方案,把注意力头分成若干组,每组共享一组 KV。比如 Llama 2 70B 用了 8 组,KV Cache 缩小到 1/8。

从论文的实验看,MQA 在质量损失上比较明显,GQA 在 group 数大于 4 时质量损失基本可以忽略。工程上的选择通常是:如果显存极度紧张且能接受一定质量损失,用 MQA;否则用 GQA,group 数取 4-8。

提示:GQA 的 KV Cache 缩小效果只在推理时体现,训练时因为需要完整的注意力矩阵,显存节省有限。另外 GQA 的模型不能直接加载 MHA 的权重,需要做 head 维度的平均或复制。

3.3 KV Cache 量化:INT8 与 INT4 的取舍

KV Cache 量化的思路很直接:把 FP16 的 KV Cache 量化到 INT8 或 INT4,显存占用直接减半或降到 1/4。但 KV Cache 的量化比权重量化更敏感,因为 Key 和 Value 的分布随层数和位置变化很大,而且量化误差会在注意力计算中被放大。

INT8 量化通常用 per-token 或 per-channel 的 scale,精度损失在大多数任务上小于 1%。INT4 量化则需要更精细的方案,比如分组量化(group-wise quantization)加上 outlier 处理。论文中常见的做法是把 Key 中的 outlier 通道单独用 FP16 存储,其余通道用 INT4。

工程上,INT8 KV Cache 量化已经比较成熟,vLLM、TensorRT-LLM 都支持。INT4 的 KV Cache 量化还在演进中,我实测下来在长序列任务上质量损失有时会超过 3%,需要根据具体任务评估。

4. 并行策略与服务调度论文的实战解读

4.1 Tensor Parallel 与 Pipeline Parallel 的组合策略

Tensor Parallel(TP)是把单个矩阵乘法切分到多卡上,每张卡计算一部分,然后通过 All-Reduce 汇总结果。TP 的优点是扩展效率高,缺点是通信量大,每层都需要 All-Reduce。Pipeline Parallel(PP)是把模型的不同层放到不同卡上,数据以 micro-batch 的形式流水线执行。PP 的优点是通信量小(只在层边界传激活值),缺点是会有流水线气泡(bubble)。

工程上的常见组合是:节点内用 TP,节点间用 PP。因为 TP 的 All-Reduce 通信量大,需要高带宽的 NVLink 或 NVSwitch;PP 的通信量小,可以用 InfiniBand 甚至以太网。比如一个 70B 模型部署在 8 卡 A100 上,通常用 TP=8;如果部署在 16 卡上,可以用 TP=8 × PP=2。

TP 的切分维度也有讲究。对于 Transformer 层,通常切分的是注意力头的维度和 FFN 的中间维度。切分后每张卡上的计算量是原来的 1/TP,但通信量是 2 × hidden_size × batch_size × seq_len × dtype_size(两次 All-Reduce)。当 TP 增大时,通信占比会上升,扩展效率下降。实测 TP=8 时扩展效率通常在 70-80%,TP=16 时降到 50-60%。

4.2 Continuous Batching 与 Chunked Prefill 的调度逻辑

Continuous Batching(也叫 iteration-level scheduling)解决的是传统静态 batching 中“短请求等长请求”的问题。在静态 batching 中,一个 batch 的所有请求必须同时开始、同时结束,如果其中一个请求生成了 1000 个 token,其他只生成 10 个 token 的请求也要等它结束。Continuous Batching 的做法是每个 decode step 都重新组 batch,已经完成的请求立即退出,新的请求立即加入。

Chunked Prefill 则是解决 prefill 和 decode 相互阻塞的问题。在标准实现中,prefill 阶段(处理输入序列)和 decode 阶段(生成输出)是串行的,一个长 prefill 会阻塞所有 decode 请求,导致 TTFT 和 TPOT 都变差。Chunked Prefill 把长 prefill 切成多个 chunk,每个 chunk 和 decode 请求一起组 batch,这样 decode 请求不会被长时间阻塞。

这两个技术通常一起使用,vLLM 的默认调度策略就是 Continuous Batching + Chunked Prefill。工程上需要注意的是 chunk size 的选择:太小则 prefill 效率低,太大则 decode 延迟高。通常 chunk size 取 512 或 1024 比较平衡。

4.3 Prefill-Decode 分离架构的权衡

Prefill-Decode 分离是最近比较热的方向,核心思路是把 prefill 和 decode 放到不同的实例上,因为这两个阶段的资源需求完全不同:prefill 是计算密集型,需要高 FLOPs;decode 是显存带宽密集型,需要高带宽。

分离架构的好处是可以分别针对两个阶段做优化:prefill 实例可以用大 batch、高并行度;decode 实例可以用小 batch、大 KV Cache。另外分离后可以独立扩展,比如 prefill 用 2 张卡、decode 用 6 张卡。

但分离架构也有代价:KV Cache 需要从 prefill 实例传到 decode 实例,这个传输开销在长序列下不可忽略。另外架构复杂度上升,需要额外的调度层来协调两个实例。我实测下来,在序列长度小于 1024 时,分离架构的收益不明显;序列长度大于 4096 时,TTFT 可以降低 30-50%。

5. 论文复现与工程落地的常见坑

5.1 论文指标与实测指标的差异来源

论文里的性能数字和你在自己环境里跑出来的数字往往差很多,原因通常有这几个:硬件差异(论文用 H100,你用 A100)、软件版本(CUDA、PyTorch、推理框架的版本不同)、测试配置(batch size、序列长度、精度不同)、基线选择(论文可能选了一个较弱的基线)。

我踩过最典型的一个坑是:某篇论文声称比基线快 3 倍,我复现后发现只快 1.5 倍。后来仔细看论文的实验配置,发现它的基线没有开 FlashAttention,而它的方案开了。这种“不公平对比”在论文中并不少见,读的时候一定要看清楚基线配置。

另一个常见问题是论文只报了最优情况下的数字。比如某篇量化论文只报了 batch size=1 时的加速比,但实际部署中 batch size 往往大于 1,量化在 batch size 大时加速效果会下降。所以读论文时要特别关注实验部分的配置说明,不要只看结论。

5.2 从论文到生产:需要额外考虑的因素

论文验证的是“在理想条件下这个方案能 work”,但生产环境要考虑的东西多得多:容错(某张卡挂了怎么办)、动态负载(请求量波动大时怎么扩缩容)、多模型共存(多个模型共享 GPU 时怎么隔离)、监控(怎么知道当前瓶颈在哪)。

举个例子,PagedAttention 在论文里假设所有请求都是独立的,但生产环境中可能有优先级调度、超时取消、请求抢占等需求。这些在论文里不会讲,需要自己在工程上补。vLLM 在这方面做了很多工作,比如支持请求优先级、支持抢占后重新调度,但这些细节在论文里是看不到的。

还有一个容易被忽略的点是数值稳定性。论文里的方案通常在 FP16 或 BF16 下验证,但生产环境可能混用 FP8、INT8、INT4,不同精度下的数值行为差异很大。比如 FlashAttention 在 FP16 下没问题,但在 FP8 下可能需要额外的 scale 处理,否则会溢出。

5.3 常见问题速查表

问题现象可能原因排查方向
吞吐量远低于论文batch size 太小检查调度策略,尝试 Continuous Batching
显存溢出KV Cache 未优化检查是否用了 PagedAttention/GQA/量化
多卡扩展效率低通信瓶颈检查 TP/PP 配置,确认 NVLink 是否启用
TTFT 高prefill 阻塞尝试 Chunked Prefill 或 Prefill-Decode 分离
decode 速度慢显存带宽瓶颈检查 KV Cache 大小,尝试量化或 GQA
精度下降量化误差累积检查量化方案,尝试 per-channel 或混合精度

提示:排查性能问题时,先用 nsight systems 或 torch profiler 抓一个 trace,看清楚时间花在哪里,不要凭感觉猜。我见过太多人一上来就调 batch size,结果发现瓶颈在 CPU 端的 tokenization 上。

6. 个人实操体会与后续方向

读 LLM Infra 论文这件事,我最大的体会是:不要追求读完,要追求用上。这个领域的论文数量增长极快,你不可能全部读完,但你可以把每一篇读过的论文都变成自己工具箱里的一件工具。比如读了 PagedAttention,就在自己的推理服务里把 KV Cache 管理改成 block 方式;读了 Chunked Prefill,就把调度器改成 chunk 粒度。

另一个体会是论文和代码要对照着看。很多论文的细节在正文里讲不清楚,但在开源代码里有完整实现。比如 FlashAttention 的在线 softmax,论文里只有几行公式,但看代码里的m_i和l_i的更新逻辑,一下子就明白了。我通常的流程是:先读论文理解核心思路,然后看代码确认实现细节,最后自己写一个简化版验证理解是否正确。

后续如果继续深入,我会重点关注两个方向:一是异构硬件上的 LLM 推理,比如 CPU+GPU 混合、NPU 上的推理优化,这方面的论文还比较少,工程实践走在前面;二是多模态模型的 Infra,视觉编码器的计算模式和 LLM 差异很大,现有的优化方案不一定适用,需要新的思路。

最后分享一个小技巧:读论文时建一个自己的知识库,每篇论文记三个东西——解决什么问题、核心方法一句话、我能在哪里用上。这个知识库不需要很复杂,一个 Markdown 文件就够了。积累到几十篇之后,你会发现很多论文之间是有关联的,比如 PagedAttention 和 Chunked Prefill 都涉及 block 粒度的管理,vLLM 的调度器就是把这两个思路结合起来的。这种关联性的理解,是单读一篇论文得不到的。

返回列表