ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Paddle PIR 序列化版本兼容机制:Save/Load 的 Patch 补丁体系与版本号管理实战

Paddle PIR 序列化版本兼容机制:Save/Load 的 Patch 补丁体系与版本号管理实战 Paddle PIR 序列化版本兼容机制Save/Load 的 Patch 补丁体系与版本号管理实战【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice 『飞桨』核心框架深度学习机器学习高性能单机、分布式训练和跨平台部署项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle导读本文面向需要维护 Paddle飞桨PIRPaddle Intermediate Representation程序序列化/反序列化兼容性的开发者系统讲解paddle/fluid/pir/serialize_deserialize模块中「patch 补丁 PIR 版本号」的版本兼容方案。读完本文你将掌握op_patches/op_pair_patches/attr_patches/type_patches四类补丁 YAML 的完整配置语法含 Attribute、OpresultAttribute、OpOperand、OpResult 的增删改规则理解DEVELOP_VERSION与RELEASE_VERSION的迭代流程与发版约束并能够结合仓库中的 CMake 配置、PatchBuilder源码与单测用例独立完成新增 patch、升级版本号、验证兼容性的全流程操作。一、为什么需要版本兼容机制PIR Save/Load 面临的问题PIRPaddle Intermediate Representation是飞桨新一代基于 MLIR 理念的中间表示承担算子级 IR 的表示、优化与执行。当Program通过WriteModule序列化落盘、再通过ReadModule反序列化恢复时存在一个天然矛盾模型文件的「编写时代」可能早于运行时的「读取时代」两者分属不同 Paddle 版本算子定义OpDef本身会随版本演化——Attribute 的名称、类型可能变化OpResult 的数量可能增减甚至某个 Type 类型会被整体废弃替换若旧版本写入的模型无法被新版本正确读取会导致跨版本模型加载失败或语义错误。为此PIR 引入了一套以 YAML patch 补丁为核心的版本兼容机制每次 PIR 定义发生变化时在patch目录下新增一个顺序编号的 YAML 文件描述「如何把旧版本写入的结构修补成当前版本可理解的结构」反序列化时根据模型文件记录的版本号自动叠加对应区间的补丁从而让旧模型平滑升级到新版本语义。该机制的完整文档位于 paddle/fluid/pir/serialize_deserialize/patch/Readme.md本文即以此为主线展开。二、patch 目录结构与补丁文件组织补丁文件统一存放在paddle/fluid/pir/serialize_deserialize/patch/目录下当前仓库中实际存在0.yaml4.yaml顺序编号的补丁文件其中2.yaml为空占位1.yaml、3.yaml、4.yaml为实际补丁内容Readme.md本机制的使用说明文档template.h.in生成patch.h的模板文件。此外单测专用的补丁位于 test/cpp/pir/serialize_deserialize/patch/ 目录含0.yaml、1.yaml配合 save_load_version_compat_test.cc 使用。补丁文件如何被编译进二进制补丁 YAML 并非运行时从磁盘读取而是在构建期被「内嵌」进代码。核心逻辑在 CMakeLists.txtfile(GLOB_RECURSE YAML_PATCH_FILES *.yaml) # change pir version when new patches are added add_definitions(-DDEVELOP_VERSION4) add_definitions(-DRELEASE_VERSION4) set(TEMPLATE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/patch/template.h.in) set(PATCH_HEADER ${CMAKE_CURRENT_BINARY_DIR}/patch/patch.h) configure_file(${TEMPLATE_FILE} ${PATCH_HEADER} ONLY) file(WRITE ${PATCH_HEADER} #include map\n#include string\n\n const std::mapstd::string, std::string yaml_files {\n) foreach(PATCH_FILE ${YAML_PATCH_FILES}) get_filename_component(FILENAME ${PATCH_FILE} NAME_WE) file(READ ${PATCH_FILE} FILE_CONTENT) set(CONTENT R\(${FILE_CONTENT})\) file(APPEND ${PATCH_HEADER} { \${FILENAME}\, ${CONTENT} },\n) endforeach() file(APPEND ${PATCH_HEADER} };\n)其工作方式为用GLOB_RECURSE收集patch目录下所有*.yaml通过configure_file先按 template.h.in 生成骨架yaml_files是std::mapstd::string, std::string用file(APPEND)把每个 YAML 文件内容以C 原始字符串字面量R(...)追加进patch.h键为不含扩展名的文件名如0、1最终将patch.h作为源码编入pir_save_load库。也就是说yaml_files是一个「文件名版本号→ YAML 内容」的静态映射表运行时补丁内容全部来自这份内嵌数据路径参数仅在测试场景下用于从外部文件加载见后文BuildPatch的path参数。三、补丁 YAML 配置详解补丁 YAML 是一个顶层包含四类补丁列表的文档op_patches、op_pair_patches、attr_patches、type_patches。所有 op 补丁均以op_name为标识actions为操作列表每个 action 由action操作名称与object操作对象构成部分操作还需type目标类型与data具体数值。3.1 op_patches针对单个 op 的操作op_patches用于描述对某个算子的 Attribute / OpresultAttribute / OpOperand / OpResult 的增删改一个 op 可以包含多条 actionop_patches: - op_name : pd_op.xxx # op_name 为标识 actions: # 补丁操作列表 - action : xxxx # 具体操作名称 object : xxxx # 具体操作对象 - action : xxxx # 可能会对一个 op 进行多次操作 object : xxxx type : xxxx data : xxx - op_name : builtin.xxx # 对另一个 op 进行操作 actions : - action : xxxx - op_name : pd_op.xxx actions : - action : xxxx object : xxx以仓库中真实的 4.yaml 为例它集中演示了「Attribute 类型升级」类补丁——把一批 op 的浮点属性从低精度类型统一修改为pir::DoubleAttribute、整型属性修改为pir::Int64Attributeop_patches: - op_name : pd_op.leaky_relu actions: - action : modify_attr object : negative_slope type : pir::DoubleAttribute - op_name : pd_op.softplus actions: - action : modify_attr object : beta type : pir::DoubleAttribute - action : modify_attr object : threshold type : pir::DoubleAttribute - op_name : onednn_op.fused_softplus actions: - action : modify_attr object : beta type : pir::DoubleAttribute - action : modify_attr object : threshold type : pir::DoubleAttribute - action : modify_attr object : fuse_alpha type : pir::DoubleAttribute - action : modify_attr object : fuse_beta type : pir::DoubleAttribute - op_name : pd_op.bicubic_interp actions: - action : modify_attr object : scale type : pir::ArrayAttribute data : - type: pir::DoubleAttribute - type: pir::DoubleAttribute ... - op_name : pd_op.moe_permute actions: - action : add_attr object : using_ue8m0_scale type : pir::BoolAttribute data : false - action : add_attr object : return_expert_indices type : pir::BoolAttribute data : false - action : add_attr object : override_buffer_size type : pir::Int32Attribute data : -1注意其中的规律modify_attr若只写object与type不写data表示仅修改属性类型、值保持不变若同时给出data则连同默认值一起覆盖例如pd_op.p_norm的porder类型改为pir::DoubleAttribute且默认值置为2。add_attr则必须提供完整的type与data作为新属性的初始值。Attribute / OpresultAttribute 增删改增 / 改Attribute与OpresultAttribute的新增和修改格式类似都需要指定object属性名、type属性类型、data属性值当目标是pir::ArrayAttribute时data是一个列表其中每个元素都要再标注自身的type与dataop_patches: - op_name : pd_op.data actions: - action : modify_output_attr # 修改 OpresultAttribute object : stop_gradient # 修改的具体属性名为 stop_gradient type : pir::ArrayAttribute # 修改属性类型为 ArrayAttribute data : # 修改属性为具体值 - type: pir::BoolAttribute # ArrayAttribute 类型需要对每一个子元素标识类型和值 data: false - action : modify_attr # 修改 Attribute与修改 OpresultAttribute 类似 object : name type : pir::StrAttribute data : B - op_name : pd_op.pool actions : - action : modify_attr # 修改 Attribute object : kernel_size type : pir::ArrayAttribute data : - type: pir::Int64Attribute # 对于修改 ArrayAttribute 内部类型的情况依然归属在 modify_attr 中但是 default 值置为空即可 - type: pir::Int64Attribute - op_name : builtin.parameter actions : - action : add_attr # 新增 Attribute object : new_attribute # 新增属性名为 new_attribute type : pir::StrAttribute # 新增属性类型为 StrAttribute data : new.attribute # 新增属性值为 new.attribute - action : add_output_attr # 新增 OpresultAttribute object : new_Attribute # 新增属性名为 new_output type : pir::Int64Attribute # 新增属性类型为 ArrayAttribute data : 1 # 新增属性为具体值删Attribute与OpresultAttribute的删除格式类似只需指定需要删除的对象object无需type与data- op_name : pd_op.fetch actions : - action : delete_attr # 删除 Attribute object : col # 删除属性名为 colOpOperand / OpResult 增删改OpOperand 不需要修改只存在增删场景OpResult Type 修改每个输出有且仅有一个 Type因此不存在增删 Type 的情况只有「修改」op_patches: - op_name : pd_op.data actions: - action : modify_output_type # 修改 Opresult Type object : 0 # 修改第几个输出的 Type type : pir::DenseTensorType # 修改属性类型为 DenseTensorType data : [pir::Float32Type,[-1,30],NCHW,[],0] # 修改属性为具体值data中依次是元素数据类型、shape支持 -1 动态维、data layout如NCHW、loD、以及第 0 个参数如初始值。OpOperand / OpResult 的增删需上升到 block 层处理其基本约束如下op 输入的删和op 输出的增不会破坏 program 结构删除输入直接修改输入列表即可增加输出时取当前 block 中 op id 的最大值在其上顺延新增即可单独 op 输入的增和单独 op 输出的删会造成网络结构错误因此直接报错op 作为组合pair出现时可以在组合内部进行「输出的删 输入的增」保证外部结构不变。op_patches: - op_name : pd_op.data actions : - action : add_output # 增加输出 object : 1 # 增加为第几个输出 type : pir::DenseTensorType # 修改属性类型为 DenseTensorType data : [pir::Float32Type,[-1,30],NCHW,[],0] # 修改属性为具体值 - op_name : pd_op.add actions : - action : delete_input # 删除输入 object : 0 # 删除第几个输入3.2 op_pair_patches输入输出作为组合修改当「A 算子的输出」恰好是「B 算子的输入」、且需要成对增删时必须使用op_pair_patches保持网络结构完整op_pair_patches: # 输入输出作为组合进行修改 - op_pair : [pd_op.full, pd_op.full_like] actions: - action : add_value # 增加输入和输出 object : [1,2] # 增加为第 1 个 op 的第几个输入第 2 个 op 的第几个输出 type : pir::DenseTensorType data : [pir::Float32Type,[1],NCHW,[],0] - action : delete_value # 删除输入和输出 object : [1, 2] # 删除第 1 个 op 的第几个输入第 2 个 op 的第几个输出其中object : [1,2]的含义是为组合中第 1 个 oppd_op.full增加/删除第 1 个输入为第 2 个 oppd_op.full_like增加/删除第 2 个输出。从源码看ApplyOpPairPatches在反序列化时会为成对新增的 value 分配同一个新的 value id取当前 block 最大 id 顺延并分别写入op1的OPRESULTS[ADD]与op2的OPOPERANDS[ADD]从而保证「生产者-消费者」关系在修补后依然成立// version_compat.cc - PatchBuilder::ApplyOpPairPatches for (uint64_t i 0; i op1_patch[ADD].size(); i) { max_id; op1_patch[ADD][i][VALUE_ID] max_id; op2_patch[ADD][i][VALUE_ID] max_id; op_patches_[op1][OPRESULTS][ADD].push_back(op1_patch[ADD][i]); op_patches_[op2][OPOPERANDS][ADD].push_back(op2_patch[ADD][i]); }3.3 attr_patches修改 Attribute 名称 / 类型Attribute 值的修改都随 op 进行即通过op_patches中的modify_attrattr_patches处理的是 Attribute 本体的演化名称修改存储中使用的是 Attribute 的缩写 name只要缩写不变名称的修改不影响兼容性只需在反序列化阶段把存储名对应修改为修改后的名称即可类型废弃替换例如废弃Int32Attribute、全部改为Int64Attributeattr_patches: - attr_name : Int32Attribute # 修改的 attribute 名称 actions: - action : modify_name # 修改 Attribute 名称 type : Int64Attribute # 修改为 Int64Attribute3.4 type_patches修改 TypeType 的修改与 Attribute 类似值的修改都随 OpResult 进行通过modify_output_typetype_patches处理 Type 本体的演化名称修改与 Attribute 名称修改同理类型废弃替换将某一 Type 整体更改为其他类型Type 内部属性增删某些 Type 内部存在多个属性例如DenseTensorType内嵌 dtype、dims、layout 等可以按属性序号精确增删type_patches: - type_name : Int32Type # 修改的 Type 名称 actions: - action : modify_name # 修改 Type 名称 type : Int64Type # 修改为 Int64Type - type_name : DenseTensorType # 修改的 Type 名称 actions: - action : add_type_attr # 新增 Type 属性 object : 5 # 新增属性为第几个属性 type : pir::Int64Attribute # 新增属性类型为 Int64Attribute data : 0 # 新增属性默认值3.5 真实补丁内容速览1.yaml版本 1 补丁对pd_op.lp_pool2d、pd_op.pool2d的strides/paddings、pd_op.pool3d的kernel_size/strides/paddings、pd_op.expand_as的target_shape等 ArrayAttribute 内部元素类型统一升级为pir::Int64Attribute。3.yaml版本 3 补丁pd_op.kthvalue的k修改为pir::Int64Attribute为pd_op.repeat_interleave、pd_op.repeat_interleave_with_tensor_index新增output_size属性默认 -1。4.yaml版本 4 补丁如前所述对 softplus / logit / pad3d / group_norm / gaussian / baddbmm / addmm 等大量算子做浮点属性类型升级并为pd_op.moe_permute、pd_op.moe_unpermute等新增布尔/整型属性。测试侧 test/cpp/pir/serialize_deserialize/patch/0.yaml 与 1.yaml 则覆盖了op_pair_patchespd_op.full/pd_op.scale成对增删 value、attr_patchespir::FloatAttribute→pir::DoubleAttribute、type_patchespir::Float32Type→pir::Float64Type等场景可作为编写新补丁时的参照模板。四、pir_version版本号管理与迭代流程4.1 版本号定义位置与 CMake 配置版本号管理在C 端配置于paddle/fluid/pir/serialize_deserialize/CMakeLists.txtPIR 版本号定义 PIR 的版本迭代与 yaml 文件名强相关每次 PIR 更新并新增 patch 文件后patch 文件名顺序递增版本号同时顺序递增PIR 版本与 Paddle 主版本号解耦可独立迭代。# change pir version when new patches are added add_definitions(-DDEVELOP_VERSION4) add_definitions(-DRELEASE_VERSION4)目录结构示意patch/ ├─ 0.yaml └─ 1.yamlRELEASE_VERSION已发布版本中的 PIR 版本号即 patch yaml 文件名的最大值。每次新版本发布且存在新增 patch 时RELEASE_VERSION 1若无新增 patch 则无需修改。DEVELOP_VERSION当前 develop 分支下的 PIR 版本号。若需要新增 patch配置在0.yaml中若0.yaml不存在说明当前是新版本发布后的第一次新增 patch需要新建0.yaml文件并将-DDEVELOP_VERSION设置为 0。4.2 为什么 develop 版本号从 0 重新计数BuildPatch中有一个关键细节补丁文件名并非「从 1 顺序取到当前版本」而是v % max_version// version_compat.cc - PatchBuilder::BuildPatch for (auto v file_version_; v pir_version; v) { std::string file_name std::to_string(v % max_version); ... }因此当 develop 阶段新增补丁时统一写入0.yaml版本号0发布后整体版本号 1、0.yaml的内容被固化进对应版本的正式补丁从而保证版本号与文件名之间稳定的一一对应关系。这也解释了 Readme 中「新增 patch 配置在 0.yaml 中」的约定。4.3 版本号如何传递ReadModule与WriteModule参数中的pir_version均设有默认值-1可以不显式传递。pir_version默认值为 -1进入函数后会获取 CMake 中配置的当前 PIR 版本号。相关接口签名见 include/interface.hvoid IR_API WriteModule(const pir::Program program, const std::string file_path, bool overwrite true, bool readable false, bool trainable true, int64_t pir_version -1); bool IR_API ReadModule(const std::string file_path, pir::Program* program, int64_t pir_version -1);WriteModule把 PIRProgram写入文件。overwrite决定是否覆盖已存在文件readable为 true 时生成带缩进结构的可读文件trainable控制是否写训练相关的opresult_attrs如stop_gradient、persistable为 false 时可能仅写 op info 属性pir_version为 -1 时使用 CMake 配置的当前版本。ReadModule从文件恢复Program。当传入的pir_version大于文件内记录的版本号时会触发版本兼容修补规则这也是整个 patch 机制被激活的入口条件。五、PatchBuilder补丁的构建与合并原理补丁的构建与合并全部由PatchBuilder完成其类定义见 include/version_compat.h实现见 src/version_compat.cc。5.1 构建流程BuildPatchBuildPatch(pir_version, max_version, path)的核心逻辑是从file_version_模型文件记录的版本逐个版本叠加到当前pir_version把每个版本对应的 YAML 补丁合并进内存中的补丁表void PatchBuilder::BuildPatch(uint64_t pir_version, uint64_t max_version, const std::string path) { for (auto v file_version_; v pir_version; v) { std::string file_name std::to_string(v % max_version); // 默认从内嵌 yaml_files 中取path 非空时从外部文件加载测试用 patch_json YamlParser(file_name, file_path); // 合并 op_pair_patches for (auto patch_info : patch_json[op_pair_patches]) { ... } // 合并 op_patches同一 op 的 actions 按规则逐 key 合并 for (auto patch_info : patch_json[op_patches]) { if (op_patches_.count(patch_info[op_name])) { // 已存在则合并OPOPERANDS/OPRESULTS 按 action 合并其余列表 append } else { op_patches_[patch_info[op_name]] patch_info[PATCH]; } } // 合并 type_patches / attr_patchesJson::update 语义 for (auto patch_info : patch_json[type_patches]) { ... } for (auto patch_info : patch_json[attr_patches]) { ... } } }合并规则中值得注意的几点同一 op 跨版本多次补丁会被合并OPOPERANDS/OPRESULTS按 action 名ADD/DELETE/UPDATE逐项合并其余属性列表直接 appendNEW_NAME以新值为准SetFileVersion用于显式指定模型文件中的版本号file_version_当file_version ! pir_version时才需要构建补丁测试场景下可通过path参数从外部目录加载 YAML例如单测中传入patch目录生产环境则使用 CMake 内嵌的yaml_files。5.2 应用流程Apply*构建完成后反序列化阶段ProgramReader对每个 op / type / attr 调用对应的Apply*方法完成修补ApplyOpPatches(op_name, json, patch)处理 op 的OPOPERANDS按 id 插入/删除输入、OPRESULTS按 id 更新输出类型 / 插入 / 删除输出、以及ATTRS/OPRESULTS_ATTRS的增删改其中对builtin.parameter有专门的紧凑属性is_distributed、is_parameter、need_clip、parameter_name、persistable、stop_gradient、trainable等按固定索引处理ApplyTypePatches(type_name, json, patch)修改 Type 的 id新名称并对DenseTensorType递归处理其内部第 0 个属性dtype的 Type 补丁ApplyAttrPatches(attr_name, json, patch)按 patch 逐项修改 Attribute 的类型与数据merge_patch语义并更新名称ApplyAttrTypePatches(attr_name, json, patch)仅处理 Attribute 类型整体改名场景NEW_NAME。六、Python 端与发版约束6.1 Python 端无需关心 pir_versionPaddle 的主版本号定义在Python 端与 PIR version 不产生关联Python 端不再需要获取和传入pir_version直接使用默认值即可——版本兼容修补完全由 C 端在ReadModule内部依据文件版本与当前版本自动完成。6.2 Paddle 发版要求发版时需要确认develop 版本被修改为正式版本即若 Python 端的版本号不为0.0.0则pir_version不能为 0。这是防止将 develop 阶段「未发布」的补丁状态版本 0误作为正式版本发布的硬性约束。七、版本兼容的验证单测与完整修改流程7.1 单测验证补丁机制有完整的 C 单测支撑位于 test/cpp/pir/serialize_deserialize/save_load_version_compat_test.cc测试补丁文件在 test/cpp/pir/serialize_deserialize/patch/。核心验证思路如下摘自测试代码构造一个 Program写入带版本号的文件// Save the program into file pir::WriteModule(program, ./test_save_load, true, false, true, /*pir_version*/ 1);用更高版本号读取触发 patch 修补并断言修补结果// Load the program from file pir::Program new_program(ctx); ReadModuleForTest(./test_save_load, new_program, 2); // In patch yaml, the value of attribute parameter_name in builtin.parameter // is changed into fc_0 EXPECT_EQ(new_program.block() -front() .attribute(parameter_name) .dyn_cast::pir::StrAttribute() .AsString(), fc_0);ReadModuleForTest中展示了版本兼容修补的完整触发流程解析文件中的BASE_CODE.MAGIC与PIRVERSION若file_version ! pir_version则SetFileVersion(file_version)后BuildPatch(2, 2, patch)最后通过ProgramReader的RecoverProgram重建带补丁语义的 Program。测试补丁 0.yaml / 1.yaml 完整覆盖了本文所述的大部分 action 类型modify_attr、delete_attr、add_attr、modify_output_attr、modify_output_type、add_output、delete_input、add_value、delete_value、modify_name、attr_patches、type_patches是学习与调试补丁语法的最佳范例。7.2 完整修改配置流程新增一个 PIR 兼容补丁的完整操作流程可参考文档引用的 PR 思路修改DEVELOP_VERSION的 PR 与新增 patch yaml 的 PR确认当前 develop 分支的DEVELOP_VERSION查看 CMakeLists.txt若0.yaml不存在则新建0.yaml将新增的 op / type / attr 补丁按第四节语法写入将-DDEVELOP_VERSION设置为 0表示 develop 阶段的未发布补丁在 test/cpp/pir/serialize_deserialize/patch/ 中补充或调整测试补丁并在 save_load_version_compat_test.cc 中新增断言验证属性值、输出类型、输入输出数量等修补结果运行pir_save_load相关单测确认兼容修补正确发版时将补丁内容固化到正式版本号文件、RELEASE_VERSION 1如有新增 patch并确保 Python 端版本号非0.0.0时pir_version不为 0。八、总结PIR 的 Save/Load 版本兼容机制本质上是一个「版本号 声明式补丁」的模型升级管道声明用op_patches/op_pair_patches/attr_patches/type_patches四类 YAML 精确描述旧版本结构到新版本结构的迁移动作构建构建期把补丁 YAML 内嵌进二进制运行时BuildPatch依据「文件版本 → 当前版本」区间叠加合并补丁应用反序列化时Apply*系列方法按补丁逐 op / type / attr 修补恢复出符合当前版本语义的 Program版本DEVELOP_VERSION/RELEASE_VERSION与 patch 文件名强绑定、与 Paddle 主版本解耦通过发版约束保证补丁状态不被误发布。这套机制保证了飞桨模型在跨版本场景下的可加载性与语义一致性是 PIR 序列化体系稳定演进的重要基础设施。对于需要为自定义算子或新增属性维护向后兼容的开发者理解并正确编写补丁 YAML、遵守版本号迭代约定即可将模型升级成本控制在一次声明式配置之内。【免费下载链接】PaddlePArallel Distributed Deep LEarning: Machine Learning Framework from Industrial Practice 『飞桨』核心框架深度学习机器学习高性能单机、分布式训练和跨平台部署项目地址: https://gitcode.com/GitHub_Trending/pa/Paddle创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表