Reed's News
← 返回精选

本地LLM为何感觉更笨:推理实现细节的影响

AI 83 felineflock 2026/8/22 1880 字 原文 ↗

我们都有过这样的经历:在论坛、聊天群、Reddit、Discord、YouTube或是其他平台上,看到有人大呼“Model XYZ简直神了!zomgwtfbbq”,于是兴冲冲下载(更可能是下载量化版本),结果却吐槽“呃…这玩意儿真烂!”

本文将通过一系列偏技术向的实验,展示推理过程中“实现差异”带来的影响。我会用“参考实现”一词指代发布模型、提供官方托管并公布初始基准测试结果的实验室。他们的硬件和你的不同,软件更是大相径庭。而且本文的对比实验,绝非用Ollama跑个2.58位量化的GGUF模型、丢几个测试提示词那么简单。

为了让内容更易读,我刻意省略了一些新兴研究领域、海量论文和文献综述。别纠结我的简化表述,不然我就把满是公式的超长硬核版甩给你。

如今每一套运行大语言模型(LLM)的软硬件环境都有所不同,某些情况下差异还极大。普通个人实验室用户可能混用不同代际的GPU,这些芯片的指令集各不相同,即便运行完全相同的模型权重,指令集执行运算、生成下一个token的方式也会因人而异。

这就引出第一个问题:你的特定配置到底“拉胯”多少?其实有不少方法可以衡量。

实用派的方法很直接:跑各类标准基准测试,比如Terminal Bench、HLE、SWEthis、HELLAthat、MMLU系列等等,选适合你实际工作负载的就行。千万别把温度设为0,丢3个测试提示词就判定模型好坏。零样本测试无法模拟大多数智能体任务,你需要长上下文工具调用、特定领域知识评估,才能找出和别人用相同权重跑相同基准时,你的配置短板在哪。

不过我最先关注的是纯数学层面的答案,正如@wendell所说:

“Logits(对数几率)”是模型对每个候选下一个token的评分。它们会被归一化为概率,传入配置好的采样器,再由反分词器转换为文本,生成THE→NE→XT→TOK→EN这样的逐token输出。

关于采样器设置的小提示:Hugging Face(HF)的模型卡片通常会明确标注推荐的采样器设置(以及聊天模板),比如温度1.0、top-p 0.95等,不同模型参数不同,务必使用正确设置。顺便说一句,把温度设得太低,就是你的Qwen模型陷入思考循环、无法输出的原因。不用谢,能帮你解决问题我很高兴。

当下一个token的概率变化足够大时,原本的THE→NE→XT就可能变成THE→NE→W→DAY…这种细微变化或许无关紧要,但往往就是你隐隐觉得“哪里不对”的开端。

有些人可能听过KLD或KL散度这个术语。别担心,我不会让你做数学题,也不会用满屏的极小小数轰炸你。简单来说就是:将输出的logits转换为概率分布,再衡量该分布与选定基线分布的偏离程度。KLD值越低,说明越接近基线,但不代表模型“更聪明”。此外KLD是有方向性的,两个分布的顺序会影响结果。

提醒一句:别被HF模型卡片上低得离谱的KLD数值忽悠。除非作者披露了参考 checkpoint、完整运行环境、评估文本、校准数据、上下文长度、采样位置、KL散度计算方向、词汇截断情况,以及结果汇总方式,否则这个数字毫无意义。方法和数值本身同样重要,很多人都在这上面犯了错。

现在,我们得简单梳理一下推理引擎里庞大的软件堆栈在做什么,才能理解这些差异的来源。

在这个简化流程图的每一个环节,都有组件会根据你的软硬件环境、模型、量化方式、张量形状等因素进行配置或调整。

我使用的VLLM nightly容器镜像包含734个(其中252个是uv/pip安装的Python)包,这意味着734个代码库,每个都有自己的bug和未公开的特性。你的特定实现在这座代码大山中走的路径,必然独一无二。

