在批处理终结点中部署语言模型

适用范围:Azure CLI ml 扩展 v2(最新版)Python SDK azure-ai-ml v2(最新版)

批处理终结点可用于针对文本数据部署昂贵的模型(如语言模型)。 在本教程中,你将了解如何部署一个模型,该模型可以使用 HuggingFace 的模型对长文本序列执行文本摘要操作。 它还演示了如何使用 HuggingFace optimumaccelerate 库进行推理优化。

关于此示例

我们将使用的模型是使用常用库转换器构建的。 该模型是在 BART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation 论文中引入的。 此模型具有以下约束,对于部署而言非常重要,请务必牢记:

  • 最多可以处理 1024 个标记的序列。
  • 训练用途是对英语文本进行摘要。
  • 我们将使用 Torch 作为后端。

本文中的示例基于 azureml-examples 存储库中包含的代码示例。 要在无需复制/粘贴 YAML 和其他文件的情况下在本地运行命令,请先克隆存储库,然后将目录更改为以下文件夹:

git clone https://github.com/Azure/azureml-examples --depth 1
cd azureml-examples/cli

此示例的文件位于以下位置:

cd endpoints/batch/deploy-models/huggingface-text-summarization

在 Jupyter Notebook 中继续操作

可以在 Jupyter Notebook 中按照此示例进行操作。 在克隆的存储库中,打开以下笔记本: text-summarization-batch.ipynb

先决条件

