五、深入学习TensorRT,Developer Guide篇(四)

上一篇文章我们介绍了C++的API,这篇文章我们主要针对的是Python的API,起始C++和Python在整体流程上面基本一致,但是由于Python天然的简洁性和易用性,Python的API相对来讲还是比较简单的,我们一起来看一下吧。

文章目录

  • 4. The Python API
    • 4.1 The Build Phase
      • 4.1.1 Creating a Network Definition in Python
      • 4.1.2 Importing a Model Using the ONNX Parser
      • 4.1.3 Building an Engine
    • 4.2 Deserializing a Plan
    • 4.3 Performing Inference
    • 4.4 samples研究

4. The Python API

本章节还是基于ONNX模型来阐述的,参考 onnx_resnet50.py获取更多信息(老样子,我们后面单独讲代码)。
Python API都可以从tensorrt模块中获取到:

import tensorrt as trt

4.1 The Build Phase

创建一个builder之前,需要创建一个logger,这样你后面所有的信息都可以通过logger来进行输出并进行分析,你可以直接像下面这样进行定义:

logger = trt.Logger(trt.Logger.WARNING)

也可以自定义,主要设计继承ILogger类进行实现:

class MyLogger(trt.ILogger):def __init__(self):trt.ILogger.__init__(self)def log(self, severity, msg):pass # Your custom logging implementation herelogger = MyLogger()

然后创建builder:

builder = trt.Builder(logger)

还是和C++一样的说辞,builder比较耗时,如何让builder更快,参考:Optimizing Builder Performance

4.1.1 Creating a Network Definition in Python

创建完builder后,首先要做的就是创建一个网络定义(network definition):

network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))

在使用 ONNX parser的方式来导入模型的时候,必须指定EXPLICIT_BATCH这个flag,更多细节请参考: Explicit Versus Implicit Batch

4.1.2 Importing a Model Using the ONNX Parser

使用ONNX来填充我们定义好的网络框架,首先声明一个parser:

parser = trt.OnnxParser(network, logger)

然后,读取模型文件并且处理errors:

success = parser.parse_from_file(model_path) # 模型文件的路径
for idx in range(parser.num_errors):print(parser.get_error(idx))if not success:pass # Error handling code here

4.1.3 Building an Engine

接下来是创建一个build configuration来配置TensorRT如何进行模型优化:

config = builder.create_builder_config()

这个接口有甚多你可以设置的属性。一个重要的属性就是最大空间( maximum workspace size)。Layer的实现通常需要一个临时空间,这个参数限制了网络中的任意layer可以使用的最大空间。如果你没有提供一个足够的空间,TensorRT就无法找到一个层的实现(就是放不下了)。默认情况下,workspace被设置为给定设备的所有全局内存大小(total global memory),当你需要的时候,你应该来进行限定,比如说你只有一个设备,但是有多个engine在build:

config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 20) # 1 MiB,2^20

指定完configuration后,就可以构建和序列化模型了:

serialized_engine = builder.build_serialized_network(network, config)

然后把engine存到本地磁盘,后续再使用:

with open(“sample.engine”, “wb”) as f:f.write(serialized_engine)

注意:Serialized engines不能跨平台或跨TensorRT版本进行移植。Engines是特定于它们所构建的确切GPU模型的(除了平台和TensorRT版本,也就是建议我们在哪用就在哪构建,除非你能保证版本都一致)。

4.2 Deserializing a Plan

进行推理的时候,使用Runtime接口来序列化模型,和builder一样,runtime也需要一个logger实例:

runtime = trt.Runtime(logger)

从内存中序列化engine:

engine = runtime.deserialize_cuda_engine(serialized_engine)

当然咯,你也可以从本地文件中进行读取:

with open(“sample.engine”, “rb”) as f:serialized_engine = f.read()

4.3 Performing Inference

这个时候,所有的模型信息都给了engine变量,但是我们必须要管理中间激活( intermediate activations)的附加状态(真拗口,啥是中间激活,先有个印象)。我们通过ExecutionContext接口来进行:

context = engine.create_execution_context()

