torchserve for docker

torchserve for docker

什么是TorchServe

TorchServe是用于服务PyTorch模型的灵活易用的工具。它没有TFX的复杂性,因此,它没有提供那么多的功能。但是,这是完成工作的直接方法!

TorchServe提供了一组必要的功能,例如服务器,模型存档器工具,API端点规范,日志记录,度量,批处理推断和模型快照等。它还提供了一系列高级功能,例如,对定制推理服务的支持,单元测试以及通过JMeter收集基准数据的简便方法。目前,它处于实验阶段,但在大多数情况下,它的工作原理就像是一种魅力。实际上,我们将在本文后面的部分中进行测试。

  • 支持HuggingFace,这是一个深度学习库,其任务是为所有人提高NLP的使用程度,并提供文档和示例
  • 支持Nvidia Waveglow(一个基于流的语音合成生成网络,提供文档和示例)
  • 与Model Zoo紧密集成,Model Zoo是一个深度学习注册表,其中包含根据流行的预训练模型创建的模型档案
  • 支持AWS Cloud Forming,可通过简单的配置文件(YAML或JSON)方便地在EC2实例上启动服务器
  • 支持通过snakevize分析器对TorchServe Python执行性能分析,以详细报告执行时间
  • 具有清晰说明的重构文档

环境依赖

  • openjdk-11-jdk
  • torch
  • torchserve
  • torch-model-archiver
## pretrained类型
/usr/local/python3/bin/torch-model-archiver --model-name BERTSeqClassification --version 1.0 --serialized-file ./Transformer_model_pretrained/pytorch_model.bin --handler ./Transformer_model_pretrained/Transformer_handler_generalized.py --extra-files "./Transformer_model_pretrained/config.json,./Transformer_model_pretrained/setup_config.json,./Transformer_model_pretrained/index_to_name.json"

## torchscript类型
/usr/local/python3/bin/torch-model-archiver --model-name BERTSeqClassification --version 1.0 --serialized-file ./Transformer_model_torchscript/traced_model.pt --handler ./Transformer_model_torchscript/Transformer_handler_generalized.py --extra-files "./Transformer_model_torchscript/setup_config.json,./Transformer_model_torchscript/index_to_name.json"

## 模型启动路径
mkdir model_store
mv BERTSeqClassification.mar model_store/

## 启动模型并注册
/usr/local/python3/bin/torchserve --start --model-store model_store --ts-config model_store/config.properties --models my_tc=BERTSeqClassification.mar --ncs

## 先启动模型在注册
/usr/local/python3/bin/torchserve --start --model-store model_store --ts-config model_store/config.properties --ncs
curl -X POST "localhost:8081/models?model_name=my_tc&url=BERTSeqClassification.mar&batch_size=4&max_batch_delay=5000&initial_workers=3&synchronous=true"

## 测试
curl -X POST http://127.0.0.1:8080/predictions/my_tc -T sample_text_captum_input.txt

扩展:
变量–model-name定义了模型的最终名称。这是非常重要的,因为它将是endpoint的名称空间,负责进行预测。你还可以指定一个–version。
–serialized-file指向我们之前创建的存储的 .pt模型。–handler 是一个python文件,我们在其中调用我们的自定义handler。
它暴露了一个handle函数,我们从该函数调用自定义handler中的方法。你可以使用默认名称来使用默认handler(例如,–handler
image_classifier)。
在–extra-files中,你需要将路径传递给你的handlers正在使用的所有文件。在本例中,我们必须向.json文件中添加路径。使用所有人类可
读标签名称,并在MyHandler.py 中定义每个类别。
如果你传递一个index_to_name.json文件,它将自动加载到handler ,并通过self.mapping访问。
–export-path就是 xxx.mar存放的地方,我还添加了-f来覆盖原有的文件。
如果一切顺利的话,你可以看到xxx.mar存放在./model-store路径中。

我个人已经封装好了docker镜像,可以刻直接拿来使用docker pull registry.cn-hangzhou.aliyuncs.com/daiyizheng/centos-ssh-py37-java11:v2.0.0

当然也可以使用官方的镜像Github

示例