我们从推理流程图的一部分说起。在预填充(提示词处理)阶段,推理引擎会选择多种注意力后端之一。这会影响预填充的速度和精度,且不同GPU系列/ SM计算能力需要不同的CUDA内核1.3. The CUDA platform — CUDA Programming Guide。我们来测试并对比它们的表现。

(真的很抱歉,但我必须这么做…)

我以Qwen3.6-27B的官方BF16 checkpoint为基础,在RTX PRO 6000 Blackwell GPU上运行,张量并行度设为1。KV缓存采用BF16格式,未对权重/激活值或KV缓存做量化。软件使用固定版本的VLLM nightly构建,采用即时执行模式,禁用CUDA图、前缀缓存和MTP,预填充采用2k token分块方式。

Qwen3.6-27B是密集型模型,而非混合专家(MoE)模型,但它属于混合架构:64层按“3个门控DeltaNet/线性注意力层 + 1个全注意力层”的模式重复。本次实验中,只有16个全注意力层会使用可选的注意力后端,门控DeltaNet路径保持固定。

实验复现的是“Prompt 2”——一个约10万token的上下文,来自Turnstone实验室真实工作流,包含多个工具调用和实际工作产物。选择它是因为更贴近本地智能体的真实任务,而非人工构建的“大海捞针”测试。更重要的是,它目前未出现在任何公开基准或训练数据集中,没人能针对它优化基准测试,也没法为它校准量化模型。

针对这个任务,VLLM提供三种可选的全注意力后端:FlashAttention 2、Flash Inference和Triton Attention。每次实验只改变这一个变量,其余软硬件环境保持不变。

我还做了同后端跨GPU的可重复性对照实验。在这个实验中,每处理32个提示词token,就以BF16格式捕获全词汇表的logits,之后用FP64精度基于存储的logits计算KLD等分布差异指标。

Top-1一致性指的是,logits得分最高的token(即贪心算法选出的argmax结果)是否一致。所有后端都基于相同的强制token历史进行评估。因此“Top-1翻转”意味着,在该位置某后端本会选择一个不同的贪心下一个token。我们并未让这个选择改变后续的token历史,这样能保证数学对比的可控性,但无法展示无约束生成会偏离多远,或是工具调用最终是否会失败…这部分会在测试2中呈现 ;D

下图展示了采样logits中出现token翻转的百分比:

在前几千个token阶段,无论使用哪种后端,模型对下一个token的选择完全一致。但在提示词的后半段,不同后端开始出现分歧。为了简化后续量化实验,我们将Triton设为基线。

每个8k token窗口包含250个采样点,每32个token采集一次。百分比表示,在这些采样点中,其他后端的最高得分token与Triton不同的比例。

通过多次运行相同注意力后端的测试,我们排除了随机噪声的影响。每次运行中,每个隐藏状态的logits都完全一致,这说明这种差异完全来自预填充阶段trt/fa2/fi中的矩阵乘法和加法运算。

分歧集中出现,且随提示词内容变化,而非随上下文长度平稳增加。这并不存在一个“模型崩溃”的通用长度阈值,但…我们很快就会触及这个问题

有了基于真实提示词的基线对比,接下来我们深入研究…

沿用相同方法,我们以采用Triton后端的BF16权重+BF16 KV缓存为基线,进行下一个实验:保持权重和激活值不变,只量化KV缓存会发生什么?

答案是差异显著。这也引出了我们今晚第一个“翻车现场”:一个可完全复现的工具调用错误。

在工具调用过程中,足够多的Top-token发生了翻转。我们让生成过程自然进行:BF16版本运行正常,INT8 KV缓存版本最终勉强恢复,而INT4版本彻底失败!

这次我们保持所有KV缓存为完整大小的BF16格式,同时引入新的对比对象:

