为了让飞桨开发者们掌握第一手技术动态、让企业落地更加高效,飞桨官方在7月至10月特设《飞桨框架3.0全面解析》系列技术稿件及直播课程。技术解析加代码实战,带大家掌握包括核心框架、分布式计算、产业级大模型套件及低代码工具、前沿科学计算技术案例等多个方面的框架技术及大模型训推优化经验。本文是该系列第四篇技术解读,文末附对应直播课程详情。
随着人工智能技术的高速发展,深度学习框架作为该领域进步的核心驱动力,加速着计算机视觉、自然语言处理、自动驾驶等多个技术领域的应用落地。
但框架层持续扩增的庞大算子体系,给模型性能优化、多硬件适配等工作带来了诸多挑战。
如何有效地控制深度学习框架中的算子数量,降低开发维护成本并提升二次开发体验,是当前深度学习框架优化与发展的重要方向。
飞桨框架3.0版本创新性地研制了全新的算子组合机制。
该机制巧妙地将算子划分为基础算子和组合算子两大类,并严格限制基础算子的数量,其他所有算子均通过基础算子进行组合实现。这一设计使得上下游生态仅需适配基础算子集合即可实现无缝接入,从而大幅度降低了适配成本,提升了整体效率。
-
编译器
:基于有限集合的基础算子,飞桨框架3.0实现了框架与编译器后端算子之间的高效映射机制,降低了编译复杂度和运行时开销,有效提升了编译效率和泛化能力,为在框架中实现更为丰富、高效的编译优化技术提供了有力支持;
-
科学计算
:飞桨框架3.0通过组合机制将高阶微分计算分解为基于基础算子的组合操作,解决了传统方法中需要为每种高阶导数编写大量特定代码的问题,显著增强了代码的可维护性和可扩展性;
-
多硬件适配
:飞桨框架3.0通过严格控制基础算子的数量,并提供一套标准化的算子接口,让新硬件仅需关注基础算子即可快速接入飞桨框架,降低了新硬件接入的门槛和成本,加速了新技术在深度学习领域的应用与推广进程,为行业的创新发展注入了新的活力。
动静统一的组合机制
-
精简算子数量
。
引入基础算子和组合机制概念,大幅精简庞大的算子集合。开发者们更专注于基础算子的优化与实现,而无需为每一个特定操作编写专门的算子代码,显著降低了高阶微分、新硬件接入以及编译器对接等场景下的接入门槛;
-
前反向算子集合统一。
通过算子组合机制,实现了前反向算子集合的统一。计算图中的所有前向和反向操作都基于同一批基础算子集合进行构建,不仅简化了计算图的构建过程,还提高了计算图的清晰度和可维护性;
-
开发者友好。
构建了开发者友好、高可扩展性、灵活的组合机制体系,轻松应对未来可能出现的各种新需求和新挑战,也使得更多的社区力量能够低成本地参与到框架生态的繁荣工作中来;
基础算子体系
飞桨框架3.0设计了一套高效、全面且易于扩展的基础算子集合,将其作为构建复杂模型和算法的基石,有力支撑了框架的性能、灵活性和可维护性。
基础算子集合的设计遵循
以下原则:
-
原子性
:基础算子应具备不可分割性,即其执行的操作无法被进一步拆分为更基本的操作。例如,三角函数中的 sin 和 cos ,它们是数学上的基本函数;
-
实用性
:基础算子应具备广泛的应用场景,充分考虑其在现实任务中的实用价值和需求频率,支持多种深度学习模型中的常见操作;
-
面向Tensor
:基础算子的操作粒度应直接面向 Tensor (张量),能够高效地处理多维数组,包括元素级或更复杂的矩阵运算;
-
完备性
:基础算子集合应覆盖所有算子和 API ,确保其他复杂算子、模型和算子可由基础算子来构建;
在实际应用中,除了上述基础设计原则以外,还有一些特殊情况需要综合考虑。如以下情况下应作为基础算子扩展集合成员:
-
组合实现导致性能或精度问题
:某些算子虽然理论上可以拆分为更基础的操作,但这种拆分在实际应用中可能会引入性能瓶颈或精度损失。例如, logsoftmax 和 sigmoid 等函数,由于它们内部包含了对数、指数等复杂运算,直接实现这些函数往往比通过拆分后的组合运算更为高效和准确。因此,这些函数应作为独立的基础算子来定义。
-
优先考虑硬件或计算库的定制优化
:随着硬件和计算库的不断发展,许多常用算法已经得到了针对特定硬件或计算环境的优化。例如,卷积(conv)和矩阵乘法(matmul)等操作在GPU、XPU等加速器上通常具有高效的实现。将这些优化后的算子纳入基础算子集合中,可以显著提高框架的性能和效率。同时,这也要求框架设计者密切关注硬件和计算库的发展趋势,及时将新的优化成果融入基础算子集合中。
组合机制设计
组合机制依托于飞桨框架体系,
整体调度执行逻辑包含动态图和静态图两大分支
,在流程上分为反向拆分执行、前向拆分执行、代码自动生成、调度4大部分。
-
绿色支线是动态图分支
,仅拆解反向微分,在调用反向算子时根据环境变量,判断是选择调用原生反向算子还是反向算子的拆解规则;
-
黄色直线是静态图分支
,前向拆解基于图变换,即遍历计算图,将定义拆解规则的算子替换成对应的拆解规则基础算子集合;反向同时支持图变换和动态图分支的拆解方式。
组合机制架构
-
支持动态图反向拆解。
在模型训练过程中,反向是梯度计算和模型参数更新的关键步骤。传统方法上需要开发者手动定义高阶微分算子,工作量大且容易出错。
飞桨框架3.0通过自动化机制实现高阶微分的计算图生成,反向算子自动拆分。无需开发者手动干预,提高了开发效率和准确性。
在动态图下,仅需反向高阶微分的拆分即可,因为绝大多数场景下动态图对调度要求很高,需要保证在传统动态图执行场景(非高阶微分)中不引入额外的拆分逻辑。
-
统一基础算子体系。
在算子拆解的过程中涉及到多种场景:动、静态图拆解,前、反向算子拆解。飞桨框架3.
0采用了代码自动生成技术,涉及实现了多场景统一的基础算子体系
。开发者只需要调用同一个封装好的算子API接口,组合机制会针对不同场景自动分发。
-
动静图拆解规则统一。
飞桨框架3.0
采用了模版特化技术,确保了动态图和静态图在拆解规则上的一致性
,降低了开发者在开发反向算子拆解规则时的工作量,开发者可以更加专注于算法优化,而无需担心底层实现细节;
-
精简反向拆解算子。
理想情况下,
开发者仅需适配少量基础算子
(如 sin、cos等)和
关键算子
(如 batch_norm、layer_norm等)的反向拆解规则,总数不超过200个。极大地简化了反向传播的实现复杂度,降低了开发者的学习成本和维护难度;
-
灵活高可拓展。
用户可以通过设置环境变量来控制组合机制的开启与关闭,以适应不同的使用场景和需求。飞桨正在引入自动判断机制,根据模型结构和训练任务的特点自动决定是否开启组合机制,以进一步提升使用的便捷性和效率;
在深度学习框架中,算子的拆解与融合是优化计算图执行效率和资源利用率的重要手段。飞桨框架面向用户提供了动静统一的使用方式,其中静态图组合模式在提供通用的接口以外,亦可通过「动转静+编译器」自动触发组合算子拆解,实现转静训练的一键加速体验。
组
合
机
制
静态图模
式
结合编译器
飞桨框架提供了自定义反向拆解规则,可以有效解决算子拆解可能引入显存和数值问题。
自定义反向拆解规则的算子数量远小于自动微分,可减少求解自动微分时的中间变量,能有效减少显存占用;自定义反向拆解规则可以基于数学推导,解决silu等算子的精度问题。
自定义反向示意图
-
第一次前向拆解:对没有反向拆解规则的算子执行前向拆解;
-
反向微分变换:针对步骤1产生的计算图执行自动微分,对所有定义反向拆解规则的算子(包括定义反向拆解规则的基础算子和非基础算子)执行反向拆解;
-
第二次前向拆解:对步骤1跳过的非基础算子,执行拆解。
在组合算子场景下,Tensor 相关的运算需要支持自动微分的功能,以确保动静态图的微分完整性。因此飞桨框架3.0提出了
运行时多态分发
的方式,利用抽象类型 AbstractOperants 进行PHI和主框架的系统编译隔离,进而通过在 Manager 里进行多态分发完成运算符在不同运行模式下的不同行为变换,显著提升了组合算子开发的便利性。
运算符重载机制
飞桨框架对不同的反向算子拆解场景设计了灵活的组合机制接入方式。以下以实例介绍支持的场景:
原始子图:算子op-A和对应的反向算子op-A_g
场景1: 先拆解算子 op-A ,再根据基础算子(op-a1, op-a2)求解自动微分,调用对应基础算子的反向规则(op-c1, op-c2, op-c3)。
场景2:开发者获取包含前向算子和反向算子的计算图(op-A, op-A_g)。这种场景的算子拆解以图变换的方式,调用组合机制提供的api接口,遍历计算图,将前反向基础算子替换成基础算子集合(op-a1, op-a2, op-c1, op-c2)。
先对op-A微分,再前反向拆分
场景1是当前组合机制默认的模式,其优点是需要适配的算子反向拆解规则少,仅需适配基础算子和少量非基础算子的反向拆解规则,也是飞桨框架推荐的流程。
组合算子开发实践
这里以算子 Silu 为例,简要介绍组合算子的开发步骤,更详细的开发指导
,可参考【飞桨社区文档】。
https://github.com/PaddlePaddle/community/tree/master/pfcc/paddle-code-reading/operator_decomposition_mechanism
Step 1 : 注册需要拆解的算子名称,组合机制根据注册的 op 信息自动生成对应的调用链代码。
decomp_interface_declare_gen_op_list = ["silu", ...]
decomp_interface_implementation_gen_op_list = ["silu", ...]
CUSTOM_VJP = ["silu_grad", ...]
Step 2 : 定义拆解规则,所有拆解规则调用统一的基础算子 api 和工具函数
定义拆解规则时调用的
基础算子接口
位于
primitive/generated_primitive.h
,调用的
公共工具函数
位于
primitive/utils/utils.h
template <typename T>
Tensor silu_decomp(const Tensor& x) {
auto org_dtype = x.dtype();
auto x_tmp = x;
auto res = x_tmp * sigmoid(x_tmp);
return res;
}
template <typename T>
void silu_grad(const Tensor& x,
const Tensor& out,
const Tensor& out_grad,
Tensor* x_grad) {
if (x_grad) {
auto one = full_scalar(1.0, x.dtype());
auto res = out_grad * sigmoid(x) * (one + x - out);
set_output(res, x_grad);
}
Step 3 : 适配对应单测,大部分算子单测基于飞桨单测体系进行了高级封装,仅需更改几处,即可满足组合机制的测试需要。
class TestSilu(TestActivation):
def setUp(self):
self.op_type = "silu"
self
.prim_op_type = "comp"
self.python_api = paddle.nn.functional.silu
self.public_python_api = paddle.nn.functional.silu
def test_check_output(self):
self.check_output(
check_prim=True,
check_pir=True,
check_prim_pir=True,
check_pir_onednn=self.check_pir_onednn)
def test_check_grad(self):
self.check_grad(
['X'],
'Out',
check_prim=True,
check_pir=True,
check_prim_pir=True,
check_pir_onednn=self.check_pir_onednn)
以上三个步骤完成后,提交的 Pull request 通过飞桨 Github 流水线测试,即完成对应算子组合规则的开发。开发一个常规的算子前向或者反向的拆解规则,一般需要修改的文件数不超过3个,代码量不超过100行。
飞桨框架3.0的组合机制极大简化了算子组合规则的开发流程
,开发者仅需了解算子数学语义和基本的 c++ 和 python 知识,就能快速上手并完成开发工作。
总结
算子组合机制,在单机模型训练,推理,分布式模型训练场景得到了充分的验证。
性能方面,结合编译器,性能普遍提升,在某些场景,模型加速比达70%。功能方面,支持的高阶微分功能在科学计算模型得到了广泛应用
。
飞桨框架3.0引入的算子组合机制,是一种全新的算子管理方法,其核心思想在于将复杂的算子操作分解为一系列基础且高度可复用的基础算子,并通过组合这些基础算子来实现更高级别的计算功能。这一机制不仅简化了算子管理的复杂度,还为实现更高效、更灵活的深度学习计算提供了可能。未来飞桨将继续秉承开放、合作、共赢的理念,与全球开发者共同推动深度学习技术的繁荣发展。
7月至10月特设《飞桨框架3.0全面解析》直播课程,
技术解析
加
代码实战
,
带大家掌握核心框架、分布式计算、产业级大模型套件及低代码工具、前沿科学计算技术案例等多个方面的框架技术及大模型训推优化经验,实打实地帮助大家用飞桨框架3.0在实际开发工作中提效创新!
为了让优秀的飞桨开发者们掌握第一手技术动态、让企业落地更加高效,根据大家的呼声安排史上最强飞桨技术大餐!涵盖飞桨框架3.0、低代码开发工具 PaddleX 、大语言模型开发套件 PaddleNLP、
多模态大模型
开发套件 PaddleMIX 、典型产业场景下硬件适配技术等多个方向,一起来看吧!
温馨提示:以上仅为当前筹备中的部分课程,如有变动,敬请谅解。