本地跑 NeoHorse-1 显存爆掉时,先把问题拆成「权重常驻」和「KV 缓存随上下文增长」两块,比直接换推理后端更容易定位。判断顺序建议是:固定量化版本、先把最大上下文压短,确认能稳定加载;如果压到能用了但不想牺牲上下文,再换更低比特量化;最后才去调推理后端的批处理和缓存策略。
显存不足通常来自两块:模型权重常驻和 KV 缓存随上下文增长。建议先固定量化版本,把最大上下文和批大小设到保守值,记录加载后空闲显存与单请求峰值;确认能稳定加载后,再逐档放大上下文。若仍不够,再换更低比特量化并复测输出连贯性。批处理与缓存策略属于第三层调整,改完必须用同一段输入复测,避免把截断或崩溃误判成显存问题。
记录加载模型后的基础显存占用
先把两种溢出分开:一种是权重本身放不进显存,加载阶段就报 out of memory;另一种是权重进去了,但请求变长后 KV 缓存把剩余空间吃光。前者只能靠换量化或换设备,后者可以先从上下文长度下手。
在加载前后各记一次读数,建议记录「空闲基线、加载完成后、静置一段时间后」三个时间点。静置后占用仍缓慢上涨,通常是缓存没有随请求释放,而不是权重变大。
nvidia-smi `--query-gpu`=memory.used,memory.total `--format`=csv -l 2
nvidia-smi `--query-compute-apps`=pid,process_name,used_memory `--format`=csv
非 NVIDIA 平台可以用 rocm-smi 或系统自带的显存面板;统一内存平台看的是系统内存与显存共享的总量。重点是同一命令在加载前后的差值,不必追求绝对值。
| 占用来源 | 观察方式 | 增长时机 | 主要调整手段 |
|---|---|---|---|
| 模型权重 | 加载完成后、无请求时的占用 | 加载阶段一次性 | 换更低比特量化、分片加载 |
| KV 缓存 | 请求变长、并发变多时的增量 | 随上下文与并发增长 | 缩短最大长度、限制并发、调整缓存分配 |
| 框架运行时开销 | 加载后与空进程的差值 | 启动时固定 | 换后端、关闭不必要的预处理 |
| 批处理副本 | 并发请求数增加时的占用 | 随批大小增长 | 降批大小与并发数 |
缩短上下文长度并观察峰值
这一步用来验证长上下文是不是主要增量。先把最大长度和批大小设成保守值,加载成功后再逐档放大。
model: NeoHorse-1
max_context_len: MAX_CTX # 先给保守值,字段名按你的框架替换
max_batch_size: 1
max_concurrency: 1
测试时用同一条提示词,把输入长度按短、中、长分成几档,每档单独发一次请求,同时用 nvidia-smi -l 1 或框架自带的显存统计观察峰值。峰值至少记三个值:加载后基线、单请求峰值、请求结束后是否回落到基线附近。
如果短输入正常、长输入才爆,基本可以确认瓶颈在 KV 缓存,继续压上下文或限制并发即可;如果短输入也爆,说明权重本身占满了,直接进入量化那一步。
换成更低比特量化版本
量化版本通常会在文件名或配置里带位数标识,例如 8bit、4bit、Q4、Q5、int8、fp8 这类命名,具体以仓库提供的版本为准。位数越低,权重占用越小,但输出质量和速度能不能接受,需要自己判断。
# 示意,字段名按你的加载方式替换
quant_type: int4
load_in_4bit: true
dtype: auto
换量化后不要只看能不能加载,还要做一次连贯性检查:用同一段输入,看输出有没有明显重复、语句断裂、数字或代码片段被改写。偶发轻微退化通常可以接受;如果整段跑偏,就不建议为了省显存硬换。
需要留意的是,KV 缓存一般不会因为权重量化而同步变小,量化主要解决加载阶段的权重占用。长上下文仍是瓶颈的话,还是要回到上一步或继续调缓存策略。
调整推理后端的批处理和缓存策略
这一层改的是运行时怎么分配显存,适合权重已经能加载、但并发一上来就溢出的情况。常见可调项包括批大小、最大并发数和 KV 缓存开关。
max_batch_size: 1
max_concurrency: 1
kv_cache: on # 单请求调试阶段可先关掉,观察纯权重占用
gpu_memory_utilization: 按保守值填写
验证顺序建议一项一项来:先只降批大小,用同一请求复测;再只降并发数;最后才考虑暂时关掉 KV 缓存观察纯权重占用。每改一项就记一次加载后基线和峰值,避免一次改多项之后无法判断是哪一项起作用。
关 KV 缓存这类做法只是临时定位手段,长文本生成会明显变慢甚至不可用,不适合当成长期方案。
用同一段任务复测稳定性
显存降下来不等于能正常用。准备一段固定输入,最好包含长段落和少量结构化内容,每次调整后都跑同一段。
重点记录三件事:输出长度是否明显短于调整前、是否出现截断或重复、错误日志里有没有 out of memory 之外的新报错(例如缓存分配失败、请求被中断)。
# 通用观察骨架
请求输入:固定文本 + 固定 max_tokens
记录:生成 token 数、结束原因、退出码、错误日志行
对比:调整前后是否都能完整跑完同一段任务
如果调整后能跑完、输出完整,只是速度慢了些,这个配置通常就是可用的;如果出现截断或中途退出,说明改动引入了新问题,需要回退该项再试。显存不够时,先降上下文、再换量化、最后调后端,是比较省事的排查顺序。