LingBot-Vision 批量图像推理的性能调优实践

文章导读
如果你的 LingBot-Vision 在批量跑图片时速度达不到预期,优先怀疑的是调用方式而不是模型本身。最常用的提升手段集中在三处:把单张循环改成 batch 推理、把默认的 FP32 改成半精度、把 CPU 上的图片解码和缩放挪到独立线程里。每一步都要先用脚本量出当前的总耗时和吞吐量,再做改动,否则无法判断哪个优化真正有效。改动后的收益必须用同一批图片、相同硬件环境下多次运行取平均值来确认,不
📋 目录
  1. 基线脚本:单张循环推理的耗时测量
  2. 开启批处理模式
  3. 使用半精度与可能存在的加速后端
  4. 多线程或多进程数据加载
  5. 验证优化效果并对比
A A

如果你的 LingBot-Vision 在批量跑图片时速度达不到预期,优先怀疑的是调用方式而不是模型本身。最常用的提升手段集中在三处:把单张循环改成 batch 推理、把默认的 FP32 改成半精度、把 CPU 上的图片解码和缩放挪到独立线程里。每一步都要先用脚本量出当前的总耗时和吞吐量,再做改动,否则无法判断哪个优化真正有效。改动后的收益必须用同一批图片、相同硬件环境下多次运行取平均值来确认,不保证固定提速倍数。

判断:LingBot-Vision 批量推理慢,通常先从“单张循环送 GPU”改起。操作:逐张读取图片改为 batch 维度拼接,再配合 model.half() 或 torch.cuda.amp.autocast,以及多线程预取图片。验证:用同一图片集在优化前后分别跑多次,记录总耗时和每秒处理图片数。边界:加速效果依赖显卡型号、batch size 和图片尺寸,换机后需要重新测量。

基线脚本:单张循环推理的耗时测量

先建立可量化的基线,不要凭感觉判断快慢。下面这段脚本逐张读取图片并调用 LingBot-Vision 推理,记录总耗时和每张平均耗时。把模型加载、图片读取和推理计时分开,方便后续定位瓶颈。

import torch, time, cv2, glob
from lingbot_vision import LingBotVision

model = LingBotVision.from_pretrained("your-model-path")
model.eval()

image_paths = glob.glob("./images/*.jpg")  # 换成你的图片目录