一个engine可以有多个execution contexts,允许一组权重用于多个重叠的推理任务(除非使用了dynamic shapes,每个optimization profile只能有一个 execution context,除非指定了预览特性kPROFILE_SHARING_0806,后续有机会再补充)。
运行推理,你还必须要指定input和output的buffer:

context.set_tensor_address(name, ptr)

几个Python包允许你在GPU上分配内存,包括但不限于官方CUDA Python bindings,PyTorch, cuPy和Numba。

这样你就完成了input的设置,你可以调用execute_async_v3()方法来使用 CUDA stream进行推理,根据网络的结构和特点,网络可以异步执行,也可以同步执行。例如,可能导致同步行为的情况包括依赖数据的形状(data dependent shapes)、DLA的使用、循环和同步的插件(plugin)。

首先,创建一个 CUDA stream,如果你已经有了一个 CUDA stream,你可以使用一个指向已经存在的stream的指针,比如对于Pytorch CUDA stream就是torch.cuda.Stream(),你可以使用cuda_stream属性来获取这个指针,对于 Polygraphy CUDA streams,使用ptr属性,或者直接调用cudaStreamCreate()来创建一个CUDA Python binding(后面我们结合代码来看一下)。
然后开始推理:

context.execute_async_v3(buffers, stream_ptr)

推荐你在kernels从GPU传输完数据后进行异步数据传输的同步操作(其实就是调用cudaMemcpyAsync()函数),这样可以保证数据传输完整。

要确定推理(可能还有cudaMemcpyAsync())何时完成,请使用标准的CUDA同步机制,例如事件( events)或着等待这个流结束。例如对于PyTorch CUDA streams 或 Polygraphy CUDA streams你可以使用stream.synchronize(),对于CUDA Python binding你可以使用cudaStreamSynchronize(stream)

4.4 samples研究

