llama.cpp 转 Qwen3.8-27B 时改了什么:48 个 V 头要重新排队

2026-08-24

把一个模型转成 GGUF,直觉上像是换个封装格式——权重还是那些权重,只是文件布局变了。

Qwen3.8-27B 不是这样。llama.cpp 的转换脚本在处理它时,会把一部分权重矩阵里的行和列重新排一遍顺序,涉及六类张量。不这么做,转出来的模型算出的结果就是错的。

这篇讲清楚为什么要重排、重排了什么,以及顺带被补上的那个字段。

这篇的依据

来源是 llama.cpp 主干仓库的 conversion/qwen.py(809 行),相关代码在 446 到 640 行区间。采集时间 2026-08-24

我们没有转换过这个模型、没有跑过 llama.cpp、没有生成过任何 GGUF 文件。 下面的说明全部来自源码文本和其中的注释。

顺带说一句:如果你之前搜过「llama.cpp 支不支持 Qwen3.8」并得到了否定答案,那八成是搜错了关键词——它用的命名是 Qwen35(没有下划线),而且转换逻辑已从 convert_hf_to_gguf.py 挪进了 conversion/ 包。相关的核实方法见 支持哪些推理框架

注册在哪里

先定位。文件第 630 行:

@ModelBase.register("Qwen3_5ForConditionalGeneration", "Qwen3_5ForCausalLM")
class Qwen3_5TextModel(_Qwen35MRopeMixin, _LinearAttentionVReorderBase):
    model_arch = gguf.MODEL_ARCH.QWEN35

一个装饰器注册了多模态和纯文本两个架构名,都映射到 QWEN35。真正干活的逻辑在两个基类里,名字已经剧透了:一个管 MRoPE,一个管线性注意力的 V 重排

为什么要重排:16 个 K 头配 48 个 V 头

根源在 config.json 里这两个字段:

linear_num_key_heads   = 16
linear_num_value_heads = 48

K 头和 V 头数量不等,48 除以 16 等于 3——每个 K 头对应 3 个 V 头(这两个字段的其他含义见 线性注意力参数)。

数量不等就带来一个问题:这 48 个 V 头在内存里按什么顺序排?

源码的文档字符串把两边的约定写得很清楚:

HF 权重把 V 头按 K 头分组存储:[G0_v0..v{r-1}, G1_v0..v{r-1}, ...] 而 ggml 的二元运算用的是平铺广播[K0, K1, ..., K0, K1, ...]

翻译成人话:

HF 的存法是「先把第 0 组的 3 个 V 头排完,再排第 1 组的 3 个」——组内连续。

ggml 期望的是「先把每组的第 0 个 V 头排完,再排每组的第 1 个」——跨组交错。

注释里给出了重排后的目标顺序:[G0_v0, G1_v0, ..., G0_v1, G1_v1, ...]

两种顺序装的是同一批数字,但排列方式不同。如果不转换直接用,广播时对应关系就全错了。

至于为什么要迁就 ggml 而不是反过来,注释也说了:这样能让 ggml_repeat 直接完成广播,替代掉一个代价高昂的交错重复操作。也就是说,重排是一次性的转换期成本,换来的是推理期每一步都受益。

触发条件

重排不是无条件执行的。modify_tensors 里的判断是:

if num_k_heads > 0 and num_v_heads > 0 and num_k_heads != num_v_heads and "linear_attn." in name:

三个条件同时满足才做:两个头数都有效、两者不相等、张量名里含 linear_attn.

对 Qwen3.8-27B 来说,16 不等于 48,所以这个模型必然触发重排。如果哪个型号的 K 头和 V 头数量相同,这段逻辑就会整体跳过。

六类张量,六种排法

需要处理的张量分六类,每一类的排法都不一样:

张量处理方式
.in_proj_qkv.按 Q、K、V 切三段,只重排 V 那一段的行
.in_proj_z.整体重排行
.in_proj_a. / .in_proj_b.重排行,但每头维度按 1 计
.A_log / .dt_bias / .dt_proj一维参数,每个 V 头一个元素
.conv1d切出 QK 通道和 V 通道,只重排 V 通道部分
.out_proj.重排(输入维度),不是行

几个值得注意的地方。

QKV 是融合存储的,但只有 V 需要动。 代码先按 head_k_dim * num_k_heads 算出 Q 和 K 各自的长度,切出三段,只对 V 段重排,再拼回去。Q 和 K 的头数本来就是 16,不存在错配。

out_proj 排的是列不是行。 因为它是输出投影,V 头在它这里是输入维度。同一个重排逻辑,作用的轴不同。