详细代码参阅GitHub
Haddler主要时代码推理的逻辑,其中initialize是初始化函数, preprocess是Api请求过来的信息,基本的文本处理,inference是推理函数,注意推理函数返回的一定是数组List类型, postprocess是推理后结果的后处理函数,这里返回结果是List,并且长度为1

from abc import ABC
import os
import json
import logging
import ast

from tqdm import tqdm
import numpy as np
import torch
from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
from torch.utils.data import TensorDataset
from ts.torch_handler.base_handler import BaseHandler


from transformers import BertModel, AutoTokenizer

logger = logging.getLogger(__name__)

class BERTHandler(BaseHandler, ABC):
    def __init__(self):
        super(BERTHandler, self).__init__()
        self.initialized = False
    def initialize(self, ctx):
        """模型初始化"""
        self.manifest = ctx.manifest
        properties = ctx.system_properties
        ## 模型根目录
        model_dir = properties.get("model_dir")
        serialized_file = self.manifest["model"]["serializedFile"] # 对应 --serialized-file的参数
        model_pt_path = os.path.join(model_dir, serialized_file)

        ## setup_config的配置文件
        setup_config_path = os.path.join(model_dir, "setup_config.json")
        if os.path.isfile(setup_config_path):
            with open(setup_config_path) as setup_config_file:
                self.setup_config = json.load(setup_config_file)
        else:
            logger.warning("Missing the setup_config.json file.")

        self.device = torch.device(
            "cuda:" + str(properties.get("gpu_id"))
            if torch.cuda.is_available() and properties.get("gpu_id") is not None and self.setup_config['is_gpu']
            else "cpu"
        )
        ## 加载模型
        if self.setup_config["save_mode"] == "torchscript":
            self.model = torch.jit.load(model_pt_path, map_location=self.device)
        elif self.setup_config["save_mode"] == "pretrained":
            self.model = BertModel.from_pretrained(model_dir)

        ## 加载tokenizer分词器
        if any(fname for fname in os.listdir(model_dir) if fname.startswith("vocab.") and os.path.isfile(fname)):
            self.tokenizer = AutoTokenizer.from_pretrained(
                model_dir, do_lower_case=self.setup_config["do_lower_case"]
            )
        else:
            self.tokenizer = AutoTokenizer.from_pretrained(
                self.setup_config["model_name"],
                do_lower_case=self.setup_config["do_lower_case"],
            )

        self.model.to(self.device)
        self.model.eval()
        logger.info(
            "Transformer model from path %s loaded successfully", model_dir
        )

        ## 加载标签映射文件
        mapping_file_path = os.path.join(model_dir, "index_to_name.json")
        if os.path.isfile(mapping_file_path):
            with open(mapping_file_path) as f:
                self.mapping = json.load(f)
        else:
            logger.warning("Missing the index_to_name.json file.")

        self.initialized = True

    def preprocess(self, requests):
        """基本文本预处理
        [{'body': {'text': ['Bloomberg']}}]
        """
        input_ids_batch = None
        attention_mask_batch = None
        for idx, data in enumerate(requests):
            input_text = data.get("data")
            if input_text is None:
                input_text = data.get("body")
            if isinstance(input_text, (bytes, bytearray)):
                input_text = input_text.decode('utf-8')
            input_text_target = ast.literal_eval(input_text)
            input_text = input_text_target[self.setup_config["columns"]]
            max_length = self.setup_config["max_length"]
            logger.info("Received text: '%s'", input_text)
            inputs = self.tokenizer.batch_encode_plus(input_text,
                                                max_length=int(max_length),
                                                pad_to_max_length=True,
                                                add_special_tokens=True,
                                                return_tensors='pt')
            input_ids = inputs["input_ids"].to(self.device)
            attention_mask = inputs["attention_mask"].to(self.device)
            if input_ids.shape is not None:
                if input_ids_batch is None:
                    input_ids_batch = input_ids
                    attention_mask_batch = attention_mask
                else:
                    input_ids_batch = torch.cat((input_ids_batch, input_ids), 0)
                    attention_mask_batch = torch.cat((attention_mask_batch, attention_mask), 0)

        dataset = TensorDataset(input_ids_batch, attention_mask_batch)
        return dataset



    def inference(self, dataset):
        """
        模型推理
        """
        eval_sampler = SequentialSampler(dataset)
        eval_dataloader = DataLoader(dataset, sampler=eval_sampler, batch_size=int(self.setup_config["batch_size"]))

        # Eval!
        logger.info("***** Running evaluation on dev dataset *****")
        logger.info("  Num examples = %d", len(dataset))
        logger.info("  Batch size = %d", int(self.setup_config["batch_size"]))

        self.model.eval()

        inferences = None
        for batch in tqdm(eval_dataloader, desc="Prediction"):
            batch = tuple(t.to(self.device) for t in batch)
            with torch.no_grad():
                inputs = {'input_ids': batch[0],
                          'attention_mask': batch[1]}
                outputs = self.model(**inputs)  # (sequence_output, pooler_output)
                if inferences is None:
                    inferences = outputs[1].detach().cpu().numpy()
                else:
                    inferences = np.append(inferences, outputs[1].detach().cpu().numpy(), axis=0)
        return inferences.tolist()


    def postprocess(self, inference_output):
        """模型推理后处理 返回必须时数[[]]组"""

        return [inference_output]

