在这里插入图片描述

一、写在前面

用int8模型推理,模型大小是fp32的1/4,推理速度必然会加快,但精度会受影响,平均可能掉1-2个点,大家酌情使用。

二、int8推理的两种方式

2.1 模型转int8修改

那么,模型调为int8推理 需要做哪些修改?

将模型调整为 int8 推理 可以显著提高推理速度并降低内存占用,但需要做一些特定的修改和配置。以下是实现模型 int8 推理 的步骤和所需修改:


1. 检查硬件支持

首先确保你的硬件支持 int8 推理。许多现代 GPU 和 CPU 都支持 INT8 加速,例如:

  • NVIDIA GPU:Ampere 架构及更新的 Tensor Cores 支持 INT8 加速。
  • Intel CPU:支持 INT8 推理(通过 Intel MKL 或 oneDNN)。
  • TPU:支持 INT8 运算。

如果你的硬件支持 INT8,便可以利用相关库(如 NVIDIA TensorRT、ONNX Runtime、PyTorch Quantization 等)优化推理。


2. 选择量化策略

在进行 INT8 推理时,量化策略是关键:

  • 动态量化(Dynamic Quantization)
    • 仅在推理时将权重量化为 INT8,激活值在运行时动态量化。
    • 适用于 RNN/LSTM 等。
  • 静态量化(Static Quantization)
    • 权重和激活值都预先量化,需在量化前收集校准数据。
    • 适用于 CNN/Transformer 等。
  • 混合量化(Quantization Aware Training, QAT)
    • 在训练时模拟量化误差,性能更高但复杂度更大。

3. 使用框架支持的量化工具

根据你的深度学习框架,选择合适的工具来实现 INT8 量化。

PyTorch

PyTorch 提供了多种量化工具,如 torch.quantizationtorch.ao.quantization。以下是常用方法:

  • 动态量化
    动态量化适合模型较大的情况,例如 Transformer。

    import torch
    from transformers import AutoModel
    
    # 加载预训练模型
    model = AutoModel.from_pretrained("bert-base-uncased")
    model.eval()
    
    # 应用动态量化
    quantized_model = torch.quantization.quantize_dynamic(
        model,  # 模型
        {torch.nn.Linear},  # 要量化的层
        dtype=torch.qint8  # 量化类型
    )
    
  • 静态量化
    静态量化需要先进行校准:

    import torch
    from torchvision.models import resnet18
    
    model = resnet18(pretrained=True).eval()
    
    # 准备量化模型
    model.qconfig = torch.quantization.get_default_qconfig('fbgemm')  # CPU 配置
    torch.quantization.prepare(model, inplace=True)
    
    # 校准模型(使用真实数据)
    for images, _ in dataloader:
        model(images)
    
    # 转换为量化模型
    torch.quantization.convert(model, inplace=True)
    
  • QAT
    需要在训练时进行量化感知训练:

    import torch
    model = torch.quantization.prepare_qat(model)
    # 继续训练模型
    torch.quantization.convert(model)
    

TensorRT

TensorRT 是 NVIDIA 提供的高性能推理引擎,支持 INT8 推理:

  1. 导出模型到 ONNX 格式
  2. 使用 TensorRT 的校准工具生成校准表
  3. 加载量化后的 TensorRT 引擎

TensorRT INT8 校准示例:

import tensorrt as trt

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network()

# 加载 ONNX 模型
parser = trt.OnnxParser(network, TRT_LOGGER)
with open("model.onnx", "rb") as model_file:
    parser.parse(model_file.read())

# 配置量化
config = builder.create_builder_config()
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = trt.Int8Calibrator()  # 校准器

engine = builder.build_engine(network, config)

ONNX Runtime

ONNX Runtime 支持 INT8 量化并能方便地将模型量化:

  • 使用 onnxruntime.quantization.quantize_dynamic

    from onnxruntime.quantization import quantize_dynamic
    
    quantize_dynamic(
        model_input="model.onnx",  # 原始模型路径
        model_output="model_int8.onnx",  # 量化后模型路径
        weight_type=QuantType.QInt8  # 使用 INT8 量化
    )
    
  • 静态量化:
    利用 onnxruntime.quantization.quantize_static 进行校准并量化。


4. 处理自定义算子

如果模型包含自定义算子或不支持的操作,你需要:

  1. 替换或重写算子,使其支持 INT8 计算。
  2. 使用模拟量化的方式,避免量化对精度的过度影响。

