resnet18 并行推理
线程安全的本地推理
为了方便,假设将tensorrt推理功能封装为名称为 TensorrtTensor
的计算后端。由于计算发生在gpu设备上,我们加上SyncTensor
表示gpu上的流同步操作。
配置项 | 参数 | 说明 |
---|---|---|
backend | "SyncTensor[TensorrtTensor]" | 计算后端和tensorrt推理本身一样,不是线程安全的。 |
max | 4 | 模型支持的最大batchsize,用于模型转换(onnx->tensorrt) |
如果可以从多个线程中同时调用某个函数,则该函数是线程安全的。提供线程安全的接口将极大的方便使用者。
torchpipe默认会在此计算后端上包裹一层可扩展的单节点调度后端,实现以下三个基本能力:
- 前向接口线程安全性
多实例并行
配置项 默认值 说明 instance_num 1 多个模型实例并行执行推理任务。
Batching
对于resnet18, 模型本身输入为
-1x3x224x224
, batchsize越大,单位硬件资源所完成的任务越多。 batchsize 从计算后端(TensorrtTensor)读取。配置项 默认值 说明 batching_timeout 0 单位为毫秒,在此时间内如果没有接收到 batchsize 个数目的请求,则放弃等待。
性能调优技巧
汇总以上步骤,我们获得推理resnet18在torchpipe下的必要参数:
- Python
- C++
import torchpipe as tp
import torch
config = {
# 单节点调度器参数:
"instance_num":2,
"batching_timeout":5,
# 计算后端:
"backend":"SyncTensor[TensorrtTensor]",
# 计算后端参数:
"model":"resnet18_-1x3x224x224.onnx",
"max":4
}
# 初始化
models = tp.pipe(config)
data = torch.ones(1,3,224,224).cuda()
## 前向
input = {"data":data}
models(input) # <== 可多线程调用
result: torch.Tensor = input["result"] # 失败则 "result" 不存在
#include "Interpreter.hpp"
#include "ATen/ATen.h"
int main(void) {
std::unordered_map<std::string, std::string> config = {// 单节点调度器参数:
{"instance_num", "2"},
{"batching_timeout", "5"},
// 计算后端:
{"backend", "SyncTensor[TensorrtTensor]"},
// 计算后端参数:
{"model", "resnet18_-1x3x224x224.onnx"},
{"max", "4"}};
// 初始化
ipipe::Interpreter model(config);
// 准备数据
auto input = std::make_shared<std::unordered_map<std::string, ipipe::any>>();
(*input)["data"] =
at::empty({1, 3, 224, 224}, at::TensorOptions().device(at::kCUDA, -1).dtype(at::kFloat));
// 前向
model(input); // <== 可多线程调用
at::Tensor result = ipipe::any_cast<at::Tensor>(input->at(ipipe::TASK_RESULT_KEY));
return 0;
};
假设我们想要支持最多10路的客户端/并发请求, instance_num 一般设置2,以便最多有处理 instance_num*max = 8 路的能力。
性能取舍
请注意,我们的加速做了如下假设:
cpu到cpu,单张显卡内部gpu到gpu的数据拷贝)速度快,消耗资源少,整体上可忽略不计。
提示
相对于异构设备间的数据拷贝以及其他的计算,这条假设是没问题的。后面我们将看到,在一些特殊场景,这条假设可能不成立,需要相应的规避手段。
小结
从接口层面,我们实现了所有的预定目标:
- 使用tensorrt等框架进行模型针对性加速
- 避免频繁显存申请 : 透过pytorch的显存池完成
- 多实例,batching : 透过单节点调度后端完成
- 优化数据传输: 透过将
torch.Tensor
作为输入输出载体实现。