7B的模型,4.5G的权重文件,24G的卡,看起来绰绰有余。第一次对话正常,问到一个长文档,进程直接退出。日志最后一行是CUDA out of memory。显存不是被权重吃掉的,是被上下文吃掉的。
跑推理的时候,显存分三块。权重是一块,固定不变,量化到四比特之后,7B模型大约4G。KV缓存是一块,跟上下文长度成正比,这是最容易失控的一块。激活值是一块,跟批大小有关,一般不大。三块加起来超过卡的容量,进程就没了,报错通常只留一行,看不出是哪块超的。想定位只能自己算。
KV缓存的大小可以粗算。每一层都要存一份K和V,7B模型三十二层,每层维度四千左右。按半精度算,一个token大约半兆。上下文开到三万两千,单个请求就是16G。同时来两个请求,卡就撑不住了。这个数乘以并发数,就是最坏情况下的显存占用。
Ollama的num_ctx默认两千零四十八,新版本改过这个默认值,很多人为了读长文档手动改到三万二。改完之后模型能读了,一并发就崩。更麻烦的是它不会在加载时报错,加载很轻松,跑到一半才退。日志里能看到offloaded 33/33 layers to GPU,说明全部卸载上去了,看着一切正常,崩的时候在推理阶段,不在加载阶段。这一点最容易误判,看到加载成功就以为没事了,回头去查网络和前端,查半天。真正的分界在第一个长请求。加载时不做显存预检,配多少按多少算,这点和vLLM的预分配不同。
判断是不是这一块,你开着nvidia-smi盯一会儿就行。加载完占6G,一提问窜到23G,然后掉到零,答案就很清楚了。空闲时那几G是权重,涨上去的那部分是缓存。
解法按代价排,先降上下文,再降并发,最后才是换卡。num_ctx从三万二降到八千,显存立刻省下四分之三,代价是长文档要分段送。分段送不等于效果差,关键是把相邻两段带上一点重叠,答案跨段的时候不至于断。降并发更直接,排队等着就行,代价是响应变慢。
量化也能省,但别一步跨太大。7B模型从四比特降到三比特,权重要省一个多G,质量掉得厉害,中文尤其明显。八比特反而没必要,省下来的显存不多,速度还慢。四比特到五比特之间,是这类小卡的甜点。
还有一种办法是把一部分层留在内存里,靠CPU补。速度会掉到原来的三分之一,但至少不崩。二十四G的卡跑7B模型,八千上下文加两个并发,是目前实测比较稳的组合。再往上加,就要换卡或者换更小的模型了。这套配置跑了一个月,没再退出过一次。
A5创业网 版权所有