更新时间:2024-10-18 GMT+08:00
分享

自定义镜像规范

AI Gallery支持托管自定义镜像,但是托管的自定义镜像要满足规范才支持使用AI Gallery工具链服务(微调大师、在线推理服务)。

自定义镜像的使用流程

  1. 托管自定义镜像,操作步骤请参考托管模型到AI Gallery

  2. 上架自定义镜像,操作步骤请参考发布模型到AI Gallery
  3. 在AI Gallery进行自定义镜像训练或推理。使用AI Gallery微调大师训练模型使用AI Gallery在线推理服务部署模型

自定义镜像规范(训练)

当托管自定义镜像到AI Gallery时,如果镜像要支持AI Gallery的模型微调,则需要在“README.md”文件配置自定义镜像的训练参数,如下示例所示,参数说明请参见表1

Framework:
  - mindspore
Minimum_hardware_requirement: 1-snt9b-16-cpu-24-96
Hardware: gpu
Language:
  - en
Train_image_url: swr.<swr-domain-name><namespace><repository>.myhuaweicloud.com
Train_command_path: /xxx/xxx/xxx.py

Readme的文件必须按照YAML语法书写才能使配置生效。

表1 自定义镜像的训练参数

参数名称

说明

Train_image_url

必填,训练镜像路径,输入镜像存放的SWR路径地址,例如“swr.<swr-domain-name><namespace><repository>.myhuaweicloud.com”(地址必须是swr开头、myhuaweicloud.com结尾)。仅当配置了该参数,AI Gallery才会使用自定义镜像进行训练,否则使用AI Gallery的预置镜像进行训练。

Train_command_path

必填,训练启动脚本,输入启动脚本地址,例如“/xxx/xxx/main.py”。仅支持shell脚本和python脚本。脚本示例可以参考train.py示例。如果是SWR容器内的地址,则填写绝对路径;如果是AI Gallery仓库内的地址,则填写相对路径。

同时,还需要在“模型文件”添加gallery_train文件夹,文件夹内容参考表2

表2 gallery_train文件列表

文件类型

文件说明

“train_params.json”

必选文件,训练参数文件,定义了模型训练的必要参数,例如训练方式、超参信息。该参数会显示在微调工作流的“作业设置”页面的算法配置和超参数设置里面。代码示例请参见train_params.json示例

“dataset_readme.md”

必选文件,数据集要求说明,定义了模型训练时对数据集的要求,会显示在微调工作流的“准备数据”页面。

自定义镜像规范(推理)

当托管自定义镜像到AI Gallery时,如果镜像要支持AI Gallery的推理服务,则需要在“README.md”文件配置自定义镜像的推理参数,如下示例所示,参数说明请参见表3

Framework:
  - mindspore
Minimum_hardware_requirement: 1-snt9b-16-cpu-24-96
Hardware: gpu
Language:
  - en
Infer_image_url: swr.<swr-domain-name><namespace><repository>.myhuaweicloud.com
Infer_command_path: /xxx/xxx/xxx.py
Infer_port: 8081

Readme的文件必须按照YAML语法书写才能使配置生效。

表3 自定义镜像的推理参数

参数名称

说明

Infer_image_url

必填,推理镜像路径,输入镜像存放的SWR路径地址,例如“swr.<swr-domain-name><namespace><repository>.myhuaweicloud.com”(地址必须是swr开头、myhuaweicloud.com结尾)。仅当配置了该参数,AI Gallery才会使用自定义镜像进行推理,否则使用AI Gallery的预置镜像进行推理。

Infer_command_path

必填,推理启动脚本,输入启动脚本地址,例如“/xxx/xxx/main.py”。仅支持shell脚本和python脚本。如果是SWR容器内的地址,则填写绝对路径;如果是AI Gallery仓库内的地址,则填写相对路径。

Infer_port

选填,推理服务提供的端口,缺省值为8080。只支持部署HTTP服务。

自定义镜像可以通过是否上传自定义推理参数文件“gallery_inference/inference_params.json”决定镜像在部署推理服务时是否支持设置推理参数。

如果在自定义镜像的“模型文件”下上传了“gallery_inference/inference_params.json”文件,则在推理启动脚本中需要使用环境变量来指定“inference_params.json”中的参数,否则配置的参数将无法在推理过程中生效。

“inference_params.json”文件的参数请参见表4。该参数会显示在部署推理服务页面,在“高级设置”下会新增“参数设置”,基于配置的推理参数供模型使用者修改自定义镜像的部署参数。

表4 自定义推理参数说明

参数名称

说明

name

参数名称,只能包含英文、数字、下划线。

type

参数类型,可选值:float、int、str、bool、enum。

default

参数默认值,如果是“none”则无默认值,否则需要填写。

help