首先打开samples/python/introductory_parser_samples/onnx_resnet50.py文件

  1. Build a TensorRT engine
    一起看main()build_engine_onnx()

    def build_engine_onnx(model_file):builder = trt.Builder(TRT_LOGGER) # 声明buildernetwork = builder.create_network(common.EXPLICIT_BATCH) # 定义网络config = builder.create_builder_config() # 声明configparser = trt.OnnxParser(network, TRT_LOGGER) # 声明parserconfig.max_workspace_size = common.GiB(1) # 配置config# Load the Onnx model and parse it in order to populate the TensorRT network.with open(model_file, "rb") as model:if not parser.parse(model.read()): # 读本地文件并解析print("ERROR: Failed to parse the ONNX file.")for error in range(parser.num_errors):print(parser.get_error(error))return Nonereturn builder.build_engine(network, config) # network->parser->builder这样的顺序链接起来
    
  2. Allocate buffers and create a CUDA stream
    一起看common.allocate_buffers(engine)函数:

    # Allocates all buffers required for an engine, i.e. host/device inputs/outputs.
    # If engine uses dynamic shapes, specify a profile to find the maximum input & output size.
    def allocate_buffers(engine: trt.ICudaEngine, profile_idx: Optional[int] = None):inputs = []outputs = []bindings = []stream = cuda_call(cudart.cudaStreamCreate()) # 和C++不同,这里需要特殊处理这个stream,因为python没有指针的概念tensor_names = [engine.get_tensor_name(i) for i in range(engine.num_io_tensors)]for binding in tensor_names:# 根据名称获得每个tensor的max shape,这样就可以分配足够的内存了# get_tensor_profile_shape returns (min_shape, optimal_shape, max_shape)# Pick out the max shape to allocate enough memory for the binding.shape = engine.get_tensor_shape(binding) if profile_idx is None else engine.get_tensor_profile_shape(binding, profile_idx)[-1]shape_valid = np.all([s >= 0 for s in shape])if not shape_valid and profile_idx is None:raise ValueError(f"Binding {binding} has dynamic shape, " +\"but no profile was specified.")size = trt.volume(shape)if engine.has_implicit_batch_dimension:size *= engine.max_batch_sizedtype = np.dtype(trt.nptype(engine.get_tensor_dtype(binding)))# Allocate host and device buffers# 这个函数比较重要,是核心函数,我们后面单独拎出来看一下bindingMemory = HostDeviceMem(size, dtype) # 是一个类# Append the device buffer to device bindings.# 把cudaMalloc()获得的nbytes数据的空间全部放到bindings列表中去bindings.append(int(bindingMemory.device))# Append to the appropriate list.# 单独处理输入输出节点if engine.get_tensor_mode(binding) == trt.TensorIOMode.INPUT:inputs.append(bindingMemory)else:outputs.append(bindingMemory)return inputs, outputs, bindings, stream
    

    上面的allocate_buffers()函数到底干了啥事呢?就是逐层遍历,获取size和dtype,然后cudamalloc()申请空间,把大小都放在bindings里面,然后对于输出输出单独拎出来返回。我们一起再来研究一下HostDeviceMem()类到底干啥了,我们先只看他的初始化函数。

    class HostDeviceMem:
    # 意思就是说,host内存包装在了一个numpy数组中了
    """Pair of host and device memory, where the host memory is wrapped in a numpy array"""
    def __init__(self, size: int, dtype: np.dtype):nbytes = size * dtype.itemsize# cudart.cudaMallocHost(nbytes)这个就是在Host上进行内存申请的语句host_mem = cuda_call(cudart.cudaMallocHost(nbytes)) # CPU内存pointer_type = ctypes.POINTER(np.ctypeslib.as_ctypes_type(dtype))# cast是判断host_mem是不是pointer_type的,如果是就转换成numpy arrayself._host = np.ctypeslib.as_array(ctypes.cast(host_mem, pointer_type), (size,))self._device = cuda_call(cudart.cudaMalloc(nbytes)) # GPU内存self._nbytes = nbytes
    
  3. 创建execution context,推理的时候都会用到哟

    context = engine.create_execution_context()
    
  4. 加载并预处理输入数据

    # Load a normalized test case into the host input page-locked buffer.
    # 锁页内存更快,类似零拷贝:https://www.jianshu.com/p/e92e72c0ba51
    test_image = random.choice(test_images)
    test_case = load_normalized_test_case(test_image, inputs[0].host)
    

    load_normalized_test_case()函数实现了预处理和数据拷贝:

    def load_normalized_test_case(test_image, pagelocked_buffer):
    # Converts the input image to a CHW Numpy array
    def normalize_image(image):# Resize, anti alias (Image.LANCZOS下采样过滤插值法) and transpose the image to CHW.c, h, w = ModelData.INPUT_SHAPEimage_arr = (np.asarray(image.resize((w, h), Image.LANCZOS)).transpose([2, 0, 1]).astype(trt.nptype(ModelData.DTYPE)).ravel())# This particular ResNet50 model requires some preprocessing, specifically, mean normalization.# ResNet50 要求的数据预处理return (image_arr / 255.0 - 0.45) / 0.225# Normalize the image and copy to pagelocked memory.
    # 使用np.copyto拷贝内存
    np.copyto(pagelocked_buffer, normalize_image(Image.open(test_image)))
    return test_image
    
  5. 运行
    输出是一个有1000长度的1维向量,代表1000分类,再来看一下怎么运行的吧:

    def _do_inference_base(inputs, outputs, stream, execute_async):# Transfer input data to the GPU.kind = cudart.cudaMemcpyKind.cudaMemcpyHostToDevice# 支持多个输入,从host逐个拷贝到device中去[cuda_call(cudart.cudaMemcpyAsync(inp.device, inp.host, inp.nbytes, kind, stream)) for inp in inputs]# Run inference.# 其实是 context.execute_async_v2(bindings=bindings, stream_handle=stream)execute_async()# Transfer predictions back from the GPU.kind = cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost# 从device逐个拷贝output到host[cuda_call(cudart.cudaMemcpyAsync(out.host, out.device, out.nbytes, kind, stream)) for out in outputs]# Synchronize the stream# 就是我们上面将的同步,这样保证数据传输完整cuda_call(cudart.cudaStreamSynchronize(stream))# Return only the host outputs.return [out.host for out in outputs]
    
  6. 后处理
    就是利用argmax取出最大索引的位置作为输出:

    pred = labels[np.argmax(trt_outputs[0])] # 这里只拿了第一个输出,其实应该有几个输入就有几个输出吧
    common.free_buffers(inputs, outputs, stream)
    if "_".join(pred.split()) in os.path.splitext(os.path.basename(test_case))[0]:print("Correctly recognized " + test_case + " as " + pred)
    else:print("Incorrectly recognized " + test_case + " as " + pred)
    

