实时视觉:边缘部署


文档摘要

实时视觉:边缘部署 本节摘要:边缘推理的纪律,是让一个 90% 准确率的模型在 2 GB 内存的设备上跑 30 fps——每一个百分点的准确率都要拿毫秒级延迟来换。本节先立测量纪律(延迟、峰值内存、功耗三个预算,加预热、同步、固定输入三条铁律),再走三个旋钮:选更小的架构、把 FP32 量化成 INT8、换推理运行时(ONNX Runtime、TensorRT、Core ML、TFLite)。读完本节,你能为手机、Jetson、工业相机、浏览器挑出 MobileNetV3 / EfficientNet-Lite / ConvNeXt-Tiny / MobileViT,并验证每一档省了多少、掉了多少精度。 对应原课程:Phase 4 · Lesson 15 · (原英文 )。

实时视觉:边缘部署

本节摘要:边缘推理的纪律,是让一个 90% 准确率的模型在 2 GB 内存的设备上跑 30 fps——每一个百分点的准确率都要拿毫秒级延迟来换。本节先立测量纪律(延迟、峰值内存、功耗三个预算,加预热、同步、固定输入三条铁律),再走三个旋钮:选更小的架构、把 FP32 量化成 INT8、换推理运行时(ONNX Runtime、TensorRT、Core ML、TFLite)。读完本节,你能为手机、Jetson、工业相机、浏览器挑出 MobileNetV3 / EfficientNet-Lite / ConvNeXt-Tiny / MobileViT,并验证每一档省了多少、掉了多少精度。

