PaddlePaddle / Paddle2ONNX

ONNX Model Exporter for PaddlePaddle
Apache License 2.0
670 stars 157 forks source link

【Hackathon 6th No.58】support model convert from fp32 to fp16 #1268

Open xiaoyewww opened 3 weeks ago

xiaoyewww commented 3 weeks ago

Details: support model convert from fp32 to fp16

xiaoyewww commented 3 weeks ago

@Zheng-Bicheng 大佬,具体做的时候有以下几个问题:

  1. 添加对半精度 Paddle 模型的支持,这个具体行为是指将paddle fp16模型转化成onnx fp16的模型吗?还是将paddle fp32模型转化成onnx fp16模型就好,我看这个原生是支持的,只是过程中会将数值截断成fp16,是具体对这个地方修改吗?
  2. 对算子的FP16支持具体是怎么做呢?在每个op里面做一下数值的fp32到fp16的转换?
Zheng-Bicheng commented 3 weeks ago

@xiaoyewww

添加对半精度 Paddle 模型的支持,这个具体行为是指将paddle fp16模型转化成onnx fp16的模型吗?还是将paddle fp32模型转化成onnx fp16模型就好,我看这个原生是支持的,只是过程中会将数值截断成fp16,是具体对这个地方修改吗?

Paddle2ONNX 目前支持 FP16 模型的方式类似把 FP32 格式的 Paddle 模型转换为 FP32 的ONNX模型,然后再把 FP32 的 ONNX 转为 FP16 的 ONNX 模型。这次的任务是直接转换 FP16 格式的 Paddle 模型。

对算子的FP16支持具体是怎么做呢?在每个op里面做一下数值的fp32到fp16的转换?

基本上可以按照以下步骤进行:

Zheng-Bicheng commented 3 weeks ago

FP16的Resnet模型可以通过类似以下方式来实现

import paddle
from paddlenlp.transformers import UIEX # 从模型代码中导入模型
model = UIEX.from_pretrained("uie-x-base") # 实例化模型
model.to(dtype="float16") # 加载预训练模型参数
model.eval() # 将模型设置为评估状态

input_spec = [
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="input_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="token_type_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="position_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="attention_mask"),
paddle.static.InputSpec(shape=[None, None, 4], dtype="int64", name="bbox"),
paddle.static.InputSpec(shape=[None, 3, 224, 224], dtype="float16", name="image"),
] # # 定义输入数据

print("Exporting ONNX model to %s" % "./uiex_fp16.onnx")
paddle.onnx.export(model, "./uiex_fp16", input_spec=input_spec) # ONNX模型导出
print("ONNX model exported.")
xiaoyewww commented 3 weeks ago

FP16的Resnet模型可以通过类似以下方式来实现

import paddle
from paddlenlp.transformers import UIEX # 从模型代码中导入模型
model = UIEX.from_pretrained("uie-x-base") # 实例化模型
model.to(dtype="float16") # 加载预训练模型参数
model.eval() # 将模型设置为评估状态

input_spec = [
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="input_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="token_type_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="position_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="attention_mask"),
paddle.static.InputSpec(shape=[None, None, 4], dtype="int64", name="bbox"),
paddle.static.InputSpec(shape=[None, 3, 224, 224], dtype="float16", name="image"),
] # # 定义输入数据

print("Exporting ONNX model to %s" % "./uiex_fp16.onnx")
paddle.onnx.export(model, "./uiex_fp16", input_spec=input_spec) # ONNX模型导出
print("ONNX model exported.")

这里我看了一下PaddleClas相关的代码,Resnet看上去好像没有from_pretrained这种调用方式,所以我直接通过下载模型及其权重来转换了。

xiaoyewww commented 3 weeks ago

@xiaoyewww

添加对半精度 Paddle 模型的支持,这个具体行为是指将paddle fp16模型转化成onnx fp16的模型吗?还是将paddle fp32模型转化成onnx fp16模型就好,我看这个原生是支持的,只是过程中会将数值截断成fp16,是具体对这个地方修改吗?

Paddle2ONNX 目前支持 FP16 模型的方式类似把 FP32 格式的 Paddle 模型转换为 FP32 的ONNX模型,然后再把 FP32 的 ONNX 转为 FP16 的 ONNX 模型。这次的任务是直接转换 FP16 格式的 Paddle 模型。

对算子的FP16支持具体是怎么做呢?在每个op里面做一下数值的fp32到fp16的转换?

