news 2026/9/18 1:25:22

ik_llama.cpp 离线 Repack 实战指南:将已有 GGUF 重新打包为行交错 R4 变体,摆脱 --run-time-repack 并启用 mmap

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ik_llama.cpp 离线 Repack 实战指南:将已有 GGUF 重新打包为行交错 R4 变体,摆脱 --run-time-repack 并启用 mmap

ik_llama.cpp 离线 Repack 实战指南:将已有 GGUF 重新打包为行交错 R4 变体,摆脱 --run-time-repack 并启用 mmap

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

导读

本文围绕 ik_llama.cpp 社区讨论 #323 的核心议题展开:如何将一个已经量化好的 GGUF(例如 DeepSeek-V3-0324 的 Q4_K_XL 量化版)**离线重新打包(repack)**为行交错(row-interleaved)的_R4变体,从而在加载模型时不再依赖--run-time-repack-rtr)选项,进而重新获得mmap内存映射加载能力。读完本文,你将掌握llama-quantize --repack--repack-pattern正则的完整用法、--override-tensor与 repack 正则之间的对应关系、多部分 GGUF 的处理方式,以及混合 CPU/GPU 部署下专家张量(exps 系列)放置与性能调试的实战经验。

背景:-rtr与 mmap 的取舍

在 ik_llama.cpp 中,--run-time-repack-rtr)会在模型加载时把所有张量就地(in-place)重打包为对应的行交错变体(如果存在的话)。这种做法带来的直接代价可以在 common/common.cpp 的参数解析逻辑中看到:

if (arg == "-rtr" || arg == "--run-time-repack") { params.repack_tensors = true; params.use_mmap = false; return true; }

也就是说,一旦启用-rtrmmap会被强制关闭。这正是讨论发起人 Lissanro 面临的两难处境:

  • 使用 Unsloth 发布的DeepSeek-V3-0324-GGUF-UD-Q4_K_XL配合-rtr运行时,在 EPYC 7763(64 核)+ 1TB DDR4-3200 内存 + 4×RTX 3090 的机器上可以获得7+ tokens/s的生成速度;
  • -rtr禁用 mmap 后,每次重新加载模型都需要大量计算(每次加载都要重新做一遍 repack),而他的工作流需要频繁切换模型(例如先用 72B 视觉模型处理图片,再切换回 DeepSeek-V3),切换成本被显著放大。

于是核心诉求就变成了:能不能把-rtr在加载时做的 repack 一次性离线完成、固化到一个新的 GGUF 文件里,这样加载时无需-rtr、可以继续使用 mmap、还能保住同样的性能。

核心概念:行交错(Row-Interleaved)R4 量化变体

要理解 repack,先要理解 ik_llama.cpp 特有的行交错量化类型。这类类型以_R4_R8(以及更高阶的_R16)后缀命名,可以从 examples/quantize/quantize.cpp 中的QUANT_OPTIONS列表一览全貌,例如:

  • IQ2_K_R4("IQ2_K repacked")、IQ4_K_R4IQ4_KS_R4IQ5_KS_R4
  • Q2_K_R4Q3_K_R4Q4_K_R4Q5_K_R4Q6_K_R4Q8_K_R8
  • IQ1_S_R4IQ1_M_R4IQ2_XXS_R4IQ2_XS_R4IQ3_XXS_R4IQ3_S_R4
  • Q4_0_R8Q8_0_R8Q8_KV_R8BF16_R16MXFP4_R8

这些类型的共同点是:把若干行(row)的数据交错打包在一起,从而让内存布局更有利于 CPU 侧的矩阵乘法(特别是配合 MoE 专家张量在 CPU 上执行时),实现比传统同 bpw 类型更高的吞吐。也正因如此,它们通常被当作"运行在 CPU 内存上的张量"的首选格式。

从 src/llama-quantize.cpp 的repacked_ftype()映射表可以看出,每个传统量化类型都有且仅有一个对应的行交错变体,例如:

原类型repack 后类型
Q4_K_S / Q4_K_MQ4_K_R4
Q5_K_S / Q5_K_MQ5_K_R4
Q6_KQ6_K_R4
Q8_KQ8_K_R8
IQ2_K / IQ3_K / IQ4_K / IQ5_KIQ2_K_R4 / IQ3_K_R4 / IQ4_K_R4 / IQ5_K_R4
IQ4_XSIQ4_XS_R8
Q4_0 / Q8_0Q4_0_R8 / Q8_0_R8
BF16BF16_R16

重要限制:repack 只能把某个类型转换到它唯一的对应行交错变体,不能随意改造成别的量化类型。正如作者 ikawrakow 在讨论中明确回应的:"No. The repacking is only to the corresponding row-interleaved type. Repacking to something else would result in quality loss."(不行,repack 只针对对应的行交错类型,改成别的类型会导致质量损失。)例如IQ4_XS只能变成IQ4_XS_R4IQ4_XS_R8(视映射而定),不会变成Q4_K_R4

离线 Repack 实操:llama-quantize --repack

基础命令

离线 repack 正是由llama-quantize工具完成的,核心参数是--repack--repack-pattern。作者 ikawrakow 给出的最简命令如下:

./bin/llama-quantize --repack \ --repack-pattern exps \ ~/models/DeepSeek-V3-0324-GGUF-UD-Q4_K_XL/DeepSeek-V3-0324-UD-Q4_K_XL-00001-of-00009.gguf \ repacked_model_file_name q4_k_r4

逐项解读:

  • --repack:进入"仅 repack"模式,等价于把 examples/quantize/quantize.cpp 中的params.only_repack置为true,此时工具不做重新量化,只对匹配到的张量做类型重打包;
  • --repack-pattern:逗号分隔的正则表达式列表,只有张量名匹配其中任一正则的张量才会被 repack(参数解析见 quantize.cpp)。exps会匹配 DeepSeek 这类 MoE 模型中以ffn_down_expsffn_up_expsgate_exps结尾的专家张量;
  • q4_k_r4:目标类型参数,这里表示把匹配到的张量转换为Q4_K_R4(大小写不敏感,工具内部会统一转大写后查表);
  • 输出文件:必须与输入模型是不同路径。命令不会覆盖原模型,因此磁盘上需要同时容纳新旧两份模型的空间(对于 DeepSeek-V3 这类数百 GB 的模型,务必提前确认磁盘余量)。

repack 正则与 --override-tensor 的对应关系

讨论中最实用的一条技巧是:--repack-pattern的正则可以直接从--override-tensor参数里抄过来,去掉=CPU部分即可。因为两者使用的都是张量名正则匹配机制。

例如,服务器启动参数中的:

--override-tensor "ffn_down_exps=CPU, ffn_up_exps=CPU, gate_exps=CPU"

等价于更简洁的写法(--override-tensor本身支持正则):

--override-tensor exps=CPU

而与之对应的 repack 命令则是:

./bin/llama-quantize --repack --repack-pattern "ffn_down_exps,ffn_up_exps,gate_exps" ... q4_k_r4

即:你打算在 CPU 上运行的张量,就写进--repack-pattern;你打算放在 GPU 上的张量,就不要 repack(保持非 R4 类型)。这是整个 repack 部署策略的核心准则。

多部分 GGUF 的处理

DeepSeek-V3 / R1 的 GGUF 往往被拆成多个分片(例如-00001-of-00009.gguf)。ikawrakow 坦言自己从未 repack 过多部分 GGUF,不确定llama-quantize是否能正确加载全部分片;讨论参与者 saood06 则补充了关键细节:按 gguf-split 方式拆分的文件,必须用 gguf-split 工具合并,不能直接用cat拼接。因此,如果遇到多分片 repack 失败的情况,合理的做法是先合并分片得到单一 GGUF 文件,再执行 repack。

另外值得注意的是,Lissanro 在转换日志中看到llama_model_loader: additional 8 GGUFs metadata loaded.,说明在部分场景下加载器确实能够读取到其余分片,但为稳妥起见,仍建议优先保证单一文件输入。

实战案例:DeepSeek-R1 / V3 的专家张量离线 Repack

用户的最终 repack 命令

经过多轮调试,Lissanro 最终用于 R1 的离线 repack 命令如下:

~/pkgs/ik_llama.cpp/build/bin/llama-quantize --repack \ --repack-pattern "(^blk\.[7-9]|\d\d).ffn_(up|gate)_exps|ffn_down_exps" \ /mnt/secondary/neuro/DeepSeek-R1-GGUF_Q4_K_M-163840seq/DeepSeek-R1-Q4_K_M-00001-of-00011.gguf \ /home/lissanro/neuro/DeepSeek-R1-GGUF_Q4_K_M-163840seq/DeepSeek-R1-GGUF_Q4_K_M_R4.gguf \ q4_k_r4

这个正则(^blk\.[7-9]|\d\d).ffn_(up|gate)_exps|ffn_down_exps的含义是:

  • (^blk\.[7-9]|\d\d).ffn_(up|gate)_exps:匹配第 7~9 层以及所有两位数的层(即 10 层及以上)的ffn_up_expsffn_gate_exps张量;
  • ffn_down_exps:匹配所有ffn_down_exps张量。

这样精心构造的意图是:第 0~6 层(含一位数编号的层)的ffn_up_exps/ffn_gate_exps要保留在 GPU 上执行,因此不 repack;其余专家张量全部转为Q4_K_R4交给 CPU 处理。正如 ubergarm 所建议的:先规划好哪些层要放 GPU,把对应张量排除在 repack 之外,只把剩余的路由专家层 repack 成 CPU 友好格式。

配套的服务器启动命令

repack 之后,加载模型就无需-rtr,可以正常享受 mmap。Lissanro 的启动命令为:

taskset -c 0-63 ~/pkgs/ik_llama.cpp/build/bin/llama-server \ --model /home/lissanro/neuro/DeepSeek-R1-GGUF_Q4_K_M-163840seq/DeepSeek-R1-GGUF_Q4_K_M_R4.gguf \ --ctx-size 73728 --n-gpu-layers 62 --tensor-split 25,25,25,25 -mla 2 -fa -ctk q8_0 -amb 1024 -fmoe \ -ot "blk\.3\.ffn_up_exps=CUDA0, blk\.3\.ffn_gate_exps=CUDA0" \ -ot "blk\.4\.ffn_up_exps=CUDA1, blk\.4\.ffn_gate_exps=CUDA1" \ -ot "blk\.5\.ffn_up_exps=CUDA2, blk\.5\.ffn_gate_exps=CUDA2" \ -ot "blk\.6\.ffn_up_exps=CUDA3, blk\.6\.ffn_gate_exps=CUDA3" \ -ot "ffn_down_exps=CPU, ffn_up_exps=CPU, gate_exps=CPU" \ --threads 64 --host 0.0.0.0 --port 5000

这里有两条非常实用的经验:

  1. -ot--override-tensor)支持正则,例如blk\.3\.ffn_up_exps=CUDA0中的\.是转义后的字面点号;正则匹配同样适用于 repack,因此--repack-pattern可以直接复用这些正则(去掉=设备部分)。
  2. CPU 覆盖规则必须放在最后:Lissanro 实测发现,如果先写ffn_down_exps=CPU, ffn_up_exps=CPU, gate_exps=CPU,再写各 GPU 的覆盖,CUDA 覆盖会不生效;把 CPU 兜底规则放在最后一条-ot,各 GPU 的逐层覆盖才真正生效。
  3. 由于单个-ot参数无法表达多行格式,可以用多个-ot参数,每个参数一行,便于在脚本中组织可读性。

专家张量放置策略:up/gate 优先上 GPU

关于"剩余 VRAM 该放哪些张量",ikawrakow 给出了明确的策略性建议:在 VRAM 有富余时,优先把若干层的ffn_up_expsffn_gate_exps成对放进 GPU——这比只放某一个专家张量、或把三个专家张量都放进去收益更大,尤其是在开启-fmoe时。他在低配硬件(Ryzen 5975WX + RTX 4080)上实验 Llama-4-Scout 时使用的配置是一个很好的模板:

-ot "blk\.[0-9]\.ffn_up_exps=CUDA0,blk\.[0-9]\.ffn_gate_exps=CUDA0,blk\.1[0-9]\.ffn_up_exps=CUDA0,blk\.1[0-9]\.ffn_gate_exps=CUDA0,exps=CPU" -ngl 100

