
前阵子把 DeepMind 那份大模型推理指南翻出来重读里面有句话让我印象特别深大模型推理的瓶颈从来不在算力而在显存带宽。第一次看到这个结论时我其实是有点抗拒的毕竟大家买卡看的第一指标永远是算力谁会去关心 HBM 的带宽数字但后来自己动手压测了几轮从数据中心卡一路试到 Jetson AGX Orin 这类边缘设备才彻底认同了 DeepMind 的判断。这篇文章我就想把自己对这个结论的理解、验证过程以及围绕带宽瓶颈做推理优化时踩过的坑和总结出来的方法完整分享出来。这篇内容适合正在做大模型推理部署、做性能调优的工程师也适合那些刚接触 llama.cpp 或 vLLM、想搞清楚“为什么 token/s 上不去”的开发者。我会先讲清楚“为什么是带宽”这个核心问题再拆解 KV Cache、量化、批处理、投机采样这些工程手段背后的带宽逻辑最后用一套 Jetson AGX Orin 上的 llama.cpp 实战记录把理论和落地串起来。1. 一个反直觉的判断算力很富余带宽才是卡点1.1 DeepMind 推理指南的核心结论到底说了什么DeepMind 那份指南的核心观点简单说就是在自回归解码阶段模型的每一步生成都受限于“从显存里读取参数”的速度而不是“计算这些参数”的速度。这里有个非常反直觉的点大多数人会默认 GPU 是算得慢才导致推理慢但实际上在大模型 decode 这个场景里GPU 的计算单元大部分时间都在空转真正拖后腿的是 HBM 显存把权重数据搬运到计算核心的那条路。我用一个 7B 模型来举例FP16 精度下权重文件大概是 14GB。每生成一个 token理论上都需要把这 14GB 权重全部从 HBM 读一遍。在 H100 SXM 上显存带宽是 3.35TB/s纯访存时间算下来大概是 4.2 毫秒。但一个 token 的前向计算量才多少大概 14 GFLOPsH100 的 FP16 稠密算力是 989 TFLOPs算完它只需要 0.014 毫秒。两下一比算力利用率连 0.5% 都不到这还不是瓶颈是什么我一开始也怀疑这个算法是不是太理想化了毕竟真实推理还有算子开销和 kernel launch 延迟。但后来用 ncu 抓过一轮 profiling结果显示 decode 阶段的 SM 利用率确实低得可怜而 HBM 读带宽几乎被吃满。那一刻我才真正接受了 DeepMind 的判断模型越大参数越多权重搬家花的带宽成本就越离谱算力反而成了最不缺的资源。1.2 为什么多数人在算力误区里打转这个问题其实不难解释大家选卡时习惯看规格表上的 TFLOPs这数字又大又直观谁都会觉得算力高就代表推理快。但 TFLOPs 衡量的是“计算核心的理论极限”而实际推理性能往往由“数据能不能及时送到计算核心”决定。放到生活里类比一下算力就像是餐厅里刀工极好的厨师带宽则是后厨到前厅的传菜员。如果传菜员一次只能端一盘菜那厨师再快也没用顾客感受到的上菜速度完全由传菜速度决定。还有个原因是大模型的“计算量幻觉”。模型参数量动辄几十亿上百亿大家自然觉得每次前向都要做海量计算。但 decode 阶段 batch size 通常很小实际计算量远没有想象中那么大。反而是权重的体积摆在那里7B 模型的 FP16 权重就有 14GB70B 模型直接 140GB光把这些数据搬一遍就是一笔巨大的访存开销。所以只要模型权重没有被完整缓存在片上 SRAM 里带宽瓶颈就一定会出现。1.3 一次压测给我的直观冲击理论讲多了容易飘我还是习惯用数据说话。之前我在同一台机器上对比过两张卡跑同一个 7B Q4 模型一张是 RTX 4090一张是 A100 80GB。4090 的算力大约是 82.6 TFLOPsA100 是 312 TFLOPs如果按算力论A100 应该快接近四倍。但实际跑下来4090 大概 90 token/sA100 反而只有 60 token/s 上下。原因就在于 4090 虽然算力低但 GDDR6X 带宽 1.01TB/sA100 虽然算力高但 HBM2e 带宽只有 2TB/s同时它要加载的 KV Cache 和上下文更长实际访存开销更大。这张表我后面还会反复引用先把几个典型平台的带宽和对应 7B Q4 模型的理论上限列出来平台显存/内存带宽7B Q4 权重体积单 token 理论生成上限H100 SXM3.35 TB/s约 4.2 GB约 790 token/sA100 80GB2.00 TB/s约 4.2 GB约 470 token/sRTX 40901.01 TB/s约 4.2 GB约 240 token/sJetson AGX Orin 64GB204.8 GB/s约 4.2 GB约 48 token/s注意这只是权重搬运的理论上限真实场景还要算上 KV Cache 读取、算子开销和访存效率损耗实测通常只能达到理论值的 50% 到 70%。但这个数量级已经足够说明问题带宽低一个数量级token/s 就低一个数量级跟算力关系不大。2. 显存带宽为何成了大模型推理的命门2.1 自回归解码的每一步都要搬一遍全部权重要理解带宽瓶颈先得搞清楚自回归解码的机制。大模型生成文本是一个 token 一个 token 来的每生成一个新 token都要把完整的权重矩阵从头到尾做一次前向计算。这意味着无论 batch size 是多少只要模型权重没有变每一轮 decode 就必须把全部权重从显存搬到计算单元。这不是优化能绕开的问题除非把权重完全放进片上缓存但以现在的 SRAM 容量哪怕 7B 模型也是天方夜谭。我做视觉分析时喜欢用一个词叫“全局数据重用率低”。大模型推理恰恰就是这种模式权重整体只被“读一遍”没有任何重复利用的机会。对比一下传统的卷积神经网络同一个卷积核可以在空间维度上反复滑动复用权重在片上缓存的命中率很高。但 Transformer 的矩阵乘法是逐层串行每层的结果都要写回 HBM 再被下一层读取中间几乎没有数据重用的空间。还有一个加重问题的因素现在的模型动不动就是长上下文。上下文一旦拉长KV Cache 的体积就会线性膨胀。比如 7B 模型跑 32K 上下文KV Cache 可能超过 2GB这一部分也要参与每轮访存。于是每生成一个 token不仅要搬权重还要搬 KV Cache。权重是固定的但 KV Cache 越大带宽压力越大这也是长上下文推理性能暴跌的核心原因之一。2.2 算术强度与 Roofline 模型一眼看穿 Memory Bound工程上判断一个算子到底是计算密集还是访存密集有个非常成熟的分析框架叫 Roofline 模型。核心就一个指标算术强度单位是 FLOPs/byte也就是“每读入一个字节的数据能支撑多少次浮点运算”。CPU 和 GPU 上都有一个“转折点”低于这个点的负载就是访存受限高于这个点才是算力受限。算一下 decode 阶段的算术强度就明白了。7B 模型一次前向计算量约 14 GFLOPs访存量约 14GB算术强度大概只有 1 FLOPs/byte。而 H100 的转折点大概在 300 FLOPs/byte 左右差了整整两个数量级。这意味着我们处在一个极端访存受限的区间算力再高也没用因为数据送不过来。这个模型也帮我想通了很多现象。比如为什么小 batch 的 decode 那么慢因为 batch1 时算术强度本来就低得可怜为什么加 batch 后吞吐能成倍上涨因为权重只读一次被多个请求分摊后有效算术强度会成倍提升。Roofline 模型相当于给调优工作画了张地图先判断自己在哪个瓶颈区间再决定用哪套优化策略不然很容易白忙一场。2.3 Prefill 和 Decode一个吃算力一个吃带宽大模型推理可以拆成两个阶段Prefill 和 Decode。Prefill 阶段处理用户输入的整段 prompt一次性并行计算所有输入 token这时的计算量非常密集batch size 就是 prompt 长度GPU 的算力能被充分利用属于典型的计算受限场景。Decode 阶段则完全不同它逐 token 自回归生成batch 通常不会太大权重访存占据了绝对主导属于带宽受限场景。这两个阶段的属性差异直接决定了推理系统的架构优化方向。比如很多生产系统会用分离式部署让 Prefill 和 Decode 跑在不同的实例上因为两者对硬件资源的需求完全不同。我把两个阶段的特征整理成一个对照表方便直观感受阶段单次处理的 token 数主要瓶颈典型优化手段Prefillprompt 长度通常数百到数千算力FLOPs并行计算、Tensor Parallel、算子融合Decodebatch size通常个位数到几十显存带宽KV Cache、量化、批处理、投机采样理解了这个区别很多东西就自动串起来了。为什么量化对 decode 的提升那么明显因为它直接压缩了权重访存体积正好打在带宽瓶颈上。为什么投机采样在长上下文场景特别有效因为它用一个小模型做草稿生成大模型只做验证大幅减少了大模型 decode 的次数也就减少了大模型“搬权重”的次数。3. 推理工程的优化方向所有招数都在绕着带宽转3.1 KV Cache用显存容量换时间但长上下文会反噬KV Cache 算是 Transformer 推理最基础的优化。它的思路非常直白既然每一轮自回归生成都要用之前所有 token 的 Key 和 Value 做注意力计算那就把历史 token 的 K、V 矩阵缓存下来避免重复算一遍历史部分的注意力。这个优化效果立竿见影没有 KV Cache 的话7B 模型的 decode 速度会慢到没法用。但 KV Cache 不是免费的午餐。它是典型的“用容量换时间”需要额外占显存。更关键的是在每次 decode 时不仅权重要被读一遍KV Cache 也要被读一遍。上下文越长KV Cache 体积越大带宽占用就越高直接吃掉原本可以用来搬运权重的带宽。这也是为什么长上下文场景下即使显存装得下 KV Cachetoken/s 还是肉眼可见地往下掉。工程上有两个解决方向。一个是优化 KV Cache 的布局比如 vLLM 里的 PagedAttention把 KV Cache 切成固定大小的块避免显存碎片浪费。另一个是 KV Cache 量化比如把 K、V 从 FP16 压到 INT8访存体积直接减半。但这两招只能缓解不能根治因为长上下文本身的 KV 数据量摆在那里只要注意力计算还需要读它们带宽压力就会持续存在。3.2 量化降精度就是在降带宽在大模型推理优化里量化可以说是收益最高、落地最广的手段。很多人把量化理解成“牺牲精度换速度”这个说法不够准确。量化的核心效果其实是把权重体积压缩了FP16 的 7B 权重是 14GBINT8 降到 7GBINT4 再降到 3.5GB。权重体积变小每轮 decode 需要从 HBM 搬的数据量也跟着成比例下降预算出来的延迟自然就下来了。在 llama.cpp 生态里GGUF 格式已经成了事实标准量化等级从 Q2 到 Q8 有好几档。我自己的实操经验是Q4_K_M 是边缘设备上性价比最高的档位速度比 FP16 快 3 到 4 倍而质量损失几乎感知不到。Q8_0 质量更好但体积只比 FP16 小一半带宽优势就没那么大了。对于 7B 这种规模的模型Q4 是甜点位再低到 Q3 或 Q2虽然速度更快但语言能力和逻辑推理能力会肉眼可见地变差。量化还有一个重要价值容易被忽略它能让模型塞进更小显存的设备。举个例子70B 模型 FP16 要 140GB 显存INT4 只要 35GB一台 48GB 的消费级工作站就能跑。这个能力直接决定了模型能在多少种硬件上落地是推理工程里“性价比”的代名词。3.3 动态批处理让一次权重读取服务更多请求批处理是服务端推理里提升吞吐的最强手段原理也是从带宽入手的。关键洞察是同一模型权重只需要在显存里存一份多个请求同时 decode 时权重只需要被读一次而这一次读取的计算结果可以被所有请求共享。也就是说batch size 从 1 涨到 4权重访存成本不变但一次前向算出了 4 个 token有效吞吐直接翻了 4 倍。这个逻辑放到算术强度上就是batch1 时14 GFLOPs 要搬 14GB 数据算术强度 1batch16 时计算量变成 224 GFLOPs访存量还是 14GB 加 KV Cache算术强度翻了 16 倍。虽然还是达不到算力受限的区间但已经明显向算力侧移动吞吐自然会提升。批处理在实际工程里不是简单地把请求凑一起就完事。vLLM 的 continuous batching 做得更精细不是等一个 batch 全部生成完再接收新请求而是每步 decode 后已经完成的请求立刻退出空出的位置马上被新请求填上。这样能最大化权重的复用效率而不是傻等最慢的请求拖垮整个 batch 的延迟。这也是 vLLM 比传统简单批处理吞吐高出一截的根本原因。3.4 投机采样与算子融合减少“搬数据”的次数投机采样是一个特别有意思的优化思路完全绕开了“搬权重”这个动作。它先让一个小模型快速生成一串候选 token生成结果通常又快又稳然后再让大模型一次性检查这串 token 的合理性只接受验证通过的“连续片段”。这样大模型每做一次完整前向就能“白捡”好几个 token等于把大模型的高成本前向次数压到了最低省下的就是大模型的访存开销。这个方案在延迟要求不高的场景很好用但也不是万能药。小模型生成草稿本身也有延迟如果草稿质量太差验证经常被拒绝收益就被抵消了。实际工程里通常会让草稿模型跟目标模型共享部分词表同时控制草稿长度在 4 到 8 个 token 左右兼顾触发概率和验证成本。算子融合则是另一个维度。FlashAttention 的核心思想是分块计算注意力中间结果始终留在片上 SRAM 里不需要反复写回 HBM。这种“少搬几趟数据”的思路把注意力算子的访存开销从 O(N^2) 级别降到了 O(N) 级别对长序列提升尤为显著。所有绕开带宽的优化本质上都在做同一件事让每次“搬数据”都能干更多的活。4. 边缘设备实战Jetson AGX Orin 部署 llama.cpp4.1 为什么选 AGX Orin 和 llama.cpp讲完理论来一个完整的边缘部署实战。我选择 Jetson AGX Orin 64GB 作为实验平台原因主要有三个第一它的功耗只有 60W却能提供 204.8GB/s 的内存带宽是边缘设备里“带宽天花板”的代表正好用来验证带宽瓶颈的极限第二它的架构是 CPU 和 GPU 共享同一片内存天然适合跑大模型推理第三它跑 llama.cpp 非常方便生态成熟度高踩坑率相对低。llama.cpp 选择它的理由也很充分纯 C/C 实现代码轻量、易交叉编译对显存占用控制精细而且原生支持 GGUF 量化和 CPU/GPU 混合推理。虽然 vLLM 和 TensorRT-LLM 性能更强但它们在 Jetson 上的部署复杂度高得多对轻量边缘推理场景来说llama.cpp 是性价比最高的选择。4.2 部署步骤与关键参数环境准备阶段我建议直接用 NVIDIA 官方提供的 JetPack 6.0 镜像里面已经预装了 CUDA、cuDNN 和 TensorRT省去手动搭环境的痛苦。llama.cpp 官方仓库里提供了针对 Jetson 的构建说明核心步骤是打开 CUDA 支持后编译git clone https://github.com/ggerganov/llama.cpp cd llama.cpp mkdir build cd build cmake .. -DGGML_CUDAON -DCMAKE_CUDA_ARCHITECTURES87 make -j$(nproc)这里有个非常容易踩的坑CMAKE_CUDA_ARCHITECTURES 必须显式设成 87对应 AGX Orin 的 Ampere 架构。如果不写cmake 会默认按宿主机显卡架构编译最后在 Orin 上跑起来会直接报 “no kernel image available” 的错误。模型方面我用的是 Qwen2.5-7B-Instruct 的 GGUF 量化版本。下载 Q4_K_M 档位的文件后启动命令的关键参数如下./llama-cli -m /path/to/qwen2.5-7b-instruct-q4_k_m.gguf \ --n-gpu-layers 999 \ --threads 8 \ -c 4096 \ -b 512 \ --temp 0.7参数含义--n-gpu-layers 控制模型层放入 GPU 的数量999 表示全部层都放--threads 设置 CPU 线程数-c 是上下文长度-b 是 batch size。在 AGX Orin 上实测全部层放 GPU 比纯 CPU 推理快 5 倍以上因为 GPU 侧能利用更高的内存带宽。4.3 实测数据分析带宽如何决定性能上限跑一轮 benchmark 看数据。模型是 Qwen2.5-7B-Instruct Q4_K_M权重体积约 4.4GBAGX Orin 的内存带宽 204.8GB/s。理论上限算一下4.4GB / 204.8GB/s约 21.5 毫秒出一个 token也就是 46 token/s。实测的 llama.cpp 输出分为两段prefill 阶段和 generation 阶段。prefill 阶段速度取决于 prompt 长度通常能跑到 300 token/s 以上因为它是计算密集generation 阶段才是我们要关注的 decode 吞吐实测大概在 28 到 32 token/s 之间达到了理论值的 60% 到 70%。我把不同量化档位的实测数据整理成表量化档位权重体积实测 token/sdecode带宽利用率估算Q8_0约 7.5 GB约 18 token/s66%Q4_K_M约 4.4 GB约 30 token/s65%Q3_K_M约 3.3 GB约 38 token/s62%12 token/s 的差距对交互体验的影响是非常直观的。Q4_K_M 大概两三秒出一个短句Q8_0 用户会明显感到卡顿。这个数据也再次印证了核心结论在带宽受限的场景里权重体积几乎决定了速度上限精度反而成了次要矛盾。5. 常见问题排查与调优实录5.1 为什么 token/s 低得离谱先查内存频率和 GPU 利用率如果实测的 token/s 远低于理论估算值的 50%我一般会按下面的顺序排查。先看设备的带宽是否真正跑满在 AGX Orin 上可以用 tegrastats 查看内存控制器频率如果它没有跑到最高频率token/s 会一直上不去。有一次我发现 Orin 的内存频率被锁在低频档调整电源模式到最高性能档后token/s 直接翻倍。再查 GPU 利用率decode 阶段 GPU 利用率不应该很高一般 50% 到 70% 都属于正常。如果 GPU 利用率接近 100%说明可能在偷懒用算力硬扛需要检查是不是 weight 没有全部放 GPU或者 batch size 设置得太大反而触碰了算力上限。反过来如果 GPU 利用率低到 10% 以下且 token/s 依然很低那多半是单线程瓶颈需要调大 --threads 或者确认 CPU 侧有没有其他进程抢占带宽。5.2 量化后输出变差怎么在速度和质量之间找平衡量化最大的副作用是精度损失尤其在数学推理和代码生成任务上表现明显。我的经验是如果业务对输出质量要求高优先尝试 Q8_0如果显存或带宽实在紧张再降到 Q5_K_M 或 Q4_K_M。Q3 以下的档位只适合闲聊类场景不建议用在严肃用途上。还有个技巧是量化感知训练但这对普通工程团队来说成本太高。更实用的方案是做“混合精度”权重主体保持 Q8 或 FP16只对注意力层的 Q、K、V 部分做 INT8 量化因为这部分对精度的影响相对可控。在 llama.cpp 里这是通过 mmq 参数和不同的 GGUF 结构实现的效果通常比全局 Q4 更好。5.3 批处理上去了单请求延迟却变高了这是服务端推理里最常见的矛盾加 batch 能提升吞吐但单个请求的延迟也会上升。原因是 batch 变大后每个请求分到的算力和带宽都被摊薄了decode 的每一步都需要等整个 batch 算完。如果业务对延迟敏感比如在线聊天需要限制 batch 上限或者在延迟和吞吐之间做加权权衡。vLLM 的 continuous batching 对这个问题缓解得比较好但也不是没有代价。它的调度开销比静态批处理更高如果单个请求特别长还是会拖慢整个 batch 的尾部延迟。我在生产环境里的经验是用 P99 延迟作为主要监控指标而不是追求最大化平均吞吐这样能保证大多数用户的实际体验。5.4 判断当前系统是计算瓶颈还是带宽瓶颈的实用方法最后分享一个判断瓶颈类型的野路子方法不需要 profiling 工具。先跑一遍推理记录当前带宽利用率和 GPU SM 利用率。然后只做一件事把权重量化到更低档位比如从 Q8 降到 Q4其他参数不变。如果 token/s 几乎没变说明系统是算力受限如果 token/s 明显提升说明是带宽受限。这个方法的逻辑很简单量化只压缩了权重体积不会提升算力如果量化后速度明显变快说明之前就是被权重搬运卡住了。另一个更简单的判断是看 GPU 的显存控制器利用率和 SM 占用率曲线。如果显存控制器长期处于 90% 以上而 SM 利用率只有 50% 以下那基本可以断定瓶颈在带宽。用 ncu 抓 kernel 级别的数据会更准确但对于大多数场景上面这个野路子方法已经足够指导优化方向了。我在 AGX Orin 上跑 Qwen2.5-7B 那一周最大的感受是大模型推理性能调优这件事本质上就是在跟显存带宽做博弈。所有主流的优化手段量化也好、KV Cache 也好、连续批处理也好最终都落在同一个逻辑上——要么减少数据传输量要么提高每次数据搬运的利用率。理解了这条主线再去看各种推理框架的技术选型和文档很多设计决策就都能看懂了。如果你的推理性能离预期差得很远别急着换卡先用带宽估算一下理论上限再检查瓶颈到底出在哪个环节往往能省下一大笔算力预算。