基本上可以按照以下步骤进行:

  • 1 升级基本转换机制:paddle2onnx/parser/parser.cc 需要对 FP16 格式的 Paddle 模型做适配,否则会直接报错退出
  • 2 升级算子:paddle2onnx/mapper/nn/batch_norm.cc 中的算子,需要新增 GetMinOpset 函数,在函数中需要判断当前是否是FP16算子,是的话需要升级 opset,并添加针对性的实现

针对第二点,请问一下具体是什么呢?比如batch_norm算子是对相关float的参数做处理?

另外用paddle原生直接对resnet的模型做半精度推理,是不支持的吗,报了下面的错:

Traceback (most recent call last):
  File "/wuzp/Paddle2ONNX/tests/test_resnet_fp16.py", line 57, in <module>
    paddle_output = model(paddle_input)
  File "/root/miniconda3/envs/paddle_onnx/lib/python3.9/site-packages/paddle/nn/layer/layers.py", line 1429, in __call__
    return self.forward(*inputs, **kwargs)
  File "/root/miniconda3/envs/paddle_onnx/lib/python3.9/site-packages/paddle/jit/translated_layer.py", line 1477, in __i_m_p_l__
    return _run_dygraph(self, input, program_holder)
  File "/root/miniconda3/envs/paddle_onnx/lib/python3.9/site-packages/paddle/jit/translated_layer.py", line 1004, in _run_dygraph
    _legacy_C_ops.run_program(
ValueError: (InvalidArgument) Scale input should be of float type
  [Hint: Expected bn_param_type == framework::TransToProtoVarType( ctx.Input<phi::DenseTensor>("Scale")->dtype()), but received bn_param_type:5 != framework::TransToProtoVarType( ctx.Input<phi::DenseTensor>("Scale")->dtype()):4.] (at ../paddle/fluid/operators/batch_norm_op.cc:198)
  [operator < batch_norm > error]  [operator < run_program > error]
Zheng-Bicheng commented 3 weeks ago

这里我看了一下PaddleClas相关的代码,Resnet看上去好像没有from_pretrained这种调用方式,所以我直接通过下载模型及其权重来转换了。

没问题的

Zheng-Bicheng commented 3 weeks ago

另外用paddle原生直接对resnet的模型做半精度推理,是不支持的吗

要用GPU推理我记得,这个最好是去Paddle的Issues提一下

xiaoyewww commented 3 weeks ago

另外用paddle原生直接对resnet的模型做半精度推理,是不支持的吗

要用GPU推理我记得,这个最好是去Paddle的Issues提一下

我这里已经用的是gpu进行推理,我去issue上问一下

https://github.com/PaddlePaddle/Paddle/issues/64935

Zheng-Bicheng commented 3 weeks ago

比如batch_norm算子是对相关float的参数做处理?

比如 layer_norm.cc#L35 这里会强转FP16为FP32,需要针对性添加支持

xiaoyewww commented 3 weeks ago

比如batch_norm算子是对相关float的参数做处理?

比如 layer_norm.cc#L35 这里会强转FP16为FP32,需要针对性添加支持

resnet中没有layernorm,所以我对matmul修改了一下,麻烦看一下这样修改是否正确,没问题的话我剩下相关的layer都这样处理。

另外像batchnorm这些层该怎么升级op处理呢?这里说实话有点看不懂,这里有相关的dtype constraints,第一行看上去float,float16这些都支持,后面又说限制float,这里具体怎么处理呢?

Zheng-Bicheng commented 2 weeks ago

比如batch_norm算子是对相关float的参数做处理?

比如 layer_norm.cc#L35 这里会强转FP16为FP32,需要针对性添加支持

resnet中没有layernorm,所以我对matmul修改了一下,麻烦看一下这样修改是否正确,没问题的话我剩下相关的layer都这样处理。

另外像batchnorm这些层该怎么升级op处理呢?这里说实话有点看不懂,这里有相关的dtype constraints,第一行看上去float,float16这些都支持,后面又说限制float,这里具体怎么处理呢?

我只是举了个例子,可以按实际情况来处理,例如如果实际没有这个算子就不需要升级

Zheng-Bicheng commented 2 weeks ago

@xiaoyewww 这个有些问题,以你这里适配的matmul为例子:

xiaoyewww commented 2 weeks ago

@Zheng-Bicheng 大佬,我修改layer_norm这个算子的时候遇到点问题,onnx上不是最低只支持opset17吗,怎么p2o上有opset7的支持呢? https://onnx.ai/onnx/operators/onnx__LayerNormalization.html#layernormalization-17

xiaoyewww commented 2 weeks ago

另外,这个误差范围多少合适呢,我目前比较的是fp32下的推理结果:

Mismatched elements: 35 / 1000 (3.5%)
Max absolute difference: 0.00010923
Max relative difference: 0.01125892
 x: array([[3.214033e-05, 2.723171e-04, 3.689834e-04, 3.431249e-04,
        5.028006e-04, 3.669124e-04, 9.550410e-04, 1.896740e-04,
        8.601158e-05, 1.495139e-04, 5.724585e-04, 1.789319e-03,...
 y: array([[3.2008e-05, 2.7180e-04, 3.6883e-04, 3.4189e-04, 5.0068e-04,
        3.6669e-04, 9.5177e-04, 1.8966e-04, 8.5950e-05, 1.4973e-04,
        5.7554e-04, 1.8015e-03, 7.2336e-04, 1.2159e-03, 5.3692e-04,...
xiaoyewww commented 2 weeks ago

@xiaoyewww 这个有些问题,以你这里适配的matmul为例子:

  • 1 先打开 ONNX Matmul OP 查看一下算子的相关信息
  • 2 可以看到Mutmul在 opset为9的情况下是不支持fp16的,因此需要添加matmul对opset13的支持

抱歉才看到,这里我看不是支持的吗,opset13支持的是BF16?

Zheng-Bicheng commented 2 weeks ago

@xiaoyewww 这个有些问题,以你这里适配的matmul为例子:

  • 1 先打开 ONNX Matmul OP 查看一下算子的相关信息
  • 2 可以看到Mutmul在 opset为9的情况下是不支持fp16的,因此需要添加matmul对opset13的支持

抱歉才看到,这里我看不是支持的吗,opset13支持的是BF16?

这里看错了,确实是支持FP16的,没有问题

Zheng-Bicheng commented 2 weeks ago

@Zheng-Bicheng 大佬,我修改layer_norm这个算子的时候遇到点问题,onnx上不是最低只支持opset17吗,怎么p2o上有opset7的支持呢? https://onnx.ai/onnx/operators/onnx__LayerNormalization.html#layernormalization-17

Opset7的支持是把这个算子拆分成很多个其他算子来做的

xiaoyewww commented 1 week ago

@Zheng-Bicheng 修改好了,麻烦再review一下,目前有这么几个问题麻烦确认一下:

  1. 首先这个地方paddle2onnx/converter.cc这样修改有没有问题,我的想法是对inputs有fp16和export_fp16_model=true的情况,都对outputs中dtype==fp32或者fp64的情况进行强转fp16
  2. numpy 这两天更新到了2.0.0,我这边目前onnxruntime==1.18.0不支持2.0.0,所以这里做了一下限制 numpy <= 2.0.0,麻烦看一下有没有必要
  3. 最近新的commit中删掉了一点内容,导致paddle.onnx.export中没有了dygraph2onnx api接口,麻烦看一下这里是否要重新加回来,否则的话要对应修改paddle
Zheng-Bicheng commented 1 week ago

numpy 这两天更新到了2.0.0,我这边目前onnxruntime==1.18.0不支持2.0.0,所以这里做了一下限制 numpy <= 2.0.0,麻烦看一下有没有必要

关于onnx的问题,您能帮忙在 README 中也做对应的修改吗?

xiaoyewww commented 1 week ago

numpy 这两天更新到了2.0.0,我这边目前onnxruntime==1.18.0不支持2.0.0,所以这里做了一下限制 numpy <= 2.0.0,麻烦看一下有没有必要

关于onnx的问题,您能帮忙在 README 中也做对应的修改吗?

已修改

xiaoyewww commented 4 days ago

大佬,数据类型列表这块是不是改成储存支持的数据类型好一点?我看你这漏了不少数据类型啊。

郑师傅,我这里这样理解的:

  1. 首先存不支持的数据比较直观点,可以明显看到哪些不支持的,可以方便后续添加支持
  2. 第二不支持的类别数量我理解在大多数算子上肯定比较少,方便管理
  3. 最重要的是我觉得需要补全测例吧,如果测例类型存在,就可以明确哪些数据类型明确不支持的,因为存在onnx不支持和p2o的情况,onnx不支持我们需要看文档,但是我们p2o不支持只能测试才能发现了,所以我感觉直接写个不支持的列表先默认不在列表内的支持,能让转换模型中的cast应少尽少

比如对pool这个算子,因为没有int8的测例,但又因为这是onnx支持的,所以我第一反应是这个算子是不需要cast的,所以没放在列表里

xiaoyewww commented 3 days ago

@Zheng-Bicheng 已按要求修改,麻烦再review一下