在至强(Xeon)平台上借助 🤗 Optimum Intel 实现 SetFit 的超快推理
如何处理训练中标注数据不足的问题是建模中常见的一大挑战,而 SetFit 是一个很有前景的解决方案。SetFit 由 Hugging Face 的研究合作伙伴 英特尔实验室 和 UKP 实验室 共同开发,是一个用于 Sentence Transformers 模型的小样本微调的高效框架。
SetFit 只需少量标注数据就能实现高准确率——例如,在 Banking 77 金融意图数据集上,SetFit 的 3-shot 性能 优于 GPT-3.5 的 3-shot prompt,而 5-shot 性能也优于 GPT-4 的 3-shot prompt。
与基于大语言模型(LLM)的方法相比,SetFit 有两个独特的优势
🗣 无需提示词(prompt)或言语化(verbaliser):基于大语言模型的小样本上下文学习需要手工制作提示词,这使得结果不稳定,对措辞敏感,且依赖于用户的专业知识。SetFit 通过直接从少量带标签的文本示例中生成丰富的嵌入向量,完全省去了提示词。
🏎 训练速度快:SetFit 不依赖于 GPT-3.5 或 Llama2 等大语言模型来实现高准确率。因此,其训练和推理速度通常快一个数量级(或更多)。
有关 SetFit 的更多详细信息,请查看我们的论文、博客、代码和数据。
Setfit 已被 AI 开发者社区广泛采用,每月下载量约 10 万次,Hub 上有约 1500 个 SetFit 模型,并且以平均每天约 4 个模型的速度增长!
更快!
在这篇博文中,我们将解释如何通过使用 🤗 Optimum Intel 优化你的 SetFit 模型,在英特尔 CPU 上将 SetFit 的推理速度提升 7.8 倍。我们将展示如何通过对模型进行简单的训练后量化步骤来实现巨大的吞吐量提升。这使得在英特尔至强(Xeon)CPU 上部署生产级的 SetFit 解决方案成为可能。
Optimum Intel 是一个开源库,可在英特尔硬件上加速使用 Hugging Face 库构建的端到端流水线。Optimum Intel 包含多种加速模型的技术,如低比特量化、模型权重剪枝、蒸馏和加速运行时。
Optimum Intel 中包含的运行时和优化利用了英特尔 CPU 上的英特尔® 高级矢量扩展 512(Intel® AVX-512)、矢量神经网络指令(VNNI)和英特尔® 高级矩阵扩展(Intel® AMX)来加速模型。具体来说,它在每个核心中都内置了 BFloat16 (bf16) 和 int8 GEMM 加速器,以加速深度学习训练和推理工作负载。AMX 加速推理在 PyTorch 2.0 和 Intel Extension for PyTorch (IPEX) 中引入,此外还为各种常见算子提供了其他优化。
使用 Optimum Intel 可以轻松优化预训练模型;许多简单的示例可以在这里找到。我们的博客附带了一个notebook,提供了逐步的演练。
第一步:使用 🤗 Optimum Intel 量化 SetFit 模型
为了优化我们的 SetFit 模型,我们将使用 Optimum Intel 的一部分——英特尔神经压缩器 (Intel Neural Compressor, INC),对模型主体进行量化。
量化是一种非常流行的深度学习模型优化技术,用于提高推理速度。它通过将一组高精度数值转换为低比特数据表示(例如 INT8),来最小化表示神经网络中权重和/或激活值所需的比特数。此外,量化可以在较低精度下实现更快的计算。
具体来说,我们将应用训练后静态量化(Post-Training Static Quantization, PTQ)。PTQ 可以在仅使用一个小的未标记校准集且无需任何训练的情况下,减少内存占用和推理延迟,同时保持模型的准确性。在开始之前,请确保你已安装所有必要的库,并且你的 Optimum Intel 版本至少是 1.14.0
,因为该功能是在此版本中引入的。
pip install --upgrade-strategy eager optimum[ipex]
准备校准数据集
校准数据集应该能够代表未见数据的分布。一般来说,准备 100 个样本就足以进行校准。在我们的案例中,我们将使用 rotten_tomatoes
数据集,因为它由电影评论组成,与我们的目标数据集 sst2
类似。
首先,我们将从该数据集中加载 100 个随机样本。然后,为了准备用于量化的数据集,我们需要对每个示例进行分词。我们不需要 “text” 和 “label” 列,所以将它们移除。
calibration_set = load_dataset("rotten_tomatoes", split="train").shuffle(seed=42).select(range(100))
def tokenize(examples):
return tokenizer(examples["text"], padding="max_length", max_length=512, truncation=True)
tokenizer = setfit_model.model_body.tokenizer
calibration_set = calibration_set.map(tokenize, remove_columns=["text", "label"])
运行量化
在运行量化之前,我们需要定义所需的量化过程——在我们的案例中是静态训练后量化(Static Post Training Quantization),并使用 optimum.intel
在我们的校准数据集上运行量化。
from optimum.intel import INCQuantizer
from neural_compressor.config import PostTrainingQuantConfig
setfit_body = setfit_model.model_body[0].auto_model
quantizer = INCQuantizer.from_pretrained(setfit_body)
optimum_model_path = "/tmp/bge-small-en-v1.5_setfit-sst2-english_opt"
quantization_config = PostTrainingQuantConfig(approach="static", backend="ipex", domain="nlp")
quantizer.quantize(
quantization_config=quantization_config,
calibration_dataset=calibration_set,
save_directory=optimum_model_path,
batch_size=1,
)
tokenizer.save_pretrained(optimum_model_path)
就是这样!我们现在有了一个本地的量化 SetFit 模型副本。让我们来测试一下吧。
第二步:推理性能基准测试
在我们的 notebook 中,我们设置了一个 PerformanceBenchmark
类来计算模型的延迟和吞吐量,以及准确率指标。让我们用它来对我们的 Optimum Intel 模型与其他两种常用方法进行基准测试。
- 使用 PyTorch 和 🤗 Transformers 库(fp32)。
- 使用 Intel Extension for PyTorch (IPEX) 运行时(bf16),并使用 TorchScript 跟踪模型。
加载我们的测试数据集 sst2
,并使用 PyTorch 和 🤗 Transformers 库运行基准测试。
from datasets import load_dataset
from setfit import SetFitModel
test_dataset = load_dataset("SetFit/sst2")["validation"]
model_path = "dkorat/bge-small-en-v1.5_setfit-sst2-english"
setfit_model = SetFitModel.from_pretrained(model_path)
pb = PerformanceBenchmark(
model=setfit_model,
dataset=test_dataset,
optim_type="bge-small (transformers)",
)
perf_metrics = pb.run_benchmark()
对于第二个基准测试,我们将使用 Intel Extension for PyTorch (IPEX),采用 bf16 精度和 TorchScript 跟踪。要使用 IPEX,我们只需导入 IPEX 库,并对目标模型应用 ipex.optimize()
,在我们的案例中,目标模型是 SetFit 的(transformer)模型主体。
dtype = torch.bfloat16
body = ipex.optimize(setfit_model.model_body, dtype=dtype)
对于 TorchScript 跟踪,我们根据模型的最大输入长度生成一个随机序列,其中的词符(token)从分词器的词汇表中采样。
tokenizer = setfit_model.model_body.tokenizer
d = generate_random_sequences(batch_size=1, length=tokenizer.model_max_length, vocab_size=tokenizer.vocab_size)
body = torch.jit.trace(body, (d,), check_trace=False, strict=False)
setfit_model.model_body = torch.jit.freeze(body)
现在让我们用量化后的 Optimum 模型来运行基准测试。我们首先需要为我们的 SetFit 模型定义一个包装器,该包装器在推理时插入我们量化后的模型主体(而不是原始的模型主体)。然后,我们可以使用这个包装器来运行基准测试。
from optimum.intel import IPEXModel
class OptimumSetFitModel:
def __init__(self, setfit_model, model_body):
model_body.tokenizer = setfit_model.model_body.tokenizer
self.model_body = model_body
self.model_head = setfit_model.model_head
optimum_model = IPEXModel.from_pretrained(optimum_model_path)
optimum_setfit_model = OptimumSetFitModel(setfit_model, model_body=optimum_model)
pb = PerformanceBenchmark(
model=optimum_setfit_model,
dataset=test_dataset,
optim_type=f"bge-small (optimum-int8)",
model_path=optimum_model_path,
autocast_dtype=torch.bfloat16,
)
perf_metrics.update(pb.run_benchmark())
结果
批处理大小=1 时的准确率 vs 延迟
bge-small (transformers) | bge-small (ipex-bfloat16) | bge-small (optimum-int8) | |
---|---|---|---|
模型大小 | 127.32 MB | 63.74 MB | 44.65 MB |
在测试集上的准确率 | 88.4% | 88.4% | 88.1% |
延迟 (bs=1) | 15.69 +/- 0.57 毫秒 | 5.67 +/- 0.66 毫秒 | 4.55 +/- 0.25 毫秒 |
在批处理大小为 1 的情况下,我们优化后的模型延迟降低了 3.45 倍。请注意,这是在几乎没有准确率下降的情况下实现的!还值得一提的是,模型大小缩小了 2.85 倍。
接下来我们关注的重点是在不同批处理大小下的吞吐量。在这里,优化带来了更大的速度提升。当比较可达到的最高吞吐量(在任何批处理大小下)时,优化后的模型比原始的 transformers fp32 模型快了 7.8 倍!
总结
在这篇博文中,我们展示了如何使用 🤗 Optimum Intel 中的量化功能来优化 SetFit 模型。在运行了一个快速简便的训练后量化过程后,我们观察到准确率水平得到了保持,而推理吞吐量则提高了 7.8 倍。这种优化方法可以轻松应用于任何在英特尔至强(Xeon)上运行的现有 SetFit 部署。
参考文献
- Lewis Tunstall, Nils Reimers, Unso Eun Seo Jo, Luke Bates, Daniel Korat, Moshe Wasserblat, Oren Pereg, 2022. "Efficient Few-Shot Learning Without Prompts". https://arxiv.org/abs/2209.11055