☰
PaddleCV 系统设计思想深度解析:基于 DAG 的统一推理部署框架
2026/10/9 2:14:23 网站建设 项目流程
  • 人工智能
  • 深度学习
  • 计算机视觉
  • NLP
  • 语音

【免费下载链接】models

Officially maintained, supported by PaddlePaddle, including CV, NLP, Speech, Rec, TS, big models and so on.

项目地址:https://gitcode.com/gh_mirrors/mo/models
点击查看免费下载

导读

PaddleCV 是飞桨(PaddlePaddle)官方维护的视觉统一推理部署套件,其核心设计目标是用一套通用系统同时解决「深度学习单模型部署」与「多模型复杂串联系统部署」两大难题。本文以 系统设计思想 为骨架,结合仓库中配置模块、输入模块、算子体系与 DAG 执行器(framework.py)的真实实现,完整拆解 PaddleCV 的四大核心设计:配置模块、输入模块、三类算子与系统串联方案。读完本文,你将理解配置文件中ENV/MODEL/Inputs各字段的底层含义,掌握如何仅通过修改配置文件复现 OCR、行人分析等复杂系统串联,并具备基于ModelBaseOp/ConnectorBaseOp/OutputBaseOp自定义算子的完整能力。

一、设计目标:一个系统满足三类部署诉求

面对深度学习模型部署的多样性,飞桨模型团队为 PaddleCV 设定了三个核心目标:

  1. 通用性(Generality):系统既要满足单模型部署,又要支持多模型之间复杂的拓扑关系。同一套框架既能跑通 PP-PicoDet 这样的单模型,也能串联 PP-OCRv3 中「检测 → 抠图 → 识别」的级联流水线。
  2. 高可用性(High Availability):支持图片、视频、numpy 数组等多种输入类型,通过配置文件即可高效复现整套系统串联,无需修改代码。
  3. 高灵活性(High Flexibility):支持自定义算子便捷接入,通过统一的注册机制与基类继承,灵活实现定制化部署需求。

从仓库 ppcv/core/framework.py 的实现可以看到,这套设计目标被落实为「配置驱动 + DAG 调度 + 算子化封装」的整体架构。

二、整体框架设计

系统整体架构如下图所示,配置解析、输入解析与算子执行三条链路通过Pipeline → Executor → Op逐层衔接:

  • Pipeline(pipeline.py):负责解析配置文件(ConfigParser)、构建执行器(Executor),并统一处理图片/视频/数据三种输入类型的解码与批量组织。
  • Executor(framework.py):将模型配置组织为 DAG,按拓扑排序后的顺序逐个执行算子。
  • Op 算子:每个算子独立完成「输入过滤 → 计算 → 输出检查」的完整闭环。

从源码结构看,Pipeline.__init__依次完成ConfigParser解析、Executor构建,并在run()中根据输入类型分发到predict_images或predict_video,这是全系统的主调用链。

2.1 配置模块设计

配置模块是 PaddleCV 的「指挥中枢」,负责:

  1. 解析配置文件:将 YAML 配置文件拆分为**环境配置(ENV)与模型配置(MODEL)**两部分;
  2. 校验配置合规性:检查算子name是否存在、Inputs是否遵循{last_op_name}.{last_op_output_name}格式、device是否合法(仅允许CPU/GPU/XPU);
  3. 管理模型间串联关系:通过每个算子下的Inputs配置段建立有向边。

以上逻辑对应 config.py 中ConfigParser的__init__ / merge_cfg / check_cfg三个方法。

配置文件的三大段结构

以单模型示例 PP-PicoDet.yml 为例,配置整体分为:

image_shape: &image_shape 320 # YAML 锚点,供后续字段引用 ENV: # 环境配置段 min_subgraph_size: 3 # TensorRT 最小子图大小 trt_calib_mode: False # TensorRT 离线量化校准时设为 True cpu_threads: 1 # CPU 部署时的线程数 trt_use_static: False # 是否加载预生成的 TensorRT engine 文件 save_img: True # 是否保存可视化图片(默认输出到 output 目录) save_res: True # 是否保存结构化输出(默认输出到 output 目录) return_res: True # 是否返回全量结构化输出结果 MODEL: # 模型配置段(算子列表) - DetectionOp: # 算子类名 name: det # 算子实例名,同一配置文件中不能重复 param_path: paddlecv://models/picodet_s_320_coco_lcnet/model.pdiparams model_path: paddlecv://models/picodet_s_320_coco_lcnet/model.pdmodel batch_size: 2 image_shape: [3, *image_shape, *image_shape] # 引用锚点后的网络输入 shape PreProcess: # 预处理算子链 - Resize: interp: 2 keep_ratio: false target_size: [*image_shape, *image_shape] - NormalizeImage: is_scale: true mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] - Permute: PostProcess: # 后处理算子链 - ParserDetResults: label_list: paddlecv://dict/detection/coco_label_list.json threshold: 0.5 Inputs: # 输入字段声明 - input.image - DetOutput: # 输出算子 name: vis Inputs: - input.fn - input.image - det.dt_bboxes - det.dt_scores - det.dt_cls_names

