Creación del entorno ARM de TVM en Docker para x86

prefacio

  Este artículo presenta cómo compilar el entorno en , x86y cómo usar el entorno para compilar y ejecutar en el entorno. También presenta cómo compilar y ejecutar en el entorno, y proporciona ejemplos detallados para la verificación, incluido el código de prueba, el código de prueba, modelar la inferencia y continuar .   Como se muestra en la siguiente figura, se muestra la información de la arquitectura :dockertvmARMRPCx86armarmrpcaclpytorcharmarmautotvm
x86cpu

inserte la descripción de la imagen aquí

  Se recomienda encarecidamente utilizar ubuntu:20.04esta versión, ¡ ubuntu:18.04esta versión glibccaerá en el foso al actualizar! ! !

1. Cargue la imagen arm-ubuntu

dockerExtraiga la imagen   del repositorio espejo arm-ubuntu:

docker pull arm64v8/ubuntu:20.04

  Dado que la arquitectura local cpues x86la arquitectura, no hay forma de ejecutar directamente armla imagen de la arquitectura, y se necesita una herramienta de terceros: QEMU
  QEMU es un simulador de emulación multiplataforma de código abierto general, que puede simular la ejecución o construcción de aplicaciones bajo una arquitectura específica, como en Una aplicación x86que se ejecuta en un sistema operativo con la misma arquitectura ARM.
  En la actualidad, qemuhay dos formas de usar la simulación: una es dockerusar [usado en este blog] en combinación, y la otra es usar el código fuenteqemu oficial para compilar e instalar el sistema correspondiente manualmente . Puede consultar este blog .iso

  Al usar dockerel armentorno construido y lscpuverlo con instrucciones cpu, cpusigue model namesiendo intelsí, archaarch64; al usar el código fuente compilado para construir el simulador, se especificará qemu-system-aarch64el modelo específico . Por ejemplo , como no he probado este método, no estoy seguro si es si o si..cpuqemu-system-aarch64 -cpu cortex-a72cpumodel nameintelarm

docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

  Este comando instalará qemu-user-staticy, una vez completada la instalación, puede ejecutar la imagen en la arquitectura normalmente x86en , pero este comando aún no se ha probado. Utilizo el siguiente método, puede consultar este artículo :   primero descargue el paquete de instalación:dockerARMubuntu
qemu-aarch64-static

inserte la descripción de la imagen aquí
  Para qemu-aarch64-staticconfigurar:

sudo cp qemu-aarch64-static /usr/bin/
sudo chmod +x /usr/bin/qemu-aarch64-static
# 注册QEMU虚拟机
docker run --rm --privileged multiarch/qemu-user-static:register

  Entonces se puede cargar normalmente arm-ubuntu:

docker run --platform linux/arm64/v8 -it -v /home/liyanpeng/arm64v8_work:/home/liyanpeng/arm64v8_work -w /home/liyanpeng arm64v8/ubuntu:20.04 bash

# uname -a
# lscpu

inserte la descripción de la imagen aquí

2. Instale la biblioteca acl

  ARMLa biblioteca informática es(Arm Compute Library, ACL) un proyecto de código abierto que proporciona núcleos acelerados para la arquitectura y . Los binarios precompilados se pueden descargar desde el software ARM :ARMCPUGPU

inserte la descripción de la imagen aquí

# 将压缩包解压到 acl_tmp 目录
tar -zxvf arm_compute-v22.08-bin-linux-arm64-v8.2-a-neon.tar.gz -C acl_tmp

  Al compilar directamente ARMbajo la arquitectura runtime, se informará un error y el directorio correspondiente debe ajustarse manualmente. Puede consultar tvmun script proporcionado por el oficial: ubuntu_download_arm_compute_lib_binaries.sh

cp -r acl_tmp/include acl/
cp -r acl_tmp/arm_compute acl/include/
cp -r acl_tmp/support acl/include/
cp -r acl_tmp/utils acl/include/
cp -r acl_tmp/lib/arm64-v8.2-a-neon acl/lib

3. Compile el tiempo de ejecución del brazo

  Antes de compilar, aún debe configurar elarm-ubuntu entorno básico, incluidos los entornos básicos como C/C++, CMakey . No entraré en detalles aquí.Python

  Modificar build/config.cmakeel archivo:

set(USE_LLVM OFF)	# line 136(default)
set(USE_ARM_COMPUTE_LIB OFF)	# line 236(default)
set(USE_ARM_COMPUTE_LIB_GRAPH_EXECUTOR "/home/liyanpeng/arm64v8_work/acl")	# acl的路径

  Compilar:

cd build
cmake ..
make runtime -j6

  La información después de una compilación exitosa es la siguiente:

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
  No olvides agregar tvmel pythonentorno:

export PYTHONPATH=$PYTHONPATH:/home/liyanpeng/arm64v8_work/tvm_work/tvm/python

  tvmVerificación de la versión:

import tvm

print(tvm.__version__)

inserte la descripción de la imagen aquí

4. Compilar en x86 y ejecutar en brazo

4.1 Cree el entorno de compilación arm en el entorno x86

  Modificar build/config.cmakeel archivo:

set(USE_LLVM ON)	# line 136
set(USE_ARM_COMPUTE_LIB ON)	# line 236
set(USE_ARM_COMPUTE_LIB_GRAPH_EXECUTOR OFF) # line 237

  Compilar:

cd build
cmake ..
make -j6

  Se compila en poco tiempo:

inserte la descripción de la imagen aquí
Una vez completada la construcción , los operadores admitidos se   pueden x86compilar en el entorno . Solo se compila y no se puede ejecutar directamente en la plataforma.armx86

4.2 Probar si x86-ubuntu y arm-ubuntu pueden hacer ping

  Esto se puede RPC(Remote Produce Call)lograr con la ayuda de 编译在x86,运行在ARM, por lo tanto, las direcciones que deben conocerse arm-ubuntu: ipInstale el paquete de herramientas de red en
  :arm-ubuntu

apt-get update
# ifconfig
apt-get install net-tools
# ping
apt-get install inetutils-ping

arm-ubuntuDirección   vista ip:

inserte la descripción de la imagen aquí

x86-ubuntuDirección   vista ip:

inserte la descripción de la imagen aquí
  Prueba x86-ubuntuy arm-ubuntusi puede pingpasar:

# x86-ubuntu
ping 172.17.0.2

inserte la descripción de la imagen aquí

# arm-ubuntu
ping 172.17.0.3

inserte la descripción de la imagen aquí

4.3 Llamada RPC

arm-ubuntuComience en el   entorno RPC:

python -m tvm.exec.rpc_server --host 0.0.0.0 --port=9090

  La información de inicio exitosa es la siguiente:

inserte la descripción de la imagen aquí

x86-ubuntuCree un archivo   en el entorno rpc_test.pycon el siguiente contenido:

# rpc_test.py
import numpy as np

import tvm
from tvm import te
from tvm import rpc
from tvm.contrib import utils, tar


n = tvm.runtime.convert(1024)
A = te.placeholder((n,), name="A")
B = te.compute((n,), lambda i: A[i] + 1.0, name="B")
s = te.create_schedule(B.op)

local_demo = False

if local_demo:
    target = "llvm"
else:
    # target = "llvm -mtriple=armv7l-linux-gnueabihf"     # Raspberry Pi 3B
    # target = "llvm -mtriple=aarch64-linux-gnu"
    # target = tvm.target.arm_cpu() # error: error adding symbols: file in wrong format
    target = "llvm -mtriple=aarch64-linux-gnu -mattr=+neon"

func = tvm.build(s, [A, B], target=target, name="add_one")

# save the lib at a local temp folder
temp = utils.tempdir()
path = temp.relpath("lib_rpc_test.tar")
func.export_library(path, tar.tar)

print("lib path: ", path)

if local_demo:
    remote = rpc.LocalSession()
else:
    # The following is my environment, change this to the IP address of your target device
    host = "172.17.0.5"		# arm-ubuntu ip
    port = 9090
    remote = rpc.connect(host, port)

remote.upload(path)
func = remote.load_module("lib_rpc_test.tar")

# create arrays on the remote device
dev = remote.cpu()
a = tvm.nd.array(np.random.uniform(size=1024).astype(A.dtype), dev)
b = tvm.nd.array(np.zeros(1024, dtype=A.dtype), dev)