docker 一键部署

FROM registry.cn-hangzhou.aliyuncs.com/daiyizheng/centos-ssh-py37-java11:v2.0.0
## 工作目录
WORKDIR /root/model_deploy

RUN mkdir bert_embbeding

## 复制项目
COPY ./  ./bert_embbeding

## 安装除torchserver torch和transformer的依赖
RUN pip install --upgrade pip && pip install -i  https://mirrors.aliyun.com/pypi/simple -r bert_embbeding/requirements.txt

EXPOSE 8080/tcp
EXPOSE 8081/tcp

RUN chmod 777 ./bert_embbeding/run.sh

CMD ["bash","-c","./bert_embbeding/run.sh start && tail -f /dev/null"]

启动镜像
dos2unix run.sh
docker build -t repu/torchserve:v1.0.0 .
docker run -dit -p 8880:8080 -p 8081:8081 --name bert repu/torchserve:v1.0.0

接口调试

import requests
import json
def tester():
    server_url = 'http://xxxx:8880/predictions/bertEmbbeding'

    payload = {
        "text":["做基因检测采集什么样本检测结果最准确"],
    }
    json_payload = json.dumps(payload)
    content_length = len(json_payload)

    headers = {'Content-Type': 'application/text', 'Content-Length': '2'}
    response = requests.post(server_url, data=json_payload, headers=headers, allow_redirects=True)

    if response.status_code == requests.codes.ok:
        print ('Headers: {}\nResponse: {}'.format(response.headers, response.text))

模型管理监控

  1. 注册新模型
    curl -X POST “http://xxxx:8081/models?url=xxx.mar”

  2. 查询已注册模型的列表
    curl "http://xxxx:8081/models"

  3. 新模型分配工人
    curl -v -X PUT "http://localhost:8081/models/xxx?min_worker=2"
    curl "http://localhost:8081/models/xxx"

{
    "modelName": "bertEmbbeding",
    "modelVersion": "1.0",
    "modelUrl": "bertEmbbeding.mar",
    "runtime": "python",
    "minWorkers": 48,
    "maxWorkers": 48,
    "batchSize": 1,
    "maxBatchDelay": 100,
    "loadedAtStartup": true,
    [ "workers": [
      {
        "id": "9000",
        "startTime": "2021-12-06T14:32:58.109Z",
        "status": "READY",
        "memoryUsage": 566935552,
        "pid": 81,
        "gpu": false,
        "gpuUsage": "N/A"
      },
      ......
    ]
}
  1. 注销模型
    curl -X DELETE http://localhost:8081/models/xxx/

  2. 模型版本控制
    torch-model-archiver --model-name xxx--version 1.0 ...

METRICS 管理

最后

TorchServe是一种用于服务PyTorch模型的灵活易用的工具。提供的功能以及如何利用其实用程序通过REST端点为PyTorch模型提供服务。通过MNIST示例对其进行了测试。为了进一步了解,请深入阅读文档和官方示例

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发呆的比目鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值