有奖捉虫:办公协同&微信生态&物联网文档专题 HOT

TI 内置推理框架 tiinfer 介绍

tiinfer 是腾讯云 TI-ONE 模型服务使用的推理框架。基于mosec 提供的基础能力,tiinfer 提供了以下功能:
高性能的 HTTP 服务
自由编排计算过程

安装

tiinfer 支持 Gnu/Linux 系统,依赖 python 3.8 及以上版本。需要使用以下命令安装 PyPI 包:
pip install -i https://mirrors.cloud.tencent.com/pypi/simple tiinfer

HelloWorld!

以下代码展示了一个最简单的 HelloWorld 示例:
from typing import Dict
import tiinfer

### 日志相关代码 ###
# 增加以下代码,才能将框架记录的请求日志打印到终端
import logging
import sys
logger_formatter = "%(asctime)s %(levelname)s    %(module)s:%(lineno)d    %(message)s"
logging.basicConfig(stream=sys.stdout, format=logger_formatter, level=logging.DEBUG)
### 日志相关代码 ###

# tiinfer支持原生的mosec.Worker
class HelloWorld(mosec.Worker):
    def forward(self, req: Dict) -> Dict:
        return {"hello": f"world. raw req is {req}"}

# 启动两个进程同时处理请求
tiinfer.append_worker(HelloWorld, num=2)
将上述代码保存为 model_service.py 文件。执行以下命令可以拉起整个推理服务:
TI_MODEL_DIR=`pwd` python3 -m tiinfer --timeout 30000
模型拉起后,会在8501端口启动一个 HTTP 服务,请求地址为/v1/models/m:predict。用以下命令可以访问:
> curl -X POST -d '{"key": "values"}' http://127.0.0.1:8501/v1/models/m:predict
{
"hello": "world. raw is {'key': 'values'}"
}

tiinfer的架构




为了降低模型的上线难度,平台提供的 tiinfer 镜像已经打包了诸多流行的推理引擎,会读取客户提供的 model_service.py 文件,自动拉起 HTTP 服务。具体的服务启动过程如下所示:
1. 使用 pip 安装 requirements.txt 文件中填写的 python 依赖
2. 读取 model_service.py 并实例化几个处理过程的进程
3. 启动 HTTP 服务




自定义推理Worker

每一个处理 Worker 需要继承 mosec.Worker 来定义处理逻辑:
1. 可选:覆写__init__函数,进行一些初始化工作。只在必要的时候覆写,注意必须先调用super.__init__()以完成父类初始化。
2. 必须:自定义 forward 函数,提供处理能力。通常在 forward 中完成数据的处理工作。
class Worker:
def __init__(self)
def forward(data)
在完成定义推理 Worker 后,需要调用tiinfer.append_worker()函数来进行编排:
def append_worker(
    worker: Type[mosec.Worker],
    num: int = 1,
    start_method: str = "spawn",
    env: Union[None, List[Dict[str, str]]] = None,
) -> None
"""
worker: 继承mosec.Worker并实现了forward方法的处理Worker
num: 并行计算的进程数(>=1)
start_method: 进程启动方法("spawn" 或 "fork")
env: 进程启动前的一些环境变量
"""
完整的推理过程中,一般需要对输入进行预处理,再输入到模型进行推理,并对推理结果进行后处理,最终才返回给调用方。推理过程通常是在 GPU 上完成的,而预处理、后处理往往是 CPU 计算甚至是一些 IO 处理。如果将预处理、后处理同推理放在同一个进程中处理,启动的进程数目主要受限于 GPU 的显存及算力。此时,将预处理、后处理同推理进程剥离,能够充分利用 CPU 的处理能力,参考以下代码片段:
import tiinfer
from mosec import Worker
from typing import Dict, Any


class MyPreprocess(Worker):
def forward(self, data: Dict) -> Any:
# 输入是json转化来的Dict,进行一些必要的预处理

class MyPredict(Worker):
def __int__(self):
super.__init__()
# 读取并加载模型
def forward(self, data: Any) -> Any:
# 输入是预处理的结果,通过调用模型推理后,得到推理结果


class MyPostprocess(Worker):
def forward(self, data: Any) -> Dict:
# 输入为推理结果,通过一些后处理手段转化为Dict作为Json返回给调用方

# 编排处理过程: 1x预处理Worker -> 2x推理Worker -> 1x后处理Worker
tiinfer.append_worker(MyPreprocess, 1)
tiinfer.append_worker(MyPredict, 2)
tiinfer.append_worker(MyPostprocess, 1)

model_service.py 简介

对 model_service.py 的要求

参考上文 tiinfer 介绍中的相关章节。

分类模型推理脚本 Pytorch 实现示例


import logging
import os
import time
from typing import Dict, List
from urllib.request import urlretrieve

import cv2  # type: ignore
import numpy as np  # type: ignore

import torch  # type: ignore

import tiinfer
import tiinfer.utils
import mosec