这4种量化版本覆盖了权重和激活值的多种量化方式。对我们的数学分析来说,一个关键信息是:每种量化版本计算logits时使用的CUDA内核/通用矩阵乘法(GEMM)/矩阵乘累加(MMA)指令各不相同:

  • 权重/激活值:BF16权重,BF16激活值

  • 线性运算/GEMM:UnquantizedLinearMethod → torch.nn.functional.linear,根据张量形状/几何特征选择CUDA tile

  • KV缓存:BF16(强制设置)

  • 说明:参考checkpoint

  • 权重/激活值:128×128块的E4M3 FP8权重;转换后的线性层内动态FP8激活值量化;lm_head等未量化模块保持BF16格式

  • 线性运算/GEMM:Fp8LinearMethod → CutlassFp8BlockScaledMMKernel

  • KV缓存:BF16(强制设置)

  • 说明:VLLM标记E8M0缩放格式对该架构(SM120)会降低精度,因此自动禁用DeepGemm,转而选择CUTLASS。已发布文件中未提及校准数据集。

  • 权重/激活值:静态对称按通道量化的INT8线性权重;BF16激活值(W8A16)。GDN/linear_attn投影层和lm_head未参与量化

  • 线性运算/GEMM:CompressedTensorsWNA16 → MarlinLinearKernel

  • KV缓存:BF16(强制设置)

  • 说明:一次性量化,明确未使用校准数据集。结合W8A16量化方式和未量化的GDN投影层,它的超高保真度就不难理解了。

  • 权重/激活值:混合checkpoint——208个静态FP8 W8A8目标(覆盖64个全注意力投影层和144个GDN投影层);193个NVFP4 W4A16目标(覆盖192个MLP投影层和lm_head,分组大小16)

  • 线性运算/GEMM

    • FP8目标:ModelOptFp8LinearMethod → FlashInferFP8ScaledMMLinearKernel
    • NVFP4目标:NVFP4 GEMM → MarlinNvFp4LinearKernel
  • KV缓存:BF16(强制设置)

  • 说明:在我们使用的上游nightly版本中,未启用原生FP4运算。VLLM判定该GPU路径不支持原生FP4,因此通过Marlin显式选择仅权重FP4压缩。为了公平对比,checkpoint内置的FP8 KV方案被强制替换为BF16 KV缓存。

  • 权重/激活值:静态非对称INT4权重,分组大小32,采用MSE观测器;BF16激活值(W4A16)。GDN/linear_attn投影层和lm_head未参与量化

  • 线性运算/GEMM:CompressedTensorsWNA16 → MarlinLinearKernel

  • KV缓存:BF16(强制设置)

  • 说明:AWQ校准数据集披露为“STEM和智能体任务”。

本次实验的其他重要信息:

  • 所有模型均使用全softmax/GQA注意力,后端为AttentionBackendEnum.TRITON_ATTN;JIT监控显示使用kernel_unified_attention内核
  • GDN预填充:使用Triton/FLA GDN预填充内核,指定为triton,head_k_dim=128
  • 执行过程中,循环路径还JIT编译了_causal_conv1d_update_kernel、fused_recurrent_gated_delta_rule_packed_decode_kernel和reduce_segments内核
  • 张量并行度1,即时执行模式,禁用CUDA图、MTP/投机解码,仅运行语言生成任务。

下一个token翻转的结果符合预期:TheDude的W8A16版本表现碾压其他所有模型,击败了官方FP8(W8A8)版和英伟达的“伪FP4”版。事实上,在5个选项中,英伟达版本排名垫底,上下文长度达到88k时,token翻转率约为50%。

NVFP4和AWQ W4A16版本均未能正确结束工具调用,还搞砸了Cisco命令行语法(正确命令是‘show arp’,它们却执行了‘show run’),而FP8和INT8版本则能完成正确调用。

在未来的实验中,我将探索为相同权重使用不同融合GEMM的影响——这是另一个有趣的差异来源,有时你不得不为了速度牺牲精度。

我还有不少实验和观察结果要发布,但需要大量GPU并行时间,才能在多个提示词、数十种不同设置下,计算并记录超长上下文链中的每个采样logits。

如果有具体问题,给我发私信或者在Discord上@我就行。