Optimum:onnx模型量化
量化
量化是一种通过使用低精度数据类型(如8位整数 int8)代替通常的32位浮点数(float32)来表示权重和激活值,从而降低运行推理的计算和内存成本的技术。
减少比特数意味着最终的模型需要更少的内存存储,消耗更少的能源(理论上),并且像矩阵乘法这样的操作可以用整数算术更快地执行。它还允许在嵌入式设备上运行模型,这些设备有时只支持整数数据类型。
校准
float32 值的 [a, b] 范围是如何确定的?这就是校准发挥作用的地方。
校准是量化过程中计算 float32 范围的步骤。对于权重来说,这很简单,因为实际范围在量化时是已知的。但对于激活值来说,情况就不那么明朗了,存在不同的方法:
- 
训练后动态量化:每个激活值的范围在运行时动态计算。虽然这种方法无需太多工作就能获得很好的结果,但由于每次计算范围会引入开销,因此可能比静态量化稍慢。在某些硬件上,它也不是一个可行的选项。 
- 
训练后静态量化:每个激活值的范围在量化时预先计算好,通常是通过将代表性数据通过模型并记录激活值来实现。实践中的步骤是: 
 在激活值上放置观察器以记录其值。
 在校准数据集上进行一定数量的前向传播(大约 200 个样本就足够了)。
 根据某种校准技术计算每个计算的范围。
- 
量化感知训练:每个激活值的范围在训练时计算,其思想与训练后静态量化相同。但使用“伪量化”算子代替观察器:它们像观察器一样记录值,但同时也模拟量化引起的误差,让模型适应它。 
 对于训练后静态量化和量化感知训练,都需要定义校准技术,最常见的有:
- 
最小值-最大值:计算的范围是 [观察到的最小值, 观察到的最大值],这对于权重效果很好。 
 移动平均最小值-最大值:计算的范围是 [移动平均观察到的最小值, 移动平均观察到的最大值],这对于激活值效果很好。
- 
直方图:记录值的直方图以及最小值和最大值,然后根据某个标准进行选择。 - 熵:计算的范围是使全精度数据和量化数据之间误差最小的范围。
- 均方误差:计算的范围是使全精度数据和量化数据之间均方误差最小的范围。
- 百分位数:使用给定的百分位值 p 对观察值计算范围。其思想是尝试使 p% 的观察值在计算的范围内。虽然在进行仿射量化时这是可能的,但在进行对称量化时并不总能精确匹配。
 
将模型量化到 int8 的实际步骤
为了有效地将模型量化到 int8,需要遵循以下步骤:
- 选择要量化的算子。适合量化的算子是那些在计算时间上占主导地位的,例如线性投影和矩阵乘法。
- 尝试训练后动态量化,如果速度足够快,则到此为止,否则继续第3步。
- 尝试训练后静态量化,这可能比动态量化更快,但通常会降低精度。在您想要量化的地方为模型应用观察器。
- 选择一种校准技术并执行。
- 将模型转换为其量化形式:移除观察器,并将 float32 算子转换为其 int8 对应物。
- 评估量化后的模型:精度是否足够好?如果是,则到此为止,否则从第3步重新开始,但这次使用量化感知训练。
在 🤗 Optimum 中执行量化支持的工具
🤗 Optimum 提供了 API,可以使用不同的工具针对不同的目标执行量化:
optimum.onnxruntime 包允许使用 ONNX Runtime 工具量化和运行 ONNX 模型。
optimum.intel 包能够量化 🤗 Transformers 模型,同时满足精度和延迟约束。
optimum.fx 包提供了对 PyTorch 量化函数 的封装,以允许在 PyTorch 中对 🤗 Transformers 模型进行图模式量化。与上述两种相比,这是一个更低级的 API,提供了更大的灵活性,但需要您做更多的工作。
optimum.gptq 包允许使用 GPTQ 量化和运行 LLM 模型。
(官方)测试用例复测
- 模型转onnx
from optimum.onnxruntime import ORTModelForSequenceClassification
# from transformers import AutoTokenizer
from modelscope import AutoTokenizermodel_checkpoint = "./distilbert-base-uncased-finetuned-sst-2-english"
save_directory = "tmp/onnx/"
# Load a model from transformers and export it to ONNX
ort_model = ORTModelForSequenceClassification.from_pretrained(model_checkpoint, export=True, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, trust_remote_code=True)
# Save the onnx model and tokenizer
ort_model.save_pretrained(save_directory)
tokenizer.save_pretrained(save_directory)
- 动态量化
from optimum.onnxruntime.configuration import AutoQuantizationConfig
from optimum.onnxruntime import ORTQuantizer
# Define the quantization methodology
ort_model = "tmp/onnx"
save_directory = "tmp/onnx-dyna/"
qconfig = AutoQuantizationConfig.arm64(is_static=False, per_channel=False)
quantizer = ORTQuantizer.from_pretrained(ort_model, file_name="model.onnx")
# Apply dynamic quantization on the model
quantizer.quantize(save_dir=save_directory, quantization_config=qconfig)from optimum.onnxruntime import ORTModelForSequenceClassification
from transformers import pipeline, AutoTokenizer
model = ORTModelForSequenceClassification.from_pretrained(save_directory, file_name="model_quantized.onnx")
tokenizer = AutoTokenizer.from_pretrained(save_directory)
cls_pipeline = pipeline("text-classification", model=model, tokenizer=tokenizer)
results = cls_pipeline("I love burritos!")
print(results)
- 静态量化
from optimum.onnxruntime import ORTModelForSequenceClassification
# from transformers import AutoTokenizer
from modelscope import AutoTokenizermodel_checkpoint = "./distilbert-base-uncased-finetuned-sst-2-english"
ort_model = "tmp/onnx/"
save_directory = "tmp/onnx-static/"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, trust_remote_code=True)from optimum.onnxruntime.configuration import AutoQuantizationConfig
from optimum.onnxruntime import ORTQuantizer
# Define the quantization methodology
quantizer = ORTQuantizer.from_pretrained(ort_model, file_name="model.onnx")
qconfig = AutoQuantizationConfig.arm64(is_static=True, per_channel=False)from functools import partial
from optimum.onnxruntime.configuration import AutoCalibrationConfig
# Define the processing function to apply to each example after loading the dataset
def preprocess_fn(ex, tokenizer):return tokenizer(ex["sentence"])# Create the calibration dataset
calibration_dataset = quantizer.get_calibration_dataset("./glue/sst2",# dataset_config_name="sst2",preprocess_function=partial(preprocess_fn, tokenizer=tokenizer),num_samples=50,dataset_split="train",
)
# Create the calibration configuration containing the parameters related to calibration.
calibration_config = AutoCalibrationConfig.minmax(calibration_dataset)
# Perform the calibration step: computes the activations quantization ranges
ranges = quantizer.fit(dataset=calibration_dataset,calibration_config=calibration_config,operators_to_quantize=qconfig.operators_to_quantize,
)
# Apply static quantization on the model
quantizer.quantize(save_dir=save_directory,calibration_tensors_range=ranges,quantization_config=qconfig,
)from optimum.onnxruntime import ORTModelForSequenceClassification
from transformers import pipeline, AutoTokenizer
model = ORTModelForSequenceClassification.from_pretrained(save_directory, file_name="model_quantized.onnx")
tokenizer = AutoTokenizer.from_pretrained(save_directory)
cls_pipeline = pipeline("text-classification", model=model, tokenizer=tokenizer)
results = cls_pipeline("I love burritos!")
print(results)