其含义是:所有 attention 与共享专家张量放 GPU,前 20 层(blk.[0-9]blk.1[0-9])的ffn_up_exps/ffn_gate_exps也放 GPU,其余专家(exps)全部留在 CPU。

至于具体放多少层,取决于剩余 VRAM 与张量大小——Lissanro 在 4×24GB VRAM、72K 上下文的约束下,最终只能在每张卡上各放一层(blk.3~blk.6)的 up/gate 专家对。

性能调试实录:mmap 与页缓存问题

离线 repack 与在线 repack 是否等价?

ikawrakow 明确表示:"The offline repacking command should produce a result that is 100% equivalent to what happens with online repacking."(离线 repack 命令的结果应当与在线 repack 100% 等价。)但他同时指出,两种运行方式下内存的分配与张量归属方式不同,因此性能表现可能不完全一致——只是在他的硬件上差异从未达到 Lissanro 报告的那么大。

Lissanro 实测对比(同一 prompt,多次运行取计时行):

  • 原版 Unsloth 量化 +-rtr:约7.26 / 7.29 / 7.56 tokens/s
  • 离线 repack 后(不带-rtr):约4.28 / 5.07 / 3.96 tokens/s

他观察到 EPYC 7763 的 64 核在两种场景下都接近满载,怀疑转换后的量化在 CPU 侧存在额外瓶颈。

排查步骤:drop_caches、--no-mmap 与 BIOS

ikawrakow 建议先尝试清空页缓存再加载离线 repack 的模型:

echo 3 | sudo tee /proc/sys/vm/drop_caches

Lissanro 照做后(系统 1TB 内存、无 swap,模型约 378GB,加载后仍有 300+GB 空闲),性能不升反降(从接近 7.5 掉到不足 4 tokens/s);随后又发现:

  • 给 repack 后的模型加上-rtr再跑,性能又恢复到 7.3~7.5 tokens/s(说明 repack 操作本身没问题);
  • --no-mmap显式关闭 mmap、不带-rtr运行,性能恢复到 7.34 tokens/s——由此定位到问题出在 mmap 而非量化文件。

ubergarm 则补充了两点重要的 mmap 基准测试常识:

  1. mmap 模式下模型是懒加载进页缓存的,第一轮完整运行的计时通常会偏慢,需要先"预热"页缓存再统计;可以借助btop观察磁盘 I/O 与cached来确认。而关闭 mmap 时启动更慢(要把整个模型读入 RAM),但后续运行更快。
  2. 关闭 mmap 时,系统可能自动利用透明大页(transparent huge pages),可以用numastat -m -p $(pidof llama-server)(或 llama-bench 等进程)检查,该因素对性能的影响因系统而异。

最终 Lissanro 通过重置 BIOS、只保留必要设置解决了 mmap 下的性能问题——他怀疑是 BIOS 中关于内存吞吐的性能调优项影响了 mmap 访问效率。修复后,mmap 模式下 repack 模型的性能为:

  • 上下文填充约 2.5K token 时:7.86 tokens/s
  • 32K 填充时:5.09 tokens/s
  • 64K+ 填充时:略高于 3 tokens/s,输入处理约 50~80 tokens/s;
  • mmap 加载模型耗时约 45 秒(相比-rtr每次重新计算 repack,切换模型的成本大幅下降)。

源码级原理支撑

1. repack 的目标类型由映射表决定

离线 repack 时,llama-quantize会读取输入模型的ftype,通过repacked_ftype()(见 src/llama-quantize.cpp)查出整模型的 repack 目标类型;对每个张量则通过iqk_repacked_type()判断是否存在行交错变体(相关逻辑见 src/llama-quantize.cpp)。若张量类型没有对应 R4/R8 变体,或张量名不匹配任何--repack-pattern正则,则保持原样写入新文件。

2. --repack 与 --repack-pattern 的参数解析

两个参数在 examples/quantize/quantize.cpp 中解析:--repackparams.only_repack置为true--repack-pattern把逗号分隔的正则列表拆分为std::vector<std::string>。工具的 usage 帮助文本(quantize.cpp)也明确写道:

--repack Repack all tensors to the corresponding _r4/8 variant if available. --repack-pattern Comma separated list of regexs to use for matching tensor names to be repacked.

