输入输出内存拷贝优化

MegEngine Lite 中的内存拷贝优化主要指输入输出 Tensor 内存拷贝的优化,模型内部固有的内存拷贝优化不能够被优化,主要有下面几种情况:

  • device IO 优化:输入数据本来就不是 CPU 端的内存,如:是一段 CUDA 的内存或者一段 OpenCL 的内存,希望模型推理直接使用这段内存作为输入,避免将其拷贝到 CPU 端,然后再在模型内部从 CPU 拷贝到设备上,节省两次内存拷贝。

  • 输入输出零拷贝:希望模型的推理结果保存在用户提供的内存中,避免将数据保存在 MegEngine 自己申请的内存中,然后再将内存拷贝到用户指定的内存中。

Device IO 优化

MegEngine Lite 支持模型的输入输出配置,用户可以根据实际情况灵活配置。主要方式是在创建 Network 时候配置其 IO 属性, 下面的 example 是指定模型名字为 “data” 的 Tensor 的内存为 CUDA 设备上内存,输出名字为 “TRUE_DIV” 的 Tensor 数据 保存在 CUDA 设备上。

std::string network_path = args.model_path;
std::string input_path = args.input_path;
//! config the network running in CUDA device
lite::Config config{LiteDeviceType::LITE_CUDA};
//! set NetworkIO include input and output
NetworkIO network_io;
std::string input_name = "data";
std::string output_name = "TRUE_DIV";
bool is_host = false;
IO device_input{input_name, is_host};
IO device_output{output_name, is_host};
network_io.inputs.push_back(device_input);
network_io.outputs.push_back(device_output);

//! create and load the network
std::shared_ptr<Network> network = std::make_shared<Network>(config, network_io);
network->load_model(network_path);

std::shared_ptr<Tensor> input_tensor_device = network->get_input_tensor(0);
Layout input_layout = input_tensor_device->get_layout();

//! malloc the device memory
auto tensor_device = Tensor(LiteDeviceType::LITE_CUDA, input_layout);

//! copy to the device memory
input_tensor_device->copy_from(tensor_device);

//! forward
network->forward();
network->wait();

//! output_tensor_device is in device
std::shared_ptr<Tensor> output_tensor_device = network->get_io_tensor(output_name);
from megenginelite import *
import numpy as np
import os


model_path = ...
# construct LiteOption
net_config = LiteConfig(device_type=LiteDeviceType.LITE_CUDA)

# set the input tensor "data" memory is not in host, but in device
io_input = LiteIO("data", is_host=False)
# set the output tensor "TRUE_DIV" memory is in device
io_output = LiteIO("TRUE_DIV", is_host=False)
# constuct LiteIO with LiteIO
ios = LiteNetworkIO(inputs=[io_input], outputs=[io_output])

network = LiteNetwork(config=net_config, io=ios)
network.load(model_path)

dev_input_tensor = network.get_io_tensor("data")
# read input to input_data
dev_input_data = LiteTensor(layout=dev_input_tensor.layout, device_type=LiteDeviceType.LITE_CUDA)
# fill dev_input_data with device memory
#......

# set device input data to input_tensor of the network without copy
dev_input_tensor.share_memory_with(dev_input_data)

# inference
network.forward()
network.wait()

output_tensor = network.get_io_tensor("TRUE_DIV")
output_data = output_tensor.to_numpy()
print('output max={}, sum={}'.format(output_data.max(), output_data.sum()))

上面分别是 C++ 和 Python 使用 MegEngine Lite 配置 IO 为 device 上输入输出的示例,C++ 主要的配置为:

NetworkIO network_io;
std::string input_name = "data";
std::string output_name = "TRUE_DIV";
bool is_host = false;
IO device_input{input_name, is_host};
IO device_output{output_name, is_host};
network_io.inputs.push_back(device_input);
network_io.outputs.push_back(device_output);
//! create and load the network
std::shared_ptr<Network> network = std::make_shared<Network>(config, network_io);
# constuct LiteIO, is_host=False means the input tensor will use device memory
ios = LiteNetworkIO()
# set the input tensor "data" memory is not in host, but in device
ios.add_input(LiteIO("data", is_host=False))
# set the output tensor "TRUE_DIV" memory is in device
ios.add_output(LiteIO("TRUE_DIV", is_host=False))
network = LiteNetwork(config=net_config, io=ios)

