用于本地运行语言模型的 llama.cpp 在新版本中修复了 Gemma 模型的词元嵌入权重在 CPU 内存中存储两份的问题。词元嵌入权重是将词元转换为数值向量的学习参数。若用户关闭了将模型文件直接映射到内存的内存映射功能,在相应条件下就能以更少的内存加载同一模型。
1. 修复输入与输出权重共享
修复对象是 Gemma 3n 和 Gemma 4。这两个模型在处理输入时使用的词元嵌入权重,也就是将词元转换为数值向量的学习参数,也会用于生成模型答案的最后计算阶段,即最终输出层。这相当于输入和输出查阅同一张表,只要能使用同一块内存,就无需保存两份表。
此前先创建输出层,因而找不到可共享的嵌入;此次改为先创建嵌入,再让输出层引用它。这项修复并未改变模型权重的内容,而是先准备好共享对象,从而消除产生多余副本的原因。
修改作者解释说,其他 94 个输入与输出共享权重的模型架构早已采用这一顺序,只有这两个模型是例外。此次修复让这两个模型也按与现有架构相同的顺序准备权重。
2. 重复存储发生于关闭内存映射的 CPU 运行
问题的触发条件取决于文件加载方式与设备分配的组合。不使用内存映射,且输入与输出使用同一类型的内存缓冲区时,就会发生重复存储。这包括纯 CPU 运行,以及虽将部分层交给其他设备、但仍将输出层留在 CPU 上的运行方式。
默认使用内存映射时,输入与输出权重指向同一映射区域,因此原本就不会分配额外副本。所以,想确认更新效果的用户,除了是否使用 CPU,还需要检查模型的加载方式。
3. 测试模型的缓冲区减少 216 MiB
修改作者在纯 CPU、无内存映射条件下加载 gemma-4-E2B-it Q4_K_M 进行测量,结果显示,存放模型权重的 CPU 模型缓冲区从 3163.73 MiB 降至 2947.73 MiB。作者在变更提案中说明,216 MiB 的差值恰好等于一份词元嵌入的大小。
表:Gemma 4 CPU 模型缓冲区测量
条件:gemma-4-E2B-it Q4_K_M,纯 CPU,无内存映射,禁用权重重打包
单位:MiB
| 项目 | 数值 |
|---|---|
| 修改前 | 3163.73 |
| 修改后 | 2947.73 |
| 减少量 | 216 |
对比基准是修复前的开发版本 cf24317be。表中的减少量是作者给出的差值,代表移除一份重复分配的嵌入副本后节省的内存。
作者还以纯 CPU 方式运行同一模型,检查了每次选择概率最高词元时的输出。他表示,无论开启还是关闭内存映射,输出都与修复前一致。
4. 复现命令与内存节省范围
以下是变更提案中原样列出的测试命令。除了纯 CPU 运行和禁用内存映射,还关闭了权重重打包,即将权重布局重新组织为适合执行的形式,用于查看模型加载过程中报告的缓冲区大小。
llama-server -m gemma-4-E2B-it-Q4_K_M.gguf -dev none -ngl 0 -lm none --no-repack -lv 4实际收益是在 CPU 内存紧张的环境中腾出容纳模型的空间。相反,若将输出层放在 GPU 上,在该设备上单独存放嵌入副本的内存开销仍然存在。
公开的节省数据来自上述模型和配置,未提供单独的运行速度测量结果。
b12008 按正确顺序连接 Gemma 的共享权重,减少了不使用内存映射的 CPU 运行中的重复内存占用。