A_logdt_biasdt_proj 这几个名字很关键。 熟悉状态空间模型的会认出来,这是 Mamba 系列的典型参数命名。这与 vLLM 那边的发现完全一致——那边的线性注意力实现就放在 layers/mamba/gdn/ 目录下(见 64 层在 vLLM 里怎么分流)。两个独立的框架实现,都指向同一个事实:这 48 层线性注意力走的是状态空间那套机制。

conv1d 的存在也印证了 linear_conv_kernel_dim = 4 这个配置字段——线性注意力这一支里确实有一个卷积。

4 位量化的额外麻烦

如果权重是 nvfp4 格式(4 位浮点),重排会更麻烦一层。

4 位意味着两个数挤在一个字节里。要按行重排,就不能直接对字节操作,得先把每个字节拆成高低两个半字节、按新顺序重排、再打包回去。源码里为此专门写了 unpack_nibblespack_nibbles 两个函数。

out_proj 的列重排还要更复杂:量化权重带着缩放因子,缩放因子是按 16 列一组存的。所以重排列的时候,得同时把对应的缩放因子也按组重排。代码里有一串断言在检查这个前提——列的排列必须是整组整组地换,不能把一个组拆散。

这解释了 Ollama 上为什么会有专门的 27b-nvfp4 标签(见 十二个标签):量化格式和权重重排是耦合的,不是先量化再随便排就行。

顺带补上的那个字段

另一个基类 _Qwen35MRopeMixin 干的事简单得多,但同样值得说。

它的注释写道:Qwen3.5 总是使用交错式 MRoPE,上游默认的 mrope_section[11, 11, 10],而 llama.cpp 的 QWEN35 加载器把 qwen35.rope.dimension_sections 当作必填字段。所以哪怕某份 checkpoint 里没写这个字段,转换时也要补上:

_QWEN35_DEFAULT_MROPE_SECTION = [11, 11, 10, 0]

注意补出来的是四段,最后多了个 0——上游只有三段。

这里有个漂亮的交叉印证。11 + 11 + 10 = 32,而 RoPE 通常按维度对处理,32 对就是 64 维。本站写过 RoPE 维度 64 在 config.json 里找不到对应字段——那个数字在配置文件里没有直接来源,现在从 llama.cpp 这边的 mrope_section 拆分,正好能对上。

为什么要补第四段 0? 源码没解释。能确定的是:上游是三段、llama.cpp 的加载器要求这个键存在、补进去的第四段是 0。这属于两边字段约定不一致时的兼容处理,和 vLLM 那边为了适配不同 transformers 版本而同时接受两个配置类名,是同一类工程问题。

小结

  • llama.cpp 转 Qwen3.8-27B 不是格式平移,要重排权重
  • 根因是 16 个 K 头配 48 个 V 头,HF 按组存、ggml 要平铺,两种顺序不兼容
  • 六类张量各有排法,in_proj_qkvconv1d 只动 V 那部分,out_proj 动的是列
  • 4 位量化时要拆半字节重排再打包,缩放因子得跟着一起按组换
  • A_logdt_biasconv1d 这些名字再次证明线性注意力走的是状态空间机制
  • mrope_section 上游三段 [11, 11, 10],llama.cpp 补零成四段;三段之和 32 恰好对应 64 维 RoPE

这些细节的实用价值在于:如果你自己转换这个模型时结果不对,问题很可能不在量化参数,而在权重顺序。用官方转换脚本、别自己写简化版,是最省事的规避办法。

从这段代码能学到的一件通用的事

这段重排逻辑其实揭示了一个容易被忽视的道理:模型格式转换不是无损搬运,它是在两套不同的计算约定之间做翻译。

HF 那边的权重布局是照着 PyTorch 的张量语义组织的,ggml 那边有自己的广播规则和内存布局偏好。两边都没错,但它们对「48 个 V 头该怎么排」这件事的默认理解不一样。转换脚本存在的意义,一大半就是消化这类不一致。

这也解释了一个常见现象:为什么某个模型在 HF 上发布之后,各个推理框架的支持不是当天就位的。 光认出 architectures 字段是不够的,还得逐个张量确认布局约定是否兼容、不兼容的地方怎么转。像这次这种 K 头 V 头数量不等的结构,就需要专门写一套重排逻辑并覆盖六类张量——这是实打实的工作量。

所以下次看到「某框架尚未支持某模型」,多数时候不是没人想做,而是这类活儿得一个一个啃。

更多拆解在 Qwen3.8-27B 专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。