Network 的 IO 中 input 名字为 “data” 和 output 名字为 “TRUE_DIV” 的 IO 的 is_host 属性为 false,host 默认指 CPU 端, 为 flase 则表述输入或者输出的内存为设备端。

输入输出拷贝优化

Device 上输入输出优化

Device 上进行模型推理除了 Device IO 优化 的情况外,都需要将输入从 CPU 拷贝到 Device 上,然后执行模型推理,执行完成之后,将 输出数据拷贝到 CPU 上,这是在 Device 上执行推理不可缺少的情况,(除了 Device IO 优化 )。但是我们可以优化输入从真实数据拷贝 到模型的 CPU 输入数据和输出从 CPU 再拷贝到用户指定的内存中这些内存拷贝操作。

Config config;
std::string model_path = ...;
std::string input_name = "data";
std::string output_name = "TRUE_DIV";

std::shared_ptr<Network> network = std::make_shared<Network>(config);

network->load_model(model_path);
std::shared_ptr<Tensor> input_tensor = network->get_io_tensor(input_name);

auto src_ptr = malloc(input_tensor->get_tensor_total_size_in_byte());
auto src_layout = input_tensor->get_layout();
input_tensor->reset(src_ptr, src_layout);

std::shared_ptr<Tensor> output_tensor = network->get_io_tensor(output_name);

void* out_data = malloc(output_tensor->get_tensor_total_size_in_byte());
output_tensor->reset(out_data, output_tensor->get_layout());

network->forward();
network->wait();

delete src_ptr;
delete out_data;
from megenginelite import *
import numpy as np
import os

model_path = "./shufflenet.mge"
# construct LiteOption
net_config = LiteConfig()

network = LiteNetwork(config=net_config)
network.load(model_path)

input_tensor = network.get_io_tensor("data")
# read input to input_data
input_data = LiteTensor(layout=input_tensor.layout)
# fill input_data with device data

# set device input data to input_tensor of the network without copy
input_tensor.share_memory_with(input_data)

output_tensor = network.get_io_tensor(network.get_output_name(0))
out_array = np.zeros(output_tensor.layout.shapes, output_tensor.layout.dtype)

output_tensor.set_data_by_share(out_array)

# inference
network.forward()
network.wait()

print('output max={}, sum={}'.format(out_array.max(), out_array.sum()))

该优化主要是使用 LiteTensor 的 reset 或者 memory share 的接口,将用户的内存共享到 Network 中的输入输出 LiteTensor 中。

CPU 上输入输出零拷贝

输入输出零拷贝,指用户的输入数据可以不用拷贝到 MegEngine Lite 中,模型推理完成的输出数据可以直接写到用户指定的内存中, 减少将输出数据拷贝到用户的内存中的过程,用户的内存 MegEngine Lite 不会进行管理,用户需要确保 内存的生命周期大于模型推理的生命周期

实现这个功能主要将上面 Device 上输入输出优化 优化中配置 network 时,使能 force_output_use_user_specified_memory 选项:

  • 设置 force_output_use_user_specified_memory 为 True。

  • 模型运行之前通过 LiteTensor 的 reset 接口设置设置自己管理的内存到输入输出 Tensor 中,在 python 中可以调用 set_data_by_share 达到相同的功能。

警告

  • 使用 force_output_use_user_specified_memory 这个参数时,只能获取模型计算的输出 Tensor 的结果,获取中间 Tensor 的计算结果是不被允许的。

  • 模型必须是静态模型,输出 LiteTensor 的 layout 需要在模型载入之后就能够被推导出来。

  • force_output_use_user_specified_memory 参数目前只在 CPU 使用,其他 Device 上不能使用。