想在本机跑 LingBot-Depth 2.0,先别急着接到摄像头或批处理流程里。影响能否跑动的两个量是输入分辨率决定的显存峰值,以及单帧推理耗时;这两个量必须放在同一组对照里测,否则很容易出现“能出图但接不上节拍”或者“小图跑得动、实际业务图一上就爆显存”的情况。建议的做法是先跑一个只做单帧推理的最小脚本,把分辨率当自变量,把显存峰值和单帧耗时当因变量,得到一张属于自己机器的对照表,再决定用哪一档。
判断顺序建议是:先用单帧脚本跑通基线,再按分辨率档位采集显存峰值与单帧耗时,最后用实测耗时反推能接受的输入频率。分辨率同时决定显存上限和单帧耗时,通常是第一优先调整项;输入频率属于业务节拍,能降就先降。换机器、换精度模式、换运行时版本后,显存与耗时结论都需要重测,不能直接沿用。
搭一个只跑单帧推理的最小脚本
最小脚本的用途是排除数据加载、多进程预处理、后处理和可视化干扰,只保留模型加载与一次前向。计时起止点要放在设备同步之后,否则异步内核排队的时间会被算丢,测出来的耗时偏乐观。预热次数建议至少三次,让显存池分配和算子选择稳定后再开始计时。
import time
import torch
def load_model(ckpt, device):
# 换成 LingBot-Depth 2.0 实际的加载入口
model = build_model(ckpt)
model.to(device).eval()
return model
@torch.no_grad()
def one_frame(model, x):
return model(x)
def bench(model, x, warmup=3, iters=5, device='cuda'):
for _ in range(warmup):
one_frame(model, x)
torch.cuda.synchronize(device)
t0 = time.perf_counter()
for _ in range(iters):
one_frame(model, x)
torch.cuda.synchronize(device)
t1 = time.perf_counter()
return (t1 - t0) / iters把 ckpt、设备、精度模式(fp32 / fp16 / bf16)作为可替换项写进参数里,后面每一档分辨率都复用同一个函数,避免不同脚本之间的写法差异污染对比结果。
采集显存峰值而不是只看平均占用
平均占用会掩盖瞬时峰值,而峰值才是决定后续能不能加大批或叠加其他模块的边界。做法是在推理线程之外另起一个采样线程,按固定间隔读显存用量,记录采样期间的最大值,同时记录推理结束并同步之后的残留占用。
import threading, time
import pynvml # 也可用 subprocess 调 nvidia-smi 轮询
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
def sample_peak(stop, result, interval=0.02):
peak = 0
while not stop.is_set():
used = pynvml.nvmlDeviceGetMemoryInfo(handle).used
peak = max(peak, used)
time.sleep(interval)
result['peak'] = peak
stop = threading.Event()
result = {}
th = threading.Thread(target=sample_peak, args=(stop, result), daemon=True)
th.start()
run_one_inference()
stop.set(); th.join()
print(result['peak'] / 1024**2, 'MiB')
print(torch.cuda.memory_reserved() / 1024**2, 'MiB reserved')
print(torch.cuda.memory_allocated() / 1024**2, 'MiB allocated')采样间隔可以先设 20 到 50 毫秒,间隔太粗会漏掉峰值,太细会引入较多读取开销。要注意区分首次推理与后续推理:首次通常包含显存池建立、权重首次搬运和算子选择,峰值和耗时都偏高;稳定值应以第二次及以后为准,同时把首次数值单独记一列,作为冷启动参考。
按输入分辨率跑一组对照
建议选三到四档分辨率,例如短边 384、512、640、768,保持长宽比并按模型要求的倍数对齐(常见要求是 32 或 64 的倍数,需要结合本地配置确认)。每档重复五到十次,丢掉第一次,取中位数或去掉极值后的均值,记录输入尺寸、显存峰值、单帧耗时三个字段。
| 输入尺寸 | 显存峰值(MiB) | 单帧耗时(ms) | 备注 |
|---|---|---|---|
| 待填 | 待填 | 待填 | 首次 / 稳定值分开记 |
| 待填 | 待填 | 待填 | |
| 待填 | 待填 | 待填 | |
| 待填 | 待填 | 待填 |
同一档分辨率下如果多个批大小都要用,就把批大小也作为一个维度写进备注,而不是只测批大小为一的情况。判断依据是:分辨率主要推高显存峰值,批大小主要推高显存峰值和单帧耗时总量;显存吃紧时优先降批大小或降分辨率,耗时吃紧时优先降分辨率。
测出稳定帧率的边界
由单帧耗时 t 秒可以推算连续处理时的理论上限,约为 1/t 次每秒;但连续处理还要叠加图像读取、缩放、拷贝、后处理和其它进程占用,所以实际可用频率建议明显低于这个上限,具体留多少余量要结合本地资源占用确认。可以用一个时间窗口内跑固定次数推理,看总耗时是否稳定、显存峰值是否逐步抬升,来判断能否长时间维持。
出现超时的处理顺序:先降输入分辨率,因为分辨率同时降低显存峰值和单帧耗时,收益最直接;每个分辨率档都记好下限。只有当分辨率已经降到任务可接受的下限、画质或精度不能再退时,才去降输入频率,例如抽帧、跳帧或降低触发频率。如果既不接受降分辨率也不接受降频率,就应该考虑换更大显存的设备或改用更小的精度模式,而不是在软件层面硬撑。
把测试环境参数一并记录
只记一张表还不够,结论要能跨机器复用,必须把环境一起记下来。建议记录的项包括:设备型号与显存容量、驱动版本、运行时与框架版本(例如 PyTorch 与 CUDA 版本)、推理精度模式、输入分辨率、批大小,以及是否开启显存池、是否设置过显存增长策略。
- 设备型号与显存容量:决定分辨率档位的上限。
- 驱动与运行时版本:影响算子实现和显存占用。
- 精度模式:fp32 与 fp16 / bf16 的显存和耗时差异明显,需要分别测。
- 输入分辨率与批大小:对照表的两个自变量。
- 采样方式与间隔:说明峰值是怎么采到的,便于复现。
换环境后不要全表重跑,可以先重测当前使用档以及相邻的上下各一档:如果当前档的显存峰值和单帧耗时与旧记录接近,其余档位通常也可以沿用;如果当前档明显偏高或偏低,就需要把整组分辨率对照重做一遍。