参数描述,非必选。当用来替换placehoder时,超过20个字符则截断。

valid_range

参数属性值范围,当“type”“enum”时表示的是枚举值,当“type”“float”“int”时表示的是属性值范围。

“inference_params.json”的代码示例如下所示。

{
    "name": "max_input_length",
    "type": "int",
    "default": 2048,
    "valid_range": [
        0,
        10000
    ]
}

对应的推理启动脚本“main.py”如下所示。

text-generation-launcher \
--model-id ${model-id} \
--max-input-length ${max_input_length} \
--max-total-tokens ${max-total-tokens} \
--max-batch-prefill-tokens 4096 \
--trust-remote-code \
--sharded false \
--num-shard 1 \
--max-waiting-tokens 1 \
--max-concurrent-requests 1000 \
--waiting-served-ratio 0.2 \
--hostname 0.0.0.0 \
--port 8085

“train.py”示例

表5 环境变量说明

变量名称

说明

示例

ENV_AG_MODEL_DIR

模型存放路径,AI Gallery的模型仓库地址,包含模型仓库的所有文件。

“/home/ma-user/.cache/gallery/model/ur12345--gpt2”

ENV_AG_DATASET_DIR

数据集存放路径,AI Gallery的数据集仓库地址,包含数据集仓库的所有文件。

“/home/ma-user/.cache/gallery/dataset/ur12345--data_demo”

ENV_AG_USER_PARAMS

配置的训练超参json字符串。创建训练任务时在算法配置页面设置的超参,用json字符串表示。

{"per_device_eval_batch_size":"32","lr":"0.001","logging_steps":"24"}

ENV_AG_TRAIN_OUTPUT_DIR

训练产物文件存放路径。训练产物将被保存到该路径。训练任务结束后,由AI Gallery平台将该目录上传到新模型的仓库中。

“/home/ma-user/.cache/gallery/output”

ENV_AG_USER_METRICS_LOG_PATH

训练数据的日志文件存放路径。训练过程中的迭代次数、LOSS和吞吐数据按照“迭代次数|loss|吞吐”格式记录在日志中,AI Gallery通过环境变量找到日志,从中获取实际数据绘制成“吞吐”“训练LOSS”曲线,呈现在训练的“指标效果”中。具体请参见查看训练效果

说明:

日志文件中的迭代次数、LOSS和吞吐数据必须按照“迭代次数|loss|吞吐”格式存放,否则AI Gallery会数据解析失败,导致“吞吐”“训练LOSS”曲线异常。

“/var/logs/user_metrics.log”

import json
import os

from datasets import load_dataset
from transformers import AutoImageProcessor
from torchvision.transforms import RandomResizedCrop, Compose, Normalize, ToTensor, RandomHorizontalFlip
import numpy as np
from transformers import AutoModelForImageClassification, TrainingArguments, Trainer
from transformers import DefaultDataCollator
from sklearn import metrics

# 环境变量
# 工作目录
ENV_AG_WORK_DIR = 'ENV_AG_WORK_DIR'
# 模型存放路径
ENV_AG_MODEL_DIR = 'ENV_AG_MODEL_DIR'
# 数据集存放路径
ENV_AG_DATASET_DIR = 'ENV_AG_DATASET_DIR'
# 配置的训练超参json字符串
ENV_AG_USER_PARAMS = 'ENV_AG_USER_PARAMS'
# 训练产物存放路径
ENV_AG_TRAIN_OUTPUT_DIR = 'ENV_AG_TRAIN_OUTPUT_DIR'

_transforms = None


def _multi_class_classification_metrics(pred):
    raw_predictions, labels = pred
    predictions = np.argmax(raw_predictions, axis=1)
    results = {
        "f1_macro": metrics.f1_score(labels, predictions, average="macro"),
        "f1_micro": metrics.f1_score(labels, predictions, average="micro"),
        "f1_weighted": metrics.f1_score(labels, predictions, average="weighted"),
        "precision_macro": metrics.precision_score(labels, predictions, average="macro"),
        "precision_micro": metrics.precision_score(labels, predictions, average="micro"),
        "precision_weighted": metrics.precision_score(labels, predictions, average="weighted"),
        "recall_macro": metrics.recall_score(labels, predictions, average="macro"),
        "recall_micro": metrics.recall_score(labels, predictions, average="micro"),
        "recall_weighted": metrics.recall_score(labels, predictions, average="weighted"),
        "accuracy": metrics.accuracy_score(labels, predictions),
    }
    return results


def parse_args():
    """ 从AIGallery环境变量中获取用户配置的超参json """
    return json.loads(os.getenv(ENV_AG_USER_PARAMS))


