到这里读者已经认识了两种给模型分配合适量化类型的方式。第一种是内置规则型第 3 章讲的Q4_K_M——llama.cpp 用一个固定规则主体 Q4_K、输出层 Q6_K、归一化 F32去分配所有张量。第二种是格式本身的差异第 11 章讲的IQ1_S、IQ2_XXS等它们在编码格式这一层把每个权重压得更狠。这一章要讲第三种——不是编码格式而是**用外部搜索来决定每个张量用哪种已有格式**的做法它的产品名是 Unsloth DynamicUD常写作UD-IQ1_S读作UD 前缀的 IQ1_S。先给一个关键定性UD 不是一种新的 ggml 类型。“UD-IQ1_S这个模型里代码跑起来用的仍然是IQ1_Stype id 19、IQ2_XXS16、Q4_K12这些我们在前面章节见过的格式。UD 改变的不是每个权重怎么编码”而是哪些张量用 IQ1_S、哪些要用更保质量的类型。为什么值得专门讲一章因为它在 2025 年初把极限压缩推到了新的位置一个 70B 模型在保留大部分能力的同时被压到约 1/8 的体积。但要理解这种收益从哪来、代价在哪必须绕过三个概念敏感度矩阵那个决定谁配什么的依据、分配搜索如何从敏感度推出逐张量类型、可复现性为什么同名的 UD 模型可能字节级不同。先统一几个术语。逐张量分配per-tensor allocation为每个张量单独挑选量化格式而不是所有张量一刀切。敏感度sensitivity量化某个张量后模型输出的变化有多大越敏感的张量越应该保留高位宽。敏感度矩阵把每个张量 × 每种候选格式的预估损失排成一张表。动态量化dynamic quantization在推理时按实际激活动态选择 KV cache 等部分的精度UD 的另一半见 12.2.1。可复现性reproducibility同样的输入流程能否产出完全相同的文件。12.1 UD 的本质策略而非编码12.1.1 UD-IQ1_S内部到底是什么先做一个反直觉的验证把UD-IQ1_S当作一个文件来解剖。下面这个脚本和第 3 章统计Q4_K_M类型分布的脚本几乎一样——这正是想强调的第一课UD 模型的内部是几十种标准 ggml 类型的混合体。fromcollectionsimportCounterfromggufimportGGUFReaderforpathin[ud-iq1_s-model.gguf,iq1_s-plain.gguf]:readerGGUFReader(path)counter:Counter[str]Counter(t.tensor_type.namefortinreader.tensors)print(f\n{path})forqtype,countincounter.most_common():print(f{qtype:12s}{count:5d}张量)输出会呈现出一种金字塔式的分布 ud-iq1_s-model.gguf IQ1_S 196 张量 ← 大部分低敏感度张量压到 1.6 bpw IQ2_XXS 48 张量 ← 敏感一些的用 2.06 bpw IQ2_XS 18 张量 Q4_K 8 张量 ← 很关键的张量上到 4.5 bpw Q6_K 2 张量 ← 输出层 F32 70 张量 ← 归一化等 iq1_s-plain.gguf IQ1_S 270 张量 ← 无差别的 IQ1_S除了 F32 归一化 F32 52 张量对比说明一切“UD-IQ1_S里的 IQ1_S 只描述绝大多数无关紧要的张量用 IQ1_S”而不是整个模型都是 IQ1_S。命名上它继承了哪个格式打底但真实内容是分配策略的产物——这正是第 12.1 节要钉死的核心。12.1.2 UD 和_M后缀的区别固定规则 vs 外部搜索乍看UD-IQ1_S和Q4_K_M有相似之处都是一个名字内部混合。但两者的机制不同放在一起对比最清晰维度Q4_K_M内置规则UD-IQ1_S外部搜索决策依据llama.cpp 里写死的模板模型数据算出的敏感度矩阵决策粒度按张量角色分组QKV/FFN/输出层/归一化逐张量独立可有几十种组合是否可复现是——任何版本llama-quantize结果一致不确定——依赖工具版本和校准数据依赖外部数据否是——需要校准数据来估计敏感度上限/下限只在使用规则内找最优理论上可在全格式空间里找一句话总结_M是手工规则UD 是机器搜索。前者可复现、粗粒度、零依赖后者更细、更贴模型、但依赖工具链和数据。注意一个细节UD 的策略是外部搜索但最后落盘的仍是标准 GGUF 标准 type id。所以一个 UD 模型文件对 llama.cpp 而言没有任何特殊之处——只要构建支持它内部用到的那几种格式就能直接加载。第 13 章讲的三值格式则是反例它们的落盘格式本身不在主线加载就受限。12.2 UD-IQ1_S 的实测表现12.2.1 89% 体积缩减 vs 约 72% top-1一个诚实的权衡表UD 发布时的宣传点是极限压缩下的质量保留。以 70B 档模型为例一个常用的实例数字是从 F16 出发UD-IQ1_S 把文件压缩约 89%接近原体积的 1/9同时在与原始模型的对比中保留约 72% 的 top-1 准确率。这两个数字要拆开看因为它们不是同一条曲线上的点F16 100% 体积 ~100% top-1 │ │ ≈ 89% 体积缩减 ▼ UD-IQ1_S ~11% 体积 ~72% top-1特定任务集 │ ▼ IQ1_S 全量 ~11% 体积 ~60-65% top-1同任务集更差注意UD 的收益不在把 IQ1_S 变强而在于把有限的量化预算花在刀刃上。同样的总体积均匀压成 IQ1_S 的模型在敏感任务上掉得更多UD 在保持总体积不变的前提下把关键的少量张量抽出来降速用 Q4_K/Q6_K从而显著改善任务保留。诚实的注脚必须写三句数字高度依赖模型和任务集。72% top-1 是特定配置某 70B 模型 某评测集下的结果换模型换任务会明显浮动写作时请以你实测为准。KV cache 量化贡献了另一半。UD 不只是权重量化它还会对 KV cache 做动态量化例如 key 用 4-bit、value 用 8-bit按头缩放。因此89% 体积缩减有一部分来自更少的内存开销而不是更小的权重文件。极限压缩的任务退化是非线性的。和 Q2_K第 10 章一样UD 在简短问答、检索上相对抗压在 JSON 生成、长代码、多步指令上退化明显。用 PPL 或单一准确率数字外推是危险的——必须逐任务验证。12.2.2 逐张量类型检查确认 UD 不是单一格式12.1.1 的脚本已经完成了验证。这里补充一个更工程化的检查脚本——它打印每个张量的名字、类型、字节数方便定位到底哪个张量被升到了 Q4_KfromggufimportGGUFReader readerGGUFReader(ud-iq1_s-model.gguf)# 只看非 IQ1_S 的张量即 UD 特意升级的部分fortinreader.tensors:ift.tensor_type.name!IQ1_S:mbt.n_bytes/(1024*1024)print(f{t.name:56s}{t.tensor_type.name:10s}{mb:8.2f}MB)输出大致是model.layers.0.attn_norm.weight F32 0.00 MB model.layers.0.attn_q.weight Q4_K 18.42 MB model.layers.0.attn_v.weight IQ2_XS 9.21 MB model.layers.0.attn_k.weight IQ2_XXS 4.60 MB ... model.output.weight Q6_K 60.2 MB观察点同一个attn层的 Q、K、V 可能被分配不同的格式——这不是笔误而是敏感度搜索的结论某些输出投影对量化格外敏感对应的输入投影则更耐受。读者可以用这个脚本验证任何声称UD的模型检查它内部是不是真的混合而不是单一格式。正因为落盘仍用标准 type id任何能读 GGUF 的引擎都可以枚举这些内部类型不依赖 Unsloth 的工具。下面是 C 与 C# 的等价枚举参照第 3 章读 header 的写法——注意我们只读目录tensor_infos不碰数据段C沿用第 3 章的gguf_header_t跳过 KV 区后逐个读 tensor_infos#includecstdio#includecstring#includecstdint#includevector#pragmapack(push,1)structgguf_str_t{uint64_tlen;chardata[0];};structgguf_ti_head_t{uint64_tname_len;// name 字符串跟随其后uint32_tn_dims;// 然后是 dims[8] × n_dims、type(u32)、offset(u64)};#pragmapack(pop)// 读一个带长度前缀的字符串staticstd::stringread_string(FILE*f){uint64_tlen;fread(len,8,1,f);std::strings(len,\0);fread(s.data(),1,len,f);returns;}intmain(){FILE*ffopen(ud-iq1_s-model.gguf,rb);uint32_tmagic,version;uint64_ttensor_count,kv_count;fread(magic,4,1,f);fread(version,4,1,f);fread(tensor_count,8,1,f);fread(kv_count,8,1,f);for(uint64_ti0;ikv_count;i)read_string(f);// 跳过 KVfor(uint64_ti0;itensor_count;i){std::string nameread_string(f);uint32_tn_dims,type;uint64_toffset;fread(n_dims,4,1,f);std::vectoruint64_tdims(n_dims);fread(dims.data(),8,n_dims,f);fread(type,4,1,f);fread(offset,8,1,f);printf(%-56s type%-4u\n,name.c_str(),type);}return0;}为聚焦主题KV 用read_string简化绕过——真实 KV 还含类型字节。完整解析见附录 C 的最小解析器。C#.NET 5用BinaryReaderusingSystem;usingSystem.IO;usingSystem.Text;usingvarrnewBinaryReader(File.OpenRead(ud-iq1_s-model.gguf));stringReadStr(){intnchecked((int)r.ReadUInt64());returnEncoding.UTF8.GetString(r.ReadBytes(n));}r.ReadBytes(4);r.ReadUInt32();// magic, versionulongtensorCountr.ReadUInt64();ulongkvCountr.ReadUInt64();for(ulongi0;ikvCount;i){ReadStr();r.ReadUInt32();ReadValue(r);}// 简化for(ulongi0;itensorCount;i){stringnameReadStr();intndr.ReadInt32();for(intd0;dnd;d)r.ReadUInt64();uinttyper.ReadUInt32();r.ReadUInt64();// offsetConsole.WriteLine(${name,-56}type{type});}staticvoidReadValue(BinaryReaderr){// 按 GGUFValueType 分派这里为示例略过}三种语言的核心一致跳过 KV逐个张量读出 name / type / offset就能画出一张UD 模型内部类型金字塔。Python 走 gguf-py 高层封装C/C# 走结构体 BinaryReader的低层拆解——结果是同一个 GGUF 文件的同一张分布表。这个文件可移植、读者可自证的结论正是第 12.3.2 节标准 type id 落盘的工程保障。12.3 UD 的生成流程与生态风险12.3.1 敏感度矩阵、分配、验证的标准流程UD 模型的生成不是llama-quantize一行命令而是一条多步流水线。大致分三步UD 模型生成流程 ① 敏感度估计 F16 模型 校准数据 │ 对每个张量分别量化到每个候选格式 │ 度量输出损失如 KL 散度 ▼ 敏感度矩阵 S[张量 × 格式] ② 动态分配 在总字节数预算约束下从矩阵 S 找出预期总损失最小的逐张量格式组合 本质是一个背包问题 / 贪心搜索 │ ▼ 分配表张量 → 格式 ③ 量产与验证 按分配表逐张量量化合并成 GGUF │ ▼ 在验证集上评估PPL / 任务得分 不合格则回到 ② 调整预算关键点在于第 ① 步的度量。UD 的敏感度不算逐权重的激活值那是第 11 章 imatrix 干的事而是粗粒度地测把整个张量换成某格式、输出变多少。因为要在张量 × 格式矩阵上跑数据量很可观通常用少量校准文本 并行的量化-推理循环完成。第 ② 步的搜索是核心卖点它让总体积不变误差最小成为一个可优化的目标而不是拍脑袋的规则。12.3.2 跨工具可复现性问题同名 UD ≠ 字节级复现UD 的质量依赖两样东西校准数据和生成工具。这带来一个工程上很现实的坑校准数据的措辞、长度、领域直接影响敏感度估计不同版本的工具Unsloth 的 fork 与上游 llama.cpp 的量化器对同一码表、同一 round/floor 可能有一丝差异分配搜索本身若没有固定随机种子可能产出不同的次优解。结果是两个工具在同一个模型上生成的UD-IQ1_S内部类型分布可能不同文件也就不是字节级可复现的。这不一定是 bug——但它意味着不能假设文件名相同 文件相同。发布者应同时公开生成工具版本与校准数据跨工具对比评测时要注明谁生成的否则数字不可比与第 12.1.2 表里的可复现性一栏呼应规则的确定性是 UD 用外部搜索换来的这是代价。这也把全书的主题往前推了一步从字节布局格式到分配策略质量的锚点从规范转移到了数据 流程。第 20 章讨论生态治理时会回到这一点。本章小结与通往三值极限的桥梁这一章可以压缩成三个事实。第一UD 是策略而非编码。“UD-IQ1_S模型内部是 IQ1_S、IQ2_XXS、Q4_K、Q6_K、F32 的标准混合体UD只说明这些类型是被外部敏感度搜索分配出来的”。用 gguf-py 一统计就能打破UD 是单一格式的误解。第二UD 的权衡是总体积不变、误差最小的优化而不是更狠的压缩。它在一个相当激进的体积预算下只把极少数敏感的 QKV / 输出层张量留高位宽其余压到 IQ1_S从而在近似 F16 的 1/9 体积下保留约 72% 的 top-1数字随模型/任务浮动。代价是依赖校准数据、工具版本且不可字节级复现。第三UD 是一次范式外的尝试它没有发明新格式却重新组织了已有格式。它证明了逐张量混合 外部搜索这条路能把极限压缩推得更远——那么问题来了如果还想再压编码格式这一层还有没有空间UD 的极限停留在大约 1.6 bpw因为它依赖的 IQ1_S 本身是 1.56 bpw。下一章把位宽推到底进入三值量化每个权重只用 ({-d, 0, d}) 三个取值理论上可以低于 1.6 bpwSTQ1_0 约 1.31 bpw、PTQ1_0 约 1.75 bpw。三值格式的挑战不在存而在解码端需要什么额外变换——尤其是 PTQ1_0 的 Hadamard 折叠。第 13 章将回答格式正确但缺失旋转为什么仍然全错