在本地笔记本上跑 MiniCPM-V 4.7 这类视觉语言模型,能不能跑起来主要取决于两块:显卡实际可用的显存,和量化档位决定的权重占用。建议先把机器可用显存量出来,再用参数量和位宽把权重占用算成一个区间,然后只带一张小图跑一次最小推理,看实际峰值是否落在估算区间内。加载阶段就报显存不足,优先降量化档位;加载成功但推理中途报错,优先缩小输入图片尺寸和张数。
判断顺序建议固定为:先量可用显存,再按参数量×位宽÷8 估权重占用,然后跑最小推理对齐估算与实测。加载即 OOM 时先换更低的量化档位;加载正常、生成中途 OOM 时,先把输入图片长边调小、张数减到 1。量化档位和图片尺寸不要同时改,一次只动一个变量,才能知道是哪一项吃掉了显存。具体数值需要结合你的权重版本和推理框架确认。
先用命令量出机器可用的显存与内存
不要按显卡标称容量判断。桌面合成、浏览器、IDE 都会占用显存,标称 8GB 的卡留给模型的部分可能明显小于 8GB。先关掉能关的图形类程序,再读一次数值。
NVIDIA 显卡在 Linux 和 Windows 上都可以用下面的命令读:
nvidia-smi `--query-gpu`=name,memory.total,memory.used,memory.free `--format`=csv
watch -n 1 nvidia-smi需要读的字段是 total(总量)、used(已用)、free(可用)。free 是瞬时值,建议在关掉其它推理进程后再看一遍,并以较低的那次为准。需要注意的是,nvidia-smi 里的已用量包含当前桌面占用,真正可分配余量有时比 free 更小,建议留出 0.5-1GB 余量再决定档位。
内存用 free 的 available 字段,而不是 free 字段本身:
free -h
# 关注 Mem 行的 available,而不是 freeApple 芯片是统一内存,没有独立的显存数值,用 vm_stat 和活动监视器的内存压力判断更可靠;不同芯片可分配给 GPU 的上限不一样,需要结合具体机型确认。在这类机器上能不能跑,常常取决于内存够不够,而不是显存够不够。
把权重文件大小换算成显存占用区间
权重占用可以先用参数量和位宽估一个下限:
权重占用(GB)≈ 参数量(B)× 位宽(bit)÷ 8
举例说明,假设你的权重是 8B 参数:FP16 时 8×16÷8=16GB,INT8 时 8×8÷8=8GB,INT4 时 8×4÷8=4GB。这个式子只算权重本体,实际还要加上视觉编码器、投影层、embedding 以及推理框架自身的开销,通常要在结果上再留一档余量。
真正占显存的不止权重。推理过程中还会产生激活值、KV 缓存和图片预处理后的张量,这些和一次喂进去多少 token、图片多大强相关。所以估算值只是一个下界,用来判断这档量化在理论上有没有可能,最终是否跑得通要靠下一节的最小推理确认。
另外注意:如果权重目录里已经有量化好的文件,文件名通常带 q4、int8、awq、gptq 之类后缀,这时位宽以文件为准,不要按原始 FP16 去估。
跑一次最小推理并记录峰值显存
这一段只验证一件事:加载后加一次单图生成的峰值显存,与上一节的估算是否接近。脚本骨架按本地权重仓库的类名替换,下面是一种通用写法:
import time, torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForImageTextToText
model_id = '/path/to/local/weights'
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(
model_id, torch_dtype=torch.float16, device_map='auto')
image = Image.open('one_small_image.jpg').convert('RGB')
messages = [{'role': 'user', 'content': [
{'type': 'image'}, {'type': 'text', 'text': '这张图里有什么?'}]}]
prompt = processor.apply_chat_template(messages, add_generation_prompt=True)
inputs = processor(text=prompt, images=[image], return_tensors='pt').to(model.device)
print({k: tuple(v.shape) for k, v in inputs.items() if hasattr(v, 'shape')})
t0 = time.time()
with torch.inference_mode():
out = model.generate(**inputs, max_new_tokens=32)
print('elapsed', round(time.time() - t0, 2))
print(processor.batch_decode(out, skip_special_tokens=True)[0])模型类名、processor 方法名和图片传参格式以本地权重目录里的 config 与示例为准,对不上就按示例改,不要照抄。
另开一个终端采样显存:
nvidia-smi `--query-gpu`=memory.used `--format`=csv,noheader,nounits -l 1记录四列就够:加载完成后的空载显存、图片编码阶段显存、generate 过程中的峰值、生成结束后的残留。耗时用上面的 elapsed 记。把峰值减去第二节算出的权重占用,差值就是激活值和缓存的开销,后面调图片尺寸时这个差值会跟着变。
固定图片只改量化档位做对照
量化档位的配置位置通常有三种:一是加载参数里,如 load_in_4bit、load_in_8bit 或 quantization_config;二是权重文件名本身区分档位,如带 -int8、-q4 后缀的独立目录;三是启动脚本里的启动参数。先用 ls 和权重目录里的 config.json、quantization_config 字段确认当前用的是哪一种,再决定怎么切换。
做法是固定同一张图片、同一句提示词,只换量化档位,每一档记录四件事:加载是否成功、报错原文(完整复制,OOM 通常会写 attempted to allocate xxx MiB,以及 GPU 总容量和剩余容量)、加载完成后的空载显存、最小推理的峰值显存。报错原文要整段贴出来,只写显存不足会丢掉判断依据。
判定方式:如果低档量化能加载、高档不能,说明瓶颈在权重,继续降档或换更小的权重版本即可;如果所有档位都在加载阶段失败,要检查是不是 device_map 把层放到了 CPU,或者内存本身不够,这时调量化没有意义。如果加载都正常、只有生成阶段报错,问题就不在权重,转到下一节处理。
把输入图片尺寸与张数纳入同一张对照表
图片预处理尺寸的调整位置,一般在 processor 的 image_size、max_pixels、min_pixels 这类参数上,部分实现用切图数量(如 max_slice_nums)控制高分辨率输入,具体以权重目录里的 preprocessor_config.json 为准。张数就是一次请求里 images 列表的长度。
建议按下面的表头逐档记录,每一项都跑三次取稳定值:
图片长边(px) | 张数 | 加载后显存 | 峰值显存 | 单次耗时(s) | 是否成功判定标准可以定为:连续三次不 OOM,且峰值显存距离第一节量到的可用值仍留有余量,通常留 1GB 以上给系统和其它进程,这个档位才算可稳定运行,超过这个档位就退回上一档。调整时先只减图片长边,长边降到不能再降,再减张数,最后才考虑换量化档位,因为换档位要重新加载模型,比较费时间。
如果长边从大调到小、峰值显存几乎不动,说明瓶颈还在权重,回到第四节降量化档位更直接。