# the function will run on the remote device
func(a, b)
np.testing.assert_equal(b.numpy(), a.numpy() + 1)

time_f = func.time_evaluator(func.entry_name, dev, number=10)
cost = time_f(a, b).mean
print("%g secs/op" % cost)

  El código anterior muestra una operación de suma, y ​​el resultado de la ejecución es el siguiente:

inserte la descripción de la imagen aquí

arm-ubuntuPuede ver la información de conexión   desde x86-ubuntu:

inserte la descripción de la imagen aquí

4.4 Uso de ACL

  ACLPara el uso, consulte el documento de muestratvm oficial . Aquí hay un ejemplo. El método de uso es el mismo que en la sección anterior :RPC

# acl_test.py
import tvm
from tvm import relay
from tvm import rpc
from tvm.contrib import utils, tar
from tvm.relay.op.contrib.arm_compute_lib import partition_for_arm_compute_lib

import numpy as np


data_type = "float32"
data_shape = (1, 14, 14, 512)
strides = (2, 2)
padding = (0, 0, 0, 0)
pool_size = (2, 2)
layout = "NHWC"
output_shape = (1, 7, 7, 512)

# use a single max_pool2d operator
data = relay.var('data', shape=data_shape, dtype=data_type)
out = relay.nn.max_pool2d(data, pool_size=pool_size, strides=strides, layout=layout, padding=padding)
module = tvm.IRModule.from_expr(out)

# annotate and partition the graph for ACL
module = partition_for_arm_compute_lib(module)

# build the Relay graph.
target = "llvm -mtriple=aarch64-linux-gnu -mattr=+neon"
with tvm.transform.PassContext(opt_level=3, disabled_pass=["AlterOpLayout"]):
    lib = relay.build(module, target=target)

# export the module
lib_path = './lib_acl.tar'
# cross_compile = 'aarch64-linux-gnu-c++'
# lib.export_library(lib_path, cc=cross_compile)
lib.export_library(lib_path)

# rpc
host = "172.17.0.2"		# arm-ubuntu ip
port = 9090
remote = rpc.connect(host, port)

remote.upload(lib_path)
loaded_lib = remote.load_module("lib_acl.tar")

# run Inference
# dev = tvm.cpu(0)
# loaded_lib = tvm.runtime.load_module('lib_acl.so')
dev = remote.cpu(0)
module = tvm.contrib.graph_executor.GraphModule(loaded_lib['default'](dev))
d_data = np.random.uniform(0, 1, data_shape).astype(data_type)
map_inputs = {
    
    'data': d_data}
module.set_input(**map_inputs)
module.run()

# get output
output = module.get_output(0)
print("TVM MaxPool2d[acl] output: ", output)

  El resultado de la operación es el siguiente:

inserte la descripción de la imagen aquí

  Los ejemplos anteriores solo muestran ACLcómo usar un solo Maxpool2Dejemplo básico. Si desea ver la implementación de cada operador en la red, consulte : tests/python/contrib/test_arm_compute_lib.

5. La versión arm del entorno de tiempo de ejecución y compilación de tvm

5.1 Cree la versión arm del entorno de tiempo de ejecución y compilación de tvm

  ARMLa compilación de tvmla versión y la construcción del entorno de ejecución x86son casi iguales a las de la versión.Puedes seguir este artículo: "Instalación y compilación de tvm en entorno linux y cómo vscode configura el entorno de depuración de conexión remota de tvm" para configuración, por lo que no entraré en detalles aquí. Sin embargo, cabe señalar que la versión requerida en el archivo arm-ubuntuno se encontró en , por lo que aquí hay una pequeña modificación:conda/build-environment.yamlllvmdev ==10.0.0

# conda/build-environment.yaml
# 这里将llvmdev更改为10.0.1版本
# 这样在编译时cmake会自动安装llvm
llvmdev ==10.0.1

  Según la configuración anterior, modifique build/config.cmakeel archivo nuevamente:

set(USE_LLVM ON)	# line 136
set(USE_ARM_COMPUTE_LIB ON)	# line 236
set(USE_ARM_COMPUTE_LIB_GRAPH_EXECUTOR "/home/liyanpeng/arm64v8_work/acl") # line 237

  Luego compílalo:

cd build
cmake ..
make -j6

  Pasaron dos horas. . .

