Colibrì 跑小模型时内存吃紧,把冷层权重挪到磁盘通常能压住常驻内存峰值,但代价主要落在首次首字延迟上:第一批请求需要把冷层从盘读进来。所以别先改配置,先把改前的加载耗时、首字延迟、进程常驻内存峰值三项量下来做基准,再决定分层值不值得留。用分层前,建议明确三件事:分层只对权重占用有效,如果内存吃紧来自 KV cache 或长上下文,挪权重基本没用;对照必须在同一批输入、同一上下文长度、重启进程后做;回退路径要提前想好,磁盘读写一旦成为瓶颈,进程会表现为首字延迟抖动而不是稳定变慢。
内存吃紧时把冷层权重落盘,通常能降低进程常驻内存峰值,代价集中在首次首字延迟。建议先记录改前的加载耗时、首字延迟与常驻内存峰值作为基线,再启用冷层目录配置、重启进程做同题对照。判断标准是首次首字延迟的增量是否影响交互,以及省下的内存是否足以让进程在目标机器上留出余量;两者都不满足就换更快的盘或退回全内存加载。
先确认内存吃紧发生在加载阶段还是生成阶段
直接改分层配置最常见的失误,是把 KV cache 或上下文带来的增长当成权重问题。建议在改动前先采一组数,把瓶颈定位到具体阶段。需要记录三个量:
- 加载耗时:从进程启动到日志出现就绪行之间的时间差。用启动前后时间戳相减即可,也可以在启动命令外包一层计时,例如
date +%s.%N打点,或直接看日志行自带的时间戳。 - 首字延迟:从发出请求到收到第一个 token 的时间。用客户端计时,不用总耗时减生成耗时去估算;首次请求和第二次请求分开记,因为第一次可能包含冷层从盘读入的时间,第二次可能命中页缓存。
- 进程常驻内存峰值:不要只看某一时刻的 RSS。峰值可以读
/proc/<pid>/status里的 VmHWM,也可以在生成过程中用ps -o rss=定时采样取最大值。
判读方式比较朴素:如果 RSS 在加载阶段就冲到接近上限、生成阶段基本持平,权重分层是有效的方向;如果加载后 RSS 不高,但长上下文请求推进过程中 RSS 持续爬升,那属于缓存或激活占用,改冷层目录不会有帮助,应该先从上下文长度和并发数上找余地。
把冷层目录和热层上限写进配置
下面是一个可复制的配置骨架,字段名、层级结构和是否支持环境变量覆盖,都需要以仓库实际示例配置为准。关键是三件事:模型路径、留在内存里的热层数量、冷层落盘目录。上下文长度和并发参数在对照实验期间必须保持不动,否则测出来的差异说明不了问题。
# 示意骨架,字段名以仓库文档/示例配置为准
model = ./models/small.gguf
ctx_len = 2048 # 对照期间固定不变
gpu_layers = 0 # 纯 CPU 场景留 0
hot_layers = 12 # 常驻内存的热层数量,按可用内存调
cold_dir = /var/lib/colibri/cold # 冷层权重落盘目录
# cold_mmap = true # 若仓库支持 mmap 读冷层则打开,不支持就删掉这行如果实现支持环境变量覆盖,写法大致如下。变量名前缀和覆盖优先级同样需要确认,不确定时优先改配置文件,避免两处配置互相打架。
export COLIBRI_MODEL=./models/small.gguf
export COLIBRI_HOT_LAYERS=12
export COLIBRI_COLD_DIR=/var/lib/colibri/cold
./colibri `--config` ./colibri.conf启动后第一件事是核对常驻内存是否真的低于改前水平:grep VmHWM /proc/<pid>/status 对比峰值,ps -o rss=,vsz= -p <pid> 看当前驻留。同时看启动日志里有没有冷层目录、冷层数量、mmap 之类的行,确认配置被真正读进去了,而不是解析失败后悄悄退回默认值。冷层目录也要确认确实有文件落盘,比如用 du -sh /var/lib/colibri/cold 看体积。
冷层落盘后重启进程做一次同题对照
分层是否划算只能用同一批输入比出来。做法是:改前跑一遍记录基准,停进程、清一次页缓存或至少重启进程,改配置后再跑同一批请求,上下文长度、并发数、生成参数保持一致。每条配置跑三次取中位数,首次请求和第二次请求分开统计。
| 配置 | 加载耗时 | 首字延迟(首次) | 首字延迟(第二次) | 常驻内存峰值 | 备注 |
|---|---|---|---|---|---|
| 全内存加载(改前基准) | (待填) | (待填) | (待填) | (待填) | 记录 VmHWM |
| 冷层落盘 + hot_layers=12 | (待填) | (待填) | (待填) | (待填) | 记录冷层目录体积 |
| 冷层落盘 + hot_layers 调大 | (待填) | (待填) | (待填) | (待填) | 确认峰值仍低于上限 |
判断口径建议落在两处。一是首次首字延迟相对基准的增量,是否已经到了交互场景下能明显感知的程度;如果客户端用户会盯着屏幕等,这个增量就是真实成本。二是第二次首字延迟有没有回到接近基准的水平:如果回了,说明主要成本在冷层首次加载,可以用启动后的一次预热请求把这部分摊掉,分层就划算;如果第二次依然明显变慢,说明冷层在生成过程中被反复读取,此时节省的内存换来的代价偏大。还要注意一个边界:常驻内存峰值虽然降了,但如果降下去的余量不足以让你在目标机器上稳定运行,或者仍然贴着上限,那这次分层只是把崩溃推后,不解决问题。
磁盘读写成为新瓶颈时的回退做法
分层本身不是性能提升手段,它只是用延迟换内存。当冷层被频繁读取,盘会成为新的瓶颈,表现往往是首字延迟波动变大而不是稳定偏高。观察方法很简单:进程侧看 grep read_bytes /proc/<pid>/io,间隔几秒取两次差值,判断生成阶段是否还在持续读盘;系统侧用 pidstat -d 1 -p <pid> 或 iostat -x 1 看该进程的读吞吐和磁盘利用率。如果生成阶段 read_bytes 一直涨,说明热层数量设小了,或者模型层访问本身缺乏局部性。
止损顺序建议由轻到重:先把热层数量调大,把访问最频繁的层留在内存,重新对照首字延迟;再把冷层目录从网络盘或机械盘换到本地 SSD 或 NVMe,目录路径改完要重启进程并确认日志指向新路径;如果换盘后首字延迟的增量依然影响交互,就直接回退。回退时删掉或注释掉 cold_dir、hot_layers 这类分层相关配置,恢复改前的配置文件,重启进程,再用 grep VmHWM /proc/<pid>/status 确认常驻内存回到改前水平,并跑一遍同一批输入确认首字延迟回到基准附近。回退后如果内存仍然不够,就该换模型规格或降低上下文长度,而不是继续在分层参数上打转。