1. RT-DETR 导出 ONNX 后,为什么还要折腾 onnxruntime 部署
RT-DETR 是百度开源的一套实时端到端目标检测算法,它把 DETR 系列里最让人头疼的 NMS 后处理直接省掉了,速度和精度在同类里都挺能打,Ultralytics 官方仓库也已经支持。很多朋友在训练完之后会先把rtdetr-l.pt导出成 ONNX,然后想用 onnxruntime 做推理部署,因为 onnxruntime 跨平台、依赖少、CPU/GPU 都能跑,特别适合把模型塞进一个轻量服务里。
但真正动手时,问题往往不在模型本身,而在“周边”:导出 ONNX 时 opset 选多少、LayerNormalization 会不会被拆散、输入输出张量叫什么名字、后处理坐标怎么还原、GPU 版和 CPU 版 onnxruntime 装重了怎么办。更麻烦的是,如果你同时还在用别的 AI 工具(比如代码补全、模型对话、Agent 编排),每个工具一套 Key、一套配置,管理起来非常乱。这篇就把两件事合在一起讲:RT-DETR 在 onnxruntime 上的完整推理链路,以及用 TaoToken 统一 Key 通道来管理多工具配置,最后给一份可复制的config.toml骨架。
适合谁看:已经拿到 RT-DETR 权重、想把它部署成可调用服务的开发者;手上有多个 AI 工具、Key 散落各处想统一收口的同学;以及第一次接触 onnxruntime 推理、需要一份能直接跑通的脚本的人。
2. 前置准备:TaoToken 统一 Key 与 onnxruntime 环境
2.1 为什么用 TaoToken 收口多工具 Key
部署 RT-DETR 本身不需要联网,但围绕它的开发流程里通常还有别的 AI 能力:写推理脚本时用代码补全、调 prompt 时用模型对话、跑批量任务时用 Agent。这些工具如果各自申请 Key,配置会散在环境变量、IDE 插件、脚本里,换机器就得重新配一遍。TaoToken 的思路是提供一个统一的 API 通道和 Key 管理入口,把模型对话、编码计划、控制台、API Keys 这些能力集中到一处,你只需要维护一份配置。
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= API 地址:https://taotoken.net/api
需要说明的是,TaoToken 在这里扮演的是“统一 Key 与 API 通道”的角色,它不替代 onnxruntime,也不替代你的编辑器,RT-DETR 的推理仍然在本地 onnxruntime 里完成。两者是并行的:模型推理走本地,开发辅助走统一通道。
2.2 安装 onnxruntime(GPU 与 CPU 二选一)
onnxruntime 分 GPU 版和 CPU 版,直接 pip 安装即可:
# GPU 版本 pip install onnxruntime-gpu # CPU 版本 pip install onnxruntime注意:GPU 版和 CPU 版建议只装一个。如果两个都装了,运行时默认可能落到 CPU 版本,导致你以为在用 GPU 其实没有。可以用
ort.get_device()确认当前实际使用的设备。
2.3 导出 RT-DETR 为 ONNX
用 Ultralytics 的命令行导出,关键是opset=17和simplify=True:
yolo export model=rtdetr-l.pt format=onnx opset=17 simplify=Trueopset=17的原因:ONNX 从版本 17 才开始原生支持 LayerNormalization 算子。如果 opset 小于 17,导出时会把 LayerNormalization 拆成一堆子算子,图结构变得很碎,既不好看也影响推理效率。simplify=True会调用 onnx-simplifier 把零碎算子合并,导出的rtdetr-l.onnx更干净。
导出后建议先确认一下模型结构,用 Netron 打开看一眼输入输出,正常应该是单个输入images,形状[1, 3, 640, 640],单个输出output0,形状[1, 300, 84]。300 表示最多检测 300 个目标,84 = 4 个坐标(cx, cy, w, h)+ 80 个类别置信度。
3. 可复制配置:config.toml 骨架与推理脚本
3.1 config.toml 配置骨架
下面这份config.toml把模型路径、onnxruntime provider、TaoToken 统一通道、推理参数都收在一起。你可以直接复制,按需改路径和 Key。
# config.toml —— RT-DETR onnxruntime 部署 + TaoToken 统一 Key [model] # 导出的 ONNX 模型路径 onnx_path = "./rtdetr-l.onnx" # 输入尺寸,与导出时保持一致 input_width = 640 input_height = 640 # 置信度阈值 conf_threshold = 0.5 [onnxruntime] # GPU 用 CUDAExecutionProvider,CPU 用 CPUExecutionProvider providers = ["CUDAExecutionProvider", "CPUExecutionProvider"] # 线程数,CPU 推理时可调 intra_op_num_threads = 4 [taotoken] # 统一 API 通道地址 base_url = "https://taotoken.net/api" # 统一 Key,建议从环境变量注入,不要硬编码进仓库 api_key = "${TAOTOKEN_API_KEY}" # 默认使用的模型对话入口 chat_model = "claude-sonnet" # 编码计划入口,用于 Agent / 长任务 coding_plan_endpoint = "/coding-plan" [logging] level = "INFO"注意:
api_key用${TAOTOKEN_API_KEY}占位,实际运行时从环境变量读取。把 Key 写进仓库是大忌,尤其是公开仓库。
3.2 加载模型并打印输入输出属性
先写一段最小代码,确认模型能加载、输入输出符合预期:
import onnxruntime as ort session = ort.InferenceSession( "rtdetr-l.onnx", providers=["CUDAExecutionProvider", "CPUExecutionProvider"] ) for inp in session.get_inputs(): print("input name: ", inp.name) print("input shape: ", inp.shape) print("input type: ", inp.type) for out in session.get_outputs(): print("output name: ", out.name) print("output shape: ", out.shape) print("output type: ", out.type)正常输出应该是:
input name: images input shape: [1, 3, 640, 640] input type: tensor(float) output name: output0 output shape: [1, 300, 84] output type: tensor(float)如果 providers 里 CUDA 没生效,onnxruntime 会静默回退到 CPU,所以打印一下ort.get_device()确认。
3.3 数据预处理
预处理用 OpenCV + NumPy,流程和 YOLOv8 一致:BGR 转 RGB、resize 到 640x640、归一化、HWC 转 CHW、扩维到 NCHW。
import cv2 import numpy as np def prepare_input(bgr_image, width, height): image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB) image = cv2.resize(image, (width, height)).astype(np.float32) image = image / 255.0 image = np.transpose(image, (2, 0, 1)) input_tensor = np.expand_dims(image, axis=0) return input_tensor image = cv2.imread("soccer.jpg") image_height, image_width, _ = image.shape input_tensor = prepare_input(image, 640, 640) print("input_tensor shape: ", input_tensor.shape)处理完input_tensor的形状是[1, 3, 640, 640],和模型输入对齐。
3.4 推理与后处理
推理一行搞定,后处理因为 RT-DETR 不需要 NMS,所以非常短:
outputs = session.run(None, {session.get_inputs()[0].name: input_tensor}) output = np.squeeze(outputs[0]) print("output shape: ", output.shape) # (300, 84) for out in output: confidence = out[4:].max() if confidence < 0.5: continue class_id = out[4:].argmax() cx, cy, bw, bh = out[:4] xmin = (cx - 0.5 * bw) * image_width xmax = (cx + 0.5 * bw) * image_width ymin = (cy - 0.5 * bh) * image_height ymax = (cy + 0.5 * bh) * image_height cv2.rectangle(image, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (0, 255, 0), 2) cv2.putText(image, f"{class_id}:{confidence:.2f}", (int(xmin), int(ymin) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) cv2.imwrite("result.jpg", image)坐标是归一化的,乘以原图宽高就能还原。RT-DETR 的输出已经是端到端结果,不需要再做 NMS,这也是它相比 YOLO 系列部署时最省心的地方。
4. 验证请求:跑通链路并确认成功结果
4.1 本地推理验证
把上面几段拼成一个infer.py,直接运行:
python infer.py成功的话会在当前目录生成result.jpg,打开能看到检测框和类别标签。同时终端会打印:
input_tensor shape: (1, 3, 640, 640) output shape: (300, 84)如果output shape是(300, 84),说明模型加载、预处理、推理三步都通了。如果形状不对,多半是导出时 opset 或 simplify 参数没设对,回到 2.3 重新导出。
4.2 TaoToken 通道连通性验证
本地推理不依赖网络,但如果你要用 TaoToken 的统一通道做开发辅助,可以先验证一下通道是否可达。用 curl 发一个最小请求:
curl -X POST "https://taotoken.net/api/chat" \ -H "Authorization: Bearer ${TAOTOKEN_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"model": "claude-sonnet", "messages": [{"role": "user", "content": "ping"}]}'返回里有正常的响应体,说明 Key 和通道都通了。这一步和 RT-DETR 推理是独立的,但建议在正式写业务代码前先确认,避免后面排查时把网络问题和模型问题混在一起。
4.3 把两者串起来:推理脚本 + 统一配置
实际项目里,我习惯把config.toml放在项目根目录,推理脚本读配置,TaoToken 的 Key 从环境变量注入:
import os import tomllib with open("config.toml", "rb") as f: cfg = tomllib.load(f) api_key = os.environ.get("TAOTOKEN_API_KEY") assert api_key, "TAOTOKEN_API_KEY 未设置" onnx_path = cfg["model"]["onnx_path"] providers = cfg["onnxruntime"]["providers"]这样换机器时只需要改config.toml和设置一个环境变量,不用满项目找 Key。
5. 本篇常见错排查
5.1 LayerNormalization 被拆散,图很碎
现象:Netron 打开 ONNX,看到 LayerNormalization 被拆成一堆 Add、Mul、ReduceMean 之类的小算子。 原因:导出时 opset 小于 17。 解决:重新导出,加opset=17。如果已经导出且不想重导,可以用 onnx-simplifier 再简化一次,但根治还是重导。
5.2 GPU 版 onnxruntime 没生效
现象:装了onnxruntime-gpu,但推理速度跟 CPU 一样,ort.get_device()返回CPU。 原因:CPU 版和 GPU 版同时装了,或者 CUDA/cuDNN 版本不匹配。 解决:先pip uninstall onnxruntime onnxruntime-gpu,再只装 GPU 版;确认 CUDA 版本和 onnxruntime-gpu 要求的版本一致。providers 里把CUDAExecutionProvider放第一位。
5.3 输出形状不是 (300, 84)
现象:output shape打印出来是别的值。 原因:导出时用了不同的模型或不同的输入尺寸,或者 simplify 没生效导致输出层名字变了。 解决:确认导出命令是yolo export model=rtdetr-l.pt format=onnx opset=17 simplify=True,输入尺寸保持 640x640。如果输出层名字不是output0,用session.get_outputs()打印实际名字。
5.4 检测框位置偏移
现象:框画出来了,但位置整体偏了。 原因:后处理时用了 resize 后的尺寸而不是原图尺寸,或者坐标还原时忘了乘原图宽高。 解决:xmin/xmax乘image_width,ymin/ymax乘image_height,这里的宽高是cv2.imread读到的原图尺寸,不是 640。
5.5 TaoToken Key 读取失败
现象:脚本报TAOTOKEN_API_KEY 未设置。 原因:环境变量没导出,或者写在了别的 shell 会话里。 解决:在运行脚本的同一个终端里export TAOTOKEN_API_KEY=你的Key,或者写进.env用 dotenv 加载。不要把 Key 直接写进config.toml提交到仓库。
6. 接入与后续:按场景选对入口
排障和接入相关的问题,优先看 API Keys 和接入文档,里面把 Key 申请、通道地址、请求格式都写清楚了:
API Keys 入口:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
如果你主要是想验证模型对话效果、调 prompt,用模型对话入口更直接:
模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
如果是长期编码、Agent 编排这类需要持续跑的任务,走 Coding Plan 更合适:
Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
控制台可以统一查看用量和 Key 状态:
控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite
最后补一个实操里的小经验:RT-DETR 导出 ONNX 后,先用 Netron 确认输入输出,再写推理脚本,能省掉一大半“形状对不上”的排查时间。另外config.toml里的providers建议写成列表而不是单个字符串,这样 GPU 不可用时能自动回退到 CPU,服务不会直接挂掉。