关于Python API的接口就这么多啦,能明显感觉到Pyhton API比C++更简单易用,加上这么多Python的第三方库,不管是预处理还是后处理都会比较方便,所以掌握Python API的使用也是非常重要的,大家一起加油呀!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/698465.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下快速安装nginx 并配置开机自启动

1、下载地址&#xff1a;http://nginx.org/en/download.html 2、启动nginx 注意⚠️ 不要直接双击nginx.exe&#xff0c;这样会导致修改配置后重启、停止nginx无效&#xff0c;需要手动关闭任务管理器内的所有nginx进程。 在nginx.exe目录&#xff0c;打开命令行工具&#xf…

【springblade】springblade(bladeX) 数据权限失效原因分析

文章目录 数据权限接口权限 前言&#xff1a;最近博主在按照bladeX官方文档 配置数据权限 结果发现失效了&#xff0c;网上搜了一下没找到合适的答案&#xff0c;本着求人不如求己的精神&#xff0c;自己调试了一下发现了问题所在&#xff0c;也大致看了一下bladeX的权限逻辑。…

代码随想录算法训练营29期Day56|LeetCode 300,674,718

文档讲解&#xff1a;最长递增子序列 最长连续递增序列 最长重复子数组 300.最长递增子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-increasing-subsequence/description/ 思路&#xff1a; 设dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长…

unity——shader入门知识点 学习笔记【个人复习向/侵删/有不足之处欢迎斧正】

零、不同图形接口程序对Shader开发的影响&#xff1a; 1.渲染管线(流水线)和图形接口程序的关系&#xff1a;图形接口程序(OpenGL、 DX等)提供了对渲染管线(流水线)的控制和管理功能&#xff0c;它是开发者和硬件打交道的中间层 2. Shader和图形接口程序的关系&#xf…

计算机毕业设计 基于SpringBoot的宠物商城网站系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

LeetCode206反转链表

LeetCode206,直接看下面链接的动画演示部分就行了,就能快速理解pre和cur的作用 代码随想录:反转链表 这个是另外的在B站的视频讲解链接 【帮你拿下反转链表 | LeetCode&#xff1a;206.反转链表 | 双指针法 | 递归法】 https://www.bilibili.com/video/BV1nB4y1i7eL/?share_so…

Linux理解

VMware安装Linux安装 目录 VMware安装Linux安装 1.1 什么是Linux 1.2 为什么要学Linux 1.3 学完Linux能干什么 2.1 主流操作系统 2.2 Linux系统版本 VMware安装Linux安装 1.1 什么是Linux Linux是一套免费使用和自由传播的操作系统。 1.2 为什么要学Linux 1). 企业用人…

Python3获取办公室IP并更新至腾讯云安全组

需求来源&#xff1a; 由于云服务器的安全组限制了只能在公司IP下远程连接&#xff0c;每次更新安全组时只能通过手动更新&#xff0c;办公室IP是动态IP&#xff0c;每隔三天变一次IP&#xff0c;时间久了就觉得麻烦&#xff0c;所以在查阅了腾讯云文档后发现有安全组的SDK&…

【Git】:初识git

初识git 一.创建git仓库二.管理文件三.认识.git内部结构 一.创建git仓库 1.安装git 使用yum install git -y即可安装git。 2.创建仓库 首先创建一个git目录。 3.初始化仓库 这里面有很多内容&#xff0c;后面会将&#xff0c;主要是用来进行追踪的。 4.配置name和email 当然也…

Node.js的debug模块源码分析及在harmonyOS平台移植

