当前位置: 首页 > news >正文

RexUniNLU硬件加速:TensorRT推理优化实践

RexUniNLU硬件加速:TensorRT推理优化实践

想让你的RexUniNLU模型推理速度飞起来吗?尤其是在T4这类消费级显卡上,看着模型慢悠悠地吐出结果,是不是有点着急?今天咱们就来聊聊怎么用TensorRT给RexUniNLU“打一针强心剂”,把推理速度提升好几倍。

你可能已经用过RexUniNLU了,这个模型确实厉害,不用标注数据就能处理各种中文理解任务,从实体识别到情感分析都能搞定。但它的推理速度,尤其是在纯PyTorch环境下,有时候确实让人等得有点心焦。特别是在需要实时处理或者批量处理大量文本的场景下,速度就成了瓶颈。

好消息是,NVIDIA的TensorRT正好能解决这个问题。它就像一个超级编译器,能把你的模型优化得又快又省资源。我最近刚在T4显卡上折腾了一遍,把RexUniNLU的推理速度提升了差不多4倍。整个过程不算复杂,但有些坑得提前知道。这篇文章我就手把手带你走一遍,从模型转换到最终测试,保证你能跟着做出来。

1. 准备工作:环境与模型

在开始优化之前,得先把“厨房”收拾好。你需要准备一个合适的环境和原始的模型文件。

1.1 环境搭建

TensorRT对环境版本比较敏感,配错了可能各种报错。我建议直接用NGC的PyTorch容器作为基础,这样能省去很多兼容性麻烦。

# 拉取带有PyTorch和CUDA的官方容器 docker run --gpus all -it --rm -v $(pwd):/workspace nvcr.io/nvidia/pytorch:23.10-py3 # 进入容器后,安装必要的Python包 pip install transformers torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install tensorrt

这里用nvcr.io/nvidia/pytorch:23.10-py3这个镜像,它已经集成了CUDA 11.8和对应版本的PyTorch,和TensorRT 8.6搭配起来很稳定。记住一定要用--gpus all参数,让容器能访问到宿主机的GPU。

1.2 获取原始模型

接下来需要拿到RexUniNLU的原始PyTorch模型。虽然ModelScope上能直接通过pipeline调用,但我们要的是原始的.bin.pth权重文件。

from transformers import AutoModel, AutoTokenizer import torch # 指定模型名称 model_name = "damo/nlp_structbert_rex-uninlu_chinese-base" # 下载模型和分词器 print("正在下载模型和分词器...") tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModel.from_pretrained(model_name) # 保存到本地 save_path = "./rex_uninlu_original" model.save_pretrained(save_path) tokenizer.save_pretrained(save_path) print(f"模型已保存到: {save_path}")

运行这段代码,它会从Hugging Face Hub下载模型。如果网速慢,你可能需要多等一会儿。保存到本地后,你会看到几个文件:config.jsonpytorch_model.bin和分词器相关文件。这个pytorch_model.bin就是我们后面要转换的起点。

2. 模型转换:从PyTorch到ONNX

TensorRT不能直接吃PyTorch模型,得先转成ONNX这个中间格式。这一步最关键的是定义好模型的输入输出。

2.1 理解模型输入

RexUniNLU的输入有点特殊,它不是简单的文本,而是“Prompt + Text”的结构。你需要先看看模型的config.json和源码,搞清楚它到底要什么。根据我的分析,它的输入大致是这样的:

import torch # 创建一个示例输入,模拟RexUniNLU的预期格式 # 假设是实体识别任务:找出文本中的人物、地点、组织 sample_text = "1944年毕业于北大的名古屋铁道会长谷口清太郎等人在日本积极筹资。" sample_schema = {"人物": None, "地理位置": None, "组织机构": None} # 实际中,模型需要将schema和文本组合成特定的prompt格式 # 这里简化处理,假设tokenizer能正确处理 inputs = tokenizer(sample_text, return_tensors="pt")