在按照本文中的步骤操作之前,请确保满足以下先决条件:

  • Azure 订阅。 如果没有 Azure 订阅,可在开始前创建一个试用帐户。 试用 Azure 机器学习

  • 一个 Azure 机器学习工作区。 如果你没有,请按照管理 Azure 机器学习工作区一文中的步骤创建一个。

  • 确保你在工作区中具有以下权限:

    • 创建或管理批处理终结点和部署:使用允许 Microsoft.MachineLearningServices/workspaces/batchEndpoints/* 的所有者、参与者或自定义角色。

    • 在工作区资源组中创建 ARM 部署:在部署工作区的资源组中使用允许 Microsoft.Resources/deployments/write 的所有者角色、参与者角色或自定义角色。

  • 需要安装以下软件才能使用 Azure 机器学习:

    Azure CLIml适用于 Azure 机器学习的扩展

    az extension add -n ml
    

    注意

    Azure CLI 的 ml 扩展版本 2.7 中引入了 Batch 终结点的管道组件部署。 使用 az extension update --name ml 获取它的上一个版本。

连接到工作区

工作区是 Azure 机器学习的顶级资源,为使用 Azure 机器学习时创建的所有项目提供了一个集中的处理位置。 在本部分,我们将连接到要在其中执行部署任务的工作区。

在以下代码中传入订阅 ID、工作区、位置和资源组的值:

az account set --subscription <subscription>
az configure --defaults workspace=<workspace> group=<resource-group> location=<location>

注册模型

由于此模型的大小,因此尚未包含在此存储库中。 你可以转而从 HuggingFace 模型的中心下载副本。 你需要在你正在使用的环境中安装 transformerstorch 包。

%pip install transformers torch

使用以下代码将模型下载到文件夹 model

from transformers import pipeline

model = pipeline("summarization", model="facebook/bart-large-cnn")

<!--NOT AVAILABLE ON FEATURE facebook-->


model_local_path = 'model'
summarizer.save_pretrained(model_local_path)

现在,可以在 Azure 机器学习注册表中注册此模型:

MODEL_NAME='bart-text-summarization'
az ml model create --name $MODEL_NAME --path "model"

创建终结点

我们将创建一个名为 text-summarization-batch 的批处理终结点,用于部署 HuggingFace 模型来对英语文本文件运行文本摘要。

  1. 确定终结点的名称。 终结点名称最终将包含在与终结点关联的 URI 中。 因此,批处理终结点名称在 Azure 区域内需是唯一的。 例如,westus2 中只能有一个名为 mybatchendpoint 的批处理终结点。

    在本例中,我们将终结点名称放在一个变量中,以便稍后可以轻松引用它。

    ENDPOINT_NAME="text-summarization-batch"
    
  2. 配置批处理终结点

    以下 YAML 文件定义了批处理终结点:

    endpoint.yml

     $schema: https://azuremlschemas.azureedge.net/latest/batchEndpoint.schema.json
     name: text-summarization-batch
     description: A batch endpoint for summarizing text using a HuggingFace transformer model.
     auth_mode: aad_token
    
  3. 创建终结点:

    az ml batch-endpoint create --file endpoint.yml  --name $ENDPOINT_NAME
    

创建部署

我们来创建托管模型的部署:

  1. 我们需要创建评分脚本,该脚本应能读取批处理部署提供的 CSV 文件并返回模型的分数以及摘要。 以下脚本会执行以下操作:

    • 指示 init 函数来检测硬件配置(CPU 与 GPU)并相应地加载模型。 模型和 tokenizer 会加载到全局变量中。 我们不会使用 HuggingFace 中的 pipeline 对象来解释当前所用模型序列长度中的限制。
    • 请注意,我们正在使用 optimumaccelerate 库来执行模型优化,以提高性能。 如果模型或硬件不支持它,我们将运行部署而不进行此类优化。
    • 表示 run 函数,将对批处理部署提供的每个迷你批处理执行此函数。
    • run 函数使用 datasets 库读取整个批处理。 我们需要汇总的文本位于 text 列中。
    • run 方法遍历每一行文本并运行预测。 由于这是一个成本很高的模型,因此对整个文件运行预测将导致内存不足异常。 请注意,模型不是使用来自 transformerspipeline 对象执行的。 这样做是考虑到长序列的文本以及我们正在使用的基础模型中 1024 个令牌的限制。
    • 此模型返回所提供文本的摘要。

    code/batch_driver.py

import os
import time
import torch
import subprocess
import mlflow
from pprint import pprint
from transformers import AutoTokenizer, BartForConditionalGeneration
from optimum.bettertransformer import BetterTransformer
from datasets import load_dataset


def init():
    global model
    global tokenizer
    global device

    cuda_available = torch.cuda.is_available()
    device = "cuda" if cuda_available else "cpu"

    if cuda_available:
        print(f"[INFO] CUDA version: {torch.version.cuda}")
        print(f"[INFO] ID of current CUDA device: {torch.cuda.current_device()}")
        print("[INFO] nvidia-smi output:")
        pprint(
            subprocess.run(["nvidia-smi"], stdout=subprocess.PIPE).stdout.decode(
                "utf-8"
            )
        )
    else:
        print(
            "[WARN] CUDA acceleration is not available. This model takes hours to run on medium size data."
        )

    # AZUREML_MODEL_DIR is an environment variable created during deployment
    model_path = os.path.join(os.environ["AZUREML_MODEL_DIR"], "model")

    # load the tokenizer
    tokenizer = AutoTokenizer.from_pretrained(
        model_path, truncation=True, max_length=1024
    )

    # Load the model
    try:
        model = BartForConditionalGeneration.from_pretrained(
            model_path, device_map="auto"
        )
    except Exception as e:
        print(
            f"[ERROR] Error happened when loading the model on GPU or the default device. Error: {e}"
        )
        print("[INFO] Trying on CPU.")
        model = BartForConditionalGeneration.from_pretrained(model_path)
        device = "cpu"

    # Optimize the model
    if device != "cpu":
        try:
            model = BetterTransformer.transform(model, keep_original_model=False)
            print("[INFO] BetterTransformer loaded.")
        except Exception as e:
            print(
                f"[ERROR] Error when converting to BetterTransformer. An unoptimized version of the model will be used.\n\t> {e}"
            )

    mlflow.log_param("device", device)
    mlflow.log_param("model", type(model).__name__)


def run(mini_batch):
    resultList = []

    print(f"[INFO] Reading new mini-batch of {len(mini_batch)} file(s).")
    ds = load_dataset("csv", data_files={"score": mini_batch})

    start_time = time.perf_counter()
    for idx, text in enumerate(ds["score"]["text"]):
        # perform inference
        inputs = tokenizer.batch_encode_plus(
            [text], truncation=True, padding=True, max_length=1024, return_tensors="pt"
        )
        input_ids = inputs["input_ids"].to(device)
        summary_ids = model.generate(
            input_ids, max_length=130, min_length=30, do_sample=False
        )
        summaries = tokenizer.batch_decode(
            summary_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False
        )

        # Get results:
        resultList.append(summaries[0])
        rps = idx / (time.perf_counter() - start_time + 00000.1)
        print("Rows per second:", rps)

    mlflow.log_metric("rows_per_second", rps)
    return resultList

提示

尽管文件由部署在小型批处理中提供,但此评分脚本一次仅处理一行。 这是处理高成本模型(如变换器)时的一种常见模式,尝试加载整个批处理并将其一次性发送到模型可能会导致批处理执行程序(OOM 执行程序)的内存压力增大。

  1. 我们需要指出我们将在哪个环境中运行部署。 在本例中,模型运行在 Torch 上,它需要来自 HuggingFace 的库 transformersaccelerateoptimum。 Azure 机器学习已有一个提供 Torch 和 GPU 支持的环境。 只需在conda.yaml文件中添加几个依赖项即可。

    environment/torch200-conda.yaml

name: huggingface-env
channels:
  - conda-forge
dependencies:
  - python=3.8.5
  - pip
  - pip:
    - torch==2.0
    - transformers
    - accelerate
    - optimum
    - datasets
    - mlflow
    - azureml-mlflow
    - azureml-core
    - azureml-dataset-runtime[fuse]
  1. 可以使用前面提到的 conda 文件,如下所示:

    环境定义将包含在部署文件中。

    deployment.yml

compute: azureml:gpu-cluster
environment:
  name: torch200-transformers-gpu
  image: mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04:latest

获取对环境的引用:

environment = Environment(
    name="torch200-transformers-gpu",
    conda_file="environment/torch200-conda.yaml",
    image="mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04:latest",
)

重要

我们创建的环境 torch200-transformers-gpu 需要与 CUDA 11.8 兼容的硬件设备才能运行 Torch 2.0 和 Ubuntu 20.04。 如果 GPU 设备不支持此版本的 CUDA,则可以检查替代的 torch113-conda.yaml conda 环境(也在存储库上提供),该存储库通过 CUDA 10.1 在 Ubuntu 18.04 上运行 Torch 1.3。 但此配置不支持使用 optimumaccelerate 库进行加速。

  1. 每个部署都在计算群集上运行。 它们支持 Azure 机器学习计算群集 (AmlCompute)Kubernetes 群集。 在此示例中,模型可以从 GPU 加速中获益,这就是使用 GPU 群集的原因。

    az ml compute create -n gpu-cluster --type amlcompute --size STANDARD_NV6 --min-instances 0 --max-instances 2
    

    注意

    此时不收取计算费用,因为在调用批处理终结点并提交批量评分作业之前,群集将保持为 0 个节点。 详细了解管理和优化 AmlCompute 的成本

  2. 现在,我们来创建部署。

    如需在创建的终结点下创建新部署,请创建如下所示的 YAML 配置。 可以检查额外属性中的完整批处理终结点 YAML 机构

    deployment.yml

$schema: https://azuremlschemas.azureedge.net/latest/batchDeployment.schema.json
endpoint_name: text-summarization-batch
name: text-summarization-optimum
description: A text summarization deployment implemented with HuggingFace and BART architecture with GPU optimization using Optimum.
type: model
model: azureml:bart-text-summarization@latest
compute: azureml:gpu-cluster
environment:
  name: torch200-transformers-gpu
  image: mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.8-cudnn8-ubuntu22.04:latest
  conda_file: environment/torch200-conda.yaml
code_configuration:
  code: code
  scoring_script: batch_driver.py
resources:
  instance_count: 2
settings:
  max_concurrency_per_instance: 1
  mini_batch_size: 1
  output_action: append_row
  output_file_name: predictions.csv
  retry_settings:
    max_retries: 1
    timeout: 3000
  error_threshold: -1
  logging_level: info

然后,用以下命令创建部署:

az ml batch-deployment create --file deployment.yml --endpoint-name $ENDPOINT_NAME --set-default

如需用所示环境和评分脚本创建新部署,请使用以下代码:

deployment = BatchDeployment(
    name="text-summarization-hfbart",
    description="A text summarization deployment implemented with HuggingFace and BART architecture",
    endpoint_name=endpoint.name,
    model=model,
    environment=environment,
    code_configuration=CodeConfiguration(
        code="code",
        scoring_script="batch_driver.py",
    ),
    compute=compute_name,
    instance_count=2,
    max_concurrency_per_instance=1,
    mini_batch_size=1,
    output_action=BatchDeploymentOutputAction.APPEND_ROW,
    output_file_name="predictions.csv",
    retry_settings=BatchRetrySettings(max_retries=3, timeout=3000),
    logging_level="info",
)

然后,用以下命令创建部署:

ml_client.batch_deployments.begin_create_or_update(deployment)

重要

在此部署中,你会注意到 retry_settings 参数中 timeout 的值很高。 原因是我们正在运行的模型的性质。 这是一个成本很高的模型,单行推理最长可能需要 60 秒。 timeout 参数控制批处理部署应等待评分脚本完成每个微型批处理的时间。 由于我们的模型逐行运行预测,因此处理长文件可能需要一定时间。 另请注意,每个批处理的文件数设置为 1 (mini_batch_size=1)。 这又与我们所做工作的性质有关。 每批一次处理一个文件的成本足以证明它的合理性。 你会注意到这是 NLP 处理中的一种模式。

  1. 尽管可以在终结点内部调用特定部署,但通常需要调用终结点本身,并支持终结点决定使用哪个部署。 此类部署命名为“默认”部署。 这样可以更改默认部署,从而更改为部署提供服务的模型,且无需更改与调用终结点的用户的协定。 使用以下说明更新默认部署:

    DEPLOYMENT_NAME="text-summarization-hfbart"
    az ml batch-endpoint update --name $ENDPOINT_NAME --set defaults.deployment_name=$DEPLOYMENT_NAME
    
  2. 此时,我们的批处理终结点随时可用。

测试部署

为了测试终结点,我们将使用数据集 BillSum:美国立法自动摘要语料库 的示例。 此示例包含在文件夹 data 的存储库中。 请注意,数据采用 CSV 格式,而要汇总的内容位于列 text 下面,这与模型的预期一样。

  1. 我们来调用终结点:

    JOB_NAME=$(az ml batch-endpoint invoke --name $ENDPOINT_NAME --input data --input-type uri_folder --query name -o tsv)
    

    注意

    并不是每次安装时都会安装实用工具 jq。 可点击此链接了解相关说明。

    提示

    请注意,通过指示本地路径作为输入,数据将上传到 Azure 机器学习默认的存储帐户。

  2. 命令返回后立即启动批处理作业。 在作业完成前可监视作业状态:

    az ml job show -n $JOB_NAME --web
    
  3. 部署完成后,可下载预测:

    如需下载预测,请使用以下命令:

    az ml job download --name $JOB_NAME --output-name score --download-path .
    

部署处理文本的模型时的注意事项

正如本教程的一些注释中所提到的,处理文本可能有一些特殊性,需要针对批量部署进行特定配置。 设计批处理部署时,请考虑以下因素:

  • 一些 NLP 模型在内存和计算时间方面的成本可能会很高。 如果是这种情况,请考虑减少每个微型批处理中包含的文件数量。 在上面的示例中,该数量被设为最小,即每批 1 个文件。 虽然你不一定会遇到这种情况,但请考虑你的模型每次可以对多少文件个文件进行评分。 请记住,对于深度学习模型,输入大小与模型的内存占用之间的关系可能不是线性的。
  • 如果你的模型一次连一个文件都无法处理(如本例所示),请考虑以行/块的形式读取输入数据。 如果你需要实现更高的吞吐量或硬件利用率,请在行级别实施批处理。
  • 根据你的模型的成本和你希望处理的数据量来设置部署的 timeout 值。 请记住,timeout 表示批处理部署将等待你的评分脚本对给定批处理运行的时间。 如果你的批处理包含许多文件或是文件包含许多行,这会影响此参数的正确值。

处理文本的 MLflow 模型的注意事项

上述注意事项同样适用于 MLflow 模型。 但由于无需为 MLflow 模型部署提供评分脚本,因此所提及的某些建议可能需要采取不同的方法。

  • 批处理终结点中的 MLflow 模型支持将表格数据读取为输入数据,这可能会包含长序列文本。 有关支持哪些文件类型的详细信息,请参阅文件的类型支持
  • 批处理部署将调用 MLflow 模型的预测函数,其中包含作为 Pandas 数据帧中整个文件的内容。 如果输入数据包含许多行,则运行复杂模型(如本教程中介绍的模型)可能会导致内存不足异常。 如果是这种情况,可以考虑: