同一份模型权重,在官方演示里对答如流,部署到本地却频繁表现失常:长上下文读到后半段输出开始漂移,量化版本连工具调用都闭合不了。Level1Techs 论坛上一篇带完整数据的受控实验《Why your local LLM feels dumber than it is》把原因指向了推理软件栈:从注意力算子到量化 kernel,每一层都在改变下一个 token 的概率分布,误差累积到一定程度,模型的实际行为就分岔了。

这篇帖子 8 月中旬发布后登上 Hacker News 首页。作者 thr3e 用一块 RTX PRO 6000 Blackwell、Qwen3.6-27B 官方 BF16 权重和固定版本的 vLLM nightly,把「同一个模型在不同配置下到底偏了多少」拆成了三组可复现实验。本文整理其方法与数据,并补充必要的背景。
推理栈有多厚
从输入文本到输出 token,中间要经过 Tokenizer、Scheduler、Prefill、Decode、Logits、Sampler、Detokenizer 一整条流水线,每个环节都存在实现选择:注意力算子用哪个后端、矩阵乘法走哪个 CUDA kernel、KV Cache 存什么精度。作者统计,其使用的 vLLM nightly 容器镜像里装了 734 个软件包(其中 252 个是 Python 包),每个包都携带自己的缺陷与未文档化的行为差异。模型发布方的「参考实现」跑在另外的硬件和软件上,两边没有任何理由逐位一致。
测量方法本身也有严格定义。作者在 prompt 每 32 个 token 处抓取一次全词表 logits(BF16 存储),事后用 FP64 计算 KL 散度;更直观的指标是 top-1 一致率:给定同一段强制 token 历史,两个配置的贪心 argmax 是否选中同一个词。一次 top-1 翻转意味着该配置在这个位置会走出不同的下一步。同一后端重复运行时,所有隐藏状态的 logits 逐位一致,说明后续看到的分歧全部来自矩阵乘法与累加顺序的差异,与随机噪声无关。
实验一:只换注意力后端,结果就开始分岔

实验对象是 Qwen3.6-27B 的官方 BF16 checkpoint。这个模型是 dense 结构但属混合架构:64 层按「三层 Gated DeltaNet 线性注意力 + 一层全注意力」的节奏排布,只有 16 层全注意力受 attention backend 选择影响。其余变量全部固定:TP1、eager 模式、关闭 CUDA graphs、prefix caching 与 MTP、2k chunked prefill、KV Cache 保持 BF16。
工作负载是一段约 100k token 的真实工作流上下文(含多次工具调用)。选它的理由直接:这段文本不在任何公开基准或训练集里,没有人能针对它做优化或校准。
三组可切换的后端:FlashAttention 2、FlashInfer、Triton Attention,唯一变量就是后端本身。结果分三层:
- 前几千个 token,三个后端对下一个 token 的判断完全一致;
- 进入上下文后半段,分歧开始出现。以 8k token 为窗口统计,每窗 250 个采样点,FlashAttention 2 与 FlashInfer 相对 Triton 基线的 top-1 不一致率随内容波动上升,在 80k 至 96k 区间最为明显;
- 分歧呈簇状分布,与 prompt 内容相关,并非随长度平滑增长。不存在一个统一的「模型在 X k 处崩坏」的临界长度。
浮点运算不满足结合律,不同 kernel 的分块与归约顺序不同,舍入误差的累积路径就不同。单次差异在小数点后几位,长链累乘之后就是可见的行为差异。
实验二:KV Cache 量化,工具调用最先受影响

第二组实验只动一个开关:KV Cache 精度。权重和激活保持 BF16,缓存分别降到 INT8 与 INT4(per-token/per-head 量化),后端固定 Triton。
图中 INT4 的红色曲线在后半程显著爬升,INT8 居中,BF16 基线接近零。落到任务层面,作者记录到一个完全可复现的工具调用错误:BF16 正常完成;INT8 一度出错但最终恢复;INT4 未能恢复。每 8k 窗口 250 个采样点,一次 winner 变化对应 0.4 个百分点,图上数个百分点的漂移意味着几十次「本可以选择另一个词」的位置。
KV Cache 存的是注意力机制中历史 token 的键值投影,供每步解码时回看。它随上下文线性增长,是长对话内存的主要消耗方,因此也是量化省内存的头号目标。这份实验给出的边界:INT8 级别的 KV 量化在真实 agent 工作流里风险可控,INT4 可能直接打断工具调用链。
实验三:五种量化方案对比

