Pytorch导出FP16 ONNX模型

一般Pytorch导出ONNX时默认都是用的FP32,但有时需要导出FP16的ONNX模型,这样在部署时能够方便的将计算以及IO改成FP16,并且ONNX文件体积也会更小。想导出FP16的ONNX模型也比较简单,一般情况下只需要在导出FP32 ONNX的基础上调用下model.half()将模型相关权重转为FP16,然后输入的Tensor也改成FP16即可,具体操作可参考如下示例代码。这里需要注意下,当前Pytorch要导出FP16的ONNX必须将模型以及输入Tensor的device设置成GPU,否则会报很多算子不支持FP16计算的提示。

import torch
from torchvision.models import resnet50


def main():
    export_fp16 = True
    export_onnx_path = f"resnet50_fp{16 if export_fp16 else 32}.onnx"
    device = torch.device("cuda:0")

    model = resnet50()
    model.eval()
    model.to(device)
    if export_fp16:
        model.half()

    with torch.inference_mode():
        dtype = torch.float16 if export_fp16 else torch.float32
        x = torch.randn(size=(1, 3, 224, 224), dtype=dtype, device=device)
        torch.onnx.export(model=model,
                          args=(x,),
                          f=export_onnx_path,
                          input_names=["image"],
                          output_names=["output"],
                          dynamic_axes={"image": {2: "width", 3: "height"}},
                          opset_version=17)


if __name__ == '__main__':
    main()

通过Netron可视化工具可以看到导出的FP16 ONNX的输入/输出的tensor类型都是float16
在这里插入图片描述

并且通过对比可以看到,FP16的ONNX模型比FP32的文件更小(48.6MB vs 97.3MB)。
在这里插入图片描述
大多数情况可以按照上述操作进行正常转换,但也有一些比较头大的场景,因为你永远无法知道拿到的模型会有多奇葩,例如下面示例:
错误导出FP16 ONNX示例

import torch
import torch.nn as nn
import torch.nn.functional as F


class MyModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.conv = nn.Conv2d(3, 1, kernel_size=3, stride=2, padding=1)

    def forward(self, x):
        x = self.conv(x)

        kernel = torch.tensor([[0.1, 0.1, 0.1],
                               [0.1, 0.1, 0.1],
                               [0.1, 0.1, 0.1]], dtype=torch.float32, device=x.device).reshape([1, 1, 3, 3])
        x = F.conv2d(x, weight=kernel, bias=None, stride=1)

        return x


def main():
    export_fp16 = True
    export_onnx_path = f"my_model_fp{16 if export_fp16 else 32}.onnx"
    device = torch.device("cuda:0")

    model = MyModel()
    model.eval()
    model.to(device)
    if export_fp16:
        model.half()

    with torch.inference_mode():
        dtype = torch.float16 if export_fp16 else torch.float32
        x = torch.randn(size=(1, 3, 224, 224), dtype=dtype, device=device)
        model(x)
        torch.onnx.export(model=model,
                          args=(x,),
                          f=export_onnx_path,
                          input_names=["image"],
                          output_names=["output"],
                          dynamic_axes={"image": {2: "width", 3: "height"}},
                          opset_version=17)


if __name__ == '__main__':
    main()

执行以上代码后会报如下错误信息:

