OneFlow源码解析:静态图与运行时

e280ea25e4fec9b711e37c34b99cec10.jpeg


作者|郑建华
更新|许啸宇、张文骁、成诚


OneFlow静态图的训练效率远高于动态图(eager模式)。本文试图通过一个简单例子,结合v0.8.0版本的代码,解读一下静态图和运行时的实现机制。

在开始之前,建议先读一下参考资料中《OneFlow框架的系统设计(https://zhuanlan.zhihu.com/p/337851255)》等系列文章。对静态图、运行时的基本概念和设计理念有基本的了解,会更容易理解代码。

代码示例

下面的示例代码来自官方文档(https://docs.oneflow.org/master/basics/08_nn_graph.html),是一个线性模型的前向计算。后续主要基于这段代码进行分析。

import oneflow as flow
import oneflow.nn as nnclass ModuleMyLinear(nn.Module):def __init__(self, in_features, out_features):super().__init__()self.weight = nn.Parameter(flow.randn(in_features, out_features))self.bias = nn.Parameter(flow.randn(out_features))def forward(self, input):return flow.matmul(input, self.weight) + self.biaslinear_model = ModuleMyLinear(4, 3)class GraphMyLinear(nn.Graph):def __init__(self):super().__init__()# ModuleBlockself.model = linear_modeldef build(self, input):# ModuleBlock.__call__return self.model(input)graph_mylinear = GraphMyLinear()
input = flow.randn(1, 4)
out = graph_mylinear(input)
print(out)

oneflow包的初始化

import oneflow在初始化包(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/__init__.py)时,与静态图相关的主要操作如下:

  • GetEnv(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/__init__.py#L228

    • EnvGlobalObjectsScope::Init(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L126

      • 启动各个节点的控制面(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L160-L162)网络连接

      • 初始化VM(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L180

      • 启动各个节点的数据面网络连接(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L184-L188

      • 初始化KernelObserver(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L192-L203

  • NewDefaultSession(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/__init__.py#L229

    • RegsiterSession(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/multi_client_session.py#L39) 创建 Session,并注册为 default session(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/session_util.cpp#L89

    • 创建 Python MultiClientSession 并保存到dict(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/session_context.py#L40),但并不 TryInit

      • 创建 C++ MultiClientSessionContext(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/multi_client_session.py#L41) 但并不 TryInit

EnvGlobalObjectsScope::Init中先创建一个全局的ProcessCtx(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/env_global_objects_scope.cpp#L132)对象。然后根据环境变量等配置,在各个进程间创建gRPC和CommNet的连接,分别负责控制面和数据面的数据传输。其中在Bootstrap过程中会初始化全局的ProcessCtx(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/rpc/lib/grpc.cpp#L42),给每个进程分配一个全局唯一的rank编号(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/rpc/lib/global_process_ctx.cpp#L28)(machine_id(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/rpc/lib/global_process_ctx.cpp#L24))。

本文不涉及网络层面的操作,只讨论同一进程内各线程间的交互。

Module类

虽然可以直接用op和tensor构造模型,但是op的粒度太细了,直接用op构造模型会比较繁琐。

Module(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/module.py#L54)是由op和tensor构成的、可复用的子模块。利用Module可以更高效、更快捷的构建复杂模型。oneflow.nn(https://github.com/Oneflow-Inc/oneflow/blob/d825243aa7aff5cba8bd3a901b4cc56c2b1a36af/python/oneflow/nn/__init__.py)模块导出了很多预定义的Module。

Module定义了自己的属性设置逻辑(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/module.py#L262),核心逻辑是

  • 如果value是Parameter类型,就保存到Module._parameters中

  • 如果value是Module类型,就保存到Module._modules中

  • 如果value是Tensor类型,就保存到Module._buffers中

  • 否则按常规属性处理

Module可以包含子Module,形成树结构。因为Module通过setattr将子Module和Parameter都保存到字典结构中,可以方便的遍历所有Module及其参数tensor。

Graph类

4.1 构造函数

Graph的构造函数中GetDefaultSession(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L145)得到的session,就是导入oneflow包时NewDefaultSession(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/__init__.py#L229)构建的session。当时没有初始化,而是在Graph构造时进行初始化(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L147)。对应的C++函数是MultiClientSessionContext::TryInit(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/multi_client_session_context.cpp#L67),执行时会创建各种全局的资源管理器,比如:
 

  • LazyJobBuildAndInferCtxMgr

  • BufferMgr

  • RegstMgr

  • ActorMsgBus

  • ThreadMgr

4.2 __setattr__: 将Module和Tensor封装为Block

Graph.__setattr__ 支持通过设置属性的方式把一个 Module 添加到 Graph 中,之后改 Module 就可以被 Graph 调用了。添加到 Graph 中的 Module,会被包装到 Block 里面,Block 起到了代理执行的作用,它会给原 Eager 下的 Module 扩展出静态执行需要的一些特殊功能。

添加到 Graph 中的 Module 和原 Module 共享了状态(Parameter、Buffer)和 forward 执行逻辑。共享 forward 执行逻辑使得静态和动态执行计算逻辑相同。共享状态则可以使动态图下的模型状态被静态图复用。基于此,两个 Graph,一个用于训练,一个用于预测,他们都复用统一模型 Module,这样训练和预测 Graph 也就实现了模型共享。

setattr最重要的动作就是对_add_block的调用(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L1332),_add_block中主要是调用get_block_cls并保存结果(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L1326)。get_block_cls(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L39)的作用是将Module及其所有Tensor属性都转为对应的Block对象。为什么要做这个动作呢?主要是静态图编译需要借助Block类型来实现代理执行的功能,这些功能不适合直接写到 eager 下的 Module 和 Tensor 上。

这个转换是在ModuleBlock构造时调用set_origin(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L131)完成的。对于子Module,会递归调用get_block_cls函数(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L145),这样所有子Module及其Tensor属性都会被转换为对应的Block对象。

所以,上述示例代码中,GraphMyLinear实际存储的是ModuleBlock,Graph.build执行时获取的model属性也是ModuleBlock对象,ModuleBlock.origin才是ModuleMyLinear。

Graph.__setattr__不允许将Tensor对象设置为属性(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L1340)。Tensor只能存到Module中,因为 Module 是做状态共享的基本单位,而 Graph 是不允许复用的。

4.3 针对不同任务,定义不同的计算图

根据Oneflow Model Zoo的模型示例(https://github.com/Oneflow-Inc/models/blob/1b291f78d8f60e5f04ee0c5962e4611cc4bab40a/Vision/classification/image/alexnet/graph/train.py),train/eval等阶段可以创建不同的Graph子类。动态图下提供了 Module、Optimizer、Dataloader等模块,这些模型都可以被添加到 Graph 中。不同的组合可以构建不同类型的任务。

在这些不同阶段,Graph构造函数的行为、build函数的输入输出都有各自特点。了解这些,看后续代码时会更容易理解各个参数的具体含义。

  • 构造函数

    • train阶段,需要添加Module、损失函数、优化器和dataloader

    • eval阶段,只需要添加Module和dataloader

  • build函数

    • train

      • 导入样本和label

      • 调用Module得到前向计算结果

      • 计算损失

      • 计算梯度

      • 返回loss

    • eval

      • 导入样本和label

      • 调用Module得到预估结果

      • 返回预估结果和label

4.4 小结

上述几个类型的关系如下:

a714129bd3b8cbfd483f412b01a5223b.png

下面描述了GraphMyLinear的构造流程

* `__init__`* `Graph.__init__`* self.model = linear_model* `Graph.__setattr__`* _add_block* get_block_cls: 递归地把Module转为ModuleBlock* `ModuleBlock.__init__`* ModuleBlock.set_origin* `ModuleBlock._origin = origin` (Module)* 对origin的sub modules, parameters, buffers递归调用get_block_cls* `ModuleBlock.__setattr__`

逻辑图的编译

计算机语言的编译,是将高级语言的语句编译为汇编或机器指令。深度学习框架对计算任务的编译,是将用户的特定语句操作转换为DAG图。oneflow中用Job(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job.proto#L30)描述逻辑的计算图。

不同于eager模式的动态图,静态图在开始执行前可以得到整个计算任务的所有信息,可以对DAG进行多轮优化。每轮优化都是输入一个Job、得到一个新Job。

最后,根据分布式环境配置,将逻辑图Job转换为物理执行的计算图Plan(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/plan.proto#L34)。在物理图中,一个op可能分布在多个节点/进程。

启动DAG计算需要调用Graph.__call__,这个函数的执行主要分以下几个步骤:

  • __call__

    • _compile(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L221) if not _is_compiled

      • build_graph(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L741

        • __build_graph(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L759

      • finish_complie_and_init_runtime(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L742

    • __run(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L226

逻辑图编译主要在__build_graph中进行。finish_complie_and_init_runtime会继续做一些优化pass,然后构建物理图、初始化运行时Actor系统。__run会启动一次DAG的运算。

5.1 graph_build_context: 为逻辑图编译设置基本环境

在 Graph 中,build 函数里面的代码执行都在 graph_build_context 的作用域下,这样实现了动态转静态的功能。

__build_graph中的graph_build_context(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L851)虽然只有一行代码,但却做了几件非常重要的事情。

首先在context作用域内设置全局的lazy_mode为True(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L46)。在这个context作用域内,所有op都由LazyInterpreter解释执行。

其次,在JobBuildAndInferCtx(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L47)作用域内,JobBuildAndInferCtx_Open(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L57)调用类似如下C++代码

// oneflow/api/python/job_build/job_build_and_infer.h
// oneflow/core/job/job_build_and_infer_ctx_mgr.cpp// 如前所述,LazyJobBuildAndInferCtxMgr 在 MultiClientSessionContext::TryInit 执行时初始化。
// LazyJobBuildAndInferCtxMgr mgr;
mgr.OpenJobBuildAndInferCtx(job_name);

OpenJobBuildAndInferCtx会新建一个Job对象(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job_build_and_infer_ctx_mgr.cpp#L32)、一个LazyJobBuildAndInferCtx对象(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job_build_and_infer_ctx_mgr.cpp#L34)。LazyJobBuildAndInferCtx负责根据用户定制的op等操作,修改Job,其中最主要的功能是添加新 Op。

5.2 __build_io:为计算图添加input和output Op

self.__build_io("input", graph_build_util.build_graph_input_arg, *args, **kwargs)

上面这行代码(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L854-L856)的作用是,对于用户传递给graph_mylinear(input)的input参数,针对其中的每个tensor都在逻辑计算图中插入一个FeedInputOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/system_ops.h#L48)节点。也就是说,model的输入(比如样本tensor,具体参考4.3节),在静态图中也视为一个op操作。

__build_io内会用args(即input)和kwargs构造一个ArgsTree。ArgsTree 把 Python 下的输入、输出抽象成了一个树,输入、输出可以是嵌套的 Tuple、List、Dict,元素是 Tensor,嵌套的结构刚好可以表示为树,而 Tensor 是树中的叶子节点。示例代码中kwargs是空的。

遍历ArgsTree,对args和kwargs的每个tensor都调用传入的build_func,对于input来说,就是build_graph_input_arg(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L206)。后面会看到,model的output也会调用__build_io,所以这个函数名的意思应该就是对model的输入、输出进行静态图的构图工作。

build_graph_input_arg内部会构造一个FeedInputOpExpr(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L213),提交给解释器执行。因为是在lazy作用域内,由LazyInterpreter解释执行(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L471),LazyInterpreter会将对应的op插入静态图。

附:build input时ArgsTree的内部结构

0a6b6c85614174cdc63f23cb4422c0ce.png

__build_io(input) 中 ArgsTree 的内部数据组织示意

  • _named_io_args: NamedArg

    • _value: tuple

      • [0]: NamedArg

        • _value: tuple of NamedArg

          • [0]: NamedArg

            • _value: args tensor from Graph.__call__

      • [1]: NamedArg

        • _value: empty kwargs from Graph.__call__

通过pdb命令可以查看变量: p args_tree._named_io_args._value[0]._value[0]._value.to_numpy()

5.2.1 将op添加到逻辑图

LazyInterpreter::ApplyImpl(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L471)在执行时,GetCurInferCtx()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L500)返回的就是graph_build_context中OpenJobBuildAndInferCtx(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L57)创建的那个LazyJobBuildAndInferCtx对象,这个对象负责逻辑图的构建。添加op的主要调用流程如下:

  • infer_ctx->AddAndInferConsistentOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L503

  • AddAndInferOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job_build_and_infer_ctx.cpp#L563

  • ConstructOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job_build_and_infer_ctx.cpp#L580

  • CheckAndConstructOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/operator/operator.cpp#L1216

  • NewObj(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/operator/operator.cpp#L51


OperatorConf中,多种op配置共享op_type字段(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/operator/op_conf.proto#L412),protobuf oneof的op_type_case常量作为注册NewObj的key。

系统预定义的op在oneflow/core/operator(https://github.com/Oneflow-Inc/oneflow/tree/release/v0.8.0/oneflow/core/operator)下,例如UserOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/operator/user_op.h#L24)。

AddAndInferOp将返回的Operator保存到LazyJobBuildAndInferCtx的字典中。后续的函数调用,主要是进行推导并修改静态图Job,使得各个节点构成一个DAG。

JobBuildAndInferCtx相关的类关系如下:

0820ba8658ba6b061b7efe19c14ef22f.png

5.2.2 lazy tensor 和 eager tensor 的区别

LazyInterpreter::ApplyImpl的最后,会调用BuildTensor(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L518)构造一个lazy tensor,作为build_graph_input_arg的返回值(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L216)。所以__build_io返回的lazy_args(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L854)是lazy tensor,它将替代eager的args(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L828)(也就是用户输入的input)参与后续的计算图构建。

那么lazy tensor和eager tensor的区别是什么呢?eager tensor是要即时计算的,所以需要真实数据;而lazy tensor仅在静态图编译阶段用于推导,只需要描述性质的元信息。静态图编译是在lazy模式下运行,只是使用lazy tensor 做计算机构图和校验。

后面会看到,静态图的运行期已经没有tensor的概念。运行期看到的只是更广义的Regst存储,可能代表tensor/blob,也可能是其它控制信息。静态图运行时的输入,是直接读取外部 eager tensor的内存数据到到regst;输出应该是op写到regst,通过blob构造eager tensor。

5.3 build: 将UserOp和FeedVariableOp添加到逻辑图

__build_graph中的self.build()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L861)会调用GraphMyLinear.build(),以及ModuleMyLinear.forward()。因为是在lazy模式下运行,matmul和add都会调用UserOpExpr重载版本的LazyInterpreter::ApplyImpl(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L832),进而调用AddAndInferConsistentOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L940)进行构图操作。

需要说明的是,在引用Module的Parameter属性时(如weight/bias),会触发FeedVariableOp的构图操作(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L226)、调用对应版本的LazyInterpreter::ApplyImpl(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L527)。这个是怎么执行的呢?

__build_graph中,在进入lazy模式之前,先调用了_create_states_builder(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L843)。其中self._state()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L667)返回所有Module的所有Parameter(包括子Module)。

state_block的类型是TensorBlock(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L631)。所有的state_block的lazy_origin_builder().method(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L647)都被设置为调用build_graph_state(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L683-L688)。

给build_graph_state(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L220)设置个断点能让整个调用过程显形,主要的调用栈如下:

-> out = graph_mylinear(input)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/graph.py(221)__call__()
-> self._compile(*args, **kwargs)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/graph.py(741)_compile()
-> _, eager_outputs = self.build_graph(*args, **kwargs)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/graph.py(759)build_graph()
-> outputs = self.__build_graph(*args, **kwargs)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/graph.py(864)__build_graph()
-> outputs = self.build(*lazy_args, **lazy_kwargs)/mnt/project/machine-learning/oneflow/oneflow/test.py(21)build()
-> return self.model(input)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(234)__call__()
-> result = self.__block_forward(*args, **kwargs)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(266)__block_forward()
-> result = self._origin.__class__.forward(self, *args, **kwargs)/mnt/project/machine-learning/oneflow/oneflow/test.py(11)forward()
-> return flow.matmul(input, self.weight) + self.bias/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(483)__getattr__()
-> p_state = self._get_from_states(name, "_parameters")/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(521)_get_from_states()
-> _s_block.try_build()/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(679)try_build()
-> self._lazy_origin_builder.try_build(self)/usr/local/lib64/python3.6/site-packages/oneflow/nn/graph/block.py(627)try_build()
-> self.result = self.method()
> /usr/local/lib64/python3.6/site-packages/oneflow/framework/graph_build_util.py(227)build_graph_state()
-> op_name, var_conf_str, ["in_0"], ["out_0"]

这个调用过程比较容易困扰的是,执行对象会在Grpah、GraphMyLinear、ModuleMyLinear、ModuleBlock之间切换。

前面在讨论Graph的构造时已经提过,执行self.model(input)时,Graph.__getattr__返回的属性model是ModuleBlock对象,所以实际调用的是ModuleBlock.__call__。

在这个函数内调用__block_forward(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L234),其中的_origin(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L266)是ModuleMyLinear,进入到它的forward方法,执行到flow.matmul(input, self.weight) + self.bias时,matmul 会被LazyOpInterpreter 所执行,在 LazyOpInterpreter 中调用 AddAndInferConsistentOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L503

,在 Job 中添加一个 matmul operator。同理后面的加法会在 job 中添加一个 add operator。

self.weight 和 self.bias 会触发调用ModuleBlock.__getattr__,进而调用_get_from_states(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L483),调用TensorBlock.try_build()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/block.py#L521)。这里执行的就是进入lazy模式之前设置的build_graph_state(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/framework/graph_build_util.py#L220)。从而增加一个FeedVariableOp到计算图(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L527)。为什么设置和调用会距离这么远呢?主要是为了让参数尽量和消费参数的 Operator 在一个作用域下,所以实现成了惰性求值来达到延迟计算的目的。

再后面的步骤就是调用__build_io(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L869-L875)插入FetchOutputOp(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/op_interpreter/lazy_op_interpreter.cpp#L589)。也就是说,获取model的output也是一个op。

到目前为止,前向计算图就构建完成了。它的json表示可以参考附录。net.op是计算图的节点,通过input等属性可以看出节点之间的连接关系。

示例代码的前向计算图如下。从这个图可以看到,input、output、weights等都是op。

daa1b8f1e66b6e63587631b427c33760.png

5.4 逻辑图优化

在__build_graph中会调用CurJobBuildAndInferCtx_Complete对静态图进行多轮优化(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L923),对应的C++函数是LazyJobBuildAndInferCtx::Complete()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/job_build_and_infer_ctx.cpp#L975)。

这之后生成的Job是full_job。本文的示例代码比较简单,并不是典型的计算场景,其forwar和ful计算图的拓扑是一样的。实际大部的图优化都实现在这个阶段,如 Op fusion、AMP、ZeRO、常量折叠等等。

到这里,逻辑图构建的主体部分就结束了。

随后会构建一个CNNGraph对象(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L947),对应的C++类型是NNGraph(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.h#L33)。这个对象将负责构建物理计算图Plan。它也是整个运行时的拥有者和维护者。这个对象析构时,整个运行时也会有序终止并释放资源。

5.5 物理图的编译

接下来就是执行finish_complie_and_init_runtime(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L742),其中的核心调用是self._c_nn_graph.complie_and_init_runtime()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/python/oneflow/nn/graph/graph.py#L802),对应的C++函数是NNGraph::CompileAndInitRuntime(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L265)。

在这个函数中,JobCompleter().Complete()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L280)会继续对逻辑图做几轮修改优化,补全 Runtime 执行所需要的附加信息,Compiler().Compile()(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L285)将逻辑图转为分设备的物理图,并继续对Plan进行修改优化。

Plan的编译是在master节点进行的(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L282)。master节点会将Plan通过gRPC推送给各个worker节点(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L308),worker节点从master拉取物理计算图(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L310)。

之后调用NewRuntimeBuffers创建Buffer对象(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L322),Buffer应该是主要用于进程内的信息同步。

然后就准备初始化运行时了。

示例代码生成的compiled_job和物理图Plan的json参见附录。

最终生成的compiled逻辑图如下。框架自动插入了很多系统控制节点。

8f0b572fdacaa177d5038eaadc5d2faf.png


5.6 Plan的结构

示例代码输出的Plan json数据见附录。

Plan在逻辑上和compiled_job是等价的。这里主要关注task/op之间的关系。

Plan.task中的每个元素是一个task,其中的exec_sequence.exec_node对应job中的op,通常只有一个op(数组可以支持sub graph)。

exec_node.kernel_conf.op_attribute描述了op信息。其中op_conf包含op name信息。

kernel_conf.op_attribute.op_conf就是Job中的OperatorConf。

kernel_conf.op_attribute.arg_signature.bn_in_op2lbi体现了task/op之间的连接关系。

bn_in_op就是blob name in op,即op输入的blob name。

以System-AutoTick-DstSubsetTick_21为例

{"out": {"op_name": "System-AutoTick-DstSubsetTick_21","blob_name": "out"},"in_0": {"op_name": "System-EagerCriticalSection-Interface-End-Tick-19","blob_name": "out"},"in_1": {"op_name": "System-AutoTick-SrcSubsetTick_20","blob_name": "out"}
}

exec_node.bn_in_op2regst_desc_id在task层面体现了连接关系。这个map中的key表示输入输出,value是register id。

{
"out": "29",
"in_0": "27",
"in_1": "28"
}

task.produced_regst_desc描述了对应task生产的register,consumer_task_id是消费者,

produced_regst_desc.out.regst_desc_type.data_regst_desc.lbi2blob_desc.lbi就是这个register的logic blob id。

task.consumed_regst_desc_id描述了对应task消费的register信息

运行时的初始化

NNGraph::CompileAndInitRuntime中,new Runtime这行代码会初始化运行时(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/framework/nn_graph.cpp#L331)。主要做的事情包括:

  • 创建Thread

  • 通知Thread创建Actor,Actor会创建Regst和Kernel

  • 给没有输入的source_tasks发送启动信号kStart

6.1 Runtime创建Thread

在Runtime的构造函数中,DumpThreadIdsFromPlan(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L65)会将Plan中属于当前进程的task的thread id存入thread_ids_变量。AddThreads创建这些Thread对象(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L69)。

Thread在构造时会创建一个物理线程( https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/thread/thread.cpp#L39),线程执行的是PollMsgChannel方法(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/thread/thread.cpp#L44),Thread就是在这里持续等待需要处理的新消息。

Thread只处理两类命令消息:线程终止消息,创建Actor的消息。其它消息交给Actor::ProcessMsg处理(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/thread/thread.cpp#L83)。

6.2 Runtime通知Thread创建Actor

在Runtime的构造函数中,tasks被分为两类:source_tasks和other_tasks。在示例代码中,source_tasks(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L84-L85)是没有输入边的task。

从代码逻辑看,在Plan proto中,task的consumed_regst_desc_id字段是一个map。如果这个map的所有key都是in_ctrl(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L54),这个task就是source_tasks。

一些source_tasks的示例如下:

  • System-Src-WaitAndSendIds_16

  • System-AutoTick-AppendDeviceTick_9

  • System-EagerCriticalSection-Interface-End-Tick-19

  • System-EagerCriticalSection-Interface-End-Tick-25

Runtime调用HandoutTasks函数(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L100-L101)会给ActorMsgBus发送构建Actor的kConstructActor消息(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/runtime.cpp#L49)。

6.3 ActorMsgBus和Thread的消息处理

从接口看,ActorMsgBus (https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor_message_bus.cpp#L24)负责消息的发送(Actor通过ActorMsgBus发送消息),Thread::PollMsgChannel(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/thread/thread.cpp#L60) 负责消息的接收和处理。

相关实体的协作关系如下

  • Actor是自调度的基本单元,接受消息然后工作,工作完后再继续发送消息。

    • actor_id就是task_id,是在编译Plan时就确定的。task是编译时概念,actor是对等的运行时概念。

    • task_id有特定的编码格式(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/graph/task_id.cpp#L21-L29),从中可以解析出machine_id(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/graph/task_id.cpp#L73)和thread_id(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/graph/task_id.cpp#L77)。

    • 在跨网络的整个物理图Plan中,actor id相当于地址,通过它可以定位唯一的actor实体。

  • Actor 通过 ActorMsgBus::SendMsg(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor_message_bus.cpp#L24) 发送 ActorMsg(https://github.com/Oneflow-Inc/oneflow/blob/4856d691051accd72f13f4139d281e411977b297/oneflow/core/lazy/actor/actor_message.h#L34) 消息。

    • ActorMsg包含源和目的actor id(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor_message.h#L84-L85)。

    • 如果是进程内通讯(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor_message_bus.cpp#L26),将通过 ActorMsgBus::SendMsgWithoutCommNet (https://github.com/Oneflow-Inc/oneflow/blob/4856d691051accd72f13f4139d281e411977b297/oneflow/core/lazy/actor/actor_message_bus.cpp#L49)把 ActorMsg 朝目的 actor 所在的 thread 入队消息(https://github.com/Oneflow-Inc/oneflow/blob/4856d691051accd72f13f4139d281e411977b297/oneflow/core/thread/thread.h#L40)。

    • Thread::EnqueueActorMsg 会判断当前 thread 是否是 actor thread,如果是则入本地队列,否则则入 actor thead 的 channel 队列。

    • 如果ActorMsg是跨进程消息,ActorMsgBus通过CommNet发送消息(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor_message_bus.cpp#L42-L44),接收方的CommNet应该会根据actor id获得线程id,从ThreadMgr查到Thread,将消息交给Thread处理。

  • Thread::PollMsgChannel(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/thread/thread.cpp#L60) 负责消息的接收和处理。

    • 如果线程本地队列local_msg_queue_为空,则从thread的channel队列中取出全部ActorMsg放入本地队列(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/thread/thread.cpp#L63)。

    • 从本地队列中取出一个ActorMsg,然后开始处理。

    • 处理一些特殊的kCmdMsg消息(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/thread/thread.cpp#L67-L79),然后普通消息交给Actor自行处理(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/thread/thread.cpp#L83)。

  • Actor收到消息后,会判断是否满足了Act的条件,如果满足,则会执行Act,从而调用LaunchKernel执行计算,Act执行结束后通过ActorMsgBus发消息通知上下游Actor。

这些对象之间的消息传递关系如下图所示

806204607cdd45cecd5502258a28a0f2.png

6.4 激活source Actor

目前的实现中,Actor全部是自调度的,只能接受来自其他Actor的消息。Actor中有一类比较特殊的source actors,它们与source tasks对应。

source actors 没有上游 actor,它们会朝下游actor发送消息从而激活所有的Actor运行。

source actors 本身是如何执行的呢?它们在接受到 kStart 消息后就会一直 Act 直到进入退出流程。但是其 kernel 会阻塞在 Buffer(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/common/buffer.h#L26) 处,一直等待其他线程往 buffer 中添加数据后,阻塞会被激活,然后 kernel 执行读取,kernel 完成后,actor 的 Act 结束,往下游发送消息。

source actors 由于会发生阻塞,所以其必须有单独的 actor thread。

Runtime 初始化的的最后一步就是朝各 source actors 发送 kStart 消息用以激活它们,但 source actors 只有接受到 buffer 的数据后才会往下执行,然后朝下游 actors 发送消息,使所有的 actors 都执行起来。

Actor

7.1 Actor的创建

Thread在创建Actor时,会先尝试创建为LightActor(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L104),如果不成功,再尝试用预先注册的工厂创建Actor。

有几种TaskType可以用于LightActor(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/light_actor.cpp#L677-L689):

  • kNormalForward,比如matmul、add等user op。

  • kCopyHd

  • kTick

  • kCollectiveBoxingGeneric

目前大约有20多种Actor的子类型。其它Actor类型根据TaskType(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/job/task.proto#L8)预先注册。例如WaitAndSendIdsActor。

示例代码的各个节点对应的actor类型参见附录。

Actor相关的类关系如下(包含关系只是表示可以访问到相关信息,并不意味着创建或着拥有该类型对象)

2a41b458ec9cc328534b4029eee0af92.png

7.2 Actor的初始化

Actor的构造函数一般都是空的,构建之后需要执行Init(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L129)函数进行初始化。

LightActor继承自ActorBase,不是Actor的子类,有自己的Init函数实现。这里只讨论Actor的初始化。

在Actor::Init(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L129)中,首先调用ConstructKernel(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L138)创建kernel实例。和Operator类似,kernel也是以OpTypeCase作为注册的key,例如WaitAndSendIdsKernel(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/kernel/wait_and_send_ids_kernel.cpp#L51)。一个Actor通常只有一个kernel。

之后调用NewRegsts创建Regst(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L152)。Tensor是用户侧的概念。对应的运行时概念是Regst(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/register/register.h#L24),它持有Kernel需要读写的内存。Regst的概念比Tensor更宽泛,比如框架自动添加的控制Op也会用到Regst。

Actor将自己创建的Regst保存到produced_regsts_(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L153)。

TakeOverNaiveConsumed(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L182)只记录需要消费的regst id,但并不push到consumed_regsts_。

TakeOverNaiveProduced(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L183)既记录生产的regst id,也push到naive_produced_rs_(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L249)。这种区别是为了首次执行计算时,actor能顺利执行。后面分析Actor的消息处理时会再回过头来讨论一下。

调用InitBnInOp2BlobInfo会初始化BlobInfo(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L184)。

之后就是调用VirtualActorInit(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.cpp#L185),这里允许各个Actor子类定制自己的初始化逻辑。通常会调用OF_SET_MSG_HANDLER宏(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.0/oneflow/core/lazy/actor/actor.h#L76-L80)设置Actor的消息处理函数。

7.3 Actor的消息处理

LightActor 首先会根据消息类型分别处理 kRegstMsg 和 kEordMsg 消息。HandleRegstMsg(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/lazy/actor/light_actor.cpp#L424) 中根据 RegstMsg 的 type (kProduced 或 kComsumed) 来分别处理各种读写状态计数。

然后判断读写计数是否达到了判断条件,如果达到了意味着满足了读写 regst 的条件,然后就 执行 ActOnce(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/lazy/actor/light_actor.cpp#L451)。

LightActor::ActOnce 会在第一次执行时去 InitBnInOp2Blob 和 InitActMsg。InitBnInOp2Blob 初始化 resgt 中的 bn 与 Blob 的映射关系,为 kernel 提供通过 bn 访问 Blob 的功能。InitActMsg 会初始化好所有需要发送的消息避免后继发消息时重复的构建消息。

然后就是 LaunchKernel,接着会 ResetState 重置 regst 状态。

LaunchKernel 后就会把之前构建好的消息发送出去,同步消息会直接入队 thread 消息队列,异步消息通过 callback 发送到 ActorMsgBus。

普通 Actor::ProcessMsg 会调用 msg handler 来处理消息,最常见的 msg handler 就是 Actor::HandlerNormal(https://github.com/Oneflow-Inc/oneflow/blob/release/v0.8.1/oneflow/core/lazy/actor/actor.cpp#L329)。

Actor::HandlerNormal 中流程跟 LightActor 中类似,会根据不同的 regst 类型来分别处理,Actor 中对 regst 的状态管理方式与 LightActor 不同,LightActor 中的方式更加高效,Actor 中能处理一些特殊情况。

消息处理完毕后,就会调用 ActUntilFail,ActUntilFail 会判断 IsReadReady 和 IsWriteReady 来决定是否可以进行 Act。

最常见的 NaiveActor::Act() 就是执行 AsyncLaunchKernel。

Act 完成后,就开始朝上下游发送 regst 消息。

还有一些特殊的 Actor,我们以WaitAndSendIdsActor为例,观察一下这类Actor的消息处理机制。

之所以选择这个例子,一是这个Actor比较简单;二是这是一个典型的source task,想看一下计算图是怎么被触发启动计算的。

Thread收到的消息如果不是kStopThread或kConstructActor,就调用Actor::ProcessMsg(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L83),将消息转给Actor处理。

ProcessMsg函数只是简单的将消息转给handler处理(https://github.com/Oneflow-Inc/oneflow/blob/b6bf3f8843679111eb1edf79deefce814d250f4e/oneflow/core/lazy/actor/actor.h#L38)。

WaitAndSendIdsActor::VirtualActorInit中,handler被设置为HandlerWaitToStart(https://github.com/Oneflow-Inc/oneflow/blob/22f70a1719f371a54512633bb92086580d9c3c89/oneflow/core/lazy/actor/wait_and_send_ids_actor.cpp#L53)。

Runtime的构造函数中,发送的第一批消息是给source_tasks的kStart消息,这个消息就由HandlerWaitToStart函数处理。

HandlerWaitToStart校验消息类型后,将handler设置为HandlerNormal(https://github.com/Oneflow-Inc/oneflow/blob/b17a9cd6b930b5817c63623fb682bd708377a93b/oneflow/core/job/runtime.cpp#L109)(这也是大部分Actor的默认handler),然后调用ProcessMsg(https://github.com/Oneflow-Inc/oneflow/blob/22f70a1719f371a54512633bb92086580d9c3c89/oneflow/core/lazy/actor/wait_and_send_ids_actor.cpp#L74),实际就是调用新设置的handler HandlerNormal。

HandlerNormal中,如果是kCmdMsg,只允许是kStart(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L377)。通过消息类型校验后,会直接调用ActUntilFail(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L378)。

7.4 Act执行的条件

LightActor 和 Actor 判断能否进行 Act 采用了不同的策略,LightActor 的效率更高,Actor 能处理一些特殊情况。

对于 LightActor,当在读的register计数 total_reading_cnt_ 归 0,可消费的register计数 ready_consumed_ 增加到 max_ready_consumed_,前者表示所有的消费者已经读取当前 LightActor 的 Regst,后者表示当前 LightActor 消费的所有 Regst 已经到达(由上游发送的 Regst 消息)。

对于 Actor,Actor::ActUntilFail中,Act方法(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L424)是各个子类自己实现的,一般主要是启动kernel计算。

但是在执行Act之前,需要先确认:

  • Act执行依赖的数据是否都已经就绪?(IsReadReady)

  • Act生产出来的数据,消费方是否已经用完、并收到ack消息确认?(IsWriteReady)

Actor有4个与此相关的成员变量

  • RegstSlot naive_produced_rs_;

  • RegstSlot inplace_produced_rs_;

  • RegstSlot naive_consumed_rs_;

  • RegstSlot inplace_consumed_rs_;

xx_produced_rs_存储的是当前Actor的下游consumer返回的、已经使用完毕的ack regst信息。(当前Actor生产的Regst存储在produced_regsts_中。)

运行时在初始化的过程中,所有Actor都没有运行过,任何Actor都不可能收到ack消息,所以在Actor初始化时,要预先填充xx_produced_rs_,这样才能保证Actor在首次运行前是WriteReady的,才能顺利启动执行。

xx_consumed_rs_存储的是上游依赖发来的数据。它不需要预先填充。因为source_tasks没有输入依赖,自然就是ReadReady的;而xx_produced_rs_在初始化时的预先填充又保证它是WriteReady的,所以source_tasks可以直接运行。source_tasks的输出消息发给下游,下游也会变为ReadReady,而下游在初始化后也保证是WriteReady的。整个Actor系统就可以这样运转起来了。

7.5 Actor上下游之间的通知机制

Act执行完毕后,需要将结果数据发给下游consumer。以 WaitAndSendIds 的 Naive Produced 为例,ActUntilFail中的调用流程如下:

  • AsyncSendNaiveProducedRegstMsgToConsumer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L427

    • VirtualAsyncSendNaiveProducedRegstMsgToConsumer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L441

      • HandleProducedNaiveDataRegstToConsumer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L446

        • HandleRegstToConsumer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L577)

          • EnqueueAsyncMsg(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L523

            • 如果目标线程是当前线程,ActorMsgBus::SendMsg(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L662

            • 否则,将消息加入async_msg_queue_(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L664

          • 增加 total_reading_cnt_(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L526)(这个变量表示已经发消息给下游、但未收到的ack数量)

        • naive_produced_rs_.PopFrontRegsts(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L581

    • AsyncSendProducedCtrlRegstMsgToConsumer

注意naive_produced_rs_.PopFrontRegsts(https://github.com/Oneflow-Inc/oneflow/blob/06a6af1c7f760ba4b12d2dfb8f73d7fda5c7dbab/oneflow/core/lazy/actor/register_slot.cpp#L53)会将Regst指针从队列中删掉,相应的可用(https://github.com/Oneflow-Inc/oneflow/blob/06a6af1c7f760ba4b12d2dfb8f73d7fda5c7dbab/oneflow/core/lazy/actor/register_slot.cpp#L49)register计数减1(https://github.com/Oneflow-Inc/oneflow/blob/06a6af1c7f760ba4b12d2dfb8f73d7fda5c7dbab/oneflow/core/lazy/actor/register_slot.cpp#L49)。

而在Actor::HandlerNormal中处理收到的kRegstMsg消息(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L340)时,如果是consumer发来的ack消息,会调用TryUpdtStateAsProducedRegst(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L355),将Regst再添加到 naive_produced_rs_ 中(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L654),以保证当前Actor在收到所有ack后是WriteReady的;同时递减在读的 register 计数total_reading_cnt_。

Actor对依赖的上游消息的处理是类似的。通过以下函数调用给上游发送ack消息、通知 register 已经用完,可以继续更新了:

  • AsyncSendNaiveConsumedRegstMsgToProducer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L431

  • AsyncRetInplaceConsumedRegstIfNoConsumer(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L432)在Actor::HandlerNormal中收到kRegstMsg消息后,将消息添加到consumed_rs_(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L344),以保证当前Actor在收到所有依赖数据后是ReadReady的。

LightActor有自己的消息处理机制(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/light_actor.cpp#L299),大致原理应该是差不多的。

7.6 Act执行的动作

根据上述讨论,Actor收到kRegstMsg后也会进入ActUntilFail执行。如果读写都是Ready,就执行Act(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L424)。以WaitAndSendIdsActor为例,主要调用链路如下:

  • AsyncLaunchKernel(https://github.com/Oneflow-Inc/oneflow/blob/22f70a1719f371a54512633bb92086580d9c3c89/oneflow/core/lazy/actor/wait_and_send_ids_actor.cpp#L58

  • ek.kernel->Launch(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L562),启动Kernel计算

  • Forward(https://github.com/Oneflow-Inc/oneflow/blob/eae9ff38f074479d79ce24b0f6e0594f82126171/oneflow/core/kernel/kernel.cpp#L52

  • ForwardDataContent(https://github.com/Oneflow-Inc/oneflow/blob/eae9ff38f074479d79ce24b0f6e0594f82126171/oneflow/core/kernel/kernel.cpp#L65

  • buffer->Pull(https://github.com/Oneflow-Inc/oneflow/blob/b17a9cd6b930b5817c63623fb682bd708377a93b/oneflow/core/kernel/wait_and_send_ids_kernel.cpp#L40

  • 给regst的存储地址mut_dptr赋值(https://github.com/Oneflow-Inc/oneflow/blob/b17a9cd6b930b5817c63623fb682bd708377a93b/oneflow/core/kernel/wait_and_send_ids_kernel.cpp#L47


buffer->Pull会等待条件变量的通知(https://github.com/Oneflow-Inc/oneflow/blob/49f60e682518436dfeb37344a15902a959e0e4f2/oneflow/core/common/buffer.h#L60)。现在,看上去所有Actor都已准备就绪,只等发令枪一响就开跑了。

启动静态图的计算

Graph.__run(https://github.com/Oneflow-Inc/oneflow/blob/81edd938826a7ea903174d682348847658b64653/python/oneflow/nn/graph/graph.py#L226)会扣动发令枪的板机,启动计算图的一轮计算。

主要调用流程如下:

  • RunLazyNNGraph(https://github.com/Oneflow-Inc/oneflow/blob/81edd938826a7ea903174d682348847658b64653/python/oneflow/nn/graph/graph.py#L1076

  • builder->LaunchLazyJob(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/nn_graph.cpp#L568

  • LaunchLazyJobInstructionType(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/instructions_builder.cpp#L179

  • Buffer::Push(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/instructions_builder.cpp#L179

这里的Buffer::Push就是WaitAndSendIdsKernel在等待的起跑信号。

运行时的退出机制

整个运行时包含很多对象和资源,安全有序的退出是庞杂而又细致的工作。这里仅以WaitAndSendIds为例,从一个侧面观察一下运行时的退出机制。

运行时的退出始于NNGraph对象的析构(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/nn_graph.cpp#L76)。

9.1 Actor的退出

  • NNGraph在析构时,会关闭所有的Buffer对象(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/nn_graph.cpp#L82)。

  • Buffer在关闭时,会设置is_closed_ = true并通知所有监听者(https://github.com/Oneflow-Inc/oneflow/blob/49f60e682518436dfeb37344a15902a959e0e4f2/oneflow/core/common/buffer.h#L81)。但是Pull会继续处理完已经提交的计算。

    • 所以,Buffer应该是主要用于进程内的通信和异步协调的一个类。

  • WaitAndSendIdsKernel这时候正在等待新一轮计算开始(https://github.com/Oneflow-Inc/oneflow/blob/b17a9cd6b930b5817c63623fb682bd708377a93b/oneflow/core/kernel/wait_and_send_ids_kernel.cpp#L40),结果收到Pull返回的kBufferStatusErrorClosed(https://github.com/Oneflow-Inc/oneflow/blob/49f60e682518436dfeb37344a15902a959e0e4f2/oneflow/core/common/buffer.h#L61)。

  • WaitAndSendIdsActor::IsCustomizedReadReady以后就一直返回false(https://github.com/Oneflow-Inc/oneflow/blob/22f70a1719f371a54512633bb92086580d9c3c89/oneflow/core/lazy/actor/wait_and_send_ids_actor.cpp#L68),IsReadReady也返回false(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L533)。

    • 这之后,ActUntilFail只会执行异步消息发送(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L437)(不再进入while循环)

  • WaitAndSendIdsActor::HandlerNormal仍然会处理其它Actor发来的消息(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L340)。但因为IsCustomizedReadReady返回false,会进入AsyncSendEORDMsgForAllProducedRegstDesc(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L394)执行。它会给每个下游发送kEordMsg消息(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L614)。

  • Actor在收到上游发来的kEordMsg消息后,递减remaining_eord_cnt_(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L331)。

    • remaining_eord_cnt_被初始化为Actor的输入regst的数量(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L171)。

  • total_reading_cnt_是当前Actor生产的、已经发给consumer、但尚未收到ack的消息数量。

    • Actor目前仍可以正常接收consumer发来的ack消息。

  • 当上述2个变量都为0时(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L395),意味着所有上游都发出了kEordMsg消息,也收到了所有下游的ack消息。Actor就给Thread返回1(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L397)。

    • 如果上述两个变量有不为0的,就修改handler,由HandlerZombie(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/lazy/actor/actor.cpp#L399)处理后续收到的消息。

  • Thread收到Actor返回的1后(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L84),将它从自己的存储中删除(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L89),并递减运行Actor的数量。

9.2 Thread的退出

  • NNGraph重置runtime_导致运行时对象被析构(https://github.com/Oneflow-Inc/oneflow/blob/8f672eea116cae4a73bb7309e7496b08d7ec9a32/oneflow/core/framework/nn_graph.cpp#L83)。

  • Runtime删除所有Thread(https://github.com/Oneflow-Inc/oneflow/blob/b17a9cd6b930b5817c63623fb682bd708377a93b/oneflow/core/job/runtime.cpp#L117)。

  • ThreadMgr给所有Thread发送kStopThread消息(https://github.com/Oneflow-Inc/oneflow/blob/c8c6d351fa28c5ebce948d69c06670a783f83f74/oneflow/core/thread/thread_manager.cpp#L64)。同时,重置指针导致Thread析构(https://github.com/Oneflow-Inc/oneflow/blob/c8c6d351fa28c5ebce948d69c06670a783f83f74/oneflow/core/thread/thread_manager.cpp#L66)。

  • Thread的物理线程退出PollMsgChannel循环(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L68)。

  • Thread等待物理线程结束,关闭channel(https://github.com/Oneflow-Inc/oneflow/blob/55b822e4d3c88757d11077d7546981309125c73f/oneflow/core/thread/thread.cpp#L52)。

10 

分布式场景的静态图

分布式的compile_job、物理图Plan和单机场景有明显变化。

比如,每个进程都有一套WaitAndSendIds等控制节点。这也容易理解,因为每个节点都要执行__run和Buffer::Push/Pull,都要启动本进程的Actors执行计算。

matmul和broadcast_add等user op也会在两个节点进行计算。
 

082db878a0d43766c79782d31502d43f.png

10.1 示例代码

启动方式参考Global Tensor的官方文档。

import oneflow as flow
import oneflow.nn as nnP0 = flow.placement("cpu", ranks=[0, 1])
a0_sbp = flow.sbp.split(0)class ModuleMyLinear(nn.Module):def __init__(self, in_features, out_features):super().__init__()self.weight = nn.Parameter(flow.randn(in_features, out_features,placement=P0, sbp=flow.sbp.broadcast))self.bias = nn.Parameter(flow.randn(1, out_features,placement=P0, sbp=flow.sbp.broadcast))def forward(self, input):return flow.matmul(input, self.weight) + self.biaslinear_model = ModuleMyLinear(4, 3)class GraphMyLinear(nn.Graph):def __init__(self):super().__init__()# ModuleBlockself.model = linear_modeldef build(self, input):# ModuleBlock.__call__return self.model(input)graph_mylinear = GraphMyLinear()
input = flow.randn(5, 4, placement=P0, sbp=flow.sbp.split(1))
out = graph_mylinear(input)
print(out)

11 

附录

11.1 断点

11.1.1 Python断点示例

# python3 -m pdb test.py
break test.py:25
break oneflow/nn/graph/graph.py:221
break oneflow/nn/graph/graph.py:741
break oneflow/nn/graph/graph.py:745
break oneflow/nn/graph/graph.py:759
break oneflow/nn/graph/graph.py:828
break oneflow/nn/graph/graph.py:777
break oneflow/nn/graph/graph.py:1066
break oneflow/nn/graph/graph.py:1133
break oneflow/framework/graph_build_util.py:227

11.1.2 C++断点示例

启动命令

source /mnt/oneflow/build/source.sh
gdb --args python3 /mnt/oneflow/test.py
# set breakpoints
# run

断点示例

set breakpoint pending on
break oneflow::ActorMsg::BuildEordMsg
break oneflow/core/common/buffer.h:80
break oneflow::(anonymous namespace)::CheckAndConstructOp
break oneflow::WaitAndSendIdsActor::Act
break oneflow::WaitAndSendIdsActor::HandlerWaitToStart
break oneflow/core/lazy/actor/light_actor.cpp:452
break oneflow/core/lazy/actor/light_actor.cpp:485
break oneflow::ForeignInputKernel::ForwardDataContent
break oneflow::vm::LaunchLazyJobInstructionType::Compute

11.2 静态图的json表示

  • forward(https://quip.com/OMc4A0HOOr0C)

  • full(https://quip.com/JLaMAHGBLXmK)

  • compiled(https://quip.com/tXjuAiS3J0Ab)

  • plan(https://quip.com/a0DMAAIte6PQ)

11.3 actor type

naive_actor

System-AutoTick-AppendDeviceTick_9
System-AutoTick-DstSubsetTick_12
System-AutoTick-DstSubsetTick_21
System-AutoTick-DstSubsetTick_27
System-AutoTick-Prepend-DeviceTick_7
System-AutoTick-SrcSubsetTick_20
System-AutoTick-SrcSubsetTick_26
System-AutoTick-SrcSubsetTick_8
System-AutoTick-Tick_11
System-AutoTick-Tick_13
System-EagerCriticalSection-Callback-23
System-EagerCriticalSection-Callback-29
System-EagerCriticalSection-Interface-Begin-Tick-18
System-EagerCriticalSection-Interface-Begin-Tick-24
System-EagerCriticalSection-Interface-End-Tick-19
System-EagerCriticalSection-Interface-End-Tick-25
System-EagerCriticalSection-Wait-22
System-EagerCriticalSection-Wait-28

light_actor

_GraphMyLinear_0_input.0.0_2
_GraphMyLinear_0_output.0.0_2
model.bias
model-broadcast_add-1
model-matmul-0
model.weight
System-AutoTick-SinkTick_15
System-SyncAllRanksSinkTick_14

wait_and_send_ids_actor

‍‍‍
System-Src-WaitAndSendIds_16

call_back_notify_actor

‍‍‍
System-Sink-CallbackNotify_17

12 

参考资料

  • oneflow v0.8.0(https://github.com/Oneflow-Inc/oneflow/tree/release/v0.8.0)

  • OneFlow框架的系统设计(上篇)(https://zhuanlan.zhihu.com/p/337851255)

  • OneFlow框架的系统设计(中篇)(https://zhuanlan.zhihu.com/p/338699487)

  • OneFlow框架的系统设计(下篇)(https://zhuanlan.zhihu.com/p/339208452)

  • 一个Job在OneFlow中的执行过程—上篇(https://zhuanlan.zhihu.com/p/344531540)

  • 一个Job在OneFlow中的执行过程—中篇(https://zhuanlan.zhihu.com/p/355654002)

  • 一个Job在OneFlow中的执行过程—下篇(https://zhuanlan.zhihu.com/p/363689736)

  • 静态图模块 nn.Graph(https://docs.oneflow.org/master/basics/08_nn_graph.html)

  • OneFlow系统设计(https://docs.oneflow.org/v0.4.0/basics_topics/essentials_of_oneflow.html)

  • torch.nn.Module(https://pytorch.org/docs/1.10/generated/torch.nn.Module.html)

其他人都在看

  • OneFlow源码解析:自动微分机制

  • ChatGPT的一小步,NLP范式转变的一大步

  • 李白:你的模型权重很不错,可惜被我没收了

  • OpenAI掌门Sam Altman:AI下一个发展阶段

  • 32篇年度最佳AI论文;Python编译器Codon开源

  • 比快更快,开源Stable Diffusion刷新作图速度

  • OneEmbedding:单卡训练TB级推荐模型不是梦

欢迎Star、试用OneFlow最新版本:GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient. - GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.https://github.com/Oneflow-Inc/oneflow/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/27742.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

infer源码阅读之yolo.cu

目录 yolo.cu注意事项一、2023/3/30更新前言1.宏定义2.Norm3.后处理3.1 affine_project3.2 decode3.2.1 decode_common3.2.2 decode_v8 3.3 nms3.4 invoker 4.预处理5.decode_mask6.AffineMatrix7.InferImpl7.1 adjust_memory7.2 preprocess7.3 load7.4 forwards 8.其它9.拓展之…

【爬虫实例】从B站和某论文网站分析python爬虫的一般编写思路———To someone

问题背景 好久没写爬虫了,前两天友人来问我python能不能爬论文,能不能告诉她爬虫的基本运行原理是什么,跑起来是什么样子。 我一看,论文爬取——爬虫最实用的场景之一,这不拿捏? 于是便尝试现场演示一番。…

【Metaverse系列一】元宇宙的奥秘

你有没有想过逃离闷热的会议室,瞬间移动到马尔代夫的沙滩上开会?开完会,纵身跳入大海和美人鱼捉迷藏。然后一个鲤鱼打挺直冲云霄,进入天宫一号开展科学研究,发现微重力环境下韭菜的长势喜人,而且在特定光照…

科大讯飞版ChatGPT测评:很好很强大

大家好,我是黄海广。 今天我体验到了科大讯飞版本的ChatGPT,这个产品凭借其强大的功能和出色的性能,超出了我对国产大模型的预期。 一、产品简介 这个模型全名叫讯飞星火认知大模型,官方是这么解释这个产品的: “科大讯…

七大语言模型PK,ChatGPT内容基线测评稳居第一

随着ChatGPT的爆火与流行,大型语言模型(LLM)与生成式人工智能(AIGC)不断跃入大众视野,随之也带来了许多内容风险隐患。 近日,知道创宇内容安全专家对互联网上流行的7款大型语言模型进行了全面和…

ChatGPT风靡全球,我们应该为未来感到担心吗?

近期,关于ChatGPT的话题再次引爆全网,不少用户加入到“玩疯了”的阵营中……有赞叹不已的、有表示惊奇的、有展示BUG的,但总体来说,ChatGPT的整体社交评价还是非常向好的。 微软CEO纳德拉就坦言,ChatGPT服务的风靡&…

库克考虑卸任苹果CEO,谁会是下一任接班人?

作者 | Carol 出品 | CSDN(ID:CSDNnews) 十年前,8 月 24 日那天,史蒂夫乔布斯宣布辞去苹果 CEO 一职,正式任命蒂姆库克成为苹果的新掌门人。如今,恰逢库克上任苹果 CEO 十周年。在今年4月份&…

历史上的今天:乔布斯辞去苹果CEO一职;Windows 95 发布

整理 | 王启隆 透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。 今天是 2022 年 8 月 24 日,14 年前的今天,北京奥运会闭幕式举办,宣告圆满结束,为所有国人打上了一针强心剂。而在科技历史上&am…

最全盘点苹果高管团队 谁有可能成为下一代继任者?

编译|辰辰 出品|网易智能 随着苹果首席执行官蒂姆库克(Tim Cook)已经掌舵10年,他和高级副手们正将更多注意力放在培养公司新一代领导者上。 目前库克管理团队的成员都是在苹果工作20多年,已经赚到数千万美元、年龄在55岁至60岁左右…

行业报告 | AI+制造业赋能,机器视觉开启掘金新大陆(上)

原创 | 文 BFT机器人 01 核心要点 Al制造业赋能,META 发布 SAM 助力机器视觉迎来 GPT 时刻。 机器视觉技术使得工业设备能够“看到”它正在进行的操作并进行快速决策,完整机器视觉系统由硬件软件组成,分别进行成像和图像处理工作。 目前,以“…

2024奇点将至:人类尚未准备好迎接S2F大于100的巨硬资产

奇点将至! “奇点”这个词儿本来是搞人工智能的那帮人从物理学里借用过来唬人的。大意是指所谓AGI通用人工智能降临的那一刻。计算机将在智能上全方位碾压人类。 最近ChatGPT的爆红让无数人产生了错觉,真的彷佛看到AGI近在咫尺。 但是,穿越过…

chatGPT人工智能对话系统H5写作论文毕业论文付费问答写代码分销

ChatGPT对话问答系统是一款功能神奇的人工智能应用,具有广泛的用途和应用领域。以下是一些主要功能: 编写代码:ChatGPT可以帮助用户编写和理解各种编程语言的代码。撰写文案、论文、小说:ChatGPT能够根据用户的需求,协…

万字长文爆肝 DNS 协议!

试想一个问题,我们人类可以有多少种识别自己的方式?可以通过身份证来识别,可以通过社保卡号来识别,也可以通过驾驶证来识别,尽管我们有多种识别方式,但在特定的环境下,某种识别方法可能比另一种…

万字长文详细搞懂 volatile 关键字

volatile 这个关键字大家都不陌生,这个关键字一般通常用于并发编程中,是 Java 虚拟机提供的轻量化同步机制,你可能知道 volatile 是干啥的,但是你未必能够清晰明了的知道 volatile 的实现机制,以及 volatile 解决了什么…

李开复们混战AI,谁最有戏?

作者 | 王敏 编辑 | 金玙璠 2023年以来,AI“狂飙”。 ChatGPT一经问世,就掀起了新一轮AI革命。过去的几个月里,AI领域重磅“炸弹”一个接着一个。从业者们常常一觉醒来,就会因为硅谷发布新的AI产品而不得不快速更新认知。 伴随着热…

独家 前美团联合创始人王慧文“正在收购”国产AI框架OneFlow,光年之外欲添新大将

以ChatGPT为代表的AI大模型是2023年的科技C位。 2023年3月27日,ChatGPT引发的“抓马连续剧”,又有新剧更新。 前情提要: 前美团联合创始人、高级副总裁王慧文发文宣布进入AI领域,称将打造中国的OpenAI。 新闻标题一&#xff1…

笑死!这个插件太绝了;AI开发者如何稳赚这一波;MidJourney完完完全手册;零经验开发儿时3D游戏 | ShowMeAI日报

👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 🤖 『文心一言员工跳槽工资翻倍』猎头:百万年薪很正常 最近一段时间,百度文心大模型团队内的研发人员受到其他公司…

AI大爆发却找不到工作?因为学校里教的和它关系不大

Datawhale干货 最新:就业趋势,来源:新智元 【导读】猎聘大数据研究院重磅发布《AIGC就业趋势大数据报告2023》,招聘平均年薪已达40万,博士需求量同比增长超100%。 不用赘述,大家都知道,最近半年…

平替!0门槛克隆ChatGPT!30分钟训完,60亿参数性能堪比GPT-3.5

新智元报道 编辑:编辑部 【新智元导读】破解「CloseAI」,ChatGPT克隆羊问世!0门槛实现「自研」,从此大语言模型不再只是少数大公司的「金手指」。 此前,OpenAI不Open的事件,已经引发了坊间的诸多争议。 光…

当AI成为专家,要警惕话语权威的力量

来源:混沌巡洋舰 今年2月份,有一则颇具科幻色彩的新闻,美国最大的科幻杂志,由于收到太多由AI生成的稿件,导致编辑无法处理而暂停投稿通道。对此,必然的选项是由AI进行审稿。这一步一旦迈开,就意…