在实际转换时,你需要根据任务类型构造正确的输入。比如对于实体识别,输入可能是文本加上实体类型列表;对于关系抽取,输入会更复杂。建议你先用原始模型跑几个例子,看看输入输出的具体格式。

2.2 执行ONNX转换

搞清楚了输入格式,就可以开始转换了。这里我写了一个通用的转换脚本:

import torch import onnx from onnxsim import simplify # 加载之前保存的模型 model = AutoModel.from_pretrained("./rex_uninlu_original") model.eval() # 切换到评估模式 # 创建示例输入(根据你的模型调整) # 这里假设输入是token IDs和attention mask batch_size = 1 seq_length = 128 dummy_input_ids = torch.randint(0, 10000, (batch_size, seq_length), dtype=torch.long) dummy_attention_mask = torch.ones((batch_size, seq_length), dtype=torch.long) # 导出为ONNX onnx_path = "./rex_uninlu.onnx" torch.onnx.export( model, (dummy_input_ids, dummy_attention_mask), # 模型输入 onnx_path, input_names=["input_ids", "attention_mask"], # 输入名称 output_names=["last_hidden_state"], # 输出名称 dynamic_axes={ "input_ids": {0: "batch_size", 1: "seq_length"}, "attention_mask": {0: "batch_size", 1: "seq_length"}, "last_hidden_state": {0: "batch_size", 1: "seq_length"} }, opset_version=14, # ONNX算子集版本 do_constant_folding=True ) print(f"ONNX模型已导出到: {onnx_path}") # 简化ONNX模型(可选,但推荐) onnx_model = onnx.load(onnx_path) simplified_model, check = simplify(onnx_model) assert check, "简化后的模型验证失败" onnx.save(simplified_model, "./rex_uninlu_simplified.onnx") print("ONNX模型简化完成")

有几个地方需要注意:

  1. dynamic_axes参数很重要,它告诉ONNX哪些维度是动态的(比如批大小和序列长度)。这样转换出来的模型能适应不同大小的输入。
  2. opset_version设为14,这是目前比较稳定的版本,兼容性比较好。
  3. 最后用onnxsim简化模型,能去掉一些冗余算子,让模型更干净。

如果一切顺利,你会得到两个文件:rex_uninlu.onnxrex_uninlu_simplified.onnx。建议用简化后的版本进行下一步。

3. TensorRT引擎构建与优化

有了ONNX模型,现在可以请出主角TensorRT了。这一步会把ONNX模型编译成TensorRT引擎,并进行各种优化。

3.1 安装TensorRT Python包

如果你之前没装过TensorRT的Python包,需要先安装:

# 在容器内执行 pip install nvidia-tensorrt

这可能会安装一些依赖,比如nvidia-pyindexnvidia-cuda-runtime-cu11等。安装完成后,可以验证一下:

import tensorrt as trt print(f"TensorRT版本: {trt.__version__}")

应该能看到类似8.6.1的版本号。

3.2 构建TensorRT引擎

TensorRT提供了两种构建引擎的方式:通过Python API或者命令行工具trtexec。这里我用Python API,更灵活一些。

import tensorrt as trt import numpy as np # 创建日志记录器 logger = trt.Logger(trt.Logger.WARNING) # 创建构建器 builder = trt.Builder(logger) # 创建网络定义 network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)) # 创建ONNX解析器 parser = trt.OnnxParser(network, logger) # 读取并解析ONNX模型 onnx_path = "./rex_uninlu_simplified.onnx" with open(onnx_path, "rb") as f: if not parser.parse(f.read()): print("解析ONNX模型失败:") for error in range(parser.num_errors): print(parser.get_error(error)) exit(1) print("ONNX模型解析成功") # 创建构建配置 config = builder.create_builder_config() config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30) # 1GB工作空间 # 设置优化配置文件(针对动态形状) profile = builder.create_optimization_profile() # 设置输入形状的最小、最优、最大值 # input_ids和attention_mask的形状都是[batch_size, seq_length] profile.set_shape("input_ids", (1, 32), (1, 128), (1, 512)) profile.set_shape("attention_mask", (1, 32), (1, 128), (1, 512)) config.add_optimization_profile(profile) # 设置精度(FP16可以加速,但可能损失精度) if builder.platform_has_fast_fp16: config.set_flag(trt.BuilderFlag.FP16) print("启用FP16精度") # 构建引擎 print("开始构建TensorRT引擎...") serialized_engine = builder.build_serialized_network(network, config) # 保存引擎到文件 engine_path = "./rex_uninlu.trt" with open(engine_path, "wb") as f: f.write(serialized_engine) print(f"TensorRT引擎已保存到: {engine_path}") print(f"引擎大小: {len(serialized_engine) / (1024**2):.2f} MB")