第三组实验把 KV Cache 固定在 BF16,比较五种权重量化方案:
| 方案 | 权重/激活 | kernel | 校准数据 |
|---|---|---|---|
| BF16 参考 | BF16 / BF16 | torch.nn.functional.linear | 无需 |
| 官方 FP8 | E4M3 128×128 分块,动态 FP8 激活,lm_head 保持 BF16 | CutlassFp8BlockScaledMMKernel | 发布文件中未识别到 |
| 社区 INT8 W8A16 | 静态对称 channel-wise INT8,BF16 激活 | MarlinLinearKernel | 一次性量化,明确无校准集 |
| NVIDIA NVFP4 | 混合:208 个 FP8 W8A8 目标 + 193 个 NVFP4 W4A16 目标(group size 16) | FlashInferFP8 + MarlinNvFp4 | 未披露 |
| 社区 AWQ W4A16 | 静态非对称 INT4,group size 32,MSE observer | MarlinLinearKernel | STEM and Agentic |
多个方案把 GDN/linear_attn 投影与 lm_head 排除在量化之外,这类结构细节直接影响保真度排序。
结果分层清晰:
- 社区 INT8 W8A16(TheDude 版)保真度最高,超过官方 FP8 与 NVIDIA NVFP4。W8A16 只量化权重、激活保持 BF16,且 GDN 投影未量化,优势有明确的结构解释;
- NVIDIA NVFP4 排在末位:到 88k 上下文处 top-1 翻转率约 50%。这份混合 checkpoint 在该 GPU 上走 Marlin 的 weight-only FP4 压缩路径(vLLM 判定此路径缺乏原生 FP4 支持),checkpoint 内嵌的 FP8 KV 方案也被覆盖为 BF16;
- 任务层面:NVFP4 与 AWQ 两个 4-bit 方案都没能正确闭合工具调用,且敲错了 Cisco 命令行语法。正确命令是 show arp,实际执行的是 show run;FP8 与 INT8 完成了正确调用。
「官方发布」与「保真度最高」在这轮对比中没有画上等号。选量化版本时,位数之外还要看三点:激活是否同步量化、哪些模块被排除、用了什么校准集。
别轻信量化模型卡上的 KLD 数字
作者对社区量化模型卡上动辄极低的 KL 散度声明给出了方法论警告:一个 KLD 数字在没有披露参考 checkpoint、完整运行环境、评测文本、校准数据、上下文长度、采样位置、KL 方向、词表截断方式与聚合方法之前,无法解读。方法与数字同等重要,大量发布者做不到完整的披露。
另外一个常被忽略的配置项:采样参数。模型卡通常写明推荐的 temperature 与 top-p(各模型不同),温度压得过低,Qwen 会卡在 THINK 输出里循环出不来。
给本地部署者的清单
把三组实验的结论折算成可执行的检查项:
- 对比评测用长上下文加工具调用的工作负载,零样本三连问说明不了问题;
- KV Cache 量化到 INT8 级别风险可控,INT4 在 agent 场景需实测工具调用闭环;
- 4-bit 权重量化在长上下文工具任务上的退化幅度显著高于直觉,优先考虑 W8A16 或官方 FP8 路线;
- 看到量化模型卡的 KLD 声明,先查方法论披露是否完整;
- 采样参数照模型卡设置,温度过低会触发循环。
同一份权重,在不同软件栈上会表现出不同的能力。差距的来源藏在每一次浮点累加的舍入路径里,而这份实验第一次把它量化到了每个 token。
来源
- 实验原帖:thr3e,《Why your local LLM feels dumber than it is》,Level1Techs Forums(含全部图表与配置披露)
- 模型:Qwen/Qwen3.6-27B 及其 FP8、NVFP4、AWQ、INT8 量化版(Hugging Face)
- 推理引擎:vLLM(nightly build)