def load_image(path):
    img = cv2.imread(path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    return img

start = time.time()
per_image = []
for path in image_paths:
    img = load_image(path)
    t0 = time.time()
    output = model.infer(img)  # 按实际接口调整
    per_image.append(time.time() - t0)

total = time.time() - start
avg = sum(per_image) / len(per_image)
throughput = len(image_paths) / total
print(f"total: {total:.2f}s, avg: {avg:.4f}s, throughput: {throughput:.2f} img/s")

记录下这个输出结果,作为后续优化的对比基准。如果每张图片平均耗时远大于预处理耗时,优先做批处理。

开启批处理模式

GPU 擅长并行处理相同 shape 的张量。批量推理的核心是把多张图片沿 batch 维度堆叠成一个大张量,一次前向传播完成多张图片的推理。注意图片需要先缩放到统一尺寸,否则无法堆叠。

LingBot-Vision 批量图像推理的性能调优实践
import torch, time, cv2, glob, numpy as np
from lingbot_vision import LingBotVision

model = LingBotVision.from_pretrained("your-model-path")
model.eval()

image_paths = glob.glob("./images/*.jpg")
batch_size = 8  # 可调,先试 4、8、16

preprocessed = []
for path in image_paths:
    img = cv2.imread(path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (224, 224))  # 与模型输入一致
    preprocessed.append(img)
   
batch_times = []
start = time.time()
for i in range(0, len(preprocessed), batch_size):
    batch = preprocessed[i:i+batch_size]
    batch_tensor = torch.tensor(np.array(batch)).permute(0, 3, 1, 2).float()
    t0 = time.time()
    outputs = model.infer_batch(batch_tensor)  # 按实际接口调整
    batch_times.append(time.time() - t0)

total = time.time() - start
print(f"batch total: {total:.2f}s, throughput: {len(image_paths)/total:.2f} img/s")

先将 batch_size 设置为 4、8、16、32 各跑一遍,记录吞吐量。通常 batch 越大吞吐越高,但显存占用也会增加,如果报 OOM 就降一档。

使用半精度与可能存在的加速后端

半精度(FP16)能减少显存占用和计算量。在 PyTorch 中有两种常见做法:直接把模型参数转成半精度,或在推理时使用自动混合精度。前者适合模型结构固定且算子都支持 FP16 的情况,后者更灵活,会在前向传播中自动选择 FP16 或 FP32。

import torch

model.half()  # 方式一:模型参数转半精度

# 方式二:使用 autocast 做自动混合精度
from torch.cuda.amp import autocast

with torch.no_grad():    
    with autocast():
        output = model.infer_batch(batch_tensor.half())

如果 LingBot-Vision 依赖的底层算子不支持半精度,推理可能出错或变慢,需要结合环境确认。另外,某些环境还提供 TensorRT、OpenVINO 等加速后端,这类后端通常需要额外导出和配置,不能直接替换模型路径。建议先测试半精度,再决定是否引入加速后端。

多线程或多进程数据加载

图片读取、resize、归一化等操作都在 CPU 上执行。如果 GPU 每完成一个 batch 后必须等待 CPU 准备下一批,GPU 的空闲时间就白费了。用 ThreadPoolExecutor 预读并预处理图片,放入队列,让 CPU 预处理与 GPU 推理重叠。

LingBot-Vision 批量图像推理的性能调优实践
from concurrent.futures import ThreadPoolExecutor
import queue, threading

workers = 4  # 先按 CPU 核心数的一半设置
batch_queue = queue.Queue(maxsize=8)

def preprocess_images(paths, batch_size):
    for i in range(0, len(paths), batch_size):
        batch_paths = paths[i:i+batch_size]
        batch = []
        for p in batch_paths:
            img = cv2.imread(p)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            img = cv2.resize(img, (224, 224))
            batch.append(img)
        batch_tensor = torch.tensor(np.array(batch)).permute(0, 3, 1, 2).float()
        batch_queue.put(batch_tensor)
    batch_queue.put(None)  # 结束标记

# 在推理循环前启动预取线程
with ThreadPoolExecutor(max_workers=workers) as executor:
    future = executor.submit(preprocess_images, image_paths, batch_size)
    while True:
        batch = batch_queue.get()
        if batch is None:
            break
        if torch.cuda.is_available():
            batch = batch.cuda()
        # 推理并记录时间
        with torch.no_grad():
            output = model.infer_batch(batch)

worker 数量只影响 CPU 预处理侧,不是越大越好。先用 CPU 物理核心数的一半起调,如果 CPU 占用不高但 GPU 仍有空闲,再逐步增加。需要观察任务管理器或 nvidia-smi 确认 GPU 利用率是否提升。

验证优化效果并对比

回到第 1 步的脚本,把其中逐张推理的部分替换为 batch 推理 + 半精度 + 预取线程的完整流程,保持图片集和硬件环境不变。每次运行后记录总耗时与吞吐量,建议每项优化跑 3 次取平均值,避免波动。

方案总耗时(秒)吞吐量(张/秒)显存占用(MB)
基线:单张循环待记录待记录待记录
开启 batch待记录待记录待记录
batch + 半精度待记录待记录待记录
全部优化待记录待记录待记录

对比时重点看总耗时和吞吐量,batch 大小和 worker 数量可以在表格中留备注列。如果某一项优化反而让耗时增加,说明该改动在你的环境不适用,应回退。最终采用哪一组配置,以多次平均后吞吐最高、且显存不超限为准。