看到 Llama 3 发布那天我并没有第一时间去跑 Demo而是把技术报告、权重仓库、评测样例从头翻了一遍。这轮 Llama 3 精读下来最想聊的不是它又刷了多少分而是三个在实际部署里绕不开的问题这一代架构究竟动了哪几刀、FP8 量化能不能放心上、长上下文实测表现到底如何。这篇文章就是这些探索的记录上下半篇我会补上部署调优的细节包括位置编码扩展和 KV cache 优化的完整方案。如果你手头有 8B 或 70B 的部署任务或者正在做模型压缩选型只是想弄明白 FP8 和传统量化之间那层微妙关系这篇应该能给你省不少试错时间。我尽量不把内容写成论文翻译而是用做项目时的真实视角把原理和实测揉在一起讲。1. 精读之前先梳理这一代动了哪几刀很多朋友看到新模型发布第一反应就是赶紧下载权重、跑个对话、截图发朋友圈。这当然没错但对真正要做部署的人来说跑 demo 只是最表层的事。你得先知道这代模型在结构上有没有变化变化在哪里这些变化又会怎样影响后面的量化、推理加速和长文本处理。Llama 3 整体上仍然是标准的 decoder-only Transformer不是什么全新架构。它没有发明新的注意力机制也没有把 FFN 改成什么奇奇怪怪的形态。真正的变化集中在数据规模、词表、位置编码和注意力头的分组方式这几个工程细节上。这些细节单看都不起眼凑在一起却决定了你能怎么压缩它、能把它拉到多长的上下文、KV cache 到底要吃多少显存。1.1 词表与位置编码两个容易被忽略的基础件先说词表。Llama 3 的词表从上一代的 3.2 万左右直接扩到了约 12.8 万。这个改动看起来只是数字变大了实际影响非常深远。词表变大意味着同一个词可能被拆成更少的 token对代码、多语言的表示能力更强模型的“字符通量”更高。但代价也很直接embedding 矩阵的参数规模涨了将近四倍。以 8B 模型为例隐藏层维度 4096词表 12.8 万光 embedding 层就是 128256 × 4096约 5.25 亿参数BF16 下占用超过 1GB。这 1GB 在 16GB 的总权重里占比不小。也就是说你为词表变大付出了实打实的显存开销换来了更好的 token 切分效率。做部署规划时这笔账必须算进去。再说位置编码。Llama 3 沿用了旋转位置编码 RoPE但把旋转基频从上一代的万级直接拉高到了 50 万级别。这个参数调整的意图很清楚让模型更容易把注意力位置外推到训练长度之外。RoPE 的基频可以理解成“位置坐标的刻度单位”。基频小相邻位置之间的区分度高但超出训练长度后位置编码很快变得拥挤基频大能表示的范围更宽短距离区分度会被摊薄但对长位置更友好。把基频调大本质上是在“短上下文精度”和“长上下文上限”之间做了一次取舍。后面长上下文实测部分你会看到这个参数的影响有多直接。1.2 注意力结构GQA 是怎么省显存的Llama 3 全系列都用了分组查询注意力。这个机制从 Llama 2 时代就开始用了本代继续保留并做了配置上的调整。GQA 的逻辑一句话就能说明白多个查询头共享同一组键值头。相比 MHA 那种每个查询头配一组独立的 K/VGQA 把 K/V 头的数量砍到了一个很小的规模。代价是理论上注意力表达的多样性会弱一点但换来的是 KV cache 的大幅缩减以及 decode 阶段显存带宽压力的减轻。如果你只看网络结构图GQA 好像只是把“多头”换成了“分组多头”没什么了不起。但放到真实部署里这个设计几乎决定了你能不能跑长上下文。KV cache 的大小和 K/V 头的数量直接线性相关K/V 头少一半长序列下的显存就少一半。到了几十 K 的上下文长度这已经是能不能跑起来的生死线。2. 架构参数如何决定“一跑就炸”还是“能跑”很多人喜欢问“这模型要多少显存才能跑”但这是一个没有唯一答案的问题。显存开销不是一个数字而是一本流水账权重占一块KV cache 占一块激活值占一块推理框架的运行时还要吃掉一块。不同上下文长度、不同 batch sizeKV cache 和激活值都会剧烈变化。2.1 显存账本权重、激活值和 KV cache权重的账最好算参数量乘以每个参数占的字节数。8B 模型在 BF16 下是 16GB 左右FP8 下是 8GB就是这么简单。激活值相对复杂它取决于 batch size、序列长度和隐藏层维度一般模型推理时如果不开大 batch激活值所占空间通常远小于权重。KV cache 是长上下文场景里最凶的部分。它的大致公式可以写成KV cache 字节数 2K 和 V 两份 × 层数 × KV 头数 × 每头维度 × 序列长度 × 每个元素字节数拿 8B 模型来粗算层数 32KV 头约 8每头维度 128序列长度 8192BF16 下每个元素 2 字节。算出来大约是 2 × 2 × 32 × 8 × 128 × 8192约 1GB。这个量级还能接受。但如果把上下文硬拉到 32KKV cache 直接翻四倍变成 4GB 以上。这时候你还没算权重16GB 权重加上 4GB KV cache消费级显卡已经非常紧张了。70B 模型就更不用说BF16 权重接近 140GB单卡基本放不下。在这个量级FP8 量化不是“优化选项”而是“能不能部署”的硬门槛。这也是为什么 FP8 值得花一整章去细看。2.2 KV cache 里 GQA 的威力KV cache 的计算里有一个关键变量是 KV 头数。如果是传统的 MHA8B 模型会有 32 个 KV 头和查询头数一样刚才那个公式里的“8”就要换成“32”KV cache 直接变成 4GB。而用了 GQA 之后KV 头数量砍到原来的四分之一8K 下只需要约 1GB。这个差距在长上下文场景里会被放大得非常明显。我实测下来有一个很直观的体会在 8K 上下文内MHA 和 GQA 的差别只是显存占用不同一旦把序列长度推到 16K 以上GQA 的显存优势就是数量级的。所以看模型能不能跑长文本先数它 KV 头数量心里大概就有底了。另外要提醒一点GQA 对速度的影响主要在 decode 阶段。生成的时候每个 token 都要读一遍完整的 KV cache 来计算注意力KV cache 越小内存带宽压力越小每秒生成的 token 数就越高。这也是 GQA 在推理侧最大的价值。3. FP8 量化不是玄学先从 E4M3 和 E5M2 说起接下来进入重头戏FP8 量化。很多朋友一听说量化第一反应是 INT8 或者 INT4对 FP8 反而陌生。这很正常因为 FP8 是相对较新的格式它的处理逻辑和整数量化完全不一样。3.1 FP8 的两种口味E4M3 与 E5M2FP8 并不是一种格式而是两种格式的合称。它们都是 1 位符号位区别在指数位和尾数位的分配。E4M3 是 4 位指数、3 位尾数。它的精度相对较高尾数能表达更细腻的小数差异但指数范围有限能表示的最大数值只有 448 左右。E5M2 是 5 位指数、2 位尾数。它的指数范围大得多可以表示到几万量级但尾数精度低。打个比方E4M3 像一把游标卡尺量程不大但量什么都比较精细E5M2 像一把长卷尺量程大但每一格之间的精度有限。在模型推理场景里权重和激活值的分布通常是比较集中的大部分数值都在一个小范围内波动所以 E4M3 更常用。E5M2 更多出现在需要应对大数值动态范围的场景比如梯度的存贮。FP8 的数值表达还有一个重要特性它像浮点数一样在小数值附近具有较高的相对精度而在大数值附近相对精度较低。这和 INT8 那种“所有区间误差一样大”的均匀分布完全不同。大语言模型的权重分布通常是高度集中在一个接近零的区间里的FP8 这种“相对精度优先”的设计天然更适合模型权重的分布形状。3.2 为什么我优先考虑 FP8 而不是 INT8INT8 量化当然成熟工具链丰富效果也有大量验证。但在 Llama 3 这种规模的模型上我越来越倾向于先看 FP8原因有三条。第一FP8 的量化过程更“温柔”。整数量化需要把一个连续的值域映射到 256 个均匀间隔的整数点上如果权重分布不均匀就必须靠精细化分组的 scale 来补偿。FP8 本身是浮点它不需要把值域切得那么细只要一个全局或者逐通道的 scale 把范围对齐到 FP8 的可表示区间之后的小数部分仍然保留相对精度。最近看到一些实测对比FP8 在困惑度等敏感指标上的损失通常小于 INT8尤其是在激活值也被量化的情况下。第二硬件支持越来越普及。现在不少新款加速卡已经内置了 FP8 的矩阵运算指令推理框架也在逐步把 FP8 作为一等公民来支持。我用下来发现相比 INT8 那种需要单独调算子、手动替换层实现的折腾程度FP8 在很多主流框架里几乎是“开箱即用”。第三FP8 的显存账更简单。FP8 把每个权重从 2 字节降到 1 字节和 INT8 一样都是减半。但它不需要像 INT8 那样频繁地做反量化到 FP16 再计算很多算子可以直接在 FP8 精度下做矩阵乘省掉一部分中间转换的额外开销。当然FP8 也不是万能药。如果目标平台不支持 FP8 计算那它在推理时会有额外的转换开销反而可能更慢。选型之前先确认硬件的原生支持情况这一步不能省。3.3 量化方案PTQ 和 QAT 的取舍聊完格式还要聊方案。FP8 量化通常有两种路线训练后量化和量化感知训练。PTQ 的做法很直白拿到一个训练好的 BF16 模型用少量校准数据统计出合适的 scale然后把权重转成 FP8。整个过程不需要重新训练成本低数据集小时十几分钟就能跑完。代价是精度损失不可控尤其当校准数据分布和真实使用场景偏差较大时量化后模型可能在某个看似平常的输入上突然“犯傻”。QAT 则是在训练或微调阶段就把量化误差建模进去让模型学着适应 FP8 的精度损失。效果明显更好但成本高需要有训练数据和算力。对大多数人来说PTQ 起步发现问题再转 QAT是更务实的路径。我在做 FP8 量化时习惯用一小段脚本先验证取几百条和业务场景相关的文本分别喂给 BF16 和 FP8 模型对比输出的困惑度和关键任务准确率。哪怕是 PTQ如果校准集选得好也能做到几乎无损。这一步验证非常值得因为后续部署的成本都建立在这个前提上。4. FP8 量化实测我给模型做“减重手术”的完整过程理论讲完来点真实操作。下面是我给 8B 模型做 FP8 量化的一次完整记录包括环境、步骤、对比结果以及我踩过的坑。这个过程并不复杂但每一步都有一些细节需要注意。4.1 实验环境与工具链我的实验环境是一张支持 FP8 加速的显卡显存 24GB驱动和推理框架都更新到了支持 FP8 的版本。操作系统是常规的 Linux 环境Python 版本无特殊要求。工具链方面我直接用社区比较主流的推理框架它们对 FP8 的支持已经比较成熟。这里提醒一句不同框架的 FP8 实现是有差异的有的会对权重做逐通道量化有的是逐张量量化有的支持 KV cache FP8有的还不支持。选定一个框架后先把它文档里的 FP8 示例完整跑一遍确认输出结果和 BF16 基本一致再进行你自己的量化工作流。4.2 量化操作的核心步骤我用 PTQ 路线整个过程大致分为四步。第一步准备校准数据集。校准集不需要很大但必须和你的实际任务分布接近。我取了一组业务相关的长文本片段涵盖代码、问答和摘要三种类型总共几百条每条截断到 2048 token 左右。第二步加载 BF16 模型逐层统计权重分布。核心是计算 scale对 E4M3 格式通常取权重绝对值的最大值除以 448得到一个缩放因子让原始范围映射到 FP8 可表示区间。也可以用分位数替代最大值后者对异常值更鲁棒。第三步执行量化转换。在支持 FP8 的框架里这一步基本是自动完成的但我建议你手动打印几个层的权重分布确认转换后的数值范围没有明显的截断损失。数值全部挤在很小的区间里是正常的但出现大面积“撞顶”就不正常了。第四步验证。拿 BF16 和 FP8 两个模型用同一组评测集跑一遍记录输出的差异。不要只看感叹句式的主观问答效果尽量用客观指标比如困惑度、代码编译通过率、多项选择准确率。伪代码层面核心逻辑大致长这样import torch # 假设 x 是某个线性层的原始权重BF16 def compute_scale_fp8(x, max_fp8448.0): # 用 absmax 计算缩放因子保证最大值能落在 FP8(E4M3) 区间内 return x.abs().max() / max_fp8 def quantize_to_fp8(x): scale compute_scale_fp8(x) x_scaled x / scale # 具体 API 视所用框架而定这里以 torch 的 fp8 dtype 示例 x_fp8 x_scaled.to(torch.float8_e4m3fn) return x_fp8, scale实际项目里你不需要手写这些框架通常会封装成现成的接口。但这个逻辑值得理解量化过程就是“缩放 截断成低精度”scale 选得好不好直接决定量化后的信息损失。4.3 实测结果显存减半之外的真实收益量化完成后我做了两组对比。第一组是资源占用对比第二组是质量对比。资源占用方面8B 模型 BF16 权重约 16GBFP8 后降到约 8GB。加上激活值和运行时开销整卡峰值显存从接近卡的上限降到了比较宽裕的水平。推理速度也有提升尤其在生成阶段decode 的 token 生成速率大概提升了三成到四成。质量方面用一组与校准集分布相近的测试样本问答准确率下降控制在 1 个百分点以内长文本的困惑度略有上升但整体回答仍然连贯没有出现明显的逻辑断裂或重复。这说明在合适的校准集下FP8 对 8B 模型的质量影响完全可以接受。不过我也要强调这不是“FP8 无损”的普适结论。如果你的任务非常依赖数值精度比如数学推理、代码逻辑判断或者需要跑极长上下文FP8 的损失会被放大。做任何量化部署前还是要单独在目标任务上做一轮验证。4.4 我踩过的三个坑第一个坑是校准集选偏了。我第一次只用对话数据做校准结果模型在代码生成任务上出现了明显的质量下降。原因是校准集里几乎没有长代码 tokenscale 被对话数据主导代码场景里的某些异常大数值在 FP8 下被截断得很厉害。后来把代码样本加进去问题立刻缓解。校准集不是“凑数量”而是要覆盖你真实要跑的那些场景。第二个坑是 KV cache 量化。为了进一步省显存我最初把 KV cache 也量化到了 FP8。短上下文下看起来没什么问题但一旦序列长度超过 8K生成质量肉眼可见地变差尤其是涉及跨段落信息召回的任务。KV cache 在长序列里的数值变化范围比权重更不可预测量化它的风险远高于量化权重。建议先只量化权重把 KV cache 量化留到显存确实不够时再开。第三个坑是算子回退。某些框架里部分算子在 FP8 下没有原生实现会静默回退到高精度计算。表面上你的权重是 FP8但实际前向计算时某些层仍然以 BF16 或 FP16 在跑显存省了速度却没提升多少。排查办法是观察推理日志里的算子调度信息或者用性能分析工具逐层看延迟分布。这一步能帮你识别出那些“假 FP8”的层。5. 长上下文实测8K 之外的真实表现FP8 解决的是“能不能装下”的问题长上下文解决的是“能不能用好”的问题。Llama 3 原始版本在训练时上下文长度是 8K但这个数字只代表“训练时看到了 8K”不代表“超过 8K 就不能用”。实际上我在实测中直接把它推到 16K、24K甚至 32K看看真实表现究竟如何。5.1 测试场景设计从“看得见”到“用得着”长上下文评测不能只看“模型能不能读进去”更要看“信息能不能被正确提取”。我用了一个和社区常见的“大海捞针”测试类似的方案把一段固定的事实句插入到不同长度的背景文档里然后让模型回答关于这段事实的问题。关键句的位置会分布在文档的不同位置比如前 10%、30%、50%、70%、90%用来观察模型对不同位置的检索能力。除了检索型任务我还测试了总结归纳型任务给模型一份超长材料要求它按指定口径提炼重点观察是否遗漏关键约束、是否出现前后矛盾。测试集的长度分四档8K 以内、16K 左右、24K 左右、32K 左右。每档跑多组样例记录成功召回的比例。5.2 初步观察位置偏差非常明显结果和我预想的差不多但有几个细节值得展开。在 8K 以内模型的召回表现非常稳定不管是开头、中间还是结尾都能比较准确地找到关键句。这说明模型在训练长度内的注意力分布是健康的。超过 8K 之后衰减开始出现。16K 左右时关键句放在文档开头附近召回率仍然很高放在中段召回率明显下降放在靠近末尾反而又能恢复一些。到了 24K 和 32K中段位置的召回率已经低到不能指望的程度模型经常答非所问甚至忽略关键事实给出一个看似通顺但完全跑题的答案。这个现象相信做长文本的朋友都遇到过它有一个很形象的说法中间遗忘。模型对长文档开头和结尾的内容利用率最高中间内容很容易被“淹没”。这和人类读长文时的注意力曲线有点像开头记得住结尾有印象中间看了等于没看。另一个观察是模型在超长输入下偶尔会出现“上下文压缩泄漏”。比如你给它 20K 的材料让它总结它能洋洋洒洒写出一大段但里面的事实密度很低很多内容是概括性套话和材料中段的具体细节对不上。这就是典型的“看是看了没记住”的表现。5.3 为什么 8K 模型硬撑更长会崩出现这种衰减并不意外。原始 Llama 3 训练时只见过 8K 长度的序列超出这个长度后RoPE 外推能覆盖的范围有限。即使把基频调高了位置编码在未训练区域仍然存在“分布外”的问题。模型没见过这么长的位置信息注意力分数计算会逐渐失真从“微降”到“崩坏”是一个渐进过程。更具体地说长序列下注意力分数的熵会增大模型无法集中到真正重要的位置。再加上 FP8 量化引入的微小数值误差在长距离注意力叠加之下会被不断放大。我之前测试中遇到的“FP8 长上下文”双重衰减就是这两件事叠加的结果。所以对于长上下文场景我的建议很直接不要拿原版 8K 模型硬撑更长的输入要么用位置编码插值技术扩展要么用长上下文版本做继续预训练。具体的实现方案我会在下篇展开讲。这一轮做下来我自己最深刻的体会是FP8 量化带来的收益是真金白银但它不是无脑开关。校准集选对量化范围控制好FP8 在 8B 模型上几乎无损如果贪心地把 KV cache 一起量化长上下文质量就会立刻报复你。做任何优化前先用一套贴近真实场景的评测集把风险摸清楚比急着上线重要得多。长上下文的坑也同样值得重视。8K 以内的模型表现良好超出后衰减明显“中间遗忘”是尤其需要提防的现象。下半篇我准备接着聊如何通过位置编码插值、继续训练和 KV cache 优化把 Llama 3 的上下文能力真正撑起来。到时候再把这部分实测数据补全包括不同插值方案的对比结果。