def _process_input_data(image_processor):
    # 加载数据集
    dataset_path = os.getenv(ENV_AG_DATASET_DIR)
    dataset = load_dataset("imagefolder", data_dir=dataset_path)

    # 数据增强
    normalize = Normalize(mean=image_processor.image_mean, std=image_processor.image_std)
    size = (image_processor.size["shortest_edge"] if "shortest_edge" in image_processor.size else (
        image_processor.size["height"], image_processor.size["width"]))
    global _transforms
    _transforms = Compose([RandomResizedCrop(size), RandomHorizontalFlip(), ToTensor(), normalize])
    ret = dataset.with_transform(_format_transforms)
    return ret


# 转换函数
def _format_transforms(examples):
    examples["pixel_values"] = [_transforms(img.convert("RGB")) for img in examples["image"]]
    del examples["image"]
    return examples


def train(user_args):
    print('Start to process dataset')
    model_path = os.getenv(ENV_AG_MODEL_DIR)
    image_processor = AutoImageProcessor.from_pretrained(model_path)

    dataset = _process_input_data(image_processor)
    print(f"Dataset: {dataset}")
    # label和id映射
    classes = dataset["train"].features["label"].names
    label2id = {c: i for i, c in enumerate(classes)}
    id2label = {i: c for i, c in enumerate(classes)}

    print('Start to load model')
    # 加载模型
    model = AutoModelForImageClassification.from_pretrained(
        model_path,
        num_labels=len(classes),
        id2label=id2label,
        label2id=label2id,
        ignore_mismatched_sizes=True
    )

    print('Start to set training args')
    # 训练参数
    training_args = TrainingArguments(
        output_dir=os.getenv(ENV_AG_TRAIN_OUTPUT_DIR),
        remove_unused_columns=False,
        evaluation_strategy="epoch",
        save_strategy=user_args['save_strategy'],
        learning_rate=float(user_args['lr']),
        save_total_limit=3,
        per_device_train_batch_size=32,
        gradient_accumulation_steps=1,
        per_device_eval_batch_size=int(user_args['per_device_eval_batch_size']),
        num_train_epochs=int(user_args['num_train_epochs']),
        warmup_ratio=float(user_args['warmup_ratio']),
        logging_steps=int(user_args['logging_steps']),
        load_best_model_at_end=True,
        metric_for_best_model="accuracy",
        push_to_hub=False,
    )

    print('Start to train')
    # 训练参数
    trainer = Trainer(
        model=model,
        args=training_args,
        data_collator=DefaultDataCollator(),
        train_dataset=dataset["train"],
        eval_dataset=dataset["test"],
        tokenizer=image_processor,
        compute_metrics=_multi_class_classification_metrics,
    )

    # 开始训练
    train_results = trainer.train()
    print('Start to save model')
    # 保存模型
    trainer.save_model()
    trainer.log_metrics("train", train_results.metrics)
    trainer.save_metrics("train", train_results.metrics)
    trainer.save_state()

    print('Start to evaluate')
    # 在验证集上做准确性评估
    eva_metrics = trainer.evaluate()
    trainer.log_metrics("eval", eva_metrics)
    trainer.save_metrics("eval", eva_metrics)

    print('All Done')


if __name__ == '__main__':
    args = parse_args()
    train(args)

“train_params.json”示例

表6 training_methods参数说明

参数名称

说明

name

自定义的训练方式。

hyperparameters

训练方式包含的超参。具体参数说明请参见表7

表7 hyperparameters参数说明

参数名称

说明

name

超参的名称,只能包含英文、数字、下划线。

type

支持的超参类型,支持float、int、str或bool。

required

超参是否必选,支持true、false。必选不可删除,非必选可删除。

default

超参的默认值,如果无默认值,则填写空双引号。

help

超参的说明,不能超过20个字符。

{
    "training_methods": [
        {
            "name": "全参微调",
            "hyperparameters": [
                {
                    "name": "lr",
                    "type": "float",
                    "required": true,
                    "default": 0.001,
                    "help": "学习率"
                },
                {
                    "name": "per_device_eval_batch_size",
                    "type": "int",
                    "required": false,
                    "default": 32,
                    "help": "批大小"
                },
                {
                    "name": "logging_steps",
                    "type": "int",
                    "required": false,
                    "default": 24,
                    "help": "每多少步记录一次步骤"
                },
                {
                    "name": "save_strategy",
                    "type": "str",
                    "required": true,
                    "default": "epoch",
                    "help": "训练过程中保存checkpoint的策略"
                },
                {
                    "name": "num_train_epochs",
                    "type": "int",
                    "required": true,
                    "default": 20,
                    "help": "训练的总epochs数"
                },
                {
                    "name": "warmup_ratio",
                    "type": "float",
                    "required": true,
                    "default": 0.1,
                    "help": "用于指定线性热身占总训练步骤的比例"
                }
            ]
        }
    ]
}

相关文档