inserte la descripción de la imagen aquí

  La información después de una compilación exitosa es la siguiente:

inserte la descripción de la imagen aquí

5.2 Acerca de la actualización de ubuntu 18.04 glibc cayó al hoyo

  Desafortunadamente, en esta versión y versiones anteriores, pytorch 1.7.1no se proporciona la armversión oficial pytorchAquí hay dos soluciones:
  (1)descargue una versión no oficial de la comunidad pytorch-aarch64, por ejemplo: KumaTea proporciona oficialmente una versión a partir
  (2)de la versión , puede elegir una versión superior , pero aún tengo que decir que el oficial actualmente admite dos versiones principales, y otras versiones pueden ser inestables.   La versión no oficial se selecciona aquí :pytorch 1.8.0armpytorchpytorchtvm[文章发布时]pytorch 1.7pytorch 1.4
pytorch-aarch64

pip install torch==1.7.1 torchvision==0.8.2 torchaudio==0.7.2 -f https://torch.kmtea.eu/whl/stable-cn.html

  Al ver pytorchla versión, se informa un error: [ Esta versión ImportError: /lib/aarch64-linux-gnu/libc.so.6: version "GLIBC_2.28" not foundse usó originalmente y no informará un error si cambia a esta versión, puede leer directamente la sección]ubuntu:18.04ubuntu:20.045.3

inserte la descripción de la imagen aquí
  Ver la glibcversión actual del sistema

ldd --version
# or
strings /lib/aarch64-linux-gnu/libm.so.6 | grep GLIBC_

# Ubuntu 18.04: 2.27
# Ubuntu 20.04: 2.31

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
  De acuerdo, puedes detenerte, te sugiero que te des la vuelta y saltes directamente a 5.3la subsección, de lo contrario, ¡es posible que no puedas salir del pozo más tarde! ! !

inserte la descripción de la imagen aquí

  La solución puede referirse a este blog :

# 安装依赖
apt-get install gawk
apt-get install bison

apt-get install wget

# 下载、解压并配置
wget http://ftp.gnu.org/gnu/libc/glibc-2.28.tar.gz
tar -zxvf glibc-2.28.tar.gz
cd glibc-2.28
mkdir build
cd build
../configure --prefix=/usr/local --disable-sanity-checks

# 安装
make -j6
make install

  Parte de la información de registro durante el proceso de instalación es la siguiente:

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
  No hay ningún mensaje de error que indique que la instalación se realizó correctamente.

# 查看原始的软连接
ll /lib/aarch64-linux-gnu/libc.so.6

inserte la descripción de la imagen aquí

  De acuerdo con algunos tutoriales en línea , Segmentation faultse produjeron errores, lo que resultó en el uso común ls, estas instrucciones no se pueden usar, la solución:cpclear

# export LD_PRELOAD=/lib/aarch64-linux-gnu/libc-2.27.so:/lib/aarch64-linux-gnu/ld-2.27.so
unset LD_PRELOAD
# 取消软连接
LD_PRELOAD=/lib/aarch64-linux-gnu/libc-2.27.so unlink /lib/aarch64-linux-gnu/libc.so.6
# 重新恢复
LD_PRELOAD=/lib/aarch64-linux-gnu/libc-2.27.so ln -s /lib/aarch64-linux-gnu/libc-2.27.so /lib/aarch64-linux-gnu/libc.so.6

  Crear un enlace suave:

# 复制 libc
cp /usr/local/lib/libc-2.28.so /lib/aarch64-linux-gnu/
cp /usr/local/lib/ld-2.28.so /lib/aarch64-linux-gnu/

cd /lib/aarch64-linux-gnu/
# ll ld-linux-aarch64.so.1
# ll libc.so.6

ln -sf /lib/aarch64-linux-gnu/libc-2.28.so /lib/aarch64-linux-gnu/libm.so.6
# 无效, 仍然是2.27版本

  Comparando aarch64-linux-gnuel directorio con glibc-2.28el directorio de instalación, encontré que muchas bibliotecas tienen el mismo nombre, pero el número de versión es diferente ¿Hay que reemplazarlas? ? ?

inserte la descripción de la imagen aquí

