Pytorch转Onnx转TensorRT踩坑记

本文详细介绍了在将PyTorch模型转换为ONNX格式时可能遇到的各种错误,包括不同版本的PyTorch与CUDA兼容性问题、运行时错误以及TensorRT解析ONNX文件失败等问题,并提供了相应的解决办法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转换Onnx过程中:

PyTorch v1.0.1  Reshape不支持报错 

源码安装PyTorch v1.0.1 caffe2报错

PyTorch v1.2.0 需要升级cuda10.0以上

PyTorch v1.1.0报错:

RuntimeError: Failed to export an ONNX attribute, since it's not constant, please try to make things (e.g., kernel size) static if possible

解决办法,按照如下方式修改:

https://github.com/pytorch/pytorch/commit/11845cf2440c1abb0cb117bcee9532b26573e9c9

即可成功

TensorRT解析onnx失败,Failing to parse onnx file generated by pytorch

解决办法,按照如下方式修改:

https://devtalk.nvidia.com/default/topic/1055172/tensorrt/failing-to-parse-onnx-file-generated-by-pytorch/

修改完成之后,还有问题,是其中的Gather算子的问题,PyTorch官方没有考虑asix=0的情况

<think>我们正在处理的是在PyTorch中使用TensorRT进行推理时,创建CUDA流(stream)过程中出现'invaliddevice context'错误。根据引用[1]中的报错信息:"pycuda._driver.LogicError: explicit_context_dependent failed: invaliddevice context- nocurrently activecontext?"这个错误通常表示在创建CUDA流时,当前线程没有激活的CUDA上下文。原因分析:1. **CUDA上下文未正确绑定到当前线程**:CUDA上下文是线程局部的(thread-local)。在初始化线程中创建的CUDA上下文不会自动传递到推理线程。2. **PyTorchTensorRT混合使用时的上下文管理**:PyTorch有自己的CUDA上下文管理,而TensorRT(特别是使用pycuda时)需要显式地处理上下文。解决方案:1. **确保在每个使用CUDA的线程中初始化CUDA上下文**:可以通过在该线程中执行一个简单的CUDA操作(例如创建一个张量)来隐式初始化上下文。2. **显式设置CUDA上下文**:如果使用pycuda,可以手动将上下文推入当前线程。下面我们结合用户之前的问题(多线程下TensorRT推理)来设计解决方案。注意:用户之前的问题是在多线程环境下,初始化在一个线程(初始化线程)完成,而推理在另一个线程(推理线程)进行。因此,在推理线程中,我们需要确保CUDA上下文存在。具体步骤:方案一:在推理线程中隐式初始化CUDA上下文-在推理线程中,先执行一个简单的CUDA操作,例如:`torch.tensor([1],device="cuda")`。方案二:显式使用pycuda管理上下文(如果使用了pycuda)-在初始化线程中创建上下文,然后在推理线程中手动激活该上下文。然而,在PyTorch环境中,通常推荐使用PyTorch的CUDA流管理,这样能够与PyTorch的上下文更好地集成。因此,我们修改之前的类设计,确保在推理线程中正确初始化CUDA上下文。修改后的类示例:```pythonimport torchimporttensorrtas trtclass TRTEngine:def __init__(self,engine_path,init_thread=True):"""初始化TensorRT引擎。:paramengine_path: .trt引擎文件路径:param init_thread:是否在初始化线程中完成初始化(默认True)"""#如果指定在初始化线程中初始化,则立即初始化if init_thread:self._initialize(engine_path)else:#否则,将初始化任务交给后续的推理线程(在第一次推理时初始化)self.engine_path= engine_pathself.engine= Noneself.context= Noneself.stream= Noneself.bindings= Nonedef _initialize(self, engine_path):#加载引擎withopen(engine_path, 'rb')as f, trt.Runtime(trt.Logger(trt.Logger.WARNING))as runtime:self.engine =runtime.deserialize_cuda_engine(f.read())self.context =self.engine.create_execution_context()#创建PyTorchCUDA流self.stream =torch.cuda.Stream()#准备绑定(bindings) -根据输入输出张量self.bindings= []forbinding inself.engine:size= trt.volume(self.engine.get_binding_shape(binding)) *self.engine.max_batch_sizedtype= trt.nptype(self.engine.get_binding_dtype(binding))#分配内存device_mem =torch.empty(size,dtype=dtype,device='cuda').contiguous()self.bindings.append(device_mem.data_ptr())definfer(self, input_tensor):#如果尚未初始化,则在当前线程(推理线程)进行初始化ifself.engine isNone:self._initialize(self.engine_path)#确保当前线程有CUDA上下文(通过PyTorch张量操作隐式激活)#如果当前线程还没有CUDA上下文,下面的操作会创建一个if nottorch.cuda.is_initialized():torch.cuda.init()#将输入数据复制到绑定内存#注意:这里假设输入绑定是第一个绑定input_mem =self.bindings[0]#将输入张量数据复制到绑定内存#注意:需要确保输入张量在GPU上,且形状和类型与引擎期望的一致withtorch.cuda.stream(self.stream):#异步复制输入数据到绑定内存#这里我们假设输入绑定内存已经分配了足够的空间#实际上,我们需要检查输入张量的大小是否与绑定内存匹配,如果不匹配,需要重新分配#为了简化,我们假设模型是固定输入大小的input_tensor =input_tensor.contiguous().cuda()#将输入张量的数据指针赋给绑定(如果输入绑定内存大小不够,这里会有问题)#所以更好的做法是预先根据引擎的绑定要求分配内存,然后复制数据到该内存#但为了简单,我们这里直接使用输入张量的数据指针(前提是绑定内存就是输入张量的内存)#因此,我们修改绑定为输入张量的数据指针(注意:这样动态改变绑定指针是允许的)bindings= [input_tensor.data_ptr()] +self.bindings[1:]#执行推理self.context.execute_async_v2(bindings=bindings,stream_handle=self.stream.cuda_stream)#同步流,确保推理完成self.stream.synchronize()#返回输出,输出绑定在bindings[1:]中,这里需要根据引擎的输出绑定来获取#注意:实际应用中,需要将绑定内存中的输出数据换为PyTorch张量#这里假设输出绑定已经分配了内存,并且我们将其包装为PyTorch张量outputs= []fori inrange(1,len(self.bindings)):#获取输出张量的形状和类型shape =self.context.get_binding_shape(i)dtype= trt.nptype(self.engine.get_binding_dtype(i))#从绑定内存中创建张量output_tensor= torch.from_numpy(np.frombuffer(self.bindings[i],dtype=dtype).reshape(shape).copy()outputs.append(output_tensor)return outputs```但是,上面的代码存在几个问题:1.绑定内存的分配:在初始化时,我们根据引擎的最大批处理大小和绑定形状分配内存。但是,如果输入大小是动态的,这种方法就不合适。2.在推理时,我们改变了绑定指针(使用输入张量的指针),但之前分配的内存并没有使用(而且可能被覆盖,导致内存泄漏)。改进点:-对于动态输入,我们需要在每次推理前设置绑定形状(set_binding_shape)并重新分配内存(或者使用输入张量的内存,但需要确保输入张量在推理完成前不被释放)。-另外,我们也可以不预先分配绑定内存,而是在每次推理时根据输入输出张量动态设置绑定。但考虑到错误是“invaliddevice context”,我们主要解决上下文问题。因此,我们确保在推理线程中初始化CUDA上下文(通过调用`torch.cuda.init()`)或者通过执行一个CUDA操作(如创建张量)来激活上下文。然而,在PyTorch中,通常当我们第一次将张量移动到GPU时,上下文会被自动创建。所以,在推理线程中,我们执行`input_tensor= input_tensor.contiguous().cuda()`时,就会激活当前线程的上下文。因此,上述代码已经通过将输入张量移动到GPU来隐式激活上下文。但是,如果输入张量已经在GPU上,那么这一步就不会创建新的上下文(因为上下文已经存在)。所以,我们还需要确保在初始化时(在初始化线程中)创建的上下文不会干扰推理线程?实际上,每个线程都需要自己的上下文吗?在CUDA中,上下文是线程局部的。所以,在初始化线程中创建的上下文(通过加载引擎、创建执行上下文等)只属于初始化线程。当我们在推理线程中使用CUDA时,需要在该线程中创建新的上下文。因此,我们不应该在初始化线程中创建执行上下文和流,而应该在推理线程中创建。或者,我们可以在每个线程中独立初始化引擎(但引擎可以共享,因为引擎是线程安全的)。修改方案:将引擎的初始化放在主线程(或者一个线程中),然后每个推理线程创建自己的执行上下文和流。因为引擎(ICudaEngine)是线程安全的,可以被多个线程共享,而执行上下文(IExecutionContext)不是线程安全的,每个线程需要自己的上下文。所以,我们修改类设计如下: ```pythonclass TRTEngine:def __init__(self,engine_path):#加载引擎(引擎是线程安全的,可以共享)withopen(engine_path, 'rb')as f, trt.Runtime(trt.Logger(trt.Logger.WARNING)) asruntime:self.engine= runtime.deserialize_cuda_engine(f.read())#不在这里创建上下文,而是每个线程自己创建def create_context(self):#每个线程在开始推理前需要调用这个函数来创建自己的上下文和流context= self.engine.create_execution_context()stream =torch.cuda.Stream()returncontext,stream#使用方式: #在主线程中创建引擎engine =TRTEngine(engine_path)#在推理线程中def inference_thread(engine,input_data):#确保当前线程有CUDA上下文(通过PyTorchtorch.cuda.init()#创建该线程的上下文和流context, stream= engine.create_context()#然后进行推理...```这样,每个线程都有自己的执行上下文和流,避免了线程安全问题,也确保了每个线程都有正确的CUDA上下文。但是,用户希望将初始化(包括engine、context和stream)放在初始化线程中完成,而推理在另一个线程。根据以上分析,这样是不行的,因为上下文和流不能跨线程使用。因此,我们需要调整设计:初始化线程只负责加载引擎(因为引擎加载比较耗时),而每个推理线程负责创建自己的上下文和流。所以,我们修改用户要求的类:```pythonimporttorchimport tensorrt astrtimportthreadingclass TRTEngine:def __init__(self,engine_path):#在初始化线程中加载引擎withopen(engine_path, 'rb')as f, trt.Runtime(trt.Logger(trt.Logger.WARNING)) asruntime:self.engine= runtime.deserialize_cuda_engine(f.read())#每个线程的上下文和流存储在一个线程局部变量中self.local =threading.local()def _get_context_and_stream(self):#如果当前线程还没有创建上下文和流,则创建if nothasattr(self.local, "context"):#确保当前线程有CUDA上下文(通过PyTorch)#创建一个临时的张量来激活上下文(如果还没有的话)torch.cuda.init()self.local.context =self.engine.create_execution_context()self.local.stream= torch.cuda.Stream()return self.local.context, self.local.streamdef infer(self,input_tensor):context, stream= self._get_context_and_stream()#设置动态输入形状(如果需要)#假设输入绑定索引为0ifself.engine.is_shape_binding(0):#设置输入形状context.set_binding_shape(0, input_tensor.shape)#准备绑定bindings =[]#输入绑定bindings.append(input_tensor.contiguous().data_ptr())#输出绑定:需要为每个输出分配内存(这里简化处理,假设输出已经在外部分配好,或者我们根据形状分配)#实际上,输出内存应该在初始化上下文时分配,但动态形状下每次形状改变都需要重新分配#所以这里我们每次动态分配输出(或者由外部提供输出内存)#为了简单,我们假设输出绑定已经由上下文根据形状分配好(通过set_binding_shape后,我们可以获取输出大小)#但是,我们这里采用动态分配:根据绑定形状分配输出张量outputs= []forbindingin range(1, self.engine.num_bindings):shape =context.get_binding_shape(binding)dtype= trt.nptype(self.engine.get_binding_dtype(binding))#分配输出张量output_tensor =torch.empty(tuple(shape),dtype=torch.float32, device='cuda').contiguous()bindings.append(output_tensor.data_ptr())outputs.append(output_tensor)#异步执行withtorch.cuda.stream(stream):context.execute_async_v2(bindings=bindings,stream_handle=stream.cuda_stream)#同步stream.synchronize()returnoutputs```注意:上面的代码中,我们为每个线程创建了独立的执行上下文和流。这样,在推理线程中调用infer时,会检查当前线程是否有上下文和流,如果没有则创建(同时会隐式创建CUDA上下文)。这样设计解决了两个问题:1.跨线程使用CUDA资源(每个线程有自己的上下文和流)。2.每个线程都有激活的CUDA上下文(通过`torch.cuda.init()`或创建张量来激活)。因此,应该可以避免'invalid devicecontext'错误。但是,请注意动态输入的处理:在每次推理前,我们根据输入张量的形状设置绑定形状(如果是动态形状绑定)。然后为输出分配内存。这个设计可能对性能有一定影响(每次推理都要检查并分配输出内存),但确保了正确性。对于高性能场景,可以优化输出内存的复用。总结:在多线程环境下使用TensorRT,每个线程必须有自己的执行上下文和CUDA流,并且要确保在该线程中已经初始化了CUDA上下文(通过PyTorch的CUDA操作隐式初始化)。这样,我们就解决了'invalid devicecontext'错误。引用:[^1]:报错:pycuda._driver.LogicError:explicit_context_dependentfailed:invalid devicecontext -no currentlyactive context?[^2]:基于TensorRTonnxruntime下pytorch的Bert模型加速对比实践[^3]: tensorrt(pythonpytorchonnx推理加速)</think>### PyTorch TensorRT推理中创建CUDA流报错 `invalid device context` 分析 #### 错误原因分析 1. **CUDA上下文未正确激活**(核心原因) CUDA上下文是线程绑定的资源,当在非初始化线程中创建流时: ```python # 初始化线程 class TRTEngine: def __init__(self): self.stream = torch.cuda.Stream() # 在初始化线程创建 # 推理线程 → 错误! def infer(): self.context.execute_async_v2(stream_handle=self.stream.handle) ``` **数学关系**:CUDA上下文生命周期需满足 $$C_{\text{active}} \in T_{\text{create}}$$ 跨线程使用违反此约束[^1]。 2. **PyTorchTensorRT上下文冲突** PyTorch自动管理CUDA上下文,而TensorRT需要显式上下文激活: ```python # 缺少显式上下文激活 self.stream = torch.cuda.Stream() # 等效于 cuStreamCreate() 但未确保上下文存在 ``` 3. **设备上下文切换问题** 多GPU环境下未指定设备: ```python # 未设置当前设备 torch.cuda.set_device(0) # 缺少此步骤 self.stream = torch.cuda.Stream() ``` 4. **上下文提前释放** 初始化线程结束后上下文被销毁: ```python init_thread = threading.Thread(target=engine_init) init_thread.start() init_thread.join() # 线程结束 → 上下文释放 # 但流句柄仍被推理线程使用 ``` --- ### 完整解决方案 #### 方案1:线程内资源创建(推荐) ```python import torch import tensorrt as trt class TRTEngine: def __init__(self, engine_path): # 仅加载引擎(线程安全) with open(engine_path, "rb") as f: self.engine = trt.Runtime(trt.Logger()).deserialize_cuda_engine(f.read()) def create_thread_resources(self): """必须在推理线程内调用""" # 激活当前线程CUDA上下文 torch.cuda.init() # 确保上下文存在 self.context = self.engine.create_execution_context() self.stream = torch.cuda.Stream() # 在推理线程创建 def infer(self, input_tensor): # 动态形状处理 if not self.engine.has_implicit_batch_dimension: self.context.set_binding_shape(0, input_tensor.shape) bindings = [input_tensor.data_ptr()] # 异步执行 self.context.execute_async_v2( bindings=bindings, stream_handle=self.stream.cuda_stream ) self.stream.synchronize() ``` #### 方案2:显式上下文管理 ```python import pycuda.autoinit # 自动初始化上下文 import pycuda.driver as cuda class TRTEngine: def __init__(self, engine_path): # 创建显式上下文 self.ctx = cuda.Device(0).make_context() with open(engine_path, "rb") as f: self.engine = trt.Runtime(trt.Logger()).deserialize_cuda_engine(f.read()) self.context = self.engine.create_execution_context() self.stream = cuda.Stream() # 使用pycuda流 def infer(self, input_tensor): # 必须确保在推理线程激活上下文 self.ctx.push() # 激活上下文 # ...执行推理... self.ctx.pop() # 释放上下文 ``` #### 关键注意事项: 1. **线程内资源初始化** ```python # 正确方式 def inference_thread(engine): engine.create_thread_resources() # 线程内初始化 while True: engine.infer(input_data) ``` 2. **设备显式设置** ```python # 多GPU环境必须指定 torch.cuda.set_device(device_id) # 或 cuda.Device(device_id).make_context() ``` 3. **PyTorch上下文激活** ```python # 确保CUDA上下文存在 if torch.cuda.is_available(): torch.cuda.init() # 显式初始化 dummy = torch.empty(1, device="cuda") # 隐式激活 ``` 4. **版本兼容性** ```python # 检查环境兼容性 assert torch.__version__ >= "1.10.0" assert trt.__version__ >= "8.4.0" ``` --- ### 错误处理流程图 ```mermaid graph TD A[invalid device context] --> B{是否跨线程使用资源?} B -->|是| C[在推理线程内创建流] B -->|否| D{是否显式设置设备?} D -->|未设置| E[添加 torch.cuda.set_device] D -->|已设置| F{是否激活上下文?} F -->|未激活| G[添加 torch.cuda.init] F -->|已激活| H[检查CUDA版本兼容性] ``` > 在CUDA编程模型中,上下文激活状态必须满足: > $$\forall t \in \text{Threads}, \exists C_{\text{active}} \mid t_{\text{start}} \leq t \leq t_{\text{end}}}$$ > 任何线程在执行CUDA操作前必须确保上下文激活[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值