/src/ATen/native/cudnn/Conv_v8.cpp:80.)
  return F.conv2d(input, weight, bias, self.stride,
Traceback (most recent call last):
  File "/home/wz/my_projects/py_projects/export_fp16/example.py", line 47, in <module>
    main()
  File "/home/wz/my_projects/py_projects/export_fp16/example.py", line 36, in main
    model(x)
  File "/home/wz/miniconda3/envs/torch2.0.1/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1501, in _call_impl
    return forward_call(*args, **kwargs)
  File "/home/wz/my_projects/py_projects/export_fp16/example.py", line 17, in forward
    x = F.conv2d(x, weight=kernel, bias=None, stride=1)
  RuntimeError: Input type (torch.cuda.HalfTensor) and weight type (torch.cuda.FloatTensor) should be the same

简单来说就是在推理过程中遇到两种不同类型的数据要计算,torch.cuda.HalfTensor(FP16) 和torch.cuda.FloatTensor(FP32)。遇到这种情况一般常见有两种解法:

  • 一种是找到数据类型与我们预期不一致的地方,然后改成我们要想的dtype,例如上面示例是将kernel的dtype写死成了torch.float32,我们可以改成torch.float16或者写成x.dtype(这种会比较通用,会根据输入的Tensor类型自动切换)。这种方法有个弊端,如果代码里写死dtype的位置很多,改起来会比较头大。
  • 另一种是使用torch.autocast上下文管理器,该上下文管理器能够实现推理过程中自动进行混合精度计算,例如遇到能进行float16/bfloat16计算的场景会自动切换。具体使用方法可以查看官方文档。下面示例代码就是用torch.autocast上下文管理器来做自动转换。
import torch
import torch.nn as nn
import torch.nn.functional as F


class MyModel(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.conv = nn.Conv2d(3, 1, kernel_size=3, stride=2, padding=1)

    def forward(self, x):
        x = self.conv(x)

        kernel = torch.tensor([[0.1, 0.1, 0.1],
                               [0.1, 0.1, 0.1],
                               [0.1, 0.1, 0.1]], dtype=torch.float32, device=x.device).reshape([1, 1, 3, 3])
        x = F.conv2d(x, weight=kernel, bias=None, stride=1)

        return x


def main():
    export_fp16 = True
    export_onnx_path = f"my_model_fp{16 if export_fp16 else 32}.onnx"
    device = torch.device("cuda:0")

    model = MyModel()
    model.eval()
    model.to(device)
    if export_fp16:
        model.half()

    with torch.autocast(device_type="cuda", dtype=torch.float16):
        with torch.inference_mode():
            dtype = torch.float16 if export_fp16 else torch.float32
            x = torch.randn(size=(1, 3, 224, 224), dtype=dtype, device=device)
            model(x)
            torch.onnx.export(model=model,
                              args=(x,),
                              f=export_onnx_path,
                              input_names=["image"],
                              output_names=["output"],
                              dynamic_axes={"image": {2: "width", 3: "height"}},
                              opset_version=17)


if __name__ == '__main__':
    main()

使用上述代码能够正常导出ONNX模型,并且使用Netron可视化后可以看到导出的FP16 ONNX模型是符合预期的。
在这里插入图片描述

### YOLOv5 模型导出FP16 精度 ONNX 格式的教程 要将训练好的 YOLOv5 模型导出FP16 精度的 ONNX 格式,可以按照以下方法操作: #### 安装必要的依赖库 为了支持 FP16 的量化以及 ONNX 转换功能,需要先安装相关的 Python 库。可以通过运行以下命令完成环境准备: ```bash pip install onnx onnxconverter-common ``` 此步骤确保环境中具备了 ONNX 和其扩展工具的支持[^2]。 #### 修改 `export.py` 文件 YOLOv5 提供了一个脚本文件用于模型导出,即 `export.py`。如果希望将其转换为 FP16 精度,则需调整该脚本中的参数配置部分。具体来说,在调用 PyTorch 的 `.to(torch.float16)` 或者通过 ONNX 自带的功能启用半精度模式时,应设置对应的选项。 以下是修改后的关键代码片段示例: ```python import torch.onnx as onnx_utils from models.common import Detect, AutoShape def main(): model = attempt_load(weights, map_location=device) # 加载权重到指定设备上 model.eval() # 切换至评估模式 # 启用FP16支持 (仅当硬件兼容时有效) half_precision = True # 设置标志位开启半精度计算 if half_precision and device.type != 'cpu': # 如果GPU可用则切换至float16 model.half() dummy_input = torch.randn(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters())) output_file = opt.output or Path(opt.weights).with_suffix('.onnx') dynamic_axes = {'input' : {0:'batch_size', 2:'height', 3:'width'}, 'output' : {0:'batch_size'}} with torch.no_grad(): # 阻断梯度传播提升效率 onnx_utils.export( model, dummy_input, str(output_file), opset_version=opt.opset, do_constant_folding=True, input_names=['images'], output_names=['outputs'], dynamic_axes=dynamic_axes) if __name__ == "__main__": parser.add_argument('--half', action='store_true') # 添加新的CLI参数控制是否采用FP16 ... args = parser.parse_args() main(args) ``` 上述代码展示了如何在原有基础上增加对 FP16 支持的能力,并允许用户通过命令行参数灵活决定是否应用浮点数压缩技术[^1]^。 #### 执行导出过程 准备好一切之后,就可以执行实际的模型转储工作啦!只需打开终端并输入类似下面这样的指令即可顺利完成整个流程: ```bash python export.py --weights yolov5s.pt --img-size 640 --include onnx --half ``` 这里指定了目标预训练权值路径(`--weights`)、图像尺寸大小(`--img-size`)还有期望获得的结果形式(`--include`)再加上刚才新增加的那个布尔开关变量(`--half`)用来表明我们确实想要得到经过优化处理过的版本^。 完成后会在当前目录下生成一个新的`.onnx`结尾命名规则遵循原版名称加上后缀的形式保存下来的轻量级推理引擎专用格式文档副本^。 --- ###
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳花的小绿豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值