各字段含义说明:

  • 环境配置段(ENV):管理run_mode、device、cpu_threads、trt_use_static等部署环境参数。从 OutputBaseOp 的实现可见,save_img、save_res、return_res会被输出算子读取,分别控制可视化图片保存、结构化结果保存与结果返回行为。
  • 模型配置段(MODEL):模型配置是一个算子列表,每个算子由「算子类名(如DetectionOp)+ 配置字典」组成。配置字典中PreProcess/PostProcess是预/后处理算子链,由 base.py 中create_operators通过反射逐个实例化;param_path与model_path支持本地路径、HTTP(S) 链接以及paddlecv://协议(见 download.py 的get_model_path,会自动下载到~/.cache/paddlecv/models)。
  • 串联声明(Inputs):每个算子(含第一个模型算子)都必须声明Inputs,格式固定为{上一个算子名}.{上一个算子输出字段名};第一个算子的上一个算子统一约定为input,即input.image、input.fn。
命令行更新任意配置项

配置模块还支持通过命令行直接覆盖配置文件中的任意配置项,便于开发者快速切换环境、替换模型、调整超参,无需手工编辑 YAML。该能力由 ArgsParser 中的-o / --opt参数实现:

# 以 tools/predict.py 为例,用 -o 覆盖 device 与 batch_size python -u tools/predict.py --config=configs/single_op/PP-PicoDet.yml \ --input=demo/000000014439.jpg \ -o ENV.device=GPU MODEL.0.batch_size=4

从 merge_cfg 的实现可以看到,-o参数支持点分键路径(如MODEL.0.batch_size,其中0为模型配置列表中算子的下标),会被逐层合并进env_cfg或model_cfg,从而在不改动原文件的前提下完成覆盖。

按任务自动获取配置文件

配置文件管理方面,系统针对每个任务(task)推荐对应的配置文件,并提供get_config_file接口实现自动下载:

import paddlecv paddlecv.get_config_file('detection')

该接口的真实实现位于 model_zoo.py:内部维护TASK_DICT任务映射表,将任务名映射到paddlecv://configs/...的配置地址,再调用get_config_path完成解析与自动下载(缓存在~/.cache/paddlecv/configs)。仓库中已预置的任务包括:

  • 单模型(single_op):PP-LCNet、PP-LCNetV2、PP-HGNet、PP-YOLOE+、PP-YOLOE、PP-YOLO、PP-YOLOv2、PP-PicoDet、PP-HumanSegV2、PP-LiteSeg、PP-MattingV1;
  • 串联系统(system):PP-OCRv2、PP-OCRv3、PP-OCRv3-IE、PP-OCRv3-SA、PP-OCRv3-TTS、PP-Structure、PP-ShiTuV2、PP-ShiTu、PP-Human、PP-Human-Attr、PP-Vehicle、PP-Vehicle-Attr、PP-TinyPose、Face-Detection-Attr。

配置目录按任务类型分为 单模型配置 与 串联系统配置 两处,各字段详细含义可参考 配置文件说明文档。

2.2 输入模块设计

输入模块负责解析输入文件格式并统一为系统内部的批量数据结构,支持以下类型:

  • 图片文件:jpg / jpeg / png / bmp(含大写扩展名);
  • 图片文件夹:自动扫描目录下所有支持的图片扩展名;
  • 视频文件:mp4 / avi / wmv / mov / mpg / mpeg / flv;
  • numpy 数据:单张或多张np.ndarray数组。

输入统一使用input字段作为接口,具体实现见 pipeline.py 中的_parse_input与run方法:

def _parse_input(self, input): if isinstance(input, np.ndarray): return [input], 'data' if isinstance(input, Sequence) and isinstance(input[0], np.ndarray): return input, 'data' im_exts = ['jpg', 'jpeg', 'png', 'bmp'] im_exts += [ext.upper() for ext in im_exts] video_exts = ['mp4', 'avi', 'wmv', 'mov', 'mpg', 'mpeg', 'flv'] video_exts += [ext.upper() for ext in video_exts] if isinstance(input, (list, tuple)) and isinstance(input[0], str): input_type = "image" images = [ image for image in input if any([image.endswith(ext) for ext in im_exts]) ] return images, input_type if os.path.isdir(input): input_type = "image" logger.info('Input path is directory, search the images automatically') images = set() infer_dir = os.path.abspath(input) for ext in im_exts: images.update(glob.glob('{}/*.{}'.format(infer_dir, ext))) images = list(images) return images, input_type ...