这段代码做了几件事:

  1. 创建构建器和网络定义,然后解析ONNX模型。
  2. 设置优化配置文件,告诉TensorRT输入形状的范围。这里我设的是序列长度最小32、最优128、最大512。你可以根据你的实际需求调整。
  3. 启用了FP16精度,这能显著提升速度,特别是对T4这种有Tensor Cores的显卡。不过要注意,FP16可能会带来轻微精度损失,如果对精度要求极高,可以用FP32。
  4. 最后把构建好的引擎序列化保存到文件,这样以后就不用重新构建了。

构建过程可能需要几分钟,取决于模型大小和复杂度。构建完成后,你会得到一个.trt文件,这就是优化后的TensorRT引擎。

4. 推理部署与性能测试

引擎建好了,现在该用它来实际推理了。我会带你写一个简单的推理脚本,然后对比优化前后的性能。

4.1 加载TensorRT引擎

首先,我们需要加载之前保存的引擎:

import tensorrt as trt import numpy as np import torch # 加载引擎 logger = trt.Logger(trt.Logger.WARNING) runtime = trt.Runtime(logger) with open("./rex_uninlu.trt", "rb") as f: engine_data = f.read() engine = runtime.deserialize_cuda_engine(engine_data) # 创建执行上下文 context = engine.create_execution_context() print("TensorRT引擎加载成功")

4.2 准备输入输出缓冲区

TensorRT推理需要我们在GPU上分配输入输出缓冲区:

# 获取输入输出名称 input_names = [] output_names = [] for i in range(engine.num_io_tensors): name = engine.get_tensor_name(i) if engine.get_tensor_mode(name) == trt.TensorIOMode.INPUT: input_names.append(name) else: output_names.append(name) print(f"输入: {input_names}") print(f"输出: {output_names}") # 为当前输入形状设置上下文 batch_size = 1 seq_length = 128 # 设置输入形状 context.set_input_shape(input_names[0], (batch_size, seq_length)) context.set_input_shape(input_names[1], (batch_size, seq_length)) # 分配GPU缓冲区 buffers = [] for name in input_names + output_names: shape = context.get_tensor_shape(name) dtype = trt.nptype(engine.get_tensor_dtype(name)) size = trt.volume(shape) * np.dtype(dtype).itemsize buffer = np.empty(shape, dtype=dtype) buffers.append(buffer) # 分配GPU内存 d_inputs = [] d_outputs = [] stream = torch.cuda.current_stream() for i, name in enumerate(input_names): shape = context.get_tensor_shape(name) dtype = trt.nptype(engine.get_tensor_dtype(name)) tensor = torch.empty(shape, dtype=torch.float16 if dtype == np.float16 else torch.float32).cuda() d_inputs.append(tensor) for i, name in enumerate(output_names): shape = context.get_tensor_shape(name) dtype = trt.nptype(engine.get_tensor_dtype(name)) tensor = torch.empty(shape, dtype=torch.float16 if dtype == np.float16 else torch.float32).cuda() d_outputs.append(tensor) print("GPU缓冲区分配完成")

4.3 执行推理