5. 校准和评估量化模型

  • 校准是静态量化必不可少的步骤,通过真实数据确定激活值的分布范围。
  • 在量化后,运行验证数据集,检查精度损失是否在可接受范围内。

6. 常见优化点

  1. 层选择
    • 只对计算量大的层进行量化,例如 LinearConv 层。
  2. 分组量化
    • 对部分敏感层使用更高精度(如 FP16)保留精度。
  3. 后处理
    • 量化后的模型可能需要对输出进行解量化。

7. 验证和调试

  • 验证量化模型的性能提升(推理速度、内存占用)。
  • 检查量化后的精度变化,并适当调整量化参数。

通过以上步骤,你可以将模型调整为 INT8 推理,同时最大限度地保留原始模型的精度。

2.2 直接int8推理

不转换模型 直接推理fp32的onnx模型 用int8的精度

如果你不对模型进行转换,而是希望在推理时直接使用 FP32 的 ONNX 模型,但依然希望利用 INT8 精度进行推理,那么实际做法是利用 ONNX Runtime(或其他推理框架)支持的 量化推理 功能。

简而言之,你仍然需要使用 INT8 精度 来加速推理,但你不希望修改模型文件(不转换成 INT8 模型)。你希望的是,在推理时直接将 FP32 模型以 INT8 精度运行

要在 CUDAExecutionProvider 中启用 INT8 推理,你需要进行以下几个步骤:

  1. 安装支持 INT8 推理的 ONNX Runtime 版本:

    • 确保你安装的是支持 INT8 推理的 ONNX Runtime 版本,通常需要安装 GPU 版本,并确保它启用了 CUDA 支持。
    • 为了使用 INT8 推理,你的 ONNX Runtime 需要支持 TensorRT,因为 TensorRT 是 NVIDIA 提供的一个高性能推理库,它支持 INT8 推理加速。
  2. 安装 CUDA 及 TensorRT:

    • 首先,确保你已经安装了 CUDATensorRT。它们是必需的,因为 ONNX Runtime 使用 TensorRT 来支持 INT8 推理。
    • 可以通过以下命令安装 ONNX Runtime 与 CUDATensorRT 支持:
    pip install onnxruntime-gpu
    
    • 确保你的机器上安装了合适版本的 CUDATensorRT。例如:
      • CUDA: 11.2 或更高版本
      • TensorRT: 8.x 或更高版本
  3. 准备好你的 FP32 模型:

    • 你需要一个 FP32 模型来进行量化推理。如果你的模型是 FP32,那么你可以直接将它加载到 ONNX Runtime 中并进行 INT8 推理
  4. 进行 INT8 推理的配置:

    • ONNX Runtime 在 CUDA 上默认使用 FP32 来进行推理。如果你希望启用 INT8 精度,你需要使用 TensorRTExecutionProvider,并配置它使用 INT8 推理

    具体步骤如下:

步骤 1: 配置 TensorRTExecutionProvider 使用 INT8 精度

你可以通过 ONNX RuntimeExecutionProvider 来设置 TensorRT,并启用 INT8 精度:

import onnxruntime as ort

# 加载模型
model_path = "your_model.onnx"

# 配置 ONNX Runtime 使用 TensorRT,并启用 INT8 精度
providers = ['CUDAExecutionProvider', 'TensorrtExecutionProvider']

# 选择 GPU 加速,启用 INT8 精度
# TensorRT 通过优化模型来使用 INT8 推理
# 通过设置 session_options 来启用 INT8
session_options = ort.SessionOptions()
session_options.append_execution_provider_tensorrt(provider_options={'int8': True})

# 创建 ONNX Runtime 会话
session = ort.InferenceSession(model_path, providers=providers, session_options=session_options)

# 检查是否使用 TensorRT 进行推理
print(session.get_providers())  # 查看当前使用的推理后端

步骤 2: TensorRT 的 INT8 精度配置

通过 SessionOptions 设置 append_execution_provider_tensorrt 来配置 TensorRT 使用 INT8 精度。在推理时,它会自动将模型的权重量化为 INT8 来加速推理。

步骤 3: 运行推理

import numpy as np

# 假设我们有一些输入数据
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)

# 执行推理
inputs = {session.get_inputs()[0].name: input_data}
outputs = session.run(None, inputs)

print(outputs)

步骤 4: 确保你的硬件支持 INT8 推理

INT8 推理通常需要 NVIDIA Volta 或更高版本的 GPU(如 T4, V100, A100),并且你的环境中必须有 TensorRT 支持。在 CUDA 和 TensorRT 支持下,ONNX Runtime 才能真正执行 INT8 加速推理。

