已经拿到权重却在第一次推理就卡住,通常不是模型本身的问题,而是两件事没对齐:加载器到底读了目录里的哪些文件,以及图像预处理后的张量形状是否和模型声明的输入一致。建议先用一张图跑通最短链路,把加载、预处理、前向、可视化四步分开验证,哪一步报错就停在哪一步查,不要一次改多个变量。
适用场景:本地已有 LingBot-Vision 权重,但首图推理报错或输出看不懂。操作动作:先确认权重入口文件是否完整,再用最小脚本单图前向,随后逐项核对 resize、归一化与输入尺寸。验证方式:打印张量形状、看加载日志、保存输出并肉眼比对。风险边界:仓库实现差异较大,变量名与配置键需按实际代码替换;形状对不上时先修预处理,不要直接改模型定义。
确认权重目录里哪些文件是加载入口
下载下来的权重目录通常不只有权重本体,还会夹着配置文件、预处理配置、版本标记甚至 README。加载器一般只认其中两三个入口,先看目录和加载日志,比反复改推理代码更有效率。
weights/
├── model.safetensors # 也可能是 pytorch_model.bin
├── config.json
├── preprocessor_config.json # 部分仓库叫 processor_config.json
└── README.md
加载时通常被读取的文件有三类:权重本体(.safetensors 或 .bin)、模型结构配置(config.json)、预处理配置(preprocessor_config.json / processor_config.json)。缺文件时的报错样式比较固定:找不到 config.json 之类的路径错误、取配置字段时的 KeyError、或者加载完成但提示 unexpected key / missing key。可以先执行 ls -l weights/ 看清单,再在构造模型时打开 output_loading_info 或等价开关,确认 missing_keys 为空。
写最小推理脚本:从读一张图到拿到前向输出
第一版脚本只保留读图、张量转换、调用模型、取输出四件事。下面骨架里的类名、加载函数、预处理类都需要按实际仓库替换,路径也要换成自己的。
import json
import torch
from PIL import Image
# 按实际仓库替换:类名与加载函数
from lingbot_vision import LingBotVisionForInference, LingBotVisionProcessor
WEIGHTS = './weights' # 按实际权重目录替换
IMAGE = './demo.jpg' # 按实际图片路径替换
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
processor = LingBotVisionProcessor.from_pretrained(WEIGHTS)
model = LingBotVisionForInference.from_pretrained(WEIGHTS)
model.eval().to(DEVICE)
img = Image.open(IMAGE).convert('RGB')
inputs = processor(images=img, return_tensors='pt')
print({k: tuple(v.shape) for k, v in inputs.items()})
with torch.no_grad():
outputs = model(**{k: v.to(DEVICE) for k, v in inputs.items()})
print(type(outputs))
建议先只跑这一条链路,确认前向不报错再考虑批量或视频输入。如果这里就抛形状错误,问题基本落回下一节的预处理对齐。
对齐图像预处理与模型输入尺寸
输入尺寸不要凭记忆写 224 或 512,先从配置里读。打开 preprocessor_config.json 或 config.json,找 size、image_size、input_size 这类字段,以配置值为准。
cfg = json.load(open('weights/preprocessor_config.json'))
print(cfg.get('size') or cfg.get('image_size') or cfg.get('input_size'))
print('after processor:', inputs['pixel_values'].shape) # 期望 (1, C, H, W)
顺序上,先 resize 或中心裁剪到目标高宽,再按 mean/std 归一化,最后转 tensor 并补上 batch 维度。顺序颠倒时形状往往仍然能过,但数值范围已经不对,表现是输出全灰或全黑。打印张量的位置放两处即可:处理器输出后打一次,送进模型前再打一次,两次一致说明链路没被中途改动。如果形状是 (C, H, W) 而模型要 (B, C, H, W),补一个 unsqueeze(0) 就行,不用改模型。
把输出张量还原成能看的图或标注
先看输出形状再猜语义:常见是 (B, C, H, W) 的分割 logits 或掩码,也可能是 (B, N, D) 的特征序列。打印 shape 和 dtype 之后再决定后处理方式,不要直接拿输出当图片保存。
out = outputs[0] if isinstance(outputs, (list, tuple)) else outputs
x = out.detach().cpu()
print('raw out:', x.shape, x.dtype)
if x.dim() == 4 and x.shape[1] == 1:
mask = (torch.sigmoid(x) > 0.5).float()[0, 0]
Image.fromarray((mask.numpy() * 255).astype('uint8')).save('mask.png')
保存后用本地图片查看器打开,和原图并排看一遍:输出尺寸是否合理、内容是否落在目标区域。掩码存成 PNG 最省事;需要叠加时用 PIL 把掩码转成半透明图层贴回原图即可。若输出全零或全一,优先回到配置核对归一化的 mean/std 是否与实际训练时一致。
报错分流:加载失败、显存不足、形状不匹配怎么分头查
- 加载失败:第一个检查点是权重目录的入口文件是否齐全。验证命令
ls -l weights/,并打开 output_loading_info 看 missing_keys 和 unexpected_keys 是否为空。 - 显存不足:第一个检查点是 batch size 和输入分辨率。先把 batch 固定为 1、输入降到配置允许的最小尺寸再试一次,确认 OOM 是否消失。这只是定位瓶颈的排查动作,不等于性能优化。
- 形状不匹配:第一个检查点是处理器输出的 pixel_values 形状与 config 中声明的输入尺寸是否一致。用前面打印形状的代码比对,多数情况是 resize 目标高宽或通道顺序写反。
三类报错对应三个环节,按顺序比对就能把范围缩到一行配置或一次张量变换上,不必再对整个脚本做地毯式修改。