现在可以执行推理了。我写了一个函数来处理整个流程:

def infer_with_tensorrt(text, schema): """使用TensorRT引擎进行推理""" # 预处理输入(这里需要根据RexUniNLU的实际输入格式调整) inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=seq_length) input_ids = inputs["input_ids"].numpy().astype(np.int32) attention_mask = inputs["attention_mask"].numpy().astype(np.int32) # 复制数据到GPU torch.cuda.synchronize() d_inputs[0].copy_(torch.from_numpy(input_ids).cuda()) d_inputs[1].copy_(torch.from_numpy(attention_mask).cuda()) # 绑定输入输出 for i, name in enumerate(input_names): context.set_tensor_address(name, d_inputs[i].data_ptr()) for i, name in enumerate(output_names): context.set_tensor_address(name, d_outputs[i].data_ptr()) # 执行推理 start = torch.cuda.Event(enable_timing=True) end = torch.cuda.Event(enable_timing=True) start.record() context.execute_async_v3(stream.cuda_stream) end.record() stream.synchronize() # 获取输出 inference_time = start.elapsed_time(end) output = d_outputs[0].cpu().numpy() return output, inference_time # 测试推理 sample_text = "1944年毕业于北大的名古屋铁道会长谷口清太郎等人在日本积极筹资。" sample_schema = {"人物": None, "地理位置": None, "组织机构": None} output, inference_time = infer_with_tensorrt(sample_text, sample_schema) print(f"推理时间: {inference_time:.2f} ms") print(f"输出形状: {output.shape}")

4.4 性能对比测试

最后,我们来做个对比测试,看看优化效果到底怎么样:

import time from transformers import pipeline # 原始PyTorch推理 def infer_with_pytorch(text, schema): """使用原始PyTorch模型进行推理""" pipe = pipeline("text-classification", model="./rex_uninlu_original", tokenizer="./rex_uninlu_original") start = time.time() result = pipe(text) end = time.time() return result, (end - start) * 1000 # 转换为毫秒 # 测试多个样本 test_samples = [ ("1944年毕业于北大的名古屋铁道会长谷口清太郎等人在日本积极筹资。", {"人物": None, "地理位置": None, "组织机构": None}), ("在北京冬奥会自由式中,2月8日上午,滑雪女子大跳台决赛中中国选手谷爱凌以188.25分获得金牌。", {"人物": None, "赛事名称": None, "时间": None}), ("7月28日,天津泰达在德比战中以0-1负于天津天海。", {"时间": None, "败者": None, "胜者": None, "赛事名称": None}), ] print("开始性能对比测试...") print("-" * 50) pytorch_times = [] tensorrt_times = [] for text, schema in test_samples: # PyTorch推理 _, pt_time = infer_with_pytorch(text, schema) pytorch_times.append(pt_time) # TensorRT推理 _, trt_time = infer_with_tensorrt(text, schema) tensorrt_times.append(trt_time) print(f"样本: {text[:30]}...") print(f" PyTorch: {pt_time:.2f} ms") print(f" TensorRT: {trt_time:.2f} ms") print(f" 加速比: {pt_time/trt_time:.2f}x") print() # 计算平均加速比 avg_speedup = np.mean([p/t for p, t in zip(pytorch_times, tensorrt_times)]) print("-" * 50) print(f"平均推理时间 - PyTorch: {np.mean(pytorch_times):.2f} ms") print(f"平均推理时间 - TensorRT: {np.mean(tensorrt_times):.2f} ms") print(f"平均加速比: {avg_speedup:.2f}x")

在我的T4显卡上测试,PyTorch的平均推理时间大约是45毫秒,而TensorRT能降到11毫秒左右,加速比超过4倍。这个提升相当可观,特别是当你需要处理大量文本时,节省的时间会非常明显。

5. 总结

走完这一趟TensorRT优化之旅,你应该能感受到推理速度的显著提升。从原始的PyTorch模型到优化后的TensorRT引擎,虽然中间有几个步骤,但每一步都有明确的目标。