需要的硬件要求:

  1. NVIDIA GPU:支持 INT8 推理的 GPU,通常需要 Volta 架构或更高版本(如 T4, A100 等)。
  2. TensorRT 支持:需要安装 TensorRT(版本通常为 7.x 或更高)来支持 INT8 加速。

步骤 5: 调整和验证精度

使用 INT8 精度进行推理时,你可能会看到推理速度明显提升,但也可能会面临一些 精度损失。你可以通过与 FP32 模型的输出进行比较来验证精度变化。

  • INT8 优化:TensorRT 会尝试在不显著影响精度的情况下,优化模型以提高推理速度。
  • 精度验证:在使用 INT8 推理时,通常会先进行量化校准,确保精度损失最小。

总结:

  • 要使用 INT8 推理,你需要启用 TensorRTExecutionProvider,并通过 session_options.append_execution_provider_tensorrt() 启用 INT8 精度。
  • 需要确保你的 GPU 支持 INT8 推理(如 T4, V100, A100 等)。
  • 在推理时,ONNX Runtime 会使用 TensorRT 来进行量化推理,并在推理时自动将模型转换为 INT8 精度

其他注意事项:

  • TensorRT 会自动执行量化和优化,所以你不需要手动将模型转换为 INT8。
  • 你可以通过对比 FP32INT8 的推理结果来验证精度是否符合要求。如果精度损失较大,可以考虑进一步调整量化配置。

三、如何查看模型的数据位数

onnx是fp16还是 fp32 还是 int8的?

要判断一个 ONNX 模型是使用 FP16、FP32 还是 INT8,可以通过以下几种方法来查看模型的权重和计算数据类型。


1. 检查模型权重数据类型

权重的数据类型直接决定模型的精度,可以通过解析 ONNX 模型文件中的权重信息进行检查:

代码示例:使用 ONNX 的 onnx 模块
import onnx
import numpy as np

# 加载 ONNX 模型
model_path = "model.onnx"
model = onnx.load(model_path)

# 遍历模型中的权重
for tensor in model.graph.initializer:
    weight_dtype = np.dtype(onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[tensor.data_type])
    print(f"Tensor Name: {tensor.name}, Data Type: {weight_dtype}")

输出示例:

  • 如果是 FP32:Data Type: float32
  • 如果是 FP16:Data Type: float16
  • 如果是 INT8:Data Type: int8

2. 检查节点的计算精度

某些节点可能在计算时使用了不同的精度(如 FP16 或 INT8),可以通过遍历 ONNX 模型中的节点属性查看。

代码示例:检查节点的类型和精度
# 遍历模型中的节点
for node in model.graph.node:
    print(f"Node Name: {node.name}, Op Type: {node.op_type}")
    for attr in node.attribute:
        print(f"  Attribute: {attr.name}, Value: {attr}")
  • 对于 FP16 节点,可能会有标记为 float16 的计算类型。
  • 对于量化模型(如 INT8),会有 QuantizeLinearDequantizeLinear 节点。

3. 使用 onnxruntime 检查精度

在运行时,可以检查模型权重和计算的设备支持以及精度类型。

代码示例:获取权重的详细信息
import onnxruntime as ort

# 加载 ONNX 模型
session = ort.InferenceSession("model.onnx")

# 获取模型输入和输出的详细信息
for input in session.get_inputs():
    print(f"Input Name: {input.name}, Type: {input.type}")
for output in session.get_outputs():
    print(f"Output Name: {output.name}, Type: {output.type}")
  • FP16 会显示为 tensor(float16)
  • FP32 会显示为 tensor(float)
  • INT8 会显示为 tensor(int8)

4. 使用 ONNX 可视化工具

使用图形化工具可以更直观地分析模型的数据类型:

  • Netron:加载 ONNX 模型文件,权重和节点的精度会在 UI 中显示。

5. 检查量化模型的特征

量化模型通常具有以下特征:

  • 节点中包含 QuantizeLinearDequantizeLinear
  • 权重的类型通常为 INT8。
  • 使用的算子如 QLinearMatMulQLinearConv,表示量化运算。

6. 手动对比模型的性能

通过实际推理速度和精度的对比,也可以间接推断模型的精度:

  • FP16 通常较 FP32 更快,显存占用减少约 50%,但精度可能略有下降。
  • INT8 推理速度比 FP16 快,但可能对精度有显著影响,特别是在某些任务上。

通过这些方法,可以明确 ONNX 模型是使用 FP32、FP16 还是 INT8,选择最适合的推理方案。

Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