继上次尝试给 H3 训练 lora 之后(网页链接),这几天在尝试做一个新的事情,又有一个新的深坑。
为了设计跑通把任意图像转SVG HTML的工作流,想在本地跑通一个有图文多模态能力的模型,于是选定了Deepseek v4.1 Flash 1017。实际上工作流也已经用云 API 设计好了八成了,有本地模型就能放开了用了。
但即便 IQ2XXS 的 v4.1 Flash 也要 165G 模型 + 110G engram,在我凑了 100G 显存的机器上还是非常吃力的。(网页链接)于是考虑剪枝。
我现在本地主力用的 v4 Flash IQ2XXS 就是从 256 专家剪枝到 192 专家的,用的工具能直接计算专家的可替换性,效果挺不错的,于是打算同样的方式剪一下 v4.1 Flash。但那个工具是剪 HF 格式的 safetensor 的,不吃 gguf。原版 v4.1 Flash 太大了,不想下,于是打算把工具逻辑移植到 llama.cpp 里,这样就可以操作 gguf 了。
总体上移植流程还比较顺利,实现了新的逐层加载模型、逐层采集可替换性数据的流程。但这种要算数值的东西,新的路径对比原生路径的数值验证还是要做的吧,一做就发现了问题,很多对不上的地方。搞笑的是,也不是一直对不上,有的配置组合能对上,有的配置组合看起来人畜无害但就会对不上,而且有时候误差大,有时候误差小。
那还能怎么办,挖原因呗。目前挖到的复现性最高最确定的是 Hyper Connection 算子(HC),它有融合算子和分步算子两种实现,然后两种实现在 CPU 和 GPU 上都能跑。这对使用当然很方便,但坑的就是,是否融合 x 两种设备的一共四种路径,算出来的结果都不一样
更妙的是,llama.cpp 还要再横插一脚。这个 HC 要计算的位置是在一层的末端,在 MoE 部分之后。然后 llama.cpp 自己有一个性能优化,当发现 MoE 模型的第 n 层在 CPU、第 n+1 层在 GPU 的时候,会把第 n 层的 MoE 部分以及后续的部分都搬到 GPU 上去(v4 Flash管这叫“吸附”)。于是顺便就把第 n 层的 HC 计算也搬到 GPU 上去了。但运行的时候要选择用哪一种算子的实现来做这个计算,要检测模型这一层和 HC 计算所在的层是不是在同一个设备上,是的话才能用融合算子,于是就发现“第 n 层在 CPU 上,但这一层的 HC 在 GPU 上”,这种时候会自动禁用融合算子。于是,模型有没有完全放在 GPU 上,数值也会不一样……
一开始真是没想到数值核对的情况会这么乱七八糟。好在现在算是基本摸清原因了,可以设计到底怎么处理了。但回头一看这一路是怎么过来的,要跑工作流 -> 要本地部署 v4.1 Flash -> 要剪枝 -> 要移植剪枝工具 -> 要核对数值 -> 要追查误差来源 -> 要分析算子行为 -> 要设计怎么和 llama 的机制搏斗……
唉我一开始是要干什么,我现在又是在干什么啊 _(:3 」∠ )_