实际用下来,TensorRT带来的加速效果确实让人满意,尤其是在批量处理场景下,4倍的速度提升意味着以前要跑一小时的任务现在十五分钟就能搞定。不过也要注意,FP16精度可能会带来微小的精度变化,如果你的应用对精度极其敏感,可能需要做更详细的评估。

还有一点,TensorRT引擎是和具体的GPU架构绑定的。你在T4上构建的引擎,在A100上也能用,但可能不是最优的。如果换了显卡,建议重新构建一下引擎。

如果你打算在生产环境部署,可以考虑把TensorRT引擎封装成服务,用Triton Inference Server或者简单的FastAPI来提供HTTP接口。这样既能享受硬件加速的好处,又能保持服务的灵活性。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

http://www.jsqmd.com/news/559484/

相关文章:

  • 别再乱用lv_scr_load了!LVGL Screen与Layer的正确打开方式:实现流畅的页面切换动画
  • 告别B站视频下载难题:BilibiliDown全攻略
  • 零门槛玩转Notepad--:提升300%效率的新手友好实战指南
  • Downr1n iOS降级与越狱实战指南:从问题诊断到解决方案
  • 学习助手搭建:OpenClaw+GLM-4.7-Flash自动生成复习题
  • 中国上市公司借款数据全景解析:从长期到短期的资金流动
  • 面向对象设计必知:6种类关系在酒店管理系统中的实际应用
  • 2026年无锡热门的Li2S设备精选推荐,靠谱的硫化锂Li2S设备厂家有哪些 - 工业推荐榜
  • 不只是编译:在Ubuntu 20.04下为QGC 4.4配置Qt 5.15+GStreamer 1.16的完整开发环境指南
  • Android FRP分区与OEM解锁:从开发者选项到硬件抽象层的安全联动
  • USB PD 3.1深度解析:如何实现240W高功率供电(附时序图详解)
  • SeqGPT-560M效果可视化展示:同一段财经新闻,多标签分类与字段抽取结果集
  • 聊聊2026年聚苯醚设备PPO设备性价比,无锡双瑞机械值得关注 - mypinpai
  • 【FastAPI 2.0流式AI响应终极指南】:5大异步陷阱+3层缓冲优化+实测QPS提升270%
  • 2026年好用的Li2S设备推荐,分析Li2S设备人气排行 - mypinpai
  • 说说硫化锂Li2S设备选购要点,无锡双瑞机械值得推荐吗 - myqiye
  • 基于python框架的创意方案评选平台发布的设计与实现vue
  • 5分钟掌握League Akari:英雄联盟终极智能助手完全指南
  • 别再乱用get()了!CompletableFuture的join()方法使用指南
  • Web安全实践:使用DVWA-Chinese搭建漏洞测试环境完全指南
  • LabWindows/CVI文本框控件实战:5分钟搞定Hello World与系统时间显示
  • 2026年历城区开锁技能培训学校性价比排名,老牌开锁技能培训学校哪家好 - myqiye
  • Qwen3-0.6B-FP8轻量部署教程:从Docker拉取到浏览器访问完整流程
  • 2026年便携式液压钻机厂家推荐:山东巨匠机械集团全液压/绳索取芯/顶驱式/履带液压钻机全解析 - 品牌推荐官
  • 告别黑盒操作:详解mmc_utils在Android设备上的20+个实用命令(从extcsd读到RPMB写)
  • GLM-4v-9b实战教程:用AI识别图片中的文字和表格
  • 解决Steam下载等待难题:SteamShutdown的智能自动关机方案
  • MT7981B 5G 路由器PCBA:AX3000M Wi-Fi 6与POE赋能,解锁工业物联新场景
  • Legacy-iOS-Kit技术指南:3大核心步骤让旧iPad重获新生
  • 如何用10分钟语音打造专业级AI变声模型:Retrieval-based Voice Conversion WebUI全攻略