编程模型和语言层
定义了一个自定义的PyTorch操作,并将其导出为ONNX格式。
- 自定义函数:MyAddFunction 继承自 torch.autograd.Function,包含两个主要静态方法:forward(ctx, a, b):调用外部库函数 my_lib.my_add 来计算两个张量 a 和 b 的加法。symbolic(g, a, b):为ONNX定义操作的符号表示,创建一个图节点用于将 a 乘以 2,然后加上 b。
- MyAdd 是 torch.nn.Module 的子类,使用 MyAddFunction。在其 forward 方法中调用 my_add。
- 生成一个形状为 (1, 3, 10, 10) 的随机输入张量。
- 使用 torch.onnx.export 将模型导出为名为 my_add.onnx 的ONNX文件,传入相同的输入张量作为两个参数。
- 加载ONNX模型,并使用相同的输入张量进行推理,输出结果存储在 ort_output 中。
- 通过断言检查PyTorch模型的输出与ONNX模型的输出是否接近,确保导出和操作的正确性。
import torch
import my_lib
class MyAddFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, a, b):
return my_lib.my_add(a, b)
@staticmethod
def symbolic(g, a, b):
two = g.op("Constant", value_t=torch.tensor([2]))
a = g.op('Mul', a, two)
return g.op('Add', a, b)
my_add = MyAddFunction.apply
class MyAdd(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, a, b):
return my_add(a, b)
model = MyAdd()
input = torch.rand(1, 3, 10, 10)
torch.onnx.export(model, (input, input), 'my_add.onnx')
torch_output = model(input, input).detach().numpy()
import onnxruntime
import numpy as np
sess = onnxruntime.InferenceSession('my_add.onnx')
ort_output = sess.run(None, {'a': input.numpy(), 'b': input.numpy()})[0]
assert np.allclose(torch_output, ort_output)
演示如何使用 PyTorch 和 ONNX Runtime 在不同设备(如 CPU 或 GPU)上进行推理。具体来说,它通过以下步骤展示了如何使用 ONNX Runtime 来运行一个简单的加法模型(两个张量相加),并使用不同的方式将数据传递到设备上进行计算。
- 模型定义了一个简单的加法运算,它接受两个输入张量 x 和 y,返回它们的加法结果。创建并导出模型为 ONNX 格式,其中 x 和 y 的大小是动态的。
- 根据当前设备是否支持 CUDA(运行在NVIDIA GPU),创建一个 ONNX Runtime 会话,可以在 CPU 或 GPU 上运行模型。
- 在 CPU 上运行模型,输入和输出都是 NumPy 数组。使用 PyTorch 张量运行模型,在设备上使用 PyTorch 张量进行推理。
- 在main函数中,第一个调用 run(),输入 x=[1.0, 2.0, 3.0],y=[4.0, 5.0, 6.0],输出 z=[5.0, 7.0, 9.0]。 第二个调用 run_with_data_on_device(),输入 x=[1.0, 2.0, 3.0, 4.0, 5.0] 和 y=[1.0, 2.0, 3.0, 4.0, 5.0],输出 z=[2.0, 4.0, 6.0, 8.0, 10.0]。 第三个调用 run_with_torch_tensors_on_device(),生成两个随机的 PyTorch 张量,并返回加法结果,如 [0.7023, 1.3127, 1.7289, 0.3982, 0.8386]。 最后一个调用也是 run_with_torch_tensors_on_device(),但这次使用 torch.int64 类型张量,输入 x=ones(5) 和 y=zeros(5),输出 [1, 1, 1, 1, 1]
import numpy as np
import torch
import onnxruntime
MODEL_FILE = '.model.onnx'
DEVICE_NAME = 'cuda' if torch.cuda.is_available() else 'cpu'
DEVICE_INDEX = 0 # Replace this with the index of the device you want to run on
DEVICE=f'{DEVICE_NAME}:{DEVICE_INDEX}'
# A simple model to calculate addition of two tensors
def model():
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
def forward(self, x, y):
return x.add(y)
return Model()
# Create an instance of the model and export it to ONNX graph format, with dynamic size for the data
def create_model(type: torch.dtype = torch.float32):
sample_x = torch.ones(3, dtype=type)
sample_y = torch.zeros(3, dtype=type)
torch.onnx.export(model(), (sample_x, sample_y), MODEL_FILE, input_names=["x", "y"], output_names=["z"],
dynamic_axes={"x": {0 : "array_length_x"}, "y": {0: "array_length_y"}})
# Create an ONNX Runtime session with the provided model
def create_session(model: str) -> onnxruntime.InferenceSession:
providers = ['CPUExecutionProvider']
if torch.cuda.is_available():
providers.insert(0, 'CUDAExecutionProvider')
return onnxruntime.InferenceSession(model, providers=providers)
# Run the model on CPU consuming and producing numpy arrays
def run(x: np.array, y: np.array) -> np.array:
session = create_session(MODEL_FILE)
z = session.run(["z"], {"x": x, "y": y})
return z[0]
# Run the model on device consuming and producing ORTValues
def run_with_data_on_device(x: np.array, y: np.array) -> onnxruntime.OrtValue:
session = create_session(MODEL_FILE)
x_ortvalue = onnxruntime.OrtValue.ortvalue_from_numpy(x, DEVICE_NAME, DEVICE_INDEX)
y_ortvalue = onnxruntime.OrtValue.ortvalue_from_numpy(y, DEVICE_NAME, DEVICE_INDEX)
io_binding = session.io_binding()
io_binding.bind_input(name='x', device_type=x_ortvalue.device_name(), device_id=0, element_type=x.dtype, shape=x_ortvalue.shape(), buffer_ptr=x_ortvalue.data_ptr())
io_binding.bind_input(name='y', device_type=y_ortvalue.device_name(), device_id=0, element_type=y.dtype, shape=y_ortvalue.shape(), buffer_ptr=y_ortvalue.data_ptr())
io_binding.bind_output(name='z', device_type=DEVICE_NAME, device_id=DEVICE_INDEX, element_type=x.dtype, shape=x_ortvalue.shape())
session.run_with_iobinding(io_binding)
z = io_binding.get_outputs()
return z[0]
# Run the model on device consuming and producing native PyTorch tensors
def run_with_torch_tensors_on_device(x: torch.Tensor, y: torch.Tensor, np_type: np.dtype = np.float32, torch_type: torch.dtype = torch.float32) -> torch.Tensor:
session = create_session(MODEL_FILE)
binding = session.io_binding()
x_tensor = x.contiguous()
y_tensor = y.contiguous()
binding.bind_input(
name='x',
device_type=DEVICE_NAME,
device_id=DEVICE_INDEX,
element_type=np_type,
shape=tuple(x_tensor.shape),
buffer_ptr=x_tensor.data_ptr(),
)
binding.bind_input(
name='y',
device_type=DEVICE_NAME,
device_id=DEVICE_INDEX,
element_type=np_type,
shape=tuple(y_tensor.shape),
buffer_ptr=y_tensor.data_ptr(),
)
## Allocate the PyTorch tensor for the model output
z_tensor = torch.empty(x_tensor.shape, dtype=torch_type, device=DEVICE).contiguous()
binding.bind_output(
name='z',
device_type=DEVICE_NAME,
device_id=DEVICE_INDEX,
element_type=np_type,
shape=tuple(z_tensor.shape),
buffer_ptr=z_tensor.data_ptr(),
)
session.run_with_iobinding(binding)
return z_tensor
def main():
create_model()
print(run(x=np.float32([1.0, 2.0, 3.0]),y=np.float32([4.0, 5.0, 6.0])))
# [array([5., 7., 9.], dtype=float32)]
print(run_with_data_on_device(x=np.float32([1.0, 2.0, 3.0, 4.0, 5.0]), y=np.float32([1.0, 2.0, 3.0, 4.0, 5.0])).numpy())
# [ 2. 4. 6. 8. 10.]
print(run_with_torch_tensors_on_device(torch.rand(5).to(DEVICE), torch.rand(5).to(DEVICE)))
# tensor([0.7023, 1.3127, 1.7289, 0.3982, 0.8386])
create_model(torch.int64)
print(run_with_torch_tensors_on_device(torch.ones(5, dtype=torch.int64).to(DEVICE), torch.zeros(5, dtype=torch.int64).to(DEVICE), np_type=np.int64, torch_type=torch.int64))
# tensor([1, 1, 1, 1, 1])
if __name__ == "__main__":
main()
可以使用C++和ONNX Runtime来实现类似的加法操作。以下是一个简单的C++示例,它演示了如何使用ONNX Runtime来加载一个简单的加法模型,并运行推理。 使用PyTorch创建一个简单的加法模型并将其导出为ONNX格式:
import torch
class SimpleAddModel(torch.nn.Module):
def forward(self, x, y):
return x + y
# 创建并导出模型
model = SimpleAddModel()
x = torch.randn(3, dtype=torch.float32)
y = torch.randn(3, dtype=torch.float32)
torch.onnx.export(model, (x, y), "simple_add.onnx", input_names=['x', 'y'], output_names=['z'])
- 这段代码将创建一个简单的模型,将两个输入张量 x 和 y 相加,并导出为 simple_add.onnx。 编写C++代码,使用ONNX Runtime加载和运行该模型。
#include <onnxruntime/core/session/onnxruntime_cxx_api.h>
#include <iostream>
#include <vector>
#include <assert.h>
int main() {
// Initialize ONNX Runtime environment
Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "SimpleAdd");
// Create ONNX Runtime session options
Ort::SessionOptions session_options;
session_options.SetIntraOpNumThreads(1);
// Use GPU (CUDA) if available, otherwise fallback to CPU
const char* cuda_provider = "CUDAExecutionProvider";
if (Ort::GetAvailableProviders().count(cuda_provider)) {
session_options.AppendExecutionProvider_CUDA(0); // Device ID 0 for the first GPU
} else {
std::cout << "CUDA provider not available, running on CPU." << std::endl;
}
// Load the ONNX model
const char* model_path = "simple_add.onnx";
Ort::Session session(env, model_path, session_options);
// Get model input/output details
Ort::AllocatorWithDefaultOptions allocator;
// Get the name and shape of the first input tensor ('x')
char* input_name_x = session.GetInputName(0, allocator);
Ort::TypeInfo input_type_info_x = session.GetInputTypeInfo(0);
auto input_tensor_info_x = input_type_info_x.GetTensorTypeAndShapeInfo();
std::vector<int64_t> input_shape_x = input_tensor_info_x.GetShape();
// Get the name and shape of the second input tensor ('y')
char* input_name_y = session.GetInputName(1, allocator);
Ort::TypeInfo input_type_info_y = session.GetInputTypeInfo(1);
auto input_tensor_info_y = input_type_info_y.GetTensorTypeAndShapeInfo();
std::vector<int64_t> input_shape_y = input_tensor_info_y.GetShape();
// Create input data (example: 3-element float vectors)
std::vector<float> input_data_x = {1.0f, 2.0f, 3.0f};
std::vector<float> input_data_y = {4.0f, 5.0f, 6.0f};
// Create input tensor objects for 'x' and 'y'
auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
Ort::Value input_tensor_x = Ort::Value::CreateTensor<float>(memory_info, input_data_x.data(), input_data_x.size(), input_shape_x.data(), input_shape_x.size());
Ort::Value input_tensor_y = Ort::Value::CreateTensor<float>(memory_info, input_data_y.data(), input_data_y.size(), input_shape_y.data(), input_shape_y.size());
// Prepare input and output names
const char* input_names[] = {input_name_x, input_name_y};
const char* output_names[] = {"z"};
// Run inference
auto output_tensors = session.Run(Ort::RunOptions{nullptr}, input_names, &input_tensor_x, 2, output_names, 1);
// Get output tensor and data
float* output_data = output_tensors[0].GetTensorMutableData<float>();
// Print the output results
std::cout << "Output (z): ";
for (size_t i = 0; i < input_data_x.size(); i++) {
std::cout << output_data[i] << " ";
}
std::cout << std::endl;
// Clean up
allocator.Free(input_name_x);
allocator.Free(input_name_y);
return 0;
}
- 环境初始化:首先使用 Ort::Env 初始化 ONNX Runtime 环境,并指定日志级别为 ORT_LOGGING_LEVEL_WARNING。
- 加载模型:使用 Ort::Session 加载导出的 simple_add.onnx 模型。
- 输入/输出信息:通过调用 GetInputName() 和 GetInputTypeInfo() 获- 取输入和输出的名称和形状。这里假设输入 x 和 y 的形状为 [3],即长度为3的一维张量。
- 创建输入张量:使用 Ort::Value::CreateTensor 创建包含输入数据的张量,这里是长度为3的浮点数数组。
- 运行推理:通过 session.Run() 执行模型推理,并获取输出张量。
- 输出结果:输出结果将存储在 output_data 中,最后我们将其打印到控制台。 结果
Output (z): 5 7 9