对应原课程:Phase 4 · Lesson 15 · real-time-edge(原英文 phases/04-computer-vision/15-real-time-edge/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 为任意 PyTorch 模型测量推理延迟、峰值内存和吞吐,读懂 FLOPs / 参数量 / 延迟的权衡。
  2. 用 PyTorch 的训练后量化把视觉模型量化到 INT8,并验证精度损失 < 1%。
  3. 导出到 ONNX,用 ONNX Runtime 或 TensorRT 编译,说出三种最常见的导出失败及修法。
  4. 解释边缘约束下何时选 MobileNetV3、EfficientNet-Lite、ConvNeXt-Tiny、MobileViT

一、问题与直觉

训练时的视觉模型是一头浮点怪兽:一亿参数、每次前向 10 GFLOPs、2 GB 显存。手机、车机、工业相机、无人机都装不下。上线一套视觉系统,意味着把同样的预测塞进一个百倍小的预算里。

三个旋钮干了绝大部分活:模型选择(同配方下更小的架构)、量化(INT8 替 FP32)、推理运行时(ONNX Runtime、TensorRT、Core ML、TFLite)。把它们摆对,是「工作站上跑得动的 demo」与「30 美元摄像头模组上能出货的产品」之间的分野。

本节先把测量纪律立起来(测不了就优化不了),再走这三个旋钮。目标不是学遍每个边缘运行时,而是知道有哪些杠杆、怎么验证每个杠杆真的有效。

三个预算

  • 延迟:p50、p95、p99。只看 p50 均值会掩盖实时系统真正在意的尾部行为。
  • 峰值内存:设备见过的最大值,不是稳态均值。嵌入式上 OOM 是致命的。
  • 功耗 / 能量:电池设备上每推理毫焦。常用 CPU/GPU 利用率 × 时间来近似。

一张 (模型, 延迟, 内存, 精度) 表就是做边缘决策的依据。每个格子都在目标设备上测,不是工作站。

测量纪律

每份边缘性能剖析都该遵守三条:

  1. 预热——测量前用 5~10 次哑前向跑一遍。冷缓存和 JIT 编译会让第一个数字失真。
  2. 同步——GPU 工作在计时块前后用 torch.cuda.synchronize()。否则测的是 kernel 派发,不是 kernel 执行。
  3. 固定输入尺寸——到生产分辨率。224×224 的延迟不是 512×512 的延迟。

FLOPs 作代理

FLOPs(每推理浮点运算数)是便宜的、与设备无关的延迟代理。对架构比较有用,作绝对时钟则有误导。FLOPs 多 10% 的模型可能实测快 2 倍,因为它用的是硬件友好的算子(深度卷积编译得好,7×7 大卷积编译不好)。

规矩:架构搜索用 FLOPs,部署决策用设备实测延迟。

量化一段话讲清

把 FP32 权重和激活换成 INT8。模型体积缩 4 倍,内存带宽缩 4 倍,在有 INT8 kernel 的硬件上(每个现代手机 SoC、每个带 Tensor Core 的 NVIDIA GPU)算力提升 24 倍。训练后静态量化在视觉任务上精度损失通常 0.11 个百分点。

类型:

  • 动态——权重量化到 INT8,激活用 FP 算。简单,加速小。
  • 静态(训练后)——权重量化 + 在小校准集上校准激活范围。比动态快得多。
  • 量化感知训练(QAT)——训练时模拟量化让模型学着绕开它。精度最好,要有标签数据。

视觉任务上,训练后静态量化用 5% 的功夫拿到 95% 的收益。只有 PTQ 精度损失不可接受时才用 QAT。

剪枝与蒸馏

  • 剪枝——去掉不重要的权重(按幅值)或通道(结构化)。对过参数化模型有效;对已经很紧凑的架构用处不大。
  • 蒸馏——训一个小学生去模仿大教师的 logits。常能恢复因缩小模型而丢的大部分精度。生产边缘模型的标准操作。

推理运行时

  • PyTorch eager——慢,不用于部署,只用于开发。
  • TorchScript——老路子,已被 torch.compile 和 ONNX 导出取代。
  • ONNX Runtime——中立运行时。CPU、CUDA、CoreML、TensorRT、OpenVINO 都有 ONNX provider。从这里起步。
  • TensorRT——NVIDIA 的编译器。NVIDIA GPU(工作站和 Jetson)上延迟最佳,可与 ONNX Runtime 集成或独立用。
  • Core ML——苹果 iOS/macOS 运行时,要 .mlmodel.mlpackage
  • TFLite——谷歌 Android/ARM 运行时,要 .tflite
  • OpenVINO——英特尔 CPU/VPU 运行时,要 .xml + .bin

实践:PyTorch → ONNX → 为目标选运行时。ONNX 是通用语。

边缘架构挑选

预算 模型 为何
< 300 万参数 MobileNetV3-Small 处处编译得好,好基线
300~1000 万 EfficientNet-Lite-B0 TFLite 上每参数精度最佳
1000~2000 万 ConvNeXt-Tiny 每参数精度最佳,CPU 友好
2000~3000 万 MobileViT-S 或 EfficientViT 带 ImageNet 精度的 Transformer
3000~8000 万 Swin-V2-Tiny 仅当栈支持窗口注意力

除非有特定理由,这些全部量化到 INT8。

二、从零实现

步骤 1:正确测量延迟

import time import torch def measure_latency(model, input_shape, device="cpu", warmup=10, iters=50): model = model.to(device).eval() x = torch.randn(input_shape, device=device) with torch.no_grad(): for _ in range(warmup): model(x) if device == "cuda": torch.cuda.synchronize() times = [] for _ in range(iters): if device == "cuda": torch.cuda.synchronize() t0 = time.perf_counter() model(x) if device == "cuda": torch.cuda.synchronize() times.append((time.perf_counter() - t0) * 1000) times.sort() return { "p50_ms": times[len(times) // 2], "p95_ms": times[int(len(times) * 0.95)], "p99_ms": times[int(len(times) * 0.99)], "mean_ms": sum(times) / len(times), }

预热、同步、用 time.perf_counter()。报百分位,不只均值。

步骤 2:参数量与 FLOP 计数

def parameter_count(model): return sum(p.numel() for p in model.parameters()) def flops_estimate(model, input_shape): """ 仅卷积/线性模型的粗略 FLOP 计数。生产用 fvcore 或 ptflops。 """ total = 0 def conv_hook(m, inp, out): nonlocal total c_out, c_in, kh, kw = m.weight.shape h, w = out.shape[-2:] total += 2 * c_in * c_out * kh * kw * h * w def linear_hook(m, inp, out): nonlocal total total += 2 * m.in_features * m.out_features hooks = [] for m in model.modules(): if isinstance(m, torch.nn.Conv2d): hooks.append(m.register_forward_hook(conv_hook)) elif isinstance(m, torch.nn.Linear): hooks.append(m.register_forward_hook(linear_hook)) model.eval() with torch.no_grad(): model(torch.randn(input_shape)) for h in hooks: h.remove() return total

真实项目用 fvcore.nn.FlopCountAnalysisptflops,它们正确处理每种模块。

步骤 3:训练后静态量化

def quantise_ptq(model, calibration_loader, backend="x86"): import torch.ao.quantization as tq model = model.eval().cpu() model.qconfig = tq.get_default_qconfig(backend) tq.prepare(model, inplace=True) with torch.no_grad(): for x, _ in calibration_loader: model(x) tq.convert(model, inplace=True) return model

三步:配置、prepare(插观察器)、用真实数据校准、convert(融合+量化)。要求模型已融合(Conv → BN → ReLUConvBnReLU),由 torch.ao.quantization.fuse_modules 处理。

步骤 4:导出 ONNX

def export_onnx(model, sample_input, path="model.onnx"): model = model.eval() torch.onnx.export( model, sample_input, path, input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, opset_version=17, ) return path

opset_version=17 是 2026 年的安全默认。dynamic_axes 让 ONNX 模型能跑任意批大小。

步骤 5:基准对比

import torch.nn as nn from torchvision.models import mobilenet_v3_small def compare_regimes(): model = mobilenet_v3_small(weights=None, num_classes=10) params = parameter_count(model) flops = flops_estimate(model, (1, 3, 224, 224)) lat_fp32 = measure_latency(model, (1, 3, 224, 224), device="cpu") print(f"FP32 MobileNetV3-Small: {params:,} 参数 {flops/1e9:.2f} GFLOPs " f"p50={lat_fp32['p50_ms']:.2f}ms p95={lat_fp32['p95_ms']:.2f}ms")

resnet50efficientnet_v2_sconvnext_tiny 跑同一函数,就拿到部署决策要的那张对比表。

三、框架对比

生产栈收敛到三条路之一:

  • Web / 无服务器:PyTorch → ONNX → ONNX Runtime(CPU 或 CUDA provider)。最简单,多数够用。
  • NVIDIA 边缘(Jetson、GPU 服务器):PyTorch → ONNX → TensorRT。延迟最佳,工程量最大。
  • 移动端:PyTorch → ONNX → Core ML(iOS)或 TFLite(Android)。导出前先量化。

测量上,torch-tb-profilernvprof/nsys、macOS 的 Instruments 给逐层拆解。benchmark_app(OpenVINO)和 trtexec(TensorRT)给独立 CLI 数字。

四、可复用产物

本节产出两个可复用文件(位于原课程 outputs/):

  • prompt-edge-deployment-planner.md:一个提示词——给定目标设备和延迟 SLA,挑主干、量化策略和运行时。
  • skill-latency-profiler.md:一个技能——写出完整的延迟基准脚本,含预热、同步、百分位、内存追踪。

五、练习

  1. (简单) 在 CPU 上测 resnet18mobilenet_v3_smallefficientnet_v2_sconvnext_tiny 在 224×224 的 p50 延迟。报告表格,指出哪个架构每毫秒精度最佳。
  2. (中等)mobilenet_v3_small 做训练后静态量化。报告 FP32 vs INT8 延迟,以及在 CIFAR-10 或类似数据留出子集上的精度损失。
  3. (困难)convnext_tiny 导出 ONNX,用 onnxruntimeCPUExecutionProvider 跑,对比 PyTorch eager 基线的延迟。找出 ONNX Runtime 更快的第一层,解释为什么。

本节要点回顾

  1. 三个预算:延迟(p50/p95/p99)、峰值内存(OOM 致命)、功耗(电池设备每推理毫焦)。
  2. 测量三铁律:预热 5~10 次、GPU 同步、固定生产输入尺寸。
  3. FLOPs 是代理——架构搜索用它,部署决策用设备实测延迟;FLOPs 多的模型可能实测更快(算子硬件友好)。
  4. 量化一段话:FP32→INT8,体积/带宽缩 4 倍、算力 24 倍;PTQ 损失 0.11 个百分点,视觉任务用它够了。
  5. 三种量化:动态(易但慢)、静态 PTQ(够用)、QAT(精度最佳要标签)。
  6. 剪枝 + 蒸馏——剪过参数化模型有效;蒸馏训小学生模仿大教师 logits,恢复缩小模型丢的精度。
  7. 运行时三路:ONNX Runtime(中立起步)、TensorRT(NVIDIA 最佳)、Core ML/TFLite(移动)。
  8. ONNX 是通用语——PyTorch → ONNX → 按目标选运行时。
  9. 边缘架构表:< 300 万 MobileNetV3-Small,3001000 万 EfficientNet-Lite-B0,10002000 万 ConvNeXt-Tiny,再往上 MobileViT/Swin-V2-Tiny,全部 INT8。

下一节是本章毕业项目——把检测、跟踪、分类串成一条端到端实时视觉流水线,综合前面所有技能。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U