Debug库 是一个小巧但功能强大的 JavaScript 调试工具库&#xff0c;可以帮助开发人员更轻松地进行调试&#xff0c;以便更快地发现和修复问题。它的主要特点是可以轻松地添加调试日志语句&#xff0c;同时在不需要调试时可以轻松地禁用它们&#xff0c;以避免在生产环境中对性…

室内外一体化定位系统

随着科技的不断发展&#xff0c;定位系统已经成为了人们生活中不可或缺的一部分。无论是在户外还是室内&#xff0c;定位服务都给人们带来了极大的便利。然而&#xff0c;传统的定位系统主要集中在室外环境&#xff0c;对于室内环境的定位还存在一定的困难。因此&#xff0c;室…

深度优先搜索-DFS

介绍 英文全称&#xff1a;Deep First Search 枚举所有完整路径来遍历所有情况的搜索方法 return不同情况的介绍 问题描述 在n件物品中&#xff0c;输入其容量与价值。给定容量&#xff0c;求物品的价值之和最大值。 思路 用DFS思想来看&#xff0c;每件物品有选择与不选择…

Spring Boot应用集成Actuator端点自定义Filter解决未授权访问的漏洞

一、前言 我们知道想要实时监控我们的应用程序的运行状态&#xff0c;比如实时显示一些指标数据&#xff0c;观察每时每刻访问的流量&#xff0c;或者是我们数据库的访问状态等等&#xff0c;需要使用到Actuator组件&#xff0c;但是Actuator有一个访问未授权问题&#xff0c;…

Postgresql源码(123)事务提交时三段资源释放分析ResourceOwnerRelease

0 总结 三段释放原因&#xff1a;因为如果先释放锁&#xff0c;没有释放一些共享资源&#xff08;比如pin住的buffer&#xff09;&#xff0c;别人拿到锁后发现我们仍然持有一些资源&#xff0c;就会有问题。所以三阶段释放主要是以锁为分界线&#xff0c;先释放锁保护的资源&…

智慧未来:人工智能驱动下的创新与发展

智慧未来&#xff1a;人工智能驱动下的创新与发展 随着人工智能技术的迅猛发展和广泛应用&#xff0c;我们正迎来一个智慧未来的时代。人工智能作为驱动力推动着创新与发展&#xff0c;改变着我们的生活、工作和社会。让我们一起探讨人工智能驱动下的创新与发展所带来的影响和…

酷开科技丨新年新玩法!酷开系统壁纸模式给客厅“换”新

甲辰龙年即将到来&#xff0c;新年新家新气象&#xff0c;快到酷开系统壁纸模式中挑选一款喜欢的壁纸&#xff0c;为新的一年增添一份美好和喜悦吧&#xff01; 酷开科技将更多的电视新玩法带给你&#xff0c;让你的电视成为家庭中的焦点&#xff01;酷开系统壁纸模式&#xf…

LabVIEW高效核磁测井仪器多线程优化

LabVIEW高效核磁测井仪器多线程优化 为提高核磁测井仪器的测试效率与性能&#xff0c;开发了基于LabVIEW的多线程优化模型。该研究针对传统的核磁测井仪器软件&#xff0c;在多任务调度测试和并行技术需求上存在的效率不高和资源利用率低的问题&#xff0c;提出了一个多线程优…

智能家居现状分析及未来展望

当前现状 家居行业经过多年发展&#xff0c;顺利完成了从无到有的进化历程&#xff0c;现正在智能化的道路上奋力驰骋&#xff0c;虽发展迅速但也面临一些问题。主要有&#xff1a; APP操作复杂、UI不统一 传统硬件厂家的优势在设备制造领域&#xff0c;让设备“上网”不是其…

SQL注入工具之SQLmap入门操作

了解SQLmap 基础操作 SQLmap是一款自动化的SQL注入工具&#xff0c;可以用于检测和利用SQL注入漏洞。 以下是SQLmap的入门操作步骤&#xff1a; 1.下载SQLmap&#xff1a;可以从官方网站&#xff08;https://sqlmap.org/&#xff09;下载最新版本的SQLmap。 2.打开终端&#…

修改单据转换规则后保存报错提示

文章目录 修改单据转换规则后保存报错提示 修改单据转换规则后保存报错提示