InfiniTensor/USER_GUIDE_CN.md

8.6 KiB
Raw Permalink Blame History

使用指南

目录

项目简介

本项目是深度学习领域的一个编译器集合,本项目旨在缩小深度学习应用与后端硬件之间的鸿沟。本项目通过使用编译器超优化技术,对神经网络模型进行优化,从而获得更好的性能。同时,本项目与深度学习框架相互配合,为不同的硬件后端提供端倒端的编译,方便用户迁移部署。

项目设计

本项目的设计是前后端解耦合的,主要有三个模块,分别为:

  • Runtime 模块:该模式负责对不同的加速卡后端进行包装与支持,支撑后端运行。另外提供统一的向上接口,方便上层建设。
  • Compiler 模块:该模式负责对神经网络模型进行优化变换,获得更加高效的等价模型。
  • Interface 模块:该模式负责给用户提供编程与交互的接口,方便用户使用本系统。

使用方法

项目管理功能已写到 Makefile,支持下列功能:

  • 编译项目:make/make build
  • 清理生成文件:make clean
  • 安装 python 库:make install-python
  • 测试 c++ 后端:make test-cpp
  • 测试 python 前端:make test-onnx

并使用下列环境变量传递选项参数:

  • TYPE:编译模式(debug/release),默认值为 release
  • CUDA:是否编译 CUDA 后端,默认为 OFFON 打开
  • BANG:是否编译寒武纪后端,默认为 OFFON 打开
  • BACKTRACE:是否启用栈回溯,默认为 ONOFF 关闭,建议调试时打开
  • TEST:是否编译 googletest,默认为 ONOFF 关闭,只有 test-cpp 时必要

python 前端应用指南

make install-python 会将项目的 python 前端以 pyinfinitensor 为名字安装到系统目录,可以直接 import pyinfinitensor 来使用。现阶段,项目的主要用法是从 onnx 导入模型进行优化,然后可以再导出优化后的模型到 onnx也可以直接运行推理。

导入 onnx 模型

支持的模型:

import onnx
from pyinfinitensor.onnx import OnnxStub
from pyinfinitensor import backend

stub = OnnxStub(onnx.load("model_file"), backend.cpu_runtime())

onnx.load 是 onnx 提供的加载函数,将 onnx 文件读取为保存在内存中的 onnx 模型。

OnnxStub 是 onnx 模型在项目中的表示,通过构造这个对象,将 onnx 模型导入到项目中。其构造器的第一个参数是 onnx 模型文件;第二个参数是模型运行的后端运行时,可以是 backend.cpu_runtime()backend.cuda_runtime()backend.bang_runtime()

构造出的 stub 对象可以用于操作项目中的模型和运行时。

优化

TODO

导出 onnx 模型

优化后的模型可以导出成 onnx 文件提供给其他运行时。

with open("optimized.onnx", "wb") as f:
    f.write(stub.to_onnx("optimized").SerializeToString())

stub.to_onnx(<name>) 将模型转换为 onnx 模型对象,<name> 将填写到 onnx 模型的 name 字段。序列化到文件的代码见官方示例

要可视化检查导出的模型文件,可以利用 onnx 提供的功能将所有的张量的形状推理出来再导出:

from onnx.shape_inference import infer_shapes

with open("optimized.onnx", "wb") as f:
    f.write(infer_shapes(stub.to_onnx("optimized")).SerializeToString())

然后用 Netron 绘制计算图。

执行推理

也可以使用项目的运行时执行推理。

第一步是将数据传入计算图。OnnxStub.inputs 是一个 Dict[str, Tensor],保存着模型的所有输入的名字和对象。可以用 items() 来遍历。

这个代码片段显示了如何打印出模型所有输入张量的名字、形状和对象指针:

for name, tensor in stub.inputs.items():
    print(name, tensor.shape(), tensor)

对于 resnet18-v2-7.onnx,会打印出:

data [1, 3, 224, 224] <backend.Tensor object at 0x7efeb828e3b0>

当然,地址是随机的。这个输出表明需要输入一个名为 “data”形为 1×3×224×224 的数据。通常来说,这表示一张 224×224 的 rgb 图片。而这个模型是一个 1000 分类的图像分类模型。

为了方便,这里我们向模型传入一个随机的数据。

import numpy

stub.init()
for name, tensor in stub.inputs.items():
    print(name, tensor.shape(), tensor)
    input = numpy.random.random(tensor.shape()).astype(numpy.float32)
    tensor.copyin_float(input.flatten().tolist())

stub.init() 为所有张量分配空间。空间是预分配的,所以不支持动态 size 的模型。

tensor.copyin_float(<data>) 向张量传入数据。其参数必须是一个 List[float],即压平的数据。类似的函数还有 copyin_int32(<data>)copyin_int64(<data>)

然后,调用 stub.run() 执行推理:

stub.run()

最后,将结果拷贝出来,传入类似:

stub.init()
for name, tensor in stub.outputs.items():
    print(name, tensor.shape(), tensor)
    print(tensor.copyout_float())

样例代码

您可以参照./example/Resnet/resnet.py的样例代码进行了解,并尝试运行。在这个文件中,我们使用了 Pytorch 构建了 resnet 网络。您可以查阅该脚本使用方式:

python resnet.py -h

在样例代码中,我们对定义的网络进行了序列化操作,并存储为模型文件。之后加载该模型文件,并转换为本项目的模型进行优化操作,再进行推理。您可以关注一下代码中 242 行之后的代码。请注意,您可以按照您的需求来进行操作,通常来说,您所需要撰写的代码就是加载模型,转换为本项目的模型进行优化,推理运行。

技术支持

如若您遇到了本项目的问题,请联系我们的技术支持团队

测试

除了单元测试 make test-cppmake test-onnx 之外,还可以用其他方式来测试单个模型导入导出和优化的正确性。

这个脚本利用 onnxruntime 来测试导出的模型是否与导入的模型等价:

import onnx
import numpy
import sys
from onnx import ModelProto, ValueInfoProto
from pyinfinitensor.onnx import OnnxStub
from pyinfinitensor import backend
from onnxruntime import InferenceSession


def infer(model: ModelProto, input) -> dict:
    collection = set()
    for node in model.graph.node:
        for output in node.output:
            collection.add(output)
    model.graph.output.extend([ValueInfoProto(name=x) for x in collection])
    session = InferenceSession(model.SerializeToString())
    i = session.get_inputs()[0].name
    return dict(
        zip(
            [x.name for x in session.get_outputs()],
            [x.flatten() for x in session.run(None, {i: input})],
        )
    )


model0 = onnx.load(sys.argv[1])
model1 = OnnxStub(model0, backend.cpu_runtime()).to_onnx("new")

input_shape = [x.dim_value for x in model1.graph.input[0].type.tensor_type.shape.dim]
input = numpy.random.random(input_shape).astype(numpy.float32)

output0 = infer(model0, input)[model0.graph.output[0].name]
output1 = infer(model1, input)[model1.graph.output[0].name]

print("error =", sum((output1 - output0) ** 2) / len(output0))

要运行脚本,先安装 onnxruntime

pip install onnxruntime

打印出的 error = ... 是两个模型输出张量的均方误差。对于不同的模型,这个误差最小为 0最大不超过 1e-9。