DocuSeal 开源 TensorRT Ruby 绑定,在 Rails 单体中跑 GPU 推理
开源电子签名平台 DocuSeal 在 Ruby on Rails 单体中直接运行计算机视觉模型进行表单字段检测,自研并…
开源电子签名平台 DocuSeal 在其 Ruby on Rails 单体应用中直接集成了 GPU 推理能力,用于自动检测 PDF 表单上需要填写或签名的字段。整套 AI 工作负载跑在 NVIDIA GPU 实例上的 Ruby Sidekiq 进程中,没有引入 Python 微服务,也没有拆分额外的推理服务。团队还把为这一方案自研的 TensorRT Ruby 绑定以 Apache 2.0 协议开源。
整体架构:Rails 单体内的 GPU 推理
DocuSeal 的 AI 字段检测基于一个计算机视觉模型,可以从公开的 PDF 表单样本中识别出待填写的字段位置。为了不破坏 Rails 单体架构的一致性,推理流水线完全用 Ruby 写就,运行在带有 NVIDIA T4 GPU 的工作节点上,由 Sidekiq 异步任务驱动。前端通过 Redis pub/sub 接收每页的检测结果,再以 Server-Sent Events 推回浏览器,实现流式展示。
TensorRT Ruby 绑定
要在 GPU 上高效运行视觉模型,NVIDIA 提供的官方方案是 TensorRT,但 TensorRT 只暴露 C++ 接口,Ruby 生态此前并没有可用绑定。DocuSeal 用 Rice 写了一个非常薄的 C++ 绑定,只链接前向推理真正需要的几个方法:加载 engine、检查 tensor 元信息、绑定显存、执行前向、同步 CUDA stream。整个绑定收敛到一个 TensorRT::Engine 类,11 个方法构成,安装时依赖系统已装好的 TensorRT 和 CUDA。
三阶段推理流水线
- 预处理:用 PDFium 渲染 PDF 页面,等比缩放到模型输入分辨率后补齐为正方形,按 ImageNet 均值与方差做归一化,再从 HWC 转为 CHW。图像处理使用 ruby-vips,张量操作使用 Numo(NumPy 的 Ruby 替代)。
- 前向推理:输入张量按 engine 声明的 dtype 转换,序列化为二进制字符串写入 host buffer,再拷入 GPU VRAM。调用
enqueue提交 CUDA 流任务后立即返回,retrieve阻塞等待并读取输出。生产环境中 host buffer 按线程预分配并复用,稳态推理不再产生 Ruby 侧的内存分配,每个 Sidekiq 线程持有独立的 engine 实例。 - 后处理:对输出做 sigmoid 取置信度,按最高分确定类别,将框从中心格式转角点格式,再反向还原预处理中的缩放与补齐,最后过滤低置信度检测并用非极大值抑制去重,统一归一化到表单构建器使用的 0..1 坐标。每个保留下来的检测生成一个
Field对象。
异步流水线提升吞吐
预处理和后处理在 CPU 上跑,前向推理在 GPU 上跑,同步执行时 CPU 和 GPU 会互相等待。DocuSeal 把 enqueue 与 stream_synchronize 拆开,让前一页的前向推理在 GPU 上跑的同时,CPU 可以并行渲染并预处理下一页。论文式写法是:把 retrieve 包成 lambda 返回,把同步延迟显式化交给外层页面循环来调度。实测在相同模型与输入下,异步流水线相比同步执行获得约 80% 的吞吐提升。
结果回传与部署
结果流式回传采用 Redis pub/sub 桥接 Sidekiq 工作进程和 Rails Web 进程,Web 端使用 ActionController::Live 的 SSE 控制器把每页字段以 JSON 数组推送给浏览器,用户可以看到实时处理进度。部署侧直接基于 nvcr.io/nvidia/tensorrt 基础镜像构建包含 TensorRT、CUDA 和匹配驱动的 Dockerfile.tensorrt,避免自行维护整套 GPU 运行时依赖。
整体来看,这是一份典型的「不拆微服务、把 AI 留在单体里」的工程实践:围绕一个不到 11 个方法的 Ruby 绑定,加上异步流水线设计和 SSE 流式回传,把原本需要 Python 推理服务的视觉能力完整地嵌入到了 Rails 应用之中,相关绑定代码已在 GitHub 上以 Apache 2.0 开源。
