用于本地运行语言模型的 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

测试模型的缓冲区减少 216 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 运行中的重复内存占用。