打造 C++ 高性能 AI 推理服务:Drogon + ONNX Runtime
打造 C++ 高性能 AI 推理服务:Drogon + ONNX Runtime (YOLOv5 GPU)
在构建需要高并发、低延迟的深度学习推理服务时,性能至关重要。本项目展示了如何使用 C++ 的 Drogon 异步 Web 框架和 **ONNX Runtime (ORT)**,搭建一个基于 YOLOv5 模型的 GPU 加速目标检测 HTTP 服务。该架构完美结合了高性能 I/O 和计算密集型任务的并行处理。源码会在文章最后展示出来。github地址:https://github.com/yangtzeshore/Drogon-ONNX。
核心技术栈概览
本项目是典型的 C++ 高性能服务架构,融合了以下关键组件:
- Drogon (Web 框架): C++ 的高性能 Web 框架,采用非阻塞 I/O 模型,擅长处理高并发网络连接。
- ONNX Runtime (ORT): 微软推出的跨平台机器学习推理引擎,支持多种加速硬件,本项目中配置了 CUDA Execution Provider 实现 GPU 加速。
- OpenCV: 用于图像的读取、解码、预处理(如 Letterbox 缩放)以及后处理(如 NMS 非极大值抑制)。
- nlohmann/json: 轻量级的 C++ JSON 库,用于构造标准的 API 响应。
架构设计:I/O 与计算分离 (PredictController)
高并发服务的常见瓶颈在于 I/O (网络、文件读写) 和 CPU/GPU 计算 (图像解码、模型推理) 相互阻塞。本项目通过 Drogon 的异步特性和 C++ std::async 实现了 I/O 与计算的彻底分离。
1. 异步 I/O 线程 (Drogon Event Loop)
Drogon 的 I/O 线程(Event Loop)负责:
- 接收 HTTP 请求。
- 解析
multipart/form-data请求体,获取上传的图片文件内容。 - 并发控制: 检查当前的活跃推理任务数量 (
active_tasks)。如果达到设定的上限 (MAX_PARALLEL_INFERENCE = 4),立即返回503 Service Unavailable状态码,保护服务不过载。 - 任务投递: 使用
std::async(std::launch::async, ...)将计算密集型的任务立即投递到系统线程池(或计算线程池)中执行,从而不阻塞当前的 I/O Event Loop。
2. 独立计算线程 (System Thread Pool)
被 std::async 唤起的线程负责所有的计算工作:
- 图片解码: 使用
cv::imdecode将二进制文件内容解码为cv::Mat。 - 模型推理: 调用
InferenceManager::infer()完成预处理、GPU 推理和后处理。 - 构造响应: 将推理结果封装成 JSON 格式。
- 发送响应: 通过
callback(resp)将响应发送回 Drogon I/O 线程,由 I/O 线程完成网络发送。 - 计数器维护: 推理完成后,将活跃任务计数器
active_tasks减一。
模型管理与 GPU 加速 (InferenceManager)
InferenceManager 是项目的核心计算模块,负责模型的加载和推理流程。
1. 强制 GPU 加载
在 loadModel 方法中,通过配置 OrtCUDAProviderOptions,强制 ONNX Runtime 使用 CUDA Execution Provider。如果 CUDA 无法配置成功,程序将直接终止(LOG_FATAL),确保服务只运行在 GPU 加速模式下,防止在生产环境中意外降级到性能较低的 CPU 模式。
C++
1 | |
2. 完整的 YOLOv5 推理流程
infer 函数封装了目标检测的全部三个步骤:
- 预处理 (
preprocess): 接收原始图像,执行 Letterbox (保持宽高比缩放) 到 $640 \times 640$,然后进行 HWC -> CHW 布局转换和 $0-1$ 归一化。 - 模型运行 (
session_->Run): 将预处理后的float数组包装成Ort::Value,投递给 GPU 进行推理。 - 后处理 (
postprocess): 解析 ONNX Runtime 返回的原始张量 ([1, 25200, 85]),计算最终的置信度,执行 NMS (非极大值抑制),并将边界框坐标还原到原始图像尺寸。
C++ 标准和构建配置 (CMakeLists.txt)
项目的构建配置文件 CMakeLists.txt 体现了对现代 C++ 特性和依赖项的严格要求:
- C++ 标准检测: 项目会检查编译器对
any,string_view,coroutine等 C++20 特性的支持,并优先使用 C++20,否则退回到 C++17。最低要求为 C++17,以确保现代特性和库的兼容性。 - 核心依赖项: 项目明确地使用了 Drogon、
nlohmann_json、OpenCV 和onnxruntime四个关键库,通过target_link_libraries链接到主程序。
性能压测与优化
项目通过设置 MAX_PARALLEL_INFERENCE (在 PredictController.cc 中为 4) 实现了服务端限流。这能有效防止计算线程池过载,保持服务稳定。
配合 wrk 压测工具和提供的 post_image.lua 脚本,您可以轻松地对服务进行负载测试,验证其在 GPU 加速下的高吞吐量表现:
Bash
1 | |
通过这种 I/O 异步 + 计算并行 + GPU 加速的架构,本项目成功构建了一个高性能、高并发的 C++ 深度学习推理服务,是生产环境中部署 AI 模型的理想选择。
源码介绍
代码架构
- project
- build/
- controllers/
- PredictController.cc
- PredictController.h
- filters/
- models/
- onnxruntime_lib/
- plugins/
- test/
- views/
- CMakeLists.txt
- config.json
- config.yaml
- InferenceManager.cc
- InferenceManager.h
- main.cc
上述drogon的前后端代码,可以自行删减,本文专注于核心业务框架,多余的文件如果没有,不影响整体的运行和压测。onnxruntime_lib需要自己下载解压放到目录,也可以自行解压修改cmake文件。
CMakeLists.txt
1 | |
main.cc
1 | |
InferenceManager.h
1 | |
InferenceManager.cc
1 | |
PredictController.h
1 | |
PredictController.cc
1 | |
最后给出本项目的压测文件,也可以参考下:
post_image.lua
1 | |
延伸阅读
- Drogon 官方文档: 了解更多异步编程和控制器模式。
- ONNX Runtime 文档: 深入了解 CUDA Execution Provider 的配置选项。