解析完成后,run根据输入类型分发:图片与 numpy 数据走predict_images(逐张解码并构建{'input.image': ..., 'input.fn': ...}批量结构),视频走predict_video(逐帧读取并通过cv2.VideoWriter写出结果,同时把frame_id传入执行器)。这也解释了为何所有串联配置中第一个算子的Inputs固定为input.image——它就是输入模块注入的起始数据字段。

2.3 算子实现方案

系统算子分为模型算子(MODEL)、**衔接算子(CONNECTOR)与输出算子(OUTPUT)**三部分,三类算子均有固定的输出格式和输出字段约定:

算子类型基类源码位置type() 返回值职责
模型算子 MODELModelBaseOpppcv/ops/models/*'MODEL'将单个模型的预处理、前向推理、后处理端到端封装
衔接算子 CONNECTORConnectorBaseOpppcv/ops/connector/*'CONNECTOR'连接模型算子的输入输出,如抠图、过滤、矫正、旋转
输出算子 OUTPUTOutputBaseOpppcv/ops/output/*'OUTPUT'决定单模型或复杂系统的输出形式,如可视化、结果保存
统一的注册机制

三类算子都通过 workspace.py 中的@register装饰器注册到全局global_config字典,保证类名全局唯一(重复注册会抛出ValueError)。执行器通过create(op_arch, op_cfg, env_cfg)按配置中的类名字符串创建算子实例:

def register(cls): if cls.__name__ in global_config: raise ValueError("Module class already registered: {}".format(cls.__name__)) global_config[cls.__name__] = cls return cls def create(cls_name, op_cfg, env_cfg): if cls_name not in global_config: raise ValueError("The module {} is not registered".format(cls_name)) cls = global_config[cls_name] return cls(op_cfg, env_cfg)
固定的输入/输出格式

所有算子的输入输出统一为a list of dict结构,列表中的每个元素代表一个待推理对象及其中间结果。例如图像分类算子的输入与输出为:

[ {"image": img1}, {"image": img2}, ]
[ {"image": img1, "class_ids": class_id1, "scores": scores1, "label_names": label_names1}, {"image": img2, "class_ids": class_id2, "scores": scores2, "label_names": label_names2}, ]

衔接算子同样遵循此约定,以抠图算子(BboxCropOp)为例,其输入为:

[ {"image": img1, "bbox": bboxes1}, {"image": img2, "bbox": bboxes2}, ]

模型算子与衔接算子的基类在__init__中都会将自身的输出字段统一加算子名前缀(self.name + '.' + key),从而保证全系统的输出键全局唯一、可被下游Inputs精确引用。

算子注册与新增的完整流程

新增一个算子需要完成「继承基类 + 实现接口 + 注册」三步:

  1. 模型推理算子:继承 ModelBaseOp,使用@register注册,实现__init__(model_cfg, env_cfg)、preprocess、postprocess、__call__等方法;可参考图像分类算子 ClassificationOp。
  2. 模型衔接算子:继承 ConnectorBaseOp,使用@register注册,实现__init__与__call__;可参考方向矫正算子 ClsCorrectionOp。
  3. 模型输出算子:继承 OutputBaseOp,使用@register注册,实现__init__与__call__;可参考 ClasOutput。

新增算子后还应补充基于该算子的单元测试,可参考 test_classification.py。详细实现流程参见 新增算子文档。

2.4 系统串联方案:DAG 调度与执行

系统通过**有向无环图(DAG)**串联各个算子并执行,这是 PaddleCV 支撑复杂多模型系统的核心机制。

拓扑构建与排序

每个算子必须指定Inputs字段,格式为{last_op_name}.{last_op_output_name},即包含前置算子名称和对应输出字段名。由此建立算子之间的拓扑关系,并通过拓扑排序决定算子执行顺序。这部分由 DAG 类 实现:

  • build_dag扫描每个算子的Inputs,以input.split('.')[0]提取前置算子名,构建graph(op → 后继 op)、rev_graph(op → 前置 op)与in_degrees(入度表);
  • topo_sort从入度为 0 的节点(即input)出发,逐层剥离,得到层级化拓扑序sort_result;若排序结果数量与节点总数不符则返回None(说明存在环)。
执行器运行机制

Executor 是串联执行的核心,其run方法按拓扑序执行每个算子:

  1. 构建 DAG 并获取拓扑排序后的order(跳过input节点);
  2. 遍历模型配置,通过create实例化所有算子,并按op.type() == 'OUTPUT'标记是否存在输出算子;
  3. build_dep统计每个输入字段被下游引用的次数,得到依赖计数表input_dep;
  4. 每个算子执行时,先通过filter_input按Inputs字段从全量结果中过滤出本算子所需输入,再调用算子__call__计算,最后check_output校验输出键与声明一致;
  5. update_res合并算子输出,并在某字段不再被任何后续算子引用时将其从全量结果中删除,保证各算子内部计算独立、内存占用可控。

从 BaseOp.filter_input 的实现可以看到,过滤本质是[{k: last[k] for k in input_keys} for last in last_outputs],即按算子声明的Inputs键从每个元素中抽取对应字段,这正是「执行过程中维护全量输出结果、按需过滤」设计的落地点。

串联实例:PP-OCRv3 三级流水线

以真实的串联系统配置 PP-OCRv3.yml 为例,可以看到 DAG 串联思想的具体落地。该流水线由 4 个算子构成:

MODEL: - OcrDbDetOp: # ① 文本检测模型算子 name: det ... Inputs: - input.image - PolyCropOp: # ② 衔接算子:按检测多边形抠图 name: crop Inputs: - input.image - det.dt_polys - OcrCrnnRecOp: # ③ 文本识别模型算子 name: rec ... Inputs: - crop.crop_image - OCROutput: # ④ 输出算子:可视化 + 保存 name: vis Inputs: - input.fn - input.image - det.dt_polys - rec.rec_text - rec.rec_score

执行顺序由 DAG 拓扑排序确定为input → det → crop → rec → vis:检测算子输出dt_polys多边形字段供crop引用,crop输出的crop_image供识别算子rec引用,最终vis同时消费det与rec的结果完成可视化。crop算子即为 PolyCropOp 中注册的衔接算子。整个串联过程无需任何手写代码,仅通过Inputs字段声明依赖即可复现,这正是「高可用性:通过配置文件即可高效实现复现系统串联」目标的直接体现。

三、从配置到执行:一条完整调用链

综合前文,可以将 PaddleCV 从配置到输出的完整调用链总结如下(对应 tools/predict.py 与 pipeline.py):

tools/predict.py --config --input │ ┌────────────────────────────────────────────┐ ▼ │ Pipeline (engine/pipeline.py) │ 输入解码 ├─ _parse_input:图片/图片夹/视频/numpy 识别 │ │ │ run → predict_images / predict_video │ ▼ └────────────────────────────────────────────┘ ConfigParser (core/config.py) ├─ 拆分 ENV / MODEL,合并 -o 命令行覆盖项 └─ check_cfg 校验合规性 ▼ Executor (core/framework.py) ├─ DAG.build_dag:由 Inputs 建立边与入度表 ├─ topo_sort:拓扑排序确定执行顺序 ├─ create:按类名实例化 MODEL/CONNECTOR/OUTPUT └─ run:逐算子 filter_input → __call__ → update_res ▼ 输出:可视化图片 / 结构化结果 / 返回值

从源码结构看,该链路的每一环都有明确职责边界:ConfigParser只负责配置的解析、合并与校验;DAG只负责拓扑关系的建立与排序;Executor负责调度与结果生命周期管理;各类 Op 只负责自身计算。这种低耦合设计使得「替换模型、增减串联环节、接入自定义算子」都只需修改配置或新增一个注册算子。

四、小结

PaddleCV 系统设计思想的精髓可以概括为四句话:

  • 配置驱动:ENV管环境、MODEL管算子、Inputs管串联,一行配置即可替换模型或重构流水线;
  • 输入统一:图片、图片文件夹、视频、numpy 数组四种输入统一抽象为input.image/input.fn字段;
  • 算子三分:模型算子(MODEL)封装推理全流程,衔接算子(CONNECTOR)处理模型间数据变换,输出算子(OUTPUT)决定结果形态,三者共享list of dict数据协议与@register注册机制;
  • DAG 调度:通过Inputs声明依赖、拓扑排序确定顺序、依赖计数裁剪中间结果,兼顾单模型部署与复杂多模型串联。

对于想要深入实践的读者,建议按以下路径继续探索本仓库:

  • 系统设计思想(本文的原始依据)
  • 配置文件说明:字段级配置详解
  • 新增算子文档:三类算子的自定义开发指南
  • 快速上手:安装、预测部署与命令行参数说明
  • 单模型配置示例 PP-PicoDet.yml 与系统配置示例 PP-OCRv3.yml
  • 核心实现 framework.py、config.py、pipeline.py
  • 人工智能
  • 深度学习
  • 计算机视觉
  • NLP
  • 语音

【免费下载链接】models

Officially maintained, supported by PaddlePaddle, including CV, NLP, Speech, Rec, TS, big models and so on.

项目地址:https://gitcode.com/gh_mirrors/mo/models
点击查看免费下载

相关推荐

上一篇:Mermaid Live Editor:3分钟创建专业流程图的终极免费工具
下一篇:SMUDebugTool终极指南:轻松解锁AMD处理器隐藏性能的完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询