### 日志相关代码 ###
# 增加以下代码,才能将框架记录的请求日志打印到终端
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
    "%(asctime)s - %(process)d - %(levelname)s - %(filename)s:%(lineno)s - %(message)s"
)
sh = logging.StreamHandler()
sh.setFormatter(formatter)
logger.addHandler(sh)
### 日志相关代码 ###

# 预处理过程对输入的base64字符串进行解码,并按照模型要求做一些缩放操作
class Pre(mosec.Worker):
    def forward(self, req: Dict) -> cv2.Mat:
        # 预处理的输入数据是json转化而来的Dict
        img_base64_bytes = req["image"]
        img = tiinfer.utils.image_to_cv2_mat(img_base64_bytes)
        # bgr -> rgb
        img = img[:, :, ::-1]
        # 对图片做一些预处理
        img = cv2.resize(img, (256, 256))
        crop_img = (
            img[16 : 16 + 224, 16 : 16 + 224].astype(np.float32) / 255
        )  # center crop
        crop_img -= [0.485, 0.456, 0.406]
        crop_img /= [0.229, 0.224, 0.225]
        crop_img = np.transpose(crop_img, (2, 0, 1))
        return crop_img

# 加载模型,并对预处理后的结果做推理操作,并转换为最终结果传给调用方·
class Infer(mosec.Worker):
    def __init__(self) -> None:
        super().__init__()
        # 获取当前所在目录,需要据此加载模型
        self.root_path = tiinfer.TI_MODEL_DIR
# 优先使用GPU
self.device = (
            torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
        )
### 非加速模型的加载 开始###
        # 模型存在model目录下面
        model_file = os.path.join(self.root_path, "model/resNet50.pt")
        ### 非加速模型的加载 结束###
### 加速模型的加载 开始###
# #加速模型需要额外导入tiacc_inference
# import tiacc_inference
# model_file = os.path.join(self.root_path, "model/tiacc.pt")
### 加速模型的加载 结束###
        # 加载模型
        self.model = torch.jit.load(model_file)
        self.model.eval()

        # 分类需要知道最终的类别
        self.categories = load_categories()

    def forward(self, img: cv2.Mat) -> Dict:
        with torch.no_grad():
            batch = torch.stack(
                [torch.tensor(arr, device=self.device) for arr in [img]]
            )
            pred_results = self.model(batch)
            prob = torch.nn.functional.softmax(pred_results, dim=1)
            top1_prob, top1_catid = torch.topk(prob, 1)
            return [
                {
                    "confidence": top1_prob[i].tolist()[0],
                    "pred": self.categories[top1_catid[i].tolist()[0]],
                }
                for i in range(top1_prob.size(0))
            ][0]


# 从标签文件中读取标签id对应的类别信息
def load_categories() -> List[str]:
    logging.info("loading categories file...")
    local_filename = "imagenet_classes.txt"
    if not os.path.exists("imagenet_classes.txt"):
        local_filename, _ = urlretrieve(
            "https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt"
        )
    with open(local_filename, encoding="utf8") as file:
        return list(map(lambda x: x.strip(), file.readlines()))

# 编排处理过程:2个进程进行预处理 -> 1个进程进行推理
tiinfer.append_worker(Pre, 2)
tiinfer.append_worker(Infer, 1)

Demo 下载

平台提供了针对各类模型的 Demo,下载地址详见下表:
格式
场景
下载地址
TorchScript
分类
TorchScript
检测
TorchScript
NLP
TorchScript
OCR
Detectron2
检测
MMDetection
检测
HuggingFace
NLP
SavedModel
NLP
SavedModel
推荐
FrozenGraph
NLP
ONNX
检测

TI-ACC 推理加速函数介绍

tiacc_inference.load()

TI-ONE 的 Detectron2 或者 MMDetection 格式优化后的模型,需要使用 tiacc-inference.load() 函数对模型进行加载,其他格式无需使用tiacc_inference 的 load,使用原生格式 load 即可。
MMDetection 原生示例:
from mmdet.apis import init_detector
model = init_detector(config, checkpoint, device=device)
优化后使用示例:
import tiacc_inference
model = tiacc_inference.load('tiacc.pt') #tiacc.pt是模型优化后生成的新模型
Detectro2(对于 Detectron2 导出的 PyTorch 模型) 原生示例:
import torch
model = torch.load(checkpoint) #.pth模型文件
优化后使用示例:
import tiacc_inference
model = tiacc_inference.load('tiacc.pt') #tiacc.pt是模型优化后生成的新模型
Detectro2(对于通过 Detectron2.modeling.build_model 构造的模型) 原生示例:
from detectron2.config import get_cfg
from detectron2.modeling import build_model
cfg = get_cfg()
cfg.MODEL.DEVICE = device
cfg.MODEL.WEIGHTS = checkpoint
model = build_model(cfg)
优化后使用示例:
import tiacc_inference
model = tiacc_inference.load('tiacc.pt') #tiacc.pt是模型优化后生成的新模型