5.3 Verificar que la instalación fue exitosa

  En este punto, si ubuntu 18.04la glibcactualización no es exitosa, entonces utilícela ubuntu 20.04, se hace lo siguiente arm-ubuntu 20.04en , verifique pytorchla versión:

inserte la descripción de la imagen aquí

  pytorchModelo de validación:

# from_pytorch.py

import tvm
from tvm import relay
from tvm.contrib.download import download_testdata
import numpy as np

import torch
import torchvision

######################################################################
# Load a pretrained PyTorch model
pth_file = 'resnet18-f37072fd.pth'
model = torchvision.models.resnet18()
ckpt = torch.load(pth_file)
model.load_state_dict(ckpt)
model = model.eval()

# We grab the TorchScripted model via tracing
input_shape = [1, 3, 224, 224]
input_data = torch.randn(input_shape)
scripted_model = torch.jit.trace(model, input_data).eval()

######################################################################
# Load a test image
from PIL import Image

img_path = 'cat.png'
img = Image.open(img_path).resize((224, 224))

# Preprocess the image and convert to tensor
from torchvision import transforms

my_preprocess = transforms.Compose(
    [
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
    ]
)
img = my_preprocess(img)
img = np.expand_dims(img, 0)

######################################################################
# Import the graph to Relay
input_name = "input0"
shape_list = [(input_name, img.shape)]
mod, params = relay.frontend.from_pytorch(scripted_model, shape_list)

######################################################################
# Relay Build
target = tvm.target.arm_cpu()
dev = tvm.cpu(0)
with tvm.transform.PassContext(opt_level=3):
    lib = relay.build(mod, target=target, params=params)

######################################################################
# Execute the portable graph on TVM
from tvm.contrib import graph_executor

dtype = "float32"
m = graph_executor.GraphModule(lib["default"](dev))
# Set inputs
m.set_input(input_name, tvm.nd.array(img.astype(dtype)))
# Execute
m.run()
# Get outputs
tvm_output = m.get_output(0)

#####################################################################
# Look up synset name
synset_path = 'imagenet_synsets.txt'
with open(synset_path) as f:
    synsets = f.readlines()

synsets = [x.strip() for x in synsets]
splits = [line.split(" ") for line in synsets]
key_to_classname = {
    
    spl[0]: " ".join(spl[1:]) for spl in splits}

class_path = 'imagenet_classes.txt'
with open(class_path) as f:
    class_id_to_key = f.readlines()

class_id_to_key = [x.strip() for x in class_id_to_key]

# Get top-1 result for TVM
top1_tvm = np.argmax(tvm_output.numpy()[0])
tvm_class_key = class_id_to_key[top1_tvm]

# Convert input to PyTorch variable and get PyTorch result for comparison
with torch.no_grad():
    torch_img = torch.from_numpy(img)
    output = model(torch_img)

    # Get top-1 result for PyTorch
    top1_torch = np.argmax(output.numpy())
    torch_class_key = class_id_to_key[top1_torch]

print("Relay top-1 id: {}, class name: {}".format(top1_tvm, key_to_classname[tvm_class_key]))
print("Torch top-1 id: {}, class name: {}".format(top1_torch, key_to_classname[torch_class_key]))

  Los resultados de la verificación son los siguientes [la velocidad no es un poco lenta]:

inserte la descripción de la imagen aquí
  autotvmTambién oksi:

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

  Pero siento que hay algunos problemas. Solo hay uno x86arriba , pero de hecho hay uno arriba . Después de verificar, esto está incluido , y todavía está incluido . Es muy extraño. Este problema aún no se ha resuelto:task13[resnet18]arm2626taskarmx86

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

conclusión

  Este artículo es un intento de construir un entorno en . x86Dado que el servidor está usando , quería simular una versión que sea la misma que el servidor localmente. Como resultado, me encontré con el foso de la actualización , que aún no se ha completado. Sin embargo, el entorno se incorporó con éxito y se puede compilar y ejecutar. Todavía aprendiendo, ¡bienvenido a comunicarse en el área de comentarios! ! !dockertvmarmubuntu 18.04ubuntu 18.04glibcx86dockertvmarm
  tvm

Supongo que te gusta

Origin blog.csdn.net/qq_42730750/article/details/127853605
Recomendado
Clasificación