3. -rtr 与 mmap 的绑定关系

如前文所述,common/common.cpp 中-rtr同时设置params.repack_tensors = trueparams.use_mmap = false,这就是"启用-rtr必然失去 mmap"的代码根源。此外,src/llama-reload.cpp 也指出-rtr会在加载时把宿主张量就地 repack(类型变为行交错变体),且热切换(hotswap)恢复的张量无法复现 repack 后的状态——这意味着需要频繁切换模型的场景下,每次切换都要付出重新 repack 的计算代价,进一步凸显离线 repack 的价值。

总结与适用建议

  • 一句话方案:用llama-quantize --repack --repack-pattern <正则> <输入.gguf> <输出.gguf> <目标R4类型>把要留在 CPU 上的张量离线转成行交错变体,加载时去掉-rtr,即可恢复 mmap。
  • 正则设计--repack-pattern的正则直接取自--override-tensor(去掉=设备后缀);只 repack CPU 侧张量,GPU 侧张量保持非 R4 类型。
  • 类型限制:repack 只能转换为该类型唯一的对应行交错变体(如Q4_K_M → Q4_K_R4),不能跨类型转换,否则会有质量损失。
  • 多分片模型:gguf-split 拆分的分片应先用 gguf-split 合并成单文件再 repack。
  • 性能验证:离线 repack 与在线 repack 理论上 100% 等价,但实际运行时的内存分配、页缓存状态、透明大页乃至 BIOS 设置都可能造成显著差异;mmap 场景下基准测试应预热页缓存后再统计。
  • GPU 放置优先级:VRAM 富余时,优先把ffn_up_expsffn_gate_exps成对放 GPU(配合-fmoe收益更大),ffn_down_exps可留 CPU;多-ot时 CPU 兜底规则要放在最后。

进一步参考:llama-quantize 工具源码、量化核心实现、命令行参数解析 与 工具使用文档,可在当前仓库中继续深入阅读。

【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 1:24:13

Chrome CDP 从入门到实战:浏览器自动化调试协议深度解析

很多入行做爬虫、做自动化测试的朋友&#xff0c;第一次听说 chrome-cdp 的时候都是一脸懵。这玩意儿全称叫 Chrome DevTools Protocol&#xff0c;说白了就是 Chrome 浏览器留出的一扇后门&#xff0c;允许你用代码去控制浏览器几乎所有的行为&#xff1a;打开页面、点击按钮、…

作者头像 李华
网站建设 2026/9/18 1:22:56

把 Sol 5.6 当基线,TaoToken 承接 Astra 长程任务

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 1:22:55

Wireshark数据包长度统计:一眼看穿网络性能瓶颈

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 1:18:59

若依前后端分离项目Swagger接口文档配置与避坑指南

用若依做过前后端分离项目的朋友&#xff0c;应该对swagger-ui.html这个页面都有印象。打开它&#xff0c;就是一份能直接在线调用的接口文档&#xff1b;没打开过的人&#xff0c;第一次面对若依这一整套SpringBootVue工程时&#xff0c;往往会觉得无从下手。前端同事问“登录…

作者头像 李华
网站建设 2026/9/18 1:18:55

WPS高效批量修改表格样式:从样式库到宏的完整指南

用Win10系统装WPS Office 2019写文档&#xff0c;最让人抓狂的往往不是排版本身&#xff0c;而是那种“几十个表格风格来回横跳”的凌乱感。比如一份标书前面表格是蓝色底纹&#xff0c;后面变成浅灰底纹&#xff0c;有的字体是五号&#xff0c;有的是小四&#xff0c;边框一会…

作者头像 李华
网站建设 2026/9/18 1:17:04

分布式电源与电动汽车协同调度:Matlab仿真建模与代码实现全解析

源侧出力波动大、荷侧充电行为随机&#xff0c;再加上配电网容量有限&#xff0c;这三件事放到一起&#xff0c;场面确实会变得很难看。我见过不少项目前期只做“分布式电源接入分析”或者“电动汽车充电负荷预测”&#xff0c;结果放到真实调度场景里根本跑不